diff --git a/README.md b/README.md index 283f8cb..62680fd 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,13 @@ bring your production DB up to sync with the code you are deploying. Run it like `./dbutils --help` to see all options. The config file that this works on is the same that is given to "api", "services" and "frontend". +## formatfiles + +A simple wrapper frontend to black, the formatter used on this project. Running this will +auto-format all of the python code that might need formatting, leaving the rest out. When +submitting pull requests make sure to run this so that your code conforms to the style +used by this project! + ## frontend Development version of a frontend server allowing for account and server administration @@ -722,7 +729,8 @@ up your MySQL instance, see the `examples/` directory. Contributions are welcome! Before submitting a pull request, ensure that your code is type-hint clean by running `./verifytyping` and ensure that it hasn't broken basic libraries with `./verifylibs`. Make sure that it is also lint-clean with `./verifylint`. -If you are changing code related to a particular game, it is nice to include a +You should also make sure its formatted correctly by running `./formatfiles`. +If you are changing code related to a particular game, it is required to include a verification in the form of a game traffic emulator, so that basic functionality can be verified. To ensure you haven't broken another game with your changes, its recommended to run the traffic generator against your code with various games. For convenience, you diff --git a/bemani/api/app.py b/bemani/api/app.py index 009acaf..9576ab6 100644 --- a/bemani/api/app.py +++ b/bemani/api/app.py @@ -6,22 +6,25 @@ from flask import Flask, abort, request, Response from functools import wraps from bemani.api.exceptions import APIException -from bemani.api.objects import RecordsObject, ProfileObject, StatisticsObject, CatalogObject +from bemani.api.objects import ( + RecordsObject, + ProfileObject, + StatisticsObject, + CatalogObject, +) from bemani.api.types import g from bemani.common import GameConstants, APIConstants, VersionConstants from bemani.data import Config, Data -app = Flask( - __name__ -) +app = Flask(__name__) config = Config() -SUPPORTED_VERSIONS: List[str] = ['v1'] +SUPPORTED_VERSIONS: List[str] = ["v1"] -def jsonify_response(data: Dict[str, Any], code: int=200) -> Response: +def jsonify_response(data: Dict[str, Any], code: int = 200) -> Response: return Response( - json.dumps(data).encode('utf8'), + json.dumps(data).encode("utf8"), content_type="application/json; charset=utf-8", status=code, ) @@ -35,15 +38,15 @@ def before_request() -> None: g.data = Data(config) g.authorized = False - authkey = request.headers.get('Authorization') + authkey = request.headers.get("Authorization") if authkey is not None: try: - authtype, authtoken = authkey.split(' ', 1) + authtype, authtoken = authkey.split(" ", 1) except ValueError: authtype = None authtoken = None - if authtype.lower() == 'token': + if authtype.lower() == "token": g.authorized = g.data.local.api.validate_client(authtoken) @@ -59,7 +62,7 @@ def after_request(response: Response) -> Response: @app.teardown_request def teardown_request(exception: Any) -> None: - data = getattr(g, 'data', None) + data = getattr(g, "data", None) if data is not None: data.close() @@ -69,11 +72,12 @@ def authrequired(func: Callable) -> Callable: def decoratedfunction(*args: Any, **kwargs: Any) -> Response: if not g.authorized: return jsonify_response( - {'error': 'Unauthorized client!'}, + {"error": "Unauthorized client!"}, 401, ) else: return func(*args, **kwargs) + return decoratedfunction @@ -81,27 +85,30 @@ def jsonify(func: Callable) -> Callable: @wraps(func) def decoratedfunction(*args: Any, **kwargs: Any) -> Response: return jsonify_response(func(*args, **kwargs)) + return decoratedfunction @app.errorhandler(Exception) def server_exception(exception: Any) -> Response: - stack = ''.join(traceback.format_exception(type(exception), exception, exception.__traceback__)) + stack = "".join( + traceback.format_exception(type(exception), exception, exception.__traceback__) + ) print(stack) try: g.data.local.network.put_event( - 'exception', + "exception", { - 'service': 'api', - 'request': request.url, - 'traceback': stack, + "service": "api", + "request": request.url, + "traceback": stack, }, ) except Exception: pass return jsonify_response( - {'error': 'Exception occured while processing request.'}, + {"error": "Exception occured while processing request."}, 500, ) @@ -109,7 +116,7 @@ def server_exception(exception: Any) -> Response: @app.errorhandler(APIException) def api_exception(exception: Any) -> Response: return jsonify_response( - {'error': exception.message}, + {"error": exception.message}, exception.code, ) @@ -117,7 +124,7 @@ def api_exception(exception: Any) -> Response: @app.errorhandler(500) def server_error(error: Any) -> Response: return jsonify_response( - {'error': 'Exception occured while processing request.'}, + {"error": "Exception occured while processing request."}, 500, ) @@ -125,7 +132,7 @@ def server_error(error: Any) -> Response: @app.errorhandler(501) def protocol_error(error: Any) -> Response: return jsonify_response( - {'error': 'Unsupported protocol version in request.'}, + {"error": "Unsupported protocol version in request."}, 501, ) @@ -133,7 +140,7 @@ def protocol_error(error: Any) -> Response: @app.errorhandler(400) def bad_json(error: Any) -> Response: return jsonify_response( - {'error': 'Request JSON could not be decoded.'}, + {"error": "Request JSON could not be decoded."}, 500, ) @@ -141,7 +148,7 @@ def bad_json(error: Any) -> Response: @app.errorhandler(404) def unrecognized_object(error: Any) -> Response: return jsonify_response( - {'error': 'Unrecognized request game/version or object.'}, + {"error": "Unrecognized request game/version or object."}, 404, ) @@ -149,50 +156,50 @@ def unrecognized_object(error: Any) -> Response: @app.errorhandler(405) def invalid_request(error: Any) -> Response: return jsonify_response( - {'error': 'Invalid request URI or method.'}, + {"error": "Invalid request URI or method."}, 405, ) -@app.route('/', methods=['GET', 'POST']) +@app.route("/", methods=["GET", "POST"]) @authrequired def catch_all(path: str) -> Response: abort(405) -@app.route('/', methods=['GET', 'POST']) +@app.route("/", methods=["GET", "POST"]) @authrequired @jsonify def info() -> Dict[str, Any]: requestdata = request.get_json() if requestdata is None: - raise APIException('Request JSON could not be decoded.') + raise APIException("Request JSON could not be decoded.") if requestdata: - raise APIException('Unrecognized parameters for request.') + raise APIException("Unrecognized parameters for request.") return { - 'versions': SUPPORTED_VERSIONS, - 'name': g.config.name, - 'email': g.config.email, + "versions": SUPPORTED_VERSIONS, + "name": g.config.name, + "email": g.config.email, } -@app.route('///', methods=['GET', 'POST']) +@app.route("///", methods=["GET", "POST"]) @authrequired @jsonify def lookup(protoversion: str, requestgame: str, requestversion: str) -> Dict[str, Any]: requestdata = request.get_json() - for expected in ['type', 'ids', 'objects']: + for expected in ["type", "ids", "objects"]: if expected not in requestdata: - raise APIException('Missing parameters for request.') + raise APIException("Missing parameters for request.") for param in requestdata: - if param not in ['type', 'ids', 'objects', 'since', 'until']: - raise APIException('Unrecognized parameters for request.') + if param not in ["type", "ids", "objects", "since", "until"]: + raise APIException("Unrecognized parameters for request.") args = copy.deepcopy(requestdata) - del args['type'] - del args['ids'] - del args['objects'] + del args["type"] + del args["ids"] + del args["objects"] if protoversion not in SUPPORTED_VERSIONS: # Don't know about this protocol version @@ -201,13 +208,13 @@ def lookup(protoversion: str, requestgame: str, requestversion: str) -> Dict[str # Figure out what games we support based on config, and map those. gamemapping = {} for (gameid, constant) in [ - ('ddr', GameConstants.DDR), - ('iidx', GameConstants.IIDX), - ('jubeat', GameConstants.JUBEAT), - ('museca', GameConstants.MUSECA), - ('popnmusic', GameConstants.POPN_MUSIC), - ('reflecbeat', GameConstants.REFLEC_BEAT), - ('soundvoltex', GameConstants.SDVX), + ("ddr", GameConstants.DDR), + ("iidx", GameConstants.IIDX), + ("jubeat", GameConstants.JUBEAT), + ("museca", GameConstants.MUSECA), + ("popnmusic", GameConstants.POPN_MUSIC), + ("reflecbeat", GameConstants.REFLEC_BEAT), + ("soundvoltex", GameConstants.SDVX), ]: if constant in g.config.support: gamemapping[gameid] = constant @@ -216,73 +223,77 @@ def lookup(protoversion: str, requestgame: str, requestversion: str) -> Dict[str # Don't support this game! abort(404) - if requestversion[0] == 'o': + if requestversion[0] == "o": omnimix = True requestversion = requestversion[1:] else: omnimix = False - version = { - GameConstants.DDR: { - '12': VersionConstants.DDR_X2, - '13': VersionConstants.DDR_X3_VS_2NDMIX, - '14': VersionConstants.DDR_2013, - '15': VersionConstants.DDR_2014, - '16': VersionConstants.DDR_ACE, - }, - GameConstants.IIDX: { - '20': VersionConstants.IIDX_TRICORO, - '21': VersionConstants.IIDX_SPADA, - '22': VersionConstants.IIDX_PENDUAL, - '23': VersionConstants.IIDX_COPULA, - '24': VersionConstants.IIDX_SINOBUZ, - '25': VersionConstants.IIDX_CANNON_BALLERS, - '26': VersionConstants.IIDX_ROOTAGE, - }, - GameConstants.JUBEAT: { - '5': VersionConstants.JUBEAT_SAUCER, - '5a': VersionConstants.JUBEAT_SAUCER_FULFILL, - '6': VersionConstants.JUBEAT_PROP, - '7': VersionConstants.JUBEAT_QUBELL, - '8': VersionConstants.JUBEAT_CLAN, - '9': VersionConstants.JUBEAT_FESTO, - }, - GameConstants.MUSECA: { - '1': VersionConstants.MUSECA, - '1p': VersionConstants.MUSECA_1_PLUS, - }, - GameConstants.POPN_MUSIC: { - '19': VersionConstants.POPN_MUSIC_TUNE_STREET, - '20': VersionConstants.POPN_MUSIC_FANTASIA, - '21': VersionConstants.POPN_MUSIC_SUNNY_PARK, - '22': VersionConstants.POPN_MUSIC_LAPISTORIA, - '23': VersionConstants.POPN_MUSIC_ECLALE, - '24': VersionConstants.POPN_MUSIC_USANEKO, - }, - GameConstants.REFLEC_BEAT: { - '1': VersionConstants.REFLEC_BEAT, - '2': VersionConstants.REFLEC_BEAT_LIMELIGHT, - # We don't support non-final COLETTE, so just return scores for - # final colette to any network that asks. - '3w': VersionConstants.REFLEC_BEAT_COLETTE, - '3sp': VersionConstants.REFLEC_BEAT_COLETTE, - '3su': VersionConstants.REFLEC_BEAT_COLETTE, - '3a': VersionConstants.REFLEC_BEAT_COLETTE, - '3as': VersionConstants.REFLEC_BEAT_COLETTE, - # We don't support groovin'!!, so just return upper scores. - '4': VersionConstants.REFLEC_BEAT_GROOVIN, - '4u': VersionConstants.REFLEC_BEAT_GROOVIN, - '5': VersionConstants.REFLEC_BEAT_VOLZZA, - '5a': VersionConstants.REFLEC_BEAT_VOLZZA_2, - '6': VersionConstants.REFLEC_BEAT_REFLESIA, - }, - GameConstants.SDVX: { - '1': VersionConstants.SDVX_BOOTH, - '2': VersionConstants.SDVX_INFINITE_INFECTION, - '3': VersionConstants.SDVX_GRAVITY_WARS, - '4': VersionConstants.SDVX_HEAVENLY_HAVEN, - }, - }.get(game, {}).get(requestversion) + version = ( + { + GameConstants.DDR: { + "12": VersionConstants.DDR_X2, + "13": VersionConstants.DDR_X3_VS_2NDMIX, + "14": VersionConstants.DDR_2013, + "15": VersionConstants.DDR_2014, + "16": VersionConstants.DDR_ACE, + }, + GameConstants.IIDX: { + "20": VersionConstants.IIDX_TRICORO, + "21": VersionConstants.IIDX_SPADA, + "22": VersionConstants.IIDX_PENDUAL, + "23": VersionConstants.IIDX_COPULA, + "24": VersionConstants.IIDX_SINOBUZ, + "25": VersionConstants.IIDX_CANNON_BALLERS, + "26": VersionConstants.IIDX_ROOTAGE, + }, + GameConstants.JUBEAT: { + "5": VersionConstants.JUBEAT_SAUCER, + "5a": VersionConstants.JUBEAT_SAUCER_FULFILL, + "6": VersionConstants.JUBEAT_PROP, + "7": VersionConstants.JUBEAT_QUBELL, + "8": VersionConstants.JUBEAT_CLAN, + "9": VersionConstants.JUBEAT_FESTO, + }, + GameConstants.MUSECA: { + "1": VersionConstants.MUSECA, + "1p": VersionConstants.MUSECA_1_PLUS, + }, + GameConstants.POPN_MUSIC: { + "19": VersionConstants.POPN_MUSIC_TUNE_STREET, + "20": VersionConstants.POPN_MUSIC_FANTASIA, + "21": VersionConstants.POPN_MUSIC_SUNNY_PARK, + "22": VersionConstants.POPN_MUSIC_LAPISTORIA, + "23": VersionConstants.POPN_MUSIC_ECLALE, + "24": VersionConstants.POPN_MUSIC_USANEKO, + }, + GameConstants.REFLEC_BEAT: { + "1": VersionConstants.REFLEC_BEAT, + "2": VersionConstants.REFLEC_BEAT_LIMELIGHT, + # We don't support non-final COLETTE, so just return scores for + # final colette to any network that asks. + "3w": VersionConstants.REFLEC_BEAT_COLETTE, + "3sp": VersionConstants.REFLEC_BEAT_COLETTE, + "3su": VersionConstants.REFLEC_BEAT_COLETTE, + "3a": VersionConstants.REFLEC_BEAT_COLETTE, + "3as": VersionConstants.REFLEC_BEAT_COLETTE, + # We don't support groovin'!!, so just return upper scores. + "4": VersionConstants.REFLEC_BEAT_GROOVIN, + "4u": VersionConstants.REFLEC_BEAT_GROOVIN, + "5": VersionConstants.REFLEC_BEAT_VOLZZA, + "5a": VersionConstants.REFLEC_BEAT_VOLZZA_2, + "6": VersionConstants.REFLEC_BEAT_REFLESIA, + }, + GameConstants.SDVX: { + "1": VersionConstants.SDVX_BOOTH, + "2": VersionConstants.SDVX_INFINITE_INFECTION, + "3": VersionConstants.SDVX_GRAVITY_WARS, + "4": VersionConstants.SDVX_HEAVENLY_HAVEN, + }, + } + .get(game, {}) + .get(requestversion) + ) if version is None: # Don't support this version! abort(404) @@ -290,30 +301,30 @@ def lookup(protoversion: str, requestgame: str, requestversion: str) -> Dict[str # Attempt to coerce ID type. If we fail, provide the correct failure message. idtype = None try: - idtype = APIConstants(requestdata['type']) + idtype = APIConstants(requestdata["type"]) except ValueError: pass if idtype is None: - raise APIException('Invalid ID type provided!') + raise APIException("Invalid ID type provided!") # Validate the provided IDs given the ID type above. - ids = requestdata['ids'] + ids = requestdata["ids"] if idtype == APIConstants.ID_TYPE_CARD and len(ids) == 0: - raise APIException('Invalid number of IDs given!') + raise APIException("Invalid number of IDs given!") if idtype == APIConstants.ID_TYPE_SONG and len(ids) not in [1, 2]: - raise APIException('Invalid number of IDs given!') + raise APIException("Invalid number of IDs given!") if idtype == APIConstants.ID_TYPE_INSTANCE and len(ids) != 3: - raise APIException('Invalid number of IDs given!') + raise APIException("Invalid number of IDs given!") if idtype == APIConstants.ID_TYPE_SERVER and len(ids) != 0: - raise APIException('Invalid number of IDs given!') + raise APIException("Invalid number of IDs given!") responsedata = {} - for obj in requestdata['objects']: + for obj in requestdata["objects"]: handler = { - 'records': RecordsObject, - 'profile': ProfileObject, - 'statistics': StatisticsObject, - 'catalog': CatalogObject, + "records": RecordsObject, + "profile": ProfileObject, + "statistics": StatisticsObject, + "catalog": CatalogObject, }.get(obj) if handler is None: # Don't support this object type @@ -321,7 +332,7 @@ def lookup(protoversion: str, requestgame: str, requestversion: str) -> Dict[str inst = handler(g.data, game, version, omnimix) try: - fetchmethod = getattr(inst, f'fetch_{protoversion}') + fetchmethod = getattr(inst, f"fetch_{protoversion}") except AttributeError: # Don't know how to handle this object for this version abort(501) diff --git a/bemani/api/exceptions.py b/bemani/api/exceptions.py index 991e77b..ba4cc88 100644 --- a/bemani/api/exceptions.py +++ b/bemani/api/exceptions.py @@ -1,5 +1,4 @@ class APIException(Exception): - - def __init__(self, msg: str, code: int=500) -> None: + def __init__(self, msg: str, code: int = 500) -> None: self.message = msg self.code = code diff --git a/bemani/api/objects/base.py b/bemani/api/objects/base.py index c8144b6..385f652 100644 --- a/bemani/api/objects/base.py +++ b/bemani/api/objects/base.py @@ -14,11 +14,15 @@ class BaseObject: various fetch versions. """ - def __init__(self, data: Data, game: GameConstants, version: int, omnimix: bool) -> None: + def __init__( + self, data: Data, game: GameConstants, version: int, omnimix: bool + ) -> None: self.data = data self.game = game self.version = version self.omnimix = omnimix - def fetch_v1(self, idtype: APIConstants, ids: List[str], params: Dict[str, Any]) -> Any: - raise APIException('Object fetch not supported for this version!') + def fetch_v1( + self, idtype: APIConstants, ids: List[str], params: Dict[str, Any] + ) -> Any: + raise APIException("Object fetch not supported for this version!") diff --git a/bemani/api/objects/catalog.py b/bemani/api/objects/catalog.py index e430b6d..9744f59 100644 --- a/bemani/api/objects/catalog.py +++ b/bemani/api/objects/catalog.py @@ -7,31 +7,30 @@ from bemani.data import Song class CatalogObject(BaseObject): - def __format_ddr_song(self, song: Song) -> Dict[str, Any]: - groove = song.data.get_dict('groove') + groove = song.data.get_dict("groove") return { - 'editid': str(song.data.get_int('edit_id')), - 'difficulty': song.data.get_int('difficulty'), - 'bpm_min': song.data.get_int('bpm_min'), - 'bpm_max': song.data.get_int('bpm_max'), - 'category': str(song.data.get_int('category')), - 'groove': { - 'air': groove.get_int('air'), - 'chaos': groove.get_int('chaos'), - 'freeze': groove.get_int('freeze'), - 'stream': groove.get_int('stream'), - 'voltage': groove.get_int('voltage'), + "editid": str(song.data.get_int("edit_id")), + "difficulty": song.data.get_int("difficulty"), + "bpm_min": song.data.get_int("bpm_min"), + "bpm_max": song.data.get_int("bpm_max"), + "category": str(song.data.get_int("category")), + "groove": { + "air": groove.get_int("air"), + "chaos": groove.get_int("chaos"), + "freeze": groove.get_int("freeze"), + "stream": groove.get_int("stream"), + "voltage": groove.get_int("voltage"), }, } def __format_iidx_song(self, song: Song) -> Dict[str, Any]: return { - 'difficulty': song.data.get_int('difficulty'), - 'bpm_min': song.data.get_int('bpm_min'), - 'bpm_max': song.data.get_int('bpm_max'), - 'notecount': song.data.get_int('notecount'), - 'category': str(int(song.id / 1000)), + "difficulty": song.data.get_int("difficulty"), + "bpm_min": song.data.get_int("bpm_min"), + "bpm_max": song.data.get_int("bpm_max"), + "notecount": song.data.get_int("notecount"), + "category": str(int(song.id / 1000)), } def __format_jubeat_song(self, song: Song) -> Dict[str, Any]: @@ -46,67 +45,69 @@ class CatalogObject(BaseObject): 6: VersionConstants.JUBEAT_PROP, 7: VersionConstants.JUBEAT_QUBELL, 8: VersionConstants.JUBEAT_CLAN, - 9: VersionConstants.JUBEAT_FESTO + 9: VersionConstants.JUBEAT_FESTO, }.get(int(song.id / 10000000), VersionConstants.JUBEAT) # Map the category to the version numbers defined on BEMAPI. categorymapping = { - VersionConstants.JUBEAT: '1', - VersionConstants.JUBEAT_RIPPLES: '2', - VersionConstants.JUBEAT_RIPPLES_APPEND: '2a', - VersionConstants.JUBEAT_KNIT: '3', - VersionConstants.JUBEAT_KNIT_APPEND: '3a', - VersionConstants.JUBEAT_COPIOUS: '4', - VersionConstants.JUBEAT_COPIOUS_APPEND: '4a', - VersionConstants.JUBEAT_SAUCER: '5', - VersionConstants.JUBEAT_SAUCER_FULFILL: '5a', - VersionConstants.JUBEAT_PROP: '6', - VersionConstants.JUBEAT_QUBELL: '7', - VersionConstants.JUBEAT_CLAN: '8', - VersionConstants.JUBEAT_FESTO: '9', + VersionConstants.JUBEAT: "1", + VersionConstants.JUBEAT_RIPPLES: "2", + VersionConstants.JUBEAT_RIPPLES_APPEND: "2a", + VersionConstants.JUBEAT_KNIT: "3", + VersionConstants.JUBEAT_KNIT_APPEND: "3a", + VersionConstants.JUBEAT_COPIOUS: "4", + VersionConstants.JUBEAT_COPIOUS_APPEND: "4a", + VersionConstants.JUBEAT_SAUCER: "5", + VersionConstants.JUBEAT_SAUCER_FULFILL: "5a", + VersionConstants.JUBEAT_PROP: "6", + VersionConstants.JUBEAT_QUBELL: "7", + VersionConstants.JUBEAT_CLAN: "8", + VersionConstants.JUBEAT_FESTO: "9", } return { - 'difficulty': song.data.get_int('difficulty'), - 'category': categorymapping.get(song.data.get_int('version', defaultcategory), '1'), - 'bpm_min': song.data.get_int('bpm_min'), - 'bpm_max': song.data.get_int('bpm_max'), + "difficulty": song.data.get_int("difficulty"), + "category": categorymapping.get( + song.data.get_int("version", defaultcategory), "1" + ), + "bpm_min": song.data.get_int("bpm_min"), + "bpm_max": song.data.get_int("bpm_max"), } def __format_museca_song(self, song: Song) -> Dict[str, Any]: return { - 'difficulty': song.data.get_int('difficulty'), - 'bpm_min': song.data.get_int('bpm_min'), - 'bpm_max': song.data.get_int('bpm_max'), - 'limited': song.data.get_int('limited'), + "difficulty": song.data.get_int("difficulty"), + "bpm_min": song.data.get_int("bpm_min"), + "bpm_max": song.data.get_int("bpm_max"), + "limited": song.data.get_int("limited"), } def __format_popn_song(self, song: Song) -> Dict[str, Any]: return { - 'difficulty': song.data.get_int('difficulty'), - 'category': song.data.get_str('category'), + "difficulty": song.data.get_int("difficulty"), + "category": song.data.get_str("category"), } def __format_reflec_song(self, song: Song) -> Dict[str, Any]: return { - 'difficulty': song.data.get_int('difficulty'), - 'category': str(song.data.get_int('folder')), - 'musicid': song.data.get_str('chart_id'), + "difficulty": song.data.get_int("difficulty"), + "category": str(song.data.get_int("folder")), + "musicid": song.data.get_str("chart_id"), } def __format_sdvx_song(self, song: Song) -> Dict[str, Any]: return { - 'difficulty': song.data.get_int('difficulty'), - 'bpm_min': song.data.get_int('bpm_min'), - 'bpm_max': song.data.get_int('bpm_max'), - 'limited': song.data.get_int('limited'), + "difficulty": song.data.get_int("difficulty"), + "bpm_min": song.data.get_int("bpm_min"), + "bpm_max": song.data.get_int("bpm_max"), + "limited": song.data.get_int("limited"), } def __format_song(self, song: Song) -> Dict[str, Any]: base = { - 'song': str(song.id), - 'chart': str(song.chart), - 'title': song.name or "", - 'artist': song.artist or "", - 'genre': song.genre or "", + "song": str(song.id), + "chart": str(song.chart), + "title": song.name or "", + "artist": song.artist or "", + "genre": song.genre or "", } if self.game == GameConstants.DDR: @@ -198,7 +199,7 @@ class CatalogObject(BaseObject): "type": item.type[3:], } for item in items - if item.type in ['qp_body', 'qp_face', 'qp_hair', 'qp_hand', 'qp_head'] + if item.type in ["qp_body", "qp_face", "qp_hair", "qp_hand", "qp_head"] ], } @@ -222,17 +223,22 @@ class CatalogObject(BaseObject): else: return self.version - def fetch_v1(self, idtype: APIConstants, ids: List[str], params: Dict[str, Any]) -> Dict[str, List[Dict[str, Any]]]: + def fetch_v1( + self, idtype: APIConstants, ids: List[str], params: Dict[str, Any] + ) -> Dict[str, List[Dict[str, Any]]]: # Verify IDs if idtype != APIConstants.ID_TYPE_SERVER: raise APIException( - 'Unsupported ID for lookup!', + "Unsupported ID for lookup!", 405, ) # Fetch the songs songs = self.data.local.music.get_all_songs(self.game, self.music_version) - if self.game == GameConstants.JUBEAT and self.version == VersionConstants.JUBEAT_CLAN: + if ( + self.game == GameConstants.JUBEAT + and self.version == VersionConstants.JUBEAT_CLAN + ): # There's always a special case. We don't store all music IDs since those in # the range of 80000301-80000347 are actually the same song, but copy-pasted # for different prefectures and slightly different charts. So, we need to copy @@ -255,7 +261,7 @@ class CatalogObject(BaseObject): ) songs.extend(additions) retval = { - 'songs': [self.__format_song(song) for song in songs], + "songs": [self.__format_song(song) for song in songs], } # Fetch any optional extras per-game, return diff --git a/bemani/api/objects/profile.py b/bemani/api/objects/profile.py index 167b24c..e4eeab8 100644 --- a/bemani/api/objects/profile.py +++ b/bemani/api/objects/profile.py @@ -7,24 +7,23 @@ from bemani.data import UserID class ProfileObject(BaseObject): - def __format_ddr_profile(self, profile: Profile, exact: bool) -> Dict[str, Any]: return { - 'area': profile.get_int('area', -1) if exact else -1, + "area": profile.get_int("area", -1) if exact else -1, } def __format_iidx_profile(self, profile: Profile, exact: bool) -> Dict[str, Any]: - qpro = profile.get_dict('qpro') + qpro = profile.get_dict("qpro") return { - 'area': profile.get_int('pid', -1), - 'qpro': { - 'head': qpro.get_int('head', -1) if exact else -1, - 'hair': qpro.get_int('hair', -1) if exact else -1, - 'face': qpro.get_int('face', -1) if exact else -1, - 'body': qpro.get_int('body', -1) if exact else -1, - 'hand': qpro.get_int('hand', -1) if exact else -1, - } + "area": profile.get_int("pid", -1), + "qpro": { + "head": qpro.get_int("head", -1) if exact else -1, + "hair": qpro.get_int("hair", -1) if exact else -1, + "face": qpro.get_int("face", -1) if exact else -1, + "body": qpro.get_int("body", -1) if exact else -1, + "hand": qpro.get_int("hand", -1) if exact else -1, + }, } def __format_jubeat_profile(self, profile: Profile, exact: bool) -> Dict[str, Any]: @@ -35,25 +34,27 @@ class ProfileObject(BaseObject): def __format_popn_profile(self, profile: Profile, exact: bool) -> Dict[str, Any]: return { - 'character': profile.get_int('chara', -1) if exact else -1, + "character": profile.get_int("chara", -1) if exact else -1, } def __format_reflec_profile(self, profile: Profile, exact: bool) -> Dict[str, Any]: return { - 'icon': profile.get_dict('config').get_int('icon_id', -1) if exact else -1, + "icon": profile.get_dict("config").get_int("icon_id", -1) if exact else -1, } def __format_sdvx_profile(self, profile: Profile, exact: bool) -> Dict[str, Any]: return {} - def __format_profile(self, cardids: List[str], profile: Profile, settings: ValidatedDict, exact: bool) -> Dict[str, Any]: + def __format_profile( + self, cardids: List[str], profile: Profile, settings: ValidatedDict, exact: bool + ) -> Dict[str, Any]: base = { - 'name': profile.get_str('name'), - 'cards': cardids, - 'registered': settings.get_int('first_play_timestamp', -1), - 'updated': settings.get_int('last_play_timestamp', -1), - 'plays': settings.get_int('total_plays', -1), - 'match': 'exact' if exact else 'partial', + "name": profile.get_str("name"), + "cards": cardids, + "registered": settings.get_int("first_play_timestamp", -1), + "updated": settings.get_int("last_play_timestamp", -1), + "plays": settings.get_int("total_plays", -1), + "match": "exact" if exact else "partial", } if self.game == GameConstants.DDR: @@ -73,19 +74,23 @@ class ProfileObject(BaseObject): return base - def fetch_v1(self, idtype: APIConstants, ids: List[str], params: Dict[str, Any]) -> List[Dict[str, Any]]: + def fetch_v1( + self, idtype: APIConstants, ids: List[str], params: Dict[str, Any] + ) -> List[Dict[str, Any]]: # Fetch the profiles profiles: List[Tuple[UserID, Profile]] = [] if idtype == APIConstants.ID_TYPE_SERVER: - profiles.extend(self.data.local.user.get_all_profiles(self.game, self.version)) + profiles.extend( + self.data.local.user.get_all_profiles(self.game, self.version) + ) elif idtype == APIConstants.ID_TYPE_SONG: raise APIException( - 'Unsupported ID for lookup!', + "Unsupported ID for lookup!", 405, ) elif idtype == APIConstants.ID_TYPE_INSTANCE: raise APIException( - 'Unsupported ID for lookup!', + "Unsupported ID for lookup!", 405, ) elif idtype == APIConstants.ID_TYPE_CARD: @@ -103,11 +108,13 @@ class ProfileObject(BaseObject): # in the case that we returned scores for a user that doesn't have a # profile on a particular version. We allow that on this network, so in # order to not break remote networks, try our best to return any profile. - profile = self.data.local.user.get_any_profile(self.game, self.version, userid) + profile = self.data.local.user.get_any_profile( + self.game, self.version, userid + ) if profile is not None: profiles.append((userid, profile)) else: - raise APIException('Invalid ID type!') + raise APIException("Invalid ID type!") # Now, fetch the users, and filter out profiles belonging to orphaned users retval: List[Dict[str, Any]] = [] @@ -126,6 +133,13 @@ class ProfileObject(BaseObject): if settings is None: settings = ValidatedDict({}) - retval.append(self.__format_profile(id_to_cards[userid], profile, settings, profile.version == self.version)) + retval.append( + self.__format_profile( + id_to_cards[userid], + profile, + settings, + profile.version == self.version, + ) + ) return retval diff --git a/bemani/api/objects/records.py b/bemani/api/objects/records.py index edeaed9..e40cfa4 100644 --- a/bemani/api/objects/records.py +++ b/bemani/api/objects/records.py @@ -7,15 +7,14 @@ from bemani.data import Score, UserID class RecordsObject(BaseObject): - def __format_ddr_record(self, record: Score) -> Dict[str, Any]: halo = { - DBConstants.DDR_HALO_NONE: 'none', - DBConstants.DDR_HALO_GOOD_FULL_COMBO: 'gfc', - DBConstants.DDR_HALO_GREAT_FULL_COMBO: 'fc', - DBConstants.DDR_HALO_PERFECT_FULL_COMBO: 'pfc', - DBConstants.DDR_HALO_MARVELOUS_FULL_COMBO: 'mfc', - }.get(record.data.get_int('halo'), 'none') + DBConstants.DDR_HALO_NONE: "none", + DBConstants.DDR_HALO_GOOD_FULL_COMBO: "gfc", + DBConstants.DDR_HALO_GREAT_FULL_COMBO: "fc", + DBConstants.DDR_HALO_PERFECT_FULL_COMBO: "pfc", + DBConstants.DDR_HALO_MARVELOUS_FULL_COMBO: "mfc", + }.get(record.data.get_int("halo"), "none") rank = { DBConstants.DDR_RANK_AAA: "AAA", DBConstants.DDR_RANK_AA_PLUS: "AA+", @@ -33,175 +32,175 @@ class RecordsObject(BaseObject): DBConstants.DDR_RANK_D_PLUS: "D+", DBConstants.DDR_RANK_D: "D", DBConstants.DDR_RANK_E: "E", - }.get(record.data.get_int('rank'), 'E') + }.get(record.data.get_int("rank"), "E") if self.version == VersionConstants.DDR_ACE: # DDR Ace is specia - ghost = [int(x) for x in record.data.get_str('ghost')] + ghost = [int(x) for x in record.data.get_str("ghost")] else: - if 'trace' not in record.data: + if "trace" not in record.data: ghost = [] else: - ghost = record.data.get_int_array('trace', len(record.data['trace'])) + ghost = record.data.get_int_array("trace", len(record.data["trace"])) return { - 'rank': rank, - 'halo': halo, - 'combo': record.data.get_int('combo'), - 'ghost': ghost, + "rank": rank, + "halo": halo, + "combo": record.data.get_int("combo"), + "ghost": ghost, } def __format_iidx_record(self, record: Score) -> Dict[str, Any]: status = { - DBConstants.IIDX_CLEAR_STATUS_NO_PLAY: 'np', - DBConstants.IIDX_CLEAR_STATUS_FAILED: 'failed', - DBConstants.IIDX_CLEAR_STATUS_ASSIST_CLEAR: 'ac', - DBConstants.IIDX_CLEAR_STATUS_EASY_CLEAR: 'ec', - DBConstants.IIDX_CLEAR_STATUS_CLEAR: 'nc', - DBConstants.IIDX_CLEAR_STATUS_HARD_CLEAR: 'hc', - DBConstants.IIDX_CLEAR_STATUS_EX_HARD_CLEAR: 'exhc', - DBConstants.IIDX_CLEAR_STATUS_FULL_COMBO: 'fc', - }.get(record.data.get_int('clear_status'), 'np') + DBConstants.IIDX_CLEAR_STATUS_NO_PLAY: "np", + DBConstants.IIDX_CLEAR_STATUS_FAILED: "failed", + DBConstants.IIDX_CLEAR_STATUS_ASSIST_CLEAR: "ac", + DBConstants.IIDX_CLEAR_STATUS_EASY_CLEAR: "ec", + DBConstants.IIDX_CLEAR_STATUS_CLEAR: "nc", + DBConstants.IIDX_CLEAR_STATUS_HARD_CLEAR: "hc", + DBConstants.IIDX_CLEAR_STATUS_EX_HARD_CLEAR: "exhc", + DBConstants.IIDX_CLEAR_STATUS_FULL_COMBO: "fc", + }.get(record.data.get_int("clear_status"), "np") return { - 'status': status, - 'miss': record.data.get_int('miss_count', -1), - 'ghost': [b for b in record.data.get_bytes('ghost')], - 'pgreat': record.data.get_int('pgreats', -1), - 'great': record.data.get_int('greats', -1), + "status": status, + "miss": record.data.get_int("miss_count", -1), + "ghost": [b for b in record.data.get_bytes("ghost")], + "pgreat": record.data.get_int("pgreats", -1), + "great": record.data.get_int("greats", -1), } def __format_jubeat_record(self, record: Score) -> Dict[str, Any]: status = { - DBConstants.JUBEAT_PLAY_MEDAL_FAILED: 'failed', - DBConstants.JUBEAT_PLAY_MEDAL_CLEARED: 'cleared', - DBConstants.JUBEAT_PLAY_MEDAL_NEARLY_FULL_COMBO: 'nfc', - DBConstants.JUBEAT_PLAY_MEDAL_FULL_COMBO: 'fc', - DBConstants.JUBEAT_PLAY_MEDAL_NEARLY_EXCELLENT: 'nec', - DBConstants.JUBEAT_PLAY_MEDAL_EXCELLENT: 'exc', - }.get(record.data.get_int('medal'), 'failed') - if 'ghost' not in record.data: + DBConstants.JUBEAT_PLAY_MEDAL_FAILED: "failed", + DBConstants.JUBEAT_PLAY_MEDAL_CLEARED: "cleared", + DBConstants.JUBEAT_PLAY_MEDAL_NEARLY_FULL_COMBO: "nfc", + DBConstants.JUBEAT_PLAY_MEDAL_FULL_COMBO: "fc", + DBConstants.JUBEAT_PLAY_MEDAL_NEARLY_EXCELLENT: "nec", + DBConstants.JUBEAT_PLAY_MEDAL_EXCELLENT: "exc", + }.get(record.data.get_int("medal"), "failed") + if "ghost" not in record.data: ghost: List[int] = [] else: - ghost = record.data.get_int_array('ghost', len(record.data['ghost'])) + ghost = record.data.get_int_array("ghost", len(record.data["ghost"])) return { - 'status': status, - 'combo': record.data.get_int('combo', -1), - 'ghost': ghost, - 'music_rate': record.data.get_int('music_rate'), + "status": status, + "combo": record.data.get_int("combo", -1), + "ghost": ghost, + "music_rate": record.data.get_int("music_rate"), } def __format_museca_record(self, record: Score) -> Dict[str, Any]: rank = { - DBConstants.MUSECA_GRADE_DEATH: 'death', - DBConstants.MUSECA_GRADE_POOR: 'poor', - DBConstants.MUSECA_GRADE_MEDIOCRE: 'mediocre', - DBConstants.MUSECA_GRADE_GOOD: 'good', - DBConstants.MUSECA_GRADE_GREAT: 'great', - DBConstants.MUSECA_GRADE_EXCELLENT: 'excellent', - DBConstants.MUSECA_GRADE_SUPERB: 'superb', - DBConstants.MUSECA_GRADE_MASTERPIECE: 'masterpiece', - DBConstants.MUSECA_GRADE_PERFECT: 'perfect' - }.get(record.data.get_int('grade'), 'death') + DBConstants.MUSECA_GRADE_DEATH: "death", + DBConstants.MUSECA_GRADE_POOR: "poor", + DBConstants.MUSECA_GRADE_MEDIOCRE: "mediocre", + DBConstants.MUSECA_GRADE_GOOD: "good", + DBConstants.MUSECA_GRADE_GREAT: "great", + DBConstants.MUSECA_GRADE_EXCELLENT: "excellent", + DBConstants.MUSECA_GRADE_SUPERB: "superb", + DBConstants.MUSECA_GRADE_MASTERPIECE: "masterpiece", + DBConstants.MUSECA_GRADE_PERFECT: "perfect", + }.get(record.data.get_int("grade"), "death") status = { - DBConstants.MUSECA_CLEAR_TYPE_FAILED: 'failed', - DBConstants.MUSECA_CLEAR_TYPE_CLEARED: 'cleared', - DBConstants.MUSECA_CLEAR_TYPE_FULL_COMBO: 'fc', - }.get(record.data.get_int('clear_type'), 'failed') + DBConstants.MUSECA_CLEAR_TYPE_FAILED: "failed", + DBConstants.MUSECA_CLEAR_TYPE_CLEARED: "cleared", + DBConstants.MUSECA_CLEAR_TYPE_FULL_COMBO: "fc", + }.get(record.data.get_int("clear_type"), "failed") return { - 'rank': rank, - 'status': status, - 'combo': record.data.get_int('combo', -1), - 'buttonrate': record.data.get_dict('stats').get_int('btn_rate'), - 'longrate': record.data.get_dict('stats').get_int('long_rate'), - 'volrate': record.data.get_dict('stats').get_int('vol_rate'), + "rank": rank, + "status": status, + "combo": record.data.get_int("combo", -1), + "buttonrate": record.data.get_dict("stats").get_int("btn_rate"), + "longrate": record.data.get_dict("stats").get_int("long_rate"), + "volrate": record.data.get_dict("stats").get_int("vol_rate"), } def __format_popn_record(self, record: Score) -> Dict[str, Any]: status = { - DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_FAILED: 'cf', - DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_FAILED: 'df', - DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_FAILED: 'sf', - DBConstants.POPN_MUSIC_PLAY_MEDAL_EASY_CLEAR: 'ec', - DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_CLEARED: 'cc', - DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_CLEARED: 'dc', - DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_CLEARED: 'sc', - DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_FULL_COMBO: 'cfc', - DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_FULL_COMBO: 'dfc', - DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_FULL_COMBO: 'sfc', - DBConstants.POPN_MUSIC_PLAY_MEDAL_PERFECT: 'p', - }.get(record.data.get_int('medal'), 'cf') + DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_FAILED: "cf", + DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_FAILED: "df", + DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_FAILED: "sf", + DBConstants.POPN_MUSIC_PLAY_MEDAL_EASY_CLEAR: "ec", + DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_CLEARED: "cc", + DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_CLEARED: "dc", + DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_CLEARED: "sc", + DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_FULL_COMBO: "cfc", + DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_FULL_COMBO: "dfc", + DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_FULL_COMBO: "sfc", + DBConstants.POPN_MUSIC_PLAY_MEDAL_PERFECT: "p", + }.get(record.data.get_int("medal"), "cf") return { - 'status': status, - 'combo': record.data.get_int('combo', -1), + "status": status, + "combo": record.data.get_int("combo", -1), } def __format_reflec_record(self, record: Score) -> Dict[str, Any]: status = { - DBConstants.REFLEC_BEAT_CLEAR_TYPE_NO_PLAY: 'np', - DBConstants.REFLEC_BEAT_CLEAR_TYPE_FAILED: 'failed', - DBConstants.REFLEC_BEAT_CLEAR_TYPE_CLEARED: 'cleared', - DBConstants.REFLEC_BEAT_CLEAR_TYPE_HARD_CLEARED: 'hc', - DBConstants.REFLEC_BEAT_CLEAR_TYPE_S_HARD_CLEARED: 'shc', - }.get(record.data.get_int('clear_type'), 'np') + DBConstants.REFLEC_BEAT_CLEAR_TYPE_NO_PLAY: "np", + DBConstants.REFLEC_BEAT_CLEAR_TYPE_FAILED: "failed", + DBConstants.REFLEC_BEAT_CLEAR_TYPE_CLEARED: "cleared", + DBConstants.REFLEC_BEAT_CLEAR_TYPE_HARD_CLEARED: "hc", + DBConstants.REFLEC_BEAT_CLEAR_TYPE_S_HARD_CLEARED: "shc", + }.get(record.data.get_int("clear_type"), "np") halo = { - DBConstants.REFLEC_BEAT_COMBO_TYPE_NONE: 'none', - DBConstants.REFLEC_BEAT_COMBO_TYPE_ALMOST_COMBO: 'ac', - DBConstants.REFLEC_BEAT_COMBO_TYPE_FULL_COMBO: 'fc', - DBConstants.REFLEC_BEAT_COMBO_TYPE_FULL_COMBO_ALL_JUST: 'fcaj', - }.get(record.data.get_int('combo_type'), 'none') + DBConstants.REFLEC_BEAT_COMBO_TYPE_NONE: "none", + DBConstants.REFLEC_BEAT_COMBO_TYPE_ALMOST_COMBO: "ac", + DBConstants.REFLEC_BEAT_COMBO_TYPE_FULL_COMBO: "fc", + DBConstants.REFLEC_BEAT_COMBO_TYPE_FULL_COMBO_ALL_JUST: "fcaj", + }.get(record.data.get_int("combo_type"), "none") return { - 'rate': record.data.get_int('achievement_rate'), - 'status': status, - 'halo': halo, - 'combo': record.data.get_int('combo', -1), - 'miss': record.data.get_int('miss_count', -1), + "rate": record.data.get_int("achievement_rate"), + "status": status, + "halo": halo, + "combo": record.data.get_int("combo", -1), + "miss": record.data.get_int("miss_count", -1), } def __format_sdvx_record(self, record: Score) -> Dict[str, Any]: status = { - DBConstants.SDVX_CLEAR_TYPE_NO_PLAY: 'np', - DBConstants.SDVX_CLEAR_TYPE_FAILED: 'failed', - DBConstants.SDVX_CLEAR_TYPE_CLEAR: 'cleared', - DBConstants.SDVX_CLEAR_TYPE_HARD_CLEAR: 'hc', - DBConstants.SDVX_CLEAR_TYPE_ULTIMATE_CHAIN: 'uc', - DBConstants.SDVX_CLEAR_TYPE_PERFECT_ULTIMATE_CHAIN: 'puc', - }.get(record.data.get_int('clear_type'), 'np') + DBConstants.SDVX_CLEAR_TYPE_NO_PLAY: "np", + DBConstants.SDVX_CLEAR_TYPE_FAILED: "failed", + DBConstants.SDVX_CLEAR_TYPE_CLEAR: "cleared", + DBConstants.SDVX_CLEAR_TYPE_HARD_CLEAR: "hc", + DBConstants.SDVX_CLEAR_TYPE_ULTIMATE_CHAIN: "uc", + DBConstants.SDVX_CLEAR_TYPE_PERFECT_ULTIMATE_CHAIN: "puc", + }.get(record.data.get_int("clear_type"), "np") rank = { - DBConstants.SDVX_GRADE_NO_PLAY: 'E', - DBConstants.SDVX_GRADE_D: 'D', - DBConstants.SDVX_GRADE_C: 'C', - DBConstants.SDVX_GRADE_B: 'B', - DBConstants.SDVX_GRADE_A: 'A', - DBConstants.SDVX_GRADE_A_PLUS: 'A+', - DBConstants.SDVX_GRADE_AA: 'AA', - DBConstants.SDVX_GRADE_AA_PLUS: 'AA+', - DBConstants.SDVX_GRADE_AAA: 'AAA', - DBConstants.SDVX_GRADE_AAA_PLUS: 'AAA+', - DBConstants.SDVX_GRADE_S: 'S', - }.get(record.data.get_int('grade'), 'E') + DBConstants.SDVX_GRADE_NO_PLAY: "E", + DBConstants.SDVX_GRADE_D: "D", + DBConstants.SDVX_GRADE_C: "C", + DBConstants.SDVX_GRADE_B: "B", + DBConstants.SDVX_GRADE_A: "A", + DBConstants.SDVX_GRADE_A_PLUS: "A+", + DBConstants.SDVX_GRADE_AA: "AA", + DBConstants.SDVX_GRADE_AA_PLUS: "AA+", + DBConstants.SDVX_GRADE_AAA: "AAA", + DBConstants.SDVX_GRADE_AAA_PLUS: "AAA+", + DBConstants.SDVX_GRADE_S: "S", + }.get(record.data.get_int("grade"), "E") return { - 'status': status, - 'rank': rank, - 'combo': record.data.get_int('combo', -1), - 'buttonrate': record.data.get_dict('stats').get_int('btn_rate'), - 'longrate': record.data.get_dict('stats').get_int('long_rate'), - 'volrate': record.data.get_dict('stats').get_int('vol_rate'), + "status": status, + "rank": rank, + "combo": record.data.get_int("combo", -1), + "buttonrate": record.data.get_dict("stats").get_int("btn_rate"), + "longrate": record.data.get_dict("stats").get_int("long_rate"), + "volrate": record.data.get_dict("stats").get_int("vol_rate"), } def __format_record(self, cardids: List[str], record: Score) -> Dict[str, Any]: base = { - 'cards': cardids, - 'song': str(record.id), - 'chart': str(record.chart), - 'points': record.points, - 'timestamp': record.timestamp, - 'updated': record.update, + "cards": cardids, + "song": str(record.id), + "chart": str(record.chart), + "points": record.points, + "timestamp": record.timestamp, + "updated": record.update, } if self.game == GameConstants.DDR: @@ -231,9 +230,11 @@ class RecordsObject(BaseObject): else: return self.version - def fetch_v1(self, idtype: APIConstants, ids: List[str], params: Dict[str, Any]) -> List[Dict[str, Any]]: - since = params.get('since') - until = params.get('until') + def fetch_v1( + self, idtype: APIConstants, ids: List[str], params: Dict[str, Any] + ) -> List[Dict[str, Any]]: + since = params.get("since") + until = params.get("until") # Fetch the scores records: List[Tuple[UserID, Score]] = [] @@ -241,7 +242,9 @@ class RecordsObject(BaseObject): # Because of the way this query works, we can't apply since/until to it directly. # If we did, it would miss higher scores earned before since or after until, and # incorrectly report records. - records.extend(self.data.local.music.get_all_records(self.game, self.music_version)) + records.extend( + self.data.local.music.get_all_records(self.game, self.music_version) + ) elif idtype == APIConstants.ID_TYPE_SONG: if len(ids) == 1: songid = int(ids[0]) @@ -249,14 +252,25 @@ class RecordsObject(BaseObject): else: songid = int(ids[0]) chart = int(ids[1]) - records.extend(self.data.local.music.get_all_scores(self.game, self.music_version, songid=songid, songchart=chart, since=since, until=until)) + records.extend( + self.data.local.music.get_all_scores( + self.game, + self.music_version, + songid=songid, + songchart=chart, + since=since, + until=until, + ) + ) elif idtype == APIConstants.ID_TYPE_INSTANCE: songid = int(ids[0]) chart = int(ids[1]) cardid = ids[2] userid = self.data.local.user.from_cardid(cardid) if userid is not None: - score = self.data.local.music.get_score(self.game, self.music_version, userid, songid, chart) + score = self.data.local.music.get_score( + self.game, self.music_version, userid, songid, chart + ) if score is not None: records.append((userid, score)) elif idtype == APIConstants.ID_TYPE_CARD: @@ -270,9 +284,20 @@ class RecordsObject(BaseObject): continue users.add(userid) - records.extend([(userid, score) for score in self.data.local.music.get_scores(self.game, self.music_version, userid, since=since, until=until)]) + records.extend( + [ + (userid, score) + for score in self.data.local.music.get_scores( + self.game, + self.music_version, + userid, + since=since, + until=until, + ) + ] + ) else: - raise APIException('Invalid ID type!') + raise APIException("Invalid ID type!") # Now, fetch the users, and filter out scores belonging to orphaned users id_to_cards: Dict[UserID, List[str]] = {} diff --git a/bemani/api/objects/statistics.py b/bemani/api/objects/statistics.py index 9b1fab8..182bc32 100644 --- a/bemani/api/objects/statistics.py +++ b/bemani/api/objects/statistics.py @@ -7,20 +7,21 @@ from bemani.data import Attempt, UserID class StatisticsObject(BaseObject): - def __format_statistics(self, stats: Dict[str, Any]) -> Dict[str, Any]: return { - 'cards': [], - 'song': str(stats['id']), - 'chart': str(stats['chart']), - 'plays': stats.get('plays', -1), - 'clears': stats.get('clears', -1), - 'combos': stats.get('combos', -1), + "cards": [], + "song": str(stats["id"]), + "chart": str(stats["chart"]), + "plays": stats.get("plays", -1), + "clears": stats.get("clears", -1), + "combos": stats.get("combos", -1), } - def __format_user_statistics(self, cardids: List[str], stats: Dict[str, Any]) -> Dict[str, Any]: + def __format_user_statistics( + self, cardids: List[str], stats: Dict[str, Any] + ) -> Dict[str, Any]: base = self.__format_statistics(stats) - base['cards'] = cardids + base["cards"] = cardids return base @property @@ -42,11 +43,20 @@ class StatisticsObject(BaseObject): }: return True if self.game == GameConstants.IIDX: - return attempt.data.get_int('clear_status') != DBConstants.IIDX_CLEAR_STATUS_NO_PLAY + return ( + attempt.data.get_int("clear_status") + != DBConstants.IIDX_CLEAR_STATUS_NO_PLAY + ) if self.game == GameConstants.REFLEC_BEAT: - return attempt.data.get_int('clear_type') != DBConstants.REFLEC_BEAT_CLEAR_TYPE_NO_PLAY + return ( + attempt.data.get_int("clear_type") + != DBConstants.REFLEC_BEAT_CLEAR_TYPE_NO_PLAY + ) if self.game == GameConstants.SDVX: - return attempt.data.get_int('clear_type') != DBConstants.SDVX_CLEAR_TYPE_NO_PLAY + return ( + attempt.data.get_int("clear_type") + != DBConstants.SDVX_CLEAR_TYPE_NO_PLAY + ) return False @@ -55,29 +65,39 @@ class StatisticsObject(BaseObject): return False if self.game == GameConstants.DDR: - return attempt.data.get_int('rank') != DBConstants.DDR_RANK_E + return attempt.data.get_int("rank") != DBConstants.DDR_RANK_E if self.game == GameConstants.IIDX: - return attempt.data.get_int('clear_status') != DBConstants.IIDX_CLEAR_STATUS_FAILED + return ( + attempt.data.get_int("clear_status") + != DBConstants.IIDX_CLEAR_STATUS_FAILED + ) if self.game == GameConstants.JUBEAT: - return attempt.data.get_int('medal') != DBConstants.JUBEAT_PLAY_MEDAL_FAILED + return attempt.data.get_int("medal") != DBConstants.JUBEAT_PLAY_MEDAL_FAILED if self.game == GameConstants.MUSECA: - return attempt.data.get_int('clear_type') != DBConstants.MUSECA_CLEAR_TYPE_FAILED + return ( + attempt.data.get_int("clear_type") + != DBConstants.MUSECA_CLEAR_TYPE_FAILED + ) if self.game == GameConstants.POPN_MUSIC: - return attempt.data.get_int('medal') not in [ + return attempt.data.get_int("medal") not in [ DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_FAILED, DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_FAILED, DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_FAILED, ] if self.game == GameConstants.REFLEC_BEAT: - return attempt.data.get_int('clear_type') != DBConstants.REFLEC_BEAT_CLEAR_TYPE_FAILED - if self.game == GameConstants.SDVX: return ( - attempt.data.get_int('grade') != DBConstants.SDVX_GRADE_NO_PLAY and - attempt.data.get_int('clear_type') not in [ - DBConstants.SDVX_CLEAR_TYPE_NO_PLAY, - DBConstants.SDVX_CLEAR_TYPE_FAILED, - ] + attempt.data.get_int("clear_type") + != DBConstants.REFLEC_BEAT_CLEAR_TYPE_FAILED ) + if self.game == GameConstants.SDVX: + return attempt.data.get_int( + "grade" + ) != DBConstants.SDVX_GRADE_NO_PLAY and attempt.data.get_int( + "clear_type" + ) not in [ + DBConstants.SDVX_CLEAR_TYPE_NO_PLAY, + DBConstants.SDVX_CLEAR_TYPE_FAILED, + ] return False @@ -86,31 +106,37 @@ class StatisticsObject(BaseObject): return False if self.game == GameConstants.DDR: - return attempt.data.get_int('halo') != DBConstants.DDR_HALO_NONE + return attempt.data.get_int("halo") != DBConstants.DDR_HALO_NONE if self.game == GameConstants.IIDX: - return attempt.data.get_int('clear_status') == DBConstants.IIDX_CLEAR_STATUS_FULL_COMBO + return ( + attempt.data.get_int("clear_status") + == DBConstants.IIDX_CLEAR_STATUS_FULL_COMBO + ) if self.game == GameConstants.JUBEAT: - return attempt.data.get_int('medal') in [ + return attempt.data.get_int("medal") in [ DBConstants.JUBEAT_PLAY_MEDAL_FULL_COMBO, DBConstants.JUBEAT_PLAY_MEDAL_NEARLY_EXCELLENT, DBConstants.JUBEAT_PLAY_MEDAL_EXCELLENT, ] if self.game == GameConstants.MUSECA: - return attempt.data.get_int('clear_type') == DBConstants.MUSECA_CLEAR_TYPE_FULL_COMBO + return ( + attempt.data.get_int("clear_type") + == DBConstants.MUSECA_CLEAR_TYPE_FULL_COMBO + ) if self.game == GameConstants.POPN_MUSIC: - return attempt.data.get_int('medal') in [ + return attempt.data.get_int("medal") in [ DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_FULL_COMBO, DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_FULL_COMBO, DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_FULL_COMBO, DBConstants.POPN_MUSIC_PLAY_MEDAL_PERFECT, ] if self.game == GameConstants.REFLEC_BEAT: - return attempt.data.get_int('combo_type') in [ + return attempt.data.get_int("combo_type") in [ DBConstants.REFLEC_BEAT_COMBO_TYPE_FULL_COMBO, DBConstants.REFLEC_BEAT_COMBO_TYPE_FULL_COMBO_ALL_JUST, ] if self.game == GameConstants.SDVX: - return attempt.data.get_int('clear_type') in [ + return attempt.data.get_int("clear_type") in [ DBConstants.SDVX_CLEAR_TYPE_ULTIMATE_CHAIN, DBConstants.SDVX_CLEAR_TYPE_PERFECT_ULTIMATE_CHAIN, ] @@ -125,29 +151,31 @@ class StatisticsObject(BaseObject): stats[attempt.id] = {} if attempt.chart not in stats[attempt.id]: stats[attempt.id][attempt.chart] = { - 'plays': 0, - 'clears': 0, - 'combos': 0, + "plays": 0, + "clears": 0, + "combos": 0, } if self.__is_play(attempt): - stats[attempt.id][attempt.chart]['plays'] += 1 + stats[attempt.id][attempt.chart]["plays"] += 1 if self.__is_clear(attempt): - stats[attempt.id][attempt.chart]['clears'] += 1 + stats[attempt.id][attempt.chart]["clears"] += 1 if self.__is_combo(attempt): - stats[attempt.id][attempt.chart]['combos'] += 1 + stats[attempt.id][attempt.chart]["combos"] += 1 retval = [] for songid in stats: for songchart in stats[songid]: stat = stats[songid][songchart] - stat['id'] = songid - stat['chart'] = songchart + stat["id"] = songid + stat["chart"] = songchart retval.append(self.__format_statistics(stat)) return retval - def __aggregate_local(self, cards: Dict[int, List[str]], attempts: List[Tuple[UserID, Attempt]]) -> List[Dict[str, Any]]: + def __aggregate_local( + self, cards: Dict[int, List[str]], attempts: List[Tuple[UserID, Attempt]] + ) -> List[Dict[str, Any]]: stats: Dict[UserID, Dict[int, Dict[int, Dict[str, int]]]] = {} for (userid, attempt) in attempts: @@ -157,36 +185,43 @@ class StatisticsObject(BaseObject): stats[userid][attempt.id] = {} if attempt.chart not in stats[userid][attempt.id]: stats[userid][attempt.id][attempt.chart] = { - 'plays': 0, - 'clears': 0, - 'combos': 0, + "plays": 0, + "clears": 0, + "combos": 0, } if self.__is_play(attempt): - stats[userid][attempt.id][attempt.chart]['plays'] += 1 + stats[userid][attempt.id][attempt.chart]["plays"] += 1 if self.__is_clear(attempt): - stats[userid][attempt.id][attempt.chart]['clears'] += 1 + stats[userid][attempt.id][attempt.chart]["clears"] += 1 if self.__is_combo(attempt): - stats[userid][attempt.id][attempt.chart]['combos'] += 1 + stats[userid][attempt.id][attempt.chart]["combos"] += 1 retval = [] for userid in stats: for songid in stats[userid]: for songchart in stats[userid][songid]: stat = stats[userid][songid][songchart] - stat['id'] = songid - stat['chart'] = songchart + stat["id"] = songid + stat["chart"] = songchart retval.append(self.__format_user_statistics(cards[userid], stat)) return retval - def fetch_v1(self, idtype: APIConstants, ids: List[str], params: Dict[str, Any]) -> List[Dict[str, Any]]: + def fetch_v1( + self, idtype: APIConstants, ids: List[str], params: Dict[str, Any] + ) -> List[Dict[str, Any]]: retval: List[Dict[str, Any]] = [] # Fetch the attempts if idtype == APIConstants.ID_TYPE_SERVER: retval = self.__aggregate_global( - [attempt[1] for attempt in self.data.local.music.get_all_attempts(self.game, self.music_version)] + [ + attempt[1] + for attempt in self.data.local.music.get_all_attempts( + self.game, self.music_version + ) + ] ) elif idtype == APIConstants.ID_TYPE_SONG: if len(ids) == 1: @@ -196,7 +231,12 @@ class StatisticsObject(BaseObject): songid = int(ids[0]) chart = int(ids[1]) retval = self.__aggregate_global( - [attempt[1] for attempt in self.data.local.music.get_all_attempts(self.game, self.music_version, songid=songid, songchart=chart)] + [ + attempt[1] + for attempt in self.data.local.music.get_all_attempts( + self.game, self.music_version, songid=songid, songchart=chart + ) + ] ) elif idtype == APIConstants.ID_TYPE_INSTANCE: songid = int(ids[0]) @@ -206,7 +246,13 @@ class StatisticsObject(BaseObject): if userid is not None: retval = self.__aggregate_local( {userid: self.data.local.user.get_cards(userid)}, - self.data.local.music.get_all_attempts(self.game, self.music_version, songid=songid, songchart=chart, userid=userid) + self.data.local.music.get_all_attempts( + self.game, + self.music_version, + songid=songid, + songchart=chart, + userid=userid, + ), ) elif idtype == APIConstants.ID_TYPE_CARD: id_to_cards: Dict[int, List[str]] = {} @@ -221,10 +267,12 @@ class StatisticsObject(BaseObject): id_to_cards[userid] = self.data.local.user.get_cards(userid) attempts.extend( - self.data.local.music.get_all_attempts(self.game, self.music_version, userid=userid) + self.data.local.music.get_all_attempts( + self.game, self.music_version, userid=userid + ) ) retval = self.__aggregate_local(id_to_cards, attempts) else: - raise APIException('Invalid ID type!') + raise APIException("Invalid ID type!") return retval diff --git a/bemani/backend/base.py b/bemani/backend/base.py index 4ca7b44..3b7b137 100644 --- a/bemani/backend/base.py +++ b/bemani/backend/base.py @@ -3,7 +3,15 @@ import traceback from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Type from typing_extensions import Final -from bemani.common import Model, ValidatedDict, Profile, PlayStatistics, GameConstants, RegionConstants, Time +from bemani.common import ( + Model, + ValidatedDict, + Profile, + PlayStatistics, + GameConstants, + RegionConstants, + Time, +) from bemani.data import Config, Data, Arcade, Machine, UserID, RemoteUser @@ -15,6 +23,7 @@ class Status: """ List of statuses we return to the game for various reasons. """ + SUCCESS: Final[int] = 0 NO_PROFILE: Final[int] = 109 NOT_ALLOWED: Final[int] = 110 @@ -41,7 +50,7 @@ class Factory(ABC): with Base, using Base.register(). Factories specify the game code that they support, which Base will use when routing requests. """ - raise NotImplementedError('Override this in subclass!') + raise NotImplementedError("Override this in subclass!") @classmethod def run_scheduled_work(cls, data: Data, config: Config) -> None: @@ -59,10 +68,10 @@ class Factory(ABC): stack = traceback.format_exc() print(stack) data.local.network.put_event( - 'exception', + "exception", { - 'service': 'scheduler', - 'traceback': stack, + "service": "scheduler", + "traceback": stack, }, ) for event in events: @@ -88,7 +97,13 @@ class Factory(ABC): @classmethod @abstractmethod - def create(cls, data: Data, config: Config, model: Model, parentmodel: Optional[Model]=None) -> Optional['Base']: + def create( + cls, + data: Data, + config: Config, + model: Model, + parentmodel: Optional[Model] = None, + ) -> Optional["Base"]: """ Given a modelstring and an optional parent model, return an instantiated game class that can handle a packet. @@ -106,7 +121,7 @@ class Factory(ABC): A subclass of Base that hopefully has a handle__request method on it, for the particular call that Dispatch wants to resolve, or None if we can't look up a game. """ - raise NotImplementedError('Override this in subclass!') + raise NotImplementedError("Override this in subclass!") class Base(ABC): @@ -172,7 +187,13 @@ class Base(ABC): self.model = model @classmethod - def create(cls, data: Data, config: Config, model: Model, parentmodel: Optional[Model]=None) -> Optional['Base']: + def create( + cls, + data: Data, + config: Config, + model: Model, + parentmodel: Optional[Model] = None, + ) -> Optional["Base"]: """ Given a modelstring and an optional parent model, return an instantiated game class that can handle a packet. @@ -200,7 +221,9 @@ class Base(ABC): return Base(data, config, model) else: # Return the registered module providing this game - return cls.__registered_games[model.gamecode].create(data, config, model, parentmodel=parentmodel) + return cls.__registered_games[model.gamecode].create( + data, config, model, parentmodel=parentmodel + ) @classmethod def register(cls, gamecode: str, handler: Type[Factory]) -> None: @@ -216,7 +239,9 @@ class Base(ABC): cls.__registered_handlers.add(handler) @classmethod - def run_scheduled_work(cls, data: Data, config: Config) -> List[Tuple[str, Dict[str, Any]]]: + def run_scheduled_work( + cls, data: Data, config: Config + ) -> List[Tuple[str, Dict[str, Any]]]: """ Run any out-of-band scheduled work that is applicable to this game. """ @@ -267,7 +292,10 @@ class Base(ABC): Returns: True if the profile exists, False if not. """ - return self.data.local.user.get_profile(self.game, self.version, userid) is not None + return ( + self.data.local.user.get_profile(self.game, self.version, userid) + is not None + ) def get_profile(self, userid: UserID) -> Optional[Profile]: """ @@ -321,9 +349,16 @@ class Base(ABC): or an empty dictionary if nothing was found. """ userids = list(set(userids)) - profiles = self.data.remote.user.get_any_profiles(self.game, self.version, userids) + profiles = self.data.remote.user.get_any_profiles( + self.game, self.version, userids + ) return [ - (userid, profile if profile is not None else Profile(self.game, self.version, "", 0)) + ( + userid, + profile + if profile is not None + else Profile(self.game, self.version, "", 0), + ) for (userid, profile) in profiles ] @@ -336,10 +371,12 @@ class Base(ABC): profile - A dictionary that should be looked up later using get_profile. """ if RemoteUser.is_remote(userid): - raise Exception('Trying to save a remote profile locally!') + raise Exception("Trying to save a remote profile locally!") self.data.local.user.put_profile(self.game, self.version, userid, profile) - def update_play_statistics(self, userid: UserID, stats: Optional[PlayStatistics] = None) -> None: + def update_play_statistics( + self, userid: UserID, stats: Optional[PlayStatistics] = None + ) -> None: """ Given a user ID, calculate new play statistics. @@ -351,59 +388,65 @@ class Base(ABC): stats - A play statistics object we should store extra data from. """ if RemoteUser.is_remote(userid): - raise Exception('Trying to save remote statistics locally!') + raise Exception("Trying to save remote statistics locally!") # We store the play statistics in a series-wide settings blob so its available # across all game versions, since it isn't game-specific. - settings = self.data.local.game.get_settings(self.game, userid) or ValidatedDict({}) + settings = self.data.local.game.get_settings( + self.game, userid + ) or ValidatedDict({}) if stats is not None: for key in stats: # Make sure we don't override anything we manage here if key in { - 'total_plays', - 'today_plays', - 'total_days', - 'first_play_timestamp', - 'last_play_timestamp', - 'last_play_date', - 'consecutive_days', + "total_plays", + "today_plays", + "total_days", + "first_play_timestamp", + "last_play_timestamp", + "last_play_date", + "consecutive_days", }: continue # Safe to copy over settings[key] = stats[key] - settings.replace_int('total_plays', settings.get_int('total_plays') + 1) - settings.replace_int('first_play_timestamp', settings.get_int('first_play_timestamp', Time.now())) - settings.replace_int('last_play_timestamp', Time.now()) + settings.replace_int("total_plays", settings.get_int("total_plays") + 1) + settings.replace_int( + "first_play_timestamp", settings.get_int("first_play_timestamp", Time.now()) + ) + settings.replace_int("last_play_timestamp", Time.now()) - last_play_date = settings.get_int_array('last_play_date', 3) + last_play_date = settings.get_int_array("last_play_date", 3) today_play_date = Time.todays_date() yesterday_play_date = Time.yesterdays_date() if ( - last_play_date[0] == today_play_date[0] and - last_play_date[1] == today_play_date[1] and - last_play_date[2] == today_play_date[2] + last_play_date[0] == today_play_date[0] + and last_play_date[1] == today_play_date[1] + and last_play_date[2] == today_play_date[2] ): # We already played today, add one. - settings.replace_int('today_plays', settings.get_int('today_plays') + 1) + settings.replace_int("today_plays", settings.get_int("today_plays") + 1) else: # We played on a new day, so count total days up. - settings.replace_int('total_days', settings.get_int('total_days') + 1) + settings.replace_int("total_days", settings.get_int("total_days") + 1) # We played only once today (the play we are saving). - settings.replace_int('today_plays', 1) + settings.replace_int("today_plays", 1) if ( - last_play_date[0] == yesterday_play_date[0] and - last_play_date[1] == yesterday_play_date[1] and - last_play_date[2] == yesterday_play_date[2] + last_play_date[0] == yesterday_play_date[0] + and last_play_date[1] == yesterday_play_date[1] + and last_play_date[2] == yesterday_play_date[2] ): # We played yesterday, add one to consecutive days - settings.replace_int('consecutive_days', settings.get_int('consecutive_days') + 1) + settings.replace_int( + "consecutive_days", settings.get_int("consecutive_days") + 1 + ) else: # We haven't played yesterday, so we have only one consecutive day. - settings.replace_int('consecutive_days', 1) - settings.replace_int_array('last_play_date', 3, today_play_date) + settings.replace_int("consecutive_days", 1) + settings.replace_int_array("last_play_date", 3, today_play_date) # Save back self.data.local.game.put_settings(self.game, userid, settings) @@ -442,9 +485,13 @@ class Base(ABC): def get_machine_region(self) -> int: arcade = self.get_arcade() if arcade is None: - return RegionConstants.db_to_game_region(self.requires_extended_regions, self.config.server.region) + return RegionConstants.db_to_game_region( + self.requires_extended_regions, self.config.server.region + ) else: - return RegionConstants.db_to_game_region(self.requires_extended_regions, arcade.region) + return RegionConstants.db_to_game_region( + self.requires_extended_regions, arcade.region + ) def get_game_config(self) -> ValidatedDict: machine = self.data.local.machine.get_machine(self.config.machine.pcbid) @@ -452,7 +499,9 @@ class Base(ABC): # If this machine belongs to an arcade, use its settings. If the settings aren't present, # default to the game's defaults. if machine.arcade is not None: - settings = self.data.local.machine.get_settings(machine.arcade, self.game, self.version, 'game_config') + settings = self.data.local.machine.get_settings( + machine.arcade, self.game, self.version, "game_config" + ) if settings is None: settings = ValidatedDict() return settings @@ -460,7 +509,12 @@ class Base(ABC): # If this machine does not belong to an arcade, use the server-wide settings. If the settings # aren't present, default ot the game's default. else: - settings = self.data.local.machine.get_settings(self.data.local.machine.DEFAULT_SETTINGS_ARCADE, self.game, self.version, 'game_config') + settings = self.data.local.machine.get_settings( + self.data.local.machine.DEFAULT_SETTINGS_ARCADE, + self.game, + self.version, + "game_config", + ) if settings is None: settings = ValidatedDict() return settings @@ -511,38 +565,38 @@ class Base(ABC): ) # Calculate whether we are on our first play of the day or not. - last_play_date = settings.get_int_array('last_play_date', 3) + last_play_date = settings.get_int_array("last_play_date", 3) if ( - last_play_date[0] == today_play_date[0] and - last_play_date[1] == today_play_date[1] and - last_play_date[2] == today_play_date[2] + last_play_date[0] == today_play_date[0] + and last_play_date[1] == today_play_date[1] + and last_play_date[2] == today_play_date[2] ): # We last played today, so the total days and today plays are accurate # as stored. - today_count = settings.get_int('today_plays', 0) - total_days = settings.get_int('total_days', 1) - consecutive_days = settings.get_int('consecutive_days', 1) + today_count = settings.get_int("today_plays", 0) + total_days = settings.get_int("total_days", 1) + consecutive_days = settings.get_int("consecutive_days", 1) else: if ( - last_play_date[0] != 0 and - last_play_date[1] != 0 and - last_play_date[2] != 0 + last_play_date[0] != 0 + and last_play_date[1] != 0 + and last_play_date[2] != 0 ): # We've played before but not today, so the total days is # the stored count plus today. - total_days = settings.get_int('total_days') + 1 + total_days = settings.get_int("total_days") + 1 else: # We've never played before, so the total days is just 1. total_days = 1 if ( - last_play_date[0] == yesterday_play_date[0] and - last_play_date[1] == yesterday_play_date[1] and - last_play_date[2] == yesterday_play_date[2] + last_play_date[0] == yesterday_play_date[0] + and last_play_date[1] == yesterday_play_date[1] + and last_play_date[2] == yesterday_play_date[2] ): # We've played before, and it was yesterday, so today is the # next consecutive day. So add the current value and today. - consecutive_days = settings.get_int('consecutive_days') + 1 + consecutive_days = settings.get_int("consecutive_days") + 1 else: # This is the first consecutive day, we've either never played # or we played a bunch but in the past before yesterday. @@ -553,25 +607,27 @@ class Base(ABC): # Grab any extra settings that a game may have stored here. extra_settings: Dict[str, Any] = { - key: value for (key, value) in settings.items() - if key not in { - 'total_plays', - 'today_plays', - 'total_days', - 'first_play_timestamp', - 'last_play_timestamp', - 'last_play_date', - 'consecutive_days', + key: value + for (key, value) in settings.items() + if key + not in { + "total_plays", + "today_plays", + "total_days", + "first_play_timestamp", + "last_play_timestamp", + "last_play_date", + "consecutive_days", } } return PlayStatistics( self.game, - settings.get_int('total_plays') + 1, + settings.get_int("total_plays") + 1, today_count + 1, total_days, consecutive_days, - settings.get_int('first_play_timestamp', Time.now()), - settings.get_int('last_play_timestamp', Time.now()), + settings.get_int("first_play_timestamp", Time.now()), + settings.get_int("last_play_timestamp", Time.now()), extra_settings, ) diff --git a/bemani/backend/bishi/base.py b/bemani/backend/bishi/base.py index e0de271..8ed75cd 100644 --- a/bemani/backend/bishi/base.py +++ b/bemani/backend/bishi/base.py @@ -15,7 +15,7 @@ class BishiBashiBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): game: GameConstants = GameConstants.BISHI_BASHI - def previous_version(self) -> Optional['BishiBashiBase']: + def previous_version(self) -> Optional["BishiBashiBase"]: """ Returns the previous version of the game, based on this game. Should be overridden. diff --git a/bemani/backend/bishi/bishi.py b/bemani/backend/bishi/bishi.py index 882136b..aef9953 100644 --- a/bemani/backend/bishi/bishi.py +++ b/bemani/backend/bishi/bishi.py @@ -1,6 +1,7 @@ # vim: set fileencoding=utf-8 import binascii import base64 + try: # Python <= 3.9 from collections import Iterable @@ -30,58 +31,58 @@ class TheStarBishiBashi( Return all of our front-end modifiably settings. """ return { - 'bools': [ + "bools": [ { - 'name': 'Force Unlock All Characters', - 'tip': 'Force unlock all characters on select screen.', - 'category': 'game_config', - 'setting': 'force_unlock_characters', + "name": "Force Unlock All Characters", + "tip": "Force unlock all characters on select screen.", + "category": "game_config", + "setting": "force_unlock_characters", }, { - 'name': 'Unlock Non-Gacha Characters', - 'tip': 'Unlock characters that require playing a different game to unlock.', - 'category': 'game_config', - 'setting': 'force_unlock_eamuse_characters', + "name": "Unlock Non-Gacha Characters", + "tip": "Unlock characters that require playing a different game to unlock.", + "category": "game_config", + "setting": "force_unlock_eamuse_characters", }, { - 'name': 'Enable DLC levels', - 'tip': 'Enable extra DLC levels on newer cabinets.', - 'category': 'game_config', - 'setting': 'enable_dlc_levels', + "name": "Enable DLC levels", + "tip": "Enable extra DLC levels on newer cabinets.", + "category": "game_config", + "setting": "enable_dlc_levels", }, ], - 'strs': [ + "strs": [ { - 'name': 'Scrolling Announcement', - 'tip': 'An announcement that scrolls by in attract mode.', - 'category': 'game_config', - 'setting': 'big_announcement', + "name": "Scrolling Announcement", + "tip": "An announcement that scrolls by in attract mode.", + "category": "game_config", + "setting": "big_announcement", }, ], - 'longstrs': [ + "longstrs": [ { - 'name': 'Bulletin Board Announcement', - 'tip': 'An announcement displayed on a bulletin board in attract mode.', - 'category': 'game_config', - 'setting': 'bb_announcement', + "name": "Bulletin Board Announcement", + "tip": "An announcement displayed on a bulletin board in attract mode.", + "category": "game_config", + "setting": "bb_announcement", }, ], } def __update_shop_name(self, profiledata: bytes) -> None: # Figure out the profile type - csvs = profiledata.split(b',') + csvs = profiledata.split(b",") if len(csvs) < 2: # Not long enough to care about return - datatype = csvs[1].decode('ascii') - if datatype != 'IBBDAT00': + datatype = csvs[1].decode("ascii") + if datatype != "IBBDAT00": # Not the right profile type requested return # Grab the shop name try: - shopname = csvs[30].decode('shift-jis') + shopname = csvs[30].decode("shift-jis") except Exception: return self.update_machine_name(shopname) @@ -98,7 +99,9 @@ class TheStarBishiBashi( data = data.replace(";", "#;") return data - def __generate_setting(self, key: str, values: Union[int, str, Sequence[int], Sequence[str]]) -> str: + def __generate_setting( + self, key: str, values: Union[int, str, Sequence[int], Sequence[str]] + ) -> str: if isinstance(values, Iterable) and not isinstance(values, str): values = ",".join(self.__escape_string(x) for x in values) else: @@ -108,18 +111,18 @@ class TheStarBishiBashi( def handle_system_getmaster_request(self, request: Node) -> Node: # See if we can grab the request - data = request.child('data') + data = request.child("data") if not data: - root = Node.void('system') - root.add_child(Node.s32('result', 0)) + root = Node.void("system") + root.add_child(Node.s32("result", 0)) return root # Figure out what type of messsage this is - reqtype = data.child_value('datatype') - reqkey = data.child_value('datakey') + reqtype = data.child_value("datatype") + reqkey = data.child_value("datakey") # System message - root = Node.void('system') + root = Node.void("system") if reqtype == "S_SRVMSG" and reqkey == "INFO": # Settings that we can tweak from the server. @@ -179,40 +182,51 @@ class TheStarBishiBashi( settings: Dict[str, Union[int, str, Sequence[int], Sequence[str]]] = {} game_config = self.get_game_config() - enable_dlc_levels = game_config.get_bool('enable_dlc_levels') + enable_dlc_levels = game_config.get_bool("enable_dlc_levels") if enable_dlc_levels: - settings['MAL'] = 1 - force_unlock_characters = game_config.get_bool('force_unlock_eamuse_characters') + settings["MAL"] = 1 + force_unlock_characters = game_config.get_bool( + "force_unlock_eamuse_characters" + ) if force_unlock_characters: - settings['ALL'] = 1 - scrolling_message = game_config.get_str('big_announcement') + settings["ALL"] = 1 + scrolling_message = game_config.get_str("big_announcement") if scrolling_message: - settings['CM'] = scrolling_message - bb_message = game_config.get_str('bb_announcement') + settings["CM"] = scrolling_message + bb_message = game_config.get_str("bb_announcement") if bb_message: - settings['IM'] = bb_message + settings["IM"] = bb_message # Generate system message - settings_str = ";".join(self.__generate_setting(key, vals) for key, vals in settings.items()) + settings_str = ";".join( + self.__generate_setting(key, vals) for key, vals in settings.items() + ) # Send it to the client, making sure to inform the client that it was valid. - root.add_child(Node.string('strdata1', base64.b64encode(settings_str.encode('ascii')).decode('ascii'))) - root.add_child(Node.string('strdata2', "")) - root.add_child(Node.u64('updatedate', Time.now() * 1000)) - root.add_child(Node.s32('result', 1)) + root.add_child( + Node.string( + "strdata1", + base64.b64encode(settings_str.encode("ascii")).decode("ascii"), + ) + ) + root.add_child(Node.string("strdata2", "")) + root.add_child(Node.u64("updatedate", Time.now() * 1000)) + root.add_child(Node.s32("result", 1)) else: # Unknown message. - root.add_child(Node.s32('result', 0)) + root.add_child(Node.s32("result", 0)) return root def handle_playerdata_usergamedata_send_request(self, request: Node) -> Node: # Look up user by refid - refid = request.child_value('data/eaid') + refid = request.child_value("data/eaid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is None: - root = Node.void('playerdata') - root.add_child(Node.s32('result', 1)) # Unclear if this is the right thing to do here. + root = Node.void("playerdata") + root.add_child( + Node.s32("result", 1) + ) # Unclear if this is the right thing to do here. return root # Extract new profile info from old profile @@ -227,14 +241,14 @@ class TheStarBishiBashi( self.put_profile(userid, newprofile) # Return success! - root = Node.void('playerdata') - root.add_child(Node.s32('result', 0)) + root = Node.void("playerdata") + root.add_child(Node.s32("result", 0)) return root def handle_playerdata_usergamedata_recv_request(self, request: Node) -> Node: # Look up user by refid - refid = request.child_value('data/eaid') - profiletype = request.child_value('data/recv_csv').split(',')[0] + refid = request.child_value("data/eaid") + profiletype = request.child_value("data/recv_csv").split(",")[0] profile = None userid = None if refid is not None: @@ -244,40 +258,44 @@ class TheStarBishiBashi( if profile is not None: return self.format_profile(userid, profiletype, profile) else: - root = Node.void('playerdata') - root.add_child(Node.s32('result', 1)) # Unclear if this is the right thing to do here. + root = Node.void("playerdata") + root.add_child( + Node.s32("result", 1) + ) # Unclear if this is the right thing to do here. return root - def format_profile(self, userid: UserID, profiletype: str, profile: Profile) -> Node: - root = Node.void('playerdata') - root.add_child(Node.s32('result', 0)) - player = Node.void('player') + def format_profile( + self, userid: UserID, profiletype: str, profile: Profile + ) -> Node: + root = Node.void("playerdata") + root.add_child(Node.s32("result", 0)) + player = Node.void("player") root.add_child(player) records = 0 - for i in range(len(profile['strdatas'])): - strdata = profile['strdatas'][i] - bindata = profile['bindatas'][i] + for i in range(len(profile["strdatas"])): + strdata = profile["strdatas"][i] + bindata = profile["bindatas"][i] # Figure out the profile type - csvs = strdata.split(b',') + csvs = strdata.split(b",") if len(csvs) < 2: # Not long enough to care about continue - datatype = csvs[1].decode('ascii') + datatype = csvs[1].decode("ascii") if datatype != profiletype: # Not the right profile type requested continue game_config = self.get_game_config() - force_unlock_characters = game_config.get_bool('force_unlock_characters') + force_unlock_characters = game_config.get_bool("force_unlock_characters") if force_unlock_characters: - csvs[11] = b'3ffffffffffff' + csvs[11] = b"3ffffffffffff" else: # Reward characters based on playing other games on the network - hexdata = csvs[11].decode('ascii') + hexdata = csvs[11].decode("ascii") while (len(hexdata) & 1) != 0: - hexdata = '0' + hexdata + hexdata = "0" + hexdata unlock_bits = [b for b in binascii.unhexlify(hexdata)] while len(unlock_bits) < 7: unlock_bits.insert(0, 0) @@ -309,32 +327,34 @@ class TheStarBishiBashi( # Reconstruct table unlock_bits = unlock_bits[::-1] - csvs[11] = ''.join([f'{x:02x}' for x in unlock_bits]).encode('ascii') + csvs[11] = "".join([f"{x:02x}" for x in unlock_bits]).encode("ascii") # This is a valid profile node for this type, lets return only the profile values - strdata = b','.join(csvs[2:]) - record = Node.void('record') + strdata = b",".join(csvs[2:]) + record = Node.void("record") player.add_child(record) - d = Node.string('d', base64.b64encode(strdata).decode('ascii')) + d = Node.string("d", base64.b64encode(strdata).decode("ascii")) record.add_child(d) - d.add_child(Node.string('bin1', base64.b64encode(bindata).decode('ascii'))) + d.add_child(Node.string("bin1", base64.b64encode(bindata).decode("ascii"))) # Remember that we had this record records = records + 1 - player.add_child(Node.u32('record_num', records)) + player.add_child(Node.u32("record_num", records)) return root - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile, is_new: bool) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile, is_new: bool + ) -> Profile: # Profile save request, data values are base64 encoded. # d is a CSV, and bin1 is binary data. newprofile = oldprofile.clone() strdatas: List[bytes] = [] bindatas: List[bytes] = [] - record = request.child('data/record') + record = request.child("data/record") for node in record.children: - if node.name != 'd': + if node.name != "d": continue profile = base64.b64decode(node.value) @@ -344,10 +364,10 @@ class TheStarBishiBashi( if is_new: self.__update_shop_name(profile) strdatas.append(profile) - bindatas.append(base64.b64decode(node.child_value('bin1'))) + bindatas.append(base64.b64decode(node.child_value("bin1"))) - newprofile['strdatas'] = strdatas - newprofile['bindatas'] = bindatas + newprofile["strdatas"] = strdatas + newprofile["bindatas"] = bindatas # Keep track of play statistics across all versions self.update_play_statistics(userid) diff --git a/bemani/backend/bishi/factory.py b/bemani/backend/bishi/factory.py index 6f85807..2a5cbce 100644 --- a/bemani/backend/bishi/factory.py +++ b/bemani/backend/bishi/factory.py @@ -14,13 +14,19 @@ class BishiBashiFactory(Factory): @classmethod def register_all(cls) -> None: - for gamecode in ['IBB']: + for gamecode in ["IBB"]: Base.register(gamecode, BishiBashiFactory) @classmethod - def create(cls, data: Data, config: Config, model: Model, parentmodel: Optional[Model]=None) -> Optional[Base]: + def create( + cls, + data: Data, + config: Config, + model: Model, + parentmodel: Optional[Model] = None, + ) -> Optional[Base]: - if model.gamecode == 'IBB': + if model.gamecode == "IBB": return TheStarBishiBashi(data, config, model) # Unknown game version diff --git a/bemani/backend/core/cardmng.py b/bemani/backend/core/cardmng.py index 3befbf2..dd0ba06 100644 --- a/bemani/backend/core/cardmng.py +++ b/bemani/backend/core/cardmng.py @@ -19,14 +19,14 @@ class CardManagerHandler(Base): # Given a cardid, look up the dataid/refid (same thing in this system). # If the card doesn't exist or isn't allowed, return a status specifying this # instead of the results of the dataid/refid lookup. - cardid = request.attribute('cardid') - modelstring = request.attribute('model') + cardid = request.attribute("cardid") + modelstring = request.attribute("model") userid = self.data.local.user.from_cardid(cardid) if userid is None: # This user doesn't exist, force system to create new account - root = Node.void('cardmng') - root.set_attribute('status', str(Status.NOT_REGISTERED)) + root = Node.void("cardmng") + root.set_attribute("status", str(Status.NOT_REGISTERED)) return root # Special handling for looking up whether the previous game's profile existed. If we @@ -43,77 +43,82 @@ class CardManagerHandler(Base): refid = self.data.local.user.get_refid(self.game, self.version, userid) paseli_enabled = self.supports_paseli and self.config.paseli.enabled - newflag = self.data.remote.user.get_any_profile(self.game, self.version, userid) is None + newflag = ( + self.data.remote.user.get_any_profile(self.game, self.version, userid) + is None + ) - root = Node.void('cardmng') - root.set_attribute('refid', refid) - root.set_attribute('dataid', refid) + root = Node.void("cardmng") + root.set_attribute("refid", refid) + root.set_attribute("dataid", refid) # Unsure what this does, but it appears not to matter so we set it to my best guess. - root.set_attribute('newflag', '1' if newflag else '0') + root.set_attribute("newflag", "1" if newflag else "0") # Whether we've bound a profile to this refid/dataid or not. This includes current profiles and any # older game profiles that might exist that we should do a conversion from. - root.set_attribute('binded', '1' if bound else '0') + root.set_attribute("binded", "1" if bound else "0") # Whether this version of the profile is expired (was converted to newer version). We support forwards # and backwards compatibility so some games will always set this to 0. - root.set_attribute('expired', '1' if expired else '0') + root.set_attribute("expired", "1" if expired else "0") # Whether to allow paseli, as enabled by the operator and arcade owner. - root.set_attribute('ecflag', '1' if paseli_enabled else '0') + root.set_attribute("ecflag", "1" if paseli_enabled else "0") # I have absolutely no idea what these do. - root.set_attribute('useridflag', '1') - root.set_attribute('extidflag', '1') + root.set_attribute("useridflag", "1") + root.set_attribute("extidflag", "1") return root def handle_cardmng_authpass_request(self, request: Node) -> Node: # Given a dataid/refid previously found via inquire, verify the pin - refid = request.attribute('refid') - pin = request.attribute('pass') + refid = request.attribute("refid") + pin = request.attribute("pass") userid = self.data.local.user.from_refid(self.game, self.version, refid) if userid is not None: valid = self.data.local.user.validate_pin(userid, pin) else: valid = False - root = Node.void('cardmng') - root.set_attribute('status', str(Status.SUCCESS if valid else Status.INVALID_PIN)) + root = Node.void("cardmng") + root.set_attribute( + "status", str(Status.SUCCESS if valid else Status.INVALID_PIN) + ) return root def handle_cardmng_getrefid_request(self, request: Node) -> Node: # Given a cardid and a pin, register the card with the system and generate a new dataid/refid + extid - cardid = request.attribute('cardid') - pin = request.attribute('passwd') + cardid = request.attribute("cardid") + pin = request.attribute("passwd") userid = self.data.local.user.create_account(cardid, pin) if userid is None: # This user can't be created - root = Node.void('cardmng') - root.set_attribute('status', str(Status.NOT_ALLOWED)) + root = Node.void("cardmng") + root.set_attribute("status", str(Status.NOT_ALLOWED)) return root refid = self.data.local.user.create_refid(self.game, self.version, userid) - root = Node.void('cardmng') - root.set_attribute('dataid', refid) - root.set_attribute('refid', refid) + root = Node.void("cardmng") + root.set_attribute("dataid", refid) + root.set_attribute("refid", refid) return root def handle_cardmng_bindmodel_request(self, request: Node) -> Node: # Given a refid, bind the user's card to the current version of the game - refid = request.attribute('refid') + refid = request.attribute("refid") userid = self.data.local.user.from_refid(self.game, self.version, refid) self.bind_profile(userid) - root = Node.void('cardmng') - root.set_attribute('dataid', refid) + root = Node.void("cardmng") + root.set_attribute("dataid", refid) return root def handle_cardmng_getkeepspan_request(self, request: Node) -> Node: # Unclear what this method does, return an arbitrary span - root = Node.void('cardmng') - root.set_attribute('keepspan', '30') + root = Node.void("cardmng") + root.set_attribute("keepspan", "30") return root def handle_cardmng_getdatalist_request(self, request: Node) -> Node: # Unclear what this method does, return a dummy response - root = Node.void('cardmng') + root = Node.void("cardmng") return root diff --git a/bemani/backend/core/core.py b/bemani/backend/core/core.py index 6e9e729..fe0a7fa 100644 --- a/bemani/backend/core/core.py +++ b/bemani/backend/core/core.py @@ -16,42 +16,45 @@ class CoreHandler(Base): each server which handles a particular service. For us, this is always our URL since we serve everything. """ + def item(name: str, url: str) -> Node: - node = Node.void('item') - node.set_attribute('name', name) - node.set_attribute('url', url) + node = Node.void("item") + node.set_attribute("name", name) + node.set_attribute("url", url) return node url = f'{"https" if self.config.server.https else "http"}://{self.config.server.address}:{self.config.server.port}/' - root = Node.void('services') - root.set_attribute('expire', '600') + root = Node.void("services") + root.set_attribute("expire", "600") # This can be set to 'operation', 'debug', 'test', and 'factory'. - root.set_attribute('mode', 'operation') - root.set_attribute('product_domain', '1') + root.set_attribute("mode", "operation") + root.set_attribute("product_domain", "1") - root.add_child(item('cardmng', url)) - root.add_child(item('dlstatus', url)) - root.add_child(item('eacoin', url)) - root.add_child(item('facility', url)) - root.add_child(item('lobby', url)) - root.add_child(item('local', url)) - root.add_child(item('message', url)) - root.add_child(item('package', url)) - root.add_child(item('pcbevent', url)) - root.add_child(item('pcbtracker', url)) - root.add_child(item('pkglist', url)) - root.add_child(item('posevent', url)) + root.add_child(item("cardmng", url)) + root.add_child(item("dlstatus", url)) + root.add_child(item("eacoin", url)) + root.add_child(item("facility", url)) + root.add_child(item("lobby", url)) + root.add_child(item("local", url)) + root.add_child(item("message", url)) + root.add_child(item("package", url)) + root.add_child(item("pcbevent", url)) + root.add_child(item("pcbtracker", url)) + root.add_child(item("pkglist", url)) + root.add_child(item("posevent", url)) for srv in self.extra_services: root.add_child(item(srv, url)) - root.add_child(item('ntp', 'ntp://pool.ntp.org/')) + root.add_child(item("ntp", "ntp://pool.ntp.org/")) # Translate keepalive to a raw IP because we can't give out a host here keepalive = socket.gethostbyname(self.config.server.keepalive) - root.add_child(item( - 'keepalive', - f'http://{keepalive}/core/keepalive?pa={keepalive}&ia={keepalive}&ga={keepalive}&ma={keepalive}&t1=2&t2=10', - )) + root.add_child( + item( + "keepalive", + f"http://{keepalive}/core/keepalive?pa={keepalive}&ia={keepalive}&ga={keepalive}&ma={keepalive}&t1=2&t2=10", + ) + ) return root def handle_pcbtracker_alive_request(self, request: Node) -> Node: @@ -60,9 +63,12 @@ class CoreHandler(Base): which returns whether PASELI should be active or not for this session. """ # Reports that a machine is booting. Overloaded to enable/disable paseli - root = Node.void('pcbtracker') - root.set_attribute('ecenable', '1' if (self.supports_paseli and self.config.paseli.enabled) else '0') - root.set_attribute('expire', '600') + root = Node.void("pcbtracker") + root.set_attribute( + "ecenable", + "1" if (self.supports_paseli and self.config.paseli.enabled) else "0", + ) + root.set_attribute("expire", "600") return root def handle_pcbevent_put_request(self, request: Node) -> Node: @@ -70,23 +76,23 @@ class CoreHandler(Base): Handle a PCBEvent request. We do nothing for this aside from logging the event. """ for item in request.children: - if item.name == 'item': - name = item.child_value('name') - value = item.child_value('value') - timestamp = item.child_value('time') + if item.name == "item": + name = item.child_value("name") + value = item.child_value("value") + timestamp = item.child_value("time") self.data.local.network.put_event( - 'pcbevent', + "pcbevent", { - 'name': name, - 'value': value, - 'model': str(self.model), - 'pcbid': self.config.machine.pcbid, - 'ip': self.config.client.address, + "name": name, + "value": value, + "model": str(self.model), + "pcbid": self.config.machine.pcbid, + "ip": self.config.client.address, }, timestamp=timestamp, ) - return Node.void('pcbevent') + return Node.void("pcbevent") def handle_package_list_request(self, request: Node) -> Node: """ @@ -94,8 +100,8 @@ class CoreHandler(Base): We don't support this at the moment. """ # List all available update packages on the server - root = Node.void('package') - root.set_attribute('expire', '600') + root = Node.void("package") + root.set_attribute("expire", "600") return root def handle_message_get_request(self, request: Node) -> Node: @@ -103,8 +109,8 @@ class CoreHandler(Base): I have absolutely no fucking idea what this does, but it might be for operator messages? """ - root = Node.void('message') - root.set_attribute('expire', '600') + root = Node.void("message") + root.set_attribute("expire", "600") return root def handle_dlstatus_progress_request(self, request: Node) -> Node: @@ -112,7 +118,7 @@ class CoreHandler(Base): I have absolutely no fucking idea what this does either, download status reports maybe? """ - return Node.void('dlstatus') + return Node.void("dlstatus") def handle_facility_get_request(self, request: Node) -> Node: """ @@ -166,45 +172,57 @@ class CoreHandler(Base): country = "XX" regionstr = "" - root = Node.void('facility') - root.set_attribute('expire', '600') - location = Node.void('location') - location.add_child(Node.string('id', ID.format_machine_id(machine.id, region=country))) - location.add_child(Node.string('country', country)) - location.add_child(Node.string('region', regionstr)) - location.add_child(Node.string('name', machine.name)) - location.add_child(Node.u8('type', 0)) + root = Node.void("facility") + root.set_attribute("expire", "600") + location = Node.void("location") + location.add_child( + Node.string("id", ID.format_machine_id(machine.id, region=country)) + ) + location.add_child(Node.string("country", country)) + location.add_child(Node.string("region", regionstr)) + location.add_child(Node.string("name", machine.name)) + location.add_child(Node.u8("type", 0)) - line = Node.void('line') - line.add_child(Node.string('id', '.')) - line.add_child(Node.u8('class', 0)) + line = Node.void("line") + line.add_child(Node.string("id", ".")) + line.add_child(Node.u8("class", 0)) - portfw = Node.void('portfw') - portfw.add_child(Node.ipv4('globalip', self.config.client.address)) - portfw.add_child(Node.u16('globalport', machine.port)) - portfw.add_child(Node.u16('privateport', machine.port)) + portfw = Node.void("portfw") + portfw.add_child(Node.ipv4("globalip", self.config.client.address)) + portfw.add_child(Node.u16("globalport", machine.port)) + portfw.add_child(Node.u16("privateport", machine.port)) - public = Node.void('public') - public.add_child(Node.u8('flag', 1)) - public.add_child(Node.string('name', '.')) - public.add_child(Node.string('latitude', '0')) - public.add_child(Node.string('longitude', '0')) + public = Node.void("public") + public.add_child(Node.u8("flag", 1)) + public.add_child(Node.string("name", ".")) + public.add_child(Node.string("latitude", "0")) + public.add_child(Node.string("longitude", "0")) - share = Node.void('share') - eacoin = Node.void('eacoin') - eacoin.add_child(Node.s32('notchamount', 3000)) - eacoin.add_child(Node.s32('notchcount', 3)) - eacoin.add_child(Node.s32('supplylimit', 10000)) + share = Node.void("share") + eacoin = Node.void("eacoin") + eacoin.add_child(Node.s32("notchamount", 3000)) + eacoin.add_child(Node.s32("notchcount", 3)) + eacoin.add_child(Node.s32("supplylimit", 10000)) - eapass = Node.void('eapass') - eapass.add_child(Node.u16('valid', 365)) + eapass = Node.void("eapass") + eapass.add_child(Node.u16("valid", 365)) - url = Node.void('url') - url.add_child(Node.string('eapass', self.config.server.uri or 'www.ea-pass.konami.net')) - url.add_child(Node.string('arcadefan', self.config.server.uri or 'www.konami.jp/am')) - url.add_child(Node.string('konaminetdx', self.config.server.uri or 'http://am.573.jp')) - url.add_child(Node.string('konamiid', self.config.server.uri or 'https://id.konami.net')) - url.add_child(Node.string('eagate', self.config.server.uri or 'http://eagate.573.jp')) + url = Node.void("url") + url.add_child( + Node.string("eapass", self.config.server.uri or "www.ea-pass.konami.net") + ) + url.add_child( + Node.string("arcadefan", self.config.server.uri or "www.konami.jp/am") + ) + url.add_child( + Node.string("konaminetdx", self.config.server.uri or "http://am.573.jp") + ) + url.add_child( + Node.string("konamiid", self.config.server.uri or "https://id.konami.net") + ) + url.add_child( + Node.string("eagate", self.config.server.uri or "http://eagate.573.jp") + ) share.add_child(eacoin) share.add_child(url) diff --git a/bemani/backend/core/eacoin.py b/bemani/backend/core/eacoin.py index c852265..8eb9e1b 100644 --- a/bemani/backend/core/eacoin.py +++ b/bemani/backend/core/eacoin.py @@ -27,32 +27,32 @@ class PASELIHandler(Base): if not self.config.paseli.enabled: # Refuse to respond, we don't have PASELI enabled print("PASELI not enabled, ignoring eacoin request") - root = Node.void('eacoin') - root.set_attribute('status', str(Status.NOT_ALLOWED)) + root = Node.void("eacoin") + root.set_attribute("status", str(Status.NOT_ALLOWED)) return root - root = Node.void('eacoin') - cardid = request.child_value('cardid') - pin = request.child_value('passwd') + root = Node.void("eacoin") + cardid = request.child_value("cardid") + pin = request.child_value("passwd") if cardid is None or pin is None: # Refuse to return anything print("Invalid eacoin checkin request, missing cardid or pin") - root.set_attribute('status', str(Status.NO_PROFILE)) + root.set_attribute("status", str(Status.NO_PROFILE)) return root userid = self.data.local.user.from_cardid(cardid) if userid is None: # Refuse to do anything print("No user for eacoin checkin request") - root.set_attribute('status', str(Status.NO_PROFILE)) + root.set_attribute("status", str(Status.NO_PROFILE)) return root valid = self.data.local.user.validate_pin(userid, pin) if not valid: # Refuse to do anything print("User entered invalid pin for eacoin checkin request") - root.set_attribute('status', str(Status.INVALID_PIN)) + root.set_attribute("status", str(Status.INVALID_PIN)) return root session = self.data.local.user.create_session(userid) @@ -65,75 +65,77 @@ class PASELIHandler(Base): # enabled, so there's no way to find a balance. balance = 0 else: - balance = self.data.local.user.get_balance(userid, self.config.machine.arcade) + balance = self.data.local.user.get_balance( + userid, self.config.machine.arcade + ) - root.add_child(Node.s16('sequence', 0)) - root.add_child(Node.u8('acstatus', 0)) - root.add_child(Node.string('acid', 'DUMMY_ID')) - root.add_child(Node.string('acname', 'DUMMY_NAME')) - root.add_child(Node.s32('balance', balance)) - root.add_child(Node.string('sessid', session)) + root.add_child(Node.s16("sequence", 0)) + root.add_child(Node.u8("acstatus", 0)) + root.add_child(Node.string("acid", "DUMMY_ID")) + root.add_child(Node.string("acname", "DUMMY_NAME")) + root.add_child(Node.s32("balance", balance)) + root.add_child(Node.string("sessid", session)) return root def handle_eacoin_opcheckin_request(self, request: Node) -> Node: if not self.config.paseli.enabled: # Refuse to respond, we don't have PASELI enabled print("PASELI not enabled, ignoring eacoin request") - root = Node.void('eacoin') - root.set_attribute('status', str(Status.NOT_ALLOWED)) + root = Node.void("eacoin") + root.set_attribute("status", str(Status.NOT_ALLOWED)) return root - root = Node.void('eacoin') - passwd = request.child_value('passwd') + root = Node.void("eacoin") + passwd = request.child_value("passwd") if passwd is None: # Refuse to return anything print("Invalid eacoin checkin request, missing passwd") - root.set_attribute('status', str(Status.NO_PROFILE)) + root.set_attribute("status", str(Status.NO_PROFILE)) return root if self.config.machine.arcade is None: # Machine doesn't belong to an arcade print("Machine doesn't belong to an arcade") - root.set_attribute('status', str(Status.NO_PROFILE)) + root.set_attribute("status", str(Status.NO_PROFILE)) return root arcade = self.data.local.machine.get_arcade(self.config.machine.arcade) if arcade is None: # Refuse to do anything print("No arcade for operator checkin request") - root.set_attribute('status', str(Status.NO_PROFILE)) + root.set_attribute("status", str(Status.NO_PROFILE)) return root if arcade.pin != passwd: # Refuse to do anything print("User entered invalid pin for operator checkin request") - root.set_attribute('status', str(Status.INVALID_PIN)) + root.set_attribute("status", str(Status.INVALID_PIN)) return root session = self.data.local.machine.create_session(arcade.id) - root.add_child(Node.string('sessid', session)) + root.add_child(Node.string("sessid", session)) return root def handle_eacoin_consume_request(self, request: Node) -> Node: if not self.config.paseli.enabled: # Refuse to respond, we don't have PASELI enabled print("PASELI not enabled, ignoring eacoin request") - root = Node.void('eacoin') - root.set_attribute('status', str(Status.NOT_ALLOWED)) + root = Node.void("eacoin") + root.set_attribute("status", str(Status.NOT_ALLOWED)) return root def make_resp(status: int, balance: int) -> Node: - root = Node.void('eacoin') - root.add_child(Node.u8('acstatus', status)) - root.add_child(Node.u8('autocharge', 0)) - root.add_child(Node.s32('balance', balance)) + root = Node.void("eacoin") + root.add_child(Node.u8("acstatus", status)) + root.add_child(Node.u8("autocharge", 0)) + root.add_child(Node.s32("balance", balance)) return root - session = request.child_value('sessid') - payment = request.child_value('payment') - service = request.child_value('service') - details = request.child_value('detail') + session = request.child_value("sessid") + payment = request.child_value("payment") + service = request.child_value("service") + details = request.child_value("detail") if session is None or payment is None: # Refuse to do anything print("Invalid eacoin consume request, missing sessid or payment") @@ -156,20 +158,27 @@ class PASELIHandler(Base): else: # Look up the new balance based on this delta. If there isn't enough, # we will end up returning None here and exit without performing. - balance = self.data.local.user.update_balance(userid, self.config.machine.arcade, -payment) + balance = self.data.local.user.update_balance( + userid, self.config.machine.arcade, -payment + ) if balance is None: print("Not enough balance for eacoin consume request") - return make_resp(1, self.data.local.user.get_balance(userid, self.config.machine.arcade)) + return make_resp( + 1, + self.data.local.user.get_balance( + userid, self.config.machine.arcade + ), + ) else: self.data.local.network.put_event( - 'paseli_transaction', + "paseli_transaction", { - 'delta': -payment, - 'balance': balance, - 'service': -service, - 'reason': details, - 'pcbid': self.config.machine.pcbid, + "delta": -payment, + "balance": balance, + "service": -service, + "reason": details, + "pcbid": self.config.machine.pcbid, }, userid=userid, arcadeid=self.config.machine.arcade, @@ -181,16 +190,16 @@ class PASELIHandler(Base): if not self.config.paseli.enabled: # Refuse to respond, we don't have PASELI enabled print("PASELI not enabled, ignoring eacoin request") - root = Node.void('eacoin') - root.set_attribute('status', str(Status.NOT_ALLOWED)) + root = Node.void("eacoin") + root.set_attribute("status", str(Status.NOT_ALLOWED)) return root - root = Node.void('eacoin') - sessid = request.child_value('sessid') - logtype = request.child_value('logtype') - target = request.child_value('target') - limit = request.child_value('perpage') - offset = request.child_value('offset') + root = Node.void("eacoin") + sessid = request.child_value("sessid") + logtype = request.child_value("logtype") + target = request.child_value("target") + limit = request.child_value("perpage") + offset = request.child_value("offset") # Try to determine whether its a user or an arcade session userid = self.data.local.user.from_session(sessid) @@ -217,47 +226,59 @@ class PASELIHandler(Base): events = self.data.local.network.get_events( userid=userid, arcadeid=arcadeid, - event='paseli_transaction', + event="paseli_transaction", ) # Further filter it down to the current PCBID - events = [event for event in events if event.data.get('pcbid') == target] + events = [event for event in events if event.data.get("pcbid") == target] # Grab the end of day today as a timestamp end_of_today = Time.end_of_today() - time_format = '%Y-%m-%d %H:%M:%S' - date_format = '%Y-%m-%d' + time_format = "%Y-%m-%d %H:%M:%S" + date_format = "%Y-%m-%d" # Set up common structure lognode = Node.void(logtype) - topic = Node.void('topic') + topic = Node.void("topic") lognode.add_child(topic) - summary = Node.void('summary') + summary = Node.void("summary") lognode.add_child(summary) # Display what day we are summed to - topic.add_child(Node.string('sumdate', Time.format(Time.now(), date_format))) + topic.add_child(Node.string("sumdate", Time.format(Time.now(), date_format))) - if logtype == 'last7days': + if logtype == "last7days": # We show today in the today total, last 7 days prior in the week total beginning_of_today = end_of_today - Time.SECONDS_IN_DAY end_of_week = beginning_of_today beginning_of_week = end_of_week - Time.SECONDS_IN_WEEK - topic.add_child(Node.string('sumfrom', Time.format(beginning_of_week, date_format))) - topic.add_child(Node.string('sumto', Time.format(end_of_week, date_format))) - today_total = sum([ - -event.data.get_int('delta') for event in events - if event.timestamp >= beginning_of_today and event.timestamp < end_of_today - ]) + topic.add_child( + Node.string("sumfrom", Time.format(beginning_of_week, date_format)) + ) + topic.add_child(Node.string("sumto", Time.format(end_of_week, date_format))) + today_total = sum( + [ + -event.data.get_int("delta") + for event in events + if event.timestamp >= beginning_of_today + and event.timestamp < end_of_today + ] + ) - today_total = sum([ - -event.data.get_int('delta') for event in events - if event.timestamp >= beginning_of_today and event.timestamp < end_of_today - ]) + today_total = sum( + [ + -event.data.get_int("delta") + for event in events + if event.timestamp >= beginning_of_today + and event.timestamp < end_of_today + ] + ) week_txns = [ - -event.data.get_int('delta') for event in events - if event.timestamp >= beginning_of_week and event.timestamp < end_of_week + -event.data.get_int("delta") + for event in events + if event.timestamp >= beginning_of_week + and event.timestamp < end_of_week ] week_total = sum(week_txns) if len(week_txns) > 0: @@ -272,23 +293,38 @@ class PASELIHandler(Base): end_of_day = end_of_week - (days * Time.SECONDS_IN_DAY) start_of_day = end_of_day - Time.SECONDS_IN_DAY - items.append(sum([ - -event.data.get_int('delta') for event in events - if event.timestamp >= start_of_day and event.timestamp < end_of_day - ])) + items.append( + sum( + [ + -event.data.get_int("delta") + for event in events + if event.timestamp >= start_of_day + and event.timestamp < end_of_day + ] + ) + ) - topic.add_child(Node.s32('today', today_total)) - topic.add_child(Node.s32('average', week_avg)) - topic.add_child(Node.s32('total', week_total)) - summary.add_child(Node.s32_array('items', items)) + topic.add_child(Node.s32("today", today_total)) + topic.add_child(Node.s32("average", week_avg)) + topic.add_child(Node.s32("total", week_total)) + summary.add_child(Node.s32_array("items", items)) - if logtype == 'last52weeks': + if logtype == "last52weeks": # Start one week back, since the operator can look at last7days for newer stuff. beginning_of_today = end_of_today - Time.SECONDS_IN_DAY end_of_52_weeks = beginning_of_today - Time.SECONDS_IN_WEEK - topic.add_child(Node.string('sumfrom', Time.format(end_of_52_weeks - (52 * Time.SECONDS_IN_WEEK), date_format))) - topic.add_child(Node.string('sumto', Time.format(end_of_52_weeks, date_format))) + topic.add_child( + Node.string( + "sumfrom", + Time.format( + end_of_52_weeks - (52 * Time.SECONDS_IN_WEEK), date_format + ), + ) + ) + topic.add_child( + Node.string("sumto", Time.format(end_of_52_weeks, date_format)) + ) # We index backwards, where index 0 = the first week back, 1 = the next week back after that, etc... items = [] @@ -296,45 +332,53 @@ class PASELIHandler(Base): end_of_range = end_of_52_weeks - (weeks * Time.SECONDS_IN_WEEK) beginning_of_range = end_of_range - Time.SECONDS_IN_WEEK - items.append(sum([ - -event.data.get_int('delta') for event in events - if event.timestamp >= beginning_of_range and event.timestamp < end_of_range - ])) + items.append( + sum( + [ + -event.data.get_int("delta") + for event in events + if event.timestamp >= beginning_of_range + and event.timestamp < end_of_range + ] + ) + ) - summary.add_child(Node.s32_array('items', items)) + summary.add_child(Node.s32_array("items", items)) - if logtype == 'eachday': + if logtype == "eachday": start_ts = Time.now() end_ts = Time.now() weekdays = [0] * 7 for event in events: event_day = Time.days_into_week(event.timestamp) - weekdays[event_day] = weekdays[event_day] - event.data.get_int('delta') + weekdays[event_day] = weekdays[event_day] - event.data.get_int("delta") if event.timestamp < start_ts: start_ts = event.timestamp - topic.add_child(Node.string('sumfrom', Time.format(start_ts, date_format))) - topic.add_child(Node.string('sumto', Time.format(end_ts, date_format))) - summary.add_child(Node.s32_array('items', weekdays)) + topic.add_child(Node.string("sumfrom", Time.format(start_ts, date_format))) + topic.add_child(Node.string("sumto", Time.format(end_ts, date_format))) + summary.add_child(Node.s32_array("items", weekdays)) - if logtype == 'eachhour': + if logtype == "eachhour": start_ts = Time.now() end_ts = Time.now() hours = [0] * 24 for event in events: - event_hour = int((event.timestamp % Time.SECONDS_IN_DAY) / Time.SECONDS_IN_HOUR) - hours[event_hour] = hours[event_hour] - event.data.get_int('delta') + event_hour = int( + (event.timestamp % Time.SECONDS_IN_DAY) / Time.SECONDS_IN_HOUR + ) + hours[event_hour] = hours[event_hour] - event.data.get_int("delta") if event.timestamp < start_ts: start_ts = event.timestamp - topic.add_child(Node.string('sumfrom', Time.format(start_ts, date_format))) - topic.add_child(Node.string('sumto', Time.format(end_ts, date_format))) - summary.add_child(Node.s32_array('items', hours)) + topic.add_child(Node.string("sumfrom", Time.format(start_ts, date_format))) + topic.add_child(Node.string("sumto", Time.format(end_ts, date_format))) + summary.add_child(Node.s32_array("items", hours)) - if logtype == 'detail': - history = Node.void('history') + if logtype == "detail": + history = Node.void("history") lognode.add_child(history) # Respect details paging @@ -345,7 +389,7 @@ class PASELIHandler(Base): # Output the details themselves for event in events: - card_no = '' + card_no = "" if event.userid is not None: user = self.data.local.user.get_user(event.userid) if user is not None: @@ -353,24 +397,30 @@ class PASELIHandler(Base): if len(cards) > 0: card_no = CardCipher.encode(cards[0]) - item = Node.void('item') + item = Node.void("item") history.add_child(item) - item.add_child(Node.string('date', Time.format(event.timestamp, time_format))) - item.add_child(Node.s32('consume', -event.data.get_int('delta'))) - item.add_child(Node.s32('service', -event.data.get_int('service'))) - item.add_child(Node.string('cardtype', '')) - item.add_child(Node.string('cardno', ' ' * self.paseli_padding + card_no)) - item.add_child(Node.string('title', '')) - item.add_child(Node.string('systemid', '')) + item.add_child( + Node.string("date", Time.format(event.timestamp, time_format)) + ) + item.add_child(Node.s32("consume", -event.data.get_int("delta"))) + item.add_child(Node.s32("service", -event.data.get_int("service"))) + item.add_child(Node.string("cardtype", "")) + item.add_child( + Node.string("cardno", " " * self.paseli_padding + card_no) + ) + item.add_child(Node.string("title", "")) + item.add_child(Node.string("systemid", "")) - if logtype == 'lastmonths': + if logtype == "lastmonths": year, month, _ = Time.todays_date() this_month = Time.timestamp_from_date(year, month) last_month = Time.timestamp_from_date(year, month - 1) month_before = Time.timestamp_from_date(year, month - 2) - topic.add_child(Node.string('sumfrom', Time.format(month_before, date_format))) - topic.add_child(Node.string('sumto', Time.format(this_month, date_format))) + topic.add_child( + Node.string("sumfrom", Time.format(month_before, date_format)) + ) + topic.add_child(Node.string("sumto", Time.format(this_month, date_format))) for (start, end) in [(month_before, last_month), (last_month, this_month)]: year, month, _ = Time.date_from_timestamp(start) @@ -384,18 +434,24 @@ class PASELIHandler(Base): items.append(0) else: # Sum up all the txns for this day - items.append(sum([ - -event.data.get_int('delta') for event in events - if event.timestamp >= begin_ts and event.timestamp < end_ts - ])) + items.append( + sum( + [ + -event.data.get_int("delta") + for event in events + if event.timestamp >= begin_ts + and event.timestamp < end_ts + ] + ) + ) - item = Node.void('item') + item = Node.void("item") summary.add_child(item) - item.add_child(Node.s32('year', year)) - item.add_child(Node.s32('month', month)) - item.add_child(Node.s32_array('items', items)) + item.add_child(Node.s32("year", year)) + item.add_child(Node.s32("month", month)) + item.add_child(Node.s32_array("items", items)) - root.add_child(Node.u8('processing', 0)) + root.add_child(Node.u8("processing", 0)) root.add_child(lognode) return root @@ -403,37 +459,37 @@ class PASELIHandler(Base): if not self.config.paseli.enabled: # Refuse to respond, we don't have PASELI enabled print("PASELI not enabled, ignoring eacoin request") - root = Node.void('eacoin') - root.set_attribute('status', str(Status.NOT_ALLOWED)) + root = Node.void("eacoin") + root.set_attribute("status", str(Status.NOT_ALLOWED)) return root - root = Node.void('eacoin') - oldpass = request.child_value('passwd') - newpass = request.child_value('newpasswd') + root = Node.void("eacoin") + oldpass = request.child_value("passwd") + newpass = request.child_value("newpasswd") if oldpass is None or newpass is None: # Refuse to return anything print("Invalid eacoin pass change request, missing passwd") - root.set_attribute('status', str(Status.NO_PROFILE)) + root.set_attribute("status", str(Status.NO_PROFILE)) return root if self.config.machine.arcade is None: # Machine doesn't belong to an arcade print("Machine doesn't belong to an arcade") - root.set_attribute('status', str(Status.NO_PROFILE)) + root.set_attribute("status", str(Status.NO_PROFILE)) return root arcade = self.data.local.machine.get_arcade(self.config.machine.arcade) if arcade is None: # Refuse to do anything print("No arcade for operator pass change request") - root.set_attribute('status', str(Status.NO_PROFILE)) + root.set_attribute("status", str(Status.NO_PROFILE)) return root if arcade.pin != oldpass: # Refuse to do anything print("User entered invalid pin for operator pass change request") - root.set_attribute('status', str(Status.INVALID_PIN)) + root.set_attribute("status", str(Status.INVALID_PIN)) return root arcade.pin = newpass @@ -444,30 +500,30 @@ class PASELIHandler(Base): if not self.config.paseli.enabled: # Refuse to respond, we don't have PASELI enabled print("PASELI not enabled, ignoring eacoin request") - root = Node.void('eacoin') - root.set_attribute('status', str(Status.NOT_ALLOWED)) + root = Node.void("eacoin") + root.set_attribute("status", str(Status.NOT_ALLOWED)) return root - session = request.child_value('sessid') + session = request.child_value("sessid") if session is not None: # Destroy the session so it can't be used for any other purchases self.data.local.user.destroy_session(session) - root = Node.void('eacoin') + root = Node.void("eacoin") return root def handle_eacoin_opcheckout_request(self, request: Node) -> Node: if not self.config.paseli.enabled: # Refuse to respond, we don't have PASELI enabled print("PASELI not enabled, ignoring eacoin request") - root = Node.void('eacoin') - root.set_attribute('status', str(Status.NOT_ALLOWED)) + root = Node.void("eacoin") + root.set_attribute("status", str(Status.NOT_ALLOWED)) return root - session = request.child_value('sessid') + session = request.child_value("sessid") if session is not None: # Destroy the session so it can't be used for any other purchases self.data.local.machine.destroy_session(session) - root = Node.void('eacoin') + root = Node.void("eacoin") return root diff --git a/bemani/backend/ddr/base.py b/bemani/backend/ddr/base.py index af613b7..4e252d0 100644 --- a/bemani/backend/ddr/base.py +++ b/bemani/backend/ddr/base.py @@ -4,7 +4,14 @@ from typing_extensions import Final from bemani.backend.base import Base from bemani.backend.core import CoreHandler, CardManagerHandler, PASELIHandler -from bemani.common import Model, Profile, ValidatedDict, GameConstants, DBConstants, Time +from bemani.common import ( + Model, + Profile, + ValidatedDict, + GameConstants, + DBConstants, + Time, +) from bemani.data import Config, Data, Score, UserID, ScoreSaveException from bemani.protocol import Node @@ -54,12 +61,12 @@ class DDRBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): # Return the local2 service so that DDR Ace will send certain packets. extra_services: List[str] = [ - 'local2', + "local2", ] def __init__(self, data: Data, config: Config, model: Model) -> None: super().__init__(data, config, model) - if model.rev == 'X': + if model.rev == "X": self.omnimix = True else: self.omnimix = False @@ -74,39 +81,39 @@ class DDRBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): """ Given a game's rank constant, return the rank as defined above. """ - raise Exception('Implement in sub-class!') + raise Exception("Implement in sub-class!") def db_to_game_rank(self, db_rank: int) -> int: """ Given a rank as defined above, return the game's rank constant. """ - raise Exception('Implement in sub-class!') + raise Exception("Implement in sub-class!") def game_to_db_chart(self, game_chart: int) -> int: """ Given a game's chart for a song, return the chart as defined above. """ - raise Exception('Implement in sub-class!') + raise Exception("Implement in sub-class!") def db_to_game_chart(self, db_chart: int) -> int: """ Given a chart as defined above, return the game's chart constant. """ - raise Exception('Implement in sub-class!') + raise Exception("Implement in sub-class!") def game_to_db_halo(self, game_halo: int) -> int: """ Given a game's halo constant, return the halo as defined above. """ - raise Exception('Implement in sub-class!') + raise Exception("Implement in sub-class!") def db_to_game_halo(self, db_halo: int) -> int: """ Given a halo as defined above, return the game's halo constant. """ - raise Exception('Implement in sub-class!') + raise Exception("Implement in sub-class!") - def previous_version(self) -> Optional['DDRBase']: + def previous_version(self) -> Optional["DDRBase"]: """ Returns the previous version of the game, based on this game. Should be overridden. @@ -118,16 +125,20 @@ class DDRBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): Base handler for a profile. Given a userid and a profile dictionary, return a Node representing a profile. Should be overridden. """ - return Node.void('game') + return Node.void("game") - def format_scores(self, userid: UserID, profile: Profile, scores: List[Score]) -> Node: + def format_scores( + self, userid: UserID, profile: Profile, scores: List[Score] + ) -> Node: """ Base handler for a score list. Given a userid, profile and a score list, return a Node representing a score list. Should be overridden. """ - return Node.void('game') + return Node.void("game") - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: """ Base handler for profile parsing. Given a request and an old profile, return a new profile that's been updated with the contents of the request. @@ -153,7 +164,9 @@ class DDRBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): # Now, return it return self.format_profile(userid, profile) - def new_profile_by_refid(self, refid: Optional[str], name: Optional[str], area: Optional[int]) -> None: + def new_profile_by_refid( + self, refid: Optional[str], name: Optional[str], area: Optional[int] + ) -> None: """ Given a RefID and a name/area, create a new profile. """ @@ -169,8 +182,8 @@ class DDRBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): refid, 0, { - 'name': name, - 'area': area, + "name": name, + "area": area, }, ) self.put_profile(userid, defaultprofile) @@ -199,8 +212,8 @@ class DDRBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): rank: int, halo: int, combo: int, - trace: Optional[List[int]]=None, - ghost: Optional[str]=None, + trace: Optional[List[int]] = None, + ghost: Optional[str] = None, ) -> None: """ Given various pieces of a score, update the user's high score and score @@ -219,7 +232,7 @@ class DDRBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): self.CHART_DOUBLE_EXPERT, self.CHART_DOUBLE_CHALLENGE, ]: - raise Exception(f'Invalid chart {chart}') + raise Exception(f"Invalid chart {chart}") if halo not in [ self.HALO_NONE, self.HALO_GOOD_FULL_COMBO, @@ -227,7 +240,7 @@ class DDRBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): self.HALO_PERFECT_FULL_COMBO, self.HALO_MARVELOUS_FULL_COMBO, ]: - raise Exception(f'Invalid halo {halo}') + raise Exception(f"Invalid halo {halo}") if rank not in [ self.RANK_E, self.RANK_D, @@ -246,7 +259,7 @@ class DDRBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): self.RANK_AA_PLUS, self.RANK_AAA, ]: - raise Exception(f'Invalid rank {rank}') + raise Exception(f"Invalid rank {rank}") if userid is not None: oldscore = self.data.local.music.get_score( @@ -277,26 +290,26 @@ class DDRBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): scoredata = oldscore.data # Save combo - history.replace_int('combo', combo) - scoredata.replace_int('combo', max(scoredata.get_int('combo'), combo)) + history.replace_int("combo", combo) + scoredata.replace_int("combo", max(scoredata.get_int("combo"), combo)) # Save halo - history.replace_int('halo', halo) - scoredata.replace_int('halo', max(scoredata.get_int('halo'), halo)) + history.replace_int("halo", halo) + scoredata.replace_int("halo", max(scoredata.get_int("halo"), halo)) # Save rank - history.replace_int('rank', rank) - scoredata.replace_int('rank', max(scoredata.get_int('rank'), rank)) + history.replace_int("rank", rank) + scoredata.replace_int("rank", max(scoredata.get_int("rank"), rank)) # Save ghost steps if trace is not None: - history.replace_int_array('trace', len(trace), trace) + history.replace_int_array("trace", len(trace), trace) if raised: - scoredata.replace_int_array('trace', len(trace), trace) + scoredata.replace_int_array("trace", len(trace), trace) if ghost is not None: - history.replace_str('ghost', ghost) + history.replace_str("ghost", ghost) if raised: - scoredata.replace_str('ghost', ghost) + scoredata.replace_str("ghost", ghost) # Look up where this score was earned lid = self.get_machine_id() diff --git a/bemani/backend/ddr/common.py b/bemani/backend/ddr/common.py index 58f4f02..1cd94dc 100644 --- a/bemani/backend/ddr/common.py +++ b/bemani/backend/ddr/common.py @@ -7,60 +7,52 @@ from bemani.protocol import Node class DDRGameShopHandler(DDRBase): - def handle_game_shop_request(self, request: Node) -> Node: - self.update_machine_name(request.attribute('name')) + self.update_machine_name(request.attribute("name")) - game = Node.void('game') - game.set_attribute('stop', '0') + game = Node.void("game") + game.set_attribute("stop", "0") return game class DDRGameLogHandler(DDRBase): - def handle_game_log_request(self, request: Node) -> Node: - return Node.void('game') + return Node.void("game") class DDRGameMessageHandler(DDRBase): - def handle_game_message_request(self, request: Node) -> Node: - return Node.void('game') + return Node.void("game") class DDRGameRankingHandler(DDRBase): - def handle_game_ranking_request(self, request: Node) -> Node: # Ranking request, unknown what its for - return Node.void('game') + return Node.void("game") class DDRGameLockHandler(DDRBase): - def handle_game_lock_request(self, request: Node) -> Node: - game = Node.void('game') - game.set_attribute('now_login', '0') + game = Node.void("game") + game.set_attribute("now_login", "0") return game class DDRGameTaxInfoHandler(DDRBase): - def handle_game_tax_info_request(self, request: Node) -> Node: - game = Node.void('game') - tax_info = Node.void('tax_info') + game = Node.void("game") + tax_info = Node.void("tax_info") game.add_child(tax_info) - tax_info.set_attribute('tax_phase', '0') + tax_info.set_attribute("tax_phase", "0") return game class DDRGameRecorderHandler(DDRBase): - def handle_game_recorder_request(self, request: Node) -> Node: - return Node.void('game') + return Node.void("game") class DDRGameHiscoreHandler(DDRBase): - def handle_game_hiscore_request(self, request: Node) -> Node: records = self.data.remote.music.get_all_records(self.game, self.music_version) @@ -71,13 +63,16 @@ class DDRGameHiscoreHandler(DDRBase): sortedrecords[score.id] = {} sortedrecords[score.id][score.chart] = (userid, score) missing_profiles.append(userid) - users = {userid: profile for (userid, profile) in self.get_any_profiles(missing_profiles)} + users = { + userid: profile + for (userid, profile) in self.get_any_profiles(missing_profiles) + } - game = Node.void('game') + game = Node.void("game") for song in sortedrecords: - music = Node.void('music') + music = Node.void("music") game.add_child(music) - music.set_attribute('reclink_num', str(song)) + music.set_attribute("reclink_num", str(song)) for chart in sortedrecords[song]: userid, score = sortedrecords[song][chart] @@ -86,36 +81,44 @@ class DDRGameHiscoreHandler(DDRBase): except KeyError: # Don't support this chart in this game continue - gamerank = self.db_to_game_rank(score.data.get_int('rank')) - combo_type = self.db_to_game_halo(score.data.get_int('halo')) + gamerank = self.db_to_game_rank(score.data.get_int("rank")) + combo_type = self.db_to_game_halo(score.data.get_int("halo")) - typenode = Node.void('type') + typenode = Node.void("type") music.add_child(typenode) - typenode.set_attribute('diff', str(gamechart)) + typenode.set_attribute("diff", str(gamechart)) - typenode.add_child(Node.string('name', users[userid].get_str('name'))) - typenode.add_child(Node.u32('score', score.points)) - typenode.add_child(Node.u16('area', users[userid].get_int('area', self.get_machine_region()))) - typenode.add_child(Node.u8('rank', gamerank)) - typenode.add_child(Node.u8('combo_type', combo_type)) - typenode.add_child(Node.u32('code', users[userid].extid)) + typenode.add_child(Node.string("name", users[userid].get_str("name"))) + typenode.add_child(Node.u32("score", score.points)) + typenode.add_child( + Node.u16( + "area", users[userid].get_int("area", self.get_machine_region()) + ) + ) + typenode.add_child(Node.u8("rank", gamerank)) + typenode.add_child(Node.u8("combo_type", combo_type)) + typenode.add_child(Node.u32("code", users[userid].extid)) return game class DDRGameAreaHiscoreHandler(DDRBase): - def handle_game_area_hiscore_request(self, request: Node) -> Node: - shop_area = int(request.attribute('shop_area')) + shop_area = int(request.attribute("shop_area")) # First, get all users that are in the current shop's area area_users = { - uid: prof for (uid, prof) in self.data.local.user.get_all_profiles(self.game, self.version) - if prof.get_int('area', self.get_machine_region()) == shop_area + uid: prof + for (uid, prof) in self.data.local.user.get_all_profiles( + self.game, self.version + ) + if prof.get_int("area", self.get_machine_region()) == shop_area } # Second, look up records belonging only to those users - records = self.data.local.music.get_all_records(self.game, self.music_version, userlist=list(area_users.keys())) + records = self.data.local.music.get_all_records( + self.game, self.music_version, userlist=list(area_users.keys()) + ) # Now, do the same lazy thing as 'hiscore' because I don't want # to think about how to change this knowing that we only pulled @@ -126,15 +129,18 @@ class DDRGameAreaHiscoreHandler(DDRBase): area_records[score.id] = {} area_records[score.id][score.chart] = (userid, score) - game = Node.void('game') + game = Node.void("game") for song in area_records: - music = Node.void('music') + music = Node.void("music") game.add_child(music) - music.set_attribute('reclink_num', str(song)) + music.set_attribute("reclink_num", str(song)) for chart in area_records[song]: userid, score = area_records[song][chart] - if area_users[userid].get_int('area', self.get_machine_region()) != shop_area: + if ( + area_users[userid].get_int("area", self.get_machine_region()) + != shop_area + ): # Don't return this, this user isn't in this area continue try: @@ -142,29 +148,35 @@ class DDRGameAreaHiscoreHandler(DDRBase): except KeyError: # Don't support this chart in this game continue - gamerank = self.db_to_game_rank(score.data.get_int('rank')) - combo_type = self.db_to_game_halo(score.data.get_int('halo')) + gamerank = self.db_to_game_rank(score.data.get_int("rank")) + combo_type = self.db_to_game_halo(score.data.get_int("halo")) - typenode = Node.void('type') + typenode = Node.void("type") music.add_child(typenode) - typenode.set_attribute('diff', str(gamechart)) + typenode.set_attribute("diff", str(gamechart)) - typenode.add_child(Node.string('name', area_users[userid].get_str('name'))) - typenode.add_child(Node.u32('score', score.points)) - typenode.add_child(Node.u16('area', area_users[userid].get_int('area', self.get_machine_region()))) - typenode.add_child(Node.u8('rank', gamerank)) - typenode.add_child(Node.u8('combo_type', combo_type)) - typenode.add_child(Node.u32('code', area_users[userid].extid)) + typenode.add_child( + Node.string("name", area_users[userid].get_str("name")) + ) + typenode.add_child(Node.u32("score", score.points)) + typenode.add_child( + Node.u16( + "area", + area_users[userid].get_int("area", self.get_machine_region()), + ) + ) + typenode.add_child(Node.u8("rank", gamerank)) + typenode.add_child(Node.u8("combo_type", combo_type)) + typenode.add_child(Node.u32("code", area_users[userid].extid)) return game class DDRGameScoreHandler(DDRBase): - def handle_game_score_request(self, request: Node) -> Node: - refid = request.attribute('refid') - songid = int(request.attribute('mid')) - chart = self.game_to_db_chart(int(request.attribute('type'))) + refid = request.attribute("refid") + songid = int(request.attribute("mid")) + chart = self.game_to_db_chart(int(request.attribute("type"))) userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: @@ -185,22 +197,21 @@ class DDRGameScoreHandler(DDRBase): recentscores.append(0) # Return the most recent five scores - game = Node.void('game') + game = Node.void("game") for i in range(len(recentscores)): - game.set_attribute(f'sc{i + 1}', str(recentscores[i])) + game.set_attribute(f"sc{i + 1}", str(recentscores[i])) return game class DDRGameTraceHandler(DDRBase): - def handle_game_trace_request(self, request: Node) -> Node: - extid = int(request.attribute('code')) - chart = int(request.attribute('type')) - cid = intish(request.attribute('cid')) - mid = intish(request.attribute('mid')) + extid = int(request.attribute("code")) + chart = int(request.attribute("type")) + cid = intish(request.attribute("cid")) + mid = intish(request.attribute("mid")) # Base packet is just game, if we find something we add to it - game = Node.void('game') + game = Node.void("game") # Rival trace loading userid = self.data.remote.user.from_extid(self.game, self.version, extid) @@ -217,9 +228,9 @@ class DDRGameTraceHandler(DDRBase): mid, self.game_to_db_chart(chart), ) - if songscore is not None and 'trace' in songscore.data: - game.add_child(Node.u32('size', len(songscore.data['trace']))) - game.add_child(Node.u8_array('trace', songscore.data['trace'])) + if songscore is not None and "trace" in songscore.data: + game.add_child(Node.u32("size", len(songscore.data["trace"]))) + game.add_child(Node.u8_array("trace", songscore.data["trace"])) elif cid is not None: # Load trace from achievement @@ -228,35 +239,33 @@ class DDRGameTraceHandler(DDRBase): self.version, userid, (cid * 4) + chart, - 'course', + "course", ) - if coursescore is not None and 'trace' in coursescore: - game.add_child(Node.u32('size', len(coursescore['trace']))) - game.add_child(Node.u8_array('trace', coursescore['trace'])) + if coursescore is not None and "trace" in coursescore: + game.add_child(Node.u32("size", len(coursescore["trace"]))) + game.add_child(Node.u8_array("trace", coursescore["trace"])) # Nothing found, return empty return game class DDRGameLoadHandler(DDRBase): - def handle_game_load_request(self, request: Node) -> Node: - refid = request.attribute('refid') + refid = request.attribute("refid") profile = self.get_profile_by_refid(refid) if profile is not None: return profile - game = Node.void('game') - game.set_attribute('none', '0') + game = Node.void("game") + game.set_attribute("none", "0") return game class DDRGameLoadDailyHandler(DDRBase): - def handle_game_load_daily_request(self, request: Node) -> Node: - extid = intish(request.attribute('code')) - refid = request.attribute('refid') - game = Node.void('game') + extid = intish(request.attribute("code")) + refid = request.attribute("refid") + game = Node.void("game") profiledict = None if extid is not None: @@ -272,24 +281,23 @@ class DDRGameLoadDailyHandler(DDRBase): play_stats = self.get_play_statistics(userid) # Day play counts - daycount = Node.void('daycount') + daycount = Node.void("daycount") game.add_child(daycount) - daycount.set_attribute('playcount', str(play_stats.today_plays)) + daycount.set_attribute("playcount", str(play_stats.today_plays)) # Daily combo stuff, unclear how this works - dailycombo = Node.void('dailycombo') + dailycombo = Node.void("dailycombo") game.add_child(dailycombo) - dailycombo.set_attribute('daily_combo', str(0)) - dailycombo.set_attribute('daily_combo_lv', str(0)) + dailycombo.set_attribute("daily_combo", str(0)) + dailycombo.set_attribute("daily_combo_lv", str(0)) return game class DDRGameOldHandler(DDRBase): - def handle_game_old_request(self, request: Node) -> Node: - refid = request.attribute('refid') - game = Node.void('game') + refid = request.attribute("refid") + game = Node.void("game") userid = self.data.remote.user.from_refid(self.game, self.version, refid) previous_version: Optional[DDRBase] = None @@ -300,41 +308,40 @@ class DDRGameOldHandler(DDRBase): if previous_version is not None: oldprofile = previous_version.get_profile(userid) if oldprofile is not None: - game.set_attribute('name', oldprofile.get_str('name')) - game.set_attribute('area', str(oldprofile.get_int('area', self.get_machine_region()))) + game.set_attribute("name", oldprofile.get_str("name")) + game.set_attribute( + "area", str(oldprofile.get_int("area", self.get_machine_region())) + ) return game class DDRGameNewHandler(DDRBase): - def handle_game_new_request(self, request: Node) -> Node: - refid = request.attribute('refid') - area = int(request.attribute('area')) - name = request.attribute('name').strip() + refid = request.attribute("refid") + area = int(request.attribute("area")) + name = request.attribute("name").strip() # Create a new profile for this user! self.new_profile_by_refid(refid, name, area) # No response needed - game = Node.void('game') + game = Node.void("game") return game class DDRGameSaveHandler(DDRBase): - def handle_game_save_request(self, request: Node) -> Node: - refid = request.attribute('refid') + refid = request.attribute("refid") self.put_profile_by_refid(refid, request) # No response needed - game = Node.void('game') + game = Node.void("game") return game class DDRGameFriendHandler(DDRBase): - def handle_game_friend_request(self, request: Node) -> Node: - extid = intish(request.attribute('code')) + extid = intish(request.attribute("code")) userid = None friend = None @@ -347,62 +354,63 @@ class DDRGameFriendHandler(DDRBase): if friend is None: # Return an empty node to tell the game we don't have a player here - game = Node.void('game') + game = Node.void("game") return game - game = Node.void('game') - game.set_attribute('data', '1') - game.add_child(Node.u32('code', friend.extid)) - game.add_child(Node.string('name', friend.get_str('name'))) - game.add_child(Node.u8('area', friend.get_int('area', self.get_machine_region()))) - game.add_child(Node.u32('exp', play_stats.get_int('exp'))) - game.add_child(Node.u32('star', friend.get_int('star'))) + game = Node.void("game") + game.set_attribute("data", "1") + game.add_child(Node.u32("code", friend.extid)) + game.add_child(Node.string("name", friend.get_str("name"))) + game.add_child( + Node.u8("area", friend.get_int("area", self.get_machine_region())) + ) + game.add_child(Node.u32("exp", play_stats.get_int("exp"))) + game.add_child(Node.u32("star", friend.get_int("star"))) # Drill rankings - if 'title' in friend: - title = Node.void('title') + if "title" in friend: + title = Node.void("title") game.add_child(title) - titledict = friend.get_dict('title') - if 't' in titledict: - title.set_attribute('t', str(titledict.get_int('t'))) - if 's' in titledict: - title.set_attribute('s', str(titledict.get_int('s'))) - if 'd' in titledict: - title.set_attribute('d', str(titledict.get_int('d'))) + titledict = friend.get_dict("title") + if "t" in titledict: + title.set_attribute("t", str(titledict.get_int("t"))) + if "s" in titledict: + title.set_attribute("s", str(titledict.get_int("s"))) + if "d" in titledict: + title.set_attribute("d", str(titledict.get_int("d"))) - if 'title_gr' in friend: - title_gr = Node.void('title_gr') + if "title_gr" in friend: + title_gr = Node.void("title_gr") game.add_child(title_gr) - title_grdict = friend.get_dict('title_gr') - if 't' in title_grdict: - title_gr.set_attribute('t', str(title_grdict.get_int('t'))) - if 's' in title_grdict: - title_gr.set_attribute('s', str(title_grdict.get_int('s'))) - if 'd' in title_grdict: - title_gr.set_attribute('d', str(title_grdict.get_int('d'))) + title_grdict = friend.get_dict("title_gr") + if "t" in title_grdict: + title_gr.set_attribute("t", str(title_grdict.get_int("t"))) + if "s" in title_grdict: + title_gr.set_attribute("s", str(title_grdict.get_int("s"))) + if "d" in title_grdict: + title_gr.set_attribute("d", str(title_grdict.get_int("d"))) # Groove gauge level-ups - gr_s = Node.void('gr_s') + gr_s = Node.void("gr_s") game.add_child(gr_s) index = 1 - for entry in friend.get_int_array('gr_s', 5): - gr_s.set_attribute(f'gr{index}', str(entry)) + for entry in friend.get_int_array("gr_s", 5): + gr_s.set_attribute(f"gr{index}", str(entry)) index = index + 1 - gr_d = Node.void('gr_d') + gr_d = Node.void("gr_d") game.add_child(gr_d) index = 1 - for entry in friend.get_int_array('gr_d', 5): - gr_d.set_attribute(f'gr{index}', str(entry)) + for entry in friend.get_int_array("gr_d", 5): + gr_d.set_attribute(f"gr{index}", str(entry)) index = index + 1 return game class DDRGameLoadCourseHandler(DDRBase): - def handle_game_load_c_request(self, request: Node) -> Node: - extid = intish(request.attribute('code')) - refid = request.attribute('refid') + extid = intish(request.attribute("code")) + refid = request.attribute("refid") if extid is not None: # Rival score loading @@ -413,8 +421,10 @@ class DDRGameLoadCourseHandler(DDRBase): coursedata = [0] * 3200 if userid is not None: - for course in self.data.local.user.get_achievements(self.game, self.version, userid): - if course.type != 'course': + for course in self.data.local.user.get_achievements( + self.game, self.version, userid + ): + if course.type != "course": continue # Grab course ID and chart (kinda pointless because we add it right back up @@ -425,35 +435,36 @@ class DDRGameLoadCourseHandler(DDRBase): # Populate course data index = ((courseid * 4) + coursechart) * 8 if index >= 0 and index <= (len(coursedata) - 8): - coursedata[index + 0] = int(course.data.get_int('score') / 10000) - coursedata[index + 1] = course.data.get_int('score') % 10000 - coursedata[index + 2] = course.data.get_int('combo') - coursedata[index + 3] = self.db_to_game_rank(course.data.get_int('rank')) - coursedata[index + 5] = course.data.get_int('stage') - coursedata[index + 6] = course.data.get_int('combo_type') + coursedata[index + 0] = int(course.data.get_int("score") / 10000) + coursedata[index + 1] = course.data.get_int("score") % 10000 + coursedata[index + 2] = course.data.get_int("combo") + coursedata[index + 3] = self.db_to_game_rank( + course.data.get_int("rank") + ) + coursedata[index + 5] = course.data.get_int("stage") + coursedata[index + 6] = course.data.get_int("combo_type") - game = Node.void('game') - game.add_child(Node.u16_array('course', coursedata)) + game = Node.void("game") + game.add_child(Node.u16_array("course", coursedata)) return game class DDRGameSaveCourseHandler(DDRBase): - def handle_game_save_c_request(self, request: Node) -> Node: - refid = request.attribute('refid') - courseid = int(request.attribute('cid')) - chart = int(request.attribute('ctype')) + refid = request.attribute("refid") + courseid = int(request.attribute("cid")) + chart = int(request.attribute("ctype")) userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: # Calculate statistics - data = request.child('data') - points = int(data.attribute('score')) - combo = int(data.attribute('combo')) - combo_type = int(data.attribute('combo_type')) - stage = int(data.attribute('stage')) - rank = self.game_to_db_rank(int(data.attribute('rank'))) - trace = request.child_value('trace') + data = request.child("data") + points = int(data.attribute("score")) + combo = int(data.attribute("combo")) + combo_type = int(data.attribute("combo_type")) + stage = int(data.attribute("stage")) + rank = self.game_to_db_rank(int(data.attribute("rank"))) + trace = request.child_value("trace") # Grab the old course score oldcourse = self.data.local.user.get_achievement( @@ -461,38 +472,38 @@ class DDRGameSaveCourseHandler(DDRBase): self.version, userid, (courseid * 4) + chart, - 'course', + "course", ) if oldcourse is not None: - highscore = points > oldcourse.get_int('score') + highscore = points > oldcourse.get_int("score") - points = max(points, oldcourse.get_int('score')) - combo = max(combo, oldcourse.get_int('combo')) - stage = max(stage, oldcourse.get_int('stage')) - rank = max(rank, oldcourse.get_int('rank')) - combo_type = max(combo_type, oldcourse.get_int('combo_type')) + points = max(points, oldcourse.get_int("score")) + combo = max(combo, oldcourse.get_int("combo")) + stage = max(stage, oldcourse.get_int("stage")) + rank = max(rank, oldcourse.get_int("rank")) + combo_type = max(combo_type, oldcourse.get_int("combo_type")) if not highscore: # Don't overwrite the ghost for a non-highscore - trace = oldcourse.get_int_array('trace', len(trace)) + trace = oldcourse.get_int_array("trace", len(trace)) self.data.local.user.put_achievement( self.game, self.version, userid, (courseid * 4) + chart, - 'course', + "course", { - 'score': points, - 'combo': combo, - 'stage': stage, - 'rank': rank, - 'combo_type': combo_type, - 'trace': trace, + "score": points, + "combo": combo, + "stage": stage, + "rank": rank, + "combo_type": combo_type, + "trace": trace, }, ) # No response needed - game = Node.void('game') + game = Node.void("game") return game diff --git a/bemani/backend/ddr/ddr2013.py b/bemani/backend/ddr/ddr2013.py index 6c1292d..9cea2b2 100644 --- a/bemani/backend/ddr/ddr2013.py +++ b/bemani/backend/ddr/ddr2013.py @@ -53,7 +53,7 @@ class DDR2013( DDRBase, ): - name: str = 'DanceDanceRevolution 2013' + name: str = "DanceDanceRevolution 2013" version: int = VersionConstants.DDR_2013 GAME_STYLE_SINGLE: Final[int] = 0 @@ -160,30 +160,32 @@ class DDR2013( return combo_type def handle_game_common_request(self, request: Node) -> Node: - game = Node.void('game') + game = Node.void("game") for flagid in range(256): - flag = Node.void('flag') + flag = Node.void("flag") game.add_child(flag) - flag.set_attribute('id', str(flagid)) - flag.set_attribute('t', '0') - flag.set_attribute('s1', '0') - flag.set_attribute('s2', '0') - flag.set_attribute('area', str(self.get_machine_region())) - flag.set_attribute('is_final', '1') + flag.set_attribute("id", str(flagid)) + flag.set_attribute("t", "0") + flag.set_attribute("s1", "0") + flag.set_attribute("s2", "0") + flag.set_attribute("area", str(self.get_machine_region())) + flag.set_attribute("is_final", "1") - hit_chart = self.data.local.music.get_hit_chart(self.game, self.music_version, self.GAME_MAX_SONGS) + hit_chart = self.data.local.music.get_hit_chart( + self.game, self.music_version, self.GAME_MAX_SONGS + ) counts_by_reflink = [0] * self.GAME_MAX_SONGS for (reflink, plays) in hit_chart: if reflink >= 0 and reflink < self.GAME_MAX_SONGS: counts_by_reflink[reflink] = plays - game.add_child(Node.u32_array('cnt_music', counts_by_reflink)) + game.add_child(Node.u32_array("cnt_music", counts_by_reflink)) return game def handle_game_load_m_request(self, request: Node) -> Node: - extid = intish(request.attribute('code')) - refid = request.attribute('refid') + extid = intish(request.attribute("code")) + refid = request.attribute("refid") if extid is not None: # Rival score loading @@ -193,7 +195,9 @@ class DDR2013( userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: - scores = self.data.remote.music.get_scores(self.game, self.music_version, userid) + scores = self.data.remote.music.get_scores( + self.game, self.music_version, userid + ) else: scores = [] @@ -203,11 +207,11 @@ class DDR2013( sortedscores[score.id] = {} sortedscores[score.id][score.chart] = score - game = Node.void('game') + game = Node.void("game") for song in sortedscores: - music = Node.void('music') + music = Node.void("music") game.add_child(music) - music.set_attribute('reclink', str(song)) + music.set_attribute("reclink", str(song)) for chart in sortedscores[song]: score = sortedscores[song][chart] @@ -216,41 +220,41 @@ class DDR2013( except KeyError: # Don't support this chart in this game continue - gamerank = self.db_to_game_rank(score.data.get_int('rank')) - combo_type = self.db_to_game_halo(score.data.get_int('halo')) + gamerank = self.db_to_game_rank(score.data.get_int("rank")) + combo_type = self.db_to_game_halo(score.data.get_int("halo")) - typenode = Node.void('type') + typenode = Node.void("type") music.add_child(typenode) - typenode.set_attribute('diff', str(gamechart)) + typenode.set_attribute("diff", str(gamechart)) - typenode.add_child(Node.u32('score', score.points)) - typenode.add_child(Node.u16('count', score.plays)) - typenode.add_child(Node.u8('rank', gamerank)) - typenode.add_child(Node.u8('combo_type', combo_type)) + typenode.add_child(Node.u32("score", score.points)) + typenode.add_child(Node.u16("count", score.plays)) + typenode.add_child(Node.u8("rank", gamerank)) + typenode.add_child(Node.u8("combo_type", combo_type)) return game def handle_game_save_m_request(self, request: Node) -> Node: - refid = request.attribute('refid') - songid = int(request.attribute('mid')) - chart = self.game_to_db_chart(int(request.attribute('mtype'))) + refid = request.attribute("refid") + songid = int(request.attribute("mid")) + chart = self.game_to_db_chart(int(request.attribute("mtype"))) # Calculate statistics - data = request.child('data') - points = int(data.attribute('score')) - combo = int(data.attribute('combo')) - rank = self.game_to_db_rank(int(data.attribute('rank'))) + data = request.child("data") + points = int(data.attribute("score")) + combo = int(data.attribute("combo")) + rank = self.game_to_db_rank(int(data.attribute("rank"))) if points == 1000000: halo = self.HALO_MARVELOUS_FULL_COMBO - elif int(data.attribute('perf_fc')) != 0: + elif int(data.attribute("perf_fc")) != 0: halo = self.HALO_PERFECT_FULL_COMBO - elif int(data.attribute('great_fc')) != 0: + elif int(data.attribute("great_fc")) != 0: halo = self.HALO_GREAT_FULL_COMBO - elif int(data.attribute('good_fc')) != 0: + elif int(data.attribute("good_fc")) != 0: halo = self.HALO_GOOD_FULL_COMBO else: halo = self.HALO_NONE - trace = request.child_value('trace') + trace = request.child_value("trace") # Save the score, regardless of whether we have a refid. If we save # an anonymous score, it only goes into the DB to count against the @@ -268,355 +272,371 @@ class DDR2013( ) # No response needed - game = Node.void('game') + game = Node.void("game") return game def format_profile(self, userid: UserID, profile: Profile) -> Node: - root = Node.void('game') + root = Node.void("game") # Look up play stats we bridge to every mix play_stats = self.get_play_statistics(userid) # Basic game settings - root.add_child(Node.string('seq', '')) - root.add_child(Node.u32('code', profile.extid)) - root.add_child(Node.string('name', profile.get_str('name'))) - root.add_child(Node.u8('area', profile.get_int('area', self.get_machine_region()))) - root.add_child(Node.u32('cnt_s', play_stats.get_int('single_plays'))) - root.add_child(Node.u32('cnt_d', play_stats.get_int('double_plays'))) - root.add_child(Node.u32('cnt_b', play_stats.get_int('battle_plays'))) # This could be wrong, its a guess - root.add_child(Node.u32('cnt_m0', play_stats.get_int('cnt_m0'))) - root.add_child(Node.u32('cnt_m1', play_stats.get_int('cnt_m1'))) - root.add_child(Node.u32('cnt_m2', play_stats.get_int('cnt_m2'))) - root.add_child(Node.u32('cnt_m3', play_stats.get_int('cnt_m3'))) - root.add_child(Node.u32('cnt_m4', play_stats.get_int('cnt_m4'))) - root.add_child(Node.u32('cnt_m5', play_stats.get_int('cnt_m5'))) - root.add_child(Node.u32('exp', play_stats.get_int('exp'))) - root.add_child(Node.u32('exp_o', profile.get_int('exp_o'))) - root.add_child(Node.u32('star', profile.get_int('star'))) - root.add_child(Node.u32('star_c', profile.get_int('star_c'))) - root.add_child(Node.u8('combo', profile.get_int('combo', 0))) - root.add_child(Node.u8('timing_diff', profile.get_int('early_late', 0))) + root.add_child(Node.string("seq", "")) + root.add_child(Node.u32("code", profile.extid)) + root.add_child(Node.string("name", profile.get_str("name"))) + root.add_child( + Node.u8("area", profile.get_int("area", self.get_machine_region())) + ) + root.add_child(Node.u32("cnt_s", play_stats.get_int("single_plays"))) + root.add_child(Node.u32("cnt_d", play_stats.get_int("double_plays"))) + root.add_child( + Node.u32("cnt_b", play_stats.get_int("battle_plays")) + ) # This could be wrong, its a guess + root.add_child(Node.u32("cnt_m0", play_stats.get_int("cnt_m0"))) + root.add_child(Node.u32("cnt_m1", play_stats.get_int("cnt_m1"))) + root.add_child(Node.u32("cnt_m2", play_stats.get_int("cnt_m2"))) + root.add_child(Node.u32("cnt_m3", play_stats.get_int("cnt_m3"))) + root.add_child(Node.u32("cnt_m4", play_stats.get_int("cnt_m4"))) + root.add_child(Node.u32("cnt_m5", play_stats.get_int("cnt_m5"))) + root.add_child(Node.u32("exp", play_stats.get_int("exp"))) + root.add_child(Node.u32("exp_o", profile.get_int("exp_o"))) + root.add_child(Node.u32("star", profile.get_int("star"))) + root.add_child(Node.u32("star_c", profile.get_int("star_c"))) + root.add_child(Node.u8("combo", profile.get_int("combo", 0))) + root.add_child(Node.u8("timing_diff", profile.get_int("early_late", 0))) # Character stuff - chara = Node.void('chara') + chara = Node.void("chara") root.add_child(chara) - chara.set_attribute('my', str(profile.get_int('chara', 30))) - root.add_child(Node.u16_array('chara_opt', profile.get_int_array('chara_opt', 96, [208] * 96))) + chara.set_attribute("my", str(profile.get_int("chara", 30))) + root.add_child( + Node.u16_array( + "chara_opt", profile.get_int_array("chara_opt", 96, [208] * 96) + ) + ) # Drill rankings - if 'title_gr' in profile: - title_gr = Node.void('title_gr') + if "title_gr" in profile: + title_gr = Node.void("title_gr") root.add_child(title_gr) - title_grdict = profile.get_dict('title_gr') - if 't' in title_grdict: - title_gr.set_attribute('t', str(title_grdict.get_int('t'))) - if 's' in title_grdict: - title_gr.set_attribute('s', str(title_grdict.get_int('s'))) - if 'd' in title_grdict: - title_gr.set_attribute('d', str(title_grdict.get_int('d'))) + title_grdict = profile.get_dict("title_gr") + if "t" in title_grdict: + title_gr.set_attribute("t", str(title_grdict.get_int("t"))) + if "s" in title_grdict: + title_gr.set_attribute("s", str(title_grdict.get_int("s"))) + if "d" in title_grdict: + title_gr.set_attribute("d", str(title_grdict.get_int("d"))) # Calorie mode - if 'weight' in profile: + if "weight" in profile: workouts = self.data.local.user.get_time_based_achievements( self.game, self.version, userid, - achievementtype='workout', + achievementtype="workout", since=Time.now() - Time.SECONDS_IN_DAY, ) - total = sum([w.data.get_int('calories') for w in workouts]) - workout = Node.void('workout') + total = sum([w.data.get_int("calories") for w in workouts]) + workout = Node.void("workout") root.add_child(workout) - workout.set_attribute('weight', str(profile.get_int('weight'))) - workout.set_attribute('day', str(total)) - workout.set_attribute('disp', '1') + workout.set_attribute("weight", str(profile.get_int("weight"))) + workout.set_attribute("day", str(total)) + workout.set_attribute("disp", "1") # Daily play counts - daycount = Node.void('daycount') + daycount = Node.void("daycount") root.add_child(daycount) - daycount.set_attribute('playcount', str(play_stats.today_plays)) + daycount.set_attribute("playcount", str(play_stats.today_plays)) # Daily combo stuff, unknown how this works - dailycombo = Node.void('dailycombo') + dailycombo = Node.void("dailycombo") root.add_child(dailycombo) - dailycombo.set_attribute('daily_combo', str(0)) - dailycombo.set_attribute('daily_combo_lv', str(0)) + dailycombo.set_attribute("daily_combo", str(0)) + dailycombo.set_attribute("daily_combo_lv", str(0)) # Last cursor settings - last = Node.void('last') + last = Node.void("last") root.add_child(last) - lastdict = profile.get_dict('last') - last.set_attribute('rival1', str(lastdict.get_int('rival1', -1))) - last.set_attribute('rival2', str(lastdict.get_int('rival2', -1))) - last.set_attribute('rival3', str(lastdict.get_int('rival3', -1))) - last.set_attribute('fri', str(lastdict.get_int('rival1', -1))) # This literally goes to the same memory in 2013 - last.set_attribute('style', str(lastdict.get_int('style'))) - last.set_attribute('mode', str(lastdict.get_int('mode'))) - last.set_attribute('cate', str(lastdict.get_int('cate'))) - last.set_attribute('sort', str(lastdict.get_int('sort'))) - last.set_attribute('mid', str(lastdict.get_int('mid'))) - last.set_attribute('mtype', str(lastdict.get_int('mtype'))) - last.set_attribute('cid', str(lastdict.get_int('cid'))) - last.set_attribute('ctype', str(lastdict.get_int('ctype'))) - last.set_attribute('sid', str(lastdict.get_int('sid'))) + lastdict = profile.get_dict("last") + last.set_attribute("rival1", str(lastdict.get_int("rival1", -1))) + last.set_attribute("rival2", str(lastdict.get_int("rival2", -1))) + last.set_attribute("rival3", str(lastdict.get_int("rival3", -1))) + last.set_attribute( + "fri", str(lastdict.get_int("rival1", -1)) + ) # This literally goes to the same memory in 2013 + last.set_attribute("style", str(lastdict.get_int("style"))) + last.set_attribute("mode", str(lastdict.get_int("mode"))) + last.set_attribute("cate", str(lastdict.get_int("cate"))) + last.set_attribute("sort", str(lastdict.get_int("sort"))) + last.set_attribute("mid", str(lastdict.get_int("mid"))) + last.set_attribute("mtype", str(lastdict.get_int("mtype"))) + last.set_attribute("cid", str(lastdict.get_int("cid"))) + last.set_attribute("ctype", str(lastdict.get_int("ctype"))) + last.set_attribute("sid", str(lastdict.get_int("sid"))) # Result stars - result_star = Node.void('result_star') + result_star = Node.void("result_star") root.add_child(result_star) - result_stars = profile.get_int_array('result_stars', 9) + result_stars = profile.get_int_array("result_stars", 9) for i in range(9): - result_star.set_attribute(f'slot{i + 1}', str(result_stars[i])) + result_star.set_attribute(f"slot{i + 1}", str(result_stars[i])) # Target stuff - target = Node.void('target') + target = Node.void("target") root.add_child(target) - target.set_attribute('flag', str(profile.get_int('target_flag'))) - target.set_attribute('setnum', str(profile.get_int('target_setnum'))) + target.set_attribute("flag", str(profile.get_int("target_flag"))) + target.set_attribute("setnum", str(profile.get_int("target_setnum"))) # Groove gauge level-ups - gr_s = Node.void('gr_s') + gr_s = Node.void("gr_s") root.add_child(gr_s) index = 1 - for entry in profile.get_int_array('gr_s', 5): - gr_s.set_attribute(f'gr{index}', str(entry)) + for entry in profile.get_int_array("gr_s", 5): + gr_s.set_attribute(f"gr{index}", str(entry)) index = index + 1 - gr_d = Node.void('gr_d') + gr_d = Node.void("gr_d") root.add_child(gr_d) index = 1 - for entry in profile.get_int_array('gr_d', 5): - gr_d.set_attribute(f'gr{index}', str(entry)) + for entry in profile.get_int_array("gr_d", 5): + gr_d.set_attribute(f"gr{index}", str(entry)) index = index + 1 # Options in menus - root.add_child(Node.s16_array('opt', profile.get_int_array('opt', 16))) - root.add_child(Node.s16_array('opt_ex', profile.get_int_array('opt_ex', 16))) + root.add_child(Node.s16_array("opt", profile.get_int_array("opt", 16))) + root.add_child(Node.s16_array("opt_ex", profile.get_int_array("opt_ex", 16))) # Unlock flags - root.add_child(Node.u8_array('flag', profile.get_int_array('flag', 256, [1] * 256))) + root.add_child( + Node.u8_array("flag", profile.get_int_array("flag", 256, [1] * 256)) + ) # Ranking display? - root.add_child(Node.u16_array('rank', profile.get_int_array('rank', 100))) + root.add_child(Node.u16_array("rank", profile.get_int_array("rank", 100))) # Rivals links = self.data.local.user.get_links(self.game, self.version, userid) for link in links: - if link.type[:7] != 'friend_': + if link.type[:7] != "friend_": continue pos = int(link.type[7:]) friend = self.get_profile(link.other_userid) play_stats = self.get_play_statistics(link.other_userid) if friend is not None: - friendnode = Node.void('friend') + friendnode = Node.void("friend") root.add_child(friendnode) - friendnode.set_attribute('pos', str(pos)) - friendnode.set_attribute('vs', '0') - friendnode.set_attribute('up', '0') - friendnode.add_child(Node.u32('code', friend.extid)) - friendnode.add_child(Node.string('name', friend.get_str('name'))) - friendnode.add_child(Node.u8('area', friend.get_int('area', self.get_machine_region()))) - friendnode.add_child(Node.u32('exp', play_stats.get_int('exp'))) - friendnode.add_child(Node.u32('star', friend.get_int('star'))) + friendnode.set_attribute("pos", str(pos)) + friendnode.set_attribute("vs", "0") + friendnode.set_attribute("up", "0") + friendnode.add_child(Node.u32("code", friend.extid)) + friendnode.add_child(Node.string("name", friend.get_str("name"))) + friendnode.add_child( + Node.u8("area", friend.get_int("area", self.get_machine_region())) + ) + friendnode.add_child(Node.u32("exp", play_stats.get_int("exp"))) + friendnode.add_child(Node.u32("star", friend.get_int("star"))) # Drill rankings - if 'title' in friend: - title = Node.void('title') + if "title" in friend: + title = Node.void("title") friendnode.add_child(title) - titledict = friend.get_dict('title') - if 't' in titledict: - title.set_attribute('t', str(titledict.get_int('t'))) - if 's' in titledict: - title.set_attribute('s', str(titledict.get_int('s'))) - if 'd' in titledict: - title.set_attribute('d', str(titledict.get_int('d'))) + titledict = friend.get_dict("title") + if "t" in titledict: + title.set_attribute("t", str(titledict.get_int("t"))) + if "s" in titledict: + title.set_attribute("s", str(titledict.get_int("s"))) + if "d" in titledict: + title.set_attribute("d", str(titledict.get_int("d"))) - if 'title_gr' in friend: - title_gr = Node.void('title_gr') + if "title_gr" in friend: + title_gr = Node.void("title_gr") friendnode.add_child(title_gr) - title_grdict = friend.get_dict('title_gr') - if 't' in title_grdict: - title_gr.set_attribute('t', str(title_grdict.get_int('t'))) - if 's' in title_grdict: - title_gr.set_attribute('s', str(title_grdict.get_int('s'))) - if 'd' in title_grdict: - title_gr.set_attribute('d', str(title_grdict.get_int('d'))) + title_grdict = friend.get_dict("title_gr") + if "t" in title_grdict: + title_gr.set_attribute("t", str(title_grdict.get_int("t"))) + if "s" in title_grdict: + title_gr.set_attribute("s", str(title_grdict.get_int("s"))) + if "d" in title_grdict: + title_gr.set_attribute("d", str(title_grdict.get_int("d"))) # Groove gauge level-ups - gr_s = Node.void('gr_s') + gr_s = Node.void("gr_s") friendnode.add_child(gr_s) index = 1 - for entry in friend.get_int_array('gr_s', 5): - gr_s.set_attribute(f'gr{index}', str(entry)) + for entry in friend.get_int_array("gr_s", 5): + gr_s.set_attribute(f"gr{index}", str(entry)) index = index + 1 - gr_d = Node.void('gr_d') + gr_d = Node.void("gr_d") friendnode.add_child(gr_d) index = 1 - for entry in friend.get_int_array('gr_d', 5): - gr_d.set_attribute(f'gr{index}', str(entry)) + for entry in friend.get_int_array("gr_d", 5): + gr_d.set_attribute(f"gr{index}", str(entry)) index = index + 1 # Play area - areas = profile.get_int_array('play_area', 55) - play_area = Node.void('play_area') + areas = profile.get_int_array("play_area", 55) + play_area = Node.void("play_area") root.add_child(play_area) for i in range(len(areas)): - play_area.set_attribute(f'play_cnt{i}', str(areas[i])) + play_area.set_attribute(f"play_cnt{i}", str(areas[i])) return root - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() play_stats = self.get_play_statistics(userid) # Grab last node and accessories so we can make decisions based on type - last = request.child('last') - lastdict = newprofile.get_dict('last') - mode = int(last.attribute('mode')) - style = int(last.attribute('style')) + last = request.child("last") + lastdict = newprofile.get_dict("last") + mode = int(last.attribute("mode")) + style = int(last.attribute("style")) is_dp = style == self.GAME_STYLE_DOUBLE # Drill rankings - title = request.child('title') - title_gr = request.child('title_gr') - titledict = newprofile.get_dict('title') - title_grdict = newprofile.get_dict('title_gr') + title = request.child("title") + title_gr = request.child("title_gr") + titledict = newprofile.get_dict("title") + title_grdict = newprofile.get_dict("title_gr") # Groove radar level ups - gr = request.child('gr') + gr = request.child("gr") # Set the correct values depending on if we're single or double play if is_dp: - play_stats.increment_int('double_plays') + play_stats.increment_int("double_plays") if gr is not None: newprofile.replace_int_array( - 'gr_d', + "gr_d", 5, [ - intish(gr.attribute('gr1')), - intish(gr.attribute('gr2')), - intish(gr.attribute('gr3')), - intish(gr.attribute('gr4')), - intish(gr.attribute('gr5')), + intish(gr.attribute("gr1")), + intish(gr.attribute("gr2")), + intish(gr.attribute("gr3")), + intish(gr.attribute("gr4")), + intish(gr.attribute("gr5")), ], ) if title is not None: - titledict.replace_int('d', title.value) - newprofile.replace_dict('title', titledict) + titledict.replace_int("d", title.value) + newprofile.replace_dict("title", titledict) if title_gr is not None: - title_grdict.replace_int('d', title.value) - newprofile.replace_dict('title_gr', title_grdict) + title_grdict.replace_int("d", title.value) + newprofile.replace_dict("title_gr", title_grdict) else: - play_stats.increment_int('single_plays') + play_stats.increment_int("single_plays") if gr is not None: newprofile.replace_int_array( - 'gr_s', + "gr_s", 5, [ - intish(gr.attribute('gr1')), - intish(gr.attribute('gr2')), - intish(gr.attribute('gr3')), - intish(gr.attribute('gr4')), - intish(gr.attribute('gr5')), + intish(gr.attribute("gr1")), + intish(gr.attribute("gr2")), + intish(gr.attribute("gr3")), + intish(gr.attribute("gr4")), + intish(gr.attribute("gr5")), ], ) if title is not None: - titledict.replace_int('s', title.value) - newprofile.replace_dict('title', titledict) + titledict.replace_int("s", title.value) + newprofile.replace_dict("title", titledict) if title_gr is not None: - title_grdict.replace_int('s', title.value) - newprofile.replace_dict('title_gr', title_grdict) - play_stats.increment_int(f'cnt_m{mode}') + title_grdict.replace_int("s", title.value) + newprofile.replace_dict("title_gr", title_grdict) + play_stats.increment_int(f"cnt_m{mode}") # Result stars - result_star = request.child('result_star') + result_star = request.child("result_star") if result_star is not None: newprofile.replace_int_array( - 'result_stars', + "result_stars", 9, [ - intish(result_star.attribute('slot1')), - intish(result_star.attribute('slot2')), - intish(result_star.attribute('slot3')), - intish(result_star.attribute('slot4')), - intish(result_star.attribute('slot5')), - intish(result_star.attribute('slot6')), - intish(result_star.attribute('slot7')), - intish(result_star.attribute('slot8')), - intish(result_star.attribute('slot9')), + intish(result_star.attribute("slot1")), + intish(result_star.attribute("slot2")), + intish(result_star.attribute("slot3")), + intish(result_star.attribute("slot4")), + intish(result_star.attribute("slot5")), + intish(result_star.attribute("slot6")), + intish(result_star.attribute("slot7")), + intish(result_star.attribute("slot8")), + intish(result_star.attribute("slot9")), ], ) # Target stuff - target = request.child('target') + target = request.child("target") if target is not None: - newprofile.replace_int('target_flag', intish(target.attribute('flag'))) - newprofile.replace_int('target_setnum', intish(target.attribute('setnum'))) + newprofile.replace_int("target_flag", intish(target.attribute("flag"))) + newprofile.replace_int("target_setnum", intish(target.attribute("setnum"))) # Update last attributes - lastdict.replace_int('rival1', intish(last.attribute('rival1'))) - lastdict.replace_int('rival2', intish(last.attribute('rival2'))) - lastdict.replace_int('rival3', intish(last.attribute('rival3'))) - lastdict.replace_int('style', intish(last.attribute('style'))) - lastdict.replace_int('mode', intish(last.attribute('mode'))) - lastdict.replace_int('cate', intish(last.attribute('cate'))) - lastdict.replace_int('sort', intish(last.attribute('sort'))) - lastdict.replace_int('mid', intish(last.attribute('mid'))) - lastdict.replace_int('mtype', intish(last.attribute('mtype'))) - lastdict.replace_int('cid', intish(last.attribute('cid'))) - lastdict.replace_int('ctype', intish(last.attribute('ctype'))) - lastdict.replace_int('sid', intish(last.attribute('sid'))) - newprofile.replace_dict('last', lastdict) + lastdict.replace_int("rival1", intish(last.attribute("rival1"))) + lastdict.replace_int("rival2", intish(last.attribute("rival2"))) + lastdict.replace_int("rival3", intish(last.attribute("rival3"))) + lastdict.replace_int("style", intish(last.attribute("style"))) + lastdict.replace_int("mode", intish(last.attribute("mode"))) + lastdict.replace_int("cate", intish(last.attribute("cate"))) + lastdict.replace_int("sort", intish(last.attribute("sort"))) + lastdict.replace_int("mid", intish(last.attribute("mid"))) + lastdict.replace_int("mtype", intish(last.attribute("mtype"))) + lastdict.replace_int("cid", intish(last.attribute("cid"))) + lastdict.replace_int("ctype", intish(last.attribute("ctype"))) + lastdict.replace_int("sid", intish(last.attribute("sid"))) + newprofile.replace_dict("last", lastdict) # Grab character options - chara = request.child('chara') + chara = request.child("chara") if chara is not None: - newprofile.replace_int('chara', intish(chara.attribute('my'))) - chara_opt = request.child('chara_opt') + newprofile.replace_int("chara", intish(chara.attribute("my"))) + chara_opt = request.child("chara_opt") if chara_opt is not None: # A bug in old versions of AVS returns the wrong number for set - newprofile.replace_int_array('chara_opt', 96, chara_opt.value[:96]) + newprofile.replace_int_array("chara_opt", 96, chara_opt.value[:96]) # Options - opt = request.child('opt') + opt = request.child("opt") if opt is not None: # A bug in old versions of AVS returns the wrong number for set - newprofile.replace_int_array('opt', 16, opt.value[:16]) + newprofile.replace_int_array("opt", 16, opt.value[:16]) # Experience and stars - exp = request.child_value('exp') + exp = request.child_value("exp") if exp is not None: - play_stats.replace_int('exp', play_stats.get_int('exp') + exp) - star = request.child_value('star') + play_stats.replace_int("exp", play_stats.get_int("exp") + exp) + star = request.child_value("star") if star is not None: - newprofile.replace_int('star', newprofile.get_int('star') + star) - star_c = request.child_value('star_c') + newprofile.replace_int("star", newprofile.get_int("star") + star) + star_c = request.child_value("star_c") if star_c is not None: - newprofile.replace_int('star_c', newprofile.get_int('star_c') + exp) + newprofile.replace_int("star_c", newprofile.get_int("star_c") + exp) # Update game flags for child in request.children: - if child.name != 'flag': + if child.name != "flag": continue try: - value = int(child.attribute('data')) - offset = int(child.attribute('no')) + value = int(child.attribute("data")) + offset = int(child.attribute("no")) except ValueError: continue - flags = newprofile.get_int_array('flag', 256, [1] * 256) + flags = newprofile.get_int_array("flag", 256, [1] * 256) if offset < 0 or offset >= len(flags): continue flags[offset] = value - newprofile.replace_int_array('flag', 256, flags) + newprofile.replace_int_array("flag", 256, flags) # Workout mode support newweight = -1 - oldweight = newprofile.get_int('weight') + oldweight = newprofile.get_int("weight") for child in request.children: - if child.name != 'weight': + if child.name != "weight": continue newweight = child.value if newweight < 0: @@ -625,14 +645,14 @@ class DDR2013( # Either update or unset the weight depending on the game if newweight == 0: # Weight is unset or we declined to use this feature, remove from profile - if 'weight' in newprofile: - del newprofile['weight'] + if "weight" in newprofile: + del newprofile["weight"] else: # Weight has been set or previously retrieved, we should save calories - newprofile.replace_int('weight', newweight) + newprofile.replace_int("weight", newweight) total = 0 for child in request.children: - if child.name != 'calory': + if child.name != "calory": continue total += child.value self.data.local.user.put_time_based_achievement( @@ -640,10 +660,10 @@ class DDR2013( self.version, userid, 0, - 'workout', + "workout", { - 'calories': total, - 'weight': newweight, + "calories": total, + "weight": newweight, }, ) @@ -651,7 +671,7 @@ class DDR2013( oldfriends: List[Optional[UserID]] = [None] * 10 links = self.data.local.user.get_links(self.game, self.version, userid) for link in links: - if link.type[:7] != 'friend_': + if link.type[:7] != "friend_": continue pos = int(link.type[7:]) @@ -660,11 +680,11 @@ class DDR2013( # Save any rivals that were added/removed/changed newfriends = oldfriends[:] for child in request.children: - if child.name != 'friend': + if child.name != "friend": continue - code = int(child.attribute('code')) - pos = int(child.attribute('pos')) + code = int(child.attribute("code")) + pos = int(child.attribute("pos")) if pos >= 0 and pos < 10: if code == 0: @@ -672,7 +692,9 @@ class DDR2013( newfriends[pos] = None else: # Try looking up the userid - newfriends[pos] = self.data.remote.user.from_extid(self.game, self.version, code) + newfriends[pos] = self.data.remote.user.from_extid( + self.game, self.version, code + ) # Diff the set of links to determine updates for i in range(10): @@ -685,7 +707,7 @@ class DDR2013( self.game, self.version, userid, - f'friend_{i}', + f"friend_{i}", oldfriends[i], ) elif oldfriends[i] is None: @@ -694,7 +716,7 @@ class DDR2013( self.game, self.version, userid, - f'friend_{i}', + f"friend_{i}", newfriends[i], {}, ) @@ -704,24 +726,24 @@ class DDR2013( self.game, self.version, userid, - f'friend_{i}', + f"friend_{i}", oldfriends[i], ) self.data.local.user.put_link( self.game, self.version, userid, - f'friend_{i}', + f"friend_{i}", newfriends[i], {}, ) # Play area counter - shop_area = int(request.attribute('shop_area')) + shop_area = int(request.attribute("shop_area")) if shop_area >= 0 and shop_area < 55: - areas = newprofile.get_int_array('play_area', 55) + areas = newprofile.get_int_array("play_area", 55) areas[shop_area] = areas[shop_area] + 1 - newprofile.replace_int_array('play_area', 55, areas) + newprofile.replace_int_array("play_area", 55, areas) # Keep track of play statistics self.update_play_statistics(userid, play_stats) diff --git a/bemani/backend/ddr/ddr2014.py b/bemani/backend/ddr/ddr2014.py index 13d5bf2..3e33a6d 100644 --- a/bemani/backend/ddr/ddr2014.py +++ b/bemani/backend/ddr/ddr2014.py @@ -47,7 +47,7 @@ class DDR2014( DDRBase, ): - name: str = 'DanceDanceRevolution 2014' + name: str = "DanceDanceRevolution 2014" version: int = VersionConstants.DDR_2014 GAME_STYLE_SINGLE: Final[int] = 0 @@ -154,47 +154,53 @@ class DDR2014( return combo_type def handle_game_common_request(self, request: Node) -> Node: - game = Node.void('game') + game = Node.void("game") for flagid in range(512): - flag = Node.void('flag') + flag = Node.void("flag") game.add_child(flag) - flag.set_attribute('id', str(flagid)) - flag.set_attribute('t', '0') - flag.set_attribute('s1', '0') - flag.set_attribute('s2', '0') - flag.set_attribute('area', str(self.get_machine_region())) - flag.set_attribute('is_final', '0') + flag.set_attribute("id", str(flagid)) + flag.set_attribute("t", "0") + flag.set_attribute("s1", "0") + flag.set_attribute("s2", "0") + flag.set_attribute("area", str(self.get_machine_region())) + flag.set_attribute("is_final", "0") # Last month's hit chart - hit_chart = self.data.local.music.get_hit_chart(self.game, self.music_version, self.GAME_MAX_SONGS, 30) + hit_chart = self.data.local.music.get_hit_chart( + self.game, self.music_version, self.GAME_MAX_SONGS, 30 + ) counts_by_reflink = [0] * self.GAME_MAX_SONGS for (reflink, plays) in hit_chart: if reflink >= 0 and reflink < self.GAME_MAX_SONGS: counts_by_reflink[reflink] = plays - game.add_child(Node.u32_array('cnt_music_monthly', counts_by_reflink)) + game.add_child(Node.u32_array("cnt_music_monthly", counts_by_reflink)) # Last week's hit chart - hit_chart = self.data.local.music.get_hit_chart(self.game, self.music_version, self.GAME_MAX_SONGS, 7) + hit_chart = self.data.local.music.get_hit_chart( + self.game, self.music_version, self.GAME_MAX_SONGS, 7 + ) counts_by_reflink = [0] * self.GAME_MAX_SONGS for (reflink, plays) in hit_chart: if reflink >= 0 and reflink < self.GAME_MAX_SONGS: counts_by_reflink[reflink] = plays - game.add_child(Node.u32_array('cnt_music_weekly', counts_by_reflink)) + game.add_child(Node.u32_array("cnt_music_weekly", counts_by_reflink)) # Last day's hit chart - hit_chart = self.data.local.music.get_hit_chart(self.game, self.music_version, self.GAME_MAX_SONGS, 1) + hit_chart = self.data.local.music.get_hit_chart( + self.game, self.music_version, self.GAME_MAX_SONGS, 1 + ) counts_by_reflink = [0] * self.GAME_MAX_SONGS for (reflink, plays) in hit_chart: if reflink >= 0 and reflink < self.GAME_MAX_SONGS: counts_by_reflink[reflink] = plays - game.add_child(Node.u32_array('cnt_music_daily', counts_by_reflink)) + game.add_child(Node.u32_array("cnt_music_daily", counts_by_reflink)) return game def handle_game_load_m_request(self, request: Node) -> Node: - extid = intish(request.attribute('code')) - refid = request.attribute('refid') + extid = intish(request.attribute("code")) + refid = request.attribute("refid") if extid is not None: # Rival score loading @@ -204,7 +210,9 @@ class DDR2014( userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: - scores = self.data.remote.music.get_scores(self.game, self.music_version, userid) + scores = self.data.remote.music.get_scores( + self.game, self.music_version, userid + ) else: scores = [] @@ -214,11 +222,11 @@ class DDR2014( sortedscores[score.id] = {} sortedscores[score.id][score.chart] = score - game = Node.void('game') + game = Node.void("game") for song in sortedscores: - music = Node.void('music') + music = Node.void("music") game.add_child(music) - music.set_attribute('reclink', str(song)) + music.set_attribute("reclink", str(song)) for chart in sortedscores[song]: score = sortedscores[song][chart] @@ -227,17 +235,17 @@ class DDR2014( except KeyError: # Don't support this chart in this game continue - gamerank = self.db_to_game_rank(score.data.get_int('rank')) - combo_type = self.db_to_game_halo(score.data.get_int('halo')) + gamerank = self.db_to_game_rank(score.data.get_int("rank")) + combo_type = self.db_to_game_halo(score.data.get_int("halo")) - typenode = Node.void('type') + typenode = Node.void("type") music.add_child(typenode) - typenode.set_attribute('diff', str(gamechart)) + typenode.set_attribute("diff", str(gamechart)) - typenode.add_child(Node.u32('score', score.points)) - typenode.add_child(Node.u16('count', score.plays)) - typenode.add_child(Node.u8('rank', gamerank)) - typenode.add_child(Node.u8('combo_type', combo_type)) + typenode.add_child(Node.u32("score", score.points)) + typenode.add_child(Node.u16("count", score.plays)) + typenode.add_child(Node.u8("rank", gamerank)) + typenode.add_child(Node.u8("combo_type", combo_type)) # The game optionally receives hard, life8, life4, risky, assist_clear, normal_clear # u8 values too, and saves music scores with these set, but the UI doesn't appear to # do anything with them, so we don't care. @@ -245,26 +253,26 @@ class DDR2014( return game def handle_game_save_m_request(self, request: Node) -> Node: - refid = request.attribute('refid') - songid = int(request.attribute('mid')) - chart = self.game_to_db_chart(int(request.attribute('mtype'))) + refid = request.attribute("refid") + songid = int(request.attribute("mid")) + chart = self.game_to_db_chart(int(request.attribute("mtype"))) # Calculate statistics - data = request.child('data') - points = int(data.attribute('score')) - combo = int(data.attribute('combo')) - rank = self.game_to_db_rank(int(data.attribute('rank'))) + data = request.child("data") + points = int(data.attribute("score")) + combo = int(data.attribute("combo")) + rank = self.game_to_db_rank(int(data.attribute("rank"))) if points == 1000000: halo = self.HALO_MARVELOUS_FULL_COMBO - elif int(data.attribute('perf_fc')) != 0: + elif int(data.attribute("perf_fc")) != 0: halo = self.HALO_PERFECT_FULL_COMBO - elif int(data.attribute('great_fc')) != 0: + elif int(data.attribute("great_fc")) != 0: halo = self.HALO_GREAT_FULL_COMBO - elif int(data.attribute('good_fc')) != 0: + elif int(data.attribute("good_fc")) != 0: halo = self.HALO_GOOD_FULL_COMBO else: halo = self.HALO_NONE - trace = request.child_value('trace') + trace = request.child_value("trace") # Save the score, regardless of whether we have a refid. If we save # an anonymous score, it only goes into the DB to count against the @@ -282,24 +290,24 @@ class DDR2014( ) # No response needed - game = Node.void('game') + game = Node.void("game") return game def handle_game_load_edit_request(self, request: Node) -> Node: - return Node.void('game') + return Node.void("game") def handle_game_save_resultshot_request(self, request: Node) -> Node: - return Node.void('game') + return Node.void("game") def handle_game_trace_request(self, request: Node) -> Node: # This is almost identical to 2013 and below, except it will never # even try to request course traces, so we fork from common functionality. - extid = int(request.attribute('code')) - chart = int(request.attribute('type')) - mid = intish(request.attribute('mid')) + extid = int(request.attribute("code")) + chart = int(request.attribute("type")) + mid = intish(request.attribute("mid")) # Base packet is just game, if we find something we add to it - game = Node.void('game') + game = Node.void("game") # Rival trace loading userid = self.data.remote.user.from_extid(self.game, self.version, extid) @@ -315,369 +323,389 @@ class DDR2014( mid, self.game_to_db_chart(chart), ) - if songscore is not None and 'trace' in songscore.data: - game.add_child(Node.u32('size', len(songscore.data['trace']))) - game.add_child(Node.u8_array('trace', songscore.data['trace'])) + if songscore is not None and "trace" in songscore.data: + game.add_child(Node.u32("size", len(songscore.data["trace"]))) + game.add_child(Node.u8_array("trace", songscore.data["trace"])) return game def format_profile(self, userid: UserID, profile: Profile) -> Node: - root = Node.void('game') + root = Node.void("game") # Look up play stats we bridge to every mix play_stats = self.get_play_statistics(userid) # Basic game settings - root.add_child(Node.string('seq', '')) - root.add_child(Node.u32('code', profile.extid)) - root.add_child(Node.string('name', profile.get_str('name'))) - root.add_child(Node.u8('area', profile.get_int('area', self.get_machine_region()))) - root.add_child(Node.u32('cnt_s', play_stats.get_int('single_plays'))) - root.add_child(Node.u32('cnt_d', play_stats.get_int('double_plays'))) - root.add_child(Node.u32('cnt_b', play_stats.get_int('battle_plays'))) # This could be wrong, its a guess - root.add_child(Node.u32('cnt_m0', play_stats.get_int('cnt_m0'))) - root.add_child(Node.u32('cnt_m1', play_stats.get_int('cnt_m1'))) - root.add_child(Node.u32('cnt_m2', play_stats.get_int('cnt_m2'))) - root.add_child(Node.u32('cnt_m3', play_stats.get_int('cnt_m3'))) - root.add_child(Node.u32('cnt_m4', play_stats.get_int('cnt_m4'))) - root.add_child(Node.u32('cnt_m5', play_stats.get_int('cnt_m5'))) - root.add_child(Node.u32('exp', play_stats.get_int('exp'))) - root.add_child(Node.u32('exp_o', profile.get_int('exp_o'))) - root.add_child(Node.u32('star', profile.get_int('star'))) - root.add_child(Node.u32('star_c', profile.get_int('star_c'))) - root.add_child(Node.u8('combo', profile.get_int('combo', 0))) - root.add_child(Node.u8('timing_diff', profile.get_int('early_late', 0))) + root.add_child(Node.string("seq", "")) + root.add_child(Node.u32("code", profile.extid)) + root.add_child(Node.string("name", profile.get_str("name"))) + root.add_child( + Node.u8("area", profile.get_int("area", self.get_machine_region())) + ) + root.add_child(Node.u32("cnt_s", play_stats.get_int("single_plays"))) + root.add_child(Node.u32("cnt_d", play_stats.get_int("double_plays"))) + root.add_child( + Node.u32("cnt_b", play_stats.get_int("battle_plays")) + ) # This could be wrong, its a guess + root.add_child(Node.u32("cnt_m0", play_stats.get_int("cnt_m0"))) + root.add_child(Node.u32("cnt_m1", play_stats.get_int("cnt_m1"))) + root.add_child(Node.u32("cnt_m2", play_stats.get_int("cnt_m2"))) + root.add_child(Node.u32("cnt_m3", play_stats.get_int("cnt_m3"))) + root.add_child(Node.u32("cnt_m4", play_stats.get_int("cnt_m4"))) + root.add_child(Node.u32("cnt_m5", play_stats.get_int("cnt_m5"))) + root.add_child(Node.u32("exp", play_stats.get_int("exp"))) + root.add_child(Node.u32("exp_o", profile.get_int("exp_o"))) + root.add_child(Node.u32("star", profile.get_int("star"))) + root.add_child(Node.u32("star_c", profile.get_int("star_c"))) + root.add_child(Node.u8("combo", profile.get_int("combo", 0))) + root.add_child(Node.u8("timing_diff", profile.get_int("early_late", 0))) # Character stuff - chara = Node.void('chara') + chara = Node.void("chara") root.add_child(chara) - chara.set_attribute('my', str(profile.get_int('chara', 30))) - root.add_child(Node.u16_array('chara_opt', profile.get_int_array('chara_opt', 96, [208] * 96))) + chara.set_attribute("my", str(profile.get_int("chara", 30))) + root.add_child( + Node.u16_array( + "chara_opt", profile.get_int_array("chara_opt", 96, [208] * 96) + ) + ) # Drill rankings - if 'title_gr' in profile: - title_gr = Node.void('title_gr') + if "title_gr" in profile: + title_gr = Node.void("title_gr") root.add_child(title_gr) - title_grdict = profile.get_dict('title_gr') - if 't' in title_grdict: - title_gr.set_attribute('t', str(title_grdict.get_int('t'))) - if 's' in title_grdict: - title_gr.set_attribute('s', str(title_grdict.get_int('s'))) - if 'd' in title_grdict: - title_gr.set_attribute('d', str(title_grdict.get_int('d'))) + title_grdict = profile.get_dict("title_gr") + if "t" in title_grdict: + title_gr.set_attribute("t", str(title_grdict.get_int("t"))) + if "s" in title_grdict: + title_gr.set_attribute("s", str(title_grdict.get_int("s"))) + if "d" in title_grdict: + title_gr.set_attribute("d", str(title_grdict.get_int("d"))) # Calorie mode - if 'weight' in profile: + if "weight" in profile: workouts = self.data.local.user.get_time_based_achievements( self.game, self.version, userid, - achievementtype='workout', + achievementtype="workout", since=Time.now() - Time.SECONDS_IN_DAY, ) - total = sum([w.data.get_int('calories') for w in workouts]) - workout = Node.void('workout') + total = sum([w.data.get_int("calories") for w in workouts]) + workout = Node.void("workout") root.add_child(workout) - workout.set_attribute('weight', str(profile.get_int('weight'))) - workout.set_attribute('day', str(total)) - workout.set_attribute('disp', '1') + workout.set_attribute("weight", str(profile.get_int("weight"))) + workout.set_attribute("day", str(total)) + workout.set_attribute("disp", "1") # Unsure if this should be last day, or total calories ever - totalcalorie = Node.void('totalcalorie') + totalcalorie = Node.void("totalcalorie") root.add_child(totalcalorie) - totalcalorie.set_attribute('total', str(total)) + totalcalorie.set_attribute("total", str(total)) # Daily play counts - daycount = Node.void('daycount') + daycount = Node.void("daycount") root.add_child(daycount) - daycount.set_attribute('playcount', str(play_stats.today_plays)) + daycount.set_attribute("playcount", str(play_stats.today_plays)) # Daily combo stuff, unknown how this works - dailycombo = Node.void('dailycombo') + dailycombo = Node.void("dailycombo") root.add_child(dailycombo) - dailycombo.set_attribute('daily_combo', str(0)) - dailycombo.set_attribute('daily_combo_lv', str(0)) + dailycombo.set_attribute("daily_combo", str(0)) + dailycombo.set_attribute("daily_combo_lv", str(0)) # Last cursor settings - last = Node.void('last') + last = Node.void("last") root.add_child(last) - lastdict = profile.get_dict('last') - last.set_attribute('rival1', str(lastdict.get_int('rival1', -1))) - last.set_attribute('rival2', str(lastdict.get_int('rival2', -1))) - last.set_attribute('rival3', str(lastdict.get_int('rival3', -1))) - last.set_attribute('fri', str(lastdict.get_int('rival1', -1))) # This literally goes to the same memory in 2014 - last.set_attribute('style', str(lastdict.get_int('style'))) - last.set_attribute('mode', str(lastdict.get_int('mode'))) - last.set_attribute('cate', str(lastdict.get_int('cate'))) - last.set_attribute('sort', str(lastdict.get_int('sort'))) - last.set_attribute('mid', str(lastdict.get_int('mid'))) - last.set_attribute('mtype', str(lastdict.get_int('mtype'))) - last.set_attribute('cid', str(lastdict.get_int('cid'))) - last.set_attribute('ctype', str(lastdict.get_int('ctype'))) - last.set_attribute('sid', str(lastdict.get_int('sid'))) + lastdict = profile.get_dict("last") + last.set_attribute("rival1", str(lastdict.get_int("rival1", -1))) + last.set_attribute("rival2", str(lastdict.get_int("rival2", -1))) + last.set_attribute("rival3", str(lastdict.get_int("rival3", -1))) + last.set_attribute( + "fri", str(lastdict.get_int("rival1", -1)) + ) # This literally goes to the same memory in 2014 + last.set_attribute("style", str(lastdict.get_int("style"))) + last.set_attribute("mode", str(lastdict.get_int("mode"))) + last.set_attribute("cate", str(lastdict.get_int("cate"))) + last.set_attribute("sort", str(lastdict.get_int("sort"))) + last.set_attribute("mid", str(lastdict.get_int("mid"))) + last.set_attribute("mtype", str(lastdict.get_int("mtype"))) + last.set_attribute("cid", str(lastdict.get_int("cid"))) + last.set_attribute("ctype", str(lastdict.get_int("ctype"))) + last.set_attribute("sid", str(lastdict.get_int("sid"))) # Result stars - result_star = Node.void('result_star') + result_star = Node.void("result_star") root.add_child(result_star) - result_stars = profile.get_int_array('result_stars', 9) + result_stars = profile.get_int_array("result_stars", 9) for i in range(9): - result_star.set_attribute(f'slot{i + 1}', str(result_stars[i])) + result_star.set_attribute(f"slot{i + 1}", str(result_stars[i])) # Groove gauge level-ups - gr_s = Node.void('gr_s') + gr_s = Node.void("gr_s") root.add_child(gr_s) index = 1 - for entry in profile.get_int_array('gr_s', 5): - gr_s.set_attribute(f'gr{index}', str(entry)) + for entry in profile.get_int_array("gr_s", 5): + gr_s.set_attribute(f"gr{index}", str(entry)) index = index + 1 - gr_d = Node.void('gr_d') + gr_d = Node.void("gr_d") root.add_child(gr_d) index = 1 - for entry in profile.get_int_array('gr_d', 5): - gr_d.set_attribute(f'gr{index}', str(entry)) + for entry in profile.get_int_array("gr_d", 5): + gr_d.set_attribute(f"gr{index}", str(entry)) index = index + 1 # Options in menus - root.add_child(Node.s16_array('opt', profile.get_int_array('opt', 16))) - root.add_child(Node.s16_array('opt_ex', profile.get_int_array('opt_ex', 16))) - option_ver = Node.void('option_ver') + root.add_child(Node.s16_array("opt", profile.get_int_array("opt", 16))) + root.add_child(Node.s16_array("opt_ex", profile.get_int_array("opt_ex", 16))) + option_ver = Node.void("option_ver") root.add_child(option_ver) - option_ver.set_attribute('ver', str(profile.get_int('option_ver', 2))) - if 'option_02' in profile: - root.add_child(Node.s16_array('option_02', profile.get_int_array('option_02', 24))) + option_ver.set_attribute("ver", str(profile.get_int("option_ver", 2))) + if "option_02" in profile: + root.add_child( + Node.s16_array("option_02", profile.get_int_array("option_02", 24)) + ) # Unlock flags - root.add_child(Node.u8_array('flag', profile.get_int_array('flag', 512, [1] * 512)[:256])) - root.add_child(Node.u8_array('flag_ex', profile.get_int_array('flag', 512, [1] * 512))) + root.add_child( + Node.u8_array("flag", profile.get_int_array("flag", 512, [1] * 512)[:256]) + ) + root.add_child( + Node.u8_array("flag_ex", profile.get_int_array("flag", 512, [1] * 512)) + ) # Ranking display? - root.add_child(Node.u16_array('rank', profile.get_int_array('rank', 100))) + root.add_child(Node.u16_array("rank", profile.get_int_array("rank", 100))) # Rivals links = self.data.local.user.get_links(self.game, self.version, userid) for link in links: - if link.type[:7] != 'friend_': + if link.type[:7] != "friend_": continue pos = int(link.type[7:]) friend = self.get_profile(link.other_userid) play_stats = self.get_play_statistics(link.other_userid) if friend is not None: - friendnode = Node.void('friend') + friendnode = Node.void("friend") root.add_child(friendnode) - friendnode.set_attribute('pos', str(pos)) - friendnode.set_attribute('vs', '0') - friendnode.set_attribute('up', '0') - friendnode.add_child(Node.u32('code', friend.extid)) - friendnode.add_child(Node.string('name', friend.get_str('name'))) - friendnode.add_child(Node.u8('area', friend.get_int('area', self.get_machine_region()))) - friendnode.add_child(Node.u32('exp', play_stats.get_int('exp'))) - friendnode.add_child(Node.u32('star', friend.get_int('star'))) + friendnode.set_attribute("pos", str(pos)) + friendnode.set_attribute("vs", "0") + friendnode.set_attribute("up", "0") + friendnode.add_child(Node.u32("code", friend.extid)) + friendnode.add_child(Node.string("name", friend.get_str("name"))) + friendnode.add_child( + Node.u8("area", friend.get_int("area", self.get_machine_region())) + ) + friendnode.add_child(Node.u32("exp", play_stats.get_int("exp"))) + friendnode.add_child(Node.u32("star", friend.get_int("star"))) # Drill rankings - if 'title' in friend: - title = Node.void('title') + if "title" in friend: + title = Node.void("title") friendnode.add_child(title) - titledict = friend.get_dict('title') - if 't' in titledict: - title.set_attribute('t', str(titledict.get_int('t'))) - if 's' in titledict: - title.set_attribute('s', str(titledict.get_int('s'))) - if 'd' in titledict: - title.set_attribute('d', str(titledict.get_int('d'))) + titledict = friend.get_dict("title") + if "t" in titledict: + title.set_attribute("t", str(titledict.get_int("t"))) + if "s" in titledict: + title.set_attribute("s", str(titledict.get_int("s"))) + if "d" in titledict: + title.set_attribute("d", str(titledict.get_int("d"))) - if 'title_gr' in friend: - title_gr = Node.void('title_gr') + if "title_gr" in friend: + title_gr = Node.void("title_gr") friendnode.add_child(title_gr) - title_grdict = friend.get_dict('title_gr') - if 't' in title_grdict: - title_gr.set_attribute('t', str(title_grdict.get_int('t'))) - if 's' in title_grdict: - title_gr.set_attribute('s', str(title_grdict.get_int('s'))) - if 'd' in title_grdict: - title_gr.set_attribute('d', str(title_grdict.get_int('d'))) + title_grdict = friend.get_dict("title_gr") + if "t" in title_grdict: + title_gr.set_attribute("t", str(title_grdict.get_int("t"))) + if "s" in title_grdict: + title_gr.set_attribute("s", str(title_grdict.get_int("s"))) + if "d" in title_grdict: + title_gr.set_attribute("d", str(title_grdict.get_int("d"))) # Groove gauge level-ups - gr_s = Node.void('gr_s') + gr_s = Node.void("gr_s") friendnode.add_child(gr_s) index = 1 - for entry in friend.get_int_array('gr_s', 5): - gr_s.set_attribute(f'gr{index}', str(entry)) + for entry in friend.get_int_array("gr_s", 5): + gr_s.set_attribute(f"gr{index}", str(entry)) index = index + 1 - gr_d = Node.void('gr_d') + gr_d = Node.void("gr_d") friendnode.add_child(gr_d) index = 1 - for entry in friend.get_int_array('gr_d', 5): - gr_d.set_attribute(f'gr{index}', str(entry)) + for entry in friend.get_int_array("gr_d", 5): + gr_d.set_attribute(f"gr{index}", str(entry)) index = index + 1 # Target stuff - target = Node.void('target') + target = Node.void("target") root.add_child(target) - target.set_attribute('flag', str(profile.get_int('target_flag'))) - target.set_attribute('setnum', str(profile.get_int('target_setnum'))) + target.set_attribute("flag", str(profile.get_int("target_flag"))) + target.set_attribute("setnum", str(profile.get_int("target_setnum"))) # Play area - areas = profile.get_int_array('play_area', 55) - play_area = Node.void('play_area') + areas = profile.get_int_array("play_area", 55) + play_area = Node.void("play_area") root.add_child(play_area) for i in range(len(areas)): - play_area.set_attribute(f'play_cnt{i}', str(areas[i])) + play_area.set_attribute(f"play_cnt{i}", str(areas[i])) return root - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() play_stats = self.get_play_statistics(userid) # Grab last node and accessories so we can make decisions based on type - last = request.child('last') - lastdict = newprofile.get_dict('last') - mode = int(last.attribute('mode')) - style = int(last.attribute('style')) + last = request.child("last") + lastdict = newprofile.get_dict("last") + mode = int(last.attribute("mode")) + style = int(last.attribute("style")) is_dp = style == self.GAME_STYLE_DOUBLE # Drill rankings - title = request.child('title') - title_gr = request.child('title_gr') - titledict = newprofile.get_dict('title') - title_grdict = newprofile.get_dict('title_gr') + title = request.child("title") + title_gr = request.child("title_gr") + titledict = newprofile.get_dict("title") + title_grdict = newprofile.get_dict("title_gr") # Groove radar level ups - gr = request.child('gr') + gr = request.child("gr") # Set the correct values depending on if we're single or double play if is_dp: - play_stats.increment_int('double_plays') + play_stats.increment_int("double_plays") if gr is not None: newprofile.replace_int_array( - 'gr_d', + "gr_d", 5, [ - intish(gr.attribute('gr1')), - intish(gr.attribute('gr2')), - intish(gr.attribute('gr3')), - intish(gr.attribute('gr4')), - intish(gr.attribute('gr5')), + intish(gr.attribute("gr1")), + intish(gr.attribute("gr2")), + intish(gr.attribute("gr3")), + intish(gr.attribute("gr4")), + intish(gr.attribute("gr5")), ], ) if title is not None: - titledict.replace_int('d', title.value) - newprofile.replace_dict('title', titledict) + titledict.replace_int("d", title.value) + newprofile.replace_dict("title", titledict) if title_gr is not None: - title_grdict.replace_int('d', title.value) - newprofile.replace_dict('title_gr', title_grdict) + title_grdict.replace_int("d", title.value) + newprofile.replace_dict("title_gr", title_grdict) else: - play_stats.increment_int('single_plays') + play_stats.increment_int("single_plays") if gr is not None: newprofile.replace_int_array( - 'gr_s', + "gr_s", 5, [ - intish(gr.attribute('gr1')), - intish(gr.attribute('gr2')), - intish(gr.attribute('gr3')), - intish(gr.attribute('gr4')), - intish(gr.attribute('gr5')), + intish(gr.attribute("gr1")), + intish(gr.attribute("gr2")), + intish(gr.attribute("gr3")), + intish(gr.attribute("gr4")), + intish(gr.attribute("gr5")), ], ) if title is not None: - titledict.replace_int('s', title.value) - newprofile.replace_dict('title', titledict) + titledict.replace_int("s", title.value) + newprofile.replace_dict("title", titledict) if title_gr is not None: - title_grdict.replace_int('s', title.value) - newprofile.replace_dict('title_gr', title_grdict) - play_stats.increment_int(f'cnt_m{mode}') + title_grdict.replace_int("s", title.value) + newprofile.replace_dict("title_gr", title_grdict) + play_stats.increment_int(f"cnt_m{mode}") # Result stars - result_star = request.child('result_star') + result_star = request.child("result_star") if result_star is not None: newprofile.replace_int_array( - 'result_stars', + "result_stars", 9, [ - intish(result_star.attribute('slot1')), - intish(result_star.attribute('slot2')), - intish(result_star.attribute('slot3')), - intish(result_star.attribute('slot4')), - intish(result_star.attribute('slot5')), - intish(result_star.attribute('slot6')), - intish(result_star.attribute('slot7')), - intish(result_star.attribute('slot8')), - intish(result_star.attribute('slot9')), + intish(result_star.attribute("slot1")), + intish(result_star.attribute("slot2")), + intish(result_star.attribute("slot3")), + intish(result_star.attribute("slot4")), + intish(result_star.attribute("slot5")), + intish(result_star.attribute("slot6")), + intish(result_star.attribute("slot7")), + intish(result_star.attribute("slot8")), + intish(result_star.attribute("slot9")), ], ) # Target stuff - target = request.child('target') + target = request.child("target") if target is not None: - newprofile.replace_int('target_flag', intish(target.attribute('flag'))) - newprofile.replace_int('target_setnum', intish(target.attribute('setnum'))) + newprofile.replace_int("target_flag", intish(target.attribute("flag"))) + newprofile.replace_int("target_setnum", intish(target.attribute("setnum"))) # Update last attributes - lastdict.replace_int('rival1', intish(last.attribute('rival1'))) - lastdict.replace_int('rival2', intish(last.attribute('rival2'))) - lastdict.replace_int('rival3', intish(last.attribute('rival3'))) - lastdict.replace_int('style', intish(last.attribute('style'))) - lastdict.replace_int('mode', intish(last.attribute('mode'))) - lastdict.replace_int('cate', intish(last.attribute('cate'))) - lastdict.replace_int('sort', intish(last.attribute('sort'))) - lastdict.replace_int('mid', intish(last.attribute('mid'))) - lastdict.replace_int('mtype', intish(last.attribute('mtype'))) - lastdict.replace_int('cid', intish(last.attribute('cid'))) - lastdict.replace_int('ctype', intish(last.attribute('ctype'))) - lastdict.replace_int('sid', intish(last.attribute('sid'))) - newprofile.replace_dict('last', lastdict) + lastdict.replace_int("rival1", intish(last.attribute("rival1"))) + lastdict.replace_int("rival2", intish(last.attribute("rival2"))) + lastdict.replace_int("rival3", intish(last.attribute("rival3"))) + lastdict.replace_int("style", intish(last.attribute("style"))) + lastdict.replace_int("mode", intish(last.attribute("mode"))) + lastdict.replace_int("cate", intish(last.attribute("cate"))) + lastdict.replace_int("sort", intish(last.attribute("sort"))) + lastdict.replace_int("mid", intish(last.attribute("mid"))) + lastdict.replace_int("mtype", intish(last.attribute("mtype"))) + lastdict.replace_int("cid", intish(last.attribute("cid"))) + lastdict.replace_int("ctype", intish(last.attribute("ctype"))) + lastdict.replace_int("sid", intish(last.attribute("sid"))) + newprofile.replace_dict("last", lastdict) # Grab character options - chara = request.child('chara') + chara = request.child("chara") if chara is not None: - newprofile.replace_int('chara', intish(chara.attribute('my'))) - chara_opt = request.child('chara_opt') + newprofile.replace_int("chara", intish(chara.attribute("my"))) + chara_opt = request.child("chara_opt") if chara_opt is not None: # A bug in old versions of AVS returns the wrong number for set - newprofile.replace_int_array('chara_opt', 96, chara_opt.value[:96]) + newprofile.replace_int_array("chara_opt", 96, chara_opt.value[:96]) # Options - option_02 = request.child('option_02') + option_02 = request.child("option_02") if option_02 is not None: # A bug in old versions of AVS returns the wrong number for set - newprofile.replace_int_array('option_02', 24, option_02.value[:24]) + newprofile.replace_int_array("option_02", 24, option_02.value[:24]) # Experience and stars - exp = request.child_value('exp') + exp = request.child_value("exp") if exp is not None: - play_stats.replace_int('exp', play_stats.get_int('exp') + exp) - star = request.child_value('star') + play_stats.replace_int("exp", play_stats.get_int("exp") + exp) + star = request.child_value("star") if star is not None: - newprofile.replace_int('star', newprofile.get_int('star') + star) - star_c = request.child_value('star_c') + newprofile.replace_int("star", newprofile.get_int("star") + star) + star_c = request.child_value("star_c") if star_c is not None: - newprofile.replace_int('star_c', newprofile.get_int('star_c') + exp) + newprofile.replace_int("star_c", newprofile.get_int("star_c") + exp) # Update game flags for child in request.children: - if child.name not in ['flag', 'flag_ex']: + if child.name not in ["flag", "flag_ex"]: continue try: - value = int(child.attribute('data')) - offset = int(child.attribute('no')) + value = int(child.attribute("data")) + offset = int(child.attribute("no")) except ValueError: continue - flags = newprofile.get_int_array('flag', 512, [1] * 512) + flags = newprofile.get_int_array("flag", 512, [1] * 512) if offset < 0 or offset >= len(flags): continue flags[offset] = value - newprofile.replace_int_array('flag', 512, flags) + newprofile.replace_int_array("flag", 512, flags) # Workout mode support newweight = -1 - oldweight = newprofile.get_int('weight') + oldweight = newprofile.get_int("weight") for child in request.children: - if child.name != 'weight': + if child.name != "weight": continue newweight = child.value if newweight < 0: @@ -686,14 +714,14 @@ class DDR2014( # Either update or unset the weight depending on the game if newweight == 0: # Weight is unset or we declined to use this feature, remove from profile - if 'weight' in newprofile: - del newprofile['weight'] + if "weight" in newprofile: + del newprofile["weight"] else: # Weight has been set or previously retrieved, we should save calories - newprofile.replace_int('weight', newweight) + newprofile.replace_int("weight", newweight) total = 0 for child in request.children: - if child.name != 'calory': + if child.name != "calory": continue total += child.value self.data.local.user.put_time_based_achievement( @@ -701,10 +729,10 @@ class DDR2014( self.version, userid, 0, - 'workout', + "workout", { - 'calories': total, - 'weight': newweight, + "calories": total, + "weight": newweight, }, ) @@ -712,7 +740,7 @@ class DDR2014( oldfriends: List[Optional[UserID]] = [None] * 10 links = self.data.local.user.get_links(self.game, self.version, userid) for link in links: - if link.type[:7] != 'friend_': + if link.type[:7] != "friend_": continue pos = int(link.type[7:]) @@ -721,11 +749,11 @@ class DDR2014( # Save any rivals that were added/removed/changed newfriends = oldfriends[:] for child in request.children: - if child.name != 'friend': + if child.name != "friend": continue - code = int(child.attribute('code')) - pos = int(child.attribute('pos')) + code = int(child.attribute("code")) + pos = int(child.attribute("pos")) if pos >= 0 and pos < 10: if code == 0: @@ -733,7 +761,9 @@ class DDR2014( newfriends[pos] = None else: # Try looking up the userid - newfriends[pos] = self.data.remote.user.from_extid(self.game, self.version, code) + newfriends[pos] = self.data.remote.user.from_extid( + self.game, self.version, code + ) # Diff the set of links to determine updates for i in range(10): @@ -746,7 +776,7 @@ class DDR2014( self.game, self.version, userid, - f'friend_{i}', + f"friend_{i}", oldfriends[i], ) elif oldfriends[i] is None: @@ -755,7 +785,7 @@ class DDR2014( self.game, self.version, userid, - f'friend_{i}', + f"friend_{i}", newfriends[i], {}, ) @@ -765,24 +795,24 @@ class DDR2014( self.game, self.version, userid, - f'friend_{i}', + f"friend_{i}", oldfriends[i], ) self.data.local.user.put_link( self.game, self.version, userid, - f'friend_{i}', + f"friend_{i}", newfriends[i], {}, ) # Play area counter - shop_area = int(request.attribute('shop_area')) + shop_area = int(request.attribute("shop_area")) if shop_area >= 0 and shop_area < 55: - areas = newprofile.get_int_array('play_area', 55) + areas = newprofile.get_int_array("play_area", 55) areas[shop_area] = areas[shop_area] + 1 - newprofile.replace_int_array('play_area', 55, areas) + newprofile.replace_int_array("play_area", 55, areas) # Keep track of play statistics self.update_play_statistics(userid, play_stats) diff --git a/bemani/backend/ddr/ddra20.py b/bemani/backend/ddr/ddra20.py index cb3245d..6cd87e7 100644 --- a/bemani/backend/ddr/ddra20.py +++ b/bemani/backend/ddr/ddra20.py @@ -10,7 +10,7 @@ class DDRA20( DDRBase, ): - name: str = 'DanceDanceRevolution A20' + name: str = "DanceDanceRevolution A20" version: int = VersionConstants.DDR_A20 def previous_version(self) -> Optional[DDRBase]: @@ -18,7 +18,7 @@ class DDRA20( @property def supports_paseli(self) -> bool: - if self.model.dest != 'J': + if self.model.dest != "J": # DDR Ace in USA mode doesn't support PASELI properly. # When in Asia mode it shows PASELI but won't let you select it. return False diff --git a/bemani/backend/ddr/ddrace.py b/bemani/backend/ddr/ddrace.py index 7a45f06..3c211e6 100644 --- a/bemani/backend/ddr/ddrace.py +++ b/bemani/backend/ddr/ddrace.py @@ -6,7 +6,15 @@ from typing_extensions import Final from bemani.backend.ess import EventLogHandler from bemani.backend.ddr.base import DDRBase from bemani.backend.ddr.ddr2014 import DDR2014 -from bemani.common import Profile, ValidatedDict, VersionConstants, CardCipher, Time, ID, intish +from bemani.common import ( + Profile, + ValidatedDict, + VersionConstants, + CardCipher, + Time, + ID, + intish, +) from bemani.data import Data, Achievement, Machine, Score, UserID from bemani.protocol import Node @@ -16,7 +24,7 @@ class DDRAce( EventLogHandler, ): - name: str = 'DanceDanceRevolution A' + name: str = "DanceDanceRevolution A" version: int = VersionConstants.DDR_ACE GAME_STYLE_SINGLE: Final[int] = 0 @@ -106,7 +114,9 @@ class DDRAce( return DDR2014(self.data, self.config, self.model) @classmethod - def run_scheduled_work(cls, data: Data, config: Dict[str, Any]) -> List[Tuple[str, Dict[str, Any]]]: + def run_scheduled_work( + cls, data: Data, config: Dict[str, Any] + ) -> List[Tuple[str, Dict[str, Any]]]: # DDR Ace has a weird bug where it sends a profile save for a blank # profile before reading it back when creating a new profile. If there # is no profile on read-back, it errors out, and it also uses the name @@ -125,20 +135,25 @@ class DDRAce( events = [] for userid, profile in profiles: - if profile.get_str('name') == "" and profile.get_int('write_time') < several_minutes_ago: + if ( + profile.get_str("name") == "" + and profile.get_int("write_time") < several_minutes_ago + ): data.local.user.delete_profile(cls.game, cls.version, userid) - events.append(( - 'ddr_profile_purge', - { - 'userid': userid, - }, - )) + events.append( + ( + "ddr_profile_purge", + { + "userid": userid, + }, + ) + ) return events @property def supports_paseli(self) -> bool: - if self.model.dest != 'J': + if self.model.dest != "J": # DDR Ace in USA mode doesn't support PASELI properly. # When in Asia mode it shows PASELI but won't let you select it. return False @@ -237,19 +252,25 @@ class DDRAce( return self.GAME_HALO_NONE def handle_tax_get_phase_request(self, request: Node) -> Node: - tax = Node.void('tax') - tax.add_child(Node.s32('phase', 0)) + tax = Node.void("tax") + tax.add_child(Node.s32("phase", 0)) return tax - def __handle_userload(self, userid: Optional[UserID], requestdata: Node, response: Node) -> None: + def __handle_userload( + self, userid: Optional[UserID], requestdata: Node, response: Node + ) -> None: has_profile: bool = False achievements: List[Achievement] = [] scores: List[Score] = [] if userid is not None: has_profile = self.has_profile(userid) - achievements = self.data.local.user.get_achievements(self.game, self.version, userid) - scores = self.data.remote.music.get_scores(self.game, self.music_version, userid) + achievements = self.data.local.user.get_achievements( + self.game, self.version, userid + ) + scores = self.data.remote.music.get_scores( + self.game, self.music_version, userid + ) # Place scores into an arrangement for easier distribution to Ace. scores_by_mcode: Dict[int, List[Optional[Score]]] = {} @@ -260,34 +281,43 @@ class DDRAce( scores_by_mcode[score.id][self.db_to_game_chart(score.chart)] = score # First, set new flag - response.add_child(Node.bool('is_new', not has_profile)) + response.add_child(Node.bool("is_new", not has_profile)) # Now, return the scores to Ace for mcode in scores_by_mcode: - music = Node.void('music') + music = Node.void("music") response.add_child(music) - music.add_child(Node.u32('mcode', mcode)) + music.add_child(Node.u32("mcode", mcode)) scores_that_matter = scores_by_mcode[mcode] while scores_that_matter[-1] is None: scores_that_matter = scores_that_matter[:-1] for score in scores_that_matter: - note = Node.void('note') + note = Node.void("note") music.add_child(note) if score is None: - note.add_child(Node.u16('count', 0)) - note.add_child(Node.u8('rank', 0)) - note.add_child(Node.u8('clearkind', 0)) - note.add_child(Node.s32('score', 0)) - note.add_child(Node.s32('ghostid', 0)) + note.add_child(Node.u16("count", 0)) + note.add_child(Node.u8("rank", 0)) + note.add_child(Node.u8("clearkind", 0)) + note.add_child(Node.s32("score", 0)) + note.add_child(Node.s32("ghostid", 0)) else: - note.add_child(Node.u16('count', score.plays)) - note.add_child(Node.u8('rank', self.db_to_game_rank(score.data.get_int('rank')))) - note.add_child(Node.u8('clearkind', self.db_to_game_halo(score.data.get_int('halo')))) - note.add_child(Node.s32('score', score.points)) - note.add_child(Node.s32('ghostid', score.key)) + note.add_child(Node.u16("count", score.plays)) + note.add_child( + Node.u8( + "rank", self.db_to_game_rank(score.data.get_int("rank")) + ) + ) + note.add_child( + Node.u8( + "clearkind", + self.db_to_game_halo(score.data.get_int("halo")), + ) + ) + note.add_child(Node.s32("score", score.points)) + note.add_child(Node.s32("ghostid", score.key)) # Active event settings activeevents = [ @@ -331,69 +361,83 @@ class DDRAce( # Event reward settings rewards = { - '30': { + "30": { 999: 5, } } # Now handle event progress and activation. - events = {ach.id: ach.data for ach in achievements if ach.type == '9999'} - progress = [ach for ach in achievements if ach.type != '9999'] + events = {ach.id: ach.data for ach in achievements if ach.type == "9999"} + progress = [ach for ach in achievements if ach.type != "9999"] # Make sure we always send a babylon's adventure save event or the game won't send progress babylon_included = False for evtprogress in progress: - if evtprogress.id == 999 and evtprogress.type == '30': + if evtprogress.id == 999 and evtprogress.type == "30": babylon_included = True break if not babylon_included: - progress.append(Achievement( - 999, - '30', - None, - { - 'completed': False, - 'progress': 0, - }, - )) + progress.append( + Achievement( + 999, + "30", + None, + { + "completed": False, + "progress": 0, + }, + ) + ) for event in activeevents: # Get completion data - playerstats = events.get(event, ValidatedDict({'completed': False})) + playerstats = events.get(event, ValidatedDict({"completed": False})) # Return the data - eventdata = Node.void('eventdata') + eventdata = Node.void("eventdata") response.add_child(eventdata) - eventdata.add_child(Node.u32('eventid', event)) - eventdata.add_child(Node.s32('eventtype', 9999)) - eventdata.add_child(Node.u32('eventno', 0)) - eventdata.add_child(Node.s64('condition', 0)) - eventdata.add_child(Node.u32('reward', 0)) - eventdata.add_child(Node.s32('comptime', 1 if playerstats.get_bool('completed') else 0)) - eventdata.add_child(Node.s64('savedata', 0)) + eventdata.add_child(Node.u32("eventid", event)) + eventdata.add_child(Node.s32("eventtype", 9999)) + eventdata.add_child(Node.u32("eventno", 0)) + eventdata.add_child(Node.s64("condition", 0)) + eventdata.add_child(Node.u32("reward", 0)) + eventdata.add_child( + Node.s32("comptime", 1 if playerstats.get_bool("completed") else 0) + ) + eventdata.add_child(Node.s64("savedata", 0)) for evtprogress in progress: # Babylon's adventure progres and anything else the game sends - eventdata = Node.void('eventdata') + eventdata = Node.void("eventdata") response.add_child(eventdata) - eventdata.add_child(Node.u32('eventid', evtprogress.id)) - eventdata.add_child(Node.s32('eventtype', int(evtprogress.type))) - eventdata.add_child(Node.u32('eventno', 0)) - eventdata.add_child(Node.s64('condition', 0)) - eventdata.add_child(Node.u32('reward', rewards.get(evtprogress.type, {}).get(evtprogress.id))) - eventdata.add_child(Node.s32('comptime', 1 if evtprogress.data.get_bool('completed') else 0)) - eventdata.add_child(Node.s64('savedata', evtprogress.data.get_int('progress'))) + eventdata.add_child(Node.u32("eventid", evtprogress.id)) + eventdata.add_child(Node.s32("eventtype", int(evtprogress.type))) + eventdata.add_child(Node.u32("eventno", 0)) + eventdata.add_child(Node.s64("condition", 0)) + eventdata.add_child( + Node.u32( + "reward", rewards.get(evtprogress.type, {}).get(evtprogress.id) + ) + ) + eventdata.add_child( + Node.s32("comptime", 1 if evtprogress.data.get_bool("completed") else 0) + ) + eventdata.add_child( + Node.s64("savedata", evtprogress.data.get_int("progress")) + ) - def __handle_usersave(self, userid: Optional[UserID], requestdata: Node, response: Node) -> None: + def __handle_usersave( + self, userid: Optional[UserID], requestdata: Node, response: Node + ) -> None: if userid is None: # the game sends us empty user ID strings when a guest is playing. # Return early so it doesn't wait a minute and a half to show the # results screen. return - if requestdata.child_value('isgameover'): - style = int(requestdata.child_value('playstyle')) + if requestdata.child_value("isgameover"): + style = int(requestdata.child_value("playstyle")) is_dp = style == self.GAME_STYLE_DOUBLE # We don't save anything for gameover requests, since we @@ -401,52 +445,52 @@ class DDRAce( # as a spot to bump play counts and such play_stats = self.get_play_statistics(userid) if is_dp: - play_stats.increment_int('double_plays') + play_stats.increment_int("double_plays") else: - play_stats.increment_int('single_plays') + play_stats.increment_int("single_plays") self.update_play_statistics(userid, play_stats) # Now is a good time to check if we have workout mode enabled, # and if so, store the calories earned for this set. profile = self.get_profile(userid) - enabled = profile.get_bool('workout_mode') - weight = profile.get_int('weight') + enabled = profile.get_bool("workout_mode") + weight = profile.get_int("weight") if enabled and weight > 0: # We enabled weight display, find the calories and save them total = 0 for child in requestdata.children: - if child.name != 'note': + if child.name != "note": continue - total = total + (child.child_value('calorie') or 0) + total = total + (child.child_value("calorie") or 0) self.data.local.user.put_time_based_achievement( self.game, self.version, userid, 0, - 'workout', + "workout", { - 'calories': total, - 'weight': weight, + "calories": total, + "weight": weight, }, ) # Find any event updates for child in requestdata.children: - if child.name != 'event': + if child.name != "event": continue # Skip empty events or events we don't support - eventid = child.child_value('eventid') - eventtype = child.child_value('eventtype') + eventid = child.child_value("eventid") + eventtype = child.child_value("eventtype") if eventid == 0 or eventtype == 0: continue # Save data to replay to the client later - completed = child.child_value('comptime') != 0 - progress = child.child_value('savedata') + completed = child.child_value("comptime") != 0 + progress = child.child_value("savedata") self.data.local.user.put_achievement( self.game, @@ -455,8 +499,8 @@ class DDRAce( eventid, str(eventtype), { - 'completed': completed, - 'progress': progress, + "completed": completed, + "progress": progress, }, ) @@ -466,23 +510,23 @@ class DDRAce( score = None stagenum = 0 for child in requestdata.children: - if child.name != 'note': + if child.name != "note": continue - if child.child_value('stagenum') > stagenum: + if child.child_value("stagenum") > stagenum: score = child - stagenum = child.child_value('stagenum') + stagenum = child.child_value("stagenum") if score is None: - raise Exception('Couldn\'t find newest score to save!') + raise Exception("Couldn't find newest score to save!") - songid = score.child_value('mcode') - chart = self.game_to_db_chart(score.child_value('notetype')) - rank = self.game_to_db_rank(score.child_value('rank')) - halo = self.game_to_db_halo(score.child_value('clearkind')) - points = score.child_value('score') - combo = score.child_value('maxcombo') - ghost = score.child_value('ghost') + songid = score.child_value("mcode") + chart = self.game_to_db_chart(score.child_value("notetype")) + rank = self.game_to_db_rank(score.child_value("rank")) + halo = self.game_to_db_halo(score.child_value("clearkind")) + points = score.child_value("score") + combo = score.child_value("maxcombo") + ghost = score.child_value("ghost") self.update_score( userid, songid, @@ -494,15 +538,17 @@ class DDRAce( ghost=ghost, ) - def __handle_rivalload(self, userid: Optional[UserID], requestdata: Node, response: Node) -> None: - data = Node.void('data') + def __handle_rivalload( + self, userid: Optional[UserID], requestdata: Node, response: Node + ) -> None: + data = Node.void("data") response.add_child(data) - data.add_child(Node.s32('recordtype', requestdata.child_value('loadflag'))) + data.add_child(Node.s32("recordtype", requestdata.child_value("loadflag"))) thismachine = self.data.local.machine.get_machine(self.config.machine.pcbid) machines_by_id: Dict[int, Optional[Machine]] = {thismachine.id: thismachine} - loadkind = requestdata.child_value('loadflag') + loadkind = requestdata.child_value("loadflag") profiles_by_userid: Dict[UserID, Profile] = {} def get_machine(lid: int) -> Optional[Machine]: @@ -522,7 +568,9 @@ class DDRAce( if loadkind == self.GAME_RIVAL_TYPE_WORLD: # Just load all scores for this network - scores = self.data.remote.music.get_all_records(self.game, self.music_version) + scores = self.data.remote.music.get_all_records( + self.game, self.music_version + ) elif loadkind == self.GAME_RIVAL_TYPE_AREA: if thismachine.arcade is not None: match_arcade = thismachine.arcade @@ -540,57 +588,71 @@ class DDRAce( # If we have an arcade to match, see if this user's location matches the arcade. # If we don't, just match lid directly if match_arcade is not None: - theirmachine = get_machine(profiledata.get_int('lid')) + theirmachine = get_machine(profiledata.get_int("lid")) if theirmachine is not None and theirmachine.arcade == match_arcade: userids.append(userid) elif match_machine is not None: - if profiledata.get_int('lid') == match_machine: + if profiledata.get_int("lid") == match_machine: userids.append(userid) # Load all scores for users in the area - scores = self.data.local.music.get_all_records(self.game, self.music_version, userlist=userids) + scores = self.data.local.music.get_all_records( + self.game, self.music_version, userlist=userids + ) elif loadkind == self.GAME_RIVAL_TYPE_MACHINE: # Load up all scores and filter them by those earned at this location - scores = self.data.local.music.get_all_records(self.game, self.music_version, locationlist=[thismachine.id]) + scores = self.data.local.music.get_all_records( + self.game, self.music_version, locationlist=[thismachine.id] + ) elif loadkind in [ self.GAME_RIVAL_TYPE_RIVAL1, self.GAME_RIVAL_TYPE_RIVAL2, self.GAME_RIVAL_TYPE_RIVAL3, ]: # Load up this user's highscores, format the way the below code expects it - extid = requestdata.child_value('ddrcode') + extid = requestdata.child_value("ddrcode") otherid = self.data.remote.user.from_extid(self.game, self.version, extid) - userscores = self.data.remote.music.get_scores(self.game, self.music_version, otherid) + userscores = self.data.remote.music.get_scores( + self.game, self.music_version, otherid + ) scores = [(otherid, score) for score in userscores] else: # Nothing here scores = [] - missing_users = [userid for (userid, _) in scores if userid not in profiles_by_userid] + missing_users = [ + userid for (userid, _) in scores if userid not in profiles_by_userid + ] for (userid, profile) in self.get_any_profiles(missing_users): profiles_by_userid[userid] = profile for userid, score in scores: if profiles_by_userid.get(userid) is None: - raise Exception(f'Logic error, couldn\'t find any profile for {userid}') + raise Exception(f"Logic error, couldn't find any profile for {userid}") profiledata = profiles_by_userid[userid] - record = Node.void('record') + record = Node.void("record") data.add_child(record) - record.add_child(Node.u32('mcode', score.id)) - record.add_child(Node.u8('notetype', self.db_to_game_chart(score.chart))) - record.add_child(Node.u8('rank', self.db_to_game_rank(score.data.get_int('rank')))) - record.add_child(Node.u8('clearkind', self.db_to_game_halo(score.data.get_int('halo')))) - record.add_child(Node.u8('flagdata', 0)) - record.add_child(Node.string('name', profiledata.get_str('name'))) - record.add_child(Node.s32('area', profiledata.get_int('area', 58))) - record.add_child(Node.s32('code', profiledata.extid)) - record.add_child(Node.s32('score', score.points)) - record.add_child(Node.s32('ghostid', score.key)) + record.add_child(Node.u32("mcode", score.id)) + record.add_child(Node.u8("notetype", self.db_to_game_chart(score.chart))) + record.add_child( + Node.u8("rank", self.db_to_game_rank(score.data.get_int("rank"))) + ) + record.add_child( + Node.u8("clearkind", self.db_to_game_halo(score.data.get_int("halo"))) + ) + record.add_child(Node.u8("flagdata", 0)) + record.add_child(Node.string("name", profiledata.get_str("name"))) + record.add_child(Node.s32("area", profiledata.get_int("area", 58))) + record.add_child(Node.s32("code", profiledata.extid)) + record.add_child(Node.s32("score", score.points)) + record.add_child(Node.s32("ghostid", score.key)) - def __handle_usernew(self, userid: Optional[UserID], requestdata: Node, response: Node) -> None: + def __handle_usernew( + self, userid: Optional[UserID], requestdata: Node, response: Node + ) -> None: if userid is None: - raise Exception('Expecting valid UserID to create new profile!') + raise Exception("Expecting valid UserID to create new profile!") machine = self.data.local.machine.get_machine(self.config.machine.pcbid) profile = Profile( @@ -599,27 +661,35 @@ class DDRAce( "", 0, { - 'lid': machine.id, + "lid": machine.id, }, ) self.put_profile(userid, profile) - response.add_child(Node.string('seq', ID.format_extid(profile.extid))) - response.add_child(Node.s32('code', profile.extid)) - response.add_child(Node.string('shoparea', '')) + response.add_child(Node.string("seq", ID.format_extid(profile.extid))) + response.add_child(Node.s32("code", profile.extid)) + response.add_child(Node.string("shoparea", "")) - def __handle_inheritance(self, userid: Optional[UserID], requestdata: Node, response: Node) -> None: + def __handle_inheritance( + self, userid: Optional[UserID], requestdata: Node, response: Node + ) -> None: if userid is not None: previous_version = self.previous_version() profile = previous_version.get_profile(userid) else: profile = None - response.add_child(Node.s32('InheritanceStatus', 1 if profile is not None else 0)) + response.add_child( + Node.s32("InheritanceStatus", 1 if profile is not None else 0) + ) - def __handle_ghostload(self, userid: Optional[UserID], requestdata: Node, response: Node) -> None: - ghostid = requestdata.child_value('ghostid') - ghost = self.data.local.music.get_score_by_key(self.game, self.music_version, ghostid) + def __handle_ghostload( + self, userid: Optional[UserID], requestdata: Node, response: Node + ) -> None: + ghostid = requestdata.child_value("ghostid") + ghost = self.data.local.music.get_score_by_key( + self.game, self.music_version, ghostid + ) if ghost is None: return @@ -628,110 +698,188 @@ class DDRAce( if profile is None: return - if 'ghost' not in score.data: + if "ghost" not in score.data: return - ghostdata = Node.void('ghostdata') + ghostdata = Node.void("ghostdata") response.add_child(ghostdata) - ghostdata.add_child(Node.s32('code', profile.extid)) - ghostdata.add_child(Node.u32('mcode', score.id)) - ghostdata.add_child(Node.u8('notetype', self.db_to_game_chart(score.chart))) - ghostdata.add_child(Node.s32('ghostsize', len(score.data['ghost']))) - ghostdata.add_child(Node.string('ghost', score.data['ghost'])) + ghostdata.add_child(Node.s32("code", profile.extid)) + ghostdata.add_child(Node.u32("mcode", score.id)) + ghostdata.add_child(Node.u8("notetype", self.db_to_game_chart(score.chart))) + ghostdata.add_child(Node.s32("ghostsize", len(score.data["ghost"]))) + ghostdata.add_child(Node.string("ghost", score.data["ghost"])) - def handle_playerdata_usergamedata_advanced_request(self, request: Node) -> Optional[Node]: - playerdata = Node.void('playerdata') + def handle_playerdata_usergamedata_advanced_request( + self, request: Node + ) -> Optional[Node]: + playerdata = Node.void("playerdata") # DDR Ace decides to be difficult and have a third level of packet switching - mode = request.child_value('data/mode') - refid = request.child_value('data/refid') - extid = request.child_value('data/ddrcode') + mode = request.child_value("data/mode") + refid = request.child_value("data/refid") + extid = request.child_value("data/ddrcode") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is None: # Possibly look up by extid instead userid = self.data.remote.user.from_extid(self.game, self.version, extid) - if mode == 'userload': - self.__handle_userload(userid, request.child('data'), playerdata) - elif mode == 'usersave': - self.__handle_usersave(userid, request.child('data'), playerdata) - elif mode == 'rivalload': - self.__handle_rivalload(userid, request.child('data'), playerdata) - elif mode == 'usernew': - self.__handle_usernew(userid, request.child('data'), playerdata) - elif mode == 'inheritance': - self.__handle_inheritance(userid, request.child('data'), playerdata) - elif mode == 'ghostload': - self.__handle_ghostload(userid, request.child('data'), playerdata) + if mode == "userload": + self.__handle_userload(userid, request.child("data"), playerdata) + elif mode == "usersave": + self.__handle_usersave(userid, request.child("data"), playerdata) + elif mode == "rivalload": + self.__handle_rivalload(userid, request.child("data"), playerdata) + elif mode == "usernew": + self.__handle_usernew(userid, request.child("data"), playerdata) + elif mode == "inheritance": + self.__handle_inheritance(userid, request.child("data"), playerdata) + elif mode == "ghostload": + self.__handle_ghostload(userid, request.child("data"), playerdata) else: # We don't support this return None - playerdata.add_child(Node.s32('result', 0)) + playerdata.add_child(Node.s32("result", 0)) return playerdata def handle_playerdata_usergamedata_send_request(self, request: Node) -> Node: - playerdata = Node.void('playerdata') - refid = request.child_value('data/refid') + playerdata = Node.void("playerdata") + refid = request.child_value("data/refid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: - profile = self.get_profile(userid) or Profile(self.game, self.version, refid, 0) - usergamedata = profile.get_dict('usergamedata') + profile = self.get_profile(userid) or Profile( + self.game, self.version, refid, 0 + ) + usergamedata = profile.get_dict("usergamedata") - for record in request.child('data/record').children: - if record.name != 'd': + for record in request.child("data/record").children: + if record.name != "d": continue strdata = base64.b64decode(record.value) - bindata = base64.b64decode(record.child_value('bin1')) + bindata = base64.b64decode(record.child_value("bin1")) # Grab and format the profile objects - strdatalist = strdata.split(b',') - profiletype = strdatalist[1].decode('utf-8') + strdatalist = strdata.split(b",") + profiletype = strdatalist[1].decode("utf-8") strdatalist = strdatalist[2:] # Extract relevant bits for frontend/API - if profiletype == 'COMMON': - profile.replace_str('name', strdatalist[self.GAME_COMMON_NAME_OFFSET].decode('ascii')) - profile.replace_int('area', intish(strdatalist[self.GAME_COMMON_AREA_OFFSET].decode('ascii'), 16)) - profile.replace_bool('workout_mode', int(strdatalist[self.GAME_COMMON_WEIGHT_DISPLAY_OFFSET].decode('ascii'), 16) != 0) - profile.replace_int('weight', int(float(strdatalist[self.GAME_COMMON_WEIGHT_OFFSET].decode('ascii')) * 10)) - profile.replace_int('character', int(strdatalist[self.GAME_COMMON_CHARACTER_OFFSET].decode('ascii'), 16)) - if profiletype == 'OPTION': - profile.replace_int('combo', int(strdatalist[self.GAME_OPTION_COMBO_POSITION_OFFSET].decode('ascii'), 16)) - profile.replace_int('early_late', int(strdatalist[self.GAME_OPTION_FAST_SLOW_OFFSET].decode('ascii'), 16)) - profile.replace_int('arrowskin', int(strdatalist[self.GAME_OPTION_ARROW_SKIN_OFFSET].decode('ascii'), 16)) - profile.replace_int('guidelines', int(strdatalist[self.GAME_OPTION_GUIDELINE_OFFSET].decode('ascii'), 16)) - profile.replace_int('filter', int(strdatalist[self.GAME_OPTION_FILTER_OFFSET].decode('ascii'), 16)) + if profiletype == "COMMON": + profile.replace_str( + "name", + strdatalist[self.GAME_COMMON_NAME_OFFSET].decode("ascii"), + ) + profile.replace_int( + "area", + intish( + strdatalist[self.GAME_COMMON_AREA_OFFSET].decode("ascii"), + 16, + ), + ) + profile.replace_bool( + "workout_mode", + int( + strdatalist[self.GAME_COMMON_WEIGHT_DISPLAY_OFFSET].decode( + "ascii" + ), + 16, + ) + != 0, + ) + profile.replace_int( + "weight", + int( + float( + strdatalist[self.GAME_COMMON_WEIGHT_OFFSET].decode( + "ascii" + ) + ) + * 10 + ), + ) + profile.replace_int( + "character", + int( + strdatalist[self.GAME_COMMON_CHARACTER_OFFSET].decode( + "ascii" + ), + 16, + ), + ) + if profiletype == "OPTION": + profile.replace_int( + "combo", + int( + strdatalist[self.GAME_OPTION_COMBO_POSITION_OFFSET].decode( + "ascii" + ), + 16, + ), + ) + profile.replace_int( + "early_late", + int( + strdatalist[self.GAME_OPTION_FAST_SLOW_OFFSET].decode( + "ascii" + ), + 16, + ), + ) + profile.replace_int( + "arrowskin", + int( + strdatalist[self.GAME_OPTION_ARROW_SKIN_OFFSET].decode( + "ascii" + ), + 16, + ), + ) + profile.replace_int( + "guidelines", + int( + strdatalist[self.GAME_OPTION_GUIDELINE_OFFSET].decode( + "ascii" + ), + 16, + ), + ) + profile.replace_int( + "filter", + int( + strdatalist[self.GAME_OPTION_FILTER_OFFSET].decode("ascii"), + 16, + ), + ) usergamedata[profiletype] = { - 'strdata': b','.join(strdatalist), - 'bindata': bindata, + "strdata": b",".join(strdatalist), + "bindata": bindata, } - profile.replace_dict('usergamedata', usergamedata) - profile.replace_int('write_time', Time.now()) + profile.replace_dict("usergamedata", usergamedata) + profile.replace_int("write_time", Time.now()) self.put_profile(userid, profile) - playerdata.add_child(Node.s32('result', 0)) + playerdata.add_child(Node.s32("result", 0)) return playerdata def handle_playerdata_usergamedata_recv_request(self, request: Node) -> Node: - playerdata = Node.void('playerdata') + playerdata = Node.void("playerdata") - player = Node.void('player') + player = Node.void("player") playerdata.add_child(player) - refid = request.child_value('data/refid') + refid = request.child_value("data/refid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: profile = self.get_profile(userid) links = self.data.local.user.get_links(self.game, self.version, userid) records = 0 - record = Node.void('record') + record = Node.void("record") player.add_child(record) def acehex(val: int) -> str: @@ -739,70 +887,92 @@ class DDRAce( if profile is None: # Just return a default empty node - record.add_child(Node.string('d', '')) + record.add_child(Node.string("d", "")) records = 1 else: # Figure out what profiles are being requested - profiletypes = request.child_value('data/recv_csv').split(',')[::2] - usergamedata = profile.get_dict('usergamedata') + profiletypes = request.child_value("data/recv_csv").split(",")[::2] + usergamedata = profile.get_dict("usergamedata") for ptype in profiletypes: if ptype in usergamedata: records = records + 1 if ptype == "COMMON": # Return basic profile options - name = profile.get_str('name') - area = profile.get_int('area', self.get_machine_region()) + name = profile.get_str("name") + area = profile.get_int("area", self.get_machine_region()) if name == "": # This is a bogus profile created by the first login, substitute the # previous version values so that profile succession works. previous_version = self.previous_version() old_profile = previous_version.get_profile(userid) if old_profile is not None: - name = old_profile.get_str('name') - area = old_profile.get_int('area', self.get_machine_region()) + name = old_profile.get_str("name") + area = old_profile.get_int( + "area", self.get_machine_region() + ) else: area = self.get_machine_region() - common = usergamedata[ptype]['strdata'].split(b',') - common[self.GAME_COMMON_NAME_OFFSET] = name.encode('ascii') - common[self.GAME_COMMON_AREA_OFFSET] = acehex(area).encode('ascii') - common[self.GAME_COMMON_WEIGHT_DISPLAY_OFFSET] = b'1' if profile.get_bool('workout_mode') else b'0' - common[self.GAME_COMMON_WEIGHT_OFFSET] = str(float(profile.get_int('weight')) / 10.0).encode('ascii') - common[self.GAME_COMMON_CHARACTER_OFFSET] = acehex(profile.get_int('character')).encode('ascii') - usergamedata[ptype]['strdata'] = b','.join(common) + common = usergamedata[ptype]["strdata"].split(b",") + common[self.GAME_COMMON_NAME_OFFSET] = name.encode("ascii") + common[self.GAME_COMMON_AREA_OFFSET] = acehex(area).encode( + "ascii" + ) + common[self.GAME_COMMON_WEIGHT_DISPLAY_OFFSET] = ( + b"1" if profile.get_bool("workout_mode") else b"0" + ) + common[self.GAME_COMMON_WEIGHT_OFFSET] = str( + float(profile.get_int("weight")) / 10.0 + ).encode("ascii") + common[self.GAME_COMMON_CHARACTER_OFFSET] = acehex( + profile.get_int("character") + ).encode("ascii") + usergamedata[ptype]["strdata"] = b",".join(common) if ptype == "OPTION": # Return user settings for frontend - option = usergamedata[ptype]['strdata'].split(b',') - option[self.GAME_OPTION_FAST_SLOW_OFFSET] = acehex(profile.get_int('early_late')).encode('ascii') - option[self.GAME_OPTION_COMBO_POSITION_OFFSET] = acehex(profile.get_int('combo')).encode('ascii') - option[self.GAME_OPTION_ARROW_SKIN_OFFSET] = acehex(profile.get_int('arrowskin')).encode('ascii') - option[self.GAME_OPTION_GUIDELINE_OFFSET] = acehex(profile.get_int('guidelines')).encode('ascii') - option[self.GAME_OPTION_FILTER_OFFSET] = acehex(profile.get_int('filter')).encode('ascii') - usergamedata[ptype]['strdata'] = b','.join(option) + option = usergamedata[ptype]["strdata"].split(b",") + option[self.GAME_OPTION_FAST_SLOW_OFFSET] = acehex( + profile.get_int("early_late") + ).encode("ascii") + option[self.GAME_OPTION_COMBO_POSITION_OFFSET] = acehex( + profile.get_int("combo") + ).encode("ascii") + option[self.GAME_OPTION_ARROW_SKIN_OFFSET] = acehex( + profile.get_int("arrowskin") + ).encode("ascii") + option[self.GAME_OPTION_GUIDELINE_OFFSET] = acehex( + profile.get_int("guidelines") + ).encode("ascii") + option[self.GAME_OPTION_FILTER_OFFSET] = acehex( + profile.get_int("filter") + ).encode("ascii") + usergamedata[ptype]["strdata"] = b",".join(option) if ptype == "LAST": # Return the number of calories expended in the last day workouts = self.data.local.user.get_time_based_achievements( self.game, self.version, userid, - achievementtype='workout', + achievementtype="workout", since=Time.now() - Time.SECONDS_IN_DAY, ) - total = sum([w.data.get_int('calories') for w in workouts]) + total = sum([w.data.get_int("calories") for w in workouts]) - last = usergamedata[ptype]['strdata'].split(b',') - last[self.GAME_LAST_CALORIES_OFFSET] = acehex(total).encode('ascii') - usergamedata[ptype]['strdata'] = b','.join(last) + last = usergamedata[ptype]["strdata"].split(b",") + last[self.GAME_LAST_CALORIES_OFFSET] = acehex(total).encode( + "ascii" + ) + usergamedata[ptype]["strdata"] = b",".join(last) if ptype == "RIVAL": # Fill in the DDR code and active status of the three active # rivals. - rival = usergamedata[ptype]['strdata'].split(b',') - lastdict = profile.get_dict('last') + rival = usergamedata[ptype]["strdata"].split(b",") + lastdict = profile.get_dict("last") friends: Dict[int, Optional[Profile]] = {} for link in links: - if link.type[:7] != 'friend_': + if link.type[:7] != "friend_": continue pos = int(link.type[7:]) @@ -815,16 +985,16 @@ class DDRAce( 3: self.GAME_RIVAL_SLOT_3_ACTIVE_OFFSET, }[rivalno] - whichfriend = lastdict.get_int(f'rival{rivalno}') - 1 + whichfriend = lastdict.get_int(f"rival{rivalno}") - 1 if whichfriend < 0: # This rival isn't active - rival[activeslot] = b'0' + rival[activeslot] = b"0" continue friendprofile = friends.get(whichfriend) if friendprofile is None: # This rival doesn't exist - rival[activeslot] = b'0' + rival[activeslot] = b"0" continue ddrcodeslot = { @@ -833,28 +1003,42 @@ class DDRAce( 3: self.GAME_RIVAL_SLOT_3_DDRCODE_OFFSET, }[rivalno] - rival[activeslot] = acehex(rivalno).encode('ascii') - rival[ddrcodeslot] = acehex(friendprofile.extid).encode('ascii') + rival[activeslot] = acehex(rivalno).encode("ascii") + rival[ddrcodeslot] = acehex(friendprofile.extid).encode( + "ascii" + ) - usergamedata[ptype]['strdata'] = b','.join(rival) + usergamedata[ptype]["strdata"] = b",".join(rival) - dnode = Node.string('d', base64.b64encode(usergamedata[ptype]['strdata']).decode('ascii')) - dnode.add_child(Node.string('bin1', base64.b64encode(usergamedata[ptype]['bindata']).decode('ascii'))) + dnode = Node.string( + "d", + base64.b64encode(usergamedata[ptype]["strdata"]).decode( + "ascii" + ), + ) + dnode.add_child( + Node.string( + "bin1", + base64.b64encode(usergamedata[ptype]["bindata"]).decode( + "ascii" + ), + ) + ) record.add_child(dnode) - player.add_child(Node.u32('record_num', records)) + player.add_child(Node.u32("record_num", records)) - playerdata.add_child(Node.s32('result', 0)) + playerdata.add_child(Node.s32("result", 0)) return playerdata def handle_system_convcardnumber_request(self, request: Node) -> Node: - cardid = request.child_value('data/card_id') + cardid = request.child_value("data/card_id") cardnumber = CardCipher.encode(cardid) - system = Node.void('system') - data = Node.void('data') + system = Node.void("system") + data = Node.void("data") system.add_child(data) - system.add_child(Node.s32('result', 0)) - data.add_child(Node.string('card_number', cardnumber)) + system.add_child(Node.s32("result", 0)) + data.add_child(Node.string("card_number", cardnumber)) return system diff --git a/bemani/backend/ddr/ddrx2.py b/bemani/backend/ddr/ddrx2.py index 14ce96a..35cb384 100644 --- a/bemani/backend/ddr/ddrx2.py +++ b/bemani/backend/ddr/ddrx2.py @@ -43,7 +43,7 @@ class DDRX2( DDRBase, ): - name: str = 'DanceDanceRevolution X2' + name: str = "DanceDanceRevolution X2" version: int = VersionConstants.DDR_X2 GAME_STYLE_SINGLE: Final[int] = 0 @@ -147,22 +147,24 @@ class DDRX2( return combo_type def handle_game_common_request(self, request: Node) -> Node: - game = Node.void('game') + game = Node.void("game") for flagid in range(256): - flag = Node.void('flag') + flag = Node.void("flag") game.add_child(flag) - flag.set_attribute('id', str(flagid)) - flag.set_attribute('s2', '0') - flag.set_attribute('s1', '0') - flag.set_attribute('t', '0') + flag.set_attribute("id", str(flagid)) + flag.set_attribute("s2", "0") + flag.set_attribute("s1", "0") + flag.set_attribute("t", "0") - hit_chart = self.data.local.music.get_hit_chart(self.game, self.music_version, self.GAME_MAX_SONGS) + hit_chart = self.data.local.music.get_hit_chart( + self.game, self.music_version, self.GAME_MAX_SONGS + ) counts_by_reflink = [0] * self.GAME_MAX_SONGS for (reflink, plays) in hit_chart: if reflink >= 0 and reflink < self.GAME_MAX_SONGS: counts_by_reflink[reflink] = plays - game.add_child(Node.u32_array('cnt_music', counts_by_reflink)) + game.add_child(Node.u32_array("cnt_music", counts_by_reflink)) return game @@ -179,13 +181,16 @@ class DDRX2( sortedrecords[score.id] = {} sortedrecords[score.id][score.chart] = (userid, score) missing_profiles.append(userid) - users = {userid: profile for (userid, profile) in self.get_any_profiles(missing_profiles)} + users = { + userid: profile + for (userid, profile) in self.get_any_profiles(missing_profiles) + } - game = Node.void('game') + game = Node.void("game") for song in sortedrecords: - music = Node.void('music') + music = Node.void("music") game.add_child(music) - music.set_attribute('reclink_num', str(song)) + music.set_attribute("reclink_num", str(song)) for chart in sortedrecords[song]: userid, score = sortedrecords[song][chart] @@ -194,24 +199,28 @@ class DDRX2( except KeyError: # Don't support this chart in this game continue - gamerank = self.db_to_game_rank(score.data.get_int('rank')) - combo_type = self.db_to_game_halo(score.data.get_int('halo')) + gamerank = self.db_to_game_rank(score.data.get_int("rank")) + combo_type = self.db_to_game_halo(score.data.get_int("halo")) - typenode = Node.void('type') + typenode = Node.void("type") music.add_child(typenode) - typenode.set_attribute('diff', str(gamechart)) + typenode.set_attribute("diff", str(gamechart)) - typenode.add_child(Node.string('name', users[userid].get_str('name'))) - typenode.add_child(Node.u32('score', score.points)) - typenode.add_child(Node.u16('area', users[userid].get_int('area', self.get_machine_region()))) - typenode.add_child(Node.u8('rank', gamerank)) - typenode.add_child(Node.u8('combo_type', combo_type)) + typenode.add_child(Node.string("name", users[userid].get_str("name"))) + typenode.add_child(Node.u32("score", score.points)) + typenode.add_child( + Node.u16( + "area", users[userid].get_int("area", self.get_machine_region()) + ) + ) + typenode.add_child(Node.u8("rank", gamerank)) + typenode.add_child(Node.u8("combo_type", combo_type)) return game def handle_game_load_m_request(self, request: Node) -> Node: - extid = intish(request.attribute('code')) - refid = request.attribute('refid') + extid = intish(request.attribute("code")) + refid = request.attribute("refid") if extid is not None: # Rival score loading @@ -221,7 +230,9 @@ class DDRX2( userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: - scores = self.data.remote.music.get_scores(self.game, self.music_version, userid) + scores = self.data.remote.music.get_scores( + self.game, self.music_version, userid + ) else: scores = [] @@ -231,11 +242,11 @@ class DDRX2( sortedscores[score.id] = {} sortedscores[score.id][score.chart] = score - game = Node.void('game') + game = Node.void("game") for song in sortedscores: - music = Node.void('music') + music = Node.void("music") game.add_child(music) - music.set_attribute('reclink', str(song)) + music.set_attribute("reclink", str(song)) for chart in sortedscores[song]: score = sortedscores[song][chart] @@ -244,39 +255,39 @@ class DDRX2( except KeyError: # Don't support this chart in this game continue - gamerank = self.db_to_game_rank(score.data.get_int('rank')) - combo_type = self.db_to_game_halo(score.data.get_int('halo')) + gamerank = self.db_to_game_rank(score.data.get_int("rank")) + combo_type = self.db_to_game_halo(score.data.get_int("halo")) - typenode = Node.void('type') + typenode = Node.void("type") music.add_child(typenode) - typenode.set_attribute('diff', str(gamechart)) + typenode.set_attribute("diff", str(gamechart)) - typenode.add_child(Node.u32('score', score.points)) - typenode.add_child(Node.u16('count', score.plays)) - typenode.add_child(Node.u8('rank', gamerank)) - typenode.add_child(Node.u8('combo_type', combo_type)) + typenode.add_child(Node.u32("score", score.points)) + typenode.add_child(Node.u16("count", score.plays)) + typenode.add_child(Node.u8("rank", gamerank)) + typenode.add_child(Node.u8("combo_type", combo_type)) return game def handle_game_save_m_request(self, request: Node) -> Node: - refid = request.attribute('refid') - songid = int(request.attribute('mid')) - chart = self.game_to_db_chart(int(request.attribute('mtype'))) + refid = request.attribute("refid") + songid = int(request.attribute("mid")) + chart = self.game_to_db_chart(int(request.attribute("mtype"))) # Calculate statistics - data = request.child('data') - points = int(data.attribute('score')) - combo = int(data.attribute('combo')) - rank = self.game_to_db_rank(int(data.attribute('rank'))) - if int(data.attribute('full')) == 0: + data = request.child("data") + points = int(data.attribute("score")) + combo = int(data.attribute("combo")) + rank = self.game_to_db_rank(int(data.attribute("rank"))) + if int(data.attribute("full")) == 0: halo = self.HALO_NONE - elif int(data.attribute('perf')) == 0: + elif int(data.attribute("perf")) == 0: halo = self.HALO_GREAT_FULL_COMBO elif points < 1000000: halo = self.HALO_PERFECT_FULL_COMBO else: halo = self.HALO_MARVELOUS_FULL_COMBO - trace = request.child_value('trace') + trace = request.child_value("trace") # Save the score, regardless of whether we have a refid. If we save # an anonymous score, it only goes into the DB to count against the @@ -294,336 +305,372 @@ class DDRX2( ) # No response needed - game = Node.void('game') + game = Node.void("game") return game def format_profile(self, userid: UserID, profile: Profile) -> Node: - root = Node.void('game') + root = Node.void("game") # Look up play stats we bridge to every mix play_stats = self.get_play_statistics(userid) # Basic game settings - root.add_child(Node.string('seq', '')) - root.add_child(Node.u32('code', profile.extid)) - root.add_child(Node.string('name', profile.get_str('name'))) - root.add_child(Node.u8('area', profile.get_int('area', self.get_machine_region()))) - root.add_child(Node.u32('cnt_s', play_stats.get_int('single_plays'))) - root.add_child(Node.u32('cnt_d', play_stats.get_int('double_plays'))) - root.add_child(Node.u32('cnt_b', play_stats.get_int('battle_plays'))) # This could be wrong, its a guess - root.add_child(Node.u32('cnt_m0', play_stats.get_int('cnt_m0'))) - root.add_child(Node.u32('cnt_m1', play_stats.get_int('cnt_m1'))) - root.add_child(Node.u32('cnt_m2', play_stats.get_int('cnt_m2'))) - root.add_child(Node.u32('cnt_m3', play_stats.get_int('cnt_m3'))) - root.add_child(Node.u32('exp', play_stats.get_int('exp'))) - root.add_child(Node.u32('exp_o', profile.get_int('exp_o'))) - root.add_child(Node.u32('star', profile.get_int('star'))) - root.add_child(Node.u32('star_c', profile.get_int('star_c'))) - root.add_child(Node.u8('combo', profile.get_int('combo', 0))) - root.add_child(Node.u8('timing_diff', profile.get_int('early_late', 0))) + root.add_child(Node.string("seq", "")) + root.add_child(Node.u32("code", profile.extid)) + root.add_child(Node.string("name", profile.get_str("name"))) + root.add_child( + Node.u8("area", profile.get_int("area", self.get_machine_region())) + ) + root.add_child(Node.u32("cnt_s", play_stats.get_int("single_plays"))) + root.add_child(Node.u32("cnt_d", play_stats.get_int("double_plays"))) + root.add_child( + Node.u32("cnt_b", play_stats.get_int("battle_plays")) + ) # This could be wrong, its a guess + root.add_child(Node.u32("cnt_m0", play_stats.get_int("cnt_m0"))) + root.add_child(Node.u32("cnt_m1", play_stats.get_int("cnt_m1"))) + root.add_child(Node.u32("cnt_m2", play_stats.get_int("cnt_m2"))) + root.add_child(Node.u32("cnt_m3", play_stats.get_int("cnt_m3"))) + root.add_child(Node.u32("exp", play_stats.get_int("exp"))) + root.add_child(Node.u32("exp_o", profile.get_int("exp_o"))) + root.add_child(Node.u32("star", profile.get_int("star"))) + root.add_child(Node.u32("star_c", profile.get_int("star_c"))) + root.add_child(Node.u8("combo", profile.get_int("combo", 0))) + root.add_child(Node.u8("timing_diff", profile.get_int("early_late", 0))) # Character stuff - chara = Node.void('chara') + chara = Node.void("chara") root.add_child(chara) - if 'chara' in profile: - chara.set_attribute('my', str(profile.get_int('chara'))) + if "chara" in profile: + chara.set_attribute("my", str(profile.get_int("chara"))) - root.add_child(Node.u8_array('chara_opt', profile.get_int_array('chara_opt', 96))) + root.add_child( + Node.u8_array("chara_opt", profile.get_int_array("chara_opt", 96)) + ) # Drill rankings - if 'title' in profile: - title = Node.void('title') + if "title" in profile: + title = Node.void("title") root.add_child(title) - titledict = profile.get_dict('title') - if 't' in titledict: - title.set_attribute('t', str(titledict.get_int('t'))) - if 's' in titledict: - title.set_attribute('s', str(titledict.get_int('s'))) - if 'd' in titledict: - title.set_attribute('d', str(titledict.get_int('d'))) + titledict = profile.get_dict("title") + if "t" in titledict: + title.set_attribute("t", str(titledict.get_int("t"))) + if "s" in titledict: + title.set_attribute("s", str(titledict.get_int("s"))) + if "d" in titledict: + title.set_attribute("d", str(titledict.get_int("d"))) - if 'title_gr' in profile: - title_gr = Node.void('title_gr') + if "title_gr" in profile: + title_gr = Node.void("title_gr") root.add_child(title_gr) - title_grdict = profile.get_dict('title_gr') - if 't' in title_grdict: - title_gr.set_attribute('t', str(title_grdict.get_int('t'))) - if 's' in title_grdict: - title_gr.set_attribute('s', str(title_grdict.get_int('s'))) - if 'd' in title_grdict: - title_gr.set_attribute('d', str(title_grdict.get_int('d'))) + title_grdict = profile.get_dict("title_gr") + if "t" in title_grdict: + title_gr.set_attribute("t", str(title_grdict.get_int("t"))) + if "s" in title_grdict: + title_gr.set_attribute("s", str(title_grdict.get_int("s"))) + if "d" in title_grdict: + title_gr.set_attribute("d", str(title_grdict.get_int("d"))) # Event progrses - if 'event' in profile: - event = Node.void('event') + if "event" in profile: + event = Node.void("event") root.add_child(event) - event_dict = profile.get_dict('event') - if 'diff_sum' in event_dict: - event.set_attribute('diff_sum', str(event_dict.get_int('diff_sum'))) - if 'welcome' in event_dict: - event.set_attribute('welcome', str(event_dict.get_int('welcome'))) - if 'e_flags' in event_dict: - event.set_attribute('e_flags', str(event_dict.get_int('e_flags'))) + event_dict = profile.get_dict("event") + if "diff_sum" in event_dict: + event.set_attribute("diff_sum", str(event_dict.get_int("diff_sum"))) + if "welcome" in event_dict: + event.set_attribute("welcome", str(event_dict.get_int("welcome"))) + if "e_flags" in event_dict: + event.set_attribute("e_flags", str(event_dict.get_int("e_flags"))) - if 'e_panel' in profile: - e_panel = Node.void('e_panel') + if "e_panel" in profile: + e_panel = Node.void("e_panel") root.add_child(e_panel) - e_panel_dict = profile.get_dict('e_panel') - if 'play_id' in e_panel_dict: - e_panel.set_attribute('play_id', str(e_panel_dict.get_int('play_id'))) - e_panel.add_child(Node.u8_array('cell', e_panel_dict.get_int_array('cell', 24))) - e_panel.add_child(Node.u8_array('panel_state', e_panel_dict.get_int_array('panel_state', 6))) + e_panel_dict = profile.get_dict("e_panel") + if "play_id" in e_panel_dict: + e_panel.set_attribute("play_id", str(e_panel_dict.get_int("play_id"))) + e_panel.add_child( + Node.u8_array("cell", e_panel_dict.get_int_array("cell", 24)) + ) + e_panel.add_child( + Node.u8_array( + "panel_state", e_panel_dict.get_int_array("panel_state", 6) + ) + ) - if 'e_pix' in profile: - e_pix = Node.void('e_pix') + if "e_pix" in profile: + e_pix = Node.void("e_pix") root.add_child(e_pix) - e_pix_dict = profile.get_dict('e_pix') - if 'max_distance' in e_pix_dict: - e_pix.set_attribute('max_distance', str(e_pix_dict.get_int('max_distance'))) - if 'max_planet' in e_pix_dict: - e_pix.set_attribute('max_planet', str(e_pix_dict.get_int('max_planet'))) - if 'total_distance' in e_pix_dict: - e_pix.set_attribute('total_distance', str(e_pix_dict.get_int('total_distance'))) - if 'total_planet' in e_pix_dict: - e_pix.set_attribute('total_planet', str(e_pix_dict.get_int('total_planet'))) - if 'border_character' in e_pix_dict: - e_pix.set_attribute('border_character', str(e_pix_dict.get_int('border_character'))) - if 'border_balloon' in e_pix_dict: - e_pix.set_attribute('border_balloon', str(e_pix_dict.get_int('border_balloon'))) - if 'border_music_aftr' in e_pix_dict: - e_pix.set_attribute('border_music_aftr', str(e_pix_dict.get_int('border_music_aftr'))) - if 'border_music_meii' in e_pix_dict: - e_pix.set_attribute('border_music_meii', str(e_pix_dict.get_int('border_music_meii'))) - if 'border_music_dirt' in e_pix_dict: - e_pix.set_attribute('border_music_dirt', str(e_pix_dict.get_int('border_music_dirt'))) - if 'flags' in e_pix_dict: - e_pix.set_attribute('flags', str(e_pix_dict.get_int('flags'))) + e_pix_dict = profile.get_dict("e_pix") + if "max_distance" in e_pix_dict: + e_pix.set_attribute( + "max_distance", str(e_pix_dict.get_int("max_distance")) + ) + if "max_planet" in e_pix_dict: + e_pix.set_attribute("max_planet", str(e_pix_dict.get_int("max_planet"))) + if "total_distance" in e_pix_dict: + e_pix.set_attribute( + "total_distance", str(e_pix_dict.get_int("total_distance")) + ) + if "total_planet" in e_pix_dict: + e_pix.set_attribute( + "total_planet", str(e_pix_dict.get_int("total_planet")) + ) + if "border_character" in e_pix_dict: + e_pix.set_attribute( + "border_character", str(e_pix_dict.get_int("border_character")) + ) + if "border_balloon" in e_pix_dict: + e_pix.set_attribute( + "border_balloon", str(e_pix_dict.get_int("border_balloon")) + ) + if "border_music_aftr" in e_pix_dict: + e_pix.set_attribute( + "border_music_aftr", str(e_pix_dict.get_int("border_music_aftr")) + ) + if "border_music_meii" in e_pix_dict: + e_pix.set_attribute( + "border_music_meii", str(e_pix_dict.get_int("border_music_meii")) + ) + if "border_music_dirt" in e_pix_dict: + e_pix.set_attribute( + "border_music_dirt", str(e_pix_dict.get_int("border_music_dirt")) + ) + if "flags" in e_pix_dict: + e_pix.set_attribute("flags", str(e_pix_dict.get_int("flags"))) # Calorie mode - if 'weight' in profile: + if "weight" in profile: workouts = self.data.local.user.get_time_based_achievements( self.game, self.version, userid, - achievementtype='workout', + achievementtype="workout", since=Time.now() - Time.SECONDS_IN_DAY, ) - total = sum([w.data.get_int('calories') for w in workouts]) - workout = Node.void('workout') + total = sum([w.data.get_int("calories") for w in workouts]) + workout = Node.void("workout") root.add_child(workout) - workout.set_attribute('weight', str(profile.get_int('weight'))) - workout.set_attribute('day', str(total)) - workout.set_attribute('disp', '1') + workout.set_attribute("weight", str(profile.get_int("weight"))) + workout.set_attribute("day", str(total)) + workout.set_attribute("disp", "1") # Last cursor settings - last = Node.void('last') + last = Node.void("last") root.add_child(last) - lastdict = profile.get_dict('last') - last.set_attribute('fri', str(lastdict.get_int('fri'))) - last.set_attribute('style', str(lastdict.get_int('style'))) - last.set_attribute('mode', str(lastdict.get_int('mode'))) - last.set_attribute('cate', str(lastdict.get_int('cate'))) - last.set_attribute('sort', str(lastdict.get_int('sort'))) - last.set_attribute('mid', str(lastdict.get_int('mid'))) - last.set_attribute('mtype', str(lastdict.get_int('mtype'))) - last.set_attribute('cid', str(lastdict.get_int('cid'))) - last.set_attribute('ctype', str(lastdict.get_int('ctype'))) - last.set_attribute('sid', str(lastdict.get_int('sid'))) + lastdict = profile.get_dict("last") + last.set_attribute("fri", str(lastdict.get_int("fri"))) + last.set_attribute("style", str(lastdict.get_int("style"))) + last.set_attribute("mode", str(lastdict.get_int("mode"))) + last.set_attribute("cate", str(lastdict.get_int("cate"))) + last.set_attribute("sort", str(lastdict.get_int("sort"))) + last.set_attribute("mid", str(lastdict.get_int("mid"))) + last.set_attribute("mtype", str(lastdict.get_int("mtype"))) + last.set_attribute("cid", str(lastdict.get_int("cid"))) + last.set_attribute("ctype", str(lastdict.get_int("ctype"))) + last.set_attribute("sid", str(lastdict.get_int("sid"))) # Groove gauge level-ups - gr_s = Node.void('gr_s') + gr_s = Node.void("gr_s") root.add_child(gr_s) index = 1 - for entry in profile.get_int_array('gr_s', 5): - gr_s.set_attribute(f'gr{index}', str(entry)) + for entry in profile.get_int_array("gr_s", 5): + gr_s.set_attribute(f"gr{index}", str(entry)) index = index + 1 - gr_d = Node.void('gr_d') + gr_d = Node.void("gr_d") root.add_child(gr_d) index = 1 - for entry in profile.get_int_array('gr_d', 5): - gr_d.set_attribute(f'gr{index}', str(entry)) + for entry in profile.get_int_array("gr_d", 5): + gr_d.set_attribute(f"gr{index}", str(entry)) index = index + 1 # Options in menus - root.add_child(Node.s16_array('opt', profile.get_int_array('opt', 16))) - root.add_child(Node.s16_array('opt_ex', profile.get_int_array('opt_ex', 16))) + root.add_child(Node.s16_array("opt", profile.get_int_array("opt", 16))) + root.add_child(Node.s16_array("opt_ex", profile.get_int_array("opt_ex", 16))) # Unlock flags - root.add_child(Node.u8_array('flag', profile.get_int_array('flag', 256, [1] * 256))) + root.add_child( + Node.u8_array("flag", profile.get_int_array("flag", 256, [1] * 256)) + ) # Ranking display? - root.add_child(Node.u16_array('rank', profile.get_int_array('rank', 100))) + root.add_child(Node.u16_array("rank", profile.get_int_array("rank", 100))) # Rivals links = self.data.local.user.get_links(self.game, self.version, userid) for link in links: - if link.type[:7] != 'friend_': + if link.type[:7] != "friend_": continue pos = int(link.type[7:]) friend = self.get_profile(link.other_userid) play_stats = self.get_play_statistics(link.other_userid) if friend is not None: - friendnode = Node.void('friend') + friendnode = Node.void("friend") root.add_child(friendnode) - friendnode.set_attribute('pos', str(pos)) - friendnode.set_attribute('vs', '0') - friendnode.set_attribute('up', '0') - friendnode.add_child(Node.u32('code', friend.extid)) - friendnode.add_child(Node.string('name', friend.get_str('name'))) - friendnode.add_child(Node.u8('area', friend.get_int('area', self.get_machine_region()))) - friendnode.add_child(Node.u32('exp', play_stats.get_int('exp'))) - friendnode.add_child(Node.u32('star', friend.get_int('star'))) + friendnode.set_attribute("pos", str(pos)) + friendnode.set_attribute("vs", "0") + friendnode.set_attribute("up", "0") + friendnode.add_child(Node.u32("code", friend.extid)) + friendnode.add_child(Node.string("name", friend.get_str("name"))) + friendnode.add_child( + Node.u8("area", friend.get_int("area", self.get_machine_region())) + ) + friendnode.add_child(Node.u32("exp", play_stats.get_int("exp"))) + friendnode.add_child(Node.u32("star", friend.get_int("star"))) # Drill rankings - if 'title' in friend: - title = Node.void('title') + if "title" in friend: + title = Node.void("title") friendnode.add_child(title) - titledict = friend.get_dict('title') - if 't' in titledict: - title.set_attribute('t', str(titledict.get_int('t'))) - if 's' in titledict: - title.set_attribute('s', str(titledict.get_int('s'))) - if 'd' in titledict: - title.set_attribute('d', str(titledict.get_int('d'))) + titledict = friend.get_dict("title") + if "t" in titledict: + title.set_attribute("t", str(titledict.get_int("t"))) + if "s" in titledict: + title.set_attribute("s", str(titledict.get_int("s"))) + if "d" in titledict: + title.set_attribute("d", str(titledict.get_int("d"))) - if 'title_gr' in friend: - title_gr = Node.void('title_gr') + if "title_gr" in friend: + title_gr = Node.void("title_gr") friendnode.add_child(title_gr) - title_grdict = friend.get_dict('title_gr') - if 't' in title_grdict: - title_gr.set_attribute('t', str(title_grdict.get_int('t'))) - if 's' in title_grdict: - title_gr.set_attribute('s', str(title_grdict.get_int('s'))) - if 'd' in title_grdict: - title_gr.set_attribute('d', str(title_grdict.get_int('d'))) + title_grdict = friend.get_dict("title_gr") + if "t" in title_grdict: + title_gr.set_attribute("t", str(title_grdict.get_int("t"))) + if "s" in title_grdict: + title_gr.set_attribute("s", str(title_grdict.get_int("s"))) + if "d" in title_grdict: + title_gr.set_attribute("d", str(title_grdict.get_int("d"))) # Groove gauge level-ups - gr_s = Node.void('gr_s') + gr_s = Node.void("gr_s") friendnode.add_child(gr_s) index = 1 - for entry in friend.get_int_array('gr_s', 5): - gr_s.set_attribute(f'gr{index}', str(entry)) + for entry in friend.get_int_array("gr_s", 5): + gr_s.set_attribute(f"gr{index}", str(entry)) index = index + 1 - gr_d = Node.void('gr_d') + gr_d = Node.void("gr_d") friendnode.add_child(gr_d) index = 1 - for entry in friend.get_int_array('gr_d', 5): - gr_d.set_attribute(f'gr{index}', str(entry)) + for entry in friend.get_int_array("gr_d", 5): + gr_d.set_attribute(f"gr{index}", str(entry)) index = index + 1 return root - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() play_stats = self.get_play_statistics(userid) # Grab last node and accessories so we can make decisions based on type - last = request.child('last') - lastdict = newprofile.get_dict('last') - mode = int(last.attribute('mode')) - style = int(last.attribute('style')) + last = request.child("last") + lastdict = newprofile.get_dict("last") + mode = int(last.attribute("mode")) + style = int(last.attribute("style")) is_dp = style == self.GAME_STYLE_DOUBLE # Drill rankings - title = request.child('title') - title_gr = request.child('title_gr') - titledict = newprofile.get_dict('title') - title_grdict = newprofile.get_dict('title_gr') + title = request.child("title") + title_gr = request.child("title_gr") + titledict = newprofile.get_dict("title") + title_grdict = newprofile.get_dict("title_gr") # Groove radar level ups - gr = request.child('gr') + gr = request.child("gr") # Set the correct values depending on if we're single or double play if is_dp: - play_stats.increment_int('double_plays') + play_stats.increment_int("double_plays") if gr is not None: newprofile.replace_int_array( - 'gr_d', + "gr_d", 5, [ - intish(gr.attribute('gr1')), - intish(gr.attribute('gr2')), - intish(gr.attribute('gr3')), - intish(gr.attribute('gr4')), - intish(gr.attribute('gr5')), + intish(gr.attribute("gr1")), + intish(gr.attribute("gr2")), + intish(gr.attribute("gr3")), + intish(gr.attribute("gr4")), + intish(gr.attribute("gr5")), ], ) if title is not None: - titledict.replace_int('d', title.value) - newprofile.replace_dict('title', titledict) + titledict.replace_int("d", title.value) + newprofile.replace_dict("title", titledict) if title_gr is not None: - title_grdict.replace_int('d', title.value) - newprofile.replace_dict('title_gr', title_grdict) + title_grdict.replace_int("d", title.value) + newprofile.replace_dict("title_gr", title_grdict) else: - play_stats.increment_int('single_plays') + play_stats.increment_int("single_plays") if gr is not None: newprofile.replace_int_array( - 'gr_s', + "gr_s", 5, [ - intish(gr.attribute('gr1')), - intish(gr.attribute('gr2')), - intish(gr.attribute('gr3')), - intish(gr.attribute('gr4')), - intish(gr.attribute('gr5')), + intish(gr.attribute("gr1")), + intish(gr.attribute("gr2")), + intish(gr.attribute("gr3")), + intish(gr.attribute("gr4")), + intish(gr.attribute("gr5")), ], ) if title is not None: - titledict.replace_int('s', title.value) - newprofile.replace_dict('title', titledict) + titledict.replace_int("s", title.value) + newprofile.replace_dict("title", titledict) if title_gr is not None: - title_grdict.replace_int('s', title.value) - newprofile.replace_dict('title_gr', title_grdict) - play_stats.increment_int(f'cnt_m{mode}') + title_grdict.replace_int("s", title.value) + newprofile.replace_dict("title_gr", title_grdict) + play_stats.increment_int(f"cnt_m{mode}") # Update last attributes - lastdict.replace_int('fri', intish(last.attribute('fri'))) - lastdict.replace_int('style', intish(last.attribute('style'))) - lastdict.replace_int('mode', intish(last.attribute('mode'))) - lastdict.replace_int('cate', intish(last.attribute('cate'))) - lastdict.replace_int('sort', intish(last.attribute('sort'))) - lastdict.replace_int('mid', intish(last.attribute('mid'))) - lastdict.replace_int('mtype', intish(last.attribute('mtype'))) - lastdict.replace_int('cid', intish(last.attribute('cid'))) - lastdict.replace_int('ctype', intish(last.attribute('ctype'))) - lastdict.replace_int('sid', intish(last.attribute('sid'))) - newprofile.replace_dict('last', lastdict) + lastdict.replace_int("fri", intish(last.attribute("fri"))) + lastdict.replace_int("style", intish(last.attribute("style"))) + lastdict.replace_int("mode", intish(last.attribute("mode"))) + lastdict.replace_int("cate", intish(last.attribute("cate"))) + lastdict.replace_int("sort", intish(last.attribute("sort"))) + lastdict.replace_int("mid", intish(last.attribute("mid"))) + lastdict.replace_int("mtype", intish(last.attribute("mtype"))) + lastdict.replace_int("cid", intish(last.attribute("cid"))) + lastdict.replace_int("ctype", intish(last.attribute("ctype"))) + lastdict.replace_int("sid", intish(last.attribute("sid"))) + newprofile.replace_dict("last", lastdict) # Grab character options - chara = request.child('chara') + chara = request.child("chara") if chara is not None: - newprofile.replace_int('chara', intish(chara.attribute('my'))) - newprofile.replace_int_array('chara_opt', 96, request.child_value('chara_opt')) + newprofile.replace_int("chara", intish(chara.attribute("my"))) + newprofile.replace_int_array("chara_opt", 96, request.child_value("chara_opt")) # Track event progress - event = request.child('event') + event = request.child("event") if event is not None: - event_dict = newprofile.get_dict('event') - event_dict.replace_int('diff_sum', intish(event.attribute('diff_sum'))) - event_dict.replace_int('e_flags', intish(event.attribute('e_flags'))) - event_dict.replace_int('welcome', intish(event.attribute('welcome'))) - newprofile.replace_dict('event', event_dict) + event_dict = newprofile.get_dict("event") + event_dict.replace_int("diff_sum", intish(event.attribute("diff_sum"))) + event_dict.replace_int("e_flags", intish(event.attribute("e_flags"))) + event_dict.replace_int("welcome", intish(event.attribute("welcome"))) + newprofile.replace_dict("event", event_dict) - e_panel = request.child('e_panel') + e_panel = request.child("e_panel") if e_panel is not None: - e_panel_dict = newprofile.get_dict('e_panel') - e_panel_dict.replace_int('play_id', intish(e_panel.attribute('play_id'))) - e_panel_dict.replace_int_array('cell', 24, e_panel.child_value('cell')) - e_panel_dict.replace_int_array('panel_state', 6, e_panel.child_value('panel_state')) - newprofile.replace_dict('e_panel', e_panel_dict) + e_panel_dict = newprofile.get_dict("e_panel") + e_panel_dict.replace_int("play_id", intish(e_panel.attribute("play_id"))) + e_panel_dict.replace_int_array("cell", 24, e_panel.child_value("cell")) + e_panel_dict.replace_int_array( + "panel_state", 6, e_panel.child_value("panel_state") + ) + newprofile.replace_dict("e_panel", e_panel_dict) - e_pix = request.child('e_pix') + e_pix = request.child("e_pix") if e_pix is not None: - e_pix_dict = newprofile.get_dict('e_pix') - max_distance = e_pix_dict.get_int('max_distance') - max_planet = e_pix_dict.get_int('max_planet') - total_distance = e_pix_dict.get_int('total_distance') - total_planet = e_pix_dict.get_int('total_planet') - cur_distance = intish(e_pix.attribute('this_distance')) - cur_planet = intish(e_pix.attribute('this_planet')) + e_pix_dict = newprofile.get_dict("e_pix") + max_distance = e_pix_dict.get_int("max_distance") + max_planet = e_pix_dict.get_int("max_planet") + total_distance = e_pix_dict.get_int("total_distance") + total_planet = e_pix_dict.get_int("total_planet") + cur_distance = intish(e_pix.attribute("this_distance")) + cur_planet = intish(e_pix.attribute("this_planet")) if cur_distance is not None: max_distance = max(max_distance, cur_distance) total_distance += cur_distance @@ -631,51 +678,51 @@ class DDRX2( max_planet = max(max_planet, cur_planet) total_planet += cur_planet - e_pix_dict.replace_int('max_distance', max_distance) - e_pix_dict.replace_int('max_planet', max_planet) - e_pix_dict.replace_int('total_distance', total_distance) - e_pix_dict.replace_int('total_planet', total_planet) - e_pix_dict.replace_int('flags', intish(e_pix.attribute('flags'))) - newprofile.replace_dict('e_pix', e_pix_dict) + e_pix_dict.replace_int("max_distance", max_distance) + e_pix_dict.replace_int("max_planet", max_planet) + e_pix_dict.replace_int("total_distance", total_distance) + e_pix_dict.replace_int("total_planet", total_planet) + e_pix_dict.replace_int("flags", intish(e_pix.attribute("flags"))) + newprofile.replace_dict("e_pix", e_pix_dict) # Options - opt = request.child('opt') + opt = request.child("opt") if opt is not None: # A bug in old versions of AVS returns the wrong number for set - newprofile.replace_int_array('opt', 16, opt.value[:16]) + newprofile.replace_int_array("opt", 16, opt.value[:16]) # Experience and stars - exp = request.child_value('exp') + exp = request.child_value("exp") if exp is not None: - play_stats.replace_int('exp', play_stats.get_int('exp') + exp) - star = request.child_value('star') + play_stats.replace_int("exp", play_stats.get_int("exp") + exp) + star = request.child_value("star") if star is not None: - newprofile.replace_int('star', newprofile.get_int('star') + star) - star_c = request.child_value('star_c') + newprofile.replace_int("star", newprofile.get_int("star") + star) + star_c = request.child_value("star_c") if star_c is not None: - newprofile.replace_int('star_c', newprofile.get_int('star_c') + exp) + newprofile.replace_int("star_c", newprofile.get_int("star_c") + exp) # Update game flags for child in request.children: - if child.name != 'flag': + if child.name != "flag": continue try: - value = int(child.attribute('data')) - offset = int(child.attribute('no')) + value = int(child.attribute("data")) + offset = int(child.attribute("no")) except ValueError: continue - flags = newprofile.get_int_array('flag', 256, [1] * 256) + flags = newprofile.get_int_array("flag", 256, [1] * 256) if offset < 0 or offset >= len(flags): continue flags[offset] = value - newprofile.replace_int_array('flag', 256, flags) + newprofile.replace_int_array("flag", 256, flags) # Workout mode support newweight = -1 - oldweight = newprofile.get_int('weight') + oldweight = newprofile.get_int("weight") for child in request.children: - if child.name != 'weight': + if child.name != "weight": continue newweight = child.value if newweight < 0: @@ -684,14 +731,14 @@ class DDRX2( # Either update or unset the weight depending on the game if newweight == 0: # Weight is unset or we declined to use this feature, remove from profile - if 'weight' in newprofile: - del newprofile['weight'] + if "weight" in newprofile: + del newprofile["weight"] else: # Weight has been set or previously retrieved, we should save calories - newprofile.replace_int('weight', newweight) + newprofile.replace_int("weight", newweight) total = 0 for child in request.children: - if child.name != 'calory': + if child.name != "calory": continue total += child.value self.data.local.user.put_time_based_achievement( @@ -699,10 +746,10 @@ class DDRX2( self.version, userid, 0, - 'workout', + "workout", { - 'calories': total, - 'weight': newweight, + "calories": total, + "weight": newweight, }, ) @@ -710,7 +757,7 @@ class DDRX2( oldfriends: List[Optional[UserID]] = [None] * 10 links = self.data.local.user.get_links(self.game, self.version, userid) for link in links: - if link.type[:7] != 'friend_': + if link.type[:7] != "friend_": continue pos = int(link.type[7:]) @@ -719,11 +766,11 @@ class DDRX2( # Save any rivals that were added/removed/changed newfriends = oldfriends[:] for child in request.children: - if child.name != 'friend': + if child.name != "friend": continue - code = int(child.attribute('code')) - pos = int(child.attribute('pos')) + code = int(child.attribute("code")) + pos = int(child.attribute("pos")) if pos >= 0 and pos < 10: if code == 0: @@ -731,7 +778,9 @@ class DDRX2( newfriends[pos] = None else: # Try looking up the userid - newfriends[pos] = self.data.remote.user.from_extid(self.game, self.version, code) + newfriends[pos] = self.data.remote.user.from_extid( + self.game, self.version, code + ) # Diff the set of links to determine updates for i in range(10): @@ -744,7 +793,7 @@ class DDRX2( self.game, self.version, userid, - f'friend_{i}', + f"friend_{i}", oldfriends[i], ) elif oldfriends[i] is None: @@ -753,7 +802,7 @@ class DDRX2( self.game, self.version, userid, - f'friend_{i}', + f"friend_{i}", newfriends[i], {}, ) @@ -763,14 +812,14 @@ class DDRX2( self.game, self.version, userid, - f'friend_{i}', + f"friend_{i}", oldfriends[i], ) self.data.local.user.put_link( self.game, self.version, userid, - f'friend_{i}', + f"friend_{i}", newfriends[i], {}, ) diff --git a/bemani/backend/ddr/ddrx3.py b/bemani/backend/ddr/ddrx3.py index 01a5c0e..f06f37b 100644 --- a/bemani/backend/ddr/ddrx3.py +++ b/bemani/backend/ddr/ddrx3.py @@ -49,7 +49,7 @@ class DDRX3( DDRBase, ): - name: str = 'DanceDanceRevolution X3 VS 2ndMIX' + name: str = "DanceDanceRevolution X3 VS 2ndMIX" version: int = VersionConstants.DDR_X3_VS_2NDMIX GAME_STYLE_SINGLE: Final[int] = 0 @@ -156,28 +156,30 @@ class DDRX3( return combo_type def handle_game_common_request(self, request: Node) -> Node: - game = Node.void('game') + game = Node.void("game") for flagid in range(256): - flag = Node.void('flag') + flag = Node.void("flag") game.add_child(flag) - flag.set_attribute('id', str(flagid)) - flag.set_attribute('s2', '0') - flag.set_attribute('s1', '0') - flag.set_attribute('t', '0') + flag.set_attribute("id", str(flagid)) + flag.set_attribute("s2", "0") + flag.set_attribute("s1", "0") + flag.set_attribute("t", "0") - hit_chart = self.data.local.music.get_hit_chart(self.game, self.music_version, self.GAME_MAX_SONGS) + hit_chart = self.data.local.music.get_hit_chart( + self.game, self.music_version, self.GAME_MAX_SONGS + ) counts_by_reflink = [0] * self.GAME_MAX_SONGS for (reflink, plays) in hit_chart: if reflink >= 0 and reflink < self.GAME_MAX_SONGS: counts_by_reflink[reflink] = plays - game.add_child(Node.u32_array('cnt_music', counts_by_reflink)) + game.add_child(Node.u32_array("cnt_music", counts_by_reflink)) return game def handle_game_load_m_request(self, request: Node) -> Node: - extid = intish(request.attribute('code')) - refid = request.attribute('refid') + extid = intish(request.attribute("code")) + refid = request.attribute("refid") if extid is not None: # Rival score loading @@ -187,10 +189,15 @@ class DDRX3( userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: - scores = self.data.remote.music.get_scores(self.game, self.music_version, userid) + scores = self.data.remote.music.get_scores( + self.game, self.music_version, userid + ) old_scores = [ - score for score in self.data.local.user.get_achievements(self.game, self.music_version, userid) - if score.type == '2ndmix' + score + for score in self.data.local.user.get_achievements( + self.game, self.music_version, userid + ) + if score.type == "2ndmix" ] else: scores = [] @@ -203,7 +210,7 @@ class DDRX3( sortedscores[score.id] = {} if score.chart not in sortedscores[score.id]: sortedscores[score.id][score.chart] = {} - sortedscores[score.id][score.chart]['score'] = score + sortedscores[score.id][score.chart]["score"] = score for oldscore in old_scores: songid = int(oldscore.id / 100) @@ -212,13 +219,13 @@ class DDRX3( sortedscores[songid] = {} if chart not in sortedscores[songid]: sortedscores[songid][chart] = {} - sortedscores[songid][chart]['oldscore'] = oldscore + sortedscores[songid][chart]["oldscore"] = oldscore - game = Node.void('game') + game = Node.void("game") for song in sortedscores: - music = Node.void('music') + music = Node.void("music") game.add_child(music) - music.set_attribute('reclink', str(song)) + music.set_attribute("reclink", str(song)) for chart in sortedscores[song]: try: @@ -228,12 +235,16 @@ class DDRX3( continue scoredict = sortedscores[song][chart] - if 'score' in scoredict: + if "score" in scoredict: # We played the normal version of this song - gamerank = self.db_to_game_rank(scoredict['score'].data.get_int('rank')) - combo_type = self.db_to_game_halo(scoredict['score'].data.get_int('halo')) - points = scoredict['score'].points # type: ignore - plays = scoredict['score'].plays # type: ignore + gamerank = self.db_to_game_rank( + scoredict["score"].data.get_int("rank") + ) + combo_type = self.db_to_game_halo( + scoredict["score"].data.get_int("halo") + ) + points = scoredict["score"].points # type: ignore + plays = scoredict["score"].plays # type: ignore else: # We only played 2nd mix version of this song gamerank = 0 @@ -241,45 +252,45 @@ class DDRX3( points = 0 plays = 0 - if 'oldscore' in scoredict: + if "oldscore" in scoredict: # We played the 2nd mix version of this song - oldpoints = scoredict['oldscore'].data.get_int('points') - oldrank = scoredict['oldscore'].data.get_int('rank') - oldplays = scoredict['oldscore'].data.get_int('plays') + oldpoints = scoredict["oldscore"].data.get_int("points") + oldrank = scoredict["oldscore"].data.get_int("rank") + oldplays = scoredict["oldscore"].data.get_int("plays") else: oldpoints = 0 oldrank = 0 oldplays = 0 - typenode = Node.void('type') + typenode = Node.void("type") music.add_child(typenode) - typenode.set_attribute('diff', str(gamechart)) + typenode.set_attribute("diff", str(gamechart)) - typenode.add_child(Node.u32('score', points)) - typenode.add_child(Node.u16('count', plays)) - typenode.add_child(Node.u8('rank', gamerank)) - typenode.add_child(Node.u8('combo_type', combo_type)) - typenode.add_child(Node.u32('score_2nd', oldpoints)) - typenode.add_child(Node.u8('rank_2nd', oldrank)) - typenode.add_child(Node.u16('cnt_2nd', oldplays)) + typenode.add_child(Node.u32("score", points)) + typenode.add_child(Node.u16("count", plays)) + typenode.add_child(Node.u8("rank", gamerank)) + typenode.add_child(Node.u8("combo_type", combo_type)) + typenode.add_child(Node.u32("score_2nd", oldpoints)) + typenode.add_child(Node.u8("rank_2nd", oldrank)) + typenode.add_child(Node.u16("cnt_2nd", oldplays)) return game def handle_game_save_m_request(self, request: Node) -> Node: - refid = request.attribute('refid') - songid = int(request.attribute('mid')) - chart = self.game_to_db_chart(int(request.attribute('mtype'))) + refid = request.attribute("refid") + songid = int(request.attribute("mid")) + chart = self.game_to_db_chart(int(request.attribute("mtype"))) userid = self.data.remote.user.from_refid(self.game, self.version, refid) # Calculate statistics - data = request.child('data') - playmode = int(data.attribute('playmode')) + data = request.child("data") + playmode = int(data.attribute("playmode")) if playmode == self.GAME_PLAY_MODE_2NDMIX: if userid is not None: # These are special cased and treated like achievements - points = int(data.attribute('score_2nd')) - combo = int(data.attribute('combo_2nd')) - rank = int(data.attribute('rank_2nd')) + points = int(data.attribute("score_2nd")) + combo = int(data.attribute("combo_2nd")) + rank = int(data.attribute("rank_2nd")) # Grab the old 2nd mix score existingscore = self.data.local.user.get_achievement( @@ -287,17 +298,17 @@ class DDRX3( self.music_version, userid, (songid * 100) + chart, - '2ndmix', + "2ndmix", ) if existingscore is not None: - highscore = points > existingscore.get_int('points') + highscore = points > existingscore.get_int("points") - plays = existingscore.get_int('plays', 0) + 1 - points = max(points, existingscore.get_int('points')) + plays = existingscore.get_int("plays", 0) + 1 + points = max(points, existingscore.get_int("points")) if not highscore: - combo = existingscore.get_int('combo', combo) - rank = existingscore.get_int('rank', rank) + combo = existingscore.get_int("combo", combo) + rank = existingscore.get_int("rank", rank) else: plays = 1 @@ -306,27 +317,27 @@ class DDRX3( self.music_version, userid, (songid * 100) + chart, - '2ndmix', + "2ndmix", { - 'points': points, - 'combo': combo, - 'rank': rank, - 'plays': plays, + "points": points, + "combo": combo, + "rank": rank, + "plays": plays, }, ) else: - points = int(data.attribute('score')) - combo = int(data.attribute('combo')) - rank = self.game_to_db_rank(int(data.attribute('rank'))) - if int(data.attribute('full')) == 0: + points = int(data.attribute("score")) + combo = int(data.attribute("combo")) + rank = self.game_to_db_rank(int(data.attribute("rank"))) + if int(data.attribute("full")) == 0: halo = self.HALO_NONE - elif int(data.attribute('perf')) == 0: + elif int(data.attribute("perf")) == 0: halo = self.HALO_GREAT_FULL_COMBO elif points < 1000000: halo = self.HALO_PERFECT_FULL_COMBO else: halo = self.HALO_MARVELOUS_FULL_COMBO - trace = request.child_value('trace') + trace = request.child_value("trace") # Save the score, regardless of whether we have a refid. If we save # an anonymous score, it only goes into the DB to count against the @@ -343,355 +354,371 @@ class DDRX3( ) # No response needed - game = Node.void('game') + game = Node.void("game") return game def format_profile(self, userid: UserID, profile: Profile) -> Node: - root = Node.void('game') + root = Node.void("game") # Look up play stats we bridge to every mix play_stats = self.get_play_statistics(userid) # Basic game settings - root.add_child(Node.string('seq', '')) - root.add_child(Node.u32('code', profile.extid)) - root.add_child(Node.string('name', profile.get_str('name'))) - root.add_child(Node.u8('area', profile.get_int('area', self.get_machine_region()))) - root.add_child(Node.u32('cnt_s', play_stats.get_int('single_plays'))) - root.add_child(Node.u32('cnt_d', play_stats.get_int('double_plays'))) - root.add_child(Node.u32('cnt_b', play_stats.get_int('battle_plays'))) # This could be wrong, its a guess - root.add_child(Node.u32('cnt_m0', play_stats.get_int('cnt_m0'))) - root.add_child(Node.u32('cnt_m1', play_stats.get_int('cnt_m1'))) - root.add_child(Node.u32('cnt_m2', play_stats.get_int('cnt_m2'))) - root.add_child(Node.u32('cnt_m3', play_stats.get_int('cnt_m3'))) - root.add_child(Node.u32('cnt_m4', play_stats.get_int('cnt_m4'))) - root.add_child(Node.u32('cnt_m5', play_stats.get_int('cnt_m5'))) - root.add_child(Node.u32('exp', play_stats.get_int('exp'))) - root.add_child(Node.u32('exp_o', profile.get_int('exp_o'))) - root.add_child(Node.u32('star', profile.get_int('star'))) - root.add_child(Node.u32('star_c', profile.get_int('star_c'))) - root.add_child(Node.u8('combo', profile.get_int('combo', 0))) - root.add_child(Node.u8('timing_diff', profile.get_int('early_late', 0))) + root.add_child(Node.string("seq", "")) + root.add_child(Node.u32("code", profile.extid)) + root.add_child(Node.string("name", profile.get_str("name"))) + root.add_child( + Node.u8("area", profile.get_int("area", self.get_machine_region())) + ) + root.add_child(Node.u32("cnt_s", play_stats.get_int("single_plays"))) + root.add_child(Node.u32("cnt_d", play_stats.get_int("double_plays"))) + root.add_child( + Node.u32("cnt_b", play_stats.get_int("battle_plays")) + ) # This could be wrong, its a guess + root.add_child(Node.u32("cnt_m0", play_stats.get_int("cnt_m0"))) + root.add_child(Node.u32("cnt_m1", play_stats.get_int("cnt_m1"))) + root.add_child(Node.u32("cnt_m2", play_stats.get_int("cnt_m2"))) + root.add_child(Node.u32("cnt_m3", play_stats.get_int("cnt_m3"))) + root.add_child(Node.u32("cnt_m4", play_stats.get_int("cnt_m4"))) + root.add_child(Node.u32("cnt_m5", play_stats.get_int("cnt_m5"))) + root.add_child(Node.u32("exp", play_stats.get_int("exp"))) + root.add_child(Node.u32("exp_o", profile.get_int("exp_o"))) + root.add_child(Node.u32("star", profile.get_int("star"))) + root.add_child(Node.u32("star_c", profile.get_int("star_c"))) + root.add_child(Node.u8("combo", profile.get_int("combo", 0))) + root.add_child(Node.u8("timing_diff", profile.get_int("early_late", 0))) # Character stuff - chara = Node.void('chara') + chara = Node.void("chara") root.add_child(chara) - chara.set_attribute('my', str(profile.get_int('chara', 30))) - root.add_child(Node.u16_array('chara_opt', profile.get_int_array('chara_opt', 96, [208] * 96))) + chara.set_attribute("my", str(profile.get_int("chara", 30))) + root.add_child( + Node.u16_array( + "chara_opt", profile.get_int_array("chara_opt", 96, [208] * 96) + ) + ) # Drill rankings - if 'title_gr' in profile: - title_gr = Node.void('title_gr') + if "title_gr" in profile: + title_gr = Node.void("title_gr") root.add_child(title_gr) - title_grdict = profile.get_dict('title_gr') - if 't' in title_grdict: - title_gr.set_attribute('t', str(title_grdict.get_int('t'))) - if 's' in title_grdict: - title_gr.set_attribute('s', str(title_grdict.get_int('s'))) - if 'd' in title_grdict: - title_gr.set_attribute('d', str(title_grdict.get_int('d'))) + title_grdict = profile.get_dict("title_gr") + if "t" in title_grdict: + title_gr.set_attribute("t", str(title_grdict.get_int("t"))) + if "s" in title_grdict: + title_gr.set_attribute("s", str(title_grdict.get_int("s"))) + if "d" in title_grdict: + title_gr.set_attribute("d", str(title_grdict.get_int("d"))) # Calorie mode - if 'weight' in profile: + if "weight" in profile: workouts = self.data.local.user.get_time_based_achievements( self.game, self.version, userid, - achievementtype='workout', + achievementtype="workout", since=Time.now() - Time.SECONDS_IN_DAY, ) - total = sum([w.data.get_int('calories') for w in workouts]) - workout = Node.void('workout') + total = sum([w.data.get_int("calories") for w in workouts]) + workout = Node.void("workout") root.add_child(workout) - workout.set_attribute('weight', str(profile.get_int('weight'))) - workout.set_attribute('day', str(total)) - workout.set_attribute('disp', '1') + workout.set_attribute("weight", str(profile.get_int("weight"))) + workout.set_attribute("day", str(total)) + workout.set_attribute("disp", "1") # Daily play counts - daycount = Node.void('daycount') + daycount = Node.void("daycount") root.add_child(daycount) - daycount.set_attribute('playcount', str(play_stats.today_plays)) + daycount.set_attribute("playcount", str(play_stats.today_plays)) # Daily combo stuff, unknown how this works - dailycombo = Node.void('dailycombo') + dailycombo = Node.void("dailycombo") root.add_child(dailycombo) - dailycombo.set_attribute('daily_combo', str(0)) - dailycombo.set_attribute('daily_combo_lv', str(0)) + dailycombo.set_attribute("daily_combo", str(0)) + dailycombo.set_attribute("daily_combo_lv", str(0)) # Last cursor settings - last = Node.void('last') + last = Node.void("last") root.add_child(last) - lastdict = profile.get_dict('last') - last.set_attribute('rival1', str(lastdict.get_int('rival1', -1))) - last.set_attribute('rival2', str(lastdict.get_int('rival2', -1))) - last.set_attribute('rival3', str(lastdict.get_int('rival3', -1))) - last.set_attribute('fri', str(lastdict.get_int('rival1', -1))) # This literally goes to the same memory in X3 - last.set_attribute('style', str(lastdict.get_int('style'))) - last.set_attribute('mode', str(lastdict.get_int('mode'))) - last.set_attribute('cate', str(lastdict.get_int('cate'))) - last.set_attribute('sort', str(lastdict.get_int('sort'))) - last.set_attribute('mid', str(lastdict.get_int('mid'))) - last.set_attribute('mtype', str(lastdict.get_int('mtype'))) - last.set_attribute('cid', str(lastdict.get_int('cid'))) - last.set_attribute('ctype', str(lastdict.get_int('ctype'))) - last.set_attribute('sid', str(lastdict.get_int('sid'))) + lastdict = profile.get_dict("last") + last.set_attribute("rival1", str(lastdict.get_int("rival1", -1))) + last.set_attribute("rival2", str(lastdict.get_int("rival2", -1))) + last.set_attribute("rival3", str(lastdict.get_int("rival3", -1))) + last.set_attribute( + "fri", str(lastdict.get_int("rival1", -1)) + ) # This literally goes to the same memory in X3 + last.set_attribute("style", str(lastdict.get_int("style"))) + last.set_attribute("mode", str(lastdict.get_int("mode"))) + last.set_attribute("cate", str(lastdict.get_int("cate"))) + last.set_attribute("sort", str(lastdict.get_int("sort"))) + last.set_attribute("mid", str(lastdict.get_int("mid"))) + last.set_attribute("mtype", str(lastdict.get_int("mtype"))) + last.set_attribute("cid", str(lastdict.get_int("cid"))) + last.set_attribute("ctype", str(lastdict.get_int("ctype"))) + last.set_attribute("sid", str(lastdict.get_int("sid"))) # Result stars - result_star = Node.void('result_star') + result_star = Node.void("result_star") root.add_child(result_star) - result_stars = profile.get_int_array('result_stars', 9) + result_stars = profile.get_int_array("result_stars", 9) for i in range(9): - result_star.set_attribute(f'slot{i + 1}', str(result_stars[i])) + result_star.set_attribute(f"slot{i + 1}", str(result_stars[i])) # Target stuff - target = Node.void('target') + target = Node.void("target") root.add_child(target) - target.set_attribute('flag', str(profile.get_int('target_flag'))) - target.set_attribute('setnum', str(profile.get_int('target_setnum'))) + target.set_attribute("flag", str(profile.get_int("target_flag"))) + target.set_attribute("setnum", str(profile.get_int("target_setnum"))) # Groove gauge level-ups - gr_s = Node.void('gr_s') + gr_s = Node.void("gr_s") root.add_child(gr_s) index = 1 - for entry in profile.get_int_array('gr_s', 5): - gr_s.set_attribute(f'gr{index}', str(entry)) + for entry in profile.get_int_array("gr_s", 5): + gr_s.set_attribute(f"gr{index}", str(entry)) index = index + 1 - gr_d = Node.void('gr_d') + gr_d = Node.void("gr_d") root.add_child(gr_d) index = 1 - for entry in profile.get_int_array('gr_d', 5): - gr_d.set_attribute(f'gr{index}', str(entry)) + for entry in profile.get_int_array("gr_d", 5): + gr_d.set_attribute(f"gr{index}", str(entry)) index = index + 1 # Options in menus - root.add_child(Node.s16_array('opt', profile.get_int_array('opt', 16))) - root.add_child(Node.s16_array('opt_ex', profile.get_int_array('opt_ex', 16))) + root.add_child(Node.s16_array("opt", profile.get_int_array("opt", 16))) + root.add_child(Node.s16_array("opt_ex", profile.get_int_array("opt_ex", 16))) # Unlock flags - root.add_child(Node.u8_array('flag', profile.get_int_array('flag', 256, [1] * 256))) + root.add_child( + Node.u8_array("flag", profile.get_int_array("flag", 256, [1] * 256)) + ) # Ranking display? - root.add_child(Node.u16_array('rank', profile.get_int_array('rank', 100))) + root.add_child(Node.u16_array("rank", profile.get_int_array("rank", 100))) # Rivals links = self.data.local.user.get_links(self.game, self.version, userid) for link in links: - if link.type[:7] != 'friend_': + if link.type[:7] != "friend_": continue pos = int(link.type[7:]) friend = self.get_profile(link.other_userid) play_stats = self.get_play_statistics(link.other_userid) if friend is not None: - friendnode = Node.void('friend') + friendnode = Node.void("friend") root.add_child(friendnode) - friendnode.set_attribute('pos', str(pos)) - friendnode.set_attribute('vs', '0') - friendnode.set_attribute('up', '0') - friendnode.add_child(Node.u32('code', friend.extid)) - friendnode.add_child(Node.string('name', friend.get_str('name'))) - friendnode.add_child(Node.u8('area', friend.get_int('area', self.get_machine_region()))) - friendnode.add_child(Node.u32('exp', play_stats.get_int('exp'))) - friendnode.add_child(Node.u32('star', friend.get_int('star'))) + friendnode.set_attribute("pos", str(pos)) + friendnode.set_attribute("vs", "0") + friendnode.set_attribute("up", "0") + friendnode.add_child(Node.u32("code", friend.extid)) + friendnode.add_child(Node.string("name", friend.get_str("name"))) + friendnode.add_child( + Node.u8("area", friend.get_int("area", self.get_machine_region())) + ) + friendnode.add_child(Node.u32("exp", play_stats.get_int("exp"))) + friendnode.add_child(Node.u32("star", friend.get_int("star"))) # Drill rankings - if 'title' in friend: - title = Node.void('title') + if "title" in friend: + title = Node.void("title") friendnode.add_child(title) - titledict = friend.get_dict('title') - if 't' in titledict: - title.set_attribute('t', str(titledict.get_int('t'))) - if 's' in titledict: - title.set_attribute('s', str(titledict.get_int('s'))) - if 'd' in titledict: - title.set_attribute('d', str(titledict.get_int('d'))) + titledict = friend.get_dict("title") + if "t" in titledict: + title.set_attribute("t", str(titledict.get_int("t"))) + if "s" in titledict: + title.set_attribute("s", str(titledict.get_int("s"))) + if "d" in titledict: + title.set_attribute("d", str(titledict.get_int("d"))) - if 'title_gr' in friend: - title_gr = Node.void('title_gr') + if "title_gr" in friend: + title_gr = Node.void("title_gr") friendnode.add_child(title_gr) - title_grdict = friend.get_dict('title_gr') - if 't' in title_grdict: - title_gr.set_attribute('t', str(title_grdict.get_int('t'))) - if 's' in title_grdict: - title_gr.set_attribute('s', str(title_grdict.get_int('s'))) - if 'd' in title_grdict: - title_gr.set_attribute('d', str(title_grdict.get_int('d'))) + title_grdict = friend.get_dict("title_gr") + if "t" in title_grdict: + title_gr.set_attribute("t", str(title_grdict.get_int("t"))) + if "s" in title_grdict: + title_gr.set_attribute("s", str(title_grdict.get_int("s"))) + if "d" in title_grdict: + title_gr.set_attribute("d", str(title_grdict.get_int("d"))) # Groove gauge level-ups - gr_s = Node.void('gr_s') + gr_s = Node.void("gr_s") friendnode.add_child(gr_s) index = 1 - for entry in friend.get_int_array('gr_s', 5): - gr_s.set_attribute(f'gr{index}', str(entry)) + for entry in friend.get_int_array("gr_s", 5): + gr_s.set_attribute(f"gr{index}", str(entry)) index = index + 1 - gr_d = Node.void('gr_d') + gr_d = Node.void("gr_d") friendnode.add_child(gr_d) index = 1 - for entry in friend.get_int_array('gr_d', 5): - gr_d.set_attribute(f'gr{index}', str(entry)) + for entry in friend.get_int_array("gr_d", 5): + gr_d.set_attribute(f"gr{index}", str(entry)) index = index + 1 # Play area - areas = profile.get_int_array('play_area', 55) - play_area = Node.void('play_area') + areas = profile.get_int_array("play_area", 55) + play_area = Node.void("play_area") root.add_child(play_area) for i in range(len(areas)): - play_area.set_attribute(f'play_cnt{i}', str(areas[i])) + play_area.set_attribute(f"play_cnt{i}", str(areas[i])) return root - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() play_stats = self.get_play_statistics(userid) # Grab last node and accessories so we can make decisions based on type - last = request.child('last') - lastdict = newprofile.get_dict('last') - mode = int(last.attribute('mode')) - style = int(last.attribute('style')) + last = request.child("last") + lastdict = newprofile.get_dict("last") + mode = int(last.attribute("mode")) + style = int(last.attribute("style")) is_dp = style == self.GAME_STYLE_DOUBLE # Drill rankings - title = request.child('title') - title_gr = request.child('title_gr') - titledict = newprofile.get_dict('title') - title_grdict = newprofile.get_dict('title_gr') + title = request.child("title") + title_gr = request.child("title_gr") + titledict = newprofile.get_dict("title") + title_grdict = newprofile.get_dict("title_gr") # Groove radar level ups - gr = request.child('gr') + gr = request.child("gr") # Set the correct values depending on if we're single or double play if is_dp: - play_stats.increment_int('double_plays') + play_stats.increment_int("double_plays") if gr is not None: newprofile.replace_int_array( - 'gr_d', + "gr_d", 5, [ - intish(gr.attribute('gr1')), - intish(gr.attribute('gr2')), - intish(gr.attribute('gr3')), - intish(gr.attribute('gr4')), - intish(gr.attribute('gr5')), + intish(gr.attribute("gr1")), + intish(gr.attribute("gr2")), + intish(gr.attribute("gr3")), + intish(gr.attribute("gr4")), + intish(gr.attribute("gr5")), ], ) if title is not None: - titledict.replace_int('d', title.value) - newprofile.replace_dict('title', titledict) + titledict.replace_int("d", title.value) + newprofile.replace_dict("title", titledict) if title_gr is not None: - title_grdict.replace_int('d', title.value) - newprofile.replace_dict('title_gr', title_grdict) + title_grdict.replace_int("d", title.value) + newprofile.replace_dict("title_gr", title_grdict) else: - play_stats.increment_int('single_plays') + play_stats.increment_int("single_plays") if gr is not None: newprofile.replace_int_array( - 'gr_s', + "gr_s", 5, [ - intish(gr.attribute('gr1')), - intish(gr.attribute('gr2')), - intish(gr.attribute('gr3')), - intish(gr.attribute('gr4')), - intish(gr.attribute('gr5')), + intish(gr.attribute("gr1")), + intish(gr.attribute("gr2")), + intish(gr.attribute("gr3")), + intish(gr.attribute("gr4")), + intish(gr.attribute("gr5")), ], ) if title is not None: - titledict.replace_int('s', title.value) - newprofile.replace_dict('title', titledict) + titledict.replace_int("s", title.value) + newprofile.replace_dict("title", titledict) if title_gr is not None: - title_grdict.replace_int('s', title.value) - newprofile.replace_dict('title_gr', title_grdict) - play_stats.increment_int(f'cnt_m{mode}') + title_grdict.replace_int("s", title.value) + newprofile.replace_dict("title_gr", title_grdict) + play_stats.increment_int(f"cnt_m{mode}") # Result stars - result_star = request.child('result_star') + result_star = request.child("result_star") if result_star is not None: newprofile.replace_int_array( - 'result_stars', + "result_stars", 9, [ - intish(result_star.attribute('slot1')), - intish(result_star.attribute('slot2')), - intish(result_star.attribute('slot3')), - intish(result_star.attribute('slot4')), - intish(result_star.attribute('slot5')), - intish(result_star.attribute('slot6')), - intish(result_star.attribute('slot7')), - intish(result_star.attribute('slot8')), - intish(result_star.attribute('slot9')), + intish(result_star.attribute("slot1")), + intish(result_star.attribute("slot2")), + intish(result_star.attribute("slot3")), + intish(result_star.attribute("slot4")), + intish(result_star.attribute("slot5")), + intish(result_star.attribute("slot6")), + intish(result_star.attribute("slot7")), + intish(result_star.attribute("slot8")), + intish(result_star.attribute("slot9")), ], ) # Target stuff - target = request.child('target') + target = request.child("target") if target is not None: - newprofile.replace_int('target_flag', intish(target.attribute('flag'))) - newprofile.replace_int('target_setnum', intish(target.attribute('setnum'))) + newprofile.replace_int("target_flag", intish(target.attribute("flag"))) + newprofile.replace_int("target_setnum", intish(target.attribute("setnum"))) # Update last attributes - lastdict.replace_int('rival1', intish(last.attribute('rival1'))) - lastdict.replace_int('rival2', intish(last.attribute('rival2'))) - lastdict.replace_int('rival3', intish(last.attribute('rival3'))) - lastdict.replace_int('style', intish(last.attribute('style'))) - lastdict.replace_int('mode', intish(last.attribute('mode'))) - lastdict.replace_int('cate', intish(last.attribute('cate'))) - lastdict.replace_int('sort', intish(last.attribute('sort'))) - lastdict.replace_int('mid', intish(last.attribute('mid'))) - lastdict.replace_int('mtype', intish(last.attribute('mtype'))) - lastdict.replace_int('cid', intish(last.attribute('cid'))) - lastdict.replace_int('ctype', intish(last.attribute('ctype'))) - lastdict.replace_int('sid', intish(last.attribute('sid'))) - newprofile.replace_dict('last', lastdict) + lastdict.replace_int("rival1", intish(last.attribute("rival1"))) + lastdict.replace_int("rival2", intish(last.attribute("rival2"))) + lastdict.replace_int("rival3", intish(last.attribute("rival3"))) + lastdict.replace_int("style", intish(last.attribute("style"))) + lastdict.replace_int("mode", intish(last.attribute("mode"))) + lastdict.replace_int("cate", intish(last.attribute("cate"))) + lastdict.replace_int("sort", intish(last.attribute("sort"))) + lastdict.replace_int("mid", intish(last.attribute("mid"))) + lastdict.replace_int("mtype", intish(last.attribute("mtype"))) + lastdict.replace_int("cid", intish(last.attribute("cid"))) + lastdict.replace_int("ctype", intish(last.attribute("ctype"))) + lastdict.replace_int("sid", intish(last.attribute("sid"))) + newprofile.replace_dict("last", lastdict) # Grab character options - chara = request.child('chara') + chara = request.child("chara") if chara is not None: - newprofile.replace_int('chara', intish(chara.attribute('my'))) - chara_opt = request.child('chara_opt') + newprofile.replace_int("chara", intish(chara.attribute("my"))) + chara_opt = request.child("chara_opt") if chara_opt is not None: # A bug in old versions of AVS returns the wrong number for set - newprofile.replace_int_array('chara_opt', 96, chara_opt.value[:96]) + newprofile.replace_int_array("chara_opt", 96, chara_opt.value[:96]) # Options - opt = request.child('opt') + opt = request.child("opt") if opt is not None: # A bug in old versions of AVS returns the wrong number for set - newprofile.replace_int_array('opt', 16, opt.value[:16]) + newprofile.replace_int_array("opt", 16, opt.value[:16]) # Experience and stars - exp = request.child_value('exp') + exp = request.child_value("exp") if exp is not None: - play_stats.replace_int('exp', play_stats.get_int('exp') + exp) - star = request.child_value('star') + play_stats.replace_int("exp", play_stats.get_int("exp") + exp) + star = request.child_value("star") if star is not None: - newprofile.replace_int('star', newprofile.get_int('star') + star) - star_c = request.child_value('star_c') + newprofile.replace_int("star", newprofile.get_int("star") + star) + star_c = request.child_value("star_c") if star_c is not None: - newprofile.replace_int('star_c', newprofile.get_int('star_c') + exp) + newprofile.replace_int("star_c", newprofile.get_int("star_c") + exp) # Update game flags for child in request.children: - if child.name != 'flag': + if child.name != "flag": continue try: - value = int(child.attribute('data')) - offset = int(child.attribute('no')) + value = int(child.attribute("data")) + offset = int(child.attribute("no")) except ValueError: continue - flags = newprofile.get_int_array('flag', 256, [1] * 256) + flags = newprofile.get_int_array("flag", 256, [1] * 256) if offset < 0 or offset >= len(flags): continue flags[offset] = value - newprofile.replace_int_array('flag', 256, flags) + newprofile.replace_int_array("flag", 256, flags) # Workout mode support newweight = -1 - oldweight = newprofile.get_int('weight') + oldweight = newprofile.get_int("weight") for child in request.children: - if child.name != 'weight': + if child.name != "weight": continue newweight = child.value if newweight < 0: @@ -700,14 +727,14 @@ class DDRX3( # Either update or unset the weight depending on the game if newweight == 0: # Weight is unset or we declined to use this feature, remove from profile - if 'weight' in newprofile: - del newprofile['weight'] + if "weight" in newprofile: + del newprofile["weight"] else: # Weight has been set or previously retrieved, we should save calories - newprofile.replace_int('weight', newweight) + newprofile.replace_int("weight", newweight) total = 0 for child in request.children: - if child.name != 'calory': + if child.name != "calory": continue total += child.value self.data.local.user.put_time_based_achievement( @@ -715,10 +742,10 @@ class DDRX3( self.version, userid, 0, - 'workout', + "workout", { - 'calories': total, - 'weight': newweight, + "calories": total, + "weight": newweight, }, ) @@ -726,7 +753,7 @@ class DDRX3( oldfriends: List[Optional[UserID]] = [None] * 10 links = self.data.local.user.get_links(self.game, self.version, userid) for link in links: - if link.type[:7] != 'friend_': + if link.type[:7] != "friend_": continue pos = int(link.type[7:]) @@ -735,11 +762,11 @@ class DDRX3( # Save any rivals that were added/removed/changed newfriends = oldfriends[:] for child in request.children: - if child.name != 'friend': + if child.name != "friend": continue - code = int(child.attribute('code')) - pos = int(child.attribute('pos')) + code = int(child.attribute("code")) + pos = int(child.attribute("pos")) if pos >= 0 and pos < 10: if code == 0: @@ -747,7 +774,9 @@ class DDRX3( newfriends[pos] = None else: # Try looking up the userid - newfriends[pos] = self.data.remote.user.from_extid(self.game, self.version, code) + newfriends[pos] = self.data.remote.user.from_extid( + self.game, self.version, code + ) # Diff the set of links to determine updates for i in range(10): @@ -760,7 +789,7 @@ class DDRX3( self.game, self.version, userid, - f'friend_{i}', + f"friend_{i}", oldfriends[i], ) elif oldfriends[i] is None: @@ -769,7 +798,7 @@ class DDRX3( self.game, self.version, userid, - f'friend_{i}', + f"friend_{i}", newfriends[i], {}, ) @@ -779,24 +808,24 @@ class DDRX3( self.game, self.version, userid, - f'friend_{i}', + f"friend_{i}", oldfriends[i], ) self.data.local.user.put_link( self.game, self.version, userid, - f'friend_{i}', + f"friend_{i}", newfriends[i], {}, ) # Play area counter - shop_area = int(request.attribute('shop_area')) + shop_area = int(request.attribute("shop_area")) if shop_area >= 0 and shop_area < 55: - areas = newprofile.get_int_array('play_area', 55) + areas = newprofile.get_int_array("play_area", 55) areas[shop_area] = areas[shop_area] + 1 - newprofile.replace_int_array('play_area', 55, areas) + newprofile.replace_int_array("play_area", 55, areas) # Keep track of play statistics self.update_play_statistics(userid, play_stats) diff --git a/bemani/backend/ddr/factory.py b/bemani/backend/ddr/factory.py index 07cf2b2..a0138d3 100644 --- a/bemani/backend/ddr/factory.py +++ b/bemani/backend/ddr/factory.py @@ -48,12 +48,17 @@ class DDRFactory(Factory): @classmethod def register_all(cls) -> None: - for gamecode in ['HDX', 'JDX', 'KDX', 'MDX']: + for gamecode in ["HDX", "JDX", "KDX", "MDX"]: Base.register(gamecode, DDRFactory) @classmethod - def create(cls, data: Data, config: Config, model: Model, parentmodel: Optional[Model]=None) -> Optional[Base]: - + def create( + cls, + data: Data, + config: Config, + model: Model, + parentmodel: Optional[Model] = None, + ) -> Optional[Base]: def version_from_date(date: int) -> Optional[int]: if date < 2014051200: return VersionConstants.DDR_2013 @@ -65,20 +70,20 @@ class DDRFactory(Factory): return VersionConstants.DDR_A20 return None - if model.gamecode == 'HDX': + if model.gamecode == "HDX": return DDRX(data, config, model) - if model.gamecode == 'JDX': + if model.gamecode == "JDX": return DDRX2(data, config, model) - if model.gamecode == 'KDX': + if model.gamecode == "KDX": return DDRX3(data, config, model) - if model.gamecode == 'MDX': + if model.gamecode == "MDX": if model.version is None: if parentmodel is None: return None # We have no way to tell apart newer versions. However, we can make # an educated guess if we happen to be summoned for old profile lookup. - if parentmodel.gamecode not in ['HDX', 'JDX', 'KDX', 'MDX']: + if parentmodel.gamecode not in ["HDX", "JDX", "KDX", "MDX"]: return None parentversion = version_from_date(parentmodel.version) diff --git a/bemani/backend/ddr/stubs.py b/bemani/backend/ddr/stubs.py index a346123..fc5eaec 100644 --- a/bemani/backend/ddr/stubs.py +++ b/bemani/backend/ddr/stubs.py @@ -7,7 +7,7 @@ from bemani.common import VersionConstants class DDRX(DDRBase): - name: str = 'DanceDanceRevolution X' + name: str = "DanceDanceRevolution X" version: int = VersionConstants.DDR_X def previous_version(self) -> Optional[DDRBase]: @@ -16,7 +16,7 @@ class DDRX(DDRBase): class DDRSuperNova2(DDRBase): - name: str = 'DanceDanceRevolution SuperNova 2' + name: str = "DanceDanceRevolution SuperNova 2" version: int = VersionConstants.DDR_SUPERNOVA_2 def previous_version(self) -> Optional[DDRBase]: @@ -25,7 +25,7 @@ class DDRSuperNova2(DDRBase): class DDRSuperNova(DDRBase): - name: str = 'DanceDanceRevolution SuperNova' + name: str = "DanceDanceRevolution SuperNova" version: int = VersionConstants.DDR_SUPERNOVA def previous_version(self) -> Optional[DDRBase]: @@ -34,7 +34,7 @@ class DDRSuperNova(DDRBase): class DDRExtreme(DDRBase): - name: str = 'DanceDanceRevolution Extreme' + name: str = "DanceDanceRevolution Extreme" version: int = VersionConstants.DDR_EXTREME def previous_version(self) -> Optional[DDRBase]: @@ -43,7 +43,7 @@ class DDRExtreme(DDRBase): class DDR7thMix(DDRBase): - name: str = 'DanceDanceRevolution 7thMix' + name: str = "DanceDanceRevolution 7thMix" version: int = VersionConstants.DDR_7THMIX def previous_version(self) -> Optional[DDRBase]: @@ -52,7 +52,7 @@ class DDR7thMix(DDRBase): class DDR6thMix(DDRBase): - name: str = 'DanceDanceRevolution 6thMix' + name: str = "DanceDanceRevolution 6thMix" version: int = VersionConstants.DDR_6THMIX def previous_version(self) -> Optional[DDRBase]: @@ -61,7 +61,7 @@ class DDR6thMix(DDRBase): class DDR5thMix(DDRBase): - name: str = 'DanceDanceRevolution 5thMix' + name: str = "DanceDanceRevolution 5thMix" version: int = VersionConstants.DDR_5THMIX def previous_version(self) -> Optional[DDRBase]: @@ -70,7 +70,7 @@ class DDR5thMix(DDRBase): class DDR4thMix(DDRBase): - name: str = 'DanceDanceRevolution 4thMix' + name: str = "DanceDanceRevolution 4thMix" version: int = VersionConstants.DDR_4THMIX def previous_version(self) -> Optional[DDRBase]: @@ -79,7 +79,7 @@ class DDR4thMix(DDRBase): class DDR3rdMix(DDRBase): - name: str = 'DanceDanceRevolution 3rdMix' + name: str = "DanceDanceRevolution 3rdMix" version: int = VersionConstants.DDR_3RDMIX def previous_version(self) -> Optional[DDRBase]: @@ -88,7 +88,7 @@ class DDR3rdMix(DDRBase): class DDR2ndMix(DDRBase): - name: str = 'DanceDanceRevolution 2ndMix' + name: str = "DanceDanceRevolution 2ndMix" version: int = VersionConstants.DDR_2NDMIX def previous_version(self) -> Optional[DDRBase]: @@ -97,5 +97,5 @@ class DDR2ndMix(DDRBase): class DDR1stMix(DDRBase): - name: str = 'DanceDanceRevolution 1stMix' + name: str = "DanceDanceRevolution 1stMix" version: int = VersionConstants.DDR_1STMIX diff --git a/bemani/backend/dispatch.py b/bemani/backend/dispatch.py index 28aa409..08cedf7 100644 --- a/bemani/backend/dispatch.py +++ b/bemani/backend/dispatch.py @@ -61,7 +61,7 @@ class Dispatch: """ self.log("Received request:\n{}", tree) - if tree.name != 'call': + if tree.name != "call": # Invalid request self.log("Invalid root node {}", tree.name) return None @@ -71,15 +71,17 @@ class Dispatch: self.log("Invalid number of children for root node") return None - modelstring = tree.attribute('model') + modelstring = tree.attribute("model") model = Model.from_modelstring(modelstring) - pcbid = tree.attribute('srcid') + pcbid = tree.attribute("srcid") # If we are enforcing, bail out if we don't recognize thie ID pcb = self.__data.local.machine.get_machine(pcbid) if self.__config.server.enforce_pcbid and pcb is None: self.log("Unrecognized PCBID {}", pcbid) - raise UnrecognizedPCBIDException(pcbid, modelstring, self.__config.client.address) + raise UnrecognizedPCBIDException( + pcbid, modelstring, self.__config.client.address + ) # If we don't have a Machine, but we aren't enforcing, we must create it if pcb is None: @@ -88,9 +90,9 @@ class Dispatch: request = tree.children[0] config = self.__config.clone() - config['machine'] = { - 'pcbid': pcbid, - 'arcade': pcb.arcade, + config["machine"] = { + "pcbid": pcbid, + "arcade": pcb.arcade, } # If the machine we looked up is in an arcade, override the global @@ -98,22 +100,29 @@ class Dispatch: if pcb.arcade is not None: arcade = self.__data.local.machine.get_arcade(pcb.arcade) if arcade is not None: - config['paseli']['enabled'] = arcade.data.get_bool('paseli_enabled') - config['paseli']['infinite'] = arcade.data.get_bool('paseli_infinite') - if arcade.data.get_bool('mask_services_url'): + config["paseli"]["enabled"] = arcade.data.get_bool("paseli_enabled") + config["paseli"]["infinite"] = arcade.data.get_bool("paseli_infinite") + if arcade.data.get_bool("mask_services_url"): # Mask the address, no matter what the server settings are - config['server']['uri'] = None + config["server"]["uri"] = None game = Base.create(self.__data, config, model) - method = request.attribute('method') + method = request.attribute("method") response = None # If we are enforcing, make sure the PCBID isn't specified to be # game-specific if config.server.enforce_pcbid and pcb.game is not None: if pcb.game != game.game: - self.log("PCBID {} assigned to game {}, but connected from game {}", pcbid, pcb.game, game.game) - raise UnrecognizedPCBIDException(pcbid, modelstring, config.client.address) + self.log( + "PCBID {} assigned to game {}, but connected from game {}", + pcbid, + pcb.game, + game.game, + ) + raise UnrecognizedPCBIDException( + pcbid, modelstring, config.client.address + ) if pcb.version is not None: if pcb.version > 0 and pcb.version != game.version: self.log( @@ -124,7 +133,9 @@ class Dispatch: game.game, game.version, ) - raise UnrecognizedPCBIDException(pcbid, modelstring, config.client.address) + raise UnrecognizedPCBIDException( + pcbid, modelstring, config.client.address + ) if pcb.version < 0 and (-pcb.version) < game.version: self.log( "PCBID {} assigned to game {} maximum version {}, but connected from game {} version {}", @@ -134,11 +145,13 @@ class Dispatch: game.game, game.version, ) - raise UnrecognizedPCBIDException(pcbid, modelstring, config.client.address) + raise UnrecognizedPCBIDException( + pcbid, modelstring, config.client.address + ) # First, try to handle with specific service/method function try: - handler = getattr(game, f'handle_{request.name}_{method}_request') + handler = getattr(game, f"handle_{request.name}_{method}_request") except AttributeError: handler = None if handler is not None: @@ -147,7 +160,7 @@ class Dispatch: if response is None: # Now, try to pass it off to a generic service handler try: - handler = getattr(game, f'handle_{request.name}_requests') + handler = getattr(game, f"handle_{request.name}_requests") except AttributeError: handler = None if handler is not None: @@ -159,12 +172,12 @@ class Dispatch: return None # Make sure we have a status value if one wasn't provided - if 'status' not in response.attributes: - response.set_attribute('status', str(Status.SUCCESS)) + if "status" not in response.attributes: + response.set_attribute("status", str(Status.SUCCESS)) - root = Node.void('response') + root = Node.void("response") root.add_child(response) - root.set_attribute('dstid', pcbid) + root.set_attribute("dstid", pcbid) self.log("Sending response:\n{}", root) diff --git a/bemani/backend/ess/eventlog.py b/bemani/backend/ess/eventlog.py index b8f83a0..01efafc 100644 --- a/bemani/backend/ess/eventlog.py +++ b/bemani/backend/ess/eventlog.py @@ -11,13 +11,13 @@ class EventLogHandler(Base): def handle_eventlog_write_request(self, request: Node) -> Node: # Just turn off further logging - gamesession = request.child_value('data/gamesession') + gamesession = request.child_value("data/gamesession") if gamesession < 0: gamesession = random.randint(1, 1000000) - root = Node.void('eventlog') - root.add_child(Node.s64('gamesession', gamesession)) - root.add_child(Node.s32('logsendflg', 0)) - root.add_child(Node.s32('logerrlevel', 0)) - root.add_child(Node.s32('evtidnosendflg', 0)) + root = Node.void("eventlog") + root.add_child(Node.s64("gamesession", gamesession)) + root.add_child(Node.s32("logsendflg", 0)) + root.add_child(Node.s32("logerrlevel", 0)) + root.add_child(Node.s32("evtidnosendflg", 0)) return root diff --git a/bemani/backend/iidx/base.py b/bemani/backend/iidx/base.py index f040e18..87df863 100644 --- a/bemani/backend/iidx/base.py +++ b/bemani/backend/iidx/base.py @@ -5,7 +5,14 @@ from typing_extensions import Final from bemani.backend.base import Base from bemani.backend.core import CoreHandler, CardManagerHandler, PASELIHandler -from bemani.common import Profile, ValidatedDict, Model, GameConstants, DBConstants, Parallel +from bemani.common import ( + Profile, + ValidatedDict, + Model, + GameConstants, + DBConstants, + Parallel, +) from bemani.data import Config, Data, Score, Machine, UserID from bemani.protocol import Node @@ -62,8 +69,8 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): DAN_RANK_CHUDEN: Final[int] = DBConstants.IIDX_DAN_RANK_CHUDEN DAN_RANK_KAIDEN: Final[int] = DBConstants.IIDX_DAN_RANK_KAIDEN - DAN_RANKING_SINGLE: Final[str] = 'sgrade' - DAN_RANKING_DOUBLE: Final[str] = 'dgrade' + DAN_RANKING_SINGLE: Final[str] = "sgrade" + DAN_RANKING_DOUBLE: Final[str] = "dgrade" GHOST_TYPE_NONE: Final[int] = 0 GHOST_TYPE_RIVAL: Final[int] = 100 @@ -78,12 +85,12 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): # Return the local2 service so that Copula and above will send certain packets. extra_services: List[str] = [ - 'local2', + "local2", ] def __init__(self, data: Data, config: Config, model: Model) -> None: super().__init__(data, config, model) - if model.rev == 'X': + if model.rev == "X": self.omnimix = True else: self.omnimix = False @@ -94,7 +101,7 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): return DBConstants.OMNIMIX_VERSION_BUMP + self.version return self.version - def previous_version(self) -> Optional['IIDXBase']: + def previous_version(self) -> Optional["IIDXBase"]: """ Returns the previous version of the game, based on this game. Should be overridden. @@ -106,9 +113,11 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): Base handler for a profile. Given a userid and a profile dictionary, return a Node representing a profile. Should be overridden. """ - return Node.void('pc') + return Node.void("pc") - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: """ Base handler for profile parsing. Given a request and an old profile, return a new profile that's been updated with the contents of the request. @@ -136,7 +145,9 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): return None return self.format_profile(userid, profile) - def new_profile_by_refid(self, refid: Optional[str], name: Optional[str], pid: Optional[int]) -> Profile: + def new_profile_by_refid( + self, refid: Optional[str], name: Optional[str], pid: Optional[int] + ) -> Profile: """ Given a RefID and an optional name, create a profile and then return that newly created profile. @@ -145,7 +156,7 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): return None if name is None: - name = 'なし' + name = "なし" if pid is None: pid = self.get_machine_region() @@ -156,10 +167,10 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): refid, 0, { - 'name': name, - 'pid': pid, - 'settings': { - 'flags': 223 # Default to turning on all optional folders + "name": name, + "pid": pid, + "settings": { + "flags": 223 # Default to turning on all optional folders }, }, ) @@ -192,8 +203,8 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): def get_clear_rates( self, - songid: Optional[int]=None, - songchart: Optional[int]=None, + songid: Optional[int] = None, + songchart: Optional[int] = None, ) -> Dict[int, Dict[int, Dict[str, int]]]: """ Returns a dictionary similar to the following: @@ -208,24 +219,26 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): }, } """ - all_attempts, remote_attempts = Parallel.execute([ - lambda: self.data.local.music.get_all_attempts( - game=self.game, - version=self.music_version, - songid=songid, - songchart=songchart, - ), - lambda: self.data.remote.music.get_clear_rates( - game=self.game, - version=self.music_version, - songid=songid, - songchart=songchart, - ), - ]) + all_attempts, remote_attempts = Parallel.execute( + [ + lambda: self.data.local.music.get_all_attempts( + game=self.game, + version=self.music_version, + songid=songid, + songchart=songchart, + ), + lambda: self.data.remote.music.get_clear_rates( + game=self.game, + version=self.music_version, + songid=songid, + songchart=songchart, + ), + ] + ) attempts: Dict[int, Dict[int, Dict[str, int]]] = {} for (_, attempt) in all_attempts: - if attempt.data.get_int('clear_status') == self.CLEAR_STATUS_NO_PLAY: + if attempt.data.get_int("clear_status") == self.CLEAR_STATUS_NO_PLAY: # This attempt was outside of the clear infra, so don't bother with it. continue @@ -234,24 +247,33 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): attempts[attempt.id] = {} if attempt.chart not in attempts[attempt.id]: attempts[attempt.id][attempt.chart] = { - 'total': 0, - 'clears': 0, - 'fcs': 0, + "total": 0, + "clears": 0, + "fcs": 0, } # We saw an attempt, keep the total attempts in sync. - attempts[attempt.id][attempt.chart]['total'] = attempts[attempt.id][attempt.chart]['total'] + 1 + attempts[attempt.id][attempt.chart]["total"] = ( + attempts[attempt.id][attempt.chart]["total"] + 1 + ) - if attempt.data.get_int('clear_status', self.CLEAR_STATUS_FAILED) == self.CLEAR_STATUS_FAILED: + if ( + attempt.data.get_int("clear_status", self.CLEAR_STATUS_FAILED) + == self.CLEAR_STATUS_FAILED + ): # This attempt was a failure, so don't count it against clears of full combos continue # It was at least a clear - attempts[attempt.id][attempt.chart]['clears'] = attempts[attempt.id][attempt.chart]['clears'] + 1 + attempts[attempt.id][attempt.chart]["clears"] = ( + attempts[attempt.id][attempt.chart]["clears"] + 1 + ) - if attempt.data.get_int('clear_status') == self.CLEAR_STATUS_FULL_COMBO: + if attempt.data.get_int("clear_status") == self.CLEAR_STATUS_FULL_COMBO: # This was a full combo clear, so it also counts here - attempts[attempt.id][attempt.chart]['fcs'] = attempts[attempt.id][attempt.chart]['fcs'] + 1 + attempts[attempt.id][attempt.chart]["fcs"] = ( + attempts[attempt.id][attempt.chart]["fcs"] + 1 + ) # Merge in remote attempts for songid in remote_attempts: @@ -261,14 +283,20 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): for songchart in remote_attempts[songid]: if songchart not in attempts[songid]: attempts[songid][songchart] = { - 'total': 0, - 'clears': 0, - 'fcs': 0, + "total": 0, + "clears": 0, + "fcs": 0, } - attempts[songid][songchart]['total'] += remote_attempts[songid][songchart]['plays'] - attempts[songid][songchart]['clears'] += remote_attempts[songid][songchart]['clears'] - attempts[songid][songchart]['fcs'] += remote_attempts[songid][songchart]['combos'] + attempts[songid][songchart]["total"] += remote_attempts[songid][ + songchart + ]["plays"] + attempts[songid][songchart]["clears"] += remote_attempts[songid][ + songchart + ]["clears"] + attempts[songid][songchart]["fcs"] += remote_attempts[songid][ + songchart + ]["combos"] # If requesting a specific song/chart, make sure its in the dict if songid is not None: @@ -278,9 +306,9 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): if songchart is not None: if songchart not in attempts[songid]: attempts[songid][songchart] = { - 'total': 0, - 'clears': 0, - 'fcs': 0, + "total": 0, + "clears": 0, + "fcs": 0, } return attempts @@ -339,26 +367,30 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): oldscore = None # Score history is verbatum, instead of highest score - history = ValidatedDict({ - 'clear_status': clear_status, - 'miss_count': miss_count, - }) + history = ValidatedDict( + { + "clear_status": clear_status, + "miss_count": miss_count, + } + ) old_ex_score = ex_score if ghost is not None: - history['ghost'] = ghost + history["ghost"] = ghost if oldscore is None: # If it is a new score, create a new dictionary to add to - scoredata = ValidatedDict({ - 'clear_status': clear_status, - 'pgreats': pgreats, - 'greats': greats, - }) + scoredata = ValidatedDict( + { + "clear_status": clear_status, + "pgreats": pgreats, + "greats": greats, + } + ) if miss_count != -1: - scoredata.replace_int('miss_count', miss_count) + scoredata.replace_int("miss_count", miss_count) if ghost is not None: - scoredata['ghost'] = ghost + scoredata["ghost"] = ghost raised = True highscore = True else: @@ -367,21 +399,25 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): highscore = ex_score >= oldscore.points ex_score = max(ex_score, oldscore.points) scoredata = oldscore.data - scoredata.replace_int('clear_status', max(scoredata.get_int('clear_status'), clear_status)) + scoredata.replace_int( + "clear_status", max(scoredata.get_int("clear_status"), clear_status) + ) if miss_count != -1: - if scoredata.get_int('miss_count', -1) == -1: - scoredata.replace_int('miss_count', miss_count) + if scoredata.get_int("miss_count", -1) == -1: + scoredata.replace_int("miss_count", miss_count) else: - scoredata.replace_int('miss_count', min(scoredata.get_int('miss_count'), miss_count)) + scoredata.replace_int( + "miss_count", min(scoredata.get_int("miss_count"), miss_count) + ) if raised: - scoredata.replace_int('pgreats', pgreats) - scoredata.replace_int('greats', greats) + scoredata.replace_int("pgreats", pgreats) + scoredata.replace_int("greats", greats) if ghost is not None: - scoredata.replace_bytes('ghost', ghost) + scoredata.replace_bytes("ghost", ghost) if shop is not None: - history.replace_int('shop', shop) - scoredata.replace_int('shop', shop) + history.replace_int("shop", shop) + scoredata.replace_int("shop", shop) # Look up where this score was earned lid = self.get_machine_id() @@ -477,42 +513,41 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): ) if dan_score is None: dan_score = ValidatedDict() - dan_score.replace_int('percent', max(percent, dan_score.get_int('percent'))) - dan_score.replace_int('stages_cleared', max(stages_cleared, dan_score.get_int('stages_cleared'))) + dan_score.replace_int("percent", max(percent, dan_score.get_int("percent"))) + dan_score.replace_int( + "stages_cleared", max(stages_cleared, dan_score.get_int("stages_cleared")) + ) self.data.local.user.put_achievement( - self.game, - self.version, - userid, - rank, - dantype, - dan_score + self.game, self.version, userid, rank, dantype, dan_score ) def db_to_game_status(self, db_status: int) -> int: """ Given a DB status, translate to a game clear status. """ - raise Exception('Implement in specific game class!') + raise Exception("Implement in specific game class!") def game_to_db_status(self, game_status: int) -> int: """ Given a game clear status, translate to DB status. """ - raise Exception('Implement in specific game class!') + raise Exception("Implement in specific game class!") def game_to_db_chart(self, game_chart: int) -> int: """ Given a game's chart for a song, return the chart as defined above. """ - raise Exception('Implement in sub-class!') + raise Exception("Implement in sub-class!") def db_to_game_chart(self, db_chart: int) -> int: """ Given a chart as defined above, return the game's chart constant. """ - raise Exception('Implement in sub-class!') + raise Exception("Implement in sub-class!") - def make_score_struct(self, scores: List[Score], cltype: int, index: int) -> List[List[int]]: + def make_score_struct( + self, scores: List[Score], cltype: int, index: int + ) -> List[List[int]]: scorestruct: Dict[int, List[int]] = {} for score in scores: @@ -560,9 +595,11 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): -1, # Miss count another, ] - scorestruct[musicid][chartindex + 2] = self.db_to_game_status(score.data.get_int('clear_status')) + scorestruct[musicid][chartindex + 2] = self.db_to_game_status( + score.data.get_int("clear_status") + ) scorestruct[musicid][chartindex + 5] = score.points - scorestruct[musicid][chartindex + 8] = score.data.get_int('miss_count', -1) + scorestruct[musicid][chartindex + 8] = score.data.get_int("miss_count", -1) return [scorestruct[s] for s in scorestruct] @@ -577,10 +614,12 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): if chart != self.CHART_TYPE_B7: continue - scorelist.append([ - musicid, - self.db_to_game_status(score.data.get_int('clear_status')), - ]) + scorelist.append( + [ + musicid, + self.db_to_game_status(score.data.get_int("clear_status")), + ] + ) return scorelist @@ -597,7 +636,7 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): # Sum up for each bucket for score in scores: - ghost = score.data.get_bytes('ghost') + ghost = score.data.get_bytes("ghost") for i in range(len(ghost)): total_ghost[i] = total_ghost[i] + ghost[i] count = count + 1 @@ -624,16 +663,16 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): delta_ghost = [total_ghost[i] - reference_ghost[i] for i in range(ghost_length)] # Return averages - return new_ex_score, struct.pack('b' * ghost_length, *delta_ghost) + return new_ex_score, struct.pack("b" * ghost_length, *delta_ghost) def user_joined_arcade(self, machine: Machine, profile: Optional[Profile]) -> bool: if profile is None: return False - if 'shop_location' not in profile: + if "shop_location" not in profile: return False - machineid = profile.get_int('shop_location') + machineid = profile.get_int("shop_location") if machineid == machine.id: # We can short-circuit arcade lookup because their machine # is the current machine. @@ -659,30 +698,39 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): if ghost_type == self.GHOST_TYPE_RIVAL: rival_extid = int(parameter) - rival_userid = self.data.remote.user.from_extid(self.game, self.version, rival_extid) + rival_userid = self.data.remote.user.from_extid( + self.game, self.version, rival_extid + ) if rival_userid is not None: rival_profile = self.get_profile(rival_userid) - rival_score = self.data.remote.music.get_score(self.game, self.music_version, rival_userid, musicid, chart) + rival_score = self.data.remote.music.get_score( + self.game, self.music_version, rival_userid, musicid, chart + ) if rival_score is not None and rival_profile is not None: ghost_score = { - 'score': rival_score.points, - 'ghost': rival_score.data.get_bytes('ghost'), - 'name': rival_profile.get_str('name'), - 'pid': rival_profile.get_int('pid'), + "score": rival_score.points, + "ghost": rival_score.data.get_bytes("ghost"), + "name": rival_profile.get_str("name"), + "pid": rival_profile.get_int("pid"), } if ( - ghost_type == self.GHOST_TYPE_GLOBAL_TOP or - ghost_type == self.GHOST_TYPE_LOCAL_TOP or - ghost_type == self.GHOST_TYPE_GLOBAL_AVERAGE or - ghost_type == self.GHOST_TYPE_LOCAL_AVERAGE + ghost_type == self.GHOST_TYPE_GLOBAL_TOP + or ghost_type == self.GHOST_TYPE_LOCAL_TOP + or ghost_type == self.GHOST_TYPE_GLOBAL_AVERAGE + or ghost_type == self.GHOST_TYPE_LOCAL_AVERAGE ): if ( - ghost_type == self.GHOST_TYPE_LOCAL_TOP or - ghost_type == self.GHOST_TYPE_LOCAL_AVERAGE + ghost_type == self.GHOST_TYPE_LOCAL_TOP + or ghost_type == self.GHOST_TYPE_LOCAL_AVERAGE ): all_scores = sorted( - self.data.local.music.get_all_scores(game=self.game, version=self.music_version, songid=musicid, songchart=chart), + self.data.local.music.get_all_scores( + game=self.game, + version=self.music_version, + songid=musicid, + songchart=chart, + ), key=lambda s: s[1].points, reverse=True, ) @@ -698,30 +746,38 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): 0, ) - if 'shop_location' in my_profile: - shop_id = my_profile.get_int('shop_location') + if "shop_location" in my_profile: + shop_id = my_profile.get_int("shop_location") machine = self.get_machine_by_id(shop_id) else: machine = None if machine is not None: all_scores = [ - score for score in all_scores - if self.user_joined_arcade(machine, self.get_any_profile(score[0])) + score + for score in all_scores + if self.user_joined_arcade( + machine, self.get_any_profile(score[0]) + ) ] else: # Not joined an arcade, so nobody matches our scores all_scores = [] else: all_scores = sorted( - self.data.remote.music.get_all_scores(game=self.game, version=self.music_version, songid=musicid, songchart=chart), + self.data.remote.music.get_all_scores( + game=self.game, + version=self.music_version, + songid=musicid, + songchart=chart, + ), key=lambda s: s[1].points, reverse=True, ) if ( - ghost_type == self.GHOST_TYPE_GLOBAL_TOP or - ghost_type == self.GHOST_TYPE_LOCAL_TOP + ghost_type == self.GHOST_TYPE_GLOBAL_TOP + or ghost_type == self.GHOST_TYPE_LOCAL_TOP ): for potential_top in all_scores: top_userid = potential_top[0] @@ -729,28 +785,30 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): top_profile = self.get_any_profile(top_userid) if top_profile is not None: ghost_score = { - 'score': top_score.points, - 'ghost': top_score.data.get_bytes('ghost'), - 'name': top_profile.get_str('name'), - 'pid': top_profile.get_int('pid'), - 'extid': top_profile.extid, + "score": top_score.points, + "ghost": top_score.data.get_bytes("ghost"), + "name": top_profile.get_str("name"), + "pid": top_profile.get_int("pid"), + "extid": top_profile.extid, } break if ( - ghost_type == self.GHOST_TYPE_GLOBAL_AVERAGE or - ghost_type == self.GHOST_TYPE_LOCAL_AVERAGE + ghost_type == self.GHOST_TYPE_GLOBAL_AVERAGE + or ghost_type == self.GHOST_TYPE_LOCAL_AVERAGE ): - average_score, delta_ghost = self.delta_score([score[1] for score in all_scores], ghost_length) + average_score, delta_ghost = self.delta_score( + [score[1] for score in all_scores], ghost_length + ) if average_score is not None and delta_ghost is not None: ghost_score = { - 'score': average_score, - 'ghost': bytes([0] * ghost_length), + "score": average_score, + "ghost": bytes([0] * ghost_length), } if ( - ghost_type == self.GHOST_TYPE_DAN_TOP or - ghost_type == self.GHOST_TYPE_DAN_AVERAGE + ghost_type == self.GHOST_TYPE_DAN_TOP + or ghost_type == self.GHOST_TYPE_DAN_AVERAGE ): is_dp = chart not in [ self.CHART_TYPE_N7, @@ -772,18 +830,28 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): if dan_rank != -1: all_scores = sorted( - self.data.local.music.get_all_scores(game=self.game, version=self.music_version, songid=musicid, songchart=chart), + self.data.local.music.get_all_scores( + game=self.game, + version=self.music_version, + songid=musicid, + songchart=chart, + ), key=lambda s: s[1].points, reverse=True, ) - all_profiles = self.data.local.user.get_all_profiles(self.game, self.version) + all_profiles = self.data.local.user.get_all_profiles( + self.game, self.version + ) relevant_userids = { - profile[0] for profile in all_profiles - if profile[1].get_int(self.DAN_RANKING_DOUBLE if is_dp else self.DAN_RANKING_SINGLE) == dan_rank + profile[0] + for profile in all_profiles + if profile[1].get_int( + self.DAN_RANKING_DOUBLE if is_dp else self.DAN_RANKING_SINGLE + ) + == dan_rank } relevant_scores = [ - score for score in all_scores - if score[0] in relevant_userids + score for score in all_scores if score[0] in relevant_userids ] if ghost_type == self.GHOST_TYPE_DAN_TOP: for potential_top in relevant_scores: @@ -792,27 +860,29 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): top_profile = self.get_any_profile(top_userid) if top_profile is not None: ghost_score = { - 'score': top_score.points, - 'ghost': top_score.data.get_bytes('ghost'), - 'name': top_profile.get_str('name'), - 'pid': top_profile.get_int('pid'), - 'extid': top_profile.extid, + "score": top_score.points, + "ghost": top_score.data.get_bytes("ghost"), + "name": top_profile.get_str("name"), + "pid": top_profile.get_int("pid"), + "extid": top_profile.extid, } break if ghost_type == self.GHOST_TYPE_DAN_AVERAGE: - average_score, delta_ghost = self.delta_score([score[1] for score in relevant_scores], ghost_length) + average_score, delta_ghost = self.delta_score( + [score[1] for score in relevant_scores], ghost_length + ) if average_score is not None and delta_ghost is not None: ghost_score = { - 'score': average_score, - 'ghost': bytes([0] * ghost_length), + "score": average_score, + "ghost": bytes([0] * ghost_length), } if ( - ghost_type == self.GHOST_TYPE_RIVAL_TOP or - ghost_type == self.GHOST_TYPE_RIVAL_AVERAGE + ghost_type == self.GHOST_TYPE_RIVAL_TOP + or ghost_type == self.GHOST_TYPE_RIVAL_AVERAGE ): - rival_extids = [int(e[1:-1]) for e in parameter.split(',')] + rival_extids = [int(e[1:-1]) for e in parameter.split(",")] rival_userids = [ self.data.remote.user.from_extid(self.game, self.version, rival_extid) for rival_extid in rival_extids @@ -820,8 +890,13 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): all_scores = sorted( [ - score for score in - self.data.remote.music.get_all_scores(game=self.game, version=self.music_version, songid=musicid, songchart=chart) + score + for score in self.data.remote.music.get_all_scores( + game=self.game, + version=self.music_version, + songid=musicid, + songchart=chart, + ) if score[0] in rival_userids ], key=lambda s: s[1].points, @@ -834,20 +909,22 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): top_profile = self.get_any_profile(top_userid) if top_profile is not None: ghost_score = { - 'score': top_score.points, - 'ghost': top_score.data.get_bytes('ghost'), - 'name': top_profile.get_str('name'), - 'pid': top_profile.get_int('pid'), - 'extid': top_profile.extid, + "score": top_score.points, + "ghost": top_score.data.get_bytes("ghost"), + "name": top_profile.get_str("name"), + "pid": top_profile.get_int("pid"), + "extid": top_profile.extid, } break if ghost_type == self.GHOST_TYPE_RIVAL_AVERAGE: - average_score, delta_ghost = self.delta_score([score[1] for score in all_scores], ghost_length) + average_score, delta_ghost = self.delta_score( + [score[1] for score in all_scores], ghost_length + ) if average_score is not None and delta_ghost is not None: ghost_score = { - 'score': average_score, - 'ghost': bytes([0] * ghost_length), + "score": average_score, + "ghost": bytes([0] * ghost_length), } return ghost_score diff --git a/bemani/backend/iidx/bistrover.py b/bemani/backend/iidx/bistrover.py index 98edd6f..6886080 100644 --- a/bemani/backend/iidx/bistrover.py +++ b/bemani/backend/iidx/bistrover.py @@ -8,7 +8,7 @@ from bemani.common import VersionConstants class IIDXBistrover(IIDXBase): - name: str = 'Beatmania IIDX BISTROVER' + name: str = "Beatmania IIDX BISTROVER" version: int = VersionConstants.IIDX_BISTROVER requires_extended_regions: bool = True diff --git a/bemani/backend/iidx/cannonballers.py b/bemani/backend/iidx/cannonballers.py index 9f7e978..880bf2a 100644 --- a/bemani/backend/iidx/cannonballers.py +++ b/bemani/backend/iidx/cannonballers.py @@ -8,14 +8,22 @@ from bemani.backend.iidx.base import IIDXBase from bemani.backend.iidx.course import IIDXCourse from bemani.backend.iidx.sinobuz import IIDXSinobuz -from bemani.common import Profile, ValidatedDict, VersionConstants, BroadcastConstants, Time, ID, intish +from bemani.common import ( + Profile, + ValidatedDict, + VersionConstants, + BroadcastConstants, + Time, + ID, + intish, +) from bemani.data import Data, UserID from bemani.protocol import Node class IIDXCannonBallers(IIDXCourse, IIDXBase): - name: str = 'Beatmania IIDX CANNON BALLERS' + name: str = "Beatmania IIDX CANNON BALLERS" version: int = VersionConstants.IIDX_CANNON_BALLERS GAME_CLTYPE_SINGLE: Final[int] = 0 @@ -100,37 +108,54 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): return IIDXSinobuz(self.data, self.config, self.model) @classmethod - def run_scheduled_work(cls, data: Data, config: Dict[str, Any]) -> List[Tuple[str, Dict[str, Any]]]: + def run_scheduled_work( + cls, data: Data, config: Dict[str, Any] + ) -> List[Tuple[str, Dict[str, Any]]]: """ Insert dailies into the DB. """ events = [] - if data.local.network.should_schedule(cls.game, cls.version, 'daily_charts', 'daily'): + if data.local.network.should_schedule( + cls.game, cls.version, "daily_charts", "daily" + ): # Generate a new list of three dailies. - start_time, end_time = data.local.network.get_schedule_duration('daily') - all_songs = list(set([song.id for song in data.local.music.get_all_songs(cls.game, cls.version)])) + start_time, end_time = data.local.network.get_schedule_duration("daily") + all_songs = list( + set( + [ + song.id + for song in data.local.music.get_all_songs( + cls.game, cls.version + ) + ] + ) + ) if len(all_songs) >= 3: daily_songs = random.sample(all_songs, 3) data.local.game.put_time_sensitive_settings( cls.game, cls.version, - 'dailies', + "dailies", { - 'start_time': start_time, - 'end_time': end_time, - 'music': daily_songs, + "start_time": start_time, + "end_time": end_time, + "music": daily_songs, }, ) - events.append(( - 'iidx_daily_charts', - { - 'version': cls.version, - 'music': daily_songs, - }, - )) + events.append( + ( + "iidx_daily_charts", + { + "version": cls.version, + "music": daily_songs, + }, + ) + ) # Mark that we did some actual work here. - data.local.network.mark_scheduled(cls.game, cls.version, 'daily_charts', 'daily') + data.local.network.mark_scheduled( + cls.game, cls.version, "daily_charts", "daily" + ) return events @classmethod @@ -139,33 +164,33 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): Return all of our front-end modifiably settings. """ return { - 'bools': [ + "bools": [ { - 'name': 'Global Shop Ranking', - 'tip': 'Return network-wide ranking instead of shop ranking on results screen.', - 'category': 'game_config', - 'setting': 'global_shop_ranking', + "name": "Global Shop Ranking", + "tip": "Return network-wide ranking instead of shop ranking on results screen.", + "category": "game_config", + "setting": "global_shop_ranking", }, { - 'name': 'Events In Omnimix', - 'tip': 'Allow events to be enabled at all for Omnimix.', - 'category': 'game_config', - 'setting': 'omnimix_events_enabled', + "name": "Events In Omnimix", + "tip": "Allow events to be enabled at all for Omnimix.", + "category": "game_config", + "setting": "omnimix_events_enabled", }, ], - 'ints': [ + "ints": [ { - 'name': 'Event Phase', - 'tip': 'Event phase for all players.', - 'category': 'game_config', - 'setting': 'event_phase', - 'values': { - 0: 'No Event', - 1: 'Gekisou! Cannon racer Continent 1', - 2: 'Gekisou! Cannon racer Continent 2', - 3: 'Gekisou! Cannon racer Continent 3', - 4: 'Gekisou! Cannon racer Continent 4', - } + "name": "Event Phase", + "tip": "Event phase for all players.", + "category": "game_config", + "setting": "event_phase", + "values": { + 0: "No Event", + 1: "Gekisou! Cannon racer Continent 1", + 2: "Gekisou! Cannon racer Continent 2", + 3: "Gekisou! Cannon racer Continent 3", + 4: "Gekisou! Cannon racer Continent 4", + }, }, ], } @@ -244,7 +269,7 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): self.DAN_RANK_KAIDEN: self.GAME_DP_DAN_RANK_KAIDEN, }[db_dan] else: - raise Exception('Invalid cltype!') + raise Exception("Invalid cltype!") def game_to_db_rank(self, game_dan: int, cltype: int) -> int: # Special case for no DAN rank @@ -296,7 +321,7 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): self.GAME_DP_DAN_RANK_KAIDEN: self.DAN_RANK_KAIDEN, }[game_dan] else: - raise Exception('Invalid cltype!') + raise Exception("Invalid cltype!") def game_to_db_chart(self, db_chart: int) -> int: return { @@ -313,80 +338,86 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): machine = self.data.local.machine.get_machine(self.config.machine.pcbid) if machine is not None: machine_name = machine.name - close = machine.data.get_bool('close') - hour = machine.data.get_int('hour') - minute = machine.data.get_int('minute') + close = machine.data.get_bool("close") + hour = machine.data.get_int("hour") + minute = machine.data.get_int("minute") else: - machine_name = '' + machine_name = "" close = False hour = 0 minute = 0 - root = Node.void('IIDX25shop') - root.set_attribute('opname', machine_name) - root.set_attribute('pid', str(self.get_machine_region())) - root.set_attribute('cls_opt', '1' if close else '0') - root.set_attribute('hr', str(hour)) - root.set_attribute('mi', str(minute)) + root = Node.void("IIDX25shop") + root.set_attribute("opname", machine_name) + root.set_attribute("pid", str(self.get_machine_region())) + root.set_attribute("cls_opt", "1" if close else "0") + root.set_attribute("hr", str(hour)) + root.set_attribute("mi", str(minute)) return root def handle_IIDX25shop_savename_request(self, request: Node) -> Node: - self.update_machine_name(request.attribute('opname')) + self.update_machine_name(request.attribute("opname")) - shop_close = intish(request.attribute('cls_opt')) or 0 - minutes = intish(request.attribute('mnt')) or 0 - hours = intish(request.attribute('hr')) or 0 + shop_close = intish(request.attribute("cls_opt")) or 0 + minutes = intish(request.attribute("mnt")) or 0 + hours = intish(request.attribute("hr")) or 0 - self.update_machine_data({ - 'close': shop_close != 0, - 'minutes': minutes, - 'hours': hours, - }) + self.update_machine_data( + { + "close": shop_close != 0, + "minutes": minutes, + "hours": hours, + } + ) - return Node.void('IIDX25shop') + return Node.void("IIDX25shop") def handle_IIDX25shop_sentinfo_request(self, request: Node) -> Node: - return Node.void('IIDX25shop') + return Node.void("IIDX25shop") def handle_IIDX25shop_sendescapepackageinfo_request(self, request: Node) -> Node: - root = Node.void('IIDX25shop') - root.set_attribute('expire', str((Time.now() + 86400 * 365) * 1000)) + root = Node.void("IIDX25shop") + root.set_attribute("expire", str((Time.now() + 86400 * 365) * 1000)) return root def handle_IIDX25shop_getconvention_request(self, request: Node) -> Node: - root = Node.void('IIDX25shop') + root = Node.void("IIDX25shop") machine = self.data.local.machine.get_machine(self.config.machine.pcbid) if machine.arcade is not None: - course = self.data.local.machine.get_settings(machine.arcade, self.game, self.music_version, 'shop_course') + course = self.data.local.machine.get_settings( + machine.arcade, self.game, self.music_version, "shop_course" + ) else: course = None if course is None: course = ValidatedDict() - root.set_attribute('music_0', str(course.get_int('music_0', 20032))) - root.set_attribute('music_1', str(course.get_int('music_1', 20009))) - root.set_attribute('music_2', str(course.get_int('music_2', 20015))) - root.set_attribute('music_3', str(course.get_int('music_3', 20064))) - root.add_child(Node.bool('valid', course.get_bool('valid'))) + root.set_attribute("music_0", str(course.get_int("music_0", 20032))) + root.set_attribute("music_1", str(course.get_int("music_1", 20009))) + root.set_attribute("music_2", str(course.get_int("music_2", 20015))) + root.set_attribute("music_3", str(course.get_int("music_3", 20064))) + root.add_child(Node.bool("valid", course.get_bool("valid"))) return root def handle_IIDX25shop_setconvention_request(self, request: Node) -> Node: machine = self.data.local.machine.get_machine(self.config.machine.pcbid) if machine.arcade is not None: course = ValidatedDict() - course.replace_int('music_0', request.child_value('music_0')) - course.replace_int('music_1', request.child_value('music_1')) - course.replace_int('music_2', request.child_value('music_2')) - course.replace_int('music_3', request.child_value('music_3')) - course.replace_bool('valid', request.child_value('valid')) - self.data.local.machine.put_settings(machine.arcade, self.game, self.music_version, 'shop_course', course) + course.replace_int("music_0", request.child_value("music_0")) + course.replace_int("music_1", request.child_value("music_1")) + course.replace_int("music_2", request.child_value("music_2")) + course.replace_int("music_3", request.child_value("music_3")) + course.replace_bool("valid", request.child_value("valid")) + self.data.local.machine.put_settings( + machine.arcade, self.game, self.music_version, "shop_course", course + ) - return Node.void('IIDX25shop') + return Node.void("IIDX25shop") def handle_IIDX25ranking_getranker_request(self, request: Node) -> Node: - root = Node.void('IIDX25ranking') - chart = self.game_to_db_chart(int(request.attribute('clid'))) + root = Node.void("IIDX25ranking") + chart = self.game_to_db_chart(int(request.attribute("clid"))) if chart not in [ self.CHART_TYPE_N7, self.CHART_TYPE_H7, @@ -400,29 +431,31 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): machine = self.data.local.machine.get_machine(self.config.machine.pcbid) if machine.arcade is not None: - course = self.data.local.machine.get_settings(machine.arcade, self.game, self.music_version, 'shop_course') + course = self.data.local.machine.get_settings( + machine.arcade, self.game, self.music_version, "shop_course" + ) else: course = None if course is None: course = ValidatedDict() - if not course.get_bool('valid'): + if not course.get_bool("valid"): # Shop course not enabled or not present return root - convention = Node.void('convention') + convention = Node.void("convention") root.add_child(convention) - convention.set_attribute('clid', str(chart)) - convention.set_attribute('update_date', str(Time.now() * 1000)) + convention.set_attribute("clid", str(chart)) + convention.set_attribute("update_date", str(Time.now() * 1000)) # Grab all scores for each of the four songs, filter all scores not achieved # on this machine and then return the top 20 scores (adding all 4 songs). songids = [ - course.get_int('music_0'), - course.get_int('music_1'), - course.get_int('music_2'), - course.get_int('music_3'), + course.get_int("music_0"), + course.get_int("music_1"), + course.get_int("music_2"), + course.get_int("music_3"), ] totalscores: Dict[UserID, int] = {} @@ -449,10 +482,7 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): totalscores[score[0]] += score[1].points topscores = sorted( - [ - (totalscores[userid], profiles[userid]) - for userid in totalscores - ], + [(totalscores[userid], profiles[userid]) for userid in totalscores], key=lambda tup: tup[0], reverse=True, )[:20] @@ -461,37 +491,37 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): for topscore in topscores: rank = rank + 1 - detail = Node.void('detail') + detail = Node.void("detail") convention.add_child(detail) - detail.set_attribute('name', topscore[1].get_str('name')) - detail.set_attribute('rank', str(rank)) - detail.set_attribute('score', str(topscore[0])) - detail.set_attribute('pid', str(topscore[1].get_int('pid'))) + detail.set_attribute("name", topscore[1].get_str("name")) + detail.set_attribute("rank", str(rank)) + detail.set_attribute("score", str(topscore[0])) + detail.set_attribute("pid", str(topscore[1].get_int("pid"))) - qpro = topscore[1].get_dict('qpro') - detail.set_attribute('head', str(qpro.get_int('head'))) - detail.set_attribute('hair', str(qpro.get_int('hair'))) - detail.set_attribute('face', str(qpro.get_int('face'))) - detail.set_attribute('body', str(qpro.get_int('body'))) - detail.set_attribute('hand', str(qpro.get_int('hand'))) + qpro = topscore[1].get_dict("qpro") + detail.set_attribute("head", str(qpro.get_int("head"))) + detail.set_attribute("hair", str(qpro.get_int("hair"))) + detail.set_attribute("face", str(qpro.get_int("face"))) + detail.set_attribute("body", str(qpro.get_int("body"))) + detail.set_attribute("hand", str(qpro.get_int("hand"))) return root def handle_IIDX25ranking_entry_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) - courseid = int(request.attribute('coid')) - chart = self.game_to_db_chart(int(request.attribute('clid'))) - course_type = int(request.attribute('regist_type')) - clear_status = self.game_to_db_status(int(request.attribute('clr'))) - pgreats = int(request.attribute('pgnum')) - greats = int(request.attribute('gnum')) + extid = int(request.attribute("iidxid")) + courseid = int(request.attribute("coid")) + chart = self.game_to_db_chart(int(request.attribute("clid"))) + course_type = int(request.attribute("regist_type")) + clear_status = self.game_to_db_status(int(request.attribute("clr"))) + pgreats = int(request.attribute("pgnum")) + greats = int(request.attribute("gnum")) if course_type == 0: index = self.COURSE_TYPE_INTERNET_RANKING elif course_type == 1: index = self.COURSE_TYPE_SECRET else: - raise Exception('Unknown registration type for course entry!') + raise Exception("Unknown registration type for course entry!") userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: @@ -508,18 +538,18 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): # We should return the user's position, but its not displayed anywhere # so fuck it. - root = Node.void('IIDX25ranking') - root.set_attribute('anum', '1') - root.set_attribute('jun', '1') + root = Node.void("IIDX25ranking") + root.set_attribute("anum", "1") + root.set_attribute("jun", "1") return root def handle_IIDX25ranking_classicentry_request(self, request: Node) -> Node: - extid = int(request.attribute('iidx_id')) - courseid = int(request.attribute('course_id')) - coursestyle = int(request.attribute('play_style')) - clear_status = self.game_to_db_status(int(request.attribute('clear_flg'))) - pgreats = int(request.attribute('pgnum')) - greats = int(request.attribute('gnum')) + extid = int(request.attribute("iidx_id")) + courseid = int(request.attribute("course_id")) + coursestyle = int(request.attribute("play_style")) + clear_status = self.game_to_db_status(int(request.attribute("clear_flg"))) + pgreats = int(request.attribute("pgnum")) + greats = int(request.attribute("gnum")) userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: @@ -534,13 +564,22 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): greats, ) - return Node.void('IIDX25ranking') + return Node.void("IIDX25ranking") def handle_IIDX25music_crate_request(self, request: Node) -> Node: - root = Node.void('IIDX25music') + root = Node.void("IIDX25music") attempts = self.get_clear_rates() - all_songs = list(set([song.id for song in self.data.local.music.get_all_songs(self.game, self.music_version)])) + all_songs = list( + set( + [ + song.id + for song in self.data.local.music.get_all_songs( + self.game, self.music_version + ) + ] + ) + ) for song in all_songs: clears = [] fcs = [] @@ -549,33 +588,33 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): placed = False if song in attempts and chart in attempts[song]: values = attempts[song][chart] - if values['total'] > 0: - clears.append(int((1000 * values['clears']) / values['total'])) - fcs.append(int((1000 * values['fcs']) / values['total'])) + if values["total"] > 0: + clears.append(int((1000 * values["clears"]) / values["total"])) + fcs.append(int((1000 * values["fcs"]) / values["total"])) placed = True if not placed: clears.append(1001) fcs.append(1001) - clearnode = Node.s32_array('c', clears + fcs) - clearnode.set_attribute('mid', str(song)) + clearnode = Node.s32_array("c", clears + fcs) + clearnode.set_attribute("mid", str(song)) root.add_child(clearnode) return root def handle_IIDX25music_getrank_request(self, request: Node) -> Node: - cltype = int(request.attribute('cltype')) + cltype = int(request.attribute("cltype")) - root = Node.void('IIDX25music') - style = Node.void('style') + root = Node.void("IIDX25music") + style = Node.void("style") root.add_child(style) - style.set_attribute('type', str(cltype)) + style.set_attribute("type", str(cltype)) for rivalid in [-1, 0, 1, 2, 3, 4]: if rivalid == -1: - attr = 'iidxid' + attr = "iidxid" else: - attr = f'iidxid{rivalid}' + attr = f"iidxid{rivalid}" try: extid = int(request.attribute(attr)) @@ -584,51 +623,59 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): continue userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: - scores = self.data.remote.music.get_scores(self.game, self.music_version, userid) + scores = self.data.remote.music.get_scores( + self.game, self.music_version, userid + ) # Grab score data for user/rival scoredata = self.make_score_struct( scores, - self.CLEAR_TYPE_SINGLE if cltype == self.GAME_CLTYPE_SINGLE else self.CLEAR_TYPE_DOUBLE, + self.CLEAR_TYPE_SINGLE + if cltype == self.GAME_CLTYPE_SINGLE + else self.CLEAR_TYPE_DOUBLE, rivalid, ) for s in scoredata: - root.add_child(Node.s16_array('m', s)) + root.add_child(Node.s16_array("m", s)) # Grab most played for user/rival most_played = [ - play[0] for play in - self.data.local.music.get_most_played(self.game, self.music_version, userid, 20) + play[0] + for play in self.data.local.music.get_most_played( + self.game, self.music_version, userid, 20 + ) ] if len(most_played) < 20: most_played.extend([0] * (20 - len(most_played))) - best = Node.u16_array('best', most_played) - best.set_attribute('rno', str(rivalid)) + best = Node.u16_array("best", most_played) + best.set_attribute("rno", str(rivalid)) root.add_child(best) if rivalid == -1: # Grab beginner statuses for user only beginnerdata = self.make_beginner_struct(scores) for b in beginnerdata: - root.add_child(Node.u16_array('b', b)) + root.add_child(Node.u16_array("b", b)) return root def handle_IIDX25music_appoint_request(self, request: Node) -> Node: - musicid = int(request.attribute('mid')) - chart = self.game_to_db_chart(int(request.attribute('clid'))) - ghost_type = int(request.attribute('ctype')) - extid = int(request.attribute('iidxid')) + musicid = int(request.attribute("mid")) + chart = self.game_to_db_chart(int(request.attribute("clid"))) + ghost_type = int(request.attribute("ctype")) + extid = int(request.attribute("iidxid")) userid = self.data.remote.user.from_extid(self.game, self.version, extid) - root = Node.void('IIDX25music') + root = Node.void("IIDX25music") if userid is not None: # Try to look up previous ghost for user - my_score = self.data.remote.music.get_score(self.game, self.music_version, userid, musicid, chart) + my_score = self.data.remote.music.get_score( + self.game, self.music_version, userid, musicid, chart + ) if my_score is not None: - mydata = Node.binary('mydata', my_score.data.get_bytes('ghost')) - mydata.set_attribute('score', str(my_score.points)) + mydata = Node.binary("mydata", my_score.data.get_bytes("ghost")) + mydata.set_attribute("score", str(my_score.points)) root.add_child(mydata) ghost_score = self.get_ghost( @@ -643,7 +690,7 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): self.GAME_GHOST_TYPE_RIVAL_TOP: self.GHOST_TYPE_RIVAL_TOP, self.GAME_GHOST_TYPE_RIVAL_AVERAGE: self.GHOST_TYPE_RIVAL_AVERAGE, }.get(ghost_type, self.GHOST_TYPE_NONE), - request.attribute('subtype'), + request.attribute("subtype"), self.GAME_GHOST_LENGTH, musicid, chart, @@ -652,27 +699,27 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): # Add ghost score if we support it if ghost_score is not None: - sdata = Node.binary('sdata', ghost_score['ghost']) - sdata.set_attribute('score', str(ghost_score['score'])) - if 'name' in ghost_score: - sdata.set_attribute('name', ghost_score['name']) - if 'pid' in ghost_score: - sdata.set_attribute('pid', str(ghost_score['pid'])) - if 'extid' in ghost_score: - sdata.set_attribute('riidxid', str(ghost_score['extid'])) + sdata = Node.binary("sdata", ghost_score["ghost"]) + sdata.set_attribute("score", str(ghost_score["score"])) + if "name" in ghost_score: + sdata.set_attribute("name", ghost_score["name"]) + if "pid" in ghost_score: + sdata.set_attribute("pid", str(ghost_score["pid"])) + if "extid" in ghost_score: + sdata.set_attribute("riidxid", str(ghost_score["extid"])) root.add_child(sdata) return root def handle_IIDX25music_breg_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) - musicid = int(request.attribute('mid')) + extid = int(request.attribute("iidxid")) + musicid = int(request.attribute("mid")) userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: - clear_status = self.game_to_db_status(int(request.attribute('cflg'))) - pgreats = int(request.attribute('pgnum')) - greats = int(request.attribute('gnum')) + clear_status = self.game_to_db_status(int(request.attribute("cflg"))) + pgreats = int(request.attribute("pgnum")) + greats = int(request.attribute("gnum")) self.update_score( userid, @@ -682,23 +729,23 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): pgreats, greats, -1, - b'', + b"", None, ) # Return nothing. - return Node.void('IIDX25music') + return Node.void("IIDX25music") def handle_IIDX25music_reg_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) - musicid = int(request.attribute('mid')) - chart = self.game_to_db_chart(int(request.attribute('clid'))) + extid = int(request.attribute("iidxid")) + musicid = int(request.attribute("mid")) + chart = self.game_to_db_chart(int(request.attribute("clid"))) userid = self.data.remote.user.from_extid(self.game, self.version, extid) # See if we need to report global or shop scores if self.machine_joined_arcade(): game_config = self.get_game_config() - global_scores = game_config.get_bool('global_shop_ranking') + global_scores = game_config.get_bool("global_shop_ranking") machine = self.data.local.machine.get_machine(self.config.machine.pcbid) else: # If we aren't in an arcade, we can only show global scores @@ -707,23 +754,26 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): # First, determine our current ranking before saving the new score all_scores = sorted( - self.data.remote.music.get_all_scores(game=self.game, version=self.music_version, songid=musicid, songchart=chart), + self.data.remote.music.get_all_scores( + game=self.game, + version=self.music_version, + songid=musicid, + songchart=chart, + ), key=lambda s: (s[1].points, s[1].timestamp), reverse=True, ) all_players = { - uid: prof for (uid, prof) in - self.get_any_profiles([s[0] for s in all_scores]) + uid: prof + for (uid, prof) in self.get_any_profiles([s[0] for s in all_scores]) } - shop_id = ID.parse_machine_id(request.attribute('location_id')) + shop_id = ID.parse_machine_id(request.attribute("location_id")) if not global_scores: all_scores = [ - score for score in all_scores - if ( - score[0] == userid or - score[1].location == shop_id - ) + score + for score in all_scores + if (score[0] == userid or score[1].location == shop_id) ] # Find our actual index @@ -734,12 +784,12 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): break if userid is not None: - clear_status = self.game_to_db_status(int(request.attribute('cflg'))) - pgreats = int(request.attribute('pgnum')) - greats = int(request.attribute('gnum')) - miss_count = int(request.attribute('mnum')) - ghost = request.child_value('ghost') - shopid = ID.parse_machine_id(request.attribute('convid')) + clear_status = self.game_to_db_status(int(request.attribute("cflg"))) + pgreats = int(request.attribute("pgnum")) + greats = int(request.attribute("gnum")) + miss_count = int(request.attribute("mnum")) + ghost = request.child_value("ghost") + shopid = ID.parse_machine_id(request.attribute("convid")) self.update_score( userid, @@ -754,52 +804,54 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): ) # Calculate and return statistics about this song - root = Node.void('IIDX25music') - root.set_attribute('clid', request.attribute('clid')) - root.set_attribute('mid', request.attribute('mid')) + root = Node.void("IIDX25music") + root.set_attribute("clid", request.attribute("clid")) + root.set_attribute("mid", request.attribute("mid")) attempts = self.get_clear_rates(musicid, chart) - count = attempts[musicid][chart]['total'] - clear = attempts[musicid][chart]['clears'] - full_combo = attempts[musicid][chart]['fcs'] + count = attempts[musicid][chart]["total"] + clear = attempts[musicid][chart]["clears"] + full_combo = attempts[musicid][chart]["fcs"] if count > 0: - root.set_attribute('crate', str(int((1000 * clear) / count))) - root.set_attribute('frate', str(int((1000 * full_combo) / count))) + root.set_attribute("crate", str(int((1000 * clear) / count))) + root.set_attribute("frate", str(int((1000 * full_combo) / count))) else: - root.set_attribute('crate', '0') - root.set_attribute('frate', '0') - root.set_attribute('rankside', '0') + root.set_attribute("crate", "0") + root.set_attribute("frate", "0") + root.set_attribute("rankside", "0") if userid is not None: # Shop ranking - shopdata = Node.void('shopdata') + shopdata = Node.void("shopdata") root.add_child(shopdata) - shopdata.set_attribute('rank', '-1' if oldindex is None else str(oldindex + 1)) + shopdata.set_attribute( + "rank", "-1" if oldindex is None else str(oldindex + 1) + ) # Grab the rank of some other players on this song - ranklist = Node.void('ranklist') + ranklist = Node.void("ranklist") root.add_child(ranklist) all_scores = sorted( - self.data.remote.music.get_all_scores(game=self.game, version=self.music_version, songid=musicid, songchart=chart), + self.data.remote.music.get_all_scores( + game=self.game, + version=self.music_version, + songid=musicid, + songchart=chart, + ), key=lambda s: (s[1].points, s[1].timestamp), reverse=True, ) - missing_players = [ - uid for (uid, _) in all_scores - if uid not in all_players - ] + missing_players = [uid for (uid, _) in all_scores if uid not in all_players] for (uid, prof) in self.get_any_profiles(missing_players): all_players[uid] = prof if not global_scores: all_scores = [ - score for score in all_scores - if ( - score[0] == userid or - score[1].location == shop_id - ) + score + for score in all_scores + if (score[0] == userid or score[1].location == shop_id) ] # Find our actual index @@ -809,58 +861,73 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): ourindex = i break if ourindex is None: - raise Exception('Cannot find our own score after saving to DB!') + raise Exception("Cannot find our own score after saving to DB!") start = ourindex - 4 end = ourindex + 4 if start < 0: start = 0 if end >= len(all_scores): end = len(all_scores) - 1 - relevant_scores = all_scores[start:(end + 1)] + relevant_scores = all_scores[start : (end + 1)] record_num = start + 1 for score in relevant_scores: profile = all_players[score[0]] - data = Node.void('data') + data = Node.void("data") ranklist.add_child(data) - data.set_attribute('iidx_id', str(profile.extid)) - data.set_attribute('name', profile.get_str('name')) + data.set_attribute("iidx_id", str(profile.extid)) + data.set_attribute("name", profile.get_str("name")) - machine_name = '' + machine_name = "" machine = self.get_machine_by_id(shop_id) if machine is not None: machine_name = machine.name - data.set_attribute('opname', machine_name) - data.set_attribute('rnum', str(record_num)) - data.set_attribute('score', str(score[1].points)) - data.set_attribute('clflg', str(self.db_to_game_status(score[1].data.get_int('clear_status')))) - data.set_attribute('pid', str(profile.get_int('pid'))) - data.set_attribute('myFlg', '1' if score[0] == userid else '0') - data.set_attribute('update', '0') + data.set_attribute("opname", machine_name) + data.set_attribute("rnum", str(record_num)) + data.set_attribute("score", str(score[1].points)) + data.set_attribute( + "clflg", + str(self.db_to_game_status(score[1].data.get_int("clear_status"))), + ) + data.set_attribute("pid", str(profile.get_int("pid"))) + data.set_attribute("myFlg", "1" if score[0] == userid else "0") + data.set_attribute("update", "0") - data.set_attribute('sgrade', str( - self.db_to_game_rank(profile.get_int(self.DAN_RANKING_SINGLE, -1), self.GAME_CLTYPE_SINGLE), - )) - data.set_attribute('dgrade', str( - self.db_to_game_rank(profile.get_int(self.DAN_RANKING_DOUBLE, -1), self.GAME_CLTYPE_DOUBLE), - )) + data.set_attribute( + "sgrade", + str( + self.db_to_game_rank( + profile.get_int(self.DAN_RANKING_SINGLE, -1), + self.GAME_CLTYPE_SINGLE, + ), + ), + ) + data.set_attribute( + "dgrade", + str( + self.db_to_game_rank( + profile.get_int(self.DAN_RANKING_DOUBLE, -1), + self.GAME_CLTYPE_DOUBLE, + ), + ), + ) - qpro = profile.get_dict('qpro') - data.set_attribute('head', str(qpro.get_int('head'))) - data.set_attribute('hair', str(qpro.get_int('hair'))) - data.set_attribute('face', str(qpro.get_int('face'))) - data.set_attribute('body', str(qpro.get_int('body'))) - data.set_attribute('hand', str(qpro.get_int('hand'))) + qpro = profile.get_dict("qpro") + data.set_attribute("head", str(qpro.get_int("head"))) + data.set_attribute("hair", str(qpro.get_int("hair"))) + data.set_attribute("face", str(qpro.get_int("face"))) + data.set_attribute("body", str(qpro.get_int("body"))) + data.set_attribute("hand", str(qpro.get_int("hand"))) record_num = record_num + 1 return root def handle_IIDX25music_play_request(self, request: Node) -> Node: - musicid = int(request.attribute('mid')) - chart = self.game_to_db_chart(int(request.attribute('clid'))) - clear_status = self.game_to_db_status(int(request.attribute('cflg'))) + musicid = int(request.attribute("mid")) + chart = self.game_to_db_chart(int(request.attribute("clid"))) + clear_status = self.game_to_db_status(int(request.attribute("cflg"))) self.update_score( None, # No userid since its anonymous @@ -875,30 +942,30 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): ) # Calculate and return statistics about this song - root = Node.void('IIDX25music') - root.set_attribute('clid', request.attribute('clid')) - root.set_attribute('mid', request.attribute('mid')) + root = Node.void("IIDX25music") + root.set_attribute("clid", request.attribute("clid")) + root.set_attribute("mid", request.attribute("mid")) attempts = self.get_clear_rates(musicid, chart) - count = attempts[musicid][chart]['total'] - clear = attempts[musicid][chart]['clears'] - full_combo = attempts[musicid][chart]['fcs'] + count = attempts[musicid][chart]["total"] + clear = attempts[musicid][chart]["clears"] + full_combo = attempts[musicid][chart]["fcs"] if count > 0: - root.set_attribute('crate', str(int((1000 * clear) / count))) - root.set_attribute('frate', str(int((1000 * full_combo) / count))) + root.set_attribute("crate", str(int((1000 * clear) / count))) + root.set_attribute("frate", str(int((1000 * full_combo) / count))) else: - root.set_attribute('crate', '0') - root.set_attribute('frate', '0') + root.set_attribute("crate", "0") + root.set_attribute("frate", "0") return root # Bare minimum response to say we handled the request def handle_IIDX25music_beginnerplay_request(self, request: Node) -> Node: - return Node.void('IIDX25music') + return Node.void("IIDX25music") def handle_IIDX25music_arenaCPU_request(self, request: Node) -> Node: - root = Node.void('IIDX25music') + root = Node.void("IIDX25music") music_list = [0, 0, 0, 0] cpu_list = [0, 0, 0, 0] grade_to_percentage_map = { @@ -917,16 +984,20 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): self.GAME_SP_DAN_RANK_KAIDEN: 80, } for item in request.children: - if item.name == 'music_list': - music_list[int(item.child_value('index'))] = int(item.child_value('total_notes')) - if item.name == 'cpu_list': - cpu_list[int(item.child_value('index'))] = int(item.child_value('grade_id')) + if item.name == "music_list": + music_list[int(item.child_value("index"))] = int( + item.child_value("total_notes") + ) + if item.name == "cpu_list": + cpu_list[int(item.child_value("index"))] = int( + item.child_value("grade_id") + ) for index in range(4): - cpu_score_list = Node.void('cpu_score_list') - cpu_score_list.add_child(Node.s32('index', index)) + cpu_score_list = Node.void("cpu_score_list") + cpu_score_list.add_child(Node.s32("index", index)) root.add_child(cpu_score_list) for i in range(4): - score_list = Node.void('score_list') + score_list = Node.void("score_list") cpu_score_list.add_child(score_list) percentage = grade_to_percentage_map.get(cpu_list[index], 0) notes = music_list[i] @@ -936,21 +1007,21 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): ghost_total = ghost_section * 64 ghost_lost = score - ghost_total ghost[0] = ghost[0] + ghost_lost - score_list.add_child(Node.s32('score', score)) - score_list.add_child(Node.bool('enable_score', True)) - score_list.add_child(Node.u8_array('ghost', ghost)) - score_list.add_child(Node.bool('enable_ghost', True)) + score_list.add_child(Node.s32("score", score)) + score_list.add_child(Node.bool("enable_score", True)) + score_list.add_child(Node.u8_array("ghost", ghost)) + score_list.add_child(Node.bool("enable_ghost", True)) return root def handle_IIDX25grade_raised_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) - cltype = int(request.attribute('gtype')) - rank = self.game_to_db_rank(int(request.attribute('gid')), cltype) + extid = int(request.attribute("iidxid")) + cltype = int(request.attribute("gtype")) + rank = self.game_to_db_rank(int(request.attribute("gid")), cltype) userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: - percent = int(request.attribute('achi')) - stages_cleared = int(request.attribute('cstage')) + percent = int(request.attribute("achi")) + stages_cleared = int(request.attribute("cstage")) cleared = stages_cleared == self.DAN_STAGES if cltype == self.GAME_CLTYPE_SINGLE: @@ -968,39 +1039,41 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): ) # Figure out number of players that played this ranking - all_achievements = self.data.local.user.get_all_achievements(self.game, self.version, achievementid=rank, achievementtype=index) - root = Node.void('IIDX25grade') - root.set_attribute('pnum', str(len(all_achievements))) + all_achievements = self.data.local.user.get_all_achievements( + self.game, self.version, achievementid=rank, achievementtype=index + ) + root = Node.void("IIDX25grade") + root.set_attribute("pnum", str(len(all_achievements))) return root def handle_IIDX25pc_common_request(self, request: Node) -> Node: - root = Node.void('IIDX25pc') - root.set_attribute('expire', '600') + root = Node.void("IIDX25pc") + root.set_attribute("expire", "600") - ir = Node.void('ir') + ir = Node.void("ir") root.add_child(ir) - ir.set_attribute('beat', '2') + ir.set_attribute("beat", "2") - vip_black_pass = Node.void('vip_pass_black') + vip_black_pass = Node.void("vip_pass_black") root.add_child(vip_black_pass) - newsong_another = Node.void('newsong_another') + newsong_another = Node.void("newsong_another") root.add_child(newsong_another) - newsong_another.set_attribute('open', '1') + newsong_another.set_attribute("open", "1") - deller_bonus = Node.void('deller_bonus') + deller_bonus = Node.void("deller_bonus") root.add_child(deller_bonus) - deller_bonus.set_attribute('open', '1') + deller_bonus.set_attribute("open", "1") - common_evnet = Node.void('common_evnet') # Yes, this is misspelled in the game + common_evnet = Node.void("common_evnet") # Yes, this is misspelled in the game root.add_child(common_evnet) - common_evnet.set_attribute('flg', '0') + common_evnet.set_attribute("flg", "0") # See if we configured event overrides if self.machine_joined_arcade(): game_config = self.get_game_config() - event_phase = game_config.get_int('event_phase') - omni_events = game_config.get_bool('omnimix_events_enabled') + event_phase = game_config.get_int("event_phase") + omni_events = game_config.get_bool("omnimix_events_enabled") else: # If we aren't in an arcade, we turn off events event_phase = 0 @@ -1013,24 +1086,26 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): boss_phase = 1 event1 = event_phase - 1 - boss = Node.void('boss') + boss = Node.void("boss") root.add_child(boss) - boss.set_attribute('phase', str(boss_phase)) + boss.set_attribute("phase", str(boss_phase)) - event1_phase = Node.void('event1_phase') + event1_phase = Node.void("event1_phase") root.add_child(event1_phase) - event1_phase.set_attribute('phase', str(event1)) + event1_phase.set_attribute("phase", str(event1)) - extra_boss_event = Node.void('extra_boss_event') # Always enable IIDX AIR RACE 5 + extra_boss_event = Node.void( + "extra_boss_event" + ) # Always enable IIDX AIR RACE 5 root.add_child(extra_boss_event) - extra_boss_event.set_attribute('phase', '4') + extra_boss_event.set_attribute("phase", "4") # Course definitions courses: List[Dict[str, Any]] = [ { - 'name': 'EUROBEAT', - 'id': 1, - 'songs': [ + "name": "EUROBEAT", + "id": 1, + "songs": [ 17040, 8006, 10033, @@ -1038,10 +1113,10 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): ], }, { - 'name': 'WEATHER', - 'id': 2, - 'open_music_id': 15104, - 'songs': [ + "name": "WEATHER", + "id": 2, + "open_music_id": 15104, + "songs": [ 17023, 9030, 15004, @@ -1049,10 +1124,10 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): ], }, { - 'name': 'FEAT. KANAE ASABA', - 'id': 3, - 'open_music_id': 24101, - 'songs': [ + "name": "FEAT. KANAE ASABA", + "id": 3, + "open_music_id": 24101, + "songs": [ 25056, 23033, 24011, @@ -1060,9 +1135,9 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): ], }, { - 'name': 'BLOOM', - 'id': 4, - 'songs': [ + "name": "BLOOM", + "id": 4, + "songs": [ 15054, 20053, 20004, @@ -1070,9 +1145,9 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): ], }, { - 'name': 'VACUUM', - 'id': 5, - 'songs': [ + "name": "VACUUM", + "id": 5, + "songs": [ 19070, 23021, 17002, @@ -1080,10 +1155,10 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): ], }, { - 'name': 'PRINCESS', - 'id': 6, - 'open_music_id': 12100, - 'songs': [ + "name": "PRINCESS", + "id": 6, + "open_music_id": 12100, + "songs": [ 25024, 20021, 19046, @@ -1091,10 +1166,10 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): ], }, { - 'name': 'TECHNO', - 'id': 7, - 'open_music_id': 19100, - 'songs': [ + "name": "TECHNO", + "id": 7, + "open_music_id": 19100, + "songs": [ 25026, 19063, 17061, @@ -1102,10 +1177,10 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): ], }, { - 'name': 'POP', - 'id': 8, - 'open_music_id': 15103, - 'songs': [ + "name": "POP", + "id": 8, + "open_music_id": 15103, + "songs": [ 15061, 20099, 20101, @@ -1113,10 +1188,10 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): ], }, { - 'name': 'CARDINAL', - 'id': 9, - 'open_music_id': 13101, - 'songs': [ + "name": "CARDINAL", + "id": 9, + "open_music_id": 13101, + "songs": [ 13038, 13026, 13000, @@ -1124,9 +1199,9 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): ], }, { - 'name': 'O2K', - 'id': 10, - 'songs': [ + "name": "O2K", + "id": 10, + "songs": [ 21059, 19002, 22054, @@ -1138,9 +1213,9 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): # Secret course definitions secret_courses: List[Dict[str, Any]] = [ { - 'name': 'EMOTIONAL', - 'id': 1, - 'songs': [ + "name": "EMOTIONAL", + "id": 1, + "songs": [ 21009, 25006, 17065, @@ -1148,9 +1223,9 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): ], }, { - 'name': 'RYU', - 'id': 2, - 'songs': [ + "name": "RYU", + "id": 2, + "songs": [ 23082, 21007, 23037, @@ -1161,88 +1236,90 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): # For some reason, omnimix crashes on course mode, so don't enable it if not self.omnimix: - internet_ranking = Node.void('internet_ranking') + internet_ranking = Node.void("internet_ranking") root.add_child(internet_ranking) used_ids: List[int] = [] for c in courses: - if c['id'] in used_ids: - raise Exception('Cannot have multiple courses with the same ID!') - elif c['id'] < 0 or c['id'] >= 20: - raise Exception('Course ID is out of bounds!') + if c["id"] in used_ids: + raise Exception("Cannot have multiple courses with the same ID!") + elif c["id"] < 0 or c["id"] >= 20: + raise Exception("Course ID is out of bounds!") else: - used_ids.append(c['id']) + used_ids.append(c["id"]) - course = Node.void('course') + course = Node.void("course") internet_ranking.add_child(course) - course.set_attribute('course_id', str(c['id'])) - course.set_attribute('name', c['name']) - course.set_attribute('mid0', str(c['songs'][0])) - course.set_attribute('mid1', str(c['songs'][1])) - course.set_attribute('mid2', str(c['songs'][2])) - course.set_attribute('mid3', str(c['songs'][3])) - course.set_attribute('open_music_id', str(c.get('open_music_id', 0))) - course.set_attribute('opflg', '1') + course.set_attribute("course_id", str(c["id"])) + course.set_attribute("name", c["name"]) + course.set_attribute("mid0", str(c["songs"][0])) + course.set_attribute("mid1", str(c["songs"][1])) + course.set_attribute("mid2", str(c["songs"][2])) + course.set_attribute("mid3", str(c["songs"][3])) + course.set_attribute("open_music_id", str(c.get("open_music_id", 0))) + course.set_attribute("opflg", "1") - secret_ex_course = Node.void('secret_ex_course') + secret_ex_course = Node.void("secret_ex_course") root.add_child(secret_ex_course) used_secret_ids: List[int] = [] for c in secret_courses: - if c['id'] in used_secret_ids: - raise Exception('Cannot have multiple secret courses with the same ID!') - elif c['id'] < 0 or c['id'] >= 20: - raise Exception('Secret course ID is out of bounds!') + if c["id"] in used_secret_ids: + raise Exception( + "Cannot have multiple secret courses with the same ID!" + ) + elif c["id"] < 0 or c["id"] >= 20: + raise Exception("Secret course ID is out of bounds!") else: - used_secret_ids.append(c['id']) + used_secret_ids.append(c["id"]) - course = Node.void('course') + course = Node.void("course") secret_ex_course.add_child(course) - course.set_attribute('course_id', str(c['id'])) - course.set_attribute('name', c['name']) - course.set_attribute('mid0', str(c['songs'][0])) - course.set_attribute('mid1', str(c['songs'][1])) - course.set_attribute('mid2', str(c['songs'][2])) - course.set_attribute('mid3', str(c['songs'][3])) + course.set_attribute("course_id", str(c["id"])) + course.set_attribute("name", c["name"]) + course.set_attribute("mid0", str(c["songs"][0])) + course.set_attribute("mid1", str(c["songs"][1])) + course.set_attribute("mid2", str(c["songs"][2])) + course.set_attribute("mid3", str(c["songs"][3])) - expert = Node.void('expert') + expert = Node.void("expert") root.add_child(expert) - expert.set_attribute('phase', '1') + expert.set_attribute("phase", "1") - expert_random_select = Node.void('expert_random_select') + expert_random_select = Node.void("expert_random_select") root.add_child(expert_random_select) - expert_random_select.set_attribute('phase', '1') + expert_random_select.set_attribute("phase", "1") - expert_full = Node.void('expert_secret_full_open') + expert_full = Node.void("expert_secret_full_open") root.add_child(expert_full) return root def handle_IIDX25pc_delete_request(self, request: Node) -> Node: - return Node.void('IIDX25pc') + return Node.void("IIDX25pc") def handle_IIDX25pc_playstart_request(self, request: Node) -> Node: - return Node.void('IIDX25pc') + return Node.void("IIDX25pc") def handle_IIDX25pc_playend_request(self, request: Node) -> Node: - return Node.void('IIDX25pc') + return Node.void("IIDX25pc") def handle_IIDX25pc_visit_request(self, request: Node) -> Node: - root = Node.void('IIDX25pc') - root.set_attribute('anum', '0') - root.set_attribute('snum', '0') - root.set_attribute('pnum', '0') - root.set_attribute('aflg', '0') - root.set_attribute('sflg', '0') - root.set_attribute('pflg', '0') + root = Node.void("IIDX25pc") + root.set_attribute("anum", "0") + root.set_attribute("snum", "0") + root.set_attribute("pnum", "0") + root.set_attribute("aflg", "0") + root.set_attribute("sflg", "0") + root.set_attribute("pflg", "0") return root def handle_IIDX25pc_shopregister_request(self, request: Node) -> Node: - root = Node.void('IIDX25pc') + root = Node.void("IIDX25pc") return root def handle_IIDX25pc_oldget_request(self, request: Node) -> Node: - refid = request.attribute('rid') + refid = request.attribute("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: oldversion = self.previous_version() @@ -1250,12 +1327,12 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): else: profile = None - root = Node.void('IIDX25pc') - root.set_attribute('status', '1' if profile is None else '0') + root = Node.void("IIDX25pc") + root.set_attribute("status", "1" if profile is None else "0") return root def handle_IIDX25pc_getname_request(self, request: Node) -> Node: - refid = request.attribute('rid') + refid = request.attribute("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: oldversion = self.previous_version() @@ -1264,151 +1341,226 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): profile = None if profile is None: raise Exception( - 'Should not get here if we have no profile, we should ' + - 'have returned \'1\' in the \'oldget\' method above ' + - 'which should tell the game not to present a migration.' + "Should not get here if we have no profile, we should " + + "have returned '1' in the 'oldget' method above " + + "which should tell the game not to present a migration." ) - root = Node.void('IIDX25pc') - root.set_attribute('name', profile.get_str('name')) - root.set_attribute('idstr', ID.format_extid(profile.extid)) - root.set_attribute('pid', str(profile.get_int('pid'))) + root = Node.void("IIDX25pc") + root.set_attribute("name", profile.get_str("name")) + root.set_attribute("idstr", ID.format_extid(profile.extid)) + root.set_attribute("pid", str(profile.get_int("pid"))) return root def handle_IIDX25pc_takeover_request(self, request: Node) -> Node: - refid = request.attribute('rid') - name = request.attribute('name') - pid = int(request.attribute('pid')) + refid = request.attribute("rid") + name = request.attribute("name") + pid = int(request.attribute("pid")) newprofile = self.new_profile_by_refid(refid, name, pid) - root = Node.void('IIDX25pc') + root = Node.void("IIDX25pc") if newprofile is not None: - root.set_attribute('id', str(newprofile.extid)) + root.set_attribute("id", str(newprofile.extid)) return root def handle_IIDX25pc_reg_request(self, request: Node) -> Node: - refid = request.attribute('rid') - name = request.attribute('name') - pid = int(request.attribute('pid')) + refid = request.attribute("rid") + name = request.attribute("name") + pid = int(request.attribute("pid")) profile = self.new_profile_by_refid(refid, name, pid) - root = Node.void('IIDX25pc') + root = Node.void("IIDX25pc") if profile is not None: - root.set_attribute('id', str(profile.extid)) - root.set_attribute('id_str', ID.format_extid(profile.extid)) + root.set_attribute("id", str(profile.extid)) + root.set_attribute("id_str", ID.format_extid(profile.extid)) return root def handle_IIDX25pc_get_request(self, request: Node) -> Node: - refid = request.attribute('rid') + refid = request.attribute("rid") root = self.get_profile_by_refid(refid) if root is None: - root = Node.void('IIDX25pc') + root = Node.void("IIDX25pc") return root def handle_IIDX25pc_save_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) + extid = int(request.attribute("iidxid")) self.put_profile_by_extid(extid, request) - return Node.void('IIDX25pc') + return Node.void("IIDX25pc") def handle_IIDX25pc_logout_request(self, request: Node) -> Node: - return Node.void('IIDX25pc') + return Node.void("IIDX25pc") def handle_IIDX25gameSystem_systemInfo_request(self, request: Node) -> Node: - root = Node.void('IIDX25gameSystem') - arena_schedule = Node.void('arena_schedule') + root = Node.void("IIDX25gameSystem") + arena_schedule = Node.void("arena_schedule") root.add_child(arena_schedule) - arena_schedule.add_child(Node.u8('phase', 1)) - arena_schedule.add_child(Node.u32('start', 0)) - arena_schedule.add_child(Node.u32('end', 0)) - cpu_grades = [6, 7, 8, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 18] - cpu_low_diff = [4, 5, 6, 6, 7, 7, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 11, 12, 12] - cpu_high_diff = [5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 12, 12, 12] + arena_schedule.add_child(Node.u8("phase", 1)) + arena_schedule.add_child(Node.u32("start", 0)) + arena_schedule.add_child(Node.u32("end", 0)) + cpu_grades = [ + 6, + 7, + 8, + 9, + 10, + 10, + 11, + 11, + 12, + 12, + 13, + 13, + 14, + 14, + 15, + 15, + 16, + 16, + 17, + 18, + ] + cpu_low_diff = [ + 4, + 5, + 6, + 6, + 7, + 7, + 8, + 8, + 9, + 9, + 9, + 10, + 10, + 10, + 11, + 11, + 11, + 11, + 12, + 12, + ] + cpu_high_diff = [ + 5, + 6, + 6, + 7, + 7, + 8, + 8, + 9, + 9, + 10, + 10, + 10, + 11, + 11, + 11, + 12, + 12, + 12, + 12, + 12, + ] for playstyle in range(2): for arena_class in range(20): - arena_music_difficult = Node.void('arena_music_difficult') + arena_music_difficult = Node.void("arena_music_difficult") root.add_child(arena_music_difficult) - arena_music_difficult.add_child(Node.s32('play_style', playstyle)) - arena_music_difficult.add_child(Node.s32('arena_class', arena_class)) - arena_music_difficult.add_child(Node.s32('low_difficult', 1)) - arena_music_difficult.add_child(Node.s32('high_difficult', 12)) - arena_music_difficult.add_child(Node.bool('is_leggendaria', True)) - arena_music_difficult.add_child(Node.s32('force_music_list_id', 0)) - arena_cpu_define = Node.void('arena_cpu_define') + arena_music_difficult.add_child(Node.s32("play_style", playstyle)) + arena_music_difficult.add_child(Node.s32("arena_class", arena_class)) + arena_music_difficult.add_child(Node.s32("low_difficult", 1)) + arena_music_difficult.add_child(Node.s32("high_difficult", 12)) + arena_music_difficult.add_child(Node.bool("is_leggendaria", True)) + arena_music_difficult.add_child(Node.s32("force_music_list_id", 0)) + arena_cpu_define = Node.void("arena_cpu_define") root.add_child(arena_cpu_define) - arena_cpu_define.add_child(Node.s32('play_style', playstyle)) - arena_cpu_define.add_child(Node.s32('arena_class', arena_class)) - arena_cpu_define.add_child(Node.s32('grade_id', cpu_grades[arena_class])) - arena_cpu_define.add_child(Node.s32('low_music_difficult', cpu_low_diff[arena_class])) - arena_cpu_define.add_child(Node.s32('high_music_difficult', cpu_high_diff[arena_class])) - arena_cpu_define.add_child(Node.bool('is_leggendaria', arena_class >= 13)) + arena_cpu_define.add_child(Node.s32("play_style", playstyle)) + arena_cpu_define.add_child(Node.s32("arena_class", arena_class)) + arena_cpu_define.add_child( + Node.s32("grade_id", cpu_grades[arena_class]) + ) + arena_cpu_define.add_child( + Node.s32("low_music_difficult", cpu_low_diff[arena_class]) + ) + arena_cpu_define.add_child( + Node.s32("high_music_difficult", cpu_high_diff[arena_class]) + ) + arena_cpu_define.add_child( + Node.bool("is_leggendaria", arena_class >= 13) + ) for matching_class in range(21): - matching_class_range = Node.void('matching_class_range') + matching_class_range = Node.void("matching_class_range") root.add_child(matching_class_range) - matching_class_range.add_child(Node.s32('play_style', playstyle)) - matching_class_range.add_child(Node.s32('matching_class', matching_class)) - matching_class_range.add_child(Node.s32('low_arena_class', 1)) - matching_class_range.add_child(Node.s32('high_arena_class', 20)) + matching_class_range.add_child(Node.s32("play_style", playstyle)) + matching_class_range.add_child( + Node.s32("matching_class", matching_class) + ) + matching_class_range.add_child(Node.s32("low_arena_class", 1)) + matching_class_range.add_child(Node.s32("high_arena_class", 20)) return root def handle_IIDX25pc_eaappliresult_request(self, request: Node) -> Node: clear_map = { - self.GAME_CLEAR_STATUS_NO_PLAY: 'NO PLAY', - self.GAME_CLEAR_STATUS_FAILED: 'FAILED', - self.GAME_CLEAR_STATUS_ASSIST_CLEAR: 'ASSIST CLEAR', - self.GAME_CLEAR_STATUS_EASY_CLEAR: 'EASY CLEAR', - self.GAME_CLEAR_STATUS_CLEAR: 'CLEAR', - self.GAME_CLEAR_STATUS_HARD_CLEAR: 'HARD CLEAR', - self.GAME_CLEAR_STATUS_EX_HARD_CLEAR: 'EX HARD CLEAR', - self.GAME_CLEAR_STATUS_FULL_COMBO: 'FULL COMBO', + self.GAME_CLEAR_STATUS_NO_PLAY: "NO PLAY", + self.GAME_CLEAR_STATUS_FAILED: "FAILED", + self.GAME_CLEAR_STATUS_ASSIST_CLEAR: "ASSIST CLEAR", + self.GAME_CLEAR_STATUS_EASY_CLEAR: "EASY CLEAR", + self.GAME_CLEAR_STATUS_CLEAR: "CLEAR", + self.GAME_CLEAR_STATUS_HARD_CLEAR: "HARD CLEAR", + self.GAME_CLEAR_STATUS_EX_HARD_CLEAR: "EX HARD CLEAR", + self.GAME_CLEAR_STATUS_FULL_COMBO: "FULL COMBO", } # first we'll grab the data from the packet # did = request.child_value('did') # rid = request.child_value('rid') - name = request.child_value('name') + name = request.child_value("name") # qpro_hair = request.child_value('qpro_hair') # qpro_head = request.child_value('qpro_head') # qpro_body = request.child_value('qpro_body') # qpro_hand = request.child_value('qpro_hand') - music_id = request.child_value('music_id') - class_id = request.child_value('class_id') + music_id = request.child_value("music_id") + class_id = request.child_value("class_id") # no_save = request.child_value('no_save') # is_couple = request.child_value('is_couple') # target_graph = request.child_value('target_graph') - target_exscore = request.child_value('target_exscore') + target_exscore = request.child_value("target_exscore") # pacemaker = request.child_value('pacemaker') - best_clear = request.child_value('best_clear') + best_clear = request.child_value("best_clear") # best_djlevel = request.child_value('best_djlevel') # best_exscore = request.child_value('best_exscore') # best_misscount = request.child_value('best_misscount') - now_clear = request.child_value('now_clear') + now_clear = request.child_value("now_clear") # now_djlevel = request.child_value('now_djlevel') - now_exscore = request.child_value('now_exscore') + now_exscore = request.child_value("now_exscore") # now_misscount = request.child_value('now_misscount') - now_pgreat = request.child_value('now_pgreat') - now_great = request.child_value('now_great') - now_good = request.child_value('now_good') - now_bad = request.child_value('now_bad') - now_poor = request.child_value('now_poor') - now_combo = request.child_value('now_combo') - now_fast = request.child_value('now_fast') - now_slow = request.child_value('now_slow') - best_clear_string = clear_map.get(best_clear, 'NO PLAY') - now_clear_string = clear_map.get(now_clear, 'NO PLAY') + now_pgreat = request.child_value("now_pgreat") + now_great = request.child_value("now_great") + now_good = request.child_value("now_good") + now_bad = request.child_value("now_bad") + now_poor = request.child_value("now_poor") + now_combo = request.child_value("now_combo") + now_fast = request.child_value("now_fast") + now_slow = request.child_value("now_slow") + best_clear_string = clear_map.get(best_clear, "NO PLAY") + now_clear_string = clear_map.get(now_clear, "NO PLAY") # let's get the song info first - song = self.data.local.music.get_song(self.game, self.music_version, music_id, self.game_to_db_chart(class_id)) - notecount = song.data.get('notecount', 0) + song = self.data.local.music.get_song( + self.game, self.music_version, music_id, self.game_to_db_chart(class_id) + ) + notecount = song.data.get("notecount", 0) # Construct the dictionary for the broadcast card_data = { BroadcastConstants.DJ_NAME: name, BroadcastConstants.SONG_NAME: song.name, BroadcastConstants.ARTIST_NAME: song.artist, - BroadcastConstants.DIFFICULTY: song.data.get('difficulty', 0), + BroadcastConstants.DIFFICULTY: song.data.get("difficulty", 0), BroadcastConstants.TARGET_EXSCORE: target_exscore, BroadcastConstants.EXSCORE: now_exscore, BroadcastConstants.BEST_CLEAR_STATUS: best_clear_string, BroadcastConstants.CLEAR_STATUS: now_clear_string, - BroadcastConstants.PLAY_STATS_HEADER: 'How did you do?', + BroadcastConstants.PLAY_STATS_HEADER: "How did you do?", BroadcastConstants.PERFECT_GREATS: now_pgreat, BroadcastConstants.GREATS: now_great, BroadcastConstants.GOODS: now_good, @@ -1422,222 +1574,262 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): max_score = notecount * 2 percent = now_exscore / max_score grade = int(9 * percent) - grades = ['F', 'F', 'E', 'D', 'C', 'B', 'A', 'AA', 'AAA', 'MAX'] + grades = ["F", "F", "E", "D", "C", "B", "A", "AA", "AAA", "MAX"] card_data[BroadcastConstants.GRADE] = grades[grade] card_data[BroadcastConstants.RATE] = str(round(percent, 2)) # Try to broadcast out the score to our webhook(s) self.data.triggers.broadcast_score(card_data, self.game, song) - return Node.void('IIDX25pc') + return Node.void("IIDX25pc") def format_profile(self, userid: UserID, profile: Profile) -> Node: - root = Node.void('IIDX25pc') + root = Node.void("IIDX25pc") # Look up play stats we bridge to every mix play_stats = self.get_play_statistics(userid) # Look up judge window adjustments - judge_dict = profile.get_dict('machine_judge_adjust') + judge_dict = profile.get_dict("machine_judge_adjust") machine_judge = judge_dict.get_dict(self.config.machine.pcbid) # Profile data - pcdata = Node.void('pcdata') + pcdata = Node.void("pcdata") root.add_child(pcdata) - pcdata.set_attribute('id', str(profile.extid)) - pcdata.set_attribute('idstr', ID.format_extid(profile.extid)) - pcdata.set_attribute('name', profile.get_str('name')) - pcdata.set_attribute('pid', str(profile.get_int('pid'))) - pcdata.set_attribute('spnum', str(play_stats.get_int('single_plays'))) - pcdata.set_attribute('dpnum', str(play_stats.get_int('double_plays'))) - pcdata.set_attribute('sach', str(play_stats.get_int('single_dj_points'))) - pcdata.set_attribute('dach', str(play_stats.get_int('double_dj_points'))) - pcdata.set_attribute('mode', str(profile.get_int('mode'))) - pcdata.set_attribute('pmode', str(profile.get_int('pmode'))) - pcdata.set_attribute('rtype', str(profile.get_int('rtype'))) - pcdata.set_attribute('sp_opt', str(profile.get_int('sp_opt'))) - pcdata.set_attribute('dp_opt', str(profile.get_int('dp_opt'))) - pcdata.set_attribute('dp_opt2', str(profile.get_int('dp_opt2'))) - pcdata.set_attribute('gpos', str(profile.get_int('gpos'))) - pcdata.set_attribute('s_sorttype', str(profile.get_int('s_sorttype'))) - pcdata.set_attribute('d_sorttype', str(profile.get_int('d_sorttype'))) - pcdata.set_attribute('s_pace', str(profile.get_int('s_pace'))) - pcdata.set_attribute('d_pace', str(profile.get_int('d_pace'))) - pcdata.set_attribute('s_gno', str(profile.get_int('s_gno'))) - pcdata.set_attribute('d_gno', str(profile.get_int('d_gno'))) - pcdata.set_attribute('s_gtype', str(profile.get_int('s_gtype'))) - pcdata.set_attribute('d_gtype', str(profile.get_int('d_gtype'))) - pcdata.set_attribute('s_sdlen', str(profile.get_int('s_sdlen'))) - pcdata.set_attribute('d_sdlen', str(profile.get_int('d_sdlen'))) - pcdata.set_attribute('s_sdtype', str(profile.get_int('s_sdtype'))) - pcdata.set_attribute('d_sdtype', str(profile.get_int('d_sdtype'))) - pcdata.set_attribute('s_timing', str(profile.get_int('s_timing'))) - pcdata.set_attribute('d_timing', str(profile.get_int('d_timing'))) - pcdata.set_attribute('s_notes', str(profile.get_float('s_notes'))) - pcdata.set_attribute('d_notes', str(profile.get_float('d_notes'))) - pcdata.set_attribute('s_judge', str(profile.get_int('s_judge'))) - pcdata.set_attribute('d_judge', str(profile.get_int('d_judge'))) - pcdata.set_attribute('s_judgeAdj', str(machine_judge.get_int('single'))) - pcdata.set_attribute('d_judgeAdj', str(machine_judge.get_int('double'))) - pcdata.set_attribute('s_hispeed', str(profile.get_float('s_hispeed'))) - pcdata.set_attribute('d_hispeed', str(profile.get_float('d_hispeed'))) - pcdata.set_attribute('s_liflen', str(profile.get_int('s_lift'))) - pcdata.set_attribute('d_liflen', str(profile.get_int('d_lift'))) - pcdata.set_attribute('s_disp_judge', str(profile.get_int('s_disp_judge'))) - pcdata.set_attribute('d_disp_judge', str(profile.get_int('d_disp_judge'))) - pcdata.set_attribute('s_opstyle', str(profile.get_int('s_opstyle'))) - pcdata.set_attribute('d_opstyle', str(profile.get_int('d_opstyle'))) - pcdata.set_attribute('s_exscore', str(profile.get_int('s_exscore'))) - pcdata.set_attribute('d_exscore', str(profile.get_int('d_exscore'))) - pcdata.set_attribute('s_graph_score', str(profile.get_int('s_graph_score'))) - pcdata.set_attribute('d_graph_score', str(profile.get_int('d_graph_score'))) - pcdata.set_attribute('s_auto_scrach', str(profile.get_int('s_auto_scrach'))) - pcdata.set_attribute('d_auto_scrach', str(profile.get_int('d_auto_scrach'))) - pcdata.set_attribute('s_gauge_disp', str(profile.get_int('s_gauge_disp'))) - pcdata.set_attribute('d_gauge_disp', str(profile.get_int('d_gauge_disp'))) - pcdata.set_attribute('s_lane_brignt', str(profile.get_int('s_lane_brignt'))) - pcdata.set_attribute('d_lane_brignt', str(profile.get_int('d_lane_brignt'))) - pcdata.set_attribute('s_camera_layout', str(profile.get_int('s_camera_layout'))) - pcdata.set_attribute('d_camera_layout', str(profile.get_int('d_camera_layout'))) + pcdata.set_attribute("id", str(profile.extid)) + pcdata.set_attribute("idstr", ID.format_extid(profile.extid)) + pcdata.set_attribute("name", profile.get_str("name")) + pcdata.set_attribute("pid", str(profile.get_int("pid"))) + pcdata.set_attribute("spnum", str(play_stats.get_int("single_plays"))) + pcdata.set_attribute("dpnum", str(play_stats.get_int("double_plays"))) + pcdata.set_attribute("sach", str(play_stats.get_int("single_dj_points"))) + pcdata.set_attribute("dach", str(play_stats.get_int("double_dj_points"))) + pcdata.set_attribute("mode", str(profile.get_int("mode"))) + pcdata.set_attribute("pmode", str(profile.get_int("pmode"))) + pcdata.set_attribute("rtype", str(profile.get_int("rtype"))) + pcdata.set_attribute("sp_opt", str(profile.get_int("sp_opt"))) + pcdata.set_attribute("dp_opt", str(profile.get_int("dp_opt"))) + pcdata.set_attribute("dp_opt2", str(profile.get_int("dp_opt2"))) + pcdata.set_attribute("gpos", str(profile.get_int("gpos"))) + pcdata.set_attribute("s_sorttype", str(profile.get_int("s_sorttype"))) + pcdata.set_attribute("d_sorttype", str(profile.get_int("d_sorttype"))) + pcdata.set_attribute("s_pace", str(profile.get_int("s_pace"))) + pcdata.set_attribute("d_pace", str(profile.get_int("d_pace"))) + pcdata.set_attribute("s_gno", str(profile.get_int("s_gno"))) + pcdata.set_attribute("d_gno", str(profile.get_int("d_gno"))) + pcdata.set_attribute("s_gtype", str(profile.get_int("s_gtype"))) + pcdata.set_attribute("d_gtype", str(profile.get_int("d_gtype"))) + pcdata.set_attribute("s_sdlen", str(profile.get_int("s_sdlen"))) + pcdata.set_attribute("d_sdlen", str(profile.get_int("d_sdlen"))) + pcdata.set_attribute("s_sdtype", str(profile.get_int("s_sdtype"))) + pcdata.set_attribute("d_sdtype", str(profile.get_int("d_sdtype"))) + pcdata.set_attribute("s_timing", str(profile.get_int("s_timing"))) + pcdata.set_attribute("d_timing", str(profile.get_int("d_timing"))) + pcdata.set_attribute("s_notes", str(profile.get_float("s_notes"))) + pcdata.set_attribute("d_notes", str(profile.get_float("d_notes"))) + pcdata.set_attribute("s_judge", str(profile.get_int("s_judge"))) + pcdata.set_attribute("d_judge", str(profile.get_int("d_judge"))) + pcdata.set_attribute("s_judgeAdj", str(machine_judge.get_int("single"))) + pcdata.set_attribute("d_judgeAdj", str(machine_judge.get_int("double"))) + pcdata.set_attribute("s_hispeed", str(profile.get_float("s_hispeed"))) + pcdata.set_attribute("d_hispeed", str(profile.get_float("d_hispeed"))) + pcdata.set_attribute("s_liflen", str(profile.get_int("s_lift"))) + pcdata.set_attribute("d_liflen", str(profile.get_int("d_lift"))) + pcdata.set_attribute("s_disp_judge", str(profile.get_int("s_disp_judge"))) + pcdata.set_attribute("d_disp_judge", str(profile.get_int("d_disp_judge"))) + pcdata.set_attribute("s_opstyle", str(profile.get_int("s_opstyle"))) + pcdata.set_attribute("d_opstyle", str(profile.get_int("d_opstyle"))) + pcdata.set_attribute("s_exscore", str(profile.get_int("s_exscore"))) + pcdata.set_attribute("d_exscore", str(profile.get_int("d_exscore"))) + pcdata.set_attribute("s_graph_score", str(profile.get_int("s_graph_score"))) + pcdata.set_attribute("d_graph_score", str(profile.get_int("d_graph_score"))) + pcdata.set_attribute("s_auto_scrach", str(profile.get_int("s_auto_scrach"))) + pcdata.set_attribute("d_auto_scrach", str(profile.get_int("d_auto_scrach"))) + pcdata.set_attribute("s_gauge_disp", str(profile.get_int("s_gauge_disp"))) + pcdata.set_attribute("d_gauge_disp", str(profile.get_int("d_gauge_disp"))) + pcdata.set_attribute("s_lane_brignt", str(profile.get_int("s_lane_brignt"))) + pcdata.set_attribute("d_lane_brignt", str(profile.get_int("d_lane_brignt"))) + pcdata.set_attribute("s_camera_layout", str(profile.get_int("s_camera_layout"))) + pcdata.set_attribute("d_camera_layout", str(profile.get_int("d_camera_layout"))) - spdp_rival = Node.void('spdp_rival') + spdp_rival = Node.void("spdp_rival") root.add_child(spdp_rival) - spdp_rival.set_attribute('flg', str(profile.get_int('spdp_rival_flag'))) + spdp_rival.set_attribute("flg", str(profile.get_int("spdp_rival_flag"))) - premium_unlocks = Node.void('ea_premium_course') + premium_unlocks = Node.void("ea_premium_course") root.add_child(premium_unlocks) - legendarias = Node.void('leggendaria_open') + legendarias = Node.void("leggendaria_open") root.add_child(legendarias) # Song unlock flags - secret_dict = profile.get_dict('secret') - secret = Node.void('secret') + secret_dict = profile.get_dict("secret") + secret = Node.void("secret") root.add_child(secret) - secret.add_child(Node.s64_array('flg1', secret_dict.get_int_array('flg1', 3))) - secret.add_child(Node.s64_array('flg2', secret_dict.get_int_array('flg2', 3))) - secret.add_child(Node.s64_array('flg3', secret_dict.get_int_array('flg3', 3))) + secret.add_child(Node.s64_array("flg1", secret_dict.get_int_array("flg1", 3))) + secret.add_child(Node.s64_array("flg2", secret_dict.get_int_array("flg2", 3))) + secret.add_child(Node.s64_array("flg3", secret_dict.get_int_array("flg3", 3))) # Favorites - for folder in ['favorite1', 'favorite2', 'favorite3']: + for folder in ["favorite1", "favorite2", "favorite3"]: favorite_dict = profile.get_dict(folder) - sp_mlist = b'' - sp_clist = b'' - singles_list = favorite_dict['single'] if 'single' in favorite_dict else [] + sp_mlist = b"" + sp_clist = b"" + singles_list = favorite_dict["single"] if "single" in favorite_dict else [] for single in singles_list: - sp_mlist = sp_mlist + struct.pack(' Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() play_stats = self.get_play_statistics(userid) # Track play counts - cltype = int(request.attribute('cltype')) + cltype = int(request.attribute("cltype")) if cltype == self.GAME_CLTYPE_SINGLE: - play_stats.increment_int('single_plays') + play_stats.increment_int("single_plays") if cltype == self.GAME_CLTYPE_DOUBLE: - play_stats.increment_int('double_plays') + play_stats.increment_int("double_plays") # Track DJ points - play_stats.replace_int('single_dj_points', int(request.attribute('s_achi'))) - play_stats.replace_int('double_dj_points', int(request.attribute('d_achi'))) + play_stats.replace_int("single_dj_points", int(request.attribute("s_achi"))) + play_stats.replace_int("double_dj_points", int(request.attribute("d_achi"))) # Profile settings - newprofile.replace_int('sp_opt', int(request.attribute('sp_opt'))) - newprofile.replace_int('dp_opt', int(request.attribute('dp_opt'))) - newprofile.replace_int('dp_opt2', int(request.attribute('dp_opt2'))) - newprofile.replace_int('gpos', int(request.attribute('gpos'))) - newprofile.replace_int('s_sorttype', int(request.attribute('s_sorttype'))) - newprofile.replace_int('d_sorttype', int(request.attribute('d_sorttype'))) - newprofile.replace_int('s_disp_judge', int(request.attribute('s_disp_judge'))) - newprofile.replace_int('d_disp_judge', int(request.attribute('d_disp_judge'))) - newprofile.replace_int('s_pace', int(request.attribute('s_pace'))) - newprofile.replace_int('d_pace', int(request.attribute('d_pace'))) - newprofile.replace_int('s_gno', int(request.attribute('s_gno'))) - newprofile.replace_int('d_gno', int(request.attribute('d_gno'))) - newprofile.replace_int('s_gtype', int(request.attribute('s_gtype'))) - newprofile.replace_int('d_gtype', int(request.attribute('d_gtype'))) - newprofile.replace_int('s_sdlen', int(request.attribute('s_sdlen'))) - newprofile.replace_int('d_sdlen', int(request.attribute('d_sdlen'))) - newprofile.replace_int('s_sdtype', int(request.attribute('s_sdtype'))) - newprofile.replace_int('d_sdtype', int(request.attribute('d_sdtype'))) - newprofile.replace_int('s_timing', int(request.attribute('s_timing'))) - newprofile.replace_int('d_timing', int(request.attribute('d_timing'))) - newprofile.replace_float('s_notes', float(request.attribute('s_notes'))) - newprofile.replace_float('d_notes', float(request.attribute('d_notes'))) - newprofile.replace_int('s_judge', int(request.attribute('s_judge'))) - newprofile.replace_int('d_judge', int(request.attribute('d_judge'))) - newprofile.replace_float('s_hispeed', float(request.attribute('s_hispeed'))) - newprofile.replace_float('d_hispeed', float(request.attribute('d_hispeed'))) - newprofile.replace_int('s_opstyle', int(request.attribute('s_opstyle'))) - newprofile.replace_int('d_opstyle', int(request.attribute('d_opstyle'))) - newprofile.replace_int('s_graph_score', int(request.attribute('s_graph_score'))) - newprofile.replace_int('d_graph_score', int(request.attribute('d_graph_score'))) - newprofile.replace_int('s_auto_scrach', int(request.attribute('s_auto_scrach'))) - newprofile.replace_int('d_auto_scrach', int(request.attribute('d_auto_scrach'))) - newprofile.replace_int('s_gauge_disp', int(request.attribute('s_gauge_disp'))) - newprofile.replace_int('d_gauge_disp', int(request.attribute('d_gauge_disp'))) - newprofile.replace_int('s_lane_brignt', int(request.attribute('s_lane_brignt'))) - newprofile.replace_int('d_lane_brignt', int(request.attribute('d_lane_brignt'))) - newprofile.replace_int('s_camera_layout', int(request.attribute('s_camera_layout'))) - newprofile.replace_int('d_camera_layout', int(request.attribute('d_camera_layout'))) - newprofile.replace_int('s_lift', int(request.attribute('s_lift'))) - newprofile.replace_int('d_lift', int(request.attribute('d_lift'))) - newprofile.replace_int('mode', int(request.attribute('mode'))) - newprofile.replace_int('pmode', int(request.attribute('pmode'))) - newprofile.replace_int('rtype', int(request.attribute('rtype'))) + newprofile.replace_int("sp_opt", int(request.attribute("sp_opt"))) + newprofile.replace_int("dp_opt", int(request.attribute("dp_opt"))) + newprofile.replace_int("dp_opt2", int(request.attribute("dp_opt2"))) + newprofile.replace_int("gpos", int(request.attribute("gpos"))) + newprofile.replace_int("s_sorttype", int(request.attribute("s_sorttype"))) + newprofile.replace_int("d_sorttype", int(request.attribute("d_sorttype"))) + newprofile.replace_int("s_disp_judge", int(request.attribute("s_disp_judge"))) + newprofile.replace_int("d_disp_judge", int(request.attribute("d_disp_judge"))) + newprofile.replace_int("s_pace", int(request.attribute("s_pace"))) + newprofile.replace_int("d_pace", int(request.attribute("d_pace"))) + newprofile.replace_int("s_gno", int(request.attribute("s_gno"))) + newprofile.replace_int("d_gno", int(request.attribute("d_gno"))) + newprofile.replace_int("s_gtype", int(request.attribute("s_gtype"))) + newprofile.replace_int("d_gtype", int(request.attribute("d_gtype"))) + newprofile.replace_int("s_sdlen", int(request.attribute("s_sdlen"))) + newprofile.replace_int("d_sdlen", int(request.attribute("d_sdlen"))) + newprofile.replace_int("s_sdtype", int(request.attribute("s_sdtype"))) + newprofile.replace_int("d_sdtype", int(request.attribute("d_sdtype"))) + newprofile.replace_int("s_timing", int(request.attribute("s_timing"))) + newprofile.replace_int("d_timing", int(request.attribute("d_timing"))) + newprofile.replace_float("s_notes", float(request.attribute("s_notes"))) + newprofile.replace_float("d_notes", float(request.attribute("d_notes"))) + newprofile.replace_int("s_judge", int(request.attribute("s_judge"))) + newprofile.replace_int("d_judge", int(request.attribute("d_judge"))) + newprofile.replace_float("s_hispeed", float(request.attribute("s_hispeed"))) + newprofile.replace_float("d_hispeed", float(request.attribute("d_hispeed"))) + newprofile.replace_int("s_opstyle", int(request.attribute("s_opstyle"))) + newprofile.replace_int("d_opstyle", int(request.attribute("d_opstyle"))) + newprofile.replace_int("s_graph_score", int(request.attribute("s_graph_score"))) + newprofile.replace_int("d_graph_score", int(request.attribute("d_graph_score"))) + newprofile.replace_int("s_auto_scrach", int(request.attribute("s_auto_scrach"))) + newprofile.replace_int("d_auto_scrach", int(request.attribute("d_auto_scrach"))) + newprofile.replace_int("s_gauge_disp", int(request.attribute("s_gauge_disp"))) + newprofile.replace_int("d_gauge_disp", int(request.attribute("d_gauge_disp"))) + newprofile.replace_int("s_lane_brignt", int(request.attribute("s_lane_brignt"))) + newprofile.replace_int("d_lane_brignt", int(request.attribute("d_lane_brignt"))) + newprofile.replace_int( + "s_camera_layout", int(request.attribute("s_camera_layout")) + ) + newprofile.replace_int( + "d_camera_layout", int(request.attribute("d_camera_layout")) + ) + newprofile.replace_int("s_lift", int(request.attribute("s_lift"))) + newprofile.replace_int("d_lift", int(request.attribute("d_lift"))) + newprofile.replace_int("mode", int(request.attribute("mode"))) + newprofile.replace_int("pmode", int(request.attribute("pmode"))) + newprofile.replace_int("rtype", int(request.attribute("rtype"))) # Update judge window adjustments per-machine - judge_dict = newprofile.get_dict('machine_judge_adjust') + judge_dict = newprofile.get_dict("machine_judge_adjust") machine_judge = judge_dict.get_dict(self.config.machine.pcbid) - machine_judge.replace_int('single', int(request.attribute('s_judgeAdj'))) - machine_judge.replace_int('double', int(request.attribute('d_judgeAdj'))) + machine_judge.replace_int("single", int(request.attribute("s_judgeAdj"))) + machine_judge.replace_int("double", int(request.attribute("d_judgeAdj"))) judge_dict.replace_dict(self.config.machine.pcbid, machine_judge) - newprofile.replace_dict('machine_judge_adjust', judge_dict) + newprofile.replace_dict("machine_judge_adjust", judge_dict) # Secret flags saving - secret = request.child('secret') + secret = request.child("secret") if secret is not None: - secret_dict = newprofile.get_dict('secret') - secret_dict.replace_int_array('flg1', 3, secret.child_value('flg1')) - secret_dict.replace_int_array('flg2', 3, secret.child_value('flg2')) - secret_dict.replace_int_array('flg3', 3, secret.child_value('flg3')) - newprofile.replace_dict('secret', secret_dict) + secret_dict = newprofile.get_dict("secret") + secret_dict.replace_int_array("flg1", 3, secret.child_value("flg1")) + secret_dict.replace_int_array("flg2", 3, secret.child_value("flg2")) + secret_dict.replace_int_array("flg3", 3, secret.child_value("flg3")) + newprofile.replace_dict("secret", secret_dict) # Basic achievements - achievements = request.child('achievements') + achievements = request.child("achievements") if achievements is not None: - newprofile.replace_int('visit_flg', int(achievements.attribute('visit_flg'))) - newprofile.replace_int('last_weekly', int(achievements.attribute('last_weekly'))) - newprofile.replace_int('weekly_num', int(achievements.attribute('weekly_num'))) + newprofile.replace_int( + "visit_flg", int(achievements.attribute("visit_flg")) + ) + newprofile.replace_int( + "last_weekly", int(achievements.attribute("last_weekly")) + ) + newprofile.replace_int( + "weekly_num", int(achievements.attribute("weekly_num")) + ) - pack_id = int(achievements.attribute('pack_id')) + pack_id = int(achievements.attribute("pack_id")) if pack_id > 0: self.data.local.user.put_achievement( self.game, self.version, userid, pack_id, - 'daily', + "daily", { - 'pack_flg': int(achievements.attribute('pack_flg')), - 'pack_comp': int(achievements.attribute('pack_comp')), + "pack_flg": int(achievements.attribute("pack_flg")), + "pack_comp": int(achievements.attribute("pack_comp")), }, ) - trophies = achievements.child('trophy') + trophies = achievements.child("trophy") if trophies is not None: # We only load the first 20 in profile load. - newprofile.replace_int_array('trophy', 20, trophies.value[:20]) + newprofile.replace_int_array("trophy", 20, trophies.value[:20]) # Deller saving - deller = request.child('deller') + deller = request.child("deller") if deller is not None: - newprofile.replace_int('deller', newprofile.get_int('deller') + int(deller.attribute('deller'))) + newprofile.replace_int( + "deller", newprofile.get_int("deller") + int(deller.attribute("deller")) + ) # Secret course expert point saving - expert_point = request.child('expert_point') + expert_point = request.child("expert_point") if expert_point is not None: - courseid = int(expert_point.attribute('course_id')) + courseid = int(expert_point.attribute("course_id")) # Update achievement to track expert points expert_point_achievement = self.data.local.user.get_achievement( @@ -1993,21 +2299,21 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): self.version, userid, courseid, - 'expert_point', + "expert_point", ) if expert_point_achievement is None: expert_point_achievement = ValidatedDict() expert_point_achievement.replace_int( - 'normal_points', - int(expert_point.attribute('n_point')), + "normal_points", + int(expert_point.attribute("n_point")), ) expert_point_achievement.replace_int( - 'hyper_points', - int(expert_point.attribute('h_point')), + "hyper_points", + int(expert_point.attribute("h_point")), ) expert_point_achievement.replace_int( - 'another_points', - int(expert_point.attribute('a_point')), + "another_points", + int(expert_point.attribute("a_point")), ) self.data.local.user.put_achievement( @@ -2015,7 +2321,7 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): self.version, userid, courseid, - 'expert_point', + "expert_point", expert_point_achievement, ) @@ -2024,174 +2330,223 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): singles = [] doubles = [] name = None - if favorite.name in ['favorite', 'extra_favorite']: - if favorite.name == 'favorite': - name = 'favorite1' - elif favorite.name == 'extra_favorite': - folder = favorite.attribute('folder_id') - if folder == '0': - name = 'favorite2' - if folder == '1': - name = 'favorite3' + if favorite.name in ["favorite", "extra_favorite"]: + if favorite.name == "favorite": + name = "favorite1" + elif favorite.name == "extra_favorite": + folder = favorite.attribute("folder_id") + if folder == "0": + name = "favorite2" + if folder == "1": + name = "favorite3" if name is None: continue - single_music_bin = favorite.child_value('sp_mlist') - single_chart_bin = favorite.child_value('sp_clist') - double_music_bin = favorite.child_value('dp_mlist') - double_chart_bin = favorite.child_value('dp_clist') + single_music_bin = favorite.child_value("sp_mlist") + single_chart_bin = favorite.child_value("sp_clist") + double_music_bin = favorite.child_value("dp_mlist") + double_chart_bin = favorite.child_value("dp_clist") for i in range(self.FAVORITE_LIST_LENGTH): - singles.append({ - 'id': struct.unpack(' List[Tuple[str, Dict[str, Any]]]: + def run_scheduled_work( + cls, data: Data, config: Dict[str, Any] + ) -> List[Tuple[str, Dict[str, Any]]]: """ Insert dailies into the DB. """ events = [] - if data.local.network.should_schedule(cls.game, cls.version, 'daily_charts', 'daily'): + if data.local.network.should_schedule( + cls.game, cls.version, "daily_charts", "daily" + ): # Generate a new list of three dailies. - start_time, end_time = data.local.network.get_schedule_duration('daily') - all_songs = list(set([song.id for song in data.local.music.get_all_songs(cls.game, cls.version)])) + start_time, end_time = data.local.network.get_schedule_duration("daily") + all_songs = list( + set( + [ + song.id + for song in data.local.music.get_all_songs( + cls.game, cls.version + ) + ] + ) + ) if len(all_songs) >= 3: daily_songs = random.sample(all_songs, 3) data.local.game.put_time_sensitive_settings( cls.game, cls.version, - 'dailies', + "dailies", { - 'start_time': start_time, - 'end_time': end_time, - 'music': daily_songs, + "start_time": start_time, + "end_time": end_time, + "music": daily_songs, }, ) - events.append(( - 'iidx_daily_charts', - { - 'version': cls.version, - 'music': daily_songs, - }, - )) + events.append( + ( + "iidx_daily_charts", + { + "version": cls.version, + "music": daily_songs, + }, + ) + ) # Mark that we did some actual work here. - data.local.network.mark_scheduled(cls.game, cls.version, 'daily_charts', 'daily') + data.local.network.mark_scheduled( + cls.game, cls.version, "daily_charts", "daily" + ) return events @classmethod @@ -137,33 +161,33 @@ class IIDXCopula(IIDXCourse, IIDXBase): Return all of our front-end modifiably settings. """ return { - 'bools': [ + "bools": [ { - 'name': 'Global Shop Ranking', - 'tip': 'Return network-wide ranking instead of shop ranking on results screen.', - 'category': 'game_config', - 'setting': 'global_shop_ranking', + "name": "Global Shop Ranking", + "tip": "Return network-wide ranking instead of shop ranking on results screen.", + "category": "game_config", + "setting": "global_shop_ranking", }, { - 'name': 'Events In Omnimix', - 'tip': 'Allow events to be enabled at all for Omnimix.', - 'category': 'game_config', - 'setting': 'omnimix_events_enabled', + "name": "Events In Omnimix", + "tip": "Allow events to be enabled at all for Omnimix.", + "category": "game_config", + "setting": "omnimix_events_enabled", }, ], - 'ints': [ + "ints": [ { - 'name': 'Event Phase', - 'tip': 'Event phase for all players.', - 'category': 'game_config', - 'setting': 'event_phase', - 'values': { - 0: 'No Event', - 1: 'Tokotoko Line', - 2: 'Mystery Line Phase 1', - 3: 'Mystery Line Phase 2', - 4: 'Mystery Line Phase 3', - } + "name": "Event Phase", + "tip": "Event phase for all players.", + "category": "game_config", + "setting": "event_phase", + "values": { + 0: "No Event", + 1: "Tokotoko Line", + 2: "Mystery Line Phase 1", + 3: "Mystery Line Phase 2", + 4: "Mystery Line Phase 3", + }, }, ], } @@ -242,7 +266,7 @@ class IIDXCopula(IIDXCourse, IIDXBase): self.DAN_RANK_KAIDEN: self.GAME_DP_DAN_RANK_KAIDEN, }[db_dan] else: - raise Exception('Invalid cltype!') + raise Exception("Invalid cltype!") def game_to_db_rank(self, game_dan: int, cltype: int) -> int: # Special case for no DAN rank @@ -294,7 +318,7 @@ class IIDXCopula(IIDXCourse, IIDXBase): self.GAME_DP_DAN_RANK_KAIDEN: self.DAN_RANK_KAIDEN, }[game_dan] else: - raise Exception('Invalid cltype!') + raise Exception("Invalid cltype!") def game_to_db_chart(self, db_chart: int) -> int: return { @@ -308,62 +332,66 @@ class IIDXCopula(IIDXCourse, IIDXBase): }[db_chart] def handle_IIDX23shop_getname_request(self, request: Node) -> Node: - root = Node.void('IIDX23shop') - root.set_attribute('cls_opt', '0') + root = Node.void("IIDX23shop") + root.set_attribute("cls_opt", "0") machine = self.data.local.machine.get_machine(self.config.machine.pcbid) - root.set_attribute('opname', machine.name) - root.set_attribute('pid', str(self.get_machine_region())) + root.set_attribute("opname", machine.name) + root.set_attribute("pid", str(self.get_machine_region())) return root def handle_IIDX23shop_savename_request(self, request: Node) -> Node: - self.update_machine_name(request.attribute('opname')) - root = Node.void('IIDX23shop') + self.update_machine_name(request.attribute("opname")) + root = Node.void("IIDX23shop") return root def handle_IIDX23shop_sentinfo_request(self, request: Node) -> Node: - root = Node.void('IIDX23shop') + root = Node.void("IIDX23shop") return root def handle_IIDX23shop_getconvention_request(self, request: Node) -> Node: - root = Node.void('IIDX23shop') + root = Node.void("IIDX23shop") machine = self.data.local.machine.get_machine(self.config.machine.pcbid) if machine.arcade is not None: - course = self.data.local.machine.get_settings(machine.arcade, self.game, self.music_version, 'shop_course') + course = self.data.local.machine.get_settings( + machine.arcade, self.game, self.music_version, "shop_course" + ) else: course = None if course is None: course = ValidatedDict() - root.set_attribute('music_0', str(course.get_int('music_0', 20032))) - root.set_attribute('music_1', str(course.get_int('music_1', 20009))) - root.set_attribute('music_2', str(course.get_int('music_2', 20015))) - root.set_attribute('music_3', str(course.get_int('music_3', 20064))) - root.add_child(Node.bool('valid', course.get_bool('valid'))) + root.set_attribute("music_0", str(course.get_int("music_0", 20032))) + root.set_attribute("music_1", str(course.get_int("music_1", 20009))) + root.set_attribute("music_2", str(course.get_int("music_2", 20015))) + root.set_attribute("music_3", str(course.get_int("music_3", 20064))) + root.add_child(Node.bool("valid", course.get_bool("valid"))) return root def handle_IIDX23shop_setconvention_request(self, request: Node) -> Node: - root = Node.void('IIDX23shop') + root = Node.void("IIDX23shop") machine = self.data.local.machine.get_machine(self.config.machine.pcbid) if machine.arcade is not None: course = ValidatedDict() - course.replace_int('music_0', request.child_value('music_0')) - course.replace_int('music_1', request.child_value('music_1')) - course.replace_int('music_2', request.child_value('music_2')) - course.replace_int('music_3', request.child_value('music_3')) - course.replace_bool('valid', request.child_value('valid')) - self.data.local.machine.put_settings(machine.arcade, self.game, self.music_version, 'shop_course', course) + course.replace_int("music_0", request.child_value("music_0")) + course.replace_int("music_1", request.child_value("music_1")) + course.replace_int("music_2", request.child_value("music_2")) + course.replace_int("music_3", request.child_value("music_3")) + course.replace_bool("valid", request.child_value("valid")) + self.data.local.machine.put_settings( + machine.arcade, self.game, self.music_version, "shop_course", course + ) return root def handle_IIDX23shop_sendescapepackageinfo_request(self, request: Node) -> Node: - root = Node.void('IIDX23shop') - root.set_attribute('expire', str((Time.now() + 86400 * 365) * 1000)) + root = Node.void("IIDX23shop") + root.set_attribute("expire", str((Time.now() + 86400 * 365) * 1000)) return root def handle_IIDX23ranking_getranker_request(self, request: Node) -> Node: - root = Node.void('IIDX23ranking') - chart = self.game_to_db_chart(int(request.attribute('clid'))) + root = Node.void("IIDX23ranking") + chart = self.game_to_db_chart(int(request.attribute("clid"))) if chart not in [ self.CHART_TYPE_N7, self.CHART_TYPE_H7, @@ -377,29 +405,31 @@ class IIDXCopula(IIDXCourse, IIDXBase): machine = self.data.local.machine.get_machine(self.config.machine.pcbid) if machine.arcade is not None: - course = self.data.local.machine.get_settings(machine.arcade, self.game, self.music_version, 'shop_course') + course = self.data.local.machine.get_settings( + machine.arcade, self.game, self.music_version, "shop_course" + ) else: course = None if course is None: course = ValidatedDict() - if not course.get_bool('valid'): + if not course.get_bool("valid"): # Shop course not enabled or not present return root - convention = Node.void('convention') + convention = Node.void("convention") root.add_child(convention) - convention.set_attribute('clid', str(chart)) - convention.set_attribute('update_date', str(Time.now() * 1000)) + convention.set_attribute("clid", str(chart)) + convention.set_attribute("update_date", str(Time.now() * 1000)) # Grab all scores for each of the four songs, filter out people who haven't # set us as their arcade and then return the top 20 scores (adding all 4 songs). songids = [ - course.get_int('music_0'), - course.get_int('music_1'), - course.get_int('music_2'), - course.get_int('music_3'), + course.get_int("music_0"), + course.get_int("music_1"), + course.get_int("music_2"), + course.get_int("music_3"), ] totalscores: Dict[UserID, int] = {} @@ -436,37 +466,37 @@ class IIDXCopula(IIDXCourse, IIDXBase): for topscore in topscores: rank = rank + 1 - detail = Node.void('detail') + detail = Node.void("detail") convention.add_child(detail) - detail.set_attribute('name', topscore[1].get_str('name')) - detail.set_attribute('rank', str(rank)) - detail.set_attribute('score', str(topscore[0])) - detail.set_attribute('pid', str(topscore[1].get_int('pid'))) + detail.set_attribute("name", topscore[1].get_str("name")) + detail.set_attribute("rank", str(rank)) + detail.set_attribute("score", str(topscore[0])) + detail.set_attribute("pid", str(topscore[1].get_int("pid"))) - qpro = topscore[1].get_dict('qpro') - detail.set_attribute('head', str(qpro.get_int('head'))) - detail.set_attribute('hair', str(qpro.get_int('hair'))) - detail.set_attribute('face', str(qpro.get_int('face'))) - detail.set_attribute('body', str(qpro.get_int('body'))) - detail.set_attribute('hand', str(qpro.get_int('hand'))) + qpro = topscore[1].get_dict("qpro") + detail.set_attribute("head", str(qpro.get_int("head"))) + detail.set_attribute("hair", str(qpro.get_int("hair"))) + detail.set_attribute("face", str(qpro.get_int("face"))) + detail.set_attribute("body", str(qpro.get_int("body"))) + detail.set_attribute("hand", str(qpro.get_int("hand"))) return root def handle_IIDX23ranking_entry_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) - courseid = int(request.attribute('coid')) - chart = self.game_to_db_chart(int(request.attribute('clid'))) - course_type = int(request.attribute('regist_type')) - clear_status = self.game_to_db_status(int(request.attribute('clr'))) - pgreats = int(request.attribute('pgnum')) - greats = int(request.attribute('gnum')) + extid = int(request.attribute("iidxid")) + courseid = int(request.attribute("coid")) + chart = self.game_to_db_chart(int(request.attribute("clid"))) + course_type = int(request.attribute("regist_type")) + clear_status = self.game_to_db_status(int(request.attribute("clr"))) + pgreats = int(request.attribute("pgnum")) + greats = int(request.attribute("gnum")) if course_type == 0: index = self.COURSE_TYPE_INTERNET_RANKING elif course_type == 1: index = self.COURSE_TYPE_SECRET else: - raise Exception('Unknown registration type for course entry!') + raise Exception("Unknown registration type for course entry!") userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: @@ -483,16 +513,25 @@ class IIDXCopula(IIDXCourse, IIDXBase): # We should return the user's position, but its not displayed anywhere # so fuck it. - root = Node.void('IIDX23ranking') - root.set_attribute('anum', '1') - root.set_attribute('jun', '1') + root = Node.void("IIDX23ranking") + root.set_attribute("anum", "1") + root.set_attribute("jun", "1") return root def handle_IIDX23music_crate_request(self, request: Node) -> Node: - root = Node.void('IIDX23music') + root = Node.void("IIDX23music") attempts = self.get_clear_rates() - all_songs = list(set([song.id for song in self.data.local.music.get_all_songs(self.game, self.music_version)])) + all_songs = list( + set( + [ + song.id + for song in self.data.local.music.get_all_songs( + self.game, self.music_version + ) + ] + ) + ) for song in all_songs: clears = [] fcs = [] @@ -501,33 +540,33 @@ class IIDXCopula(IIDXCourse, IIDXBase): placed = False if song in attempts and chart in attempts[song]: values = attempts[song][chart] - if values['total'] > 0: - clears.append(int((100 * values['clears']) / values['total'])) - fcs.append(int((100 * values['fcs']) / values['total'])) + if values["total"] > 0: + clears.append(int((100 * values["clears"]) / values["total"])) + fcs.append(int((100 * values["fcs"]) / values["total"])) placed = True if not placed: clears.append(101) fcs.append(101) - clearnode = Node.u8_array('c', clears + fcs) - clearnode.set_attribute('mid', str(song)) + clearnode = Node.u8_array("c", clears + fcs) + clearnode.set_attribute("mid", str(song)) root.add_child(clearnode) return root def handle_IIDX23music_getrank_request(self, request: Node) -> Node: - cltype = int(request.attribute('cltype')) + cltype = int(request.attribute("cltype")) - root = Node.void('IIDX23music') - style = Node.void('style') + root = Node.void("IIDX23music") + style = Node.void("style") root.add_child(style) - style.set_attribute('type', str(cltype)) + style.set_attribute("type", str(cltype)) for rivalid in [-1, 0, 1, 2, 3, 4]: if rivalid == -1: - attr = 'iidxid' + attr = "iidxid" else: - attr = f'iidxid{rivalid}' + attr = f"iidxid{rivalid}" try: extid = int(request.attribute(attr)) @@ -536,46 +575,52 @@ class IIDXCopula(IIDXCourse, IIDXBase): continue userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: - scores = self.data.remote.music.get_scores(self.game, self.music_version, userid) + scores = self.data.remote.music.get_scores( + self.game, self.music_version, userid + ) # Grab score data for user/rival scoredata = self.make_score_struct( scores, - self.CLEAR_TYPE_SINGLE if cltype == self.GAME_CLTYPE_SINGLE else self.CLEAR_TYPE_DOUBLE, + self.CLEAR_TYPE_SINGLE + if cltype == self.GAME_CLTYPE_SINGLE + else self.CLEAR_TYPE_DOUBLE, rivalid, ) for s in scoredata: - root.add_child(Node.s16_array('m', s)) + root.add_child(Node.s16_array("m", s)) # Grab most played for user/rival most_played = [ - play[0] for play in - self.data.local.music.get_most_played(self.game, self.music_version, userid, 20) + play[0] + for play in self.data.local.music.get_most_played( + self.game, self.music_version, userid, 20 + ) ] if len(most_played) < 20: most_played.extend([0] * (20 - len(most_played))) - best = Node.u16_array('best', most_played) - best.set_attribute('rno', str(rivalid)) + best = Node.u16_array("best", most_played) + best.set_attribute("rno", str(rivalid)) root.add_child(best) if rivalid == -1: # Grab beginner statuses for user only beginnerdata = self.make_beginner_struct(scores) for b in beginnerdata: - root.add_child(Node.u16_array('b', b)) + root.add_child(Node.u16_array("b", b)) return root def handle_IIDX23music_reg_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) - musicid = int(request.attribute('mid')) - chart = self.game_to_db_chart(int(request.attribute('clid'))) + extid = int(request.attribute("iidxid")) + musicid = int(request.attribute("mid")) + chart = self.game_to_db_chart(int(request.attribute("clid"))) userid = self.data.remote.user.from_extid(self.game, self.version, extid) # See if we need to report global or shop scores if self.machine_joined_arcade(): game_config = self.get_game_config() - global_scores = game_config.get_bool('global_shop_ranking') + global_scores = game_config.get_bool("global_shop_ranking") machine = self.data.local.machine.get_machine(self.config.machine.pcbid) else: # If we aren't in an arcade, we can only show global scores @@ -584,21 +629,27 @@ class IIDXCopula(IIDXCourse, IIDXBase): # First, determine our current ranking before saving the new score all_scores = sorted( - self.data.remote.music.get_all_scores(game=self.game, version=self.music_version, songid=musicid, songchart=chart), + self.data.remote.music.get_all_scores( + game=self.game, + version=self.music_version, + songid=musicid, + songchart=chart, + ), key=lambda s: (s[1].points, s[1].timestamp), reverse=True, ) all_players = { - uid: prof for (uid, prof) in - self.get_any_profiles([s[0] for s in all_scores]) + uid: prof + for (uid, prof) in self.get_any_profiles([s[0] for s in all_scores]) } if not global_scores: all_scores = [ - score for score in all_scores + score + for score in all_scores if ( - score[0] == userid or - self.user_joined_arcade(machine, all_players[score[0]]) + score[0] == userid + or self.user_joined_arcade(machine, all_players[score[0]]) ) ] @@ -610,12 +661,12 @@ class IIDXCopula(IIDXCourse, IIDXBase): break if userid is not None: - clear_status = self.game_to_db_status(int(request.attribute('cflg'))) - pgreats = int(request.attribute('pgnum')) - greats = int(request.attribute('gnum')) - miss_count = int(request.attribute('mnum')) - ghost = request.child_value('ghost') - shopid = ID.parse_machine_id(request.attribute('shopconvid')) + clear_status = self.game_to_db_status(int(request.attribute("cflg"))) + pgreats = int(request.attribute("pgnum")) + greats = int(request.attribute("gnum")) + miss_count = int(request.attribute("mnum")) + ghost = request.child_value("ghost") + shopid = ID.parse_machine_id(request.attribute("shopconvid")) self.update_score( userid, @@ -630,51 +681,56 @@ class IIDXCopula(IIDXCourse, IIDXBase): ) # Calculate and return statistics about this song - root = Node.void('IIDX23music') - root.set_attribute('clid', request.attribute('clid')) - root.set_attribute('mid', request.attribute('mid')) + root = Node.void("IIDX23music") + root.set_attribute("clid", request.attribute("clid")) + root.set_attribute("mid", request.attribute("mid")) attempts = self.get_clear_rates(musicid, chart) - count = attempts[musicid][chart]['total'] - clear = attempts[musicid][chart]['clears'] - full_combo = attempts[musicid][chart]['fcs'] + count = attempts[musicid][chart]["total"] + clear = attempts[musicid][chart]["clears"] + full_combo = attempts[musicid][chart]["fcs"] if count > 0: - root.set_attribute('crate', str(int((100 * clear) / count))) - root.set_attribute('frate', str(int((100 * full_combo) / count))) + root.set_attribute("crate", str(int((100 * clear) / count))) + root.set_attribute("frate", str(int((100 * full_combo) / count))) else: - root.set_attribute('crate', '0') - root.set_attribute('frate', '0') - root.set_attribute('rankside', '0') + root.set_attribute("crate", "0") + root.set_attribute("frate", "0") + root.set_attribute("rankside", "0") if userid is not None: # Shop ranking - shopdata = Node.void('shopdata') + shopdata = Node.void("shopdata") root.add_child(shopdata) - shopdata.set_attribute('rank', '-1' if oldindex is None else str(oldindex + 1)) + shopdata.set_attribute( + "rank", "-1" if oldindex is None else str(oldindex + 1) + ) # Grab the rank of some other players on this song - ranklist = Node.void('ranklist') + ranklist = Node.void("ranklist") root.add_child(ranklist) all_scores = sorted( - self.data.remote.music.get_all_scores(game=self.game, version=self.music_version, songid=musicid, songchart=chart), + self.data.remote.music.get_all_scores( + game=self.game, + version=self.music_version, + songid=musicid, + songchart=chart, + ), key=lambda s: (s[1].points, s[1].timestamp), reverse=True, ) - missing_players = [ - uid for (uid, _) in all_scores - if uid not in all_players - ] + missing_players = [uid for (uid, _) in all_scores if uid not in all_players] for (uid, prof) in self.get_any_profiles(missing_players): all_players[uid] = prof if not global_scores: all_scores = [ - score for score in all_scores + score + for score in all_scores if ( - score[0] == userid or - self.user_joined_arcade(machine, all_players[score[0]]) + score[0] == userid + or self.user_joined_arcade(machine, all_players[score[0]]) ) ] @@ -685,65 +741,80 @@ class IIDXCopula(IIDXCourse, IIDXBase): ourindex = i break if ourindex is None: - raise Exception('Cannot find our own score after saving to DB!') + raise Exception("Cannot find our own score after saving to DB!") start = ourindex - 4 end = ourindex + 4 if start < 0: start = 0 if end >= len(all_scores): end = len(all_scores) - 1 - relevant_scores = all_scores[start:(end + 1)] + relevant_scores = all_scores[start : (end + 1)] record_num = start + 1 for score in relevant_scores: profile = all_players[score[0]] - data = Node.void('data') + data = Node.void("data") ranklist.add_child(data) - data.set_attribute('iidx_id', str(profile.extid)) - data.set_attribute('name', profile.get_str('name')) + data.set_attribute("iidx_id", str(profile.extid)) + data.set_attribute("name", profile.get_str("name")) - machine_name = '' - if 'shop_location' in profile: - shop_id = profile.get_int('shop_location') + machine_name = "" + if "shop_location" in profile: + shop_id = profile.get_int("shop_location") machine = self.get_machine_by_id(shop_id) if machine is not None: machine_name = machine.name - data.set_attribute('opname', machine_name) - data.set_attribute('rnum', str(record_num)) - data.set_attribute('score', str(score[1].points)) - data.set_attribute('clflg', str(self.db_to_game_status(score[1].data.get_int('clear_status')))) - data.set_attribute('pid', str(profile.get_int('pid'))) - data.set_attribute('myFlg', '1' if score[0] == userid else '0') - data.set_attribute('update', '0') + data.set_attribute("opname", machine_name) + data.set_attribute("rnum", str(record_num)) + data.set_attribute("score", str(score[1].points)) + data.set_attribute( + "clflg", + str(self.db_to_game_status(score[1].data.get_int("clear_status"))), + ) + data.set_attribute("pid", str(profile.get_int("pid"))) + data.set_attribute("myFlg", "1" if score[0] == userid else "0") + data.set_attribute("update", "0") - data.set_attribute('sgrade', str( - self.db_to_game_rank(profile.get_int(self.DAN_RANKING_SINGLE, -1), self.GAME_CLTYPE_SINGLE), - )) - data.set_attribute('dgrade', str( - self.db_to_game_rank(profile.get_int(self.DAN_RANKING_DOUBLE, -1), self.GAME_CLTYPE_DOUBLE), - )) + data.set_attribute( + "sgrade", + str( + self.db_to_game_rank( + profile.get_int(self.DAN_RANKING_SINGLE, -1), + self.GAME_CLTYPE_SINGLE, + ), + ), + ) + data.set_attribute( + "dgrade", + str( + self.db_to_game_rank( + profile.get_int(self.DAN_RANKING_DOUBLE, -1), + self.GAME_CLTYPE_DOUBLE, + ), + ), + ) - qpro = profile.get_dict('qpro') - data.set_attribute('head', str(qpro.get_int('head'))) - data.set_attribute('hair', str(qpro.get_int('hair'))) - data.set_attribute('face', str(qpro.get_int('face'))) - data.set_attribute('body', str(qpro.get_int('body'))) - data.set_attribute('hand', str(qpro.get_int('hand'))) + qpro = profile.get_dict("qpro") + data.set_attribute("head", str(qpro.get_int("head"))) + data.set_attribute("hair", str(qpro.get_int("hair"))) + data.set_attribute("face", str(qpro.get_int("face"))) + data.set_attribute("body", str(qpro.get_int("body"))) + data.set_attribute("hand", str(qpro.get_int("hand"))) record_num = record_num + 1 return root def handle_IIDX23music_breg_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) - musicid = int(request.attribute('mid')) + extid = int(request.attribute("iidxid")) + musicid = int(request.attribute("mid")) userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: - clear_status = self.game_to_db_status(int(request.attribute('cflg'))) - pgreats = int(request.attribute('pgnum')) - greats = int(request.attribute('gnum')) + clear_status = self.game_to_db_status(int(request.attribute("cflg"))) + pgreats = int(request.attribute("pgnum")) + greats = int(request.attribute("gnum")) self.update_score( userid, @@ -753,18 +824,18 @@ class IIDXCopula(IIDXCourse, IIDXBase): pgreats, greats, -1, - b'', + b"", None, ) # Return nothing. - root = Node.void('IIDX23music') + root = Node.void("IIDX23music") return root def handle_IIDX23music_play_request(self, request: Node) -> Node: - musicid = int(request.attribute('mid')) - chart = self.game_to_db_chart(int(request.attribute('clid'))) - clear_status = self.game_to_db_status(int(request.attribute('cflg'))) + musicid = int(request.attribute("mid")) + chart = self.game_to_db_chart(int(request.attribute("clid"))) + clear_status = self.game_to_db_status(int(request.attribute("cflg"))) self.update_score( None, # No userid since its anonymous @@ -779,39 +850,41 @@ class IIDXCopula(IIDXCourse, IIDXBase): ) # Calculate and return statistics about this song - root = Node.void('IIDX23music') - root.set_attribute('clid', request.attribute('clid')) - root.set_attribute('mid', request.attribute('mid')) + root = Node.void("IIDX23music") + root.set_attribute("clid", request.attribute("clid")) + root.set_attribute("mid", request.attribute("mid")) attempts = self.get_clear_rates(musicid, chart) - count = attempts[musicid][chart]['total'] - clear = attempts[musicid][chart]['clears'] - full_combo = attempts[musicid][chart]['fcs'] + count = attempts[musicid][chart]["total"] + clear = attempts[musicid][chart]["clears"] + full_combo = attempts[musicid][chart]["fcs"] if count > 0: - root.set_attribute('crate', str(int((100 * clear) / count))) - root.set_attribute('frate', str(int((100 * full_combo) / count))) + root.set_attribute("crate", str(int((100 * clear) / count))) + root.set_attribute("frate", str(int((100 * full_combo) / count))) else: - root.set_attribute('crate', '0') - root.set_attribute('frate', '0') + root.set_attribute("crate", "0") + root.set_attribute("frate", "0") return root def handle_IIDX23music_appoint_request(self, request: Node) -> Node: - musicid = int(request.attribute('mid')) - chart = self.game_to_db_chart(int(request.attribute('clid'))) - ghost_type = int(request.attribute('ctype')) - extid = int(request.attribute('iidxid')) + musicid = int(request.attribute("mid")) + chart = self.game_to_db_chart(int(request.attribute("clid"))) + ghost_type = int(request.attribute("ctype")) + extid = int(request.attribute("iidxid")) userid = self.data.remote.user.from_extid(self.game, self.version, extid) - root = Node.void('IIDX23music') + root = Node.void("IIDX23music") if userid is not None: # Try to look up previous ghost for user - my_score = self.data.remote.music.get_score(self.game, self.music_version, userid, musicid, chart) + my_score = self.data.remote.music.get_score( + self.game, self.music_version, userid, musicid, chart + ) if my_score is not None: - mydata = Node.binary('mydata', my_score.data.get_bytes('ghost')) - mydata.set_attribute('score', str(my_score.points)) + mydata = Node.binary("mydata", my_score.data.get_bytes("ghost")) + mydata.set_attribute("score", str(my_score.points)) root.add_child(mydata) ghost_score = self.get_ghost( @@ -826,7 +899,7 @@ class IIDXCopula(IIDXCourse, IIDXBase): self.GAME_GHOST_TYPE_RIVAL_TOP: self.GHOST_TYPE_RIVAL_TOP, self.GAME_GHOST_TYPE_RIVAL_AVERAGE: self.GHOST_TYPE_RIVAL_AVERAGE, }.get(ghost_type, self.GHOST_TYPE_NONE), - request.attribute('subtype'), + request.attribute("subtype"), self.GAME_GHOST_LENGTH, musicid, chart, @@ -835,26 +908,26 @@ class IIDXCopula(IIDXCourse, IIDXBase): # Add ghost score if we support it if ghost_score is not None: - sdata = Node.binary('sdata', ghost_score['ghost']) - sdata.set_attribute('score', str(ghost_score['score'])) - if 'name' in ghost_score: - sdata.set_attribute('name', ghost_score['name']) - if 'pid' in ghost_score: - sdata.set_attribute('pid', str(ghost_score['pid'])) - if 'extid' in ghost_score: - sdata.set_attribute('riidxid', str(ghost_score['extid'])) + sdata = Node.binary("sdata", ghost_score["ghost"]) + sdata.set_attribute("score", str(ghost_score["score"])) + if "name" in ghost_score: + sdata.set_attribute("name", ghost_score["name"]) + if "pid" in ghost_score: + sdata.set_attribute("pid", str(ghost_score["pid"])) + if "extid" in ghost_score: + sdata.set_attribute("riidxid", str(ghost_score["extid"])) root.add_child(sdata) return root def handle_IIDX23grade_raised_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) - cltype = int(request.attribute('gtype')) - rank = self.game_to_db_rank(int(request.attribute('gid')), cltype) + extid = int(request.attribute("iidxid")) + cltype = int(request.attribute("gtype")) + rank = self.game_to_db_rank(int(request.attribute("gid")), cltype) userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: - percent = int(request.attribute('achi')) - stages_cleared = int(request.attribute('cstage')) + percent = int(request.attribute("achi")) + stages_cleared = int(request.attribute("cstage")) cleared = stages_cleared == self.DAN_STAGES if cltype == self.GAME_CLTYPE_SINGLE: @@ -872,24 +945,26 @@ class IIDXCopula(IIDXCourse, IIDXBase): ) # Figure out number of players that played this ranking - all_achievements = self.data.local.user.get_all_achievements(self.game, self.version, achievementid=rank, achievementtype=index) - root = Node.void('IIDX23grade') - root.set_attribute('pnum', str(len(all_achievements))) + all_achievements = self.data.local.user.get_all_achievements( + self.game, self.version, achievementid=rank, achievementtype=index + ) + root = Node.void("IIDX23grade") + root.set_attribute("pnum", str(len(all_achievements))) return root def handle_IIDX23pc_common_request(self, request: Node) -> Node: - root = Node.void('IIDX23pc') - root.set_attribute('expire', '600') + root = Node.void("IIDX23pc") + root.set_attribute("expire", "600") - ir = Node.void('ir') + ir = Node.void("ir") root.add_child(ir) - ir.set_attribute('beat', '2') + ir.set_attribute("beat", "2") # See if we configured event overrides if self.machine_joined_arcade(): game_config = self.get_game_config() - event_phase = game_config.get_int('event_phase') - omni_events = game_config.get_bool('omnimix_events_enabled') + event_phase = game_config.get_int("event_phase") + omni_events = game_config.get_bool("omnimix_events_enabled") else: # If we aren't in an arcade, we turn off events event_phase = 0 @@ -908,45 +983,45 @@ class IIDXCopula(IIDXCourse, IIDXBase): event1 = 0 event2 = event_phase - 1 - boss = Node.void('boss') + boss = Node.void("boss") root.add_child(boss) - boss.set_attribute('phase', str(boss_phase)) + boss.set_attribute("phase", str(boss_phase)) - event1_phase = Node.void('event1_phase') + event1_phase = Node.void("event1_phase") root.add_child(event1_phase) - event1_phase.set_attribute('phase', str(event1)) + event1_phase.set_attribute("phase", str(event1)) - event2_phase = Node.void('event2_phase') + event2_phase = Node.void("event2_phase") root.add_child(event2_phase) - event2_phase.set_attribute('phase', str(event2)) + event2_phase.set_attribute("phase", str(event2)) - extra_boss_event = Node.void('extra_boss_event') + extra_boss_event = Node.void("extra_boss_event") root.add_child(extra_boss_event) - extra_boss_event.set_attribute('phase', '1') + extra_boss_event.set_attribute("phase", "1") - bemani_summer2016 = Node.void('bemani_summer2016') + bemani_summer2016 = Node.void("bemani_summer2016") root.add_child(bemani_summer2016) - bemani_summer2016.set_attribute('phase', '1') + bemani_summer2016.set_attribute("phase", "1") - vip_black_pass = Node.void('vip_pass_black') + vip_black_pass = Node.void("vip_pass_black") root.add_child(vip_black_pass) - event1_rainbow_ticket = Node.void('event1_rainbow_ticket') + event1_rainbow_ticket = Node.void("event1_rainbow_ticket") root.add_child(event1_rainbow_ticket) - djlevel_result = Node.void('djlevel_result') + djlevel_result = Node.void("djlevel_result") root.add_child(djlevel_result) - newsong_another = Node.void('newsong_another') + newsong_another = Node.void("newsong_another") root.add_child(newsong_another) - newsong_another.set_attribute('open', '1') + newsong_another.set_attribute("open", "1") # Course definitions courses: List[Dict[str, Any]] = [ { - 'name': 'POP', - 'id': 1, - 'songs': [ + "name": "POP", + "id": 1, + "songs": [ 23034, 23012, 23011, @@ -954,9 +1029,9 @@ class IIDXCopula(IIDXCourse, IIDXBase): ], }, { - 'name': 'TRANCE', - 'id': 2, - 'songs': [ + "name": "TRANCE", + "id": 2, + "songs": [ 23014, 23033, 23038, @@ -964,9 +1039,9 @@ class IIDXCopula(IIDXCourse, IIDXBase): ], }, { - 'name': 'DJ', - 'id': 3, - 'songs': [ + "name": "DJ", + "id": 3, + "songs": [ 23025, 23026, 23024, @@ -974,9 +1049,9 @@ class IIDXCopula(IIDXCourse, IIDXBase): ], }, { - 'name': 'HCN', - 'id': 4, - 'songs': [ + "name": "HCN", + "id": 4, + "songs": [ 23016, 23010, 23057, @@ -984,9 +1059,9 @@ class IIDXCopula(IIDXCourse, IIDXBase): ], }, { - 'name': 'TRAIN', - 'id': 5, - 'songs': [ + "name": "TRAIN", + "id": 5, + "songs": [ 23023, 6029, 23047, @@ -994,9 +1069,9 @@ class IIDXCopula(IIDXCourse, IIDXBase): ], }, { - 'name': 'USAO', - 'id': 6, - 'songs': [ + "name": "USAO", + "id": 6, + "songs": [ 20064, 21015, 22044, @@ -1004,9 +1079,9 @@ class IIDXCopula(IIDXCourse, IIDXBase): ], }, { - 'name': 'New Face', - 'id': 7, - 'songs': [ + "name": "New Face", + "id": 7, + "songs": [ 23054, 23039, 23036, @@ -1014,9 +1089,9 @@ class IIDXCopula(IIDXCourse, IIDXBase): ], }, { - 'name': 'CANDY', - 'id': 8, - 'songs': [ + "name": "CANDY", + "id": 8, + "songs": [ 16021, 18069, 19073, @@ -1024,9 +1099,9 @@ class IIDXCopula(IIDXCourse, IIDXBase): ], }, { - 'name': 'ROCK', - 'id': 9, - 'songs': [ + "name": "ROCK", + "id": 9, + "songs": [ 8026, 23002, 22072, @@ -1034,9 +1109,9 @@ class IIDXCopula(IIDXCourse, IIDXBase): ], }, { - 'name': 'JAZZ', - 'id': 10, - 'songs': [ + "name": "JAZZ", + "id": 10, + "songs": [ 1017, 20088, 23035, @@ -1044,9 +1119,9 @@ class IIDXCopula(IIDXCourse, IIDXBase): ], }, { - 'name': 'TAIYO', - 'id': 11, - 'songs': [ + "name": "TAIYO", + "id": 11, + "songs": [ 11007, 23020, 13029, @@ -1054,9 +1129,9 @@ class IIDXCopula(IIDXCourse, IIDXBase): ], }, { - 'name': 'RHYZE', - 'id': 12, - 'songs': [ + "name": "RHYZE", + "id": 12, + "songs": [ 23029, 21070, 21071, @@ -1064,9 +1139,9 @@ class IIDXCopula(IIDXCourse, IIDXBase): ], }, { - 'name': 'COLLABORATION', - 'id': 13, - 'songs': [ + "name": "COLLABORATION", + "id": 13, + "songs": [ 20094, 20008, 20020, @@ -1078,9 +1153,9 @@ class IIDXCopula(IIDXCourse, IIDXBase): # Secret course definitions secret_courses: List[Dict[str, Any]] = [ { - 'name': 'COLORS', - 'id': 1, - 'songs': [ + "name": "COLORS", + "id": 1, + "songs": [ 20038, 20012, 20007, @@ -1088,9 +1163,9 @@ class IIDXCopula(IIDXCourse, IIDXBase): ], }, { - 'name': 'BROKEN', - 'id': 2, - 'songs': [ + "name": "BROKEN", + "id": 2, + "songs": [ 4003, 18028, 18068, @@ -1098,9 +1173,9 @@ class IIDXCopula(IIDXCourse, IIDXBase): ], }, { - 'name': 'PENDUAL', - 'id': 3, - 'songs': [ + "name": "PENDUAL", + "id": 3, + "songs": [ 22013, 22008, 22054, @@ -1108,9 +1183,9 @@ class IIDXCopula(IIDXCourse, IIDXBase): ], }, { - 'name': 'SYMMETRY', - 'id': 4, - 'songs': [ + "name": "SYMMETRY", + "id": 4, + "songs": [ 9052, 10024, 12054, @@ -1118,9 +1193,9 @@ class IIDXCopula(IIDXCourse, IIDXBase): ], }, { - 'name': 'SEVEN', - 'id': 5, - 'songs': [ + "name": "SEVEN", + "id": 5, + "songs": [ 13014, 11014, 17059, @@ -1128,9 +1203,9 @@ class IIDXCopula(IIDXCourse, IIDXBase): ], }, { - 'name': 'RAVE', - 'id': 6, - 'songs': [ + "name": "RAVE", + "id": 6, + "songs": [ 21051, 22078, 21083, @@ -1138,9 +1213,9 @@ class IIDXCopula(IIDXCourse, IIDXBase): ], }, { - 'name': 'P*Light', - 'id': 7, - 'songs': [ + "name": "P*Light", + "id": 7, + "songs": [ 22061, 23031, 22080, @@ -1148,9 +1223,9 @@ class IIDXCopula(IIDXCourse, IIDXBase): ], }, { - 'name': 'GRAND FINAL', - 'id': 8, - 'songs': [ + "name": "GRAND FINAL", + "id": 8, + "songs": [ 14021, 21032, 21045, @@ -1158,9 +1233,9 @@ class IIDXCopula(IIDXCourse, IIDXBase): ], }, { - 'name': 'SAY RYU', - 'id': 9, - 'songs': [ + "name": "SAY RYU", + "id": 9, + "songs": [ 13038, 15026, 21007, @@ -1168,9 +1243,9 @@ class IIDXCopula(IIDXCourse, IIDXBase): ], }, { - 'name': 'SUMMER', - 'id': 10, - 'songs': [ + "name": "SUMMER", + "id": 10, + "songs": [ 15048, 18005, 18021, @@ -1178,9 +1253,9 @@ class IIDXCopula(IIDXCourse, IIDXBase): ], }, { - 'name': 'ART CORE', - 'id': 11, - 'songs': [ + "name": "ART CORE", + "id": 11, + "songs": [ 10023, 22051, 20097, @@ -1188,9 +1263,9 @@ class IIDXCopula(IIDXCourse, IIDXBase): ], }, { - 'name': 'HAPPY', - 'id': 12, - 'songs': [ + "name": "HAPPY", + "id": 12, + "songs": [ 11036, 19070, 20040, @@ -1198,9 +1273,9 @@ class IIDXCopula(IIDXCourse, IIDXBase): ], }, { - 'name': 'TAG', - 'id': 13, - 'songs': [ + "name": "TAG", + "id": 13, + "songs": [ 21058, 20015, 18056, @@ -1211,71 +1286,73 @@ class IIDXCopula(IIDXCourse, IIDXBase): # For some reason, copula omnimix crashes on course mode, so don't enable it if not self.omnimix: - internet_ranking = Node.void('internet_ranking') + internet_ranking = Node.void("internet_ranking") root.add_child(internet_ranking) used_ids: List[int] = [] for c in courses: - if c['id'] in used_ids: - raise Exception('Cannot have multiple courses with the same ID!') - elif c['id'] < 0 or c['id'] >= 20: - raise Exception('Course ID is out of bounds!') + if c["id"] in used_ids: + raise Exception("Cannot have multiple courses with the same ID!") + elif c["id"] < 0 or c["id"] >= 20: + raise Exception("Course ID is out of bounds!") else: - used_ids.append(c['id']) - course = Node.void('course') + used_ids.append(c["id"]) + course = Node.void("course") internet_ranking.add_child(course) - course.set_attribute('opflg', '1') - course.set_attribute('course_id', str(c['id'])) - course.set_attribute('mid0', str(c['songs'][0])) - course.set_attribute('mid1', str(c['songs'][1])) - course.set_attribute('mid2', str(c['songs'][2])) - course.set_attribute('mid3', str(c['songs'][3])) - course.set_attribute('name', c['name']) + course.set_attribute("opflg", "1") + course.set_attribute("course_id", str(c["id"])) + course.set_attribute("mid0", str(c["songs"][0])) + course.set_attribute("mid1", str(c["songs"][1])) + course.set_attribute("mid2", str(c["songs"][2])) + course.set_attribute("mid3", str(c["songs"][3])) + course.set_attribute("name", c["name"]) - secret_ex_course = Node.void('secret_ex_course') + secret_ex_course = Node.void("secret_ex_course") root.add_child(secret_ex_course) used_secret_ids: List[int] = [] for c in secret_courses: - if c['id'] in used_secret_ids: - raise Exception('Cannot have multiple secret courses with the same ID!') - elif c['id'] < 0 or c['id'] >= 20: - raise Exception('Secret course ID is out of bounds!') + if c["id"] in used_secret_ids: + raise Exception( + "Cannot have multiple secret courses with the same ID!" + ) + elif c["id"] < 0 or c["id"] >= 20: + raise Exception("Secret course ID is out of bounds!") else: - used_secret_ids.append(c['id']) - course = Node.void('course') + used_secret_ids.append(c["id"]) + course = Node.void("course") secret_ex_course.add_child(course) - course.set_attribute('course_id', str(c['id'])) - course.set_attribute('mid0', str(c['songs'][0])) - course.set_attribute('mid1', str(c['songs'][1])) - course.set_attribute('mid2', str(c['songs'][2])) - course.set_attribute('mid3', str(c['songs'][3])) - course.set_attribute('name', c['name']) + course.set_attribute("course_id", str(c["id"])) + course.set_attribute("mid0", str(c["songs"][0])) + course.set_attribute("mid1", str(c["songs"][1])) + course.set_attribute("mid2", str(c["songs"][2])) + course.set_attribute("mid3", str(c["songs"][3])) + course.set_attribute("name", c["name"]) - expert = Node.void('expert') + expert = Node.void("expert") root.add_child(expert) - expert.set_attribute('phase', '1') + expert.set_attribute("phase", "1") - expert_random_select = Node.void('expert_random_select') + expert_random_select = Node.void("expert_random_select") root.add_child(expert_random_select) - expert_random_select.set_attribute('phase', '1') + expert_random_select.set_attribute("phase", "1") - expert_full = Node.void('expert_secret_full_open') + expert_full = Node.void("expert_secret_full_open") root.add_child(expert_full) return root def handle_IIDX23pc_delete_request(self, request: Node) -> Node: - return Node.void('IIDX23pc') + return Node.void("IIDX23pc") def handle_IIDX23pc_playstart_request(self, request: Node) -> Node: - return Node.void('IIDX23pc') + return Node.void("IIDX23pc") def handle_IIDX23pc_playend_request(self, request: Node) -> Node: - return Node.void('IIDX23pc') + return Node.void("IIDX23pc") def handle_IIDX23pc_oldget_request(self, request: Node) -> Node: - refid = request.attribute('rid') + refid = request.attribute("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: oldversion = self.previous_version() @@ -1283,12 +1360,12 @@ class IIDXCopula(IIDXCourse, IIDXBase): else: profile = None - root = Node.void('IIDX23pc') - root.set_attribute('status', '1' if profile is None else '0') + root = Node.void("IIDX23pc") + root.set_attribute("status", "1" if profile is None else "0") return root def handle_IIDX23pc_getname_request(self, request: Node) -> Node: - refid = request.attribute('rid') + refid = request.attribute("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: oldversion = self.previous_version() @@ -1297,137 +1374,139 @@ class IIDXCopula(IIDXCourse, IIDXBase): profile = None if profile is None: raise Exception( - 'Should not get here if we have no profile, we should ' + - 'have returned \'1\' in the \'oldget\' method above ' + - 'which should tell the game not to present a migration.' + "Should not get here if we have no profile, we should " + + "have returned '1' in the 'oldget' method above " + + "which should tell the game not to present a migration." ) - root = Node.void('IIDX23pc') - root.set_attribute('name', profile.get_str('name')) - root.set_attribute('idstr', ID.format_extid(profile.extid)) - root.set_attribute('pid', str(profile.get_int('pid'))) + root = Node.void("IIDX23pc") + root.set_attribute("name", profile.get_str("name")) + root.set_attribute("idstr", ID.format_extid(profile.extid)) + root.set_attribute("pid", str(profile.get_int("pid"))) return root def handle_IIDX23pc_takeover_request(self, request: Node) -> Node: - refid = request.attribute('rid') - name = request.attribute('name') - pid = int(request.attribute('pid')) + refid = request.attribute("rid") + name = request.attribute("name") + pid = int(request.attribute("pid")) newprofile = self.new_profile_by_refid(refid, name, pid) - root = Node.void('IIDX23pc') + root = Node.void("IIDX23pc") if newprofile is not None: - root.set_attribute('id', str(newprofile.extid)) + root.set_attribute("id", str(newprofile.extid)) return root def handle_IIDX23pc_reg_request(self, request: Node) -> Node: - refid = request.attribute('rid') - name = request.attribute('name') - pid = int(request.attribute('pid')) + refid = request.attribute("rid") + name = request.attribute("name") + pid = int(request.attribute("pid")) profile = self.new_profile_by_refid(refid, name, pid) - root = Node.void('IIDX23pc') + root = Node.void("IIDX23pc") if profile is not None: - root.set_attribute('id', str(profile.extid)) - root.set_attribute('id_str', ID.format_extid(profile.extid)) + root.set_attribute("id", str(profile.extid)) + root.set_attribute("id_str", ID.format_extid(profile.extid)) return root def handle_IIDX23pc_get_request(self, request: Node) -> Node: - refid = request.attribute('rid') + refid = request.attribute("rid") root = self.get_profile_by_refid(refid) if root is None: - root = Node.void('IIDX23pc') + root = Node.void("IIDX23pc") return root def handle_IIDX23pc_save_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) + extid = int(request.attribute("iidxid")) self.put_profile_by_extid(extid, request) - root = Node.void('IIDX23pc') + root = Node.void("IIDX23pc") return root def handle_IIDX23pc_visit_request(self, request: Node) -> Node: - root = Node.void('IIDX23pc') - root.set_attribute('anum', '0') - root.set_attribute('pnum', '0') - root.set_attribute('sflg', '0') - root.set_attribute('pflg', '0') - root.set_attribute('aflg', '0') - root.set_attribute('snum', '0') + root = Node.void("IIDX23pc") + root.set_attribute("anum", "0") + root.set_attribute("pnum", "0") + root.set_attribute("sflg", "0") + root.set_attribute("pflg", "0") + root.set_attribute("aflg", "0") + root.set_attribute("snum", "0") return root def handle_IIDX23pc_shopregister_request(self, request: Node) -> Node: - extid = int(request.child_value('iidx_id')) - location = ID.parse_machine_id(request.child_value('location_id')) + extid = int(request.child_value("iidx_id")) + location = ID.parse_machine_id(request.child_value("location_id")) userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: profile = self.get_profile(userid) if profile is None: profile = Profile(self.game, self.version, "", extid) - profile.replace_int('shop_location', location) + profile.replace_int("shop_location", location) self.put_profile(userid, profile) - root = Node.void('IIDX23pc') + root = Node.void("IIDX23pc") return root def handle_IIDX23pc_eaappliresult_request(self, request: Node) -> Node: clear_map = { - self.GAME_CLEAR_STATUS_NO_PLAY: 'NO PLAY', - self.GAME_CLEAR_STATUS_FAILED: 'FAILED', - self.GAME_CLEAR_STATUS_ASSIST_CLEAR: 'ASSIST CLEAR', - self.GAME_CLEAR_STATUS_EASY_CLEAR: 'EASY CLEAR', - self.GAME_CLEAR_STATUS_CLEAR: 'CLEAR', - self.GAME_CLEAR_STATUS_HARD_CLEAR: 'HARD CLEAR', - self.GAME_CLEAR_STATUS_EX_HARD_CLEAR: 'EX HARD CLEAR', - self.GAME_CLEAR_STATUS_FULL_COMBO: 'FULL COMBO', + self.GAME_CLEAR_STATUS_NO_PLAY: "NO PLAY", + self.GAME_CLEAR_STATUS_FAILED: "FAILED", + self.GAME_CLEAR_STATUS_ASSIST_CLEAR: "ASSIST CLEAR", + self.GAME_CLEAR_STATUS_EASY_CLEAR: "EASY CLEAR", + self.GAME_CLEAR_STATUS_CLEAR: "CLEAR", + self.GAME_CLEAR_STATUS_HARD_CLEAR: "HARD CLEAR", + self.GAME_CLEAR_STATUS_EX_HARD_CLEAR: "EX HARD CLEAR", + self.GAME_CLEAR_STATUS_FULL_COMBO: "FULL COMBO", } # first we'll grab the data from the packet # did = request.child_value('did') # rid = request.child_value('rid') - name = request.child_value('name') + name = request.child_value("name") # qpro_hair = request.child_value('qpro_hair') # qpro_head = request.child_value('qpro_head') # qpro_body = request.child_value('qpro_body') # qpro_hand = request.child_value('qpro_hand') - music_id = request.child_value('music_id') - class_id = request.child_value('class_id') + music_id = request.child_value("music_id") + class_id = request.child_value("class_id") # no_save = request.child_value('no_save') # is_couple = request.child_value('is_couple') # target_graph = request.child_value('target_graph') - target_exscore = request.child_value('target_exscore') + target_exscore = request.child_value("target_exscore") # pacemaker = request.child_value('pacemaker') - best_clear = request.child_value('best_clear') + best_clear = request.child_value("best_clear") # best_djlevel = request.child_value('best_djlevel') # best_exscore = request.child_value('best_exscore') # best_misscount = request.child_value('best_misscount') - now_clear = request.child_value('now_clear') + now_clear = request.child_value("now_clear") # now_djlevel = request.child_value('now_djlevel') - now_exscore = request.child_value('now_exscore') + now_exscore = request.child_value("now_exscore") # now_misscount = request.child_value('now_misscount') - now_pgreat = request.child_value('now_pgreat') - now_great = request.child_value('now_great') - now_good = request.child_value('now_good') - now_bad = request.child_value('now_bad') - now_poor = request.child_value('now_poor') - now_combo = request.child_value('now_combo') - now_fast = request.child_value('now_fast') - now_slow = request.child_value('now_slow') - best_clear_string = clear_map.get(best_clear, 'NO PLAY') - now_clear_string = clear_map.get(now_clear, 'NO PLAY') + now_pgreat = request.child_value("now_pgreat") + now_great = request.child_value("now_great") + now_good = request.child_value("now_good") + now_bad = request.child_value("now_bad") + now_poor = request.child_value("now_poor") + now_combo = request.child_value("now_combo") + now_fast = request.child_value("now_fast") + now_slow = request.child_value("now_slow") + best_clear_string = clear_map.get(best_clear, "NO PLAY") + now_clear_string = clear_map.get(now_clear, "NO PLAY") # let's get the song info first - song = self.data.local.music.get_song(self.game, self.music_version, music_id, self.game_to_db_chart(class_id)) - notecount = song.data.get('notecount', 0) + song = self.data.local.music.get_song( + self.game, self.music_version, music_id, self.game_to_db_chart(class_id) + ) + notecount = song.data.get("notecount", 0) # Construct the dictionary for the broadcast card_data = { BroadcastConstants.DJ_NAME: name, BroadcastConstants.SONG_NAME: song.name, BroadcastConstants.ARTIST_NAME: song.artist, - BroadcastConstants.DIFFICULTY: song.data.get('difficulty', 0), + BroadcastConstants.DIFFICULTY: song.data.get("difficulty", 0), BroadcastConstants.TARGET_EXSCORE: target_exscore, BroadcastConstants.EXSCORE: now_exscore, BroadcastConstants.BEST_CLEAR_STATUS: best_clear_string, BroadcastConstants.CLEAR_STATUS: now_clear_string, - BroadcastConstants.PLAY_STATS_HEADER: 'How did you do?', + BroadcastConstants.PLAY_STATS_HEADER: "How did you do?", BroadcastConstants.PERFECT_GREATS: now_pgreat, BroadcastConstants.GREATS: now_great, BroadcastConstants.GOODS: now_good, @@ -1441,194 +1520,224 @@ class IIDXCopula(IIDXCourse, IIDXBase): max_score = notecount * 2 percent = now_exscore / max_score grade = int(9 * percent) - grades = ['F', 'F', 'E', 'D', 'C', 'B', 'A', 'AA', 'AAA', 'MAX'] + grades = ["F", "F", "E", "D", "C", "B", "A", "AA", "AAA", "MAX"] card_data[BroadcastConstants.GRADE] = grades[grade] card_data[BroadcastConstants.RATE] = str(round(percent, 2)) # Try to broadcast out the score to our webhook(s) self.data.triggers.broadcast_score(card_data, self.game, song) - return Node.void('IIDX23pc') + return Node.void("IIDX23pc") def format_profile(self, userid: UserID, profile: Profile) -> Node: - root = Node.void('IIDX23pc') + root = Node.void("IIDX23pc") # Look up play stats we bridge to every mix play_stats = self.get_play_statistics(userid) # Look up judge window adjustments - judge_dict = profile.get_dict('machine_judge_adjust') + judge_dict = profile.get_dict("machine_judge_adjust") machine_judge = judge_dict.get_dict(self.config.machine.pcbid) # Profile data - pcdata = Node.void('pcdata') + pcdata = Node.void("pcdata") root.add_child(pcdata) - pcdata.set_attribute('id', str(profile.extid)) - pcdata.set_attribute('idstr', ID.format_extid(profile.extid)) - pcdata.set_attribute('name', profile.get_str('name')) - pcdata.set_attribute('pid', str(profile.get_int('pid'))) - pcdata.set_attribute('spnum', str(play_stats.get_int('single_plays'))) - pcdata.set_attribute('dpnum', str(play_stats.get_int('double_plays'))) - pcdata.set_attribute('sach', str(play_stats.get_int('single_dj_points'))) - pcdata.set_attribute('dach', str(play_stats.get_int('double_dj_points'))) - pcdata.set_attribute('mode', str(profile.get_int('mode'))) - pcdata.set_attribute('pmode', str(profile.get_int('pmode'))) - pcdata.set_attribute('rtype', str(profile.get_int('rtype'))) - pcdata.set_attribute('sp_opt', str(profile.get_int('sp_opt'))) - pcdata.set_attribute('dp_opt', str(profile.get_int('dp_opt'))) - pcdata.set_attribute('dp_opt2', str(profile.get_int('dp_opt2'))) - pcdata.set_attribute('gpos', str(profile.get_int('gpos'))) - pcdata.set_attribute('s_sorttype', str(profile.get_int('s_sorttype'))) - pcdata.set_attribute('d_sorttype', str(profile.get_int('d_sorttype'))) - pcdata.set_attribute('s_pace', str(profile.get_int('s_pace'))) - pcdata.set_attribute('d_pace', str(profile.get_int('d_pace'))) - pcdata.set_attribute('s_gno', str(profile.get_int('s_gno'))) - pcdata.set_attribute('d_gno', str(profile.get_int('d_gno'))) - pcdata.set_attribute('s_gtype', str(profile.get_int('s_gtype'))) - pcdata.set_attribute('d_gtype', str(profile.get_int('d_gtype'))) - pcdata.set_attribute('s_sdlen', str(profile.get_int('s_sdlen'))) - pcdata.set_attribute('d_sdlen', str(profile.get_int('d_sdlen'))) - pcdata.set_attribute('s_sdtype', str(profile.get_int('s_sdtype'))) - pcdata.set_attribute('d_sdtype', str(profile.get_int('d_sdtype'))) - pcdata.set_attribute('s_timing', str(profile.get_int('s_timing'))) - pcdata.set_attribute('d_timing', str(profile.get_int('d_timing'))) - pcdata.set_attribute('s_notes', str(profile.get_float('s_notes'))) - pcdata.set_attribute('d_notes', str(profile.get_float('d_notes'))) - pcdata.set_attribute('s_judge', str(profile.get_int('s_judge'))) - pcdata.set_attribute('d_judge', str(profile.get_int('d_judge'))) - pcdata.set_attribute('s_judgeAdj', str(machine_judge.get_int('single'))) - pcdata.set_attribute('d_judgeAdj', str(machine_judge.get_int('double'))) - pcdata.set_attribute('s_hispeed', str(profile.get_float('s_hispeed'))) - pcdata.set_attribute('d_hispeed', str(profile.get_float('d_hispeed'))) - pcdata.set_attribute('s_liflen', str(profile.get_int('s_lift'))) - pcdata.set_attribute('d_liflen', str(profile.get_int('d_lift'))) - pcdata.set_attribute('s_disp_judge', str(profile.get_int('s_disp_judge'))) - pcdata.set_attribute('d_disp_judge', str(profile.get_int('d_disp_judge'))) - pcdata.set_attribute('s_opstyle', str(profile.get_int('s_opstyle'))) - pcdata.set_attribute('d_opstyle', str(profile.get_int('d_opstyle'))) - pcdata.set_attribute('s_exscore', str(profile.get_int('s_exscore'))) - pcdata.set_attribute('d_exscore', str(profile.get_int('d_exscore'))) - pcdata.set_attribute('s_largejudge', str(profile.get_int('s_largejudge'))) - pcdata.set_attribute('d_largejudge', str(profile.get_int('d_largejudge'))) + pcdata.set_attribute("id", str(profile.extid)) + pcdata.set_attribute("idstr", ID.format_extid(profile.extid)) + pcdata.set_attribute("name", profile.get_str("name")) + pcdata.set_attribute("pid", str(profile.get_int("pid"))) + pcdata.set_attribute("spnum", str(play_stats.get_int("single_plays"))) + pcdata.set_attribute("dpnum", str(play_stats.get_int("double_plays"))) + pcdata.set_attribute("sach", str(play_stats.get_int("single_dj_points"))) + pcdata.set_attribute("dach", str(play_stats.get_int("double_dj_points"))) + pcdata.set_attribute("mode", str(profile.get_int("mode"))) + pcdata.set_attribute("pmode", str(profile.get_int("pmode"))) + pcdata.set_attribute("rtype", str(profile.get_int("rtype"))) + pcdata.set_attribute("sp_opt", str(profile.get_int("sp_opt"))) + pcdata.set_attribute("dp_opt", str(profile.get_int("dp_opt"))) + pcdata.set_attribute("dp_opt2", str(profile.get_int("dp_opt2"))) + pcdata.set_attribute("gpos", str(profile.get_int("gpos"))) + pcdata.set_attribute("s_sorttype", str(profile.get_int("s_sorttype"))) + pcdata.set_attribute("d_sorttype", str(profile.get_int("d_sorttype"))) + pcdata.set_attribute("s_pace", str(profile.get_int("s_pace"))) + pcdata.set_attribute("d_pace", str(profile.get_int("d_pace"))) + pcdata.set_attribute("s_gno", str(profile.get_int("s_gno"))) + pcdata.set_attribute("d_gno", str(profile.get_int("d_gno"))) + pcdata.set_attribute("s_gtype", str(profile.get_int("s_gtype"))) + pcdata.set_attribute("d_gtype", str(profile.get_int("d_gtype"))) + pcdata.set_attribute("s_sdlen", str(profile.get_int("s_sdlen"))) + pcdata.set_attribute("d_sdlen", str(profile.get_int("d_sdlen"))) + pcdata.set_attribute("s_sdtype", str(profile.get_int("s_sdtype"))) + pcdata.set_attribute("d_sdtype", str(profile.get_int("d_sdtype"))) + pcdata.set_attribute("s_timing", str(profile.get_int("s_timing"))) + pcdata.set_attribute("d_timing", str(profile.get_int("d_timing"))) + pcdata.set_attribute("s_notes", str(profile.get_float("s_notes"))) + pcdata.set_attribute("d_notes", str(profile.get_float("d_notes"))) + pcdata.set_attribute("s_judge", str(profile.get_int("s_judge"))) + pcdata.set_attribute("d_judge", str(profile.get_int("d_judge"))) + pcdata.set_attribute("s_judgeAdj", str(machine_judge.get_int("single"))) + pcdata.set_attribute("d_judgeAdj", str(machine_judge.get_int("double"))) + pcdata.set_attribute("s_hispeed", str(profile.get_float("s_hispeed"))) + pcdata.set_attribute("d_hispeed", str(profile.get_float("d_hispeed"))) + pcdata.set_attribute("s_liflen", str(profile.get_int("s_lift"))) + pcdata.set_attribute("d_liflen", str(profile.get_int("d_lift"))) + pcdata.set_attribute("s_disp_judge", str(profile.get_int("s_disp_judge"))) + pcdata.set_attribute("d_disp_judge", str(profile.get_int("d_disp_judge"))) + pcdata.set_attribute("s_opstyle", str(profile.get_int("s_opstyle"))) + pcdata.set_attribute("d_opstyle", str(profile.get_int("d_opstyle"))) + pcdata.set_attribute("s_exscore", str(profile.get_int("s_exscore"))) + pcdata.set_attribute("d_exscore", str(profile.get_int("d_exscore"))) + pcdata.set_attribute("s_largejudge", str(profile.get_int("s_largejudge"))) + pcdata.set_attribute("d_largejudge", str(profile.get_int("d_largejudge"))) - premium_unlocks = Node.void('ea_premium_course') + premium_unlocks = Node.void("ea_premium_course") root.add_child(premium_unlocks) # Secret flags (shh!) - secret_dict = profile.get_dict('secret') - secret = Node.void('secret') + secret_dict = profile.get_dict("secret") + secret = Node.void("secret") root.add_child(secret) - secret.add_child(Node.s64_array('flg1', secret_dict.get_int_array('flg1', 4))) - secret.add_child(Node.s64_array('flg2', secret_dict.get_int_array('flg2', 4))) - secret.add_child(Node.s64_array('flg3', secret_dict.get_int_array('flg3', 4))) + secret.add_child(Node.s64_array("flg1", secret_dict.get_int_array("flg1", 4))) + secret.add_child(Node.s64_array("flg2", secret_dict.get_int_array("flg2", 4))) + secret.add_child(Node.s64_array("flg3", secret_dict.get_int_array("flg3", 4))) # Favorites - for folder in ['favorite1', 'favorite2', 'favorite3']: + for folder in ["favorite1", "favorite2", "favorite3"]: favorite_dict = profile.get_dict(folder) - sp_mlist = b'' - sp_clist = b'' - singles_list = favorite_dict['single'] if 'single' in favorite_dict else [] + sp_mlist = b"" + sp_clist = b"" + singles_list = favorite_dict["single"] if "single" in favorite_dict else [] for single in singles_list: - sp_mlist = sp_mlist + struct.pack(' Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() play_stats = self.get_play_statistics(userid) # Track play counts - cltype = int(request.attribute('cltype')) + cltype = int(request.attribute("cltype")) if cltype == self.GAME_CLTYPE_SINGLE: - play_stats.increment_int('single_plays') + play_stats.increment_int("single_plays") if cltype == self.GAME_CLTYPE_DOUBLE: - play_stats.increment_int('double_plays') + play_stats.increment_int("double_plays") # Track DJ points - play_stats.replace_int('single_dj_points', int(request.attribute('s_achi'))) - play_stats.replace_int('double_dj_points', int(request.attribute('d_achi'))) + play_stats.replace_int("single_dj_points", int(request.attribute("s_achi"))) + play_stats.replace_int("double_dj_points", int(request.attribute("d_achi"))) # Profile settings - newprofile.replace_int('mode', int(request.attribute('mode'))) - newprofile.replace_int('pmode', int(request.attribute('pmode'))) - newprofile.replace_int('rtype', int(request.attribute('rtype'))) - newprofile.replace_int('s_lift', int(request.attribute('s_lift'))) - newprofile.replace_int('d_lift', int(request.attribute('d_lift'))) - newprofile.replace_int('sp_opt', int(request.attribute('sp_opt'))) - newprofile.replace_int('dp_opt', int(request.attribute('dp_opt'))) - newprofile.replace_int('dp_opt2', int(request.attribute('dp_opt2'))) - newprofile.replace_int('gpos', int(request.attribute('gpos'))) - newprofile.replace_int('s_sorttype', int(request.attribute('s_sorttype'))) - newprofile.replace_int('d_sorttype', int(request.attribute('d_sorttype'))) - newprofile.replace_int('s_pace', int(request.attribute('s_pace'))) - newprofile.replace_int('d_pace', int(request.attribute('d_pace'))) - newprofile.replace_int('s_gno', int(request.attribute('s_gno'))) - newprofile.replace_int('d_gno', int(request.attribute('d_gno'))) - newprofile.replace_int('s_gtype', int(request.attribute('s_gtype'))) - newprofile.replace_int('d_gtype', int(request.attribute('d_gtype'))) - newprofile.replace_int('s_sdlen', int(request.attribute('s_sdlen'))) - newprofile.replace_int('d_sdlen', int(request.attribute('d_sdlen'))) - newprofile.replace_int('s_sdtype', int(request.attribute('s_sdtype'))) - newprofile.replace_int('d_sdtype', int(request.attribute('d_sdtype'))) - newprofile.replace_int('s_timing', int(request.attribute('s_timing'))) - newprofile.replace_int('d_timing', int(request.attribute('d_timing'))) - newprofile.replace_float('s_notes', float(request.attribute('s_notes'))) - newprofile.replace_float('d_notes', float(request.attribute('d_notes'))) - newprofile.replace_int('s_judge', int(request.attribute('s_judge'))) - newprofile.replace_int('d_judge', int(request.attribute('d_judge'))) - newprofile.replace_float('s_hispeed', float(request.attribute('s_hispeed'))) - newprofile.replace_float('d_hispeed', float(request.attribute('d_hispeed'))) - newprofile.replace_int('s_disp_judge', int(request.attribute('s_disp_judge'))) - newprofile.replace_int('d_disp_judge', int(request.attribute('d_disp_judge'))) - newprofile.replace_int('s_opstyle', int(request.attribute('s_opstyle'))) - newprofile.replace_int('d_opstyle', int(request.attribute('d_opstyle'))) - newprofile.replace_int('s_exscore', int(request.attribute('s_exscore'))) - newprofile.replace_int('d_exscore', int(request.attribute('d_exscore'))) - newprofile.replace_int('s_largejudge', int(request.attribute('s_largejudge'))) - newprofile.replace_int('d_largejudge', int(request.attribute('d_largejudge'))) + newprofile.replace_int("mode", int(request.attribute("mode"))) + newprofile.replace_int("pmode", int(request.attribute("pmode"))) + newprofile.replace_int("rtype", int(request.attribute("rtype"))) + newprofile.replace_int("s_lift", int(request.attribute("s_lift"))) + newprofile.replace_int("d_lift", int(request.attribute("d_lift"))) + newprofile.replace_int("sp_opt", int(request.attribute("sp_opt"))) + newprofile.replace_int("dp_opt", int(request.attribute("dp_opt"))) + newprofile.replace_int("dp_opt2", int(request.attribute("dp_opt2"))) + newprofile.replace_int("gpos", int(request.attribute("gpos"))) + newprofile.replace_int("s_sorttype", int(request.attribute("s_sorttype"))) + newprofile.replace_int("d_sorttype", int(request.attribute("d_sorttype"))) + newprofile.replace_int("s_pace", int(request.attribute("s_pace"))) + newprofile.replace_int("d_pace", int(request.attribute("d_pace"))) + newprofile.replace_int("s_gno", int(request.attribute("s_gno"))) + newprofile.replace_int("d_gno", int(request.attribute("d_gno"))) + newprofile.replace_int("s_gtype", int(request.attribute("s_gtype"))) + newprofile.replace_int("d_gtype", int(request.attribute("d_gtype"))) + newprofile.replace_int("s_sdlen", int(request.attribute("s_sdlen"))) + newprofile.replace_int("d_sdlen", int(request.attribute("d_sdlen"))) + newprofile.replace_int("s_sdtype", int(request.attribute("s_sdtype"))) + newprofile.replace_int("d_sdtype", int(request.attribute("d_sdtype"))) + newprofile.replace_int("s_timing", int(request.attribute("s_timing"))) + newprofile.replace_int("d_timing", int(request.attribute("d_timing"))) + newprofile.replace_float("s_notes", float(request.attribute("s_notes"))) + newprofile.replace_float("d_notes", float(request.attribute("d_notes"))) + newprofile.replace_int("s_judge", int(request.attribute("s_judge"))) + newprofile.replace_int("d_judge", int(request.attribute("d_judge"))) + newprofile.replace_float("s_hispeed", float(request.attribute("s_hispeed"))) + newprofile.replace_float("d_hispeed", float(request.attribute("d_hispeed"))) + newprofile.replace_int("s_disp_judge", int(request.attribute("s_disp_judge"))) + newprofile.replace_int("d_disp_judge", int(request.attribute("d_disp_judge"))) + newprofile.replace_int("s_opstyle", int(request.attribute("s_opstyle"))) + newprofile.replace_int("d_opstyle", int(request.attribute("d_opstyle"))) + newprofile.replace_int("s_exscore", int(request.attribute("s_exscore"))) + newprofile.replace_int("d_exscore", int(request.attribute("d_exscore"))) + newprofile.replace_int("s_largejudge", int(request.attribute("s_largejudge"))) + newprofile.replace_int("d_largejudge", int(request.attribute("d_largejudge"))) # Update judge window adjustments per-machine - judge_dict = newprofile.get_dict('machine_judge_adjust') + judge_dict = newprofile.get_dict("machine_judge_adjust") machine_judge = judge_dict.get_dict(self.config.machine.pcbid) - machine_judge.replace_int('single', int(request.attribute('s_judgeAdj'))) - machine_judge.replace_int('double', int(request.attribute('d_judgeAdj'))) + machine_judge.replace_int("single", int(request.attribute("s_judgeAdj"))) + machine_judge.replace_int("double", int(request.attribute("d_judgeAdj"))) judge_dict.replace_dict(self.config.machine.pcbid, machine_judge) - newprofile.replace_dict('machine_judge_adjust', judge_dict) + newprofile.replace_dict("machine_judge_adjust", judge_dict) # Secret flags saving - secret = request.child('secret') + secret = request.child("secret") if secret is not None: - secret_dict = newprofile.get_dict('secret') - secret_dict.replace_int_array('flg1', 4, secret.child_value('flg1')) - secret_dict.replace_int_array('flg2', 4, secret.child_value('flg2')) - secret_dict.replace_int_array('flg3', 4, secret.child_value('flg3')) - newprofile.replace_dict('secret', secret_dict) + secret_dict = newprofile.get_dict("secret") + secret_dict.replace_int_array("flg1", 4, secret.child_value("flg1")) + secret_dict.replace_int_array("flg2", 4, secret.child_value("flg2")) + secret_dict.replace_int_array("flg3", 4, secret.child_value("flg3")) + newprofile.replace_dict("secret", secret_dict) # Basic achievements - achievements = request.child('achievements') + achievements = request.child("achievements") if achievements is not None: - newprofile.replace_int('visit_flg', int(achievements.attribute('visit_flg'))) - newprofile.replace_int('last_weekly', int(achievements.attribute('last_weekly'))) - newprofile.replace_int('weekly_num', int(achievements.attribute('weekly_num'))) + newprofile.replace_int( + "visit_flg", int(achievements.attribute("visit_flg")) + ) + newprofile.replace_int( + "last_weekly", int(achievements.attribute("last_weekly")) + ) + newprofile.replace_int( + "weekly_num", int(achievements.attribute("weekly_num")) + ) - pack_id = int(achievements.attribute('pack_id')) + pack_id = int(achievements.attribute("pack_id")) if pack_id > 0: self.data.local.user.put_achievement( self.game, self.version, userid, pack_id, - 'daily', + "daily", { - 'pack_flg': int(achievements.attribute('pack_flg')), - 'pack_comp': int(achievements.attribute('pack_comp')), + "pack_flg": int(achievements.attribute("pack_flg")), + "pack_comp": int(achievements.attribute("pack_comp")), }, ) - trophies = achievements.child('trophy') + trophies = achievements.child("trophy") if trophies is not None: # We only load the first 10 in profile load. - newprofile.replace_int_array('trophy', 10, trophies.value[:10]) + newprofile.replace_int_array("trophy", 10, trophies.value[:10]) # Deller saving - deller = request.child('deller') + deller = request.child("deller") if deller is not None: - newprofile.replace_int('deller', newprofile.get_int('deller') + int(deller.attribute('deller'))) + newprofile.replace_int( + "deller", newprofile.get_int("deller") + int(deller.attribute("deller")) + ) # Secret course expert point saving - expert_point = request.child('expert_point') + expert_point = request.child("expert_point") if expert_point is not None: - courseid = int(expert_point.attribute('course_id')) + courseid = int(expert_point.attribute("course_id")) # Update achievement to track expert points expert_point_achievement = self.data.local.user.get_achievement( @@ -1964,21 +2178,21 @@ class IIDXCopula(IIDXCourse, IIDXBase): self.version, userid, courseid, - 'expert_point', + "expert_point", ) if expert_point_achievement is None: expert_point_achievement = ValidatedDict() expert_point_achievement.replace_int( - 'normal_points', - int(expert_point.attribute('n_point')), + "normal_points", + int(expert_point.attribute("n_point")), ) expert_point_achievement.replace_int( - 'hyper_points', - int(expert_point.attribute('h_point')), + "hyper_points", + int(expert_point.attribute("h_point")), ) expert_point_achievement.replace_int( - 'another_points', - int(expert_point.attribute('a_point')), + "another_points", + int(expert_point.attribute("a_point")), ) self.data.local.user.put_achievement( @@ -1986,7 +2200,7 @@ class IIDXCopula(IIDXCourse, IIDXBase): self.version, userid, courseid, - 'expert_point', + "expert_point", expert_point_achievement, ) @@ -1995,131 +2209,183 @@ class IIDXCopula(IIDXCourse, IIDXBase): singles = [] doubles = [] name = None - if favorite.name in ['favorite', 'extra_favorite']: - if favorite.name == 'favorite': - name = 'favorite1' - elif favorite.name == 'extra_favorite': - folder = favorite.attribute('folder_id') - if folder == '0': - name = 'favorite2' - if folder == '1': - name = 'favorite3' + if favorite.name in ["favorite", "extra_favorite"]: + if favorite.name == "favorite": + name = "favorite1" + elif favorite.name == "extra_favorite": + folder = favorite.attribute("folder_id") + if folder == "0": + name = "favorite2" + if folder == "1": + name = "favorite3" if name is None: continue - single_music_bin = favorite.child_value('sp_mlist') - single_chart_bin = favorite.child_value('sp_clist') - double_music_bin = favorite.child_value('dp_mlist') - double_chart_bin = favorite.child_value('dp_clist') + single_music_bin = favorite.child_value("sp_mlist") + single_chart_bin = favorite.child_value("sp_clist") + double_music_bin = favorite.child_value("dp_mlist") + double_chart_bin = favorite.child_value("dp_clist") for i in range(self.FAVORITE_LIST_LENGTH): - singles.append({ - 'id': struct.unpack(' Tuple[int, int]: return (int(courseid / 6), courseid % 6) @@ -57,11 +57,15 @@ class IIDXCourse(IIDXBase): ) if course_score is None: course_score = ValidatedDict() - course_score.replace_int('clear_status', max(clear_status, course_score.get_int('clear_status'))) - old_ex_score = (course_score.get_int('pgnum') * 2) + course_score.get_int('gnum') + course_score.replace_int( + "clear_status", max(clear_status, course_score.get_int("clear_status")) + ) + old_ex_score = (course_score.get_int("pgnum") * 2) + course_score.get_int( + "gnum" + ) if old_ex_score < ((pgreats * 2) + greats): - course_score.replace_int('pgnum', pgreats) - course_score.replace_int('gnum', greats) + course_score.replace_int("pgnum", pgreats) + course_score.replace_int("gnum", greats) self.data.local.user.put_achievement( self.game, diff --git a/bemani/backend/iidx/factory.py b/bemani/backend/iidx/factory.py index ef3eb42..8981197 100644 --- a/bemani/backend/iidx/factory.py +++ b/bemani/backend/iidx/factory.py @@ -70,12 +70,17 @@ class IIDXFactory(Factory): @classmethod def register_all(cls) -> None: - for gamecode in ['JDJ', 'JDZ', 'KDZ', 'LDJ']: + for gamecode in ["JDJ", "JDZ", "KDZ", "LDJ"]: Base.register(gamecode, IIDXFactory) @classmethod - def create(cls, data: Data, config: Config, model: Model, parentmodel: Optional[Model]=None) -> Optional[Base]: - + def create( + cls, + data: Data, + config: Config, + model: Model, + parentmodel: Optional[Model] = None, + ) -> Optional[Base]: def version_from_date(date: int) -> Optional[int]: if date < 2013100200: return VersionConstants.IIDX_TRICORO @@ -97,20 +102,20 @@ class IIDXFactory(Factory): return VersionConstants.IIDX_BISTROVER return None - if model.gamecode == 'JDJ': + if model.gamecode == "JDJ": return IIDXSirius(data, config, model) - if model.gamecode == 'JDZ': + if model.gamecode == "JDZ": return IIDXResortAnthem(data, config, model) - if model.gamecode == 'KDZ': + if model.gamecode == "KDZ": return IIDXLincle(data, config, model) - if model.gamecode == 'LDJ': + if model.gamecode == "LDJ": if model.version is None: if parentmodel is None: return None # We have no way to tell apart newer versions. However, we can make # an educated guess if we happen to be summoned for old profile lookup. - if parentmodel.gamecode not in ['JDJ', 'JDZ', 'KDZ', 'LDJ']: + if parentmodel.gamecode not in ["JDJ", "JDZ", "KDZ", "LDJ"]: return None parentversion = version_from_date(parentmodel.version) if parentversion == VersionConstants.IIDX_SPADA: diff --git a/bemani/backend/iidx/heroicverse.py b/bemani/backend/iidx/heroicverse.py index bcb1409..4ecb650 100644 --- a/bemani/backend/iidx/heroicverse.py +++ b/bemani/backend/iidx/heroicverse.py @@ -8,7 +8,7 @@ from bemani.common import VersionConstants class IIDXHeroicVerse(IIDXBase): - name: str = 'Beatmania IIDX HEROIC VERSE' + name: str = "Beatmania IIDX HEROIC VERSE" version: int = VersionConstants.IIDX_HEROIC_VERSE requires_extended_regions: bool = True diff --git a/bemani/backend/iidx/pendual.py b/bemani/backend/iidx/pendual.py index 8a1e017..1545804 100644 --- a/bemani/backend/iidx/pendual.py +++ b/bemani/backend/iidx/pendual.py @@ -8,14 +8,21 @@ from bemani.backend.iidx.base import IIDXBase from bemani.backend.iidx.course import IIDXCourse from bemani.backend.iidx.spada import IIDXSpada -from bemani.common import Profile, ValidatedDict, VersionConstants, BroadcastConstants, Time, ID +from bemani.common import ( + Profile, + ValidatedDict, + VersionConstants, + BroadcastConstants, + Time, + ID, +) from bemani.data import Data, UserID from bemani.protocol import Node class IIDXPendual(IIDXCourse, IIDXBase): - name: str = 'Beatmania IIDX PENDUAL' + name: str = "Beatmania IIDX PENDUAL" version: int = VersionConstants.IIDX_PENDUAL GAME_CLTYPE_SINGLE: Final[int] = 0 @@ -95,37 +102,54 @@ class IIDXPendual(IIDXCourse, IIDXBase): return IIDXSpada(self.data, self.config, self.model) @classmethod - def run_scheduled_work(cls, data: Data, config: Dict[str, Any]) -> List[Tuple[str, Dict[str, Any]]]: + def run_scheduled_work( + cls, data: Data, config: Dict[str, Any] + ) -> List[Tuple[str, Dict[str, Any]]]: """ Insert dailies into the DB. """ events = [] - if data.local.network.should_schedule(cls.game, cls.version, 'daily_charts', 'daily'): + if data.local.network.should_schedule( + cls.game, cls.version, "daily_charts", "daily" + ): # Generate a new list of three dailies. - start_time, end_time = data.local.network.get_schedule_duration('daily') - all_songs = list(set([song.id for song in data.local.music.get_all_songs(cls.game, cls.version)])) + start_time, end_time = data.local.network.get_schedule_duration("daily") + all_songs = list( + set( + [ + song.id + for song in data.local.music.get_all_songs( + cls.game, cls.version + ) + ] + ) + ) if len(all_songs) >= 3: daily_songs = random.sample(all_songs, 3) data.local.game.put_time_sensitive_settings( cls.game, cls.version, - 'dailies', + "dailies", { - 'start_time': start_time, - 'end_time': end_time, - 'music': daily_songs, + "start_time": start_time, + "end_time": end_time, + "music": daily_songs, }, ) - events.append(( - 'iidx_daily_charts', - { - 'version': cls.version, - 'music': daily_songs, - }, - )) + events.append( + ( + "iidx_daily_charts", + { + "version": cls.version, + "music": daily_songs, + }, + ) + ) # Mark that we did some actual work here. - data.local.network.mark_scheduled(cls.game, cls.version, 'daily_charts', 'daily') + data.local.network.mark_scheduled( + cls.game, cls.version, "daily_charts", "daily" + ) return events @classmethod @@ -134,50 +158,50 @@ class IIDXPendual(IIDXCourse, IIDXBase): Return all of our front-end modifiably settings. """ return { - 'bools': [ + "bools": [ { - 'name': 'Global Shop Ranking', - 'tip': 'Return network-wide ranking instead of shop ranking on results screen.', - 'category': 'game_config', - 'setting': 'global_shop_ranking', + "name": "Global Shop Ranking", + "tip": "Return network-wide ranking instead of shop ranking on results screen.", + "category": "game_config", + "setting": "global_shop_ranking", }, { - 'name': 'Enable Coca-ColaXBEMANI Event', - 'tip': 'Enables the IIDX BLUE Gorgia Coffee Coke event.', - 'category': 'game_config', - 'setting': 'ccxbm_enable', + "name": "Enable Coca-ColaXBEMANI Event", + "tip": "Enables the IIDX BLUE Gorgia Coffee Coke event.", + "category": "game_config", + "setting": "ccxbm_enable", }, { - 'name': 'Events In Omnimix', - 'tip': 'Allow events to be enabled at all for Omnimix.', - 'category': 'game_config', - 'setting': 'omnimix_events_enabled', + "name": "Events In Omnimix", + "tip": "Allow events to be enabled at all for Omnimix.", + "category": "game_config", + "setting": "omnimix_events_enabled", }, ], - 'ints': [ + "ints": [ { - 'name': 'Present/Future Cycle', - 'tip': 'Override server defaults for present/future cycle.', - 'category': 'game_config', - 'setting': 'cycle_config', - 'values': { - 0: 'Standard Rotation', - 1: 'Swap Present/Future', - 2: 'Force Present', - 3: 'Force Future', - } + "name": "Present/Future Cycle", + "tip": "Override server defaults for present/future cycle.", + "category": "game_config", + "setting": "cycle_config", + "values": { + 0: "Standard Rotation", + 1: "Swap Present/Future", + 2: "Force Present", + 3: "Force Future", + }, }, { - 'name': 'Event Phase', - 'tip': 'Sets the machine event phase.', - 'category': 'game_config', - 'setting': 'event_phase', - 'values': { - 0: 'No Event', - 1: 'Chrono Seeker', + "name": "Event Phase", + "tip": "Sets the machine event phase.", + "category": "game_config", + "setting": "event_phase", + "values": { + 0: "No Event", + 1: "Chrono Seeker", # 2: 'Qpronicle Chord', TODO: Finish this event - 3: '-Densetsu renkin- PENDUAL TALISMAN', - } + 3: "-Densetsu renkin- PENDUAL TALISMAN", + }, }, ], } @@ -194,57 +218,61 @@ class IIDXPendual(IIDXCourse, IIDXBase): }[db_chart] def handle_IIDX22shop_getname_request(self, request: Node) -> Node: - root = Node.void('IIDX22shop') - root.set_attribute('cls_opt', '0') + root = Node.void("IIDX22shop") + root.set_attribute("cls_opt", "0") machine = self.data.local.machine.get_machine(self.config.machine.pcbid) - root.set_attribute('opname', machine.name) - root.set_attribute('pid', str(self.get_machine_region())) + root.set_attribute("opname", machine.name) + root.set_attribute("pid", str(self.get_machine_region())) return root def handle_IIDX22shop_savename_request(self, request: Node) -> Node: - self.update_machine_name(request.attribute('opname')) - root = Node.void('IIDX22shop') + self.update_machine_name(request.attribute("opname")) + root = Node.void("IIDX22shop") return root def handle_IIDX22shop_sentinfo_request(self, request: Node) -> Node: - root = Node.void('IIDX22shop') + root = Node.void("IIDX22shop") return root def handle_IIDX22shop_getconvention_request(self, request: Node) -> Node: - root = Node.void('IIDX22shop') + root = Node.void("IIDX22shop") machine = self.data.local.machine.get_machine(self.config.machine.pcbid) if machine.arcade is not None: - course = self.data.local.machine.get_settings(machine.arcade, self.game, self.music_version, 'shop_course') + course = self.data.local.machine.get_settings( + machine.arcade, self.game, self.music_version, "shop_course" + ) else: course = None if course is None: course = ValidatedDict() - root.set_attribute('music_0', str(course.get_int('music_0', 20032))) - root.set_attribute('music_1', str(course.get_int('music_1', 20009))) - root.set_attribute('music_2', str(course.get_int('music_2', 20015))) - root.set_attribute('music_3', str(course.get_int('music_3', 20064))) - root.add_child(Node.bool('valid', course.get_bool('valid'))) + root.set_attribute("music_0", str(course.get_int("music_0", 20032))) + root.set_attribute("music_1", str(course.get_int("music_1", 20009))) + root.set_attribute("music_2", str(course.get_int("music_2", 20015))) + root.set_attribute("music_3", str(course.get_int("music_3", 20064))) + root.add_child(Node.bool("valid", course.get_bool("valid"))) return root def handle_IIDX22shop_setconvention_request(self, request: Node) -> Node: - root = Node.void('IIDX22shop') + root = Node.void("IIDX22shop") machine = self.data.local.machine.get_machine(self.config.machine.pcbid) if machine.arcade is not None: course = ValidatedDict() - course.replace_int('music_0', request.child_value('music_0')) - course.replace_int('music_1', request.child_value('music_1')) - course.replace_int('music_2', request.child_value('music_2')) - course.replace_int('music_3', request.child_value('music_3')) - course.replace_bool('valid', request.child_value('valid')) - self.data.local.machine.put_settings(machine.arcade, self.game, self.music_version, 'shop_course', course) + course.replace_int("music_0", request.child_value("music_0")) + course.replace_int("music_1", request.child_value("music_1")) + course.replace_int("music_2", request.child_value("music_2")) + course.replace_int("music_3", request.child_value("music_3")) + course.replace_bool("valid", request.child_value("valid")) + self.data.local.machine.put_settings( + machine.arcade, self.game, self.music_version, "shop_course", course + ) return root def handle_IIDX22ranking_getranker_request(self, request: Node) -> Node: - root = Node.void('IIDX22ranking') - chart = self.game_to_db_chart(int(request.attribute('clid'))) + root = Node.void("IIDX22ranking") + chart = self.game_to_db_chart(int(request.attribute("clid"))) if chart not in [ self.CHART_TYPE_N7, self.CHART_TYPE_H7, @@ -258,29 +286,31 @@ class IIDXPendual(IIDXCourse, IIDXBase): machine = self.data.local.machine.get_machine(self.config.machine.pcbid) if machine.arcade is not None: - course = self.data.local.machine.get_settings(machine.arcade, self.game, self.music_version, 'shop_course') + course = self.data.local.machine.get_settings( + machine.arcade, self.game, self.music_version, "shop_course" + ) else: course = None if course is None: course = ValidatedDict() - if not course.get_bool('valid'): + if not course.get_bool("valid"): # Shop course not enabled or not present return root - convention = Node.void('convention') + convention = Node.void("convention") root.add_child(convention) - convention.set_attribute('clid', str(chart)) - convention.set_attribute('update_date', str(Time.now() * 1000)) + convention.set_attribute("clid", str(chart)) + convention.set_attribute("update_date", str(Time.now() * 1000)) # Grab all scores for each of the four songs, filter out people who haven't # set us as their arcade and then return the top 20 scores (adding all 4 songs). songids = [ - course.get_int('music_0'), - course.get_int('music_1'), - course.get_int('music_2'), - course.get_int('music_3'), + course.get_int("music_0"), + course.get_int("music_1"), + course.get_int("music_2"), + course.get_int("music_3"), ] totalscores: Dict[UserID, int] = {} @@ -317,37 +347,37 @@ class IIDXPendual(IIDXCourse, IIDXBase): for topscore in topscores: rank = rank + 1 - detail = Node.void('detail') + detail = Node.void("detail") convention.add_child(detail) - detail.set_attribute('name', topscore[1].get_str('name')) - detail.set_attribute('rank', str(rank)) - detail.set_attribute('score', str(topscore[0])) - detail.set_attribute('pid', str(topscore[1].get_int('pid'))) + detail.set_attribute("name", topscore[1].get_str("name")) + detail.set_attribute("rank", str(rank)) + detail.set_attribute("score", str(topscore[0])) + detail.set_attribute("pid", str(topscore[1].get_int("pid"))) - qpro = topscore[1].get_dict('qpro') - detail.set_attribute('head', str(qpro.get_int('head'))) - detail.set_attribute('hair', str(qpro.get_int('hair'))) - detail.set_attribute('face', str(qpro.get_int('face'))) - detail.set_attribute('body', str(qpro.get_int('body'))) - detail.set_attribute('hand', str(qpro.get_int('hand'))) + qpro = topscore[1].get_dict("qpro") + detail.set_attribute("head", str(qpro.get_int("head"))) + detail.set_attribute("hair", str(qpro.get_int("hair"))) + detail.set_attribute("face", str(qpro.get_int("face"))) + detail.set_attribute("body", str(qpro.get_int("body"))) + detail.set_attribute("hand", str(qpro.get_int("hand"))) return root def handle_IIDX22ranking_entry_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) - courseid = int(request.attribute('coid')) - chart = self.game_to_db_chart(int(request.attribute('clid'))) - course_type = int(request.attribute('regist_type')) - clear_status = self.game_to_db_status(int(request.attribute('clr'))) - pgreats = int(request.attribute('pgnum')) - greats = int(request.attribute('gnum')) + extid = int(request.attribute("iidxid")) + courseid = int(request.attribute("coid")) + chart = self.game_to_db_chart(int(request.attribute("clid"))) + course_type = int(request.attribute("regist_type")) + clear_status = self.game_to_db_status(int(request.attribute("clr"))) + pgreats = int(request.attribute("pgnum")) + greats = int(request.attribute("gnum")) if course_type == 0: index = self.COURSE_TYPE_INTERNET_RANKING elif course_type == 1: index = self.COURSE_TYPE_SECRET else: - raise Exception('Unknown registration type for course entry!') + raise Exception("Unknown registration type for course entry!") userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: @@ -364,9 +394,9 @@ class IIDXPendual(IIDXCourse, IIDXBase): # We should return the user's position, but its not displayed anywhere # so fuck it. - root = Node.void('IIDX22ranking') - root.set_attribute('anum', '1') - root.set_attribute('jun', '1') + root = Node.void("IIDX22ranking") + root.set_attribute("anum", "1") + root.set_attribute("jun", "1") return root def db_to_game_status(self, db_status: int) -> int: @@ -441,7 +471,7 @@ class IIDXPendual(IIDXCourse, IIDXBase): self.DAN_RANK_KAIDEN: self.GAME_DP_DAN_RANK_KAIDEN, }[db_dan] else: - raise Exception('Invalid cltype!') + raise Exception("Invalid cltype!") def game_to_db_rank(self, game_dan: int, cltype: int) -> int: # Special case for no DAN rank @@ -489,13 +519,22 @@ class IIDXPendual(IIDXCourse, IIDXBase): self.GAME_DP_DAN_RANK_KAIDEN: self.DAN_RANK_KAIDEN, }[game_dan] else: - raise Exception('Invalid cltype!') + raise Exception("Invalid cltype!") def handle_IIDX22music_crate_request(self, request: Node) -> Node: - root = Node.void('IIDX22music') + root = Node.void("IIDX22music") attempts = self.get_clear_rates() - all_songs = list(set([song.id for song in self.data.local.music.get_all_songs(self.game, self.music_version)])) + all_songs = list( + set( + [ + song.id + for song in self.data.local.music.get_all_songs( + self.game, self.music_version + ) + ] + ) + ) for song in all_songs: clears = [] fcs = [] @@ -504,33 +543,33 @@ class IIDXPendual(IIDXCourse, IIDXBase): placed = False if song in attempts and chart in attempts[song]: values = attempts[song][chart] - if values['total'] > 0: - clears.append(int((100 * values['clears']) / values['total'])) - fcs.append(int((100 * values['fcs']) / values['total'])) + if values["total"] > 0: + clears.append(int((100 * values["clears"]) / values["total"])) + fcs.append(int((100 * values["fcs"]) / values["total"])) placed = True if not placed: clears.append(101) fcs.append(101) - clearnode = Node.u8_array('c', clears + fcs) - clearnode.set_attribute('mid', str(song)) + clearnode = Node.u8_array("c", clears + fcs) + clearnode.set_attribute("mid", str(song)) root.add_child(clearnode) return root def handle_IIDX22music_getrank_request(self, request: Node) -> Node: - cltype = int(request.attribute('cltype')) + cltype = int(request.attribute("cltype")) - root = Node.void('IIDX22music') - style = Node.void('style') + root = Node.void("IIDX22music") + style = Node.void("style") root.add_child(style) - style.set_attribute('type', str(cltype)) + style.set_attribute("type", str(cltype)) for rivalid in [-1, 0, 1, 2, 3, 4]: if rivalid == -1: - attr = 'iidxid' + attr = "iidxid" else: - attr = f'iidxid{rivalid}' + attr = f"iidxid{rivalid}" try: extid = int(request.attribute(attr)) @@ -539,46 +578,52 @@ class IIDXPendual(IIDXCourse, IIDXBase): continue userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: - scores = self.data.remote.music.get_scores(self.game, self.music_version, userid) + scores = self.data.remote.music.get_scores( + self.game, self.music_version, userid + ) # Grab score data for user/rival scoredata = self.make_score_struct( scores, - self.CLEAR_TYPE_SINGLE if cltype == self.GAME_CLTYPE_SINGLE else self.CLEAR_TYPE_DOUBLE, + self.CLEAR_TYPE_SINGLE + if cltype == self.GAME_CLTYPE_SINGLE + else self.CLEAR_TYPE_DOUBLE, rivalid, ) for s in scoredata: - root.add_child(Node.s16_array('m', s)) + root.add_child(Node.s16_array("m", s)) # Grab most played for user/rival most_played = [ - play[0] for play in - self.data.local.music.get_most_played(self.game, self.music_version, userid, 20) + play[0] + for play in self.data.local.music.get_most_played( + self.game, self.music_version, userid, 20 + ) ] if len(most_played) < 20: most_played.extend([0] * (20 - len(most_played))) - best = Node.u16_array('best', most_played) - best.set_attribute('rno', str(rivalid)) + best = Node.u16_array("best", most_played) + best.set_attribute("rno", str(rivalid)) root.add_child(best) if rivalid == -1: # Grab beginner statuses for user only beginnerdata = self.make_beginner_struct(scores) for b in beginnerdata: - root.add_child(Node.u16_array('b', b)) + root.add_child(Node.u16_array("b", b)) return root def handle_IIDX22music_reg_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) - musicid = int(request.attribute('mid')) - chart = self.game_to_db_chart(int(request.attribute('clid'))) + extid = int(request.attribute("iidxid")) + musicid = int(request.attribute("mid")) + chart = self.game_to_db_chart(int(request.attribute("clid"))) userid = self.data.remote.user.from_extid(self.game, self.version, extid) # See if we need to report global or shop scores if self.machine_joined_arcade(): game_config = self.get_game_config() - global_scores = game_config.get_bool('global_shop_ranking') + global_scores = game_config.get_bool("global_shop_ranking") machine = self.data.local.machine.get_machine(self.config.machine.pcbid) else: # If we aren't in an arcade, we can only show global scores @@ -587,21 +632,27 @@ class IIDXPendual(IIDXCourse, IIDXBase): # First, determine our current ranking before saving the new score all_scores = sorted( - self.data.remote.music.get_all_scores(game=self.game, version=self.music_version, songid=musicid, songchart=chart), + self.data.remote.music.get_all_scores( + game=self.game, + version=self.music_version, + songid=musicid, + songchart=chart, + ), key=lambda s: (s[1].points, s[1].timestamp), reverse=True, ) all_players = { - uid: prof for (uid, prof) in - self.get_any_profiles([s[0] for s in all_scores]) + uid: prof + for (uid, prof) in self.get_any_profiles([s[0] for s in all_scores]) } if not global_scores: all_scores = [ - score for score in all_scores + score + for score in all_scores if ( - score[0] == userid or - self.user_joined_arcade(machine, all_players[score[0]]) + score[0] == userid + or self.user_joined_arcade(machine, all_players[score[0]]) ) ] @@ -613,12 +664,12 @@ class IIDXPendual(IIDXCourse, IIDXBase): break if userid is not None: - clear_status = self.game_to_db_status(int(request.attribute('cflg'))) - pgreats = int(request.attribute('pgnum')) - greats = int(request.attribute('gnum')) - miss_count = int(request.attribute('mnum')) - ghost = request.child_value('ghost') - shopid = ID.parse_machine_id(request.attribute('shopconvid')) + clear_status = self.game_to_db_status(int(request.attribute("cflg"))) + pgreats = int(request.attribute("pgnum")) + greats = int(request.attribute("gnum")) + miss_count = int(request.attribute("mnum")) + ghost = request.child_value("ghost") + shopid = ID.parse_machine_id(request.attribute("shopconvid")) self.update_score( userid, @@ -633,51 +684,56 @@ class IIDXPendual(IIDXCourse, IIDXBase): ) # Calculate and return statistics about this song - root = Node.void('IIDX22music') - root.set_attribute('clid', request.attribute('clid')) - root.set_attribute('mid', request.attribute('mid')) + root = Node.void("IIDX22music") + root.set_attribute("clid", request.attribute("clid")) + root.set_attribute("mid", request.attribute("mid")) attempts = self.get_clear_rates(musicid, chart) - count = attempts[musicid][chart]['total'] - clear = attempts[musicid][chart]['clears'] - full_combo = attempts[musicid][chart]['fcs'] + count = attempts[musicid][chart]["total"] + clear = attempts[musicid][chart]["clears"] + full_combo = attempts[musicid][chart]["fcs"] if count > 0: - root.set_attribute('crate', str(int((100 * clear) / count))) - root.set_attribute('frate', str(int((100 * full_combo) / count))) + root.set_attribute("crate", str(int((100 * clear) / count))) + root.set_attribute("frate", str(int((100 * full_combo) / count))) else: - root.set_attribute('crate', '0') - root.set_attribute('frate', '0') - root.set_attribute('rankside', '0') + root.set_attribute("crate", "0") + root.set_attribute("frate", "0") + root.set_attribute("rankside", "0") if userid is not None: # Shop ranking - shopdata = Node.void('shopdata') + shopdata = Node.void("shopdata") root.add_child(shopdata) - shopdata.set_attribute('rank', '-1' if oldindex is None else str(oldindex + 1)) + shopdata.set_attribute( + "rank", "-1" if oldindex is None else str(oldindex + 1) + ) # Grab the rank of some other players on this song - ranklist = Node.void('ranklist') + ranklist = Node.void("ranklist") root.add_child(ranklist) all_scores = sorted( - self.data.remote.music.get_all_scores(game=self.game, version=self.music_version, songid=musicid, songchart=chart), + self.data.remote.music.get_all_scores( + game=self.game, + version=self.music_version, + songid=musicid, + songchart=chart, + ), key=lambda s: (s[1].points, s[1].timestamp), reverse=True, ) - missing_players = [ - uid for (uid, _) in all_scores - if uid not in all_players - ] + missing_players = [uid for (uid, _) in all_scores if uid not in all_players] for (uid, prof) in self.get_any_profiles(missing_players): all_players[uid] = prof if not global_scores: all_scores = [ - score for score in all_scores + score + for score in all_scores if ( - score[0] == userid or - self.user_joined_arcade(machine, all_players[score[0]]) + score[0] == userid + or self.user_joined_arcade(machine, all_players[score[0]]) ) ] @@ -688,65 +744,80 @@ class IIDXPendual(IIDXCourse, IIDXBase): ourindex = i break if ourindex is None: - raise Exception('Cannot find our own score after saving to DB!') + raise Exception("Cannot find our own score after saving to DB!") start = ourindex - 4 end = ourindex + 4 if start < 0: start = 0 if end >= len(all_scores): end = len(all_scores) - 1 - relevant_scores = all_scores[start:(end + 1)] + relevant_scores = all_scores[start : (end + 1)] record_num = start + 1 for score in relevant_scores: profile = all_players[score[0]] - data = Node.void('data') + data = Node.void("data") ranklist.add_child(data) - data.set_attribute('iidx_id', str(profile.extid)) - data.set_attribute('name', profile.get_str('name')) + data.set_attribute("iidx_id", str(profile.extid)) + data.set_attribute("name", profile.get_str("name")) - machine_name = '' - if 'shop_location' in profile: - shop_id = profile.get_int('shop_location') + machine_name = "" + if "shop_location" in profile: + shop_id = profile.get_int("shop_location") machine = self.get_machine_by_id(shop_id) if machine is not None: machine_name = machine.name - data.set_attribute('opname', machine_name) - data.set_attribute('rnum', str(record_num)) - data.set_attribute('score', str(score[1].points)) - data.set_attribute('clflg', str(self.db_to_game_status(score[1].data.get_int('clear_status')))) - data.set_attribute('pid', str(profile.get_int('pid'))) - data.set_attribute('myFlg', '1' if score[0] == userid else '0') - data.set_attribute('update', '0') + data.set_attribute("opname", machine_name) + data.set_attribute("rnum", str(record_num)) + data.set_attribute("score", str(score[1].points)) + data.set_attribute( + "clflg", + str(self.db_to_game_status(score[1].data.get_int("clear_status"))), + ) + data.set_attribute("pid", str(profile.get_int("pid"))) + data.set_attribute("myFlg", "1" if score[0] == userid else "0") + data.set_attribute("update", "0") - data.set_attribute('sgrade', str( - self.db_to_game_rank(profile.get_int(self.DAN_RANKING_SINGLE, -1), self.GAME_CLTYPE_SINGLE), - )) - data.set_attribute('dgrade', str( - self.db_to_game_rank(profile.get_int(self.DAN_RANKING_DOUBLE, -1), self.GAME_CLTYPE_DOUBLE), - )) + data.set_attribute( + "sgrade", + str( + self.db_to_game_rank( + profile.get_int(self.DAN_RANKING_SINGLE, -1), + self.GAME_CLTYPE_SINGLE, + ), + ), + ) + data.set_attribute( + "dgrade", + str( + self.db_to_game_rank( + profile.get_int(self.DAN_RANKING_DOUBLE, -1), + self.GAME_CLTYPE_DOUBLE, + ), + ), + ) - qpro = profile.get_dict('qpro') - data.set_attribute('head', str(qpro.get_int('head'))) - data.set_attribute('hair', str(qpro.get_int('hair'))) - data.set_attribute('face', str(qpro.get_int('face'))) - data.set_attribute('body', str(qpro.get_int('body'))) - data.set_attribute('hand', str(qpro.get_int('hand'))) + qpro = profile.get_dict("qpro") + data.set_attribute("head", str(qpro.get_int("head"))) + data.set_attribute("hair", str(qpro.get_int("hair"))) + data.set_attribute("face", str(qpro.get_int("face"))) + data.set_attribute("body", str(qpro.get_int("body"))) + data.set_attribute("hand", str(qpro.get_int("hand"))) record_num = record_num + 1 return root def handle_IIDX22music_breg_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) - musicid = int(request.attribute('mid')) + extid = int(request.attribute("iidxid")) + musicid = int(request.attribute("mid")) userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: - clear_status = self.game_to_db_status(int(request.attribute('cflg'))) - pgreats = int(request.attribute('pgnum')) - greats = int(request.attribute('gnum')) + clear_status = self.game_to_db_status(int(request.attribute("cflg"))) + pgreats = int(request.attribute("pgnum")) + greats = int(request.attribute("gnum")) self.update_score( userid, @@ -756,18 +827,18 @@ class IIDXPendual(IIDXCourse, IIDXBase): pgreats, greats, -1, - b'', + b"", None, ) # Return nothing. - root = Node.void('IIDX22music') + root = Node.void("IIDX22music") return root def handle_IIDX22music_play_request(self, request: Node) -> Node: - musicid = int(request.attribute('mid')) - chart = self.game_to_db_chart(int(request.attribute('clid'))) - clear_status = self.game_to_db_status(int(request.attribute('cflg'))) + musicid = int(request.attribute("mid")) + chart = self.game_to_db_chart(int(request.attribute("clid"))) + clear_status = self.game_to_db_status(int(request.attribute("cflg"))) self.update_score( None, # No userid since its anonymous @@ -782,39 +853,41 @@ class IIDXPendual(IIDXCourse, IIDXBase): ) # Calculate and return statistics about this song - root = Node.void('IIDX22music') - root.set_attribute('clid', request.attribute('clid')) - root.set_attribute('mid', request.attribute('mid')) + root = Node.void("IIDX22music") + root.set_attribute("clid", request.attribute("clid")) + root.set_attribute("mid", request.attribute("mid")) attempts = self.get_clear_rates(musicid, chart) - count = attempts[musicid][chart]['total'] - clear = attempts[musicid][chart]['clears'] - full_combo = attempts[musicid][chart]['fcs'] + count = attempts[musicid][chart]["total"] + clear = attempts[musicid][chart]["clears"] + full_combo = attempts[musicid][chart]["fcs"] if count > 0: - root.set_attribute('crate', str(int((100 * clear) / count))) - root.set_attribute('frate', str(int((100 * full_combo) / count))) + root.set_attribute("crate", str(int((100 * clear) / count))) + root.set_attribute("frate", str(int((100 * full_combo) / count))) else: - root.set_attribute('crate', '0') - root.set_attribute('frate', '0') + root.set_attribute("crate", "0") + root.set_attribute("frate", "0") return root def handle_IIDX22music_appoint_request(self, request: Node) -> Node: - musicid = int(request.attribute('mid')) - chart = self.game_to_db_chart(int(request.attribute('clid'))) - ghost_type = int(request.attribute('ctype')) - extid = int(request.attribute('iidxid')) + musicid = int(request.attribute("mid")) + chart = self.game_to_db_chart(int(request.attribute("clid"))) + ghost_type = int(request.attribute("ctype")) + extid = int(request.attribute("iidxid")) userid = self.data.remote.user.from_extid(self.game, self.version, extid) - root = Node.void('IIDX22music') + root = Node.void("IIDX22music") if userid is not None: # Try to look up previous ghost for user - my_score = self.data.remote.music.get_score(self.game, self.music_version, userid, musicid, chart) + my_score = self.data.remote.music.get_score( + self.game, self.music_version, userid, musicid, chart + ) if my_score is not None: - mydata = Node.binary('mydata', my_score.data.get_bytes('ghost')) - mydata.set_attribute('score', str(my_score.points)) + mydata = Node.binary("mydata", my_score.data.get_bytes("ghost")) + mydata.set_attribute("score", str(my_score.points)) root.add_child(mydata) ghost_score = self.get_ghost( @@ -829,7 +902,7 @@ class IIDXPendual(IIDXCourse, IIDXBase): self.GAME_GHOST_TYPE_RIVAL_TOP: self.GHOST_TYPE_RIVAL_TOP, self.GAME_GHOST_TYPE_RIVAL_AVERAGE: self.GHOST_TYPE_RIVAL_AVERAGE, }.get(ghost_type, self.GHOST_TYPE_NONE), - request.attribute('subtype'), + request.attribute("subtype"), self.GAME_GHOST_LENGTH, musicid, chart, @@ -838,27 +911,27 @@ class IIDXPendual(IIDXCourse, IIDXBase): # Add ghost score if we support it if ghost_score is not None: - sdata = Node.binary('sdata', ghost_score['ghost']) - sdata.set_attribute('score', str(ghost_score['score'])) - if 'name' in ghost_score: - sdata.set_attribute('name', ghost_score['name']) - if 'pid' in ghost_score: - sdata.set_attribute('pid', str(ghost_score['pid'])) - if 'extid' in ghost_score: - sdata.set_attribute('riidxid', str(ghost_score['extid'])) + sdata = Node.binary("sdata", ghost_score["ghost"]) + sdata.set_attribute("score", str(ghost_score["score"])) + if "name" in ghost_score: + sdata.set_attribute("name", ghost_score["name"]) + if "pid" in ghost_score: + sdata.set_attribute("pid", str(ghost_score["pid"])) + if "extid" in ghost_score: + sdata.set_attribute("riidxid", str(ghost_score["extid"])) root.add_child(sdata) return root def handle_IIDX22grade_raised_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) - cltype = int(request.attribute('gtype')) - rank = self.game_to_db_rank(int(request.attribute('gid')), cltype) + extid = int(request.attribute("iidxid")) + cltype = int(request.attribute("gtype")) + rank = self.game_to_db_rank(int(request.attribute("gid")), cltype) userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: - percent = int(request.attribute('achi')) - stages_cleared = int(request.attribute('cflg')) + percent = int(request.attribute("achi")) + stages_cleared = int(request.attribute("cflg")) if cltype == self.GAME_CLTYPE_SINGLE: max_stages = self.DAN_STAGES_SINGLE else: @@ -880,34 +953,36 @@ class IIDXPendual(IIDXCourse, IIDXBase): ) # Figure out number of players that played this ranking - all_achievements = self.data.local.user.get_all_achievements(self.game, self.version, achievementid=rank, achievementtype=index) - root = Node.void('IIDX22grade') - root.set_attribute('pnum', str(len(all_achievements))) + all_achievements = self.data.local.user.get_all_achievements( + self.game, self.version, achievementid=rank, achievementtype=index + ) + root = Node.void("IIDX22grade") + root.set_attribute("pnum", str(len(all_achievements))) return root def handle_IIDX22pc_common_request(self, request: Node) -> Node: - root = Node.void('IIDX22pc') - root.set_attribute('expire', '600') + root = Node.void("IIDX22pc") + root.set_attribute("expire", "600") - ir = Node.void('ir') + ir = Node.void("ir") root.add_child(ir) - ir.set_attribute('beat', '2') + ir.set_attribute("beat", "2") - newsong_another = Node.void('newsong_another') + newsong_another = Node.void("newsong_another") root.add_child(newsong_another) - newsong_another.set_attribute('open', '1') + newsong_another.set_attribute("open", "1") - limit = Node.void('limit') + limit = Node.void("limit") root.add_child(limit) - limit.set_attribute('phase', '21') + limit.set_attribute("phase", "21") # See if we configured event overrides if self.machine_joined_arcade(): game_config = self.get_game_config() - timeshift_override = game_config.get_int('cycle_config') - ccxbm_on = game_config.get_bool('ccxbm_enable') - omni_events = game_config.get_bool('omnimix_events_enabled') - event_phase = game_config.get_int('event_phase') + timeshift_override = game_config.get_int("cycle_config") + ccxbm_on = game_config.get_bool("ccxbm_enable") + omni_events = game_config.get_bool("omnimix_events_enabled") + event_phase = game_config.get_int("event_phase") else: # If we aren't in an arcade, we turn off events timeshift_override = 0 @@ -921,34 +996,34 @@ class IIDXPendual(IIDXCourse, IIDXBase): phase = event_phase # events - boss = Node.void('boss') + boss = Node.void("boss") root.add_child(boss) - boss.set_attribute('phase', str(phase)) + boss.set_attribute("phase", str(phase)) - chrono_diver = Node.void('chrono_diver') + chrono_diver = Node.void("chrono_diver") root.add_child(chrono_diver) - chrono_diver.set_attribute('phase', '3') + chrono_diver.set_attribute("phase", "3") - qpronicle_chord = Node.void('qpronicle_chord') + qpronicle_chord = Node.void("qpronicle_chord") root.add_child(qpronicle_chord) - qpronicle_chord.set_attribute('phase', '3') + qpronicle_chord.set_attribute("phase", "3") - common_cd_event = Node.void('common_cd_event') + common_cd_event = Node.void("common_cd_event") root.add_child(common_cd_event) - common_cd_event.set_attribute('open_list', "3") + common_cd_event.set_attribute("open_list", "3") - pre_play = Node.void('pre_play') + pre_play = Node.void("pre_play") root.add_child(pre_play) - pre_play.set_attribute('phase', '3') + pre_play.set_attribute("phase", "3") - root.add_child(Node.void('vip_pass_black')) + root.add_child(Node.void("vip_pass_black")) # Course definitions courses: List[Dict[str, Any]] = [ { - 'name': 'VOCAL', - 'id': 1, - 'songs': [ + "name": "VOCAL", + "id": 1, + "songs": [ 22027, 20037, 20015, @@ -956,9 +1031,9 @@ class IIDXPendual(IIDXCourse, IIDXBase): ], }, { - 'name': 'ELECTRO', - 'id': 2, - 'songs': [ + "name": "ELECTRO", + "id": 2, + "songs": [ 20068, 20065, 21051, @@ -966,9 +1041,9 @@ class IIDXPendual(IIDXCourse, IIDXBase): ], }, { - 'name': 'CORE', - 'id': 3, - 'songs': [ + "name": "CORE", + "id": 3, + "songs": [ 22040, 20048, 22057, @@ -976,9 +1051,9 @@ class IIDXPendual(IIDXCourse, IIDXBase): ], }, { - 'name': 'COMPILATION', - 'id': 4, - 'songs': [ + "name": "COMPILATION", + "id": 4, + "songs": [ 22078, 21070, 21044, @@ -986,9 +1061,9 @@ class IIDXPendual(IIDXCourse, IIDXBase): ], }, { - 'name': 'CHARGE', - 'id': 5, - 'songs': [ + "name": "CHARGE", + "id": 5, + "songs": [ 20017, 20053, 21007, @@ -996,9 +1071,9 @@ class IIDXPendual(IIDXCourse, IIDXBase): ], }, { - 'name': 'NEKOMATA', - 'id': 6, - 'songs': [ + "name": "NEKOMATA", + "id": 6, + "songs": [ 16042, 20013, 22014, @@ -1006,9 +1081,9 @@ class IIDXPendual(IIDXCourse, IIDXBase): ], }, { - 'name': 'L.E.D.', - 'id': 7, - 'songs': [ + "name": "L.E.D.", + "id": 7, + "songs": [ 18008, 19063, 17064, @@ -1016,9 +1091,9 @@ class IIDXPendual(IIDXCourse, IIDXBase): ], }, { - 'name': 'LOW SPEED', - 'id': 8, - 'songs': [ + "name": "LOW SPEED", + "id": 8, + "songs": [ 18000, 18011, 13026, @@ -1026,9 +1101,9 @@ class IIDXPendual(IIDXCourse, IIDXBase): ], }, { - 'name': 'HIGH SPEED', - 'id': 9, - 'songs': [ + "name": "HIGH SPEED", + "id": 9, + "songs": [ 19009, 19022, 12002, @@ -1036,9 +1111,9 @@ class IIDXPendual(IIDXCourse, IIDXBase): ], }, { - 'name': 'DRAGON', - 'id': 10, - 'songs': [ + "name": "DRAGON", + "id": 10, + "songs": [ 22003, 21007, 13038, @@ -1046,9 +1121,9 @@ class IIDXPendual(IIDXCourse, IIDXBase): ], }, { - 'name': 'LEGEND', - 'id': 11, - 'songs': [ + "name": "LEGEND", + "id": 11, + "songs": [ 18004, 22054, 19002, @@ -1060,9 +1135,9 @@ class IIDXPendual(IIDXCourse, IIDXBase): # Secret course definitions secret_courses: List[Dict[str, Any]] = [ { - 'name': 'KAC FINAL', - 'id': 1, - 'songs': [ + "name": "KAC FINAL", + "id": 1, + "songs": [ 19037, 18032, 22073, @@ -1070,9 +1145,9 @@ class IIDXPendual(IIDXCourse, IIDXBase): ], }, { - 'name': 'Yossy', - 'id': 2, - 'songs': [ + "name": "Yossy", + "id": 2, + "songs": [ 10033, 13037, 15024, @@ -1080,9 +1155,9 @@ class IIDXPendual(IIDXCourse, IIDXBase): ], }, { - 'name': 'TOHO REMIX', - 'id': 3, - 'songs': [ + "name": "TOHO REMIX", + "id": 3, + "songs": [ 22085, 22086, 22084, @@ -1090,9 +1165,9 @@ class IIDXPendual(IIDXCourse, IIDXBase): ], }, { - 'name': 'VS RHYZE SIDE P', - 'id': 4, - 'songs': [ + "name": "VS RHYZE SIDE P", + "id": 4, + "songs": [ 14031, 21077, 21024, @@ -1100,9 +1175,9 @@ class IIDXPendual(IIDXCourse, IIDXBase): ], }, { - 'name': 'VS RHYZE SIDE T', - 'id': 5, - 'songs': [ + "name": "VS RHYZE SIDE T", + "id": 5, + "songs": [ 5021, 20063, 17054, @@ -1110,9 +1185,9 @@ class IIDXPendual(IIDXCourse, IIDXBase): ], }, { - 'name': 'kors k', - 'id': 6, - 'songs': [ + "name": "kors k", + "id": 6, + "songs": [ 19025, 16021, 16023, @@ -1120,9 +1195,9 @@ class IIDXPendual(IIDXCourse, IIDXBase): ], }, { - 'name': 'Eagle', - 'id': 7, - 'songs': [ + "name": "Eagle", + "id": 7, + "songs": [ 21043, 20035, 15023, @@ -1133,113 +1208,121 @@ class IIDXPendual(IIDXCourse, IIDXBase): # For some reason, pendual omnimix crashes on course mode, so don't enable it if not self.omnimix: - internet_ranking = Node.void('internet_ranking') + internet_ranking = Node.void("internet_ranking") root.add_child(internet_ranking) used_ids: List[int] = [] for c in courses: - if c['id'] in used_ids: - raise Exception('Cannot have multiple courses with the same ID!') - elif c['id'] < 0 or c['id'] >= 20: - raise Exception('Course ID is out of bounds!') + if c["id"] in used_ids: + raise Exception("Cannot have multiple courses with the same ID!") + elif c["id"] < 0 or c["id"] >= 20: + raise Exception("Course ID is out of bounds!") else: - used_ids.append(c['id']) - course = Node.void('course') + used_ids.append(c["id"]) + course = Node.void("course") internet_ranking.add_child(course) - course.set_attribute('opflg', '1') - course.set_attribute('course_id', str(c['id'])) - course.set_attribute('mid0', str(c['songs'][0])) - course.set_attribute('mid1', str(c['songs'][1])) - course.set_attribute('mid2', str(c['songs'][2])) - course.set_attribute('mid3', str(c['songs'][3])) - course.set_attribute('name', c['name']) + course.set_attribute("opflg", "1") + course.set_attribute("course_id", str(c["id"])) + course.set_attribute("mid0", str(c["songs"][0])) + course.set_attribute("mid1", str(c["songs"][1])) + course.set_attribute("mid2", str(c["songs"][2])) + course.set_attribute("mid3", str(c["songs"][3])) + course.set_attribute("name", c["name"]) - secret_ex_course = Node.void('secret_ex_course') + secret_ex_course = Node.void("secret_ex_course") root.add_child(secret_ex_course) used_secret_ids: List[int] = [] for c in secret_courses: - if c['id'] in used_secret_ids: - raise Exception('Cannot have multiple secret courses with the same ID!') - elif c['id'] < 0 or c['id'] >= 10: - raise Exception('Secret course ID is out of bounds!') + if c["id"] in used_secret_ids: + raise Exception( + "Cannot have multiple secret courses with the same ID!" + ) + elif c["id"] < 0 or c["id"] >= 10: + raise Exception("Secret course ID is out of bounds!") else: - used_secret_ids.append(c['id']) - course = Node.void('course') + used_secret_ids.append(c["id"]) + course = Node.void("course") secret_ex_course.add_child(course) - course.set_attribute('course_id', str(c['id'])) - course.set_attribute('mid0', str(c['songs'][0])) - course.set_attribute('mid1', str(c['songs'][1])) - course.set_attribute('mid2', str(c['songs'][2])) - course.set_attribute('mid3', str(c['songs'][3])) - course.set_attribute('name', c['name']) + course.set_attribute("course_id", str(c["id"])) + course.set_attribute("mid0", str(c["songs"][0])) + course.set_attribute("mid1", str(c["songs"][1])) + course.set_attribute("mid2", str(c["songs"][2])) + course.set_attribute("mid3", str(c["songs"][3])) + course.set_attribute("name", c["name"]) - expert = Node.void('expert') + expert = Node.void("expert") root.add_child(expert) - expert.set_attribute('phase', '1') + expert.set_attribute("phase", "1") - expert_random_select = Node.void('expert_random_select') + expert_random_select = Node.void("expert_random_select") root.add_child(expert_random_select) - expert_random_select.set_attribute('phase', '1') + expert_random_select.set_attribute("phase", "1") - expert_full = Node.void('expert_secret_full_open') + expert_full = Node.void("expert_secret_full_open") root.add_child(expert_full) - day_start, _ = self.data.local.network.get_schedule_duration('daily') + day_start, _ = self.data.local.network.get_schedule_duration("daily") days_since_epoch = int(day_start / 86400) - common_timeshift_phase = Node.void('common_timeshift_phase') + common_timeshift_phase = Node.void("common_timeshift_phase") root.add_child(common_timeshift_phase) if timeshift_override == 0: - common_timeshift_phase.set_attribute('phase', '1' if (days_since_epoch % 2) == 0 else '2') + common_timeshift_phase.set_attribute( + "phase", "1" if (days_since_epoch % 2) == 0 else "2" + ) elif timeshift_override == 1: - common_timeshift_phase.set_attribute('phase', '2' if (days_since_epoch % 2) == 0 else '1') + common_timeshift_phase.set_attribute( + "phase", "2" if (days_since_epoch % 2) == 0 else "1" + ) elif timeshift_override == 2: - common_timeshift_phase.set_attribute('phase', '1') + common_timeshift_phase.set_attribute("phase", "1") elif timeshift_override == 3: - common_timeshift_phase.set_attribute('phase', '2') + common_timeshift_phase.set_attribute("phase", "2") - root.add_child(Node.void('eaappli_expert')) + root.add_child(Node.void("eaappli_expert")) - cc_collabo_event = Node.void('cc_collabo_event') + cc_collabo_event = Node.void("cc_collabo_event") root.add_child(cc_collabo_event) if ccxbm_on: - cc_collabo_event.set_attribute('phase', "1") - root.add_child(Node.void('cc_collabo_license')) + cc_collabo_event.set_attribute("phase", "1") + root.add_child(Node.void("cc_collabo_license")) else: - cc_collabo_event.set_attribute('phase', "0") + cc_collabo_event.set_attribute("phase", "0") - tohoisfumo = Node.void('toho_remix') + tohoisfumo = Node.void("toho_remix") root.add_child(tohoisfumo) - tohoisfumo.set_attribute('phase', "2") + tohoisfumo.set_attribute("phase", "2") cm = Node.void("cm") root.add_child(cm) - cm.set_attribute('compo', 'cm_event3') - cm.set_attribute('folder', 'cm_event3') - cm.set_attribute('id', "0") + cm.set_attribute("compo", "cm_event3") + cm.set_attribute("folder", "cm_event3") + cm.set_attribute("id", "0") # Ranking details in title screen rankingcharts = [] - for (mid, _plays) in self.data.local.music.get_hit_chart(self.game, self.music_version, 20): + for (mid, _plays) in self.data.local.music.get_hit_chart( + self.game, self.music_version, 20 + ): rankingcharts.append(mid) - root.add_child(Node.u16_array('monthly_mranking', rankingcharts)) - root.add_child(Node.u16_array('total_mranking', rankingcharts)) + root.add_child(Node.u16_array("monthly_mranking", rankingcharts)) + root.add_child(Node.u16_array("total_mranking", rankingcharts)) # need to add in the top 20 rankings for month and total network # TODO: Make these separate in the future return root def handle_IIDX22pc_delete_request(self, request: Node) -> Node: - return Node.void('IIDX22pc') + return Node.void("IIDX22pc") def handle_IIDX22pc_playstart_request(self, request: Node) -> Node: - return Node.void('IIDX22pc') + return Node.void("IIDX22pc") def handle_IIDX22pc_playend_request(self, request: Node) -> Node: - return Node.void('IIDX22pc') + return Node.void("IIDX22pc") def handle_IIDX22pc_oldget_request(self, request: Node) -> Node: - refid = request.attribute('rid') + refid = request.attribute("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: oldversion = self.previous_version() @@ -1247,12 +1330,12 @@ class IIDXPendual(IIDXCourse, IIDXBase): else: profile = None - root = Node.void('IIDX22pc') - root.set_attribute('status', '1' if profile is None else '0') + root = Node.void("IIDX22pc") + root.set_attribute("status", "1" if profile is None else "0") return root def handle_IIDX22pc_getname_request(self, request: Node) -> Node: - refid = request.attribute('rid') + refid = request.attribute("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: oldversion = self.previous_version() @@ -1261,137 +1344,139 @@ class IIDXPendual(IIDXCourse, IIDXBase): profile = None if profile is None: raise Exception( - 'Should not get here if we have no profile, we should ' + - 'have returned \'1\' in the \'oldget\' method above ' + - 'which should tell the game not to present a migration.' + "Should not get here if we have no profile, we should " + + "have returned '1' in the 'oldget' method above " + + "which should tell the game not to present a migration." ) - root = Node.void('IIDX22pc') - root.set_attribute('name', profile.get_str('name')) - root.set_attribute('idstr', ID.format_extid(profile.extid)) - root.set_attribute('pid', str(profile.get_int('pid'))) + root = Node.void("IIDX22pc") + root.set_attribute("name", profile.get_str("name")) + root.set_attribute("idstr", ID.format_extid(profile.extid)) + root.set_attribute("pid", str(profile.get_int("pid"))) return root def handle_IIDX22pc_takeover_request(self, request: Node) -> Node: - refid = request.attribute('rid') - name = request.attribute('name') - pid = int(request.attribute('pid')) + refid = request.attribute("rid") + name = request.attribute("name") + pid = int(request.attribute("pid")) newprofile = self.new_profile_by_refid(refid, name, pid) - root = Node.void('IIDX22pc') + root = Node.void("IIDX22pc") if newprofile is not None: - root.set_attribute('id', str(newprofile.extid)) + root.set_attribute("id", str(newprofile.extid)) return root def handle_IIDX22pc_reg_request(self, request: Node) -> Node: - refid = request.attribute('rid') - name = request.attribute('name') - pid = int(request.attribute('pid')) + refid = request.attribute("rid") + name = request.attribute("name") + pid = int(request.attribute("pid")) profile = self.new_profile_by_refid(refid, name, pid) - root = Node.void('IIDX22pc') + root = Node.void("IIDX22pc") if profile is not None: - root.set_attribute('id', str(profile.extid)) - root.set_attribute('id_str', ID.format_extid(profile.extid)) + root.set_attribute("id", str(profile.extid)) + root.set_attribute("id_str", ID.format_extid(profile.extid)) return root def handle_IIDX22pc_get_request(self, request: Node) -> Node: - refid = request.attribute('rid') + refid = request.attribute("rid") root = self.get_profile_by_refid(refid) if root is None: - root = Node.void('IIDX22pc') + root = Node.void("IIDX22pc") return root def handle_IIDX22pc_save_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) + extid = int(request.attribute("iidxid")) self.put_profile_by_extid(extid, request) - root = Node.void('IIDX22pc') + root = Node.void("IIDX22pc") return root def handle_IIDX22pc_visit_request(self, request: Node) -> Node: - root = Node.void('IIDX22pc') - root.set_attribute('anum', '0') - root.set_attribute('pnum', '0') - root.set_attribute('sflg', '0') - root.set_attribute('pflg', '0') - root.set_attribute('aflg', '0') - root.set_attribute('snum', '0') + root = Node.void("IIDX22pc") + root.set_attribute("anum", "0") + root.set_attribute("pnum", "0") + root.set_attribute("sflg", "0") + root.set_attribute("pflg", "0") + root.set_attribute("aflg", "0") + root.set_attribute("snum", "0") return root def handle_IIDX22pc_shopregister_request(self, request: Node) -> Node: - extid = int(request.child_value('iidx_id')) - location = ID.parse_machine_id(request.child_value('location_id')) + extid = int(request.child_value("iidx_id")) + location = ID.parse_machine_id(request.child_value("location_id")) userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: profile = self.get_profile(userid) if profile is None: profile = Profile(self.game, self.version, "", extid) - profile.replace_int('shop_location', location) + profile.replace_int("shop_location", location) self.put_profile(userid, profile) - root = Node.void('IIDX22pc') + root = Node.void("IIDX22pc") return root def handle_IIDX22pc_eaappliresult_request(self, request: Node) -> Node: clear_map = { - self.GAME_CLEAR_STATUS_NO_PLAY: 'NO PLAY', - self.GAME_CLEAR_STATUS_FAILED: 'FAILED', - self.GAME_CLEAR_STATUS_ASSIST_CLEAR: 'ASSIST CLEAR', - self.GAME_CLEAR_STATUS_EASY_CLEAR: 'EASY CLEAR', - self.GAME_CLEAR_STATUS_CLEAR: 'CLEAR', - self.GAME_CLEAR_STATUS_HARD_CLEAR: 'HARD CLEAR', - self.GAME_CLEAR_STATUS_EX_HARD_CLEAR: 'EX HARD CLEAR', - self.GAME_CLEAR_STATUS_FULL_COMBO: 'FULL COMBO', + self.GAME_CLEAR_STATUS_NO_PLAY: "NO PLAY", + self.GAME_CLEAR_STATUS_FAILED: "FAILED", + self.GAME_CLEAR_STATUS_ASSIST_CLEAR: "ASSIST CLEAR", + self.GAME_CLEAR_STATUS_EASY_CLEAR: "EASY CLEAR", + self.GAME_CLEAR_STATUS_CLEAR: "CLEAR", + self.GAME_CLEAR_STATUS_HARD_CLEAR: "HARD CLEAR", + self.GAME_CLEAR_STATUS_EX_HARD_CLEAR: "EX HARD CLEAR", + self.GAME_CLEAR_STATUS_FULL_COMBO: "FULL COMBO", } # first we'll grab the data from the packet # did = request.child_value('did') # rid = request.child_value('rid') - name = request.child_value('name') + name = request.child_value("name") # qpro_hair = request.child_value('qpro_hair') # qpro_head = request.child_value('qpro_head') # qpro_body = request.child_value('qpro_body') # qpro_hand = request.child_value('qpro_hand') - music_id = request.child_value('music_id') - class_id = request.child_value('class_id') + music_id = request.child_value("music_id") + class_id = request.child_value("class_id") # no_save = request.child_value('no_save') # is_couple = request.child_value('is_couple') # target_graph = request.child_value('target_graph') - target_exscore = request.child_value('target_exscore') + target_exscore = request.child_value("target_exscore") # pacemaker = request.child_value('pacemaker') - best_clear = request.child_value('best_clear') + best_clear = request.child_value("best_clear") # best_djlevel = request.child_value('best_djlevel') # best_exscore = request.child_value('best_exscore') # best_misscount = request.child_value('best_misscount') - now_clear = request.child_value('now_clear') + now_clear = request.child_value("now_clear") # now_djlevel = request.child_value('now_djlevel') - now_exscore = request.child_value('now_exscore') + now_exscore = request.child_value("now_exscore") # now_misscount = request.child_value('now_misscount') - now_pgreat = request.child_value('now_pgreat') - now_great = request.child_value('now_great') - now_good = request.child_value('now_good') - now_bad = request.child_value('now_bad') - now_poor = request.child_value('now_poor') - now_combo = request.child_value('now_combo') - now_fast = request.child_value('now_fast') - now_slow = request.child_value('now_slow') - best_clear_string = clear_map.get(best_clear, 'NO PLAY') - now_clear_string = clear_map.get(now_clear, 'NO PLAY') + now_pgreat = request.child_value("now_pgreat") + now_great = request.child_value("now_great") + now_good = request.child_value("now_good") + now_bad = request.child_value("now_bad") + now_poor = request.child_value("now_poor") + now_combo = request.child_value("now_combo") + now_fast = request.child_value("now_fast") + now_slow = request.child_value("now_slow") + best_clear_string = clear_map.get(best_clear, "NO PLAY") + now_clear_string = clear_map.get(now_clear, "NO PLAY") # let's get the song info first - song = self.data.local.music.get_song(self.game, self.music_version, music_id, self.game_to_db_chart(class_id)) - notecount = song.data.get('notecount', 0) + song = self.data.local.music.get_song( + self.game, self.music_version, music_id, self.game_to_db_chart(class_id) + ) + notecount = song.data.get("notecount", 0) # Construct the dictionary for the broadcast card_data = { BroadcastConstants.DJ_NAME: name, BroadcastConstants.SONG_NAME: song.name, BroadcastConstants.ARTIST_NAME: song.artist, - BroadcastConstants.DIFFICULTY: song.data.get('difficulty', 0), + BroadcastConstants.DIFFICULTY: song.data.get("difficulty", 0), BroadcastConstants.TARGET_EXSCORE: target_exscore, BroadcastConstants.EXSCORE: now_exscore, BroadcastConstants.BEST_CLEAR_STATUS: best_clear_string, BroadcastConstants.CLEAR_STATUS: now_clear_string, - BroadcastConstants.PLAY_STATS_HEADER: 'How did you do?', + BroadcastConstants.PLAY_STATS_HEADER: "How did you do?", BroadcastConstants.PERFECT_GREATS: now_pgreat, BroadcastConstants.GREATS: now_great, BroadcastConstants.GOODS: now_good, @@ -1405,249 +1490,303 @@ class IIDXPendual(IIDXCourse, IIDXBase): max_score = notecount * 2 percent = now_exscore / max_score grade = int(9 * percent) - grades = ['F', 'F', 'E', 'D', 'C', 'B', 'A', 'AA', 'AAA', 'MAX'] + grades = ["F", "F", "E", "D", "C", "B", "A", "AA", "AAA", "MAX"] card_data[BroadcastConstants.GRADE] = grades[grade] card_data[BroadcastConstants.RATE] = str(round(percent, 2)) # Try to broadcast out the score to our webhook(s) self.data.triggers.broadcast_score(card_data, self.game, song) - return Node.void('IIDX22pc') + return Node.void("IIDX22pc") def format_profile(self, userid: UserID, profile: Profile) -> Node: - root = Node.void('IIDX22pc') + root = Node.void("IIDX22pc") # Look up play stats we bridge to every mix play_stats = self.get_play_statistics(userid) # Look up judge window adjustments - judge_dict = profile.get_dict('machine_judge_adjust') + judge_dict = profile.get_dict("machine_judge_adjust") machine_judge = judge_dict.get_dict(self.config.machine.pcbid) # Profile data - pcdata = Node.void('pcdata') + pcdata = Node.void("pcdata") root.add_child(pcdata) - pcdata.set_attribute('id', str(profile.extid)) - pcdata.set_attribute('idstr', ID.format_extid(profile.extid)) - pcdata.set_attribute('name', profile.get_str('name')) - pcdata.set_attribute('pid', str(profile.get_int('pid'))) - pcdata.set_attribute('spnum', str(play_stats.get_int('single_plays'))) - pcdata.set_attribute('dpnum', str(play_stats.get_int('double_plays'))) - pcdata.set_attribute('sach', str(play_stats.get_int('single_dj_points'))) - pcdata.set_attribute('dach', str(play_stats.get_int('double_dj_points'))) - pcdata.set_attribute('mode', str(profile.get_int('mode'))) - pcdata.set_attribute('pmode', str(profile.get_int('pmode'))) - pcdata.set_attribute('rtype', str(profile.get_int('rtype'))) - pcdata.set_attribute('sp_opt', str(profile.get_int('sp_opt'))) - pcdata.set_attribute('dp_opt', str(profile.get_int('dp_opt'))) - pcdata.set_attribute('dp_opt2', str(profile.get_int('dp_opt2'))) - pcdata.set_attribute('gpos', str(profile.get_int('gpos'))) - pcdata.set_attribute('s_sorttype', str(profile.get_int('s_sorttype'))) - pcdata.set_attribute('d_sorttype', str(profile.get_int('d_sorttype'))) - pcdata.set_attribute('s_pace', str(profile.get_int('s_pace'))) - pcdata.set_attribute('d_pace', str(profile.get_int('d_pace'))) - pcdata.set_attribute('s_gno', str(profile.get_int('s_gno'))) - pcdata.set_attribute('d_gno', str(profile.get_int('d_gno'))) - pcdata.set_attribute('s_gtype', str(profile.get_int('s_gtype'))) - pcdata.set_attribute('d_gtype', str(profile.get_int('d_gtype'))) - pcdata.set_attribute('s_sdlen', str(profile.get_int('s_sdlen'))) - pcdata.set_attribute('d_sdlen', str(profile.get_int('d_sdlen'))) - pcdata.set_attribute('s_sdtype', str(profile.get_int('s_sdtype'))) - pcdata.set_attribute('d_sdtype', str(profile.get_int('d_sdtype'))) - pcdata.set_attribute('s_timing', str(profile.get_int('s_timing'))) - pcdata.set_attribute('d_timing', str(profile.get_int('d_timing'))) - pcdata.set_attribute('s_notes', str(profile.get_float('s_notes'))) - pcdata.set_attribute('d_notes', str(profile.get_float('d_notes'))) - pcdata.set_attribute('s_judge', str(profile.get_int('s_judge'))) - pcdata.set_attribute('d_judge', str(profile.get_int('d_judge'))) - pcdata.set_attribute('s_judgeAdj', str(machine_judge.get_int('single'))) - pcdata.set_attribute('d_judgeAdj', str(machine_judge.get_int('double'))) - pcdata.set_attribute('s_hispeed', str(profile.get_float('s_hispeed'))) - pcdata.set_attribute('d_hispeed', str(profile.get_float('d_hispeed'))) - pcdata.set_attribute('s_liflen', str(profile.get_int('s_lift'))) - pcdata.set_attribute('d_liflen', str(profile.get_int('d_lift'))) - pcdata.set_attribute('s_disp_judge', str(profile.get_int('s_disp_judge'))) - pcdata.set_attribute('d_disp_judge', str(profile.get_int('d_disp_judge'))) - pcdata.set_attribute('s_opstyle', str(profile.get_int('s_opstyle'))) - pcdata.set_attribute('d_opstyle', str(profile.get_int('d_opstyle'))) - pcdata.set_attribute('s_exscore', str(profile.get_int('s_exscore'))) - pcdata.set_attribute('d_exscore', str(profile.get_int('d_exscore'))) - pcdata.set_attribute('s_largejudge', str(profile.get_int('s_largejudge'))) - pcdata.set_attribute('d_largejudge', str(profile.get_int('d_largejudge'))) + pcdata.set_attribute("id", str(profile.extid)) + pcdata.set_attribute("idstr", ID.format_extid(profile.extid)) + pcdata.set_attribute("name", profile.get_str("name")) + pcdata.set_attribute("pid", str(profile.get_int("pid"))) + pcdata.set_attribute("spnum", str(play_stats.get_int("single_plays"))) + pcdata.set_attribute("dpnum", str(play_stats.get_int("double_plays"))) + pcdata.set_attribute("sach", str(play_stats.get_int("single_dj_points"))) + pcdata.set_attribute("dach", str(play_stats.get_int("double_dj_points"))) + pcdata.set_attribute("mode", str(profile.get_int("mode"))) + pcdata.set_attribute("pmode", str(profile.get_int("pmode"))) + pcdata.set_attribute("rtype", str(profile.get_int("rtype"))) + pcdata.set_attribute("sp_opt", str(profile.get_int("sp_opt"))) + pcdata.set_attribute("dp_opt", str(profile.get_int("dp_opt"))) + pcdata.set_attribute("dp_opt2", str(profile.get_int("dp_opt2"))) + pcdata.set_attribute("gpos", str(profile.get_int("gpos"))) + pcdata.set_attribute("s_sorttype", str(profile.get_int("s_sorttype"))) + pcdata.set_attribute("d_sorttype", str(profile.get_int("d_sorttype"))) + pcdata.set_attribute("s_pace", str(profile.get_int("s_pace"))) + pcdata.set_attribute("d_pace", str(profile.get_int("d_pace"))) + pcdata.set_attribute("s_gno", str(profile.get_int("s_gno"))) + pcdata.set_attribute("d_gno", str(profile.get_int("d_gno"))) + pcdata.set_attribute("s_gtype", str(profile.get_int("s_gtype"))) + pcdata.set_attribute("d_gtype", str(profile.get_int("d_gtype"))) + pcdata.set_attribute("s_sdlen", str(profile.get_int("s_sdlen"))) + pcdata.set_attribute("d_sdlen", str(profile.get_int("d_sdlen"))) + pcdata.set_attribute("s_sdtype", str(profile.get_int("s_sdtype"))) + pcdata.set_attribute("d_sdtype", str(profile.get_int("d_sdtype"))) + pcdata.set_attribute("s_timing", str(profile.get_int("s_timing"))) + pcdata.set_attribute("d_timing", str(profile.get_int("d_timing"))) + pcdata.set_attribute("s_notes", str(profile.get_float("s_notes"))) + pcdata.set_attribute("d_notes", str(profile.get_float("d_notes"))) + pcdata.set_attribute("s_judge", str(profile.get_int("s_judge"))) + pcdata.set_attribute("d_judge", str(profile.get_int("d_judge"))) + pcdata.set_attribute("s_judgeAdj", str(machine_judge.get_int("single"))) + pcdata.set_attribute("d_judgeAdj", str(machine_judge.get_int("double"))) + pcdata.set_attribute("s_hispeed", str(profile.get_float("s_hispeed"))) + pcdata.set_attribute("d_hispeed", str(profile.get_float("d_hispeed"))) + pcdata.set_attribute("s_liflen", str(profile.get_int("s_lift"))) + pcdata.set_attribute("d_liflen", str(profile.get_int("d_lift"))) + pcdata.set_attribute("s_disp_judge", str(profile.get_int("s_disp_judge"))) + pcdata.set_attribute("d_disp_judge", str(profile.get_int("d_disp_judge"))) + pcdata.set_attribute("s_opstyle", str(profile.get_int("s_opstyle"))) + pcdata.set_attribute("d_opstyle", str(profile.get_int("d_opstyle"))) + pcdata.set_attribute("s_exscore", str(profile.get_int("s_exscore"))) + pcdata.set_attribute("d_exscore", str(profile.get_int("d_exscore"))) + pcdata.set_attribute("s_largejudge", str(profile.get_int("s_largejudge"))) + pcdata.set_attribute("d_largejudge", str(profile.get_int("d_largejudge"))) # If the user joined a particular shop, let the game know. - if 'shop_location' in profile: - shop_id = profile.get_int('shop_location') + if "shop_location" in profile: + shop_id = profile.get_int("shop_location") machine = self.get_machine_by_id(shop_id) if machine is not None: - join_shop = Node.void('join_shop') + join_shop = Node.void("join_shop") root.add_child(join_shop) - join_shop.set_attribute('joinflg', '1') - join_shop.set_attribute('join_cflg', '1') - join_shop.set_attribute('join_id', ID.format_machine_id(machine.id)) - join_shop.set_attribute('join_name', machine.name) + join_shop.set_attribute("joinflg", "1") + join_shop.set_attribute("join_cflg", "1") + join_shop.set_attribute("join_id", ID.format_machine_id(machine.id)) + join_shop.set_attribute("join_name", machine.name) # Daily recommendations - entry = self.data.local.game.get_time_sensitive_settings(self.game, self.version, 'dailies') + entry = self.data.local.game.get_time_sensitive_settings( + self.game, self.version, "dailies" + ) if entry is not None: - packinfo = Node.void('packinfo') + packinfo = Node.void("packinfo") root.add_child(packinfo) - pack_id = int(entry['start_time'] / 86400) - packinfo.set_attribute('pack_id', str(pack_id)) - packinfo.set_attribute('music_0', str(entry['music'][0])) - packinfo.set_attribute('music_1', str(entry['music'][1])) - packinfo.set_attribute('music_2', str(entry['music'][2])) + pack_id = int(entry["start_time"] / 86400) + packinfo.set_attribute("pack_id", str(pack_id)) + packinfo.set_attribute("music_0", str(entry["music"][0])) + packinfo.set_attribute("music_1", str(entry["music"][1])) + packinfo.set_attribute("music_2", str(entry["music"][2])) else: # No dailies :( pack_id = None # Track deller - deller = Node.void('deller') + deller = Node.void("deller") root.add_child(deller) - deller.set_attribute('deller', str(profile.get_int('deller'))) - deller.set_attribute('rate', '0') + deller.set_attribute("deller", str(profile.get_int("deller"))) + deller.set_attribute("rate", "0") # Secret flags (shh!) - secret_dict = profile.get_dict('secret') - secret = Node.void('secret') + secret_dict = profile.get_dict("secret") + secret = Node.void("secret") root.add_child(secret) - secret.add_child(Node.s64_array('flg1', secret_dict.get_int_array('flg1', 3))) - secret.add_child(Node.s64_array('flg2', secret_dict.get_int_array('flg2', 3))) - secret.add_child(Node.s64_array('flg3', secret_dict.get_int_array('flg3', 3))) + secret.add_child(Node.s64_array("flg1", secret_dict.get_int_array("flg1", 3))) + secret.add_child(Node.s64_array("flg2", secret_dict.get_int_array("flg2", 3))) + secret.add_child(Node.s64_array("flg3", secret_dict.get_int_array("flg3", 3))) # Tran medals and shit - achievements = Node.void('achievements') + achievements = Node.void("achievements") root.add_child(achievements) # Dailies if pack_id is None: - achievements.set_attribute('pack', '0') - achievements.set_attribute('pack_comp', '0') + achievements.set_attribute("pack", "0") + achievements.set_attribute("pack_comp", "0") else: - daily_played = self.data.local.user.get_achievement(self.game, self.version, userid, pack_id, 'daily') + daily_played = self.data.local.user.get_achievement( + self.game, self.version, userid, pack_id, "daily" + ) if daily_played is None: daily_played = ValidatedDict() - achievements.set_attribute('pack', str(daily_played.get_int('pack_flg'))) - achievements.set_attribute('pack_comp', str(daily_played.get_int('pack_comp'))) + achievements.set_attribute("pack", str(daily_played.get_int("pack_flg"))) + achievements.set_attribute( + "pack_comp", str(daily_played.get_int("pack_comp")) + ) # Weeklies - achievements.set_attribute('last_weekly', str(profile.get_int('last_weekly'))) - achievements.set_attribute('weekly_num', str(profile.get_int('weekly_num'))) + achievements.set_attribute("last_weekly", str(profile.get_int("last_weekly"))) + achievements.set_attribute("weekly_num", str(profile.get_int("weekly_num"))) # Prefecture visit flag - achievements.set_attribute('visit_flg', str(profile.get_int('visit_flg'))) + achievements.set_attribute("visit_flg", str(profile.get_int("visit_flg"))) # Number of rivals beaten - achievements.set_attribute('rival_crush', str(profile.get_int('rival_crush'))) + achievements.set_attribute("rival_crush", str(profile.get_int("rival_crush"))) # Tran medals - achievements.add_child(Node.s64_array('trophy', profile.get_int_array('trophy', 10))) + achievements.add_child( + Node.s64_array("trophy", profile.get_int_array("trophy", 10)) + ) # User settings - settings_dict = profile.get_dict('settings') + settings_dict = profile.get_dict("settings") skin = Node.s16_array( - 'skin', + "skin", [ - settings_dict.get_int('frame'), - settings_dict.get_int('turntable'), - settings_dict.get_int('burst'), - settings_dict.get_int('bgm'), - settings_dict.get_int('flags'), - settings_dict.get_int('towel'), - settings_dict.get_int('judge_pos'), - settings_dict.get_int('voice'), - settings_dict.get_int('noteskin'), - settings_dict.get_int('full_combo'), - settings_dict.get_int('beam'), - settings_dict.get_int('judge'), + settings_dict.get_int("frame"), + settings_dict.get_int("turntable"), + settings_dict.get_int("burst"), + settings_dict.get_int("bgm"), + settings_dict.get_int("flags"), + settings_dict.get_int("towel"), + settings_dict.get_int("judge_pos"), + settings_dict.get_int("voice"), + settings_dict.get_int("noteskin"), + settings_dict.get_int("full_combo"), + settings_dict.get_int("beam"), + settings_dict.get_int("judge"), 0, - settings_dict.get_int('disable_song_preview'), + settings_dict.get_int("disable_song_preview"), ], ) root.add_child(skin) # DAN rankings - grade = Node.void('grade') + grade = Node.void("grade") root.add_child(grade) - grade.set_attribute('sgid', str(self.db_to_game_rank(profile.get_int(self.DAN_RANKING_SINGLE, -1), self.GAME_CLTYPE_SINGLE))) - grade.set_attribute('dgid', str(self.db_to_game_rank(profile.get_int(self.DAN_RANKING_DOUBLE, -1), self.GAME_CLTYPE_DOUBLE))) - rankings = self.data.local.user.get_achievements(self.game, self.version, userid) + grade.set_attribute( + "sgid", + str( + self.db_to_game_rank( + profile.get_int(self.DAN_RANKING_SINGLE, -1), + self.GAME_CLTYPE_SINGLE, + ) + ), + ) + grade.set_attribute( + "dgid", + str( + self.db_to_game_rank( + profile.get_int(self.DAN_RANKING_DOUBLE, -1), + self.GAME_CLTYPE_DOUBLE, + ) + ), + ) + rankings = self.data.local.user.get_achievements( + self.game, self.version, userid + ) for rank in rankings: if rank.type == self.DAN_RANKING_SINGLE: - grade.add_child(Node.u8_array('g', [ - self.GAME_CLTYPE_SINGLE, - self.db_to_game_rank(rank.id, self.GAME_CLTYPE_SINGLE), - rank.data.get_int('stages_cleared'), - rank.data.get_int('percent'), - ])) + grade.add_child( + Node.u8_array( + "g", + [ + self.GAME_CLTYPE_SINGLE, + self.db_to_game_rank(rank.id, self.GAME_CLTYPE_SINGLE), + rank.data.get_int("stages_cleared"), + rank.data.get_int("percent"), + ], + ) + ) if rank.type == self.DAN_RANKING_DOUBLE: - grade.add_child(Node.u8_array('g', [ - self.GAME_CLTYPE_DOUBLE, - self.db_to_game_rank(rank.id, self.GAME_CLTYPE_DOUBLE), - rank.data.get_int('stages_cleared'), - rank.data.get_int('percent'), - ])) + grade.add_child( + Node.u8_array( + "g", + [ + self.GAME_CLTYPE_DOUBLE, + self.db_to_game_rank(rank.id, self.GAME_CLTYPE_DOUBLE), + rank.data.get_int("stages_cleared"), + rank.data.get_int("percent"), + ], + ) + ) # Expert courses - ir_data = Node.void('ir_data') + ir_data = Node.void("ir_data") root.add_child(ir_data) for rank in rankings: if rank.type == self.COURSE_TYPE_INTERNET_RANKING: - ir_data.add_child(Node.s32_array('e', [ - int(rank.id / 6), # course ID - rank.id % 6, # course chart - self.db_to_game_status(rank.data.get_int('clear_status')), # course clear status - rank.data.get_int('pgnum'), # flashing great count - rank.data.get_int('gnum'), # great count - ])) + ir_data.add_child( + Node.s32_array( + "e", + [ + int(rank.id / 6), # course ID + rank.id % 6, # course chart + self.db_to_game_status( + rank.data.get_int("clear_status") + ), # course clear status + rank.data.get_int("pgnum"), # flashing great count + rank.data.get_int("gnum"), # great count + ], + ) + ) - secret_course_data = Node.void('secret_course_data') + secret_course_data = Node.void("secret_course_data") root.add_child(secret_course_data) for rank in rankings: if rank.type == self.COURSE_TYPE_SECRET: - secret_course_data.add_child(Node.s32_array('e', [ - int(rank.id / 6), # course ID - rank.id % 6, # course chart - self.db_to_game_status(rank.data.get_int('clear_status')), # course clear status - rank.data.get_int('pgnum'), # flashing great count - rank.data.get_int('gnum'), # great count - ])) + secret_course_data.add_child( + Node.s32_array( + "e", + [ + int(rank.id / 6), # course ID + rank.id % 6, # course chart + self.db_to_game_status( + rank.data.get_int("clear_status") + ), # course clear status + rank.data.get_int("pgnum"), # flashing great count + rank.data.get_int("gnum"), # great count + ], + ) + ) - expert_point = Node.void('expert_point') + expert_point = Node.void("expert_point") root.add_child(expert_point) for rank in rankings: - if rank.type == 'expert_point': - detail = Node.void('detail') + if rank.type == "expert_point": + detail = Node.void("detail") expert_point.add_child(detail) - detail.set_attribute('course_id', str(rank.id)) - detail.set_attribute('n_point', str(rank.data.get_int('normal_points'))) - detail.set_attribute('h_point', str(rank.data.get_int('hyper_points'))) - detail.set_attribute('a_point', str(rank.data.get_int('another_points'))) + detail.set_attribute("course_id", str(rank.id)) + detail.set_attribute("n_point", str(rank.data.get_int("normal_points"))) + detail.set_attribute("h_point", str(rank.data.get_int("hyper_points"))) + detail.set_attribute( + "a_point", str(rank.data.get_int("another_points")) + ) # Qpro data - qpro_dict = profile.get_dict('qpro') - root.add_child(Node.u32_array( - 'qprodata', - [ - qpro_dict.get_int('head'), - qpro_dict.get_int('hair'), - qpro_dict.get_int('face'), - qpro_dict.get_int('hand'), - qpro_dict.get_int('body'), - ], - )) + qpro_dict = profile.get_dict("qpro") + root.add_child( + Node.u32_array( + "qprodata", + [ + qpro_dict.get_int("head"), + qpro_dict.get_int("hair"), + qpro_dict.get_int("face"), + qpro_dict.get_int("hand"), + qpro_dict.get_int("body"), + ], + ) + ) # Rivals - rlist = Node.void('rlist') + rlist = Node.void("rlist") root.add_child(rlist) links = self.data.local.user.get_links(self.game, self.version, userid) for link in links: rival_type = None - if link.type == 'sp_rival': - rival_type = '1' - elif link.type == 'dp_rival': - rival_type = '2' + if link.type == "sp_rival": + rival_type = "1" + elif link.type == "dp_rival": + rival_type = "2" else: # No business with this link type continue @@ -1657,295 +1796,356 @@ class IIDXPendual(IIDXCourse, IIDXBase): continue other_play_stats = self.get_play_statistics(link.other_userid) - rival = Node.void('rival') + rival = Node.void("rival") rlist.add_child(rival) - rival.set_attribute('spdp', rival_type) - rival.set_attribute('id', str(other_profile.extid)) - rival.set_attribute('id_str', ID.format_extid(other_profile.extid)) - rival.set_attribute('djname', other_profile.get_str('name')) - rival.set_attribute('pid', str(other_profile.get_int('pid'))) - rival.set_attribute('sg', str(self.db_to_game_rank(other_profile.get_int(self.DAN_RANKING_SINGLE, -1), self.GAME_CLTYPE_SINGLE))) - rival.set_attribute('dg', str(self.db_to_game_rank(other_profile.get_int(self.DAN_RANKING_DOUBLE, -1), self.GAME_CLTYPE_DOUBLE))) - rival.set_attribute('sa', str(other_play_stats.get_int('single_dj_points'))) - rival.set_attribute('da', str(other_play_stats.get_int('double_dj_points'))) + rival.set_attribute("spdp", rival_type) + rival.set_attribute("id", str(other_profile.extid)) + rival.set_attribute("id_str", ID.format_extid(other_profile.extid)) + rival.set_attribute("djname", other_profile.get_str("name")) + rival.set_attribute("pid", str(other_profile.get_int("pid"))) + rival.set_attribute( + "sg", + str( + self.db_to_game_rank( + other_profile.get_int(self.DAN_RANKING_SINGLE, -1), + self.GAME_CLTYPE_SINGLE, + ) + ), + ) + rival.set_attribute( + "dg", + str( + self.db_to_game_rank( + other_profile.get_int(self.DAN_RANKING_DOUBLE, -1), + self.GAME_CLTYPE_DOUBLE, + ) + ), + ) + rival.set_attribute("sa", str(other_play_stats.get_int("single_dj_points"))) + rival.set_attribute("da", str(other_play_stats.get_int("double_dj_points"))) # If the user joined a particular shop, let the game know. - if 'shop_location' in other_profile: - shop_id = other_profile.get_int('shop_location') + if "shop_location" in other_profile: + shop_id = other_profile.get_int("shop_location") machine = self.get_machine_by_id(shop_id) if machine is not None: - shop = Node.void('shop') + shop = Node.void("shop") rival.add_child(shop) - shop.set_attribute('name', machine.name) + shop.set_attribute("name", machine.name) - qprodata = Node.void('qprodata') + qprodata = Node.void("qprodata") rival.add_child(qprodata) - qpro = other_profile.get_dict('qpro') - qprodata.set_attribute('head', str(qpro.get_int('head'))) - qprodata.set_attribute('hair', str(qpro.get_int('hair'))) - qprodata.set_attribute('face', str(qpro.get_int('face'))) - qprodata.set_attribute('body', str(qpro.get_int('body'))) - qprodata.set_attribute('hand', str(qpro.get_int('hand'))) + qpro = other_profile.get_dict("qpro") + qprodata.set_attribute("head", str(qpro.get_int("head"))) + qprodata.set_attribute("hair", str(qpro.get_int("hair"))) + qprodata.set_attribute("face", str(qpro.get_int("face"))) + qprodata.set_attribute("body", str(qpro.get_int("body"))) + qprodata.set_attribute("hand", str(qpro.get_int("hand"))) # Step up mode - step_dict = profile.get_dict('step') - step = Node.void('step') + step_dict = profile.get_dict("step") + step = Node.void("step") root.add_child(step) - step.set_attribute('damage', str(step_dict.get_int('damage'))) - step.set_attribute('defeat', str(step_dict.get_int('defeat'))) - step.set_attribute('progress', str(step_dict.get_int('progress'))) - step.set_attribute('sp_mission', str(step_dict.get_int('sp_mission'))) - step.set_attribute('dp_mission', str(step_dict.get_int('dp_mission'))) - step.set_attribute('sp_level', str(step_dict.get_int('sp_level'))) - step.set_attribute('dp_level', str(step_dict.get_int('dp_level'))) - step.set_attribute('sp_mplay', str(step_dict.get_int('sp_mplay'))) - step.set_attribute('dp_mplay', str(step_dict.get_int('dp_mplay'))) - step.set_attribute('age_list', str(step_dict.get_int('age_list'))) - step.set_attribute('is_secret', str(step_dict.get_int('is_secret'))) - step.set_attribute('is_present', str(step_dict.get_int('is_present'))) - step.set_attribute('is_future', str(step_dict.get_int('is_future'))) - step.add_child(Node.binary('album', step_dict.get_bytes('album', b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'))) + step.set_attribute("damage", str(step_dict.get_int("damage"))) + step.set_attribute("defeat", str(step_dict.get_int("defeat"))) + step.set_attribute("progress", str(step_dict.get_int("progress"))) + step.set_attribute("sp_mission", str(step_dict.get_int("sp_mission"))) + step.set_attribute("dp_mission", str(step_dict.get_int("dp_mission"))) + step.set_attribute("sp_level", str(step_dict.get_int("sp_level"))) + step.set_attribute("dp_level", str(step_dict.get_int("dp_level"))) + step.set_attribute("sp_mplay", str(step_dict.get_int("sp_mplay"))) + step.set_attribute("dp_mplay", str(step_dict.get_int("dp_mplay"))) + step.set_attribute("age_list", str(step_dict.get_int("age_list"))) + step.set_attribute("is_secret", str(step_dict.get_int("is_secret"))) + step.set_attribute("is_present", str(step_dict.get_int("is_present"))) + step.set_attribute("is_future", str(step_dict.get_int("is_future"))) + step.add_child( + Node.binary( + "album", + step_dict.get_bytes( + "album", b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ), + ) + ) # Favorites - favorite = Node.void('favorite') + favorite = Node.void("favorite") root.add_child(favorite) - favorite_dict = profile.get_dict('favorite') + favorite_dict = profile.get_dict("favorite") - sp_mlist = b'' - sp_clist = b'' - singles_list = favorite_dict['single'] if 'single' in favorite_dict else [] + sp_mlist = b"" + sp_clist = b"" + singles_list = favorite_dict["single"] if "single" in favorite_dict else [] for single in singles_list: - sp_mlist = sp_mlist + struct.pack(' Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() play_stats = self.get_play_statistics(userid) # Track play counts - cltype = int(request.attribute('cltype')) + cltype = int(request.attribute("cltype")) if cltype == self.GAME_CLTYPE_SINGLE: - play_stats.increment_int('single_plays') + play_stats.increment_int("single_plays") if cltype == self.GAME_CLTYPE_DOUBLE: - play_stats.increment_int('double_plays') + play_stats.increment_int("double_plays") # Track DJ points - play_stats.replace_int('single_dj_points', int(request.attribute('s_achi'))) - play_stats.replace_int('double_dj_points', int(request.attribute('d_achi'))) + play_stats.replace_int("single_dj_points", int(request.attribute("s_achi"))) + play_stats.replace_int("double_dj_points", int(request.attribute("d_achi"))) # Profile settings - newprofile.replace_int('mode', int(request.attribute('mode'))) - newprofile.replace_int('pmode', int(request.attribute('pmode'))) - newprofile.replace_int('rtype', int(request.attribute('rtype'))) - newprofile.replace_int('s_lift', int(request.attribute('s_lift'))) - newprofile.replace_int('d_lift', int(request.attribute('d_lift'))) - newprofile.replace_int('sp_opt', int(request.attribute('sp_opt'))) - newprofile.replace_int('dp_opt', int(request.attribute('dp_opt'))) - newprofile.replace_int('dp_opt2', int(request.attribute('dp_opt2'))) - newprofile.replace_int('gpos', int(request.attribute('gpos'))) - newprofile.replace_int('s_sorttype', int(request.attribute('s_sorttype'))) - newprofile.replace_int('d_sorttype', int(request.attribute('d_sorttype'))) - newprofile.replace_int('s_pace', int(request.attribute('s_pace'))) - newprofile.replace_int('d_pace', int(request.attribute('d_pace'))) - newprofile.replace_int('s_gno', int(request.attribute('s_gno'))) - newprofile.replace_int('d_gno', int(request.attribute('d_gno'))) - newprofile.replace_int('s_gtype', int(request.attribute('s_gtype'))) - newprofile.replace_int('d_gtype', int(request.attribute('d_gtype'))) - newprofile.replace_int('s_sdlen', int(request.attribute('s_sdlen'))) - newprofile.replace_int('d_sdlen', int(request.attribute('d_sdlen'))) - newprofile.replace_int('s_sdtype', int(request.attribute('s_sdtype'))) - newprofile.replace_int('d_sdtype', int(request.attribute('d_sdtype'))) - newprofile.replace_int('s_timing', int(request.attribute('s_timing'))) - newprofile.replace_int('d_timing', int(request.attribute('d_timing'))) - newprofile.replace_float('s_notes', float(request.attribute('s_notes'))) - newprofile.replace_float('d_notes', float(request.attribute('d_notes'))) - newprofile.replace_int('s_judge', int(request.attribute('s_judge'))) - newprofile.replace_int('d_judge', int(request.attribute('d_judge'))) - newprofile.replace_float('s_hispeed', float(request.attribute('s_hispeed'))) - newprofile.replace_float('d_hispeed', float(request.attribute('d_hispeed'))) - newprofile.replace_int('s_disp_judge', int(request.attribute('s_disp_judge'))) - newprofile.replace_int('d_disp_judge', int(request.attribute('d_disp_judge'))) - newprofile.replace_int('s_opstyle', int(request.attribute('s_opstyle'))) - newprofile.replace_int('d_opstyle', int(request.attribute('d_opstyle'))) - newprofile.replace_int('s_exscore', int(request.attribute('s_exscore'))) - newprofile.replace_int('d_exscore', int(request.attribute('d_exscore'))) - newprofile.replace_int('s_largejudge', int(request.attribute('s_largejudge'))) - newprofile.replace_int('d_largejudge', int(request.attribute('d_largejudge'))) + newprofile.replace_int("mode", int(request.attribute("mode"))) + newprofile.replace_int("pmode", int(request.attribute("pmode"))) + newprofile.replace_int("rtype", int(request.attribute("rtype"))) + newprofile.replace_int("s_lift", int(request.attribute("s_lift"))) + newprofile.replace_int("d_lift", int(request.attribute("d_lift"))) + newprofile.replace_int("sp_opt", int(request.attribute("sp_opt"))) + newprofile.replace_int("dp_opt", int(request.attribute("dp_opt"))) + newprofile.replace_int("dp_opt2", int(request.attribute("dp_opt2"))) + newprofile.replace_int("gpos", int(request.attribute("gpos"))) + newprofile.replace_int("s_sorttype", int(request.attribute("s_sorttype"))) + newprofile.replace_int("d_sorttype", int(request.attribute("d_sorttype"))) + newprofile.replace_int("s_pace", int(request.attribute("s_pace"))) + newprofile.replace_int("d_pace", int(request.attribute("d_pace"))) + newprofile.replace_int("s_gno", int(request.attribute("s_gno"))) + newprofile.replace_int("d_gno", int(request.attribute("d_gno"))) + newprofile.replace_int("s_gtype", int(request.attribute("s_gtype"))) + newprofile.replace_int("d_gtype", int(request.attribute("d_gtype"))) + newprofile.replace_int("s_sdlen", int(request.attribute("s_sdlen"))) + newprofile.replace_int("d_sdlen", int(request.attribute("d_sdlen"))) + newprofile.replace_int("s_sdtype", int(request.attribute("s_sdtype"))) + newprofile.replace_int("d_sdtype", int(request.attribute("d_sdtype"))) + newprofile.replace_int("s_timing", int(request.attribute("s_timing"))) + newprofile.replace_int("d_timing", int(request.attribute("d_timing"))) + newprofile.replace_float("s_notes", float(request.attribute("s_notes"))) + newprofile.replace_float("d_notes", float(request.attribute("d_notes"))) + newprofile.replace_int("s_judge", int(request.attribute("s_judge"))) + newprofile.replace_int("d_judge", int(request.attribute("d_judge"))) + newprofile.replace_float("s_hispeed", float(request.attribute("s_hispeed"))) + newprofile.replace_float("d_hispeed", float(request.attribute("d_hispeed"))) + newprofile.replace_int("s_disp_judge", int(request.attribute("s_disp_judge"))) + newprofile.replace_int("d_disp_judge", int(request.attribute("d_disp_judge"))) + newprofile.replace_int("s_opstyle", int(request.attribute("s_opstyle"))) + newprofile.replace_int("d_opstyle", int(request.attribute("d_opstyle"))) + newprofile.replace_int("s_exscore", int(request.attribute("s_exscore"))) + newprofile.replace_int("d_exscore", int(request.attribute("d_exscore"))) + newprofile.replace_int("s_largejudge", int(request.attribute("s_largejudge"))) + newprofile.replace_int("d_largejudge", int(request.attribute("d_largejudge"))) # Update judge window adjustments per-machine - judge_dict = newprofile.get_dict('machine_judge_adjust') + judge_dict = newprofile.get_dict("machine_judge_adjust") machine_judge = judge_dict.get_dict(self.config.machine.pcbid) - machine_judge.replace_int('single', int(request.attribute('s_judgeAdj'))) - machine_judge.replace_int('double', int(request.attribute('d_judgeAdj'))) + machine_judge.replace_int("single", int(request.attribute("s_judgeAdj"))) + machine_judge.replace_int("double", int(request.attribute("d_judgeAdj"))) judge_dict.replace_dict(self.config.machine.pcbid, machine_judge) - newprofile.replace_dict('machine_judge_adjust', judge_dict) + newprofile.replace_dict("machine_judge_adjust", judge_dict) # Secret flags saving - secret = request.child('secret') + secret = request.child("secret") if secret is not None: - secret_dict = newprofile.get_dict('secret') - secret_dict.replace_int_array('flg1', 3, secret.child_value('flg1')) - secret_dict.replace_int_array('flg2', 3, secret.child_value('flg2')) - secret_dict.replace_int_array('flg3', 3, secret.child_value('flg3')) - newprofile.replace_dict('secret', secret_dict) + secret_dict = newprofile.get_dict("secret") + secret_dict.replace_int_array("flg1", 3, secret.child_value("flg1")) + secret_dict.replace_int_array("flg2", 3, secret.child_value("flg2")) + secret_dict.replace_int_array("flg3", 3, secret.child_value("flg3")) + newprofile.replace_dict("secret", secret_dict) # Basic achievements - achievements = request.child('achievements') + achievements = request.child("achievements") if achievements is not None: - newprofile.replace_int('visit_flg', int(achievements.attribute('visit_flg'))) - newprofile.replace_int('last_weekly', int(achievements.attribute('last_weekly'))) - newprofile.replace_int('weekly_num', int(achievements.attribute('weekly_num'))) + newprofile.replace_int( + "visit_flg", int(achievements.attribute("visit_flg")) + ) + newprofile.replace_int( + "last_weekly", int(achievements.attribute("last_weekly")) + ) + newprofile.replace_int( + "weekly_num", int(achievements.attribute("weekly_num")) + ) - pack_id = int(achievements.attribute('pack_id')) + pack_id = int(achievements.attribute("pack_id")) if pack_id > 0: self.data.local.user.put_achievement( self.game, self.version, userid, pack_id, - 'daily', + "daily", { - 'pack_flg': int(achievements.attribute('pack_flg')), - 'pack_comp': int(achievements.attribute('pack_comp')), + "pack_flg": int(achievements.attribute("pack_flg")), + "pack_comp": int(achievements.attribute("pack_comp")), }, ) - trophies = achievements.child('trophy') + trophies = achievements.child("trophy") if trophies is not None: # We only load the first 10 in profile load. - newprofile.replace_int_array('trophy', 10, trophies.value[:10]) + newprofile.replace_int_array("trophy", 10, trophies.value[:10]) # Deller saving - deller = request.child('deller') + deller = request.child("deller") if deller is not None: - newprofile.replace_int('deller', newprofile.get_int('deller') + int(deller.attribute('deller'))) + newprofile.replace_int( + "deller", newprofile.get_int("deller") + int(deller.attribute("deller")) + ) # Secret course expert point saving - expert_point = request.child('expert_point') + expert_point = request.child("expert_point") if expert_point is not None: - courseid = int(expert_point.attribute('course_id')) + courseid = int(expert_point.attribute("course_id")) # Update achievement to track expert points expert_point_achievement = self.data.local.user.get_achievement( @@ -1953,21 +2153,21 @@ class IIDXPendual(IIDXCourse, IIDXBase): self.version, userid, courseid, - 'expert_point', + "expert_point", ) if expert_point_achievement is None: expert_point_achievement = ValidatedDict() expert_point_achievement.replace_int( - 'normal_points', - int(expert_point.attribute('n_point')), + "normal_points", + int(expert_point.attribute("n_point")), ) expert_point_achievement.replace_int( - 'hyper_points', - int(expert_point.attribute('h_point')), + "hyper_points", + int(expert_point.attribute("h_point")), ) expert_point_achievement.replace_int( - 'another_points', - int(expert_point.attribute('a_point')), + "another_points", + int(expert_point.attribute("a_point")), ) self.data.local.user.put_achievement( @@ -1975,126 +2175,190 @@ class IIDXPendual(IIDXCourse, IIDXBase): self.version, userid, courseid, - 'expert_point', + "expert_point", expert_point_achievement, ) # Favorites saving - favorite = request.child('favorite') + favorite = request.child("favorite") if favorite is not None: - single_music_bin = favorite.child_value('sp_mlist') - single_chart_bin = favorite.child_value('sp_clist') - double_music_bin = favorite.child_value('dp_mlist') - double_chart_bin = favorite.child_value('dp_clist') + single_music_bin = favorite.child_value("sp_mlist") + single_chart_bin = favorite.child_value("sp_clist") + double_music_bin = favorite.child_value("dp_mlist") + double_chart_bin = favorite.child_value("dp_clist") singles = [] doubles = [] for i in range(self.FAVORITE_LIST_LENGTH): - singles.append({ - 'id': struct.unpack(' List[Tuple[str, Dict[str, Any]]]: + def run_scheduled_work( + cls, data: Data, config: Dict[str, Any] + ) -> List[Tuple[str, Dict[str, Any]]]: """ Insert dailies into the DB. """ events = [] - if data.local.network.should_schedule(cls.game, cls.version, 'daily_charts', 'daily'): + if data.local.network.should_schedule( + cls.game, cls.version, "daily_charts", "daily" + ): # Generate a new list of three dailies. - start_time, end_time = data.local.network.get_schedule_duration('daily') - all_songs = list(set([song.id for song in data.local.music.get_all_songs(cls.game, cls.version)])) + start_time, end_time = data.local.network.get_schedule_duration("daily") + all_songs = list( + set( + [ + song.id + for song in data.local.music.get_all_songs( + cls.game, cls.version + ) + ] + ) + ) if len(all_songs) >= 3: daily_songs = random.sample(all_songs, 3) data.local.game.put_time_sensitive_settings( cls.game, cls.version, - 'dailies', + "dailies", { - 'start_time': start_time, - 'end_time': end_time, - 'music': daily_songs, + "start_time": start_time, + "end_time": end_time, + "music": daily_songs, }, ) - events.append(( - 'iidx_daily_charts', - { - 'version': cls.version, - 'music': daily_songs, - }, - )) + events.append( + ( + "iidx_daily_charts", + { + "version": cls.version, + "music": daily_songs, + }, + ) + ) # Mark that we did some actual work here. - data.local.network.mark_scheduled(cls.game, cls.version, 'daily_charts', 'daily') + data.local.network.mark_scheduled( + cls.game, cls.version, "daily_charts", "daily" + ) return events @classmethod @@ -139,34 +164,34 @@ class IIDXRootage(IIDXCourse, IIDXBase): Return all of our front-end modifiably settings. """ return { - 'bools': [ + "bools": [ { - 'name': 'Global Shop Ranking', - 'tip': 'Return network-wide ranking instead of shop ranking on results screen.', - 'category': 'game_config', - 'setting': 'global_shop_ranking', + "name": "Global Shop Ranking", + "tip": "Return network-wide ranking instead of shop ranking on results screen.", + "category": "game_config", + "setting": "global_shop_ranking", }, { - 'name': 'Events In Omnimix', - 'tip': 'Allow events to be enabled at all for Omnimix.', - 'category': 'game_config', - 'setting': 'omnimix_events_enabled', + "name": "Events In Omnimix", + "tip": "Allow events to be enabled at all for Omnimix.", + "category": "game_config", + "setting": "omnimix_events_enabled", }, ], - 'ints': [ + "ints": [ { - 'name': 'Event Phase', - 'tip': 'Event phase for all players.', - 'category': 'game_config', - 'setting': 'event_phase', - 'values': { - 0: 'No Event', - 1: '蜃気楼の図書館(The Bibliotheca of Mirage) Phase 1', - 2: '蜃気楼の図書館(The Bibliotheca of Mirage) Phase 2', - 3: '蜃気楼の図書館(The Bibliotheca of Mirage) Phase 3', - 4: '蜃気楼の図書館(The Bibliotheca of Mirage) Phase 4', - 5: 'DELABITY LABORATORY', - } + "name": "Event Phase", + "tip": "Event phase for all players.", + "category": "game_config", + "setting": "event_phase", + "values": { + 0: "No Event", + 1: "蜃気楼の図書館(The Bibliotheca of Mirage) Phase 1", + 2: "蜃気楼の図書館(The Bibliotheca of Mirage) Phase 2", + 3: "蜃気楼の図書館(The Bibliotheca of Mirage) Phase 3", + 4: "蜃気楼の図書館(The Bibliotheca of Mirage) Phase 4", + 5: "DELABITY LABORATORY", + }, }, ], } @@ -245,7 +270,7 @@ class IIDXRootage(IIDXCourse, IIDXBase): self.DAN_RANK_KAIDEN: self.GAME_DP_DAN_RANK_KAIDEN, }[db_dan] else: - raise Exception('Invalid cltype!') + raise Exception("Invalid cltype!") def game_to_db_rank(self, game_dan: int, cltype: int) -> int: # Special case for no DAN rank @@ -297,7 +322,7 @@ class IIDXRootage(IIDXCourse, IIDXBase): self.GAME_DP_DAN_RANK_KAIDEN: self.DAN_RANK_KAIDEN, }[game_dan] else: - raise Exception('Invalid cltype!') + raise Exception("Invalid cltype!") def game_to_db_chart(self, db_chart: int) -> int: return { @@ -314,86 +339,101 @@ class IIDXRootage(IIDXCourse, IIDXBase): machine = self.data.local.machine.get_machine(self.config.machine.pcbid) if machine is not None: machine_name = machine.name - close = machine.data.get_bool('close') - hour = machine.data.get_int('hour') - minute = machine.data.get_int('minute') + close = machine.data.get_bool("close") + hour = machine.data.get_int("hour") + minute = machine.data.get_int("minute") else: - machine_name = '' + machine_name = "" close = False hour = 0 minute = 0 - root = Node.void('IIDX26shop') - root.set_attribute('opname', machine_name) - root.set_attribute('pid', str(self.get_machine_region())) - root.set_attribute('cls_opt', '1' if close else '0') - root.set_attribute('hr', str(hour)) - root.set_attribute('mi', str(minute)) + root = Node.void("IIDX26shop") + root.set_attribute("opname", machine_name) + root.set_attribute("pid", str(self.get_machine_region())) + root.set_attribute("cls_opt", "1" if close else "0") + root.set_attribute("hr", str(hour)) + root.set_attribute("mi", str(minute)) return root def handle_IIDX26shop_savename_request(self, request: Node) -> Node: - self.update_machine_name(request.attribute('opname')) + self.update_machine_name(request.attribute("opname")) - shop_close = intish(request.attribute('cls_opt')) or 0 - minutes = intish(request.attribute('mnt')) or 0 - hours = intish(request.attribute('hr')) or 0 + shop_close = intish(request.attribute("cls_opt")) or 0 + minutes = intish(request.attribute("mnt")) or 0 + hours = intish(request.attribute("hr")) or 0 - self.update_machine_data({ - 'close': shop_close != 0, - 'minutes': minutes, - 'hours': hours, - }) + self.update_machine_data( + { + "close": shop_close != 0, + "minutes": minutes, + "hours": hours, + } + ) - return Node.void('IIDX26shop') + return Node.void("IIDX26shop") def handle_IIDX26shop_sentinfo_request(self, request: Node) -> Node: - return Node.void('IIDX26shop') + return Node.void("IIDX26shop") def handle_IIDX26shop_sendescapepackageinfo_request(self, request: Node) -> Node: - root = Node.void('IIDX26shop') - root.set_attribute('expire', str((Time.now() + 86400 * 365) * 1000)) + root = Node.void("IIDX26shop") + root.set_attribute("expire", str((Time.now() + 86400 * 365) * 1000)) return root def handle_IIDX26shop_getconvention_request(self, request: Node) -> Node: - root = Node.void('IIDX26shop') + root = Node.void("IIDX26shop") machine = self.data.local.machine.get_machine(self.config.machine.pcbid) if machine.arcade is not None: - course = self.data.local.machine.get_settings(machine.arcade, self.game, self.music_version, 'shop_course') + course = self.data.local.machine.get_settings( + machine.arcade, self.game, self.music_version, "shop_course" + ) else: course = None if course is None: course = ValidatedDict() - root.set_attribute('music_0', str(course.get_int('music_0', 20032))) - root.set_attribute('music_1', str(course.get_int('music_1', 20009))) - root.set_attribute('music_2', str(course.get_int('music_2', 20015))) - root.set_attribute('music_3', str(course.get_int('music_3', 20064))) - root.add_child(Node.bool('valid', course.get_bool('valid'))) + root.set_attribute("music_0", str(course.get_int("music_0", 20032))) + root.set_attribute("music_1", str(course.get_int("music_1", 20009))) + root.set_attribute("music_2", str(course.get_int("music_2", 20015))) + root.set_attribute("music_3", str(course.get_int("music_3", 20064))) + root.add_child(Node.bool("valid", course.get_bool("valid"))) return root def handle_IIDX26shop_setconvention_request(self, request: Node) -> Node: machine = self.data.local.machine.get_machine(self.config.machine.pcbid) if machine.arcade is not None: course = ValidatedDict() - course.replace_int('music_0', request.child_value('music_0')) - course.replace_int('music_1', request.child_value('music_1')) - course.replace_int('music_2', request.child_value('music_2')) - course.replace_int('music_3', request.child_value('music_3')) - course.replace_bool('valid', request.child_value('valid')) - self.data.local.machine.put_settings(machine.arcade, self.game, self.music_version, 'shop_course', course) + course.replace_int("music_0", request.child_value("music_0")) + course.replace_int("music_1", request.child_value("music_1")) + course.replace_int("music_2", request.child_value("music_2")) + course.replace_int("music_3", request.child_value("music_3")) + course.replace_bool("valid", request.child_value("valid")) + self.data.local.machine.put_settings( + machine.arcade, self.game, self.music_version, "shop_course", course + ) - return Node.void('IIDX26shop') + return Node.void("IIDX26shop") def handle_IIDX26ranking_getranker_request(self, request: Node) -> Node: # Expert mode is removed so do nothing - return Node.void('IIDX26ranking') + return Node.void("IIDX26ranking") def handle_IIDX26music_crate_request(self, request: Node) -> Node: - root = Node.void('IIDX26music') + root = Node.void("IIDX26music") attempts = self.get_clear_rates() - all_songs = list(set([song.id for song in self.data.local.music.get_all_songs(self.game, self.music_version)])) + all_songs = list( + set( + [ + song.id + for song in self.data.local.music.get_all_songs( + self.game, self.music_version + ) + ] + ) + ) for song in all_songs: clears = [] fcs = [] @@ -402,33 +442,33 @@ class IIDXRootage(IIDXCourse, IIDXBase): placed = False if song in attempts and chart in attempts[song]: values = attempts[song][chart] - if values['total'] > 0: - clears.append(int((1000 * values['clears']) / values['total'])) - fcs.append(int((1000 * values['fcs']) / values['total'])) + if values["total"] > 0: + clears.append(int((1000 * values["clears"]) / values["total"])) + fcs.append(int((1000 * values["fcs"]) / values["total"])) placed = True if not placed: clears.append(1001) fcs.append(1001) - clearnode = Node.s32_array('c', clears + fcs) - clearnode.set_attribute('mid', str(song)) + clearnode = Node.s32_array("c", clears + fcs) + clearnode.set_attribute("mid", str(song)) root.add_child(clearnode) return root def handle_IIDX26music_getrank_request(self, request: Node) -> Node: - cltype = int(request.attribute('cltype')) + cltype = int(request.attribute("cltype")) - root = Node.void('IIDX26music') - style = Node.void('style') + root = Node.void("IIDX26music") + style = Node.void("style") root.add_child(style) - style.set_attribute('type', str(cltype)) + style.set_attribute("type", str(cltype)) for rivalid in [-1, 0, 1, 2, 3, 4]: if rivalid == -1: - attr = 'iidxid' + attr = "iidxid" else: - attr = f'iidxid{rivalid}' + attr = f"iidxid{rivalid}" try: extid = int(request.attribute(attr)) @@ -437,51 +477,59 @@ class IIDXRootage(IIDXCourse, IIDXBase): continue userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: - scores = self.data.remote.music.get_scores(self.game, self.music_version, userid) + scores = self.data.remote.music.get_scores( + self.game, self.music_version, userid + ) # Grab score data for user/rival scoredata = self.make_score_struct( scores, - self.CLEAR_TYPE_SINGLE if cltype == self.GAME_CLTYPE_SINGLE else self.CLEAR_TYPE_DOUBLE, + self.CLEAR_TYPE_SINGLE + if cltype == self.GAME_CLTYPE_SINGLE + else self.CLEAR_TYPE_DOUBLE, rivalid, ) for s in scoredata: - root.add_child(Node.s16_array('m', s)) + root.add_child(Node.s16_array("m", s)) # Grab most played for user/rival most_played = [ - play[0] for play in - self.data.local.music.get_most_played(self.game, self.music_version, userid, 20) + play[0] + for play in self.data.local.music.get_most_played( + self.game, self.music_version, userid, 20 + ) ] if len(most_played) < 20: most_played.extend([0] * (20 - len(most_played))) - best = Node.u16_array('best', most_played) - best.set_attribute('rno', str(rivalid)) + best = Node.u16_array("best", most_played) + best.set_attribute("rno", str(rivalid)) root.add_child(best) if rivalid == -1: # Grab beginner statuses for user only beginnerdata = self.make_beginner_struct(scores) for b in beginnerdata: - root.add_child(Node.u16_array('b', b)) + root.add_child(Node.u16_array("b", b)) return root def handle_IIDX26music_appoint_request(self, request: Node) -> Node: - musicid = int(request.attribute('mid')) - chart = self.game_to_db_chart(int(request.attribute('clid'))) - ghost_type = int(request.attribute('ctype')) - extid = int(request.attribute('iidxid')) + musicid = int(request.attribute("mid")) + chart = self.game_to_db_chart(int(request.attribute("clid"))) + ghost_type = int(request.attribute("ctype")) + extid = int(request.attribute("iidxid")) userid = self.data.remote.user.from_extid(self.game, self.version, extid) - root = Node.void('IIDX26music') + root = Node.void("IIDX26music") if userid is not None: # Try to look up previous ghost for user - my_score = self.data.remote.music.get_score(self.game, self.music_version, userid, musicid, chart) + my_score = self.data.remote.music.get_score( + self.game, self.music_version, userid, musicid, chart + ) if my_score is not None: - mydata = Node.binary('mydata', my_score.data.get_bytes('ghost')) - mydata.set_attribute('score', str(my_score.points)) + mydata = Node.binary("mydata", my_score.data.get_bytes("ghost")) + mydata.set_attribute("score", str(my_score.points)) root.add_child(mydata) ghost_score = self.get_ghost( @@ -496,7 +544,7 @@ class IIDXRootage(IIDXCourse, IIDXBase): self.GAME_GHOST_TYPE_RIVAL_TOP: self.GHOST_TYPE_RIVAL_TOP, self.GAME_GHOST_TYPE_RIVAL_AVERAGE: self.GHOST_TYPE_RIVAL_AVERAGE, }.get(ghost_type, self.GHOST_TYPE_NONE), - request.attribute('subtype'), + request.attribute("subtype"), self.GAME_GHOST_LENGTH, musicid, chart, @@ -505,27 +553,27 @@ class IIDXRootage(IIDXCourse, IIDXBase): # Add ghost score if we support it if ghost_score is not None: - sdata = Node.binary('sdata', ghost_score['ghost']) - sdata.set_attribute('score', str(ghost_score['score'])) - if 'name' in ghost_score: - sdata.set_attribute('name', ghost_score['name']) - if 'pid' in ghost_score: - sdata.set_attribute('pid', str(ghost_score['pid'])) - if 'extid' in ghost_score: - sdata.set_attribute('riidxid', str(ghost_score['extid'])) + sdata = Node.binary("sdata", ghost_score["ghost"]) + sdata.set_attribute("score", str(ghost_score["score"])) + if "name" in ghost_score: + sdata.set_attribute("name", ghost_score["name"]) + if "pid" in ghost_score: + sdata.set_attribute("pid", str(ghost_score["pid"])) + if "extid" in ghost_score: + sdata.set_attribute("riidxid", str(ghost_score["extid"])) root.add_child(sdata) return root def handle_IIDX26music_breg_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) - musicid = int(request.attribute('mid')) + extid = int(request.attribute("iidxid")) + musicid = int(request.attribute("mid")) userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: - clear_status = self.game_to_db_status(int(request.attribute('cflg'))) - pgreats = int(request.attribute('pgnum')) - greats = int(request.attribute('gnum')) + clear_status = self.game_to_db_status(int(request.attribute("cflg"))) + pgreats = int(request.attribute("pgnum")) + greats = int(request.attribute("gnum")) self.update_score( userid, @@ -535,23 +583,23 @@ class IIDXRootage(IIDXCourse, IIDXBase): pgreats, greats, -1, - b'', + b"", None, ) # Return nothing. - return Node.void('IIDX26music') + return Node.void("IIDX26music") def handle_IIDX26music_reg_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) - musicid = int(request.attribute('mid')) - chart = self.game_to_db_chart(int(request.attribute('clid'))) + extid = int(request.attribute("iidxid")) + musicid = int(request.attribute("mid")) + chart = self.game_to_db_chart(int(request.attribute("clid"))) userid = self.data.remote.user.from_extid(self.game, self.version, extid) # See if we need to report global or shop scores if self.machine_joined_arcade(): game_config = self.get_game_config() - global_scores = game_config.get_bool('global_shop_ranking') + global_scores = game_config.get_bool("global_shop_ranking") machine = self.data.local.machine.get_machine(self.config.machine.pcbid) else: # If we aren't in an arcade, we can only show global scores @@ -560,23 +608,26 @@ class IIDXRootage(IIDXCourse, IIDXBase): # First, determine our current ranking before saving the new score all_scores = sorted( - self.data.remote.music.get_all_scores(game=self.game, version=self.music_version, songid=musicid, songchart=chart), + self.data.remote.music.get_all_scores( + game=self.game, + version=self.music_version, + songid=musicid, + songchart=chart, + ), key=lambda s: (s[1].points, s[1].timestamp), reverse=True, ) all_players = { - uid: prof for (uid, prof) in - self.get_any_profiles([s[0] for s in all_scores]) + uid: prof + for (uid, prof) in self.get_any_profiles([s[0] for s in all_scores]) } - shop_id = ID.parse_machine_id(request.attribute('location_id')) + shop_id = ID.parse_machine_id(request.attribute("location_id")) if not global_scores: all_scores = [ - score for score in all_scores - if ( - score[0] == userid or - score[1].location == shop_id - ) + score + for score in all_scores + if (score[0] == userid or score[1].location == shop_id) ] # Find our actual index @@ -587,12 +638,12 @@ class IIDXRootage(IIDXCourse, IIDXBase): break if userid is not None: - clear_status = self.game_to_db_status(int(request.attribute('cflg'))) - pgreats = int(request.attribute('pgnum')) - greats = int(request.attribute('gnum')) - miss_count = int(request.attribute('mnum')) - ghost = request.child_value('ghost') - shopid = ID.parse_machine_id(request.attribute('convid')) + clear_status = self.game_to_db_status(int(request.attribute("cflg"))) + pgreats = int(request.attribute("pgnum")) + greats = int(request.attribute("gnum")) + miss_count = int(request.attribute("mnum")) + ghost = request.child_value("ghost") + shopid = ID.parse_machine_id(request.attribute("convid")) self.update_score( userid, @@ -607,52 +658,54 @@ class IIDXRootage(IIDXCourse, IIDXBase): ) # Calculate and return statistics about this song - root = Node.void('IIDX26music') - root.set_attribute('clid', request.attribute('clid')) - root.set_attribute('mid', request.attribute('mid')) + root = Node.void("IIDX26music") + root.set_attribute("clid", request.attribute("clid")) + root.set_attribute("mid", request.attribute("mid")) attempts = self.get_clear_rates(musicid, chart) - count = attempts[musicid][chart]['total'] - clear = attempts[musicid][chart]['clears'] - full_combo = attempts[musicid][chart]['fcs'] + count = attempts[musicid][chart]["total"] + clear = attempts[musicid][chart]["clears"] + full_combo = attempts[musicid][chart]["fcs"] if count > 0: - root.set_attribute('crate', str(int((1000 * clear) / count))) - root.set_attribute('frate', str(int((1000 * full_combo) / count))) + root.set_attribute("crate", str(int((1000 * clear) / count))) + root.set_attribute("frate", str(int((1000 * full_combo) / count))) else: - root.set_attribute('crate', '0') - root.set_attribute('frate', '0') - root.set_attribute('rankside', '0') + root.set_attribute("crate", "0") + root.set_attribute("frate", "0") + root.set_attribute("rankside", "0") if userid is not None: # Shop ranking - shopdata = Node.void('shopdata') + shopdata = Node.void("shopdata") root.add_child(shopdata) - shopdata.set_attribute('rank', '-1' if oldindex is None else str(oldindex + 1)) + shopdata.set_attribute( + "rank", "-1" if oldindex is None else str(oldindex + 1) + ) # Grab the rank of some other players on this song - ranklist = Node.void('ranklist') + ranklist = Node.void("ranklist") root.add_child(ranklist) all_scores = sorted( - self.data.remote.music.get_all_scores(game=self.game, version=self.music_version, songid=musicid, songchart=chart), + self.data.remote.music.get_all_scores( + game=self.game, + version=self.music_version, + songid=musicid, + songchart=chart, + ), key=lambda s: (s[1].points, s[1].timestamp), reverse=True, ) - missing_players = [ - uid for (uid, _) in all_scores - if uid not in all_players - ] + missing_players = [uid for (uid, _) in all_scores if uid not in all_players] for (uid, prof) in self.get_any_profiles(missing_players): all_players[uid] = prof if not global_scores: all_scores = [ - score for score in all_scores - if ( - score[0] == userid or - score[1].location == shop_id - ) + score + for score in all_scores + if (score[0] == userid or score[1].location == shop_id) ] # Find our actual index @@ -662,58 +715,73 @@ class IIDXRootage(IIDXCourse, IIDXBase): ourindex = i break if ourindex is None: - raise Exception('Cannot find our own score after saving to DB!') + raise Exception("Cannot find our own score after saving to DB!") start = ourindex - 4 end = ourindex + 4 if start < 0: start = 0 if end >= len(all_scores): end = len(all_scores) - 1 - relevant_scores = all_scores[start:(end + 1)] + relevant_scores = all_scores[start : (end + 1)] record_num = start + 1 for score in relevant_scores: profile = all_players[score[0]] - data = Node.void('data') + data = Node.void("data") ranklist.add_child(data) - data.set_attribute('iidx_id', str(profile.extid)) - data.set_attribute('name', profile.get_str('name')) + data.set_attribute("iidx_id", str(profile.extid)) + data.set_attribute("name", profile.get_str("name")) - machine_name = '' + machine_name = "" machine = self.get_machine_by_id(shop_id) if machine is not None: machine_name = machine.name - data.set_attribute('opname', machine_name) - data.set_attribute('rnum', str(record_num)) - data.set_attribute('score', str(score[1].points)) - data.set_attribute('clflg', str(self.db_to_game_status(score[1].data.get_int('clear_status')))) - data.set_attribute('pid', str(profile.get_int('pid'))) - data.set_attribute('myFlg', '1' if score[0] == userid else '0') - data.set_attribute('update', '0') + data.set_attribute("opname", machine_name) + data.set_attribute("rnum", str(record_num)) + data.set_attribute("score", str(score[1].points)) + data.set_attribute( + "clflg", + str(self.db_to_game_status(score[1].data.get_int("clear_status"))), + ) + data.set_attribute("pid", str(profile.get_int("pid"))) + data.set_attribute("myFlg", "1" if score[0] == userid else "0") + data.set_attribute("update", "0") - data.set_attribute('sgrade', str( - self.db_to_game_rank(profile.get_int(self.DAN_RANKING_SINGLE, -1), self.GAME_CLTYPE_SINGLE), - )) - data.set_attribute('dgrade', str( - self.db_to_game_rank(profile.get_int(self.DAN_RANKING_DOUBLE, -1), self.GAME_CLTYPE_DOUBLE), - )) + data.set_attribute( + "sgrade", + str( + self.db_to_game_rank( + profile.get_int(self.DAN_RANKING_SINGLE, -1), + self.GAME_CLTYPE_SINGLE, + ), + ), + ) + data.set_attribute( + "dgrade", + str( + self.db_to_game_rank( + profile.get_int(self.DAN_RANKING_DOUBLE, -1), + self.GAME_CLTYPE_DOUBLE, + ), + ), + ) - qpro = profile.get_dict('qpro') - data.set_attribute('head', str(qpro.get_int('head'))) - data.set_attribute('hair', str(qpro.get_int('hair'))) - data.set_attribute('face', str(qpro.get_int('face'))) - data.set_attribute('body', str(qpro.get_int('body'))) - data.set_attribute('hand', str(qpro.get_int('hand'))) + qpro = profile.get_dict("qpro") + data.set_attribute("head", str(qpro.get_int("head"))) + data.set_attribute("hair", str(qpro.get_int("hair"))) + data.set_attribute("face", str(qpro.get_int("face"))) + data.set_attribute("body", str(qpro.get_int("body"))) + data.set_attribute("hand", str(qpro.get_int("hand"))) record_num = record_num + 1 return root def handle_IIDX26music_play_request(self, request: Node) -> Node: - musicid = int(request.attribute('mid')) - chart = self.game_to_db_chart(int(request.attribute('clid'))) - clear_status = self.game_to_db_status(int(request.attribute('cflg'))) + musicid = int(request.attribute("mid")) + chart = self.game_to_db_chart(int(request.attribute("clid"))) + clear_status = self.game_to_db_status(int(request.attribute("cflg"))) self.update_score( None, # No userid since its anonymous @@ -728,30 +796,30 @@ class IIDXRootage(IIDXCourse, IIDXBase): ) # Calculate and return statistics about this song - root = Node.void('IIDX26music') - root.set_attribute('clid', request.attribute('clid')) - root.set_attribute('mid', request.attribute('mid')) + root = Node.void("IIDX26music") + root.set_attribute("clid", request.attribute("clid")) + root.set_attribute("mid", request.attribute("mid")) attempts = self.get_clear_rates(musicid, chart) - count = attempts[musicid][chart]['total'] - clear = attempts[musicid][chart]['clears'] - full_combo = attempts[musicid][chart]['fcs'] + count = attempts[musicid][chart]["total"] + clear = attempts[musicid][chart]["clears"] + full_combo = attempts[musicid][chart]["fcs"] if count > 0: - root.set_attribute('crate', str(int((1000 * clear) / count))) - root.set_attribute('frate', str(int((1000 * full_combo) / count))) + root.set_attribute("crate", str(int((1000 * clear) / count))) + root.set_attribute("frate", str(int((1000 * full_combo) / count))) else: - root.set_attribute('crate', '0') - root.set_attribute('frate', '0') + root.set_attribute("crate", "0") + root.set_attribute("frate", "0") return root # Bare minimum response to say we handled the request def handle_IIDX26music_beginnerplay_request(self, request: Node) -> Node: - return Node.void('IIDX26music') + return Node.void("IIDX26music") def handle_IIDX26music_arenaCPU_request(self, request: Node) -> Node: - root = Node.void('IIDX26music') + root = Node.void("IIDX26music") music_list = [0, 0, 0, 0] cpu_list = [0, 0, 0, 0] grade_to_percentage_map = { @@ -770,16 +838,20 @@ class IIDXRootage(IIDXCourse, IIDXBase): 18: 80, } for item in request.children: - if item.name == 'music_list': - music_list[int(item.child_value('index'))] = int(item.child_value('total_notes')) - if item.name == 'cpu_list': - cpu_list[int(item.child_value('index'))] = int(item.child_value('grade_id')) + if item.name == "music_list": + music_list[int(item.child_value("index"))] = int( + item.child_value("total_notes") + ) + if item.name == "cpu_list": + cpu_list[int(item.child_value("index"))] = int( + item.child_value("grade_id") + ) for index in range(4): - cpu_score_list = Node.void('cpu_score_list') - cpu_score_list.add_child(Node.s32('index', index)) + cpu_score_list = Node.void("cpu_score_list") + cpu_score_list.add_child(Node.s32("index", index)) root.add_child(cpu_score_list) for i in range(4): - score_list = Node.void('score_list') + score_list = Node.void("score_list") cpu_score_list.add_child(score_list) percentage = grade_to_percentage_map.get(cpu_list[index], 0) notes = music_list[i] @@ -789,21 +861,21 @@ class IIDXRootage(IIDXCourse, IIDXBase): ghost_total = ghost_section * 64 ghost_lost = score - ghost_total ghost[0] = ghost[0] + ghost_lost - score_list.add_child(Node.s32('score', score)) - score_list.add_child(Node.bool('enable_score', True)) - score_list.add_child(Node.u8_array('ghost', ghost)) - score_list.add_child(Node.bool('enable_ghost', True)) + score_list.add_child(Node.s32("score", score)) + score_list.add_child(Node.bool("enable_score", True)) + score_list.add_child(Node.u8_array("ghost", ghost)) + score_list.add_child(Node.bool("enable_ghost", True)) return root def handle_IIDX26grade_raised_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) - cltype = int(request.attribute('gtype')) - rank = self.game_to_db_rank(int(request.attribute('gid')), cltype) + extid = int(request.attribute("iidxid")) + cltype = int(request.attribute("gtype")) + rank = self.game_to_db_rank(int(request.attribute("gid")), cltype) userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: - percent = int(request.attribute('achi')) - stages_cleared = int(request.attribute('cstage')) + percent = int(request.attribute("achi")) + stages_cleared = int(request.attribute("cstage")) cleared = stages_cleared == self.DAN_STAGES if cltype == self.GAME_CLTYPE_SINGLE: @@ -821,42 +893,44 @@ class IIDXRootage(IIDXCourse, IIDXBase): ) # Figure out number of players that played this ranking - all_achievements = self.data.local.user.get_all_achievements(self.game, self.version, achievementid=rank, achievementtype=index) - root = Node.void('IIDX26grade') - root.set_attribute('pnum', str(len(all_achievements))) + all_achievements = self.data.local.user.get_all_achievements( + self.game, self.version, achievementid=rank, achievementtype=index + ) + root = Node.void("IIDX26grade") + root.set_attribute("pnum", str(len(all_achievements))) return root def handle_IIDX26pc_common_request(self, request: Node) -> Node: - root = Node.void('IIDX26pc') - root.set_attribute('expire', '600') + root = Node.void("IIDX26pc") + root.set_attribute("expire", "600") - ir = Node.void('ir') + ir = Node.void("ir") root.add_child(ir) - ir.set_attribute('beat', '2') + ir.set_attribute("beat", "2") - escape_package_info = Node.void('escape_package_info') + escape_package_info = Node.void("escape_package_info") root.add_child(escape_package_info) - vip_black_pass = Node.void('vip_pass_black') + vip_black_pass = Node.void("vip_pass_black") root.add_child(vip_black_pass) - newsong_another = Node.void('newsong_another') + newsong_another = Node.void("newsong_another") root.add_child(newsong_another) - newsong_another.set_attribute('open', '1') + newsong_another.set_attribute("open", "1") - deller_bonus = Node.void('deller_bonus') + deller_bonus = Node.void("deller_bonus") root.add_child(deller_bonus) - deller_bonus.set_attribute('open', '1') + deller_bonus.set_attribute("open", "1") - common_evnet = Node.void('common_evnet') # Yes, this is misspelled in the game + common_evnet = Node.void("common_evnet") # Yes, this is misspelled in the game root.add_child(common_evnet) - common_evnet.set_attribute('flg', '0') + common_evnet.set_attribute("flg", "0") # See if we configured event overrides if self.machine_joined_arcade(): game_config = self.get_game_config() - event_phase = game_config.get_int('event_phase') - omni_events = game_config.get_bool('omnimix_events_enabled') + event_phase = game_config.get_int("event_phase") + omni_events = game_config.get_bool("omnimix_events_enabled") else: # If we aren't in an arcade, we turn off events event_phase = 0 @@ -875,49 +949,51 @@ class IIDXRootage(IIDXCourse, IIDXBase): event1 = 0 event2 = 2 - boss = Node.void('boss') + boss = Node.void("boss") root.add_child(boss) - boss.set_attribute('phase', str(boss_phase)) + boss.set_attribute("phase", str(boss_phase)) - event1_phase = Node.void('event1_phase') + event1_phase = Node.void("event1_phase") root.add_child(event1_phase) - event1_phase.set_attribute('phase', str(event1)) + event1_phase.set_attribute("phase", str(event1)) - event2_phase = Node.void('event2_phase') + event2_phase = Node.void("event2_phase") root.add_child(event2_phase) - event2_phase.set_attribute('phase', str(event2)) + event2_phase.set_attribute("phase", str(event2)) - extra_boss_event = Node.void('extra_boss_event') + extra_boss_event = Node.void("extra_boss_event") root.add_child(extra_boss_event) - extra_boss_event.set_attribute('phase', '2') + extra_boss_event.set_attribute("phase", "2") return root def handle_IIDX26pc_delete_request(self, request: Node) -> Node: - return Node.void('IIDX26pc') + return Node.void("IIDX26pc") def handle_IIDX26pc_playstart_request(self, request: Node) -> Node: - return Node.void('IIDX26pc') + return Node.void("IIDX26pc") def handle_IIDX26pc_playend_request(self, request: Node) -> Node: - return Node.void('IIDX26pc') + return Node.void("IIDX26pc") def handle_IIDX26pc_visit_request(self, request: Node) -> Node: - root = Node.void('IIDX26pc') - root.set_attribute('anum', '0') - root.set_attribute('snum', '0') - root.set_attribute('pnum', '0') - root.set_attribute('aflg', '0') - root.set_attribute('sflg', '0') - root.set_attribute('pflg', '0') + root = Node.void("IIDX26pc") + root.set_attribute("anum", "0") + root.set_attribute("snum", "0") + root.set_attribute("pnum", "0") + root.set_attribute("aflg", "0") + root.set_attribute("sflg", "0") + root.set_attribute("pflg", "0") return root - def handle_IIDX26pc_shopregister_request(self, request: Node) -> Node: # Not used anymore???? - root = Node.void('IIDX26pc') + def handle_IIDX26pc_shopregister_request( + self, request: Node + ) -> Node: # Not used anymore???? + root = Node.void("IIDX26pc") return root def handle_IIDX26pc_oldget_request(self, request: Node) -> Node: - refid = request.attribute('rid') + refid = request.attribute("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: oldversion = self.previous_version() @@ -925,12 +1001,12 @@ class IIDXRootage(IIDXCourse, IIDXBase): else: profile = None - root = Node.void('IIDX26pc') - root.set_attribute('status', '1' if profile is None else '0') + root = Node.void("IIDX26pc") + root.set_attribute("status", "1" if profile is None else "0") return root def handle_IIDX26pc_getname_request(self, request: Node) -> Node: - refid = request.attribute('rid') + refid = request.attribute("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: oldversion = self.previous_version() @@ -939,114 +1015,116 @@ class IIDXRootage(IIDXCourse, IIDXBase): profile = None if profile is None: raise Exception( - 'Should not get here if we have no profile, we should ' + - 'have returned \'1\' in the \'oldget\' method above ' + - 'which should tell the game not to present a migration.' + "Should not get here if we have no profile, we should " + + "have returned '1' in the 'oldget' method above " + + "which should tell the game not to present a migration." ) - root = Node.void('IIDX26pc') - root.set_attribute('name', profile.get_str('name')) - root.set_attribute('idstr', ID.format_extid(profile.extid)) - root.set_attribute('pid', str(profile.get_int('pid'))) + root = Node.void("IIDX26pc") + root.set_attribute("name", profile.get_str("name")) + root.set_attribute("idstr", ID.format_extid(profile.extid)) + root.set_attribute("pid", str(profile.get_int("pid"))) return root def handle_IIDX26pc_takeover_request(self, request: Node) -> Node: - refid = request.attribute('rid') - name = request.attribute('name') - pid = int(request.attribute('pid')) + refid = request.attribute("rid") + name = request.attribute("name") + pid = int(request.attribute("pid")) newprofile = self.new_profile_by_refid(refid, name, pid) - root = Node.void('IIDX26pc') + root = Node.void("IIDX26pc") if newprofile is not None: - root.set_attribute('id', str(newprofile.extid)) + root.set_attribute("id", str(newprofile.extid)) return root def handle_IIDX26pc_reg_request(self, request: Node) -> Node: - refid = request.attribute('rid') - name = request.attribute('name') - pid = int(request.attribute('pid')) + refid = request.attribute("rid") + name = request.attribute("name") + pid = int(request.attribute("pid")) profile = self.new_profile_by_refid(refid, name, pid) - root = Node.void('IIDX26pc') + root = Node.void("IIDX26pc") if profile is not None: - root.set_attribute('id', str(profile.extid)) - root.set_attribute('id_str', ID.format_extid(profile.extid)) + root.set_attribute("id", str(profile.extid)) + root.set_attribute("id_str", ID.format_extid(profile.extid)) return root def handle_IIDX26pc_get_request(self, request: Node) -> Node: - refid = request.attribute('rid') + refid = request.attribute("rid") root = self.get_profile_by_refid(refid) if root is None: - root = Node.void('IIDX26pc') + root = Node.void("IIDX26pc") return root def handle_IIDX26pc_save_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) + extid = int(request.attribute("iidxid")) self.put_profile_by_extid(extid, request) - return Node.void('IIDX26pc') + return Node.void("IIDX26pc") def handle_IIDX26pc_logout_request(self, request: Node) -> Node: - return Node.void('IIDX26pc') + return Node.void("IIDX26pc") def handle_IIDX26pc_eaappliresult_request(self, request: Node) -> Node: clear_map = { - self.GAME_CLEAR_STATUS_NO_PLAY: 'NO PLAY', - self.GAME_CLEAR_STATUS_FAILED: 'FAILED', - self.GAME_CLEAR_STATUS_ASSIST_CLEAR: 'ASSIST CLEAR', - self.GAME_CLEAR_STATUS_EASY_CLEAR: 'EASY CLEAR', - self.GAME_CLEAR_STATUS_CLEAR: 'CLEAR', - self.GAME_CLEAR_STATUS_HARD_CLEAR: 'HARD CLEAR', - self.GAME_CLEAR_STATUS_EX_HARD_CLEAR: 'EX HARD CLEAR', - self.GAME_CLEAR_STATUS_FULL_COMBO: 'FULL COMBO', + self.GAME_CLEAR_STATUS_NO_PLAY: "NO PLAY", + self.GAME_CLEAR_STATUS_FAILED: "FAILED", + self.GAME_CLEAR_STATUS_ASSIST_CLEAR: "ASSIST CLEAR", + self.GAME_CLEAR_STATUS_EASY_CLEAR: "EASY CLEAR", + self.GAME_CLEAR_STATUS_CLEAR: "CLEAR", + self.GAME_CLEAR_STATUS_HARD_CLEAR: "HARD CLEAR", + self.GAME_CLEAR_STATUS_EX_HARD_CLEAR: "EX HARD CLEAR", + self.GAME_CLEAR_STATUS_FULL_COMBO: "FULL COMBO", } # first we'll grab the data from the packet # did = request.child_value('did') # rid = request.child_value('rid') - name = request.child_value('name') + name = request.child_value("name") # qpro_hair = request.child_value('qpro_hair') # qpro_head = request.child_value('qpro_head') # qpro_body = request.child_value('qpro_body') # qpro_hand = request.child_value('qpro_hand') - music_id = request.child_value('music_id') - class_id = request.child_value('class_id') + music_id = request.child_value("music_id") + class_id = request.child_value("class_id") # no_save = request.child_value('no_save') # is_couple = request.child_value('is_couple') # target_graph = request.child_value('target_graph') - target_exscore = request.child_value('target_exscore') + target_exscore = request.child_value("target_exscore") # pacemaker = request.child_value('pacemaker') - best_clear = request.child_value('best_clear') + best_clear = request.child_value("best_clear") # best_djlevel = request.child_value('best_djlevel') # best_exscore = request.child_value('best_exscore') # best_misscount = request.child_value('best_misscount') - now_clear = request.child_value('now_clear') + now_clear = request.child_value("now_clear") # now_djlevel = request.child_value('now_djlevel') - now_exscore = request.child_value('now_exscore') + now_exscore = request.child_value("now_exscore") # now_misscount = request.child_value('now_misscount') - now_pgreat = request.child_value('now_pgreat') - now_great = request.child_value('now_great') - now_good = request.child_value('now_good') - now_bad = request.child_value('now_bad') - now_poor = request.child_value('now_poor') - now_combo = request.child_value('now_combo') - now_fast = request.child_value('now_fast') - now_slow = request.child_value('now_slow') - best_clear_string = clear_map.get(best_clear, 'NO PLAY') - now_clear_string = clear_map.get(now_clear, 'NO PLAY') + now_pgreat = request.child_value("now_pgreat") + now_great = request.child_value("now_great") + now_good = request.child_value("now_good") + now_bad = request.child_value("now_bad") + now_poor = request.child_value("now_poor") + now_combo = request.child_value("now_combo") + now_fast = request.child_value("now_fast") + now_slow = request.child_value("now_slow") + best_clear_string = clear_map.get(best_clear, "NO PLAY") + now_clear_string = clear_map.get(now_clear, "NO PLAY") # let's get the song info first - song = self.data.local.music.get_song(self.game, self.music_version, music_id, self.game_to_db_chart(class_id)) - notecount = song.data.get('notecount', 0) + song = self.data.local.music.get_song( + self.game, self.music_version, music_id, self.game_to_db_chart(class_id) + ) + notecount = song.data.get("notecount", 0) # Construct the dictionary for the broadcast card_data = { BroadcastConstants.DJ_NAME: name, BroadcastConstants.SONG_NAME: song.name, BroadcastConstants.ARTIST_NAME: song.artist, - BroadcastConstants.DIFFICULTY: song.data.get('difficulty', 0), + BroadcastConstants.DIFFICULTY: song.data.get("difficulty", 0), BroadcastConstants.TARGET_EXSCORE: target_exscore, BroadcastConstants.EXSCORE: now_exscore, BroadcastConstants.BEST_CLEAR_STATUS: best_clear_string, BroadcastConstants.CLEAR_STATUS: now_clear_string, - BroadcastConstants.PLAY_STATS_HEADER: 'How did you do?', + BroadcastConstants.PLAY_STATS_HEADER: "How did you do?", BroadcastConstants.PERFECT_GREATS: now_pgreat, BroadcastConstants.GREATS: now_great, BroadcastConstants.GOODS: now_good, @@ -1060,122 +1138,199 @@ class IIDXRootage(IIDXCourse, IIDXBase): max_score = notecount * 2 percent = now_exscore / max_score grade = int(9 * percent) - grades = ['F', 'F', 'E', 'D', 'C', 'B', 'A', 'AA', 'AAA', 'MAX'] + grades = ["F", "F", "E", "D", "C", "B", "A", "AA", "AAA", "MAX"] card_data[BroadcastConstants.GRADE] = grades[grade] card_data[BroadcastConstants.RATE] = str(round(percent, 2)) # Try to broadcast out the score to our webhook(s) self.data.triggers.broadcast_score(card_data, self.game, song) - return Node.void('IIDX26pc') + return Node.void("IIDX26pc") def handle_IIDX26gameSystem_systemInfo_request(self, request: Node) -> Node: - root = Node.void('IIDX26gameSystem') - arena_schedule = Node.void('arena_schedule') + root = Node.void("IIDX26gameSystem") + arena_schedule = Node.void("arena_schedule") root.add_child(arena_schedule) - arena_schedule.add_child(Node.u8('phase', 1)) - arena_schedule.add_child(Node.u32('start', 0)) - arena_schedule.add_child(Node.u32('end', 0)) - cpu_grades = [6, 7, 8, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 18] - cpu_low_diff = [4, 5, 6, 6, 7, 7, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 11, 12, 12] - cpu_high_diff = [5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 12, 12, 12] + arena_schedule.add_child(Node.u8("phase", 1)) + arena_schedule.add_child(Node.u32("start", 0)) + arena_schedule.add_child(Node.u32("end", 0)) + cpu_grades = [ + 6, + 7, + 8, + 9, + 10, + 10, + 11, + 11, + 12, + 12, + 13, + 13, + 14, + 14, + 15, + 15, + 16, + 16, + 17, + 18, + ] + cpu_low_diff = [ + 4, + 5, + 6, + 6, + 7, + 7, + 8, + 8, + 9, + 9, + 9, + 10, + 10, + 10, + 11, + 11, + 11, + 11, + 12, + 12, + ] + cpu_high_diff = [ + 5, + 6, + 6, + 7, + 7, + 8, + 8, + 9, + 9, + 10, + 10, + 10, + 11, + 11, + 11, + 12, + 12, + 12, + 12, + 12, + ] for playstyle in range(2): for arena_class in range(20): - arena_music_difficult = Node.void('arena_music_difficult') + arena_music_difficult = Node.void("arena_music_difficult") root.add_child(arena_music_difficult) - arena_music_difficult.add_child(Node.s32('play_style', playstyle)) - arena_music_difficult.add_child(Node.s32('arena_class', arena_class)) - arena_music_difficult.add_child(Node.s32('low_difficult', 1)) - arena_music_difficult.add_child(Node.s32('high_difficult', 12)) - arena_music_difficult.add_child(Node.bool('is_leggendaria', True)) - arena_music_difficult.add_child(Node.s32('force_music_list_id', 0)) - arena_cpu_define = Node.void('arena_cpu_define') + arena_music_difficult.add_child(Node.s32("play_style", playstyle)) + arena_music_difficult.add_child(Node.s32("arena_class", arena_class)) + arena_music_difficult.add_child(Node.s32("low_difficult", 1)) + arena_music_difficult.add_child(Node.s32("high_difficult", 12)) + arena_music_difficult.add_child(Node.bool("is_leggendaria", True)) + arena_music_difficult.add_child(Node.s32("force_music_list_id", 0)) + arena_cpu_define = Node.void("arena_cpu_define") root.add_child(arena_cpu_define) - arena_cpu_define.add_child(Node.s32('play_style', playstyle)) - arena_cpu_define.add_child(Node.s32('arena_class', arena_class)) - arena_cpu_define.add_child(Node.s32('grade_id', cpu_grades[arena_class])) - arena_cpu_define.add_child(Node.s32('low_music_difficult', cpu_low_diff[arena_class])) - arena_cpu_define.add_child(Node.s32('high_music_difficult', cpu_high_diff[arena_class])) - arena_cpu_define.add_child(Node.bool('is_leggendaria', arena_class >= 13)) + arena_cpu_define.add_child(Node.s32("play_style", playstyle)) + arena_cpu_define.add_child(Node.s32("arena_class", arena_class)) + arena_cpu_define.add_child( + Node.s32("grade_id", cpu_grades[arena_class]) + ) + arena_cpu_define.add_child( + Node.s32("low_music_difficult", cpu_low_diff[arena_class]) + ) + arena_cpu_define.add_child( + Node.s32("high_music_difficult", cpu_high_diff[arena_class]) + ) + arena_cpu_define.add_child( + Node.bool("is_leggendaria", arena_class >= 13) + ) for matching_class in range(21): - matching_class_range = Node.void('matching_class_range') + matching_class_range = Node.void("matching_class_range") root.add_child(matching_class_range) - matching_class_range.add_child(Node.s32('play_style', playstyle)) - matching_class_range.add_child(Node.s32('matching_class', matching_class)) - matching_class_range.add_child(Node.s32('low_arena_class', 1)) - matching_class_range.add_child(Node.s32('high_arena_class', 20)) + matching_class_range.add_child(Node.s32("play_style", playstyle)) + matching_class_range.add_child( + Node.s32("matching_class", matching_class) + ) + matching_class_range.add_child(Node.s32("low_arena_class", 1)) + matching_class_range.add_child(Node.s32("high_arena_class", 20)) return root def format_profile(self, userid: UserID, profile: Profile) -> Node: - root = Node.void('IIDX26pc') + root = Node.void("IIDX26pc") # Look up play stats we bridge to every mix play_stats = self.get_play_statistics(userid) # Look up judge window adjustments - judge_dict = profile.get_dict('machine_judge_adjust') + judge_dict = profile.get_dict("machine_judge_adjust") machine_judge = judge_dict.get_dict(self.config.machine.pcbid) # Profile data - pcdata = Node.void('pcdata') + pcdata = Node.void("pcdata") root.add_child(pcdata) - pcdata.set_attribute('id', str(profile.extid)) - pcdata.set_attribute('idstr', ID.format_extid(profile.extid)) - pcdata.set_attribute('name', profile.get_str('name')) - pcdata.set_attribute('pid', str(profile.get_int('pid'))) - pcdata.set_attribute('spnum', str(play_stats.get_int('single_plays'))) - pcdata.set_attribute('dpnum', str(play_stats.get_int('double_plays'))) - pcdata.set_attribute('sach', str(play_stats.get_int('single_dj_points'))) - pcdata.set_attribute('dach', str(play_stats.get_int('double_dj_points'))) - pcdata.set_attribute('mode', str(profile.get_int('mode'))) - pcdata.set_attribute('pmode', str(profile.get_int('pmode'))) - pcdata.set_attribute('rtype', str(profile.get_int('rtype'))) - pcdata.set_attribute('sp_opt', str(profile.get_int('sp_opt'))) - pcdata.set_attribute('dp_opt', str(profile.get_int('dp_opt'))) - pcdata.set_attribute('dp_opt2', str(profile.get_int('dp_opt2'))) - pcdata.set_attribute('gpos', str(profile.get_int('gpos'))) - pcdata.set_attribute('s_sorttype', str(profile.get_int('s_sorttype'))) - pcdata.set_attribute('d_sorttype', str(profile.get_int('d_sorttype'))) - pcdata.set_attribute('s_pace', str(profile.get_int('s_pace'))) - pcdata.set_attribute('d_pace', str(profile.get_int('d_pace'))) - pcdata.set_attribute('s_gno', str(profile.get_int('s_gno'))) - pcdata.set_attribute('d_gno', str(profile.get_int('d_gno'))) - pcdata.set_attribute('s_gtype', str(profile.get_int('s_gtype'))) - pcdata.set_attribute('d_gtype', str(profile.get_int('d_gtype'))) - pcdata.set_attribute('s_sdlen', str(profile.get_int('s_sdlen'))) - pcdata.set_attribute('d_sdlen', str(profile.get_int('d_sdlen'))) - pcdata.set_attribute('s_sdtype', str(profile.get_int('s_sdtype'))) - pcdata.set_attribute('d_sdtype', str(profile.get_int('d_sdtype'))) - pcdata.set_attribute('s_timing', str(profile.get_int('s_timing'))) - pcdata.set_attribute('d_timing', str(profile.get_int('d_timing'))) - pcdata.set_attribute('s_notes', str(profile.get_float('s_notes'))) - pcdata.set_attribute('d_notes', str(profile.get_float('d_notes'))) - pcdata.set_attribute('s_judge', str(profile.get_int('s_judge'))) - pcdata.set_attribute('d_judge', str(profile.get_int('d_judge'))) - pcdata.set_attribute('s_judgeAdj', str(machine_judge.get_int('single'))) - pcdata.set_attribute('d_judgeAdj', str(machine_judge.get_int('double'))) - pcdata.set_attribute('s_hispeed', str(profile.get_float('s_hispeed'))) - pcdata.set_attribute('d_hispeed', str(profile.get_float('d_hispeed'))) - pcdata.set_attribute('s_liflen', str(profile.get_int('s_lift'))) - pcdata.set_attribute('d_liflen', str(profile.get_int('d_lift'))) - pcdata.set_attribute('s_disp_judge', str(profile.get_int('s_disp_judge'))) - pcdata.set_attribute('d_disp_judge', str(profile.get_int('d_disp_judge'))) - pcdata.set_attribute('s_opstyle', str(profile.get_int('s_opstyle'))) - pcdata.set_attribute('d_opstyle', str(profile.get_int('d_opstyle'))) - pcdata.set_attribute('s_graph_score', str(profile.get_int('s_graph_score'))) - pcdata.set_attribute('d_graph_score', str(profile.get_int('d_graph_score'))) - pcdata.set_attribute('s_auto_scrach', str(profile.get_int('s_auto_scrach'))) - pcdata.set_attribute('d_auto_scrach', str(profile.get_int('d_auto_scrach'))) - pcdata.set_attribute('s_gauge_disp', str(profile.get_int('s_gauge_disp'))) - pcdata.set_attribute('d_gauge_disp', str(profile.get_int('d_gauge_disp'))) - pcdata.set_attribute('s_lane_brignt', str(profile.get_int('s_lane_brignt'))) - pcdata.set_attribute('d_lane_brignt', str(profile.get_int('d_lane_brignt'))) - pcdata.set_attribute('s_camera_layout', str(profile.get_int('s_camera_layout'))) - pcdata.set_attribute('d_camera_layout', str(profile.get_int('d_camera_layout'))) - pcdata.set_attribute('s_ghost_score', str(profile.get_int('s_ghost_score'))) - pcdata.set_attribute('d_ghost_score', str(profile.get_int('d_ghost_score'))) - pcdata.set_attribute('s_tsujigiri_disp', str(profile.get_int('s_tsujigiri_disp'))) - pcdata.set_attribute('d_tsujigiri_disp', str(profile.get_int('d_tsujigiri_disp'))) + pcdata.set_attribute("id", str(profile.extid)) + pcdata.set_attribute("idstr", ID.format_extid(profile.extid)) + pcdata.set_attribute("name", profile.get_str("name")) + pcdata.set_attribute("pid", str(profile.get_int("pid"))) + pcdata.set_attribute("spnum", str(play_stats.get_int("single_plays"))) + pcdata.set_attribute("dpnum", str(play_stats.get_int("double_plays"))) + pcdata.set_attribute("sach", str(play_stats.get_int("single_dj_points"))) + pcdata.set_attribute("dach", str(play_stats.get_int("double_dj_points"))) + pcdata.set_attribute("mode", str(profile.get_int("mode"))) + pcdata.set_attribute("pmode", str(profile.get_int("pmode"))) + pcdata.set_attribute("rtype", str(profile.get_int("rtype"))) + pcdata.set_attribute("sp_opt", str(profile.get_int("sp_opt"))) + pcdata.set_attribute("dp_opt", str(profile.get_int("dp_opt"))) + pcdata.set_attribute("dp_opt2", str(profile.get_int("dp_opt2"))) + pcdata.set_attribute("gpos", str(profile.get_int("gpos"))) + pcdata.set_attribute("s_sorttype", str(profile.get_int("s_sorttype"))) + pcdata.set_attribute("d_sorttype", str(profile.get_int("d_sorttype"))) + pcdata.set_attribute("s_pace", str(profile.get_int("s_pace"))) + pcdata.set_attribute("d_pace", str(profile.get_int("d_pace"))) + pcdata.set_attribute("s_gno", str(profile.get_int("s_gno"))) + pcdata.set_attribute("d_gno", str(profile.get_int("d_gno"))) + pcdata.set_attribute("s_gtype", str(profile.get_int("s_gtype"))) + pcdata.set_attribute("d_gtype", str(profile.get_int("d_gtype"))) + pcdata.set_attribute("s_sdlen", str(profile.get_int("s_sdlen"))) + pcdata.set_attribute("d_sdlen", str(profile.get_int("d_sdlen"))) + pcdata.set_attribute("s_sdtype", str(profile.get_int("s_sdtype"))) + pcdata.set_attribute("d_sdtype", str(profile.get_int("d_sdtype"))) + pcdata.set_attribute("s_timing", str(profile.get_int("s_timing"))) + pcdata.set_attribute("d_timing", str(profile.get_int("d_timing"))) + pcdata.set_attribute("s_notes", str(profile.get_float("s_notes"))) + pcdata.set_attribute("d_notes", str(profile.get_float("d_notes"))) + pcdata.set_attribute("s_judge", str(profile.get_int("s_judge"))) + pcdata.set_attribute("d_judge", str(profile.get_int("d_judge"))) + pcdata.set_attribute("s_judgeAdj", str(machine_judge.get_int("single"))) + pcdata.set_attribute("d_judgeAdj", str(machine_judge.get_int("double"))) + pcdata.set_attribute("s_hispeed", str(profile.get_float("s_hispeed"))) + pcdata.set_attribute("d_hispeed", str(profile.get_float("d_hispeed"))) + pcdata.set_attribute("s_liflen", str(profile.get_int("s_lift"))) + pcdata.set_attribute("d_liflen", str(profile.get_int("d_lift"))) + pcdata.set_attribute("s_disp_judge", str(profile.get_int("s_disp_judge"))) + pcdata.set_attribute("d_disp_judge", str(profile.get_int("d_disp_judge"))) + pcdata.set_attribute("s_opstyle", str(profile.get_int("s_opstyle"))) + pcdata.set_attribute("d_opstyle", str(profile.get_int("d_opstyle"))) + pcdata.set_attribute("s_graph_score", str(profile.get_int("s_graph_score"))) + pcdata.set_attribute("d_graph_score", str(profile.get_int("d_graph_score"))) + pcdata.set_attribute("s_auto_scrach", str(profile.get_int("s_auto_scrach"))) + pcdata.set_attribute("d_auto_scrach", str(profile.get_int("d_auto_scrach"))) + pcdata.set_attribute("s_gauge_disp", str(profile.get_int("s_gauge_disp"))) + pcdata.set_attribute("d_gauge_disp", str(profile.get_int("d_gauge_disp"))) + pcdata.set_attribute("s_lane_brignt", str(profile.get_int("s_lane_brignt"))) + pcdata.set_attribute("d_lane_brignt", str(profile.get_int("d_lane_brignt"))) + pcdata.set_attribute("s_camera_layout", str(profile.get_int("s_camera_layout"))) + pcdata.set_attribute("d_camera_layout", str(profile.get_int("d_camera_layout"))) + pcdata.set_attribute("s_ghost_score", str(profile.get_int("s_ghost_score"))) + pcdata.set_attribute("d_ghost_score", str(profile.get_int("d_ghost_score"))) + pcdata.set_attribute( + "s_tsujigiri_disp", str(profile.get_int("s_tsujigiri_disp")) + ) + pcdata.set_attribute( + "d_tsujigiri_disp", str(profile.get_int("d_tsujigiri_disp")) + ) # KAC stuff # kac_entry_info = Node.void('kac_entry_info') @@ -1186,152 +1341,192 @@ class IIDXRootage(IIDXCourse, IIDXBase): # kac_secret_music = Node.void('kac_secret_music') # kac_entry_info.add_child(kac_secret_music) - legendarias = Node.void('leggendaria_open') + legendarias = Node.void("leggendaria_open") root.add_child(legendarias) - premium_unlocks = Node.void('ea_premium_course') + premium_unlocks = Node.void("ea_premium_course") root.add_child(premium_unlocks) # Song unlock flags - secret_dict = profile.get_dict('secret') - secret = Node.void('secret') + secret_dict = profile.get_dict("secret") + secret = Node.void("secret") root.add_child(secret) - secret.add_child(Node.s64_array('flg1', secret_dict.get_int_array('flg1', 3))) - secret.add_child(Node.s64_array('flg2', secret_dict.get_int_array('flg2', 3))) - secret.add_child(Node.s64_array('flg3', secret_dict.get_int_array('flg3', 3))) + secret.add_child(Node.s64_array("flg1", secret_dict.get_int_array("flg1", 3))) + secret.add_child(Node.s64_array("flg2", secret_dict.get_int_array("flg2", 3))) + secret.add_child(Node.s64_array("flg3", secret_dict.get_int_array("flg3", 3))) # Favorites - for folder in ['favorite1', 'favorite2', 'favorite3']: + for folder in ["favorite1", "favorite2", "favorite3"]: favorite_dict = profile.get_dict(folder) - sp_mlist = b'' - sp_clist = b'' - singles_list = favorite_dict['single'] if 'single' in favorite_dict else [] + sp_mlist = b"" + sp_clist = b"" + singles_list = favorite_dict["single"] if "single" in favorite_dict else [] for single in singles_list: - sp_mlist = sp_mlist + struct.pack(' Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() play_stats = self.get_play_statistics(userid) # Track play counts - cltype = int(request.attribute('cltype')) + cltype = int(request.attribute("cltype")) if cltype == self.GAME_CLTYPE_SINGLE: - play_stats.increment_int('single_plays') + play_stats.increment_int("single_plays") if cltype == self.GAME_CLTYPE_DOUBLE: - play_stats.increment_int('double_plays') + play_stats.increment_int("double_plays") # Track DJ points - play_stats.replace_int('single_dj_points', int(request.attribute('s_achi'))) - play_stats.replace_int('double_dj_points', int(request.attribute('d_achi'))) + play_stats.replace_int("single_dj_points", int(request.attribute("s_achi"))) + play_stats.replace_int("double_dj_points", int(request.attribute("d_achi"))) # Profile settings - newprofile.replace_int('sp_opt', int(request.attribute('sp_opt'))) - newprofile.replace_int('dp_opt', int(request.attribute('dp_opt'))) - newprofile.replace_int('dp_opt2', int(request.attribute('dp_opt2'))) - newprofile.replace_int('gpos', int(request.attribute('gpos'))) - newprofile.replace_int('s_sorttype', int(request.attribute('s_sorttype'))) - newprofile.replace_int('d_sorttype', int(request.attribute('d_sorttype'))) - newprofile.replace_int('s_disp_judge', int(request.attribute('s_disp_judge'))) - newprofile.replace_int('d_disp_judge', int(request.attribute('d_disp_judge'))) - newprofile.replace_int('s_pace', int(request.attribute('s_pace'))) - newprofile.replace_int('d_pace', int(request.attribute('d_pace'))) - newprofile.replace_int('s_gno', int(request.attribute('s_gno'))) - newprofile.replace_int('d_gno', int(request.attribute('d_gno'))) - newprofile.replace_int('s_gtype', int(request.attribute('s_gtype'))) - newprofile.replace_int('d_gtype', int(request.attribute('d_gtype'))) - newprofile.replace_int('s_sdlen', int(request.attribute('s_sdlen'))) - newprofile.replace_int('d_sdlen', int(request.attribute('d_sdlen'))) - newprofile.replace_int('s_sdtype', int(request.attribute('s_sdtype'))) - newprofile.replace_int('d_sdtype', int(request.attribute('d_sdtype'))) - newprofile.replace_int('s_timing', int(request.attribute('s_timing'))) - newprofile.replace_int('d_timing', int(request.attribute('d_timing'))) - newprofile.replace_float('s_notes', float(request.attribute('s_notes'))) - newprofile.replace_float('d_notes', float(request.attribute('d_notes'))) - newprofile.replace_int('s_judge', int(request.attribute('s_judge'))) - newprofile.replace_int('d_judge', int(request.attribute('d_judge'))) - newprofile.replace_float('s_hispeed', float(request.attribute('s_hispeed'))) - newprofile.replace_float('d_hispeed', float(request.attribute('d_hispeed'))) - newprofile.replace_int('s_opstyle', int(request.attribute('s_opstyle'))) - newprofile.replace_int('d_opstyle', int(request.attribute('d_opstyle'))) - newprofile.replace_int('s_graph_score', int(request.attribute('s_graph_score'))) - newprofile.replace_int('d_graph_score', int(request.attribute('d_graph_score'))) - newprofile.replace_int('s_auto_scrach', int(request.attribute('s_auto_scrach'))) - newprofile.replace_int('d_auto_scrach', int(request.attribute('d_auto_scrach'))) - newprofile.replace_int('s_gauge_disp', int(request.attribute('s_gauge_disp'))) - newprofile.replace_int('d_gauge_disp', int(request.attribute('d_gauge_disp'))) - newprofile.replace_int('s_lane_brignt', int(request.attribute('s_lane_brignt'))) - newprofile.replace_int('d_lane_brignt', int(request.attribute('d_lane_brignt'))) - newprofile.replace_int('s_camera_layout', int(request.attribute('s_camera_layout'))) - newprofile.replace_int('d_camera_layout', int(request.attribute('d_camera_layout'))) - newprofile.replace_int('s_ghost_score', int(request.attribute('s_ghost_score'))) - newprofile.replace_int('d_ghost_score', int(request.attribute('d_ghost_score'))) - newprofile.replace_int('s_tsujigiri_disp', int(request.attribute('s_tsujigiri_disp'))) - newprofile.replace_int('d_tsujigiri_disp', int(request.attribute('d_tsujigiri_disp'))) - newprofile.replace_int('s_lift', int(request.attribute('s_lift'))) - newprofile.replace_int('d_lift', int(request.attribute('d_lift'))) - newprofile.replace_int('mode', int(request.attribute('mode'))) - newprofile.replace_int('pmode', int(request.attribute('pmode'))) - newprofile.replace_int('rtype', int(request.attribute('rtype'))) + newprofile.replace_int("sp_opt", int(request.attribute("sp_opt"))) + newprofile.replace_int("dp_opt", int(request.attribute("dp_opt"))) + newprofile.replace_int("dp_opt2", int(request.attribute("dp_opt2"))) + newprofile.replace_int("gpos", int(request.attribute("gpos"))) + newprofile.replace_int("s_sorttype", int(request.attribute("s_sorttype"))) + newprofile.replace_int("d_sorttype", int(request.attribute("d_sorttype"))) + newprofile.replace_int("s_disp_judge", int(request.attribute("s_disp_judge"))) + newprofile.replace_int("d_disp_judge", int(request.attribute("d_disp_judge"))) + newprofile.replace_int("s_pace", int(request.attribute("s_pace"))) + newprofile.replace_int("d_pace", int(request.attribute("d_pace"))) + newprofile.replace_int("s_gno", int(request.attribute("s_gno"))) + newprofile.replace_int("d_gno", int(request.attribute("d_gno"))) + newprofile.replace_int("s_gtype", int(request.attribute("s_gtype"))) + newprofile.replace_int("d_gtype", int(request.attribute("d_gtype"))) + newprofile.replace_int("s_sdlen", int(request.attribute("s_sdlen"))) + newprofile.replace_int("d_sdlen", int(request.attribute("d_sdlen"))) + newprofile.replace_int("s_sdtype", int(request.attribute("s_sdtype"))) + newprofile.replace_int("d_sdtype", int(request.attribute("d_sdtype"))) + newprofile.replace_int("s_timing", int(request.attribute("s_timing"))) + newprofile.replace_int("d_timing", int(request.attribute("d_timing"))) + newprofile.replace_float("s_notes", float(request.attribute("s_notes"))) + newprofile.replace_float("d_notes", float(request.attribute("d_notes"))) + newprofile.replace_int("s_judge", int(request.attribute("s_judge"))) + newprofile.replace_int("d_judge", int(request.attribute("d_judge"))) + newprofile.replace_float("s_hispeed", float(request.attribute("s_hispeed"))) + newprofile.replace_float("d_hispeed", float(request.attribute("d_hispeed"))) + newprofile.replace_int("s_opstyle", int(request.attribute("s_opstyle"))) + newprofile.replace_int("d_opstyle", int(request.attribute("d_opstyle"))) + newprofile.replace_int("s_graph_score", int(request.attribute("s_graph_score"))) + newprofile.replace_int("d_graph_score", int(request.attribute("d_graph_score"))) + newprofile.replace_int("s_auto_scrach", int(request.attribute("s_auto_scrach"))) + newprofile.replace_int("d_auto_scrach", int(request.attribute("d_auto_scrach"))) + newprofile.replace_int("s_gauge_disp", int(request.attribute("s_gauge_disp"))) + newprofile.replace_int("d_gauge_disp", int(request.attribute("d_gauge_disp"))) + newprofile.replace_int("s_lane_brignt", int(request.attribute("s_lane_brignt"))) + newprofile.replace_int("d_lane_brignt", int(request.attribute("d_lane_brignt"))) + newprofile.replace_int( + "s_camera_layout", int(request.attribute("s_camera_layout")) + ) + newprofile.replace_int( + "d_camera_layout", int(request.attribute("d_camera_layout")) + ) + newprofile.replace_int("s_ghost_score", int(request.attribute("s_ghost_score"))) + newprofile.replace_int("d_ghost_score", int(request.attribute("d_ghost_score"))) + newprofile.replace_int( + "s_tsujigiri_disp", int(request.attribute("s_tsujigiri_disp")) + ) + newprofile.replace_int( + "d_tsujigiri_disp", int(request.attribute("d_tsujigiri_disp")) + ) + newprofile.replace_int("s_lift", int(request.attribute("s_lift"))) + newprofile.replace_int("d_lift", int(request.attribute("d_lift"))) + newprofile.replace_int("mode", int(request.attribute("mode"))) + newprofile.replace_int("pmode", int(request.attribute("pmode"))) + newprofile.replace_int("rtype", int(request.attribute("rtype"))) # Update judge window adjustments per-machine - judge_dict = newprofile.get_dict('machine_judge_adjust') + judge_dict = newprofile.get_dict("machine_judge_adjust") machine_judge = judge_dict.get_dict(self.config.machine.pcbid) - machine_judge.replace_int('single', int(request.attribute('s_judgeAdj'))) - machine_judge.replace_int('double', int(request.attribute('d_judgeAdj'))) + machine_judge.replace_int("single", int(request.attribute("s_judgeAdj"))) + machine_judge.replace_int("double", int(request.attribute("d_judgeAdj"))) judge_dict.replace_dict(self.config.machine.pcbid, machine_judge) - newprofile.replace_dict('machine_judge_adjust', judge_dict) + newprofile.replace_dict("machine_judge_adjust", judge_dict) # Secret flags saving - secret = request.child('secret') + secret = request.child("secret") if secret is not None: - secret_dict = newprofile.get_dict('secret') - secret_dict.replace_int_array('flg1', 3, secret.child_value('flg1')) - secret_dict.replace_int_array('flg2', 3, secret.child_value('flg2')) - secret_dict.replace_int_array('flg3', 3, secret.child_value('flg3')) - newprofile.replace_dict('secret', secret_dict) + secret_dict = newprofile.get_dict("secret") + secret_dict.replace_int_array("flg1", 3, secret.child_value("flg1")) + secret_dict.replace_int_array("flg2", 3, secret.child_value("flg2")) + secret_dict.replace_int_array("flg3", 3, secret.child_value("flg3")) + newprofile.replace_dict("secret", secret_dict) # Basic achievements - achievements = request.child('achievements') + achievements = request.child("achievements") if achievements is not None: - newprofile.replace_int('visit_flg', int(achievements.attribute('visit_flg'))) - newprofile.replace_int('last_weekly', int(achievements.attribute('last_weekly'))) - newprofile.replace_int('weekly_num', int(achievements.attribute('weekly_num'))) + newprofile.replace_int( + "visit_flg", int(achievements.attribute("visit_flg")) + ) + newprofile.replace_int( + "last_weekly", int(achievements.attribute("last_weekly")) + ) + newprofile.replace_int( + "weekly_num", int(achievements.attribute("weekly_num")) + ) - pack_id = int(achievements.attribute('pack_id')) + pack_id = int(achievements.attribute("pack_id")) if pack_id > 0: self.data.local.user.put_achievement( self.game, self.version, userid, pack_id, - 'daily', + "daily", { - 'pack_flg': int(achievements.attribute('pack_flg')), - 'pack_comp': int(achievements.attribute('pack_comp')), + "pack_flg": int(achievements.attribute("pack_flg")), + "pack_comp": int(achievements.attribute("pack_comp")), }, ) - trophies = achievements.child('trophy') + trophies = achievements.child("trophy") if trophies is not None: # We only load the first 20 in profile load. - newprofile.replace_int_array('trophy', 20, trophies.value[:20]) + newprofile.replace_int_array("trophy", 20, trophies.value[:20]) # Deller saving - deller = request.child('deller') + deller = request.child("deller") if deller is not None: - newprofile.replace_int('deller', newprofile.get_int('deller') + int(deller.attribute('deller'))) + newprofile.replace_int( + "deller", newprofile.get_int("deller") + int(deller.attribute("deller")) + ) # Secret course expert point saving - expert_point = request.child('expert_point') + expert_point = request.child("expert_point") if expert_point is not None: - courseid = int(expert_point.attribute('course_id')) + courseid = int(expert_point.attribute("course_id")) # Update achievement to track expert points expert_point_achievement = self.data.local.user.get_achievement( @@ -1710,21 +2041,21 @@ class IIDXRootage(IIDXCourse, IIDXBase): self.version, userid, courseid, - 'expert_point', + "expert_point", ) if expert_point_achievement is None: expert_point_achievement = ValidatedDict() expert_point_achievement.replace_int( - 'normal_points', - int(expert_point.attribute('n_point')), + "normal_points", + int(expert_point.attribute("n_point")), ) expert_point_achievement.replace_int( - 'hyper_points', - int(expert_point.attribute('h_point')), + "hyper_points", + int(expert_point.attribute("h_point")), ) expert_point_achievement.replace_int( - 'another_points', - int(expert_point.attribute('a_point')), + "another_points", + int(expert_point.attribute("a_point")), ) self.data.local.user.put_achievement( @@ -1732,7 +2063,7 @@ class IIDXRootage(IIDXCourse, IIDXBase): self.version, userid, courseid, - 'expert_point', + "expert_point", expert_point_achievement, ) @@ -1741,222 +2072,312 @@ class IIDXRootage(IIDXCourse, IIDXBase): singles = [] doubles = [] name = None - if favorite.name in ['favorite', 'extra_favorite']: - if favorite.name == 'favorite': - name = 'favorite1' - elif favorite.name == 'extra_favorite': - folder = favorite.attribute('folder_id') - if folder == '0': - name = 'favorite2' - if folder == '1': - name = 'favorite3' + if favorite.name in ["favorite", "extra_favorite"]: + if favorite.name == "favorite": + name = "favorite1" + elif favorite.name == "extra_favorite": + folder = favorite.attribute("folder_id") + if folder == "0": + name = "favorite2" + if folder == "1": + name = "favorite3" if name is None: continue - single_music_bin = favorite.child_value('sp_mlist') - single_chart_bin = favorite.child_value('sp_clist') - double_music_bin = favorite.child_value('dp_mlist') - double_chart_bin = favorite.child_value('dp_clist') + single_music_bin = favorite.child_value("sp_mlist") + single_chart_bin = favorite.child_value("sp_clist") + double_music_bin = favorite.child_value("dp_mlist") + double_chart_bin = favorite.child_value("dp_clist") for i in range(self.FAVORITE_LIST_LENGTH): - singles.append({ - 'id': struct.unpack(' List[Tuple[str, Dict[str, Any]]]: + def run_scheduled_work( + cls, data: Data, config: Dict[str, Any] + ) -> List[Tuple[str, Dict[str, Any]]]: """ Insert dailies into the DB. """ events = [] - if data.local.network.should_schedule(cls.game, cls.version, 'daily_charts', 'daily'): + if data.local.network.should_schedule( + cls.game, cls.version, "daily_charts", "daily" + ): # Generate a new list of three dailies. - start_time, end_time = data.local.network.get_schedule_duration('daily') - all_songs = list(set([song.id for song in data.local.music.get_all_songs(cls.game, cls.version)])) + start_time, end_time = data.local.network.get_schedule_duration("daily") + all_songs = list( + set( + [ + song.id + for song in data.local.music.get_all_songs( + cls.game, cls.version + ) + ] + ) + ) if len(all_songs) >= 3: daily_songs = random.sample(all_songs, 3) data.local.game.put_time_sensitive_settings( cls.game, cls.version, - 'dailies', + "dailies", { - 'start_time': start_time, - 'end_time': end_time, - 'music': daily_songs, + "start_time": start_time, + "end_time": end_time, + "music": daily_songs, }, ) - events.append(( - 'iidx_daily_charts', - { - 'version': cls.version, - 'music': daily_songs, - }, - )) + events.append( + ( + "iidx_daily_charts", + { + "version": cls.version, + "music": daily_songs, + }, + ) + ) # Mark that we did some actual work here. - data.local.network.mark_scheduled(cls.game, cls.version, 'daily_charts', 'daily') + data.local.network.mark_scheduled( + cls.game, cls.version, "daily_charts", "daily" + ) return events @classmethod @@ -137,33 +162,33 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): Return all of our front-end modifiably settings. """ return { - 'bools': [ + "bools": [ { - 'name': 'Global Shop Ranking', - 'tip': 'Return network-wide ranking instead of shop ranking on results screen.', - 'category': 'game_config', - 'setting': 'global_shop_ranking', + "name": "Global Shop Ranking", + "tip": "Return network-wide ranking instead of shop ranking on results screen.", + "category": "game_config", + "setting": "global_shop_ranking", }, { - 'name': 'Events In Omnimix', - 'tip': 'Allow events to be enabled at all for Omnimix.', - 'category': 'game_config', - 'setting': 'omnimix_events_enabled', + "name": "Events In Omnimix", + "tip": "Allow events to be enabled at all for Omnimix.", + "category": "game_config", + "setting": "omnimix_events_enabled", }, ], - 'ints': [ + "ints": [ { - 'name': 'Event Phase', - 'tip': 'Event phase for all players.', - 'category': 'game_config', - 'setting': 'event_phase', - 'values': { - 0: 'No Event', - 1: 'Koujyo SINOBUZ Den Phase 1', - 2: 'Koujyo SINOBUZ Den Phase 2', - 3: 'Koujyo SINOBUZ Den Phase 3', - 4: 'Ninnin Shichikenden', - } + "name": "Event Phase", + "tip": "Event phase for all players.", + "category": "game_config", + "setting": "event_phase", + "values": { + 0: "No Event", + 1: "Koujyo SINOBUZ Den Phase 1", + 2: "Koujyo SINOBUZ Den Phase 2", + 3: "Koujyo SINOBUZ Den Phase 3", + 4: "Ninnin Shichikenden", + }, }, ], } @@ -242,7 +267,7 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): self.DAN_RANK_KAIDEN: self.GAME_DP_DAN_RANK_KAIDEN, }[db_dan] else: - raise Exception('Invalid cltype!') + raise Exception("Invalid cltype!") def game_to_db_rank(self, game_dan: int, cltype: int) -> int: # Special case for no DAN rank @@ -294,7 +319,7 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): self.GAME_DP_DAN_RANK_KAIDEN: self.DAN_RANK_KAIDEN, }[game_dan] else: - raise Exception('Invalid cltype!') + raise Exception("Invalid cltype!") def game_to_db_chart(self, db_chart: int) -> int: return { @@ -311,80 +336,86 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): machine = self.data.local.machine.get_machine(self.config.machine.pcbid) if machine is not None: machine_name = machine.name - close = machine.data.get_bool('close') - hour = machine.data.get_int('hour') - minute = machine.data.get_int('minute') + close = machine.data.get_bool("close") + hour = machine.data.get_int("hour") + minute = machine.data.get_int("minute") else: - machine_name = '' + machine_name = "" close = False hour = 0 minute = 0 - root = Node.void('IIDX24shop') - root.set_attribute('opname', machine_name) - root.set_attribute('pid', str(self.get_machine_region())) - root.set_attribute('cls_opt', '1' if close else '0') - root.set_attribute('hr', str(hour)) - root.set_attribute('mi', str(minute)) + root = Node.void("IIDX24shop") + root.set_attribute("opname", machine_name) + root.set_attribute("pid", str(self.get_machine_region())) + root.set_attribute("cls_opt", "1" if close else "0") + root.set_attribute("hr", str(hour)) + root.set_attribute("mi", str(minute)) return root def handle_IIDX24shop_savename_request(self, request: Node) -> Node: - self.update_machine_name(request.attribute('opname')) + self.update_machine_name(request.attribute("opname")) - shop_close = intish(request.attribute('cls_opt')) or 0 - minutes = intish(request.attribute('mnt')) or 0 - hours = intish(request.attribute('hr')) or 0 + shop_close = intish(request.attribute("cls_opt")) or 0 + minutes = intish(request.attribute("mnt")) or 0 + hours = intish(request.attribute("hr")) or 0 - self.update_machine_data({ - 'close': shop_close != 0, - 'minutes': minutes, - 'hours': hours, - }) + self.update_machine_data( + { + "close": shop_close != 0, + "minutes": minutes, + "hours": hours, + } + ) - return Node.void('IIDX24shop') + return Node.void("IIDX24shop") def handle_IIDX24shop_sentinfo_request(self, request: Node) -> Node: - return Node.void('IIDX24shop') + return Node.void("IIDX24shop") def handle_IIDX24shop_sendescapepackageinfo_request(self, request: Node) -> Node: - root = Node.void('IIDX24shop') - root.set_attribute('expire', str((Time.now() + 86400 * 365) * 1000)) + root = Node.void("IIDX24shop") + root.set_attribute("expire", str((Time.now() + 86400 * 365) * 1000)) return root def handle_IIDX24shop_getconvention_request(self, request: Node) -> Node: - root = Node.void('IIDX24shop') + root = Node.void("IIDX24shop") machine = self.data.local.machine.get_machine(self.config.machine.pcbid) if machine.arcade is not None: - course = self.data.local.machine.get_settings(machine.arcade, self.game, self.music_version, 'shop_course') + course = self.data.local.machine.get_settings( + machine.arcade, self.game, self.music_version, "shop_course" + ) else: course = None if course is None: course = ValidatedDict() - root.set_attribute('music_0', str(course.get_int('music_0', 20032))) - root.set_attribute('music_1', str(course.get_int('music_1', 20009))) - root.set_attribute('music_2', str(course.get_int('music_2', 20015))) - root.set_attribute('music_3', str(course.get_int('music_3', 20064))) - root.add_child(Node.bool('valid', course.get_bool('valid'))) + root.set_attribute("music_0", str(course.get_int("music_0", 20032))) + root.set_attribute("music_1", str(course.get_int("music_1", 20009))) + root.set_attribute("music_2", str(course.get_int("music_2", 20015))) + root.set_attribute("music_3", str(course.get_int("music_3", 20064))) + root.add_child(Node.bool("valid", course.get_bool("valid"))) return root def handle_IIDX24shop_setconvention_request(self, request: Node) -> Node: machine = self.data.local.machine.get_machine(self.config.machine.pcbid) if machine.arcade is not None: course = ValidatedDict() - course.replace_int('music_0', request.child_value('music_0')) - course.replace_int('music_1', request.child_value('music_1')) - course.replace_int('music_2', request.child_value('music_2')) - course.replace_int('music_3', request.child_value('music_3')) - course.replace_bool('valid', request.child_value('valid')) - self.data.local.machine.put_settings(machine.arcade, self.game, self.music_version, 'shop_course', course) + course.replace_int("music_0", request.child_value("music_0")) + course.replace_int("music_1", request.child_value("music_1")) + course.replace_int("music_2", request.child_value("music_2")) + course.replace_int("music_3", request.child_value("music_3")) + course.replace_bool("valid", request.child_value("valid")) + self.data.local.machine.put_settings( + machine.arcade, self.game, self.music_version, "shop_course", course + ) - return Node.void('IIDX24shop') + return Node.void("IIDX24shop") def handle_IIDX24ranking_getranker_request(self, request: Node) -> Node: - root = Node.void('IIDX24ranking') - chart = self.game_to_db_chart(int(request.attribute('clid'))) + root = Node.void("IIDX24ranking") + chart = self.game_to_db_chart(int(request.attribute("clid"))) if chart not in [ self.CHART_TYPE_N7, self.CHART_TYPE_H7, @@ -398,29 +429,31 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): machine = self.data.local.machine.get_machine(self.config.machine.pcbid) if machine.arcade is not None: - course = self.data.local.machine.get_settings(machine.arcade, self.game, self.music_version, 'shop_course') + course = self.data.local.machine.get_settings( + machine.arcade, self.game, self.music_version, "shop_course" + ) else: course = None if course is None: course = ValidatedDict() - if not course.get_bool('valid'): + if not course.get_bool("valid"): # Shop course not enabled or not present return root - convention = Node.void('convention') + convention = Node.void("convention") root.add_child(convention) - convention.set_attribute('clid', str(chart)) - convention.set_attribute('update_date', str(Time.now() * 1000)) + convention.set_attribute("clid", str(chart)) + convention.set_attribute("update_date", str(Time.now() * 1000)) # Grab all scores for each of the four songs, filter out people who haven't # set us as their arcade and then return the top 20 scores (adding all 4 songs). songids = [ - course.get_int('music_0'), - course.get_int('music_1'), - course.get_int('music_2'), - course.get_int('music_3'), + course.get_int("music_0"), + course.get_int("music_1"), + course.get_int("music_2"), + course.get_int("music_3"), ] totalscores: Dict[UserID, int] = {} @@ -457,37 +490,37 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): for topscore in topscores: rank = rank + 1 - detail = Node.void('detail') + detail = Node.void("detail") convention.add_child(detail) - detail.set_attribute('name', topscore[1].get_str('name')) - detail.set_attribute('rank', str(rank)) - detail.set_attribute('score', str(topscore[0])) - detail.set_attribute('pid', str(topscore[1].get_int('pid'))) + detail.set_attribute("name", topscore[1].get_str("name")) + detail.set_attribute("rank", str(rank)) + detail.set_attribute("score", str(topscore[0])) + detail.set_attribute("pid", str(topscore[1].get_int("pid"))) - qpro = topscore[1].get_dict('qpro') - detail.set_attribute('head', str(qpro.get_int('head'))) - detail.set_attribute('hair', str(qpro.get_int('hair'))) - detail.set_attribute('face', str(qpro.get_int('face'))) - detail.set_attribute('body', str(qpro.get_int('body'))) - detail.set_attribute('hand', str(qpro.get_int('hand'))) + qpro = topscore[1].get_dict("qpro") + detail.set_attribute("head", str(qpro.get_int("head"))) + detail.set_attribute("hair", str(qpro.get_int("hair"))) + detail.set_attribute("face", str(qpro.get_int("face"))) + detail.set_attribute("body", str(qpro.get_int("body"))) + detail.set_attribute("hand", str(qpro.get_int("hand"))) return root def handle_IIDX24ranking_entry_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) - courseid = int(request.attribute('coid')) - chart = self.game_to_db_chart(int(request.attribute('clid'))) - course_type = int(request.attribute('regist_type')) - clear_status = self.game_to_db_status(int(request.attribute('clr'))) - pgreats = int(request.attribute('pgnum')) - greats = int(request.attribute('gnum')) + extid = int(request.attribute("iidxid")) + courseid = int(request.attribute("coid")) + chart = self.game_to_db_chart(int(request.attribute("clid"))) + course_type = int(request.attribute("regist_type")) + clear_status = self.game_to_db_status(int(request.attribute("clr"))) + pgreats = int(request.attribute("pgnum")) + greats = int(request.attribute("gnum")) if course_type == 0: index = self.COURSE_TYPE_INTERNET_RANKING elif course_type == 1: index = self.COURSE_TYPE_SECRET else: - raise Exception('Unknown registration type for course entry!') + raise Exception("Unknown registration type for course entry!") userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: @@ -504,18 +537,18 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): # We should return the user's position, but its not displayed anywhere # so fuck it. - root = Node.void('IIDX24ranking') - root.set_attribute('anum', '1') - root.set_attribute('jun', '1') + root = Node.void("IIDX24ranking") + root.set_attribute("anum", "1") + root.set_attribute("jun", "1") return root def handle_IIDX24ranking_classicentry_request(self, request: Node) -> Node: - extid = int(request.attribute('iidx_id')) - courseid = int(request.attribute('course_id')) - coursestyle = int(request.attribute('play_style')) - clear_status = self.game_to_db_status(int(request.attribute('clear_flg'))) - pgreats = int(request.attribute('pgnum')) - greats = int(request.attribute('gnum')) + extid = int(request.attribute("iidx_id")) + courseid = int(request.attribute("course_id")) + coursestyle = int(request.attribute("play_style")) + clear_status = self.game_to_db_status(int(request.attribute("clear_flg"))) + pgreats = int(request.attribute("pgnum")) + greats = int(request.attribute("gnum")) userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: @@ -530,13 +563,22 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): greats, ) - return Node.void('IIDX24ranking') + return Node.void("IIDX24ranking") def handle_IIDX24music_crate_request(self, request: Node) -> Node: - root = Node.void('IIDX24music') + root = Node.void("IIDX24music") attempts = self.get_clear_rates() - all_songs = list(set([song.id for song in self.data.local.music.get_all_songs(self.game, self.music_version)])) + all_songs = list( + set( + [ + song.id + for song in self.data.local.music.get_all_songs( + self.game, self.music_version + ) + ] + ) + ) for song in all_songs: clears = [] fcs = [] @@ -545,33 +587,33 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): placed = False if song in attempts and chart in attempts[song]: values = attempts[song][chart] - if values['total'] > 0: - clears.append(int((1000 * values['clears']) / values['total'])) - fcs.append(int((1000 * values['fcs']) / values['total'])) + if values["total"] > 0: + clears.append(int((1000 * values["clears"]) / values["total"])) + fcs.append(int((1000 * values["fcs"]) / values["total"])) placed = True if not placed: clears.append(1001) fcs.append(1001) - clearnode = Node.s32_array('c', clears + fcs) - clearnode.set_attribute('mid', str(song)) + clearnode = Node.s32_array("c", clears + fcs) + clearnode.set_attribute("mid", str(song)) root.add_child(clearnode) return root def handle_IIDX24music_getrank_request(self, request: Node) -> Node: - cltype = int(request.attribute('cltype')) + cltype = int(request.attribute("cltype")) - root = Node.void('IIDX24music') - style = Node.void('style') + root = Node.void("IIDX24music") + style = Node.void("style") root.add_child(style) - style.set_attribute('type', str(cltype)) + style.set_attribute("type", str(cltype)) for rivalid in [-1, 0, 1, 2, 3, 4]: if rivalid == -1: - attr = 'iidxid' + attr = "iidxid" else: - attr = f'iidxid{rivalid}' + attr = f"iidxid{rivalid}" try: extid = int(request.attribute(attr)) @@ -580,51 +622,59 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): continue userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: - scores = self.data.remote.music.get_scores(self.game, self.music_version, userid) + scores = self.data.remote.music.get_scores( + self.game, self.music_version, userid + ) # Grab score data for user/rival scoredata = self.make_score_struct( scores, - self.CLEAR_TYPE_SINGLE if cltype == self.GAME_CLTYPE_SINGLE else self.CLEAR_TYPE_DOUBLE, + self.CLEAR_TYPE_SINGLE + if cltype == self.GAME_CLTYPE_SINGLE + else self.CLEAR_TYPE_DOUBLE, rivalid, ) for s in scoredata: - root.add_child(Node.s16_array('m', s)) + root.add_child(Node.s16_array("m", s)) # Grab most played for user/rival most_played = [ - play[0] for play in - self.data.local.music.get_most_played(self.game, self.music_version, userid, 20) + play[0] + for play in self.data.local.music.get_most_played( + self.game, self.music_version, userid, 20 + ) ] if len(most_played) < 20: most_played.extend([0] * (20 - len(most_played))) - best = Node.u16_array('best', most_played) - best.set_attribute('rno', str(rivalid)) + best = Node.u16_array("best", most_played) + best.set_attribute("rno", str(rivalid)) root.add_child(best) if rivalid == -1: # Grab beginner statuses for user only beginnerdata = self.make_beginner_struct(scores) for b in beginnerdata: - root.add_child(Node.u16_array('b', b)) + root.add_child(Node.u16_array("b", b)) return root def handle_IIDX24music_appoint_request(self, request: Node) -> Node: - musicid = int(request.attribute('mid')) - chart = self.game_to_db_chart(int(request.attribute('clid'))) - ghost_type = int(request.attribute('ctype')) - extid = int(request.attribute('iidxid')) + musicid = int(request.attribute("mid")) + chart = self.game_to_db_chart(int(request.attribute("clid"))) + ghost_type = int(request.attribute("ctype")) + extid = int(request.attribute("iidxid")) userid = self.data.remote.user.from_extid(self.game, self.version, extid) - root = Node.void('IIDX24music') + root = Node.void("IIDX24music") if userid is not None: # Try to look up previous ghost for user - my_score = self.data.remote.music.get_score(self.game, self.music_version, userid, musicid, chart) + my_score = self.data.remote.music.get_score( + self.game, self.music_version, userid, musicid, chart + ) if my_score is not None: - mydata = Node.binary('mydata', my_score.data.get_bytes('ghost')) - mydata.set_attribute('score', str(my_score.points)) + mydata = Node.binary("mydata", my_score.data.get_bytes("ghost")) + mydata.set_attribute("score", str(my_score.points)) root.add_child(mydata) ghost_score = self.get_ghost( @@ -639,7 +689,7 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): self.GAME_GHOST_TYPE_RIVAL_TOP: self.GHOST_TYPE_RIVAL_TOP, self.GAME_GHOST_TYPE_RIVAL_AVERAGE: self.GHOST_TYPE_RIVAL_AVERAGE, }.get(ghost_type, self.GHOST_TYPE_NONE), - request.attribute('subtype'), + request.attribute("subtype"), self.GAME_GHOST_LENGTH, musicid, chart, @@ -648,27 +698,27 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): # Add ghost score if we support it if ghost_score is not None: - sdata = Node.binary('sdata', ghost_score['ghost']) - sdata.set_attribute('score', str(ghost_score['score'])) - if 'name' in ghost_score: - sdata.set_attribute('name', ghost_score['name']) - if 'pid' in ghost_score: - sdata.set_attribute('pid', str(ghost_score['pid'])) - if 'extid' in ghost_score: - sdata.set_attribute('riidxid', str(ghost_score['extid'])) + sdata = Node.binary("sdata", ghost_score["ghost"]) + sdata.set_attribute("score", str(ghost_score["score"])) + if "name" in ghost_score: + sdata.set_attribute("name", ghost_score["name"]) + if "pid" in ghost_score: + sdata.set_attribute("pid", str(ghost_score["pid"])) + if "extid" in ghost_score: + sdata.set_attribute("riidxid", str(ghost_score["extid"])) root.add_child(sdata) return root def handle_IIDX24music_breg_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) - musicid = int(request.attribute('mid')) + extid = int(request.attribute("iidxid")) + musicid = int(request.attribute("mid")) userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: - clear_status = self.game_to_db_status(int(request.attribute('cflg'))) - pgreats = int(request.attribute('pgnum')) - greats = int(request.attribute('gnum')) + clear_status = self.game_to_db_status(int(request.attribute("cflg"))) + pgreats = int(request.attribute("pgnum")) + greats = int(request.attribute("gnum")) self.update_score( userid, @@ -678,23 +728,23 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): pgreats, greats, -1, - b'', + b"", None, ) # Return nothing. - return Node.void('IIDX24music') + return Node.void("IIDX24music") def handle_IIDX24music_reg_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) - musicid = int(request.attribute('mid')) - chart = self.game_to_db_chart(int(request.attribute('clid'))) + extid = int(request.attribute("iidxid")) + musicid = int(request.attribute("mid")) + chart = self.game_to_db_chart(int(request.attribute("clid"))) userid = self.data.remote.user.from_extid(self.game, self.version, extid) # See if we need to report global or shop scores if self.machine_joined_arcade(): game_config = self.get_game_config() - global_scores = game_config.get_bool('global_shop_ranking') + global_scores = game_config.get_bool("global_shop_ranking") machine = self.data.local.machine.get_machine(self.config.machine.pcbid) else: # If we aren't in an arcade, we can only show global scores @@ -703,21 +753,27 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): # First, determine our current ranking before saving the new score all_scores = sorted( - self.data.remote.music.get_all_scores(game=self.game, version=self.music_version, songid=musicid, songchart=chart), + self.data.remote.music.get_all_scores( + game=self.game, + version=self.music_version, + songid=musicid, + songchart=chart, + ), key=lambda s: (s[1].points, s[1].timestamp), reverse=True, ) all_players = { - uid: prof for (uid, prof) in - self.get_any_profiles([s[0] for s in all_scores]) + uid: prof + for (uid, prof) in self.get_any_profiles([s[0] for s in all_scores]) } if not global_scores: all_scores = [ - score for score in all_scores + score + for score in all_scores if ( - score[0] == userid or - self.user_joined_arcade(machine, all_players[score[0]]) + score[0] == userid + or self.user_joined_arcade(machine, all_players[score[0]]) ) ] @@ -729,12 +785,12 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): break if userid is not None: - clear_status = self.game_to_db_status(int(request.attribute('cflg'))) - pgreats = int(request.attribute('pgnum')) - greats = int(request.attribute('gnum')) - miss_count = int(request.attribute('mnum')) - ghost = request.child_value('ghost') - shopid = ID.parse_machine_id(request.attribute('shopconvid')) + clear_status = self.game_to_db_status(int(request.attribute("cflg"))) + pgreats = int(request.attribute("pgnum")) + greats = int(request.attribute("gnum")) + miss_count = int(request.attribute("mnum")) + ghost = request.child_value("ghost") + shopid = ID.parse_machine_id(request.attribute("shopconvid")) self.update_score( userid, @@ -749,51 +805,56 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): ) # Calculate and return statistics about this song - root = Node.void('IIDX24music') - root.set_attribute('clid', request.attribute('clid')) - root.set_attribute('mid', request.attribute('mid')) + root = Node.void("IIDX24music") + root.set_attribute("clid", request.attribute("clid")) + root.set_attribute("mid", request.attribute("mid")) attempts = self.get_clear_rates(musicid, chart) - count = attempts[musicid][chart]['total'] - clear = attempts[musicid][chart]['clears'] - full_combo = attempts[musicid][chart]['fcs'] + count = attempts[musicid][chart]["total"] + clear = attempts[musicid][chart]["clears"] + full_combo = attempts[musicid][chart]["fcs"] if count > 0: - root.set_attribute('crate', str(int((1000 * clear) / count))) - root.set_attribute('frate', str(int((1000 * full_combo) / count))) + root.set_attribute("crate", str(int((1000 * clear) / count))) + root.set_attribute("frate", str(int((1000 * full_combo) / count))) else: - root.set_attribute('crate', '0') - root.set_attribute('frate', '0') - root.set_attribute('rankside', '0') + root.set_attribute("crate", "0") + root.set_attribute("frate", "0") + root.set_attribute("rankside", "0") if userid is not None: # Shop ranking - shopdata = Node.void('shopdata') + shopdata = Node.void("shopdata") root.add_child(shopdata) - shopdata.set_attribute('rank', '-1' if oldindex is None else str(oldindex + 1)) + shopdata.set_attribute( + "rank", "-1" if oldindex is None else str(oldindex + 1) + ) # Grab the rank of some other players on this song - ranklist = Node.void('ranklist') + ranklist = Node.void("ranklist") root.add_child(ranklist) all_scores = sorted( - self.data.remote.music.get_all_scores(game=self.game, version=self.music_version, songid=musicid, songchart=chart), + self.data.remote.music.get_all_scores( + game=self.game, + version=self.music_version, + songid=musicid, + songchart=chart, + ), key=lambda s: (s[1].points, s[1].timestamp), reverse=True, ) - missing_players = [ - uid for (uid, _) in all_scores - if uid not in all_players - ] + missing_players = [uid for (uid, _) in all_scores if uid not in all_players] for (uid, prof) in self.get_any_profiles(missing_players): all_players[uid] = prof if not global_scores: all_scores = [ - score for score in all_scores + score + for score in all_scores if ( - score[0] == userid or - self.user_joined_arcade(machine, all_players[score[0]]) + score[0] == userid + or self.user_joined_arcade(machine, all_players[score[0]]) ) ] @@ -804,60 +865,75 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): ourindex = i break if ourindex is None: - raise Exception('Cannot find our own score after saving to DB!') + raise Exception("Cannot find our own score after saving to DB!") start = ourindex - 4 end = ourindex + 4 if start < 0: start = 0 if end >= len(all_scores): end = len(all_scores) - 1 - relevant_scores = all_scores[start:(end + 1)] + relevant_scores = all_scores[start : (end + 1)] record_num = start + 1 for score in relevant_scores: profile = all_players[score[0]] - data = Node.void('data') + data = Node.void("data") ranklist.add_child(data) - data.set_attribute('iidx_id', str(profile.extid)) - data.set_attribute('name', profile.get_str('name')) + data.set_attribute("iidx_id", str(profile.extid)) + data.set_attribute("name", profile.get_str("name")) - machine_name = '' - if 'shop_location' in profile: - shop_id = profile.get_int('shop_location') + machine_name = "" + if "shop_location" in profile: + shop_id = profile.get_int("shop_location") machine = self.get_machine_by_id(shop_id) if machine is not None: machine_name = machine.name - data.set_attribute('opname', machine_name) - data.set_attribute('rnum', str(record_num)) - data.set_attribute('score', str(score[1].points)) - data.set_attribute('clflg', str(self.db_to_game_status(score[1].data.get_int('clear_status')))) - data.set_attribute('pid', str(profile.get_int('pid'))) - data.set_attribute('myFlg', '1' if score[0] == userid else '0') - data.set_attribute('update', '0') + data.set_attribute("opname", machine_name) + data.set_attribute("rnum", str(record_num)) + data.set_attribute("score", str(score[1].points)) + data.set_attribute( + "clflg", + str(self.db_to_game_status(score[1].data.get_int("clear_status"))), + ) + data.set_attribute("pid", str(profile.get_int("pid"))) + data.set_attribute("myFlg", "1" if score[0] == userid else "0") + data.set_attribute("update", "0") - data.set_attribute('sgrade', str( - self.db_to_game_rank(profile.get_int(self.DAN_RANKING_SINGLE, -1), self.GAME_CLTYPE_SINGLE), - )) - data.set_attribute('dgrade', str( - self.db_to_game_rank(profile.get_int(self.DAN_RANKING_DOUBLE, -1), self.GAME_CLTYPE_DOUBLE), - )) + data.set_attribute( + "sgrade", + str( + self.db_to_game_rank( + profile.get_int(self.DAN_RANKING_SINGLE, -1), + self.GAME_CLTYPE_SINGLE, + ), + ), + ) + data.set_attribute( + "dgrade", + str( + self.db_to_game_rank( + profile.get_int(self.DAN_RANKING_DOUBLE, -1), + self.GAME_CLTYPE_DOUBLE, + ), + ), + ) - qpro = profile.get_dict('qpro') - data.set_attribute('head', str(qpro.get_int('head'))) - data.set_attribute('hair', str(qpro.get_int('hair'))) - data.set_attribute('face', str(qpro.get_int('face'))) - data.set_attribute('body', str(qpro.get_int('body'))) - data.set_attribute('hand', str(qpro.get_int('hand'))) + qpro = profile.get_dict("qpro") + data.set_attribute("head", str(qpro.get_int("head"))) + data.set_attribute("hair", str(qpro.get_int("hair"))) + data.set_attribute("face", str(qpro.get_int("face"))) + data.set_attribute("body", str(qpro.get_int("body"))) + data.set_attribute("hand", str(qpro.get_int("hand"))) record_num = record_num + 1 return root def handle_IIDX24music_play_request(self, request: Node) -> Node: - musicid = int(request.attribute('mid')) - chart = self.game_to_db_chart(int(request.attribute('clid'))) - clear_status = self.game_to_db_status(int(request.attribute('cflg'))) + musicid = int(request.attribute("mid")) + chart = self.game_to_db_chart(int(request.attribute("clid"))) + clear_status = self.game_to_db_status(int(request.attribute("cflg"))) self.update_score( None, # No userid since its anonymous @@ -872,33 +948,33 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): ) # Calculate and return statistics about this song - root = Node.void('IIDX24music') - root.set_attribute('clid', request.attribute('clid')) - root.set_attribute('mid', request.attribute('mid')) + root = Node.void("IIDX24music") + root.set_attribute("clid", request.attribute("clid")) + root.set_attribute("mid", request.attribute("mid")) attempts = self.get_clear_rates(musicid, chart) - count = attempts[musicid][chart]['total'] - clear = attempts[musicid][chart]['clears'] - full_combo = attempts[musicid][chart]['fcs'] + count = attempts[musicid][chart]["total"] + clear = attempts[musicid][chart]["clears"] + full_combo = attempts[musicid][chart]["fcs"] if count > 0: - root.set_attribute('crate', str(int((1000 * clear) / count))) - root.set_attribute('frate', str(int((1000 * full_combo) / count))) + root.set_attribute("crate", str(int((1000 * clear) / count))) + root.set_attribute("frate", str(int((1000 * full_combo) / count))) else: - root.set_attribute('crate', '0') - root.set_attribute('frate', '0') + root.set_attribute("crate", "0") + root.set_attribute("frate", "0") return root def handle_IIDX24grade_raised_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) - cltype = int(request.attribute('gtype')) - rank = self.game_to_db_rank(int(request.attribute('gid')), cltype) + extid = int(request.attribute("iidxid")) + cltype = int(request.attribute("gtype")) + rank = self.game_to_db_rank(int(request.attribute("gid")), cltype) userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: - percent = int(request.attribute('achi')) - stages_cleared = int(request.attribute('cstage')) + percent = int(request.attribute("achi")) + stages_cleared = int(request.attribute("cstage")) cleared = stages_cleared == self.DAN_STAGES if cltype == self.GAME_CLTYPE_SINGLE: @@ -916,24 +992,26 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): ) # Figure out number of players that played this ranking - all_achievements = self.data.local.user.get_all_achievements(self.game, self.version, achievementid=rank, achievementtype=index) - root = Node.void('IIDX24grade') - root.set_attribute('pnum', str(len(all_achievements))) + all_achievements = self.data.local.user.get_all_achievements( + self.game, self.version, achievementid=rank, achievementtype=index + ) + root = Node.void("IIDX24grade") + root.set_attribute("pnum", str(len(all_achievements))) return root def handle_IIDX24pc_common_request(self, request: Node) -> Node: - root = Node.void('IIDX24pc') - root.set_attribute('expire', '600') + root = Node.void("IIDX24pc") + root.set_attribute("expire", "600") - ir = Node.void('ir') + ir = Node.void("ir") root.add_child(ir) - ir.set_attribute('beat', '2') + ir.set_attribute("beat", "2") # See if we configured event overrides if self.machine_joined_arcade(): game_config = self.get_game_config() - event_phase = game_config.get_int('event_phase') - omni_events = game_config.get_bool('omnimix_events_enabled') + event_phase = game_config.get_int("event_phase") + omni_events = game_config.get_bool("omnimix_events_enabled") else: # If we aren't in an arcade, we turn off events event_phase = 0 @@ -952,43 +1030,43 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): event1 = 0 event2 = 2 - boss = Node.void('boss') + boss = Node.void("boss") root.add_child(boss) - boss.set_attribute('phase', str(boss_phase)) + boss.set_attribute("phase", str(boss_phase)) - event1_phase = Node.void('event1_phase') + event1_phase = Node.void("event1_phase") root.add_child(event1_phase) - event1_phase.set_attribute('phase', str(event1)) + event1_phase.set_attribute("phase", str(event1)) - event2_phase = Node.void('event2_phase') + event2_phase = Node.void("event2_phase") root.add_child(event2_phase) - event2_phase.set_attribute('phase', str(event2)) + event2_phase.set_attribute("phase", str(event2)) - extra_boss_event = Node.void('extra_boss_event') + extra_boss_event = Node.void("extra_boss_event") root.add_child(extra_boss_event) - extra_boss_event.set_attribute('phase', '1') + extra_boss_event.set_attribute("phase", "1") - vip_black_pass = Node.void('vip_pass_black') + vip_black_pass = Node.void("vip_pass_black") root.add_child(vip_black_pass) - newsong_another = Node.void('newsong_another') + newsong_another = Node.void("newsong_another") root.add_child(newsong_another) - newsong_another.set_attribute('open', '1') + newsong_another.set_attribute("open", "1") - deller_bonus = Node.void('deller_bonus') + deller_bonus = Node.void("deller_bonus") root.add_child(deller_bonus) - deller_bonus.set_attribute('open', '1') + deller_bonus.set_attribute("open", "1") - common_evnet = Node.void('common_evnet') # Yes, this is misspelled in the game + common_evnet = Node.void("common_evnet") # Yes, this is misspelled in the game root.add_child(common_evnet) - common_evnet.set_attribute('flg', '0') + common_evnet.set_attribute("flg", "0") # Course definitions courses: List[Dict[str, Any]] = [ { - 'name': 'NINJA', - 'id': 1, - 'songs': [ + "name": "NINJA", + "id": 1, + "songs": [ 24068, 24011, 24031, @@ -996,9 +1074,9 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): ], }, { - 'name': '24A12', - 'id': 2, - 'songs': [ + "name": "24A12", + "id": 2, + "songs": [ 24024, 24023, 24005, @@ -1006,9 +1084,9 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): ], }, { - 'name': '80\'S', - 'id': 3, - 'songs': [ + "name": "80'S", + "id": 3, + "songs": [ 20033, 15029, 24056, @@ -1016,9 +1094,9 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): ], }, { - 'name': 'DJ TECHNORCH', - 'id': 4, - 'songs': [ + "name": "DJ TECHNORCH", + "id": 4, + "songs": [ 21029, 22035, 22049, @@ -1026,9 +1104,9 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): ], }, { - 'name': 'COLORS', - 'id': 5, - 'songs': [ + "name": "COLORS", + "id": 5, + "songs": [ 11032, 15022, 15004, @@ -1036,9 +1114,9 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): ], }, { - 'name': 'OHANA', - 'id': 6, - 'songs': [ + "name": "OHANA", + "id": 6, + "songs": [ 16050, 13000, 22087, @@ -1046,9 +1124,9 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): ], }, { - 'name': 'DPER', - 'id': 7, - 'songs': [ + "name": "DPER", + "id": 7, + "songs": [ 18004, 19063, 20047, @@ -1056,9 +1134,9 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): ], }, { - 'name': 'DA', - 'id': 8, - 'songs': [ + "name": "DA", + "id": 8, + "songs": [ 23058, 17021, 18025, @@ -1066,9 +1144,9 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): ], }, { - 'name': 'SOF-LAN', - 'id': 9, - 'songs': [ + "name": "SOF-LAN", + "id": 9, + "songs": [ 23079, 15005, 7002, @@ -1076,9 +1154,9 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): ], }, { - 'name': 'TEMPEST', - 'id': 10, - 'songs': [ + "name": "TEMPEST", + "id": 10, + "songs": [ 19008, 20038, 16020, @@ -1086,9 +1164,9 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): ], }, { - 'name': 'STAR LIGHT', - 'id': 11, - 'songs': [ + "name": "STAR LIGHT", + "id": 11, + "songs": [ 23082, 24027, 20066, @@ -1096,9 +1174,9 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): ], }, { - 'name': 'SCRATCH', - 'id': 12, - 'songs': [ + "name": "SCRATCH", + "id": 12, + "songs": [ 11025, 16053, 16031, @@ -1106,9 +1184,9 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): ], }, { - 'name': 'L.E.D.-G', - 'id': 13, - 'songs': [ + "name": "L.E.D.-G", + "id": 13, + "songs": [ 15007, 24000, 22011, @@ -1116,9 +1194,9 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): ], }, { - 'name': 'QQQ', - 'id': 14, - 'songs': [ + "name": "QQQ", + "id": 14, + "songs": [ 18062, 18019, 12011, @@ -1126,9 +1204,9 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): ], }, { - 'name': 'BMK 2017', - 'id': 15, - 'songs': [ + "name": "BMK 2017", + "id": 15, + "songs": [ 24084, 24017, 24022, @@ -1140,9 +1218,9 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): # Secret course definitions secret_courses: List[Dict[str, Any]] = [ { - 'name': 'L.E.D.-K', - 'id': 1, - 'songs': [ + "name": "L.E.D.-K", + "id": 1, + "songs": [ 13034, 21068, 17060, @@ -1150,9 +1228,9 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): ], }, { - 'name': 'SOTA K', - 'id': 2, - 'songs': [ + "name": "SOTA K", + "id": 2, + "songs": [ 16010, 14038, 20016, @@ -1160,9 +1238,9 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): ], }, { - 'name': 'POP', - 'id': 3, - 'songs': [ + "name": "POP", + "id": 3, + "songs": [ 22042, 14056, 15003, @@ -1170,9 +1248,9 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): ], }, { - 'name': 'REMO-CON', - 'id': 4, - 'songs': [ + "name": "REMO-CON", + "id": 4, + "songs": [ 15030, 12031, 22078, @@ -1180,9 +1258,9 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): ], }, { - 'name': 'NUMBER', - 'id': 5, - 'songs': [ + "name": "NUMBER", + "id": 5, + "songs": [ 1003, 17051, 17041, @@ -1190,9 +1268,9 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): ], }, { - 'name': 'FANTASY', - 'id': 6, - 'songs': [ + "name": "FANTASY", + "id": 6, + "songs": [ 20102, 24013, 23092, @@ -1200,9 +1278,9 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): ], }, { - 'name': 'DRUM\'N\'BASS', - 'id': 7, - 'songs': [ + "name": "DRUM'N'BASS", + "id": 7, + "songs": [ 6013, 22016, 20073, @@ -1213,98 +1291,100 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): # For some reason, omnimix crashes on course mode, so don't enable it if not self.omnimix: - internet_ranking = Node.void('internet_ranking') + internet_ranking = Node.void("internet_ranking") root.add_child(internet_ranking) used_ids: List[int] = [] for c in courses: - if c['id'] in used_ids: - raise Exception('Cannot have multiple courses with the same ID!') - elif c['id'] < 0 or c['id'] >= 20: - raise Exception('Course ID is out of bounds!') + if c["id"] in used_ids: + raise Exception("Cannot have multiple courses with the same ID!") + elif c["id"] < 0 or c["id"] >= 20: + raise Exception("Course ID is out of bounds!") else: - used_ids.append(c['id']) + used_ids.append(c["id"]) - course = Node.void('course') + course = Node.void("course") internet_ranking.add_child(course) - course.set_attribute('course_id', str(c['id'])) - course.set_attribute('name', c['name']) - course.set_attribute('mid0', str(c['songs'][0])) - course.set_attribute('mid1', str(c['songs'][1])) - course.set_attribute('mid2', str(c['songs'][2])) - course.set_attribute('mid3', str(c['songs'][3])) - course.set_attribute('opflg', '1') + course.set_attribute("course_id", str(c["id"])) + course.set_attribute("name", c["name"]) + course.set_attribute("mid0", str(c["songs"][0])) + course.set_attribute("mid1", str(c["songs"][1])) + course.set_attribute("mid2", str(c["songs"][2])) + course.set_attribute("mid3", str(c["songs"][3])) + course.set_attribute("opflg", "1") - secret_ex_course = Node.void('secret_ex_course') + secret_ex_course = Node.void("secret_ex_course") root.add_child(secret_ex_course) used_secret_ids: List[int] = [] for c in secret_courses: - if c['id'] in used_secret_ids: - raise Exception('Cannot have multiple secret courses with the same ID!') - elif c['id'] < 0 or c['id'] >= 20: - raise Exception('Secret course ID is out of bounds!') + if c["id"] in used_secret_ids: + raise Exception( + "Cannot have multiple secret courses with the same ID!" + ) + elif c["id"] < 0 or c["id"] >= 20: + raise Exception("Secret course ID is out of bounds!") else: - used_secret_ids.append(c['id']) + used_secret_ids.append(c["id"]) - course = Node.void('course') + course = Node.void("course") secret_ex_course.add_child(course) - course.set_attribute('course_id', str(c['id'])) - course.set_attribute('name', c['name']) - course.set_attribute('mid0', str(c['songs'][0])) - course.set_attribute('mid1', str(c['songs'][1])) - course.set_attribute('mid2', str(c['songs'][2])) - course.set_attribute('mid3', str(c['songs'][3])) + course.set_attribute("course_id", str(c["id"])) + course.set_attribute("name", c["name"]) + course.set_attribute("mid0", str(c["songs"][0])) + course.set_attribute("mid1", str(c["songs"][1])) + course.set_attribute("mid2", str(c["songs"][2])) + course.set_attribute("mid3", str(c["songs"][3])) - expert = Node.void('expert') + expert = Node.void("expert") root.add_child(expert) - expert.set_attribute('phase', '1') + expert.set_attribute("phase", "1") - expert_random_select = Node.void('expert_random_select') + expert_random_select = Node.void("expert_random_select") root.add_child(expert_random_select) - expert_random_select.set_attribute('phase', '1') + expert_random_select.set_attribute("phase", "1") - expert_full = Node.void('expert_secret_full_open') + expert_full = Node.void("expert_secret_full_open") root.add_child(expert_full) return root def handle_IIDX24pc_delete_request(self, request: Node) -> Node: - return Node.void('IIDX24pc') + return Node.void("IIDX24pc") def handle_IIDX24pc_playstart_request(self, request: Node) -> Node: - return Node.void('IIDX24pc') + return Node.void("IIDX24pc") def handle_IIDX24pc_playend_request(self, request: Node) -> Node: - return Node.void('IIDX24pc') + return Node.void("IIDX24pc") def handle_IIDX24pc_visit_request(self, request: Node) -> Node: - root = Node.void('IIDX24pc') - root.set_attribute('anum', '0') - root.set_attribute('snum', '0') - root.set_attribute('pnum', '0') - root.set_attribute('aflg', '0') - root.set_attribute('sflg', '0') - root.set_attribute('pflg', '0') + root = Node.void("IIDX24pc") + root.set_attribute("anum", "0") + root.set_attribute("snum", "0") + root.set_attribute("pnum", "0") + root.set_attribute("aflg", "0") + root.set_attribute("sflg", "0") + root.set_attribute("pflg", "0") return root def handle_IIDX24pc_shopregister_request(self, request: Node) -> Node: - extid = int(request.child_value('iidx_id')) - location = ID.parse_machine_id(request.child_value('location_id')) + extid = int(request.child_value("iidx_id")) + location = ID.parse_machine_id(request.child_value("location_id")) userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: profile = self.get_profile(userid) if profile is None: profile = Profile(self.game, self.version, "", extid) - profile.replace_int('shop_location', location) + profile.replace_int("shop_location", location) self.put_profile(userid, profile) - root = Node.void('IIDX24pc') + root = Node.void("IIDX24pc") return root def handle_IIDX24pc_oldget_request(self, request: Node) -> Node: - refid = request.attribute('rid') + refid = request.attribute("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: oldversion = self.previous_version() @@ -1312,12 +1392,12 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): else: profile = None - root = Node.void('IIDX24pc') - root.set_attribute('status', '1' if profile is None else '0') + root = Node.void("IIDX24pc") + root.set_attribute("status", "1" if profile is None else "0") return root def handle_IIDX24pc_getname_request(self, request: Node) -> Node: - refid = request.attribute('rid') + refid = request.attribute("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: oldversion = self.previous_version() @@ -1326,111 +1406,113 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): profile = None if profile is None: raise Exception( - 'Should not get here if we have no profile, we should ' + - 'have returned \'1\' in the \'oldget\' method above ' + - 'which should tell the game not to present a migration.' + "Should not get here if we have no profile, we should " + + "have returned '1' in the 'oldget' method above " + + "which should tell the game not to present a migration." ) - root = Node.void('IIDX24pc') - root.set_attribute('name', profile.get_str('name')) - root.set_attribute('idstr', ID.format_extid(profile.extid)) - root.set_attribute('pid', str(profile.get_int('pid'))) + root = Node.void("IIDX24pc") + root.set_attribute("name", profile.get_str("name")) + root.set_attribute("idstr", ID.format_extid(profile.extid)) + root.set_attribute("pid", str(profile.get_int("pid"))) return root def handle_IIDX24pc_takeover_request(self, request: Node) -> Node: - refid = request.attribute('rid') - name = request.attribute('name') - pid = int(request.attribute('pid')) + refid = request.attribute("rid") + name = request.attribute("name") + pid = int(request.attribute("pid")) newprofile = self.new_profile_by_refid(refid, name, pid) - root = Node.void('IIDX24pc') + root = Node.void("IIDX24pc") if newprofile is not None: - root.set_attribute('id', str(newprofile.extid)) + root.set_attribute("id", str(newprofile.extid)) return root def handle_IIDX24pc_reg_request(self, request: Node) -> Node: - refid = request.attribute('rid') - name = request.attribute('name') - pid = int(request.attribute('pid')) + refid = request.attribute("rid") + name = request.attribute("name") + pid = int(request.attribute("pid")) profile = self.new_profile_by_refid(refid, name, pid) - root = Node.void('IIDX24pc') + root = Node.void("IIDX24pc") if profile is not None: - root.set_attribute('id', str(profile.extid)) - root.set_attribute('id_str', ID.format_extid(profile.extid)) + root.set_attribute("id", str(profile.extid)) + root.set_attribute("id_str", ID.format_extid(profile.extid)) return root def handle_IIDX24pc_get_request(self, request: Node) -> Node: - refid = request.attribute('rid') + refid = request.attribute("rid") root = self.get_profile_by_refid(refid) if root is None: - root = Node.void('IIDX24pc') + root = Node.void("IIDX24pc") return root def handle_IIDX24pc_save_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) + extid = int(request.attribute("iidxid")) self.put_profile_by_extid(extid, request) - return Node.void('IIDX24pc') + return Node.void("IIDX24pc") def handle_IIDX24pc_eaappliresult_request(self, request: Node) -> Node: clear_map = { - self.GAME_CLEAR_STATUS_NO_PLAY: 'NO PLAY', - self.GAME_CLEAR_STATUS_FAILED: 'FAILED', - self.GAME_CLEAR_STATUS_ASSIST_CLEAR: 'ASSIST CLEAR', - self.GAME_CLEAR_STATUS_EASY_CLEAR: 'EASY CLEAR', - self.GAME_CLEAR_STATUS_CLEAR: 'CLEAR', - self.GAME_CLEAR_STATUS_HARD_CLEAR: 'HARD CLEAR', - self.GAME_CLEAR_STATUS_EX_HARD_CLEAR: 'EX HARD CLEAR', - self.GAME_CLEAR_STATUS_FULL_COMBO: 'FULL COMBO', + self.GAME_CLEAR_STATUS_NO_PLAY: "NO PLAY", + self.GAME_CLEAR_STATUS_FAILED: "FAILED", + self.GAME_CLEAR_STATUS_ASSIST_CLEAR: "ASSIST CLEAR", + self.GAME_CLEAR_STATUS_EASY_CLEAR: "EASY CLEAR", + self.GAME_CLEAR_STATUS_CLEAR: "CLEAR", + self.GAME_CLEAR_STATUS_HARD_CLEAR: "HARD CLEAR", + self.GAME_CLEAR_STATUS_EX_HARD_CLEAR: "EX HARD CLEAR", + self.GAME_CLEAR_STATUS_FULL_COMBO: "FULL COMBO", } # first we'll grab the data from the packet # did = request.child_value('did') # rid = request.child_value('rid') - name = request.child_value('name') + name = request.child_value("name") # qpro_hair = request.child_value('qpro_hair') # qpro_head = request.child_value('qpro_head') # qpro_body = request.child_value('qpro_body') # qpro_hand = request.child_value('qpro_hand') - music_id = request.child_value('music_id') - class_id = request.child_value('class_id') + music_id = request.child_value("music_id") + class_id = request.child_value("class_id") # no_save = request.child_value('no_save') # is_couple = request.child_value('is_couple') # target_graph = request.child_value('target_graph') - target_exscore = request.child_value('target_exscore') + target_exscore = request.child_value("target_exscore") # pacemaker = request.child_value('pacemaker') - best_clear = request.child_value('best_clear') + best_clear = request.child_value("best_clear") # best_djlevel = request.child_value('best_djlevel') # best_exscore = request.child_value('best_exscore') # best_misscount = request.child_value('best_misscount') - now_clear = request.child_value('now_clear') + now_clear = request.child_value("now_clear") # now_djlevel = request.child_value('now_djlevel') - now_exscore = request.child_value('now_exscore') + now_exscore = request.child_value("now_exscore") # now_misscount = request.child_value('now_misscount') - now_pgreat = request.child_value('now_pgreat') - now_great = request.child_value('now_great') - now_good = request.child_value('now_good') - now_bad = request.child_value('now_bad') - now_poor = request.child_value('now_poor') - now_combo = request.child_value('now_combo') - now_fast = request.child_value('now_fast') - now_slow = request.child_value('now_slow') - best_clear_string = clear_map.get(best_clear, 'NO PLAY') - now_clear_string = clear_map.get(now_clear, 'NO PLAY') + now_pgreat = request.child_value("now_pgreat") + now_great = request.child_value("now_great") + now_good = request.child_value("now_good") + now_bad = request.child_value("now_bad") + now_poor = request.child_value("now_poor") + now_combo = request.child_value("now_combo") + now_fast = request.child_value("now_fast") + now_slow = request.child_value("now_slow") + best_clear_string = clear_map.get(best_clear, "NO PLAY") + now_clear_string = clear_map.get(now_clear, "NO PLAY") # let's get the song info first - song = self.data.local.music.get_song(self.game, self.music_version, music_id, self.game_to_db_chart(class_id)) - notecount = song.data.get('notecount', 0) + song = self.data.local.music.get_song( + self.game, self.music_version, music_id, self.game_to_db_chart(class_id) + ) + notecount = song.data.get("notecount", 0) # Construct the dictionary for the broadcast card_data = { BroadcastConstants.DJ_NAME: name, BroadcastConstants.SONG_NAME: song.name, BroadcastConstants.ARTIST_NAME: song.artist, - BroadcastConstants.DIFFICULTY: song.data.get('difficulty', 0), + BroadcastConstants.DIFFICULTY: song.data.get("difficulty", 0), BroadcastConstants.TARGET_EXSCORE: target_exscore, BroadcastConstants.EXSCORE: now_exscore, BroadcastConstants.BEST_CLEAR_STATUS: best_clear_string, BroadcastConstants.CLEAR_STATUS: now_clear_string, - BroadcastConstants.PLAY_STATS_HEADER: 'How did you do?', + BroadcastConstants.PLAY_STATS_HEADER: "How did you do?", BroadcastConstants.PERFECT_GREATS: now_pgreat, BroadcastConstants.GREATS: now_great, BroadcastConstants.GOODS: now_good, @@ -1444,214 +1526,254 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): max_score = notecount * 2 percent = now_exscore / max_score grade = int(9 * percent) - grades = ['F', 'F', 'E', 'D', 'C', 'B', 'A', 'AA', 'AAA', 'MAX'] + grades = ["F", "F", "E", "D", "C", "B", "A", "AA", "AAA", "MAX"] card_data[BroadcastConstants.GRADE] = grades[grade] card_data[BroadcastConstants.RATE] = str(round(percent, 2)) # Try to broadcast out the score to our webhook(s) self.data.triggers.broadcast_score(card_data, self.game, song) - return Node.void('IIDX24pc') + return Node.void("IIDX24pc") def format_profile(self, userid: UserID, profile: Profile) -> Node: - root = Node.void('IIDX24pc') + root = Node.void("IIDX24pc") # Look up play stats we bridge to every mix play_stats = self.get_play_statistics(userid) # Look up judge window adjustments - judge_dict = profile.get_dict('machine_judge_adjust') + judge_dict = profile.get_dict("machine_judge_adjust") machine_judge = judge_dict.get_dict(self.config.machine.pcbid) # Profile data - pcdata = Node.void('pcdata') + pcdata = Node.void("pcdata") root.add_child(pcdata) - pcdata.set_attribute('id', str(profile.extid)) - pcdata.set_attribute('idstr', ID.format_extid(profile.extid)) - pcdata.set_attribute('name', profile.get_str('name')) - pcdata.set_attribute('pid', str(profile.get_int('pid'))) - pcdata.set_attribute('spnum', str(play_stats.get_int('single_plays'))) - pcdata.set_attribute('dpnum', str(play_stats.get_int('double_plays'))) - pcdata.set_attribute('sach', str(play_stats.get_int('single_dj_points'))) - pcdata.set_attribute('dach', str(play_stats.get_int('double_dj_points'))) - pcdata.set_attribute('mode', str(profile.get_int('mode'))) - pcdata.set_attribute('pmode', str(profile.get_int('pmode'))) - pcdata.set_attribute('rtype', str(profile.get_int('rtype'))) - pcdata.set_attribute('sp_opt', str(profile.get_int('sp_opt'))) - pcdata.set_attribute('dp_opt', str(profile.get_int('dp_opt'))) - pcdata.set_attribute('dp_opt2', str(profile.get_int('dp_opt2'))) - pcdata.set_attribute('gpos', str(profile.get_int('gpos'))) - pcdata.set_attribute('s_sorttype', str(profile.get_int('s_sorttype'))) - pcdata.set_attribute('d_sorttype', str(profile.get_int('d_sorttype'))) - pcdata.set_attribute('s_pace', str(profile.get_int('s_pace'))) - pcdata.set_attribute('d_pace', str(profile.get_int('d_pace'))) - pcdata.set_attribute('s_gno', str(profile.get_int('s_gno'))) - pcdata.set_attribute('d_gno', str(profile.get_int('d_gno'))) - pcdata.set_attribute('s_gtype', str(profile.get_int('s_gtype'))) - pcdata.set_attribute('d_gtype', str(profile.get_int('d_gtype'))) - pcdata.set_attribute('s_sdlen', str(profile.get_int('s_sdlen'))) - pcdata.set_attribute('d_sdlen', str(profile.get_int('d_sdlen'))) - pcdata.set_attribute('s_sdtype', str(profile.get_int('s_sdtype'))) - pcdata.set_attribute('d_sdtype', str(profile.get_int('d_sdtype'))) - pcdata.set_attribute('s_timing', str(profile.get_int('s_timing'))) - pcdata.set_attribute('d_timing', str(profile.get_int('d_timing'))) - pcdata.set_attribute('s_notes', str(profile.get_float('s_notes'))) - pcdata.set_attribute('d_notes', str(profile.get_float('d_notes'))) - pcdata.set_attribute('s_judge', str(profile.get_int('s_judge'))) - pcdata.set_attribute('d_judge', str(profile.get_int('d_judge'))) - pcdata.set_attribute('s_judgeAdj', str(machine_judge.get_int('single'))) - pcdata.set_attribute('d_judgeAdj', str(machine_judge.get_int('double'))) - pcdata.set_attribute('s_hispeed', str(profile.get_float('s_hispeed'))) - pcdata.set_attribute('d_hispeed', str(profile.get_float('d_hispeed'))) - pcdata.set_attribute('s_liflen', str(profile.get_int('s_lift'))) - pcdata.set_attribute('d_liflen', str(profile.get_int('d_lift'))) - pcdata.set_attribute('s_disp_judge', str(profile.get_int('s_disp_judge'))) - pcdata.set_attribute('d_disp_judge', str(profile.get_int('d_disp_judge'))) - pcdata.set_attribute('s_opstyle', str(profile.get_int('s_opstyle'))) - pcdata.set_attribute('d_opstyle', str(profile.get_int('d_opstyle'))) - pcdata.set_attribute('s_exscore', str(profile.get_int('s_exscore'))) - pcdata.set_attribute('d_exscore', str(profile.get_int('d_exscore'))) - pcdata.set_attribute('s_graph_score', str(profile.get_int('s_graph_score'))) - pcdata.set_attribute('d_graph_score', str(profile.get_int('d_graph_score'))) + pcdata.set_attribute("id", str(profile.extid)) + pcdata.set_attribute("idstr", ID.format_extid(profile.extid)) + pcdata.set_attribute("name", profile.get_str("name")) + pcdata.set_attribute("pid", str(profile.get_int("pid"))) + pcdata.set_attribute("spnum", str(play_stats.get_int("single_plays"))) + pcdata.set_attribute("dpnum", str(play_stats.get_int("double_plays"))) + pcdata.set_attribute("sach", str(play_stats.get_int("single_dj_points"))) + pcdata.set_attribute("dach", str(play_stats.get_int("double_dj_points"))) + pcdata.set_attribute("mode", str(profile.get_int("mode"))) + pcdata.set_attribute("pmode", str(profile.get_int("pmode"))) + pcdata.set_attribute("rtype", str(profile.get_int("rtype"))) + pcdata.set_attribute("sp_opt", str(profile.get_int("sp_opt"))) + pcdata.set_attribute("dp_opt", str(profile.get_int("dp_opt"))) + pcdata.set_attribute("dp_opt2", str(profile.get_int("dp_opt2"))) + pcdata.set_attribute("gpos", str(profile.get_int("gpos"))) + pcdata.set_attribute("s_sorttype", str(profile.get_int("s_sorttype"))) + pcdata.set_attribute("d_sorttype", str(profile.get_int("d_sorttype"))) + pcdata.set_attribute("s_pace", str(profile.get_int("s_pace"))) + pcdata.set_attribute("d_pace", str(profile.get_int("d_pace"))) + pcdata.set_attribute("s_gno", str(profile.get_int("s_gno"))) + pcdata.set_attribute("d_gno", str(profile.get_int("d_gno"))) + pcdata.set_attribute("s_gtype", str(profile.get_int("s_gtype"))) + pcdata.set_attribute("d_gtype", str(profile.get_int("d_gtype"))) + pcdata.set_attribute("s_sdlen", str(profile.get_int("s_sdlen"))) + pcdata.set_attribute("d_sdlen", str(profile.get_int("d_sdlen"))) + pcdata.set_attribute("s_sdtype", str(profile.get_int("s_sdtype"))) + pcdata.set_attribute("d_sdtype", str(profile.get_int("d_sdtype"))) + pcdata.set_attribute("s_timing", str(profile.get_int("s_timing"))) + pcdata.set_attribute("d_timing", str(profile.get_int("d_timing"))) + pcdata.set_attribute("s_notes", str(profile.get_float("s_notes"))) + pcdata.set_attribute("d_notes", str(profile.get_float("d_notes"))) + pcdata.set_attribute("s_judge", str(profile.get_int("s_judge"))) + pcdata.set_attribute("d_judge", str(profile.get_int("d_judge"))) + pcdata.set_attribute("s_judgeAdj", str(machine_judge.get_int("single"))) + pcdata.set_attribute("d_judgeAdj", str(machine_judge.get_int("double"))) + pcdata.set_attribute("s_hispeed", str(profile.get_float("s_hispeed"))) + pcdata.set_attribute("d_hispeed", str(profile.get_float("d_hispeed"))) + pcdata.set_attribute("s_liflen", str(profile.get_int("s_lift"))) + pcdata.set_attribute("d_liflen", str(profile.get_int("d_lift"))) + pcdata.set_attribute("s_disp_judge", str(profile.get_int("s_disp_judge"))) + pcdata.set_attribute("d_disp_judge", str(profile.get_int("d_disp_judge"))) + pcdata.set_attribute("s_opstyle", str(profile.get_int("s_opstyle"))) + pcdata.set_attribute("d_opstyle", str(profile.get_int("d_opstyle"))) + pcdata.set_attribute("s_exscore", str(profile.get_int("s_exscore"))) + pcdata.set_attribute("d_exscore", str(profile.get_int("d_exscore"))) + pcdata.set_attribute("s_graph_score", str(profile.get_int("s_graph_score"))) + pcdata.set_attribute("d_graph_score", str(profile.get_int("d_graph_score"))) - spdp_rival = Node.void('spdp_rival') + spdp_rival = Node.void("spdp_rival") root.add_child(spdp_rival) - spdp_rival.set_attribute('flg', str(profile.get_int('spdp_rival_flag'))) + spdp_rival.set_attribute("flg", str(profile.get_int("spdp_rival_flag"))) - premium_unlocks = Node.void('ea_premium_course') + premium_unlocks = Node.void("ea_premium_course") root.add_child(premium_unlocks) - legendarias = Node.void('leggendaria_open') + legendarias = Node.void("leggendaria_open") root.add_child(legendarias) # Song unlock flags - secret_dict = profile.get_dict('secret') - secret = Node.void('secret') + secret_dict = profile.get_dict("secret") + secret = Node.void("secret") root.add_child(secret) - secret.add_child(Node.s64_array('flg1', secret_dict.get_int_array('flg1', 3))) - secret.add_child(Node.s64_array('flg2', secret_dict.get_int_array('flg2', 3))) - secret.add_child(Node.s64_array('flg3', secret_dict.get_int_array('flg3', 3))) + secret.add_child(Node.s64_array("flg1", secret_dict.get_int_array("flg1", 3))) + secret.add_child(Node.s64_array("flg2", secret_dict.get_int_array("flg2", 3))) + secret.add_child(Node.s64_array("flg3", secret_dict.get_int_array("flg3", 3))) # Favorites - for folder in ['favorite1', 'favorite2', 'favorite3']: + for folder in ["favorite1", "favorite2", "favorite3"]: favorite_dict = profile.get_dict(folder) - sp_mlist = b'' - sp_clist = b'' - singles_list = favorite_dict['single'] if 'single' in favorite_dict else [] + sp_mlist = b"" + sp_clist = b"" + singles_list = favorite_dict["single"] if "single" in favorite_dict else [] for single in singles_list: - sp_mlist = sp_mlist + struct.pack(' Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() play_stats = self.get_play_statistics(userid) # Track play counts - cltype = int(request.attribute('cltype')) + cltype = int(request.attribute("cltype")) if cltype == self.GAME_CLTYPE_SINGLE: - play_stats.increment_int('single_plays') + play_stats.increment_int("single_plays") if cltype == self.GAME_CLTYPE_DOUBLE: - play_stats.increment_int('double_plays') + play_stats.increment_int("double_plays") # Track DJ points - play_stats.replace_int('single_dj_points', int(request.attribute('s_achi'))) - play_stats.replace_int('double_dj_points', int(request.attribute('d_achi'))) + play_stats.replace_int("single_dj_points", int(request.attribute("s_achi"))) + play_stats.replace_int("double_dj_points", int(request.attribute("d_achi"))) # Profile settings - newprofile.replace_int('mode', int(request.attribute('mode'))) - newprofile.replace_int('pmode', int(request.attribute('pmode'))) - newprofile.replace_int('rtype', int(request.attribute('rtype'))) - newprofile.replace_int('s_lift', int(request.attribute('s_lift'))) - newprofile.replace_int('d_lift', int(request.attribute('d_lift'))) - newprofile.replace_int('sp_opt', int(request.attribute('sp_opt'))) - newprofile.replace_int('dp_opt', int(request.attribute('dp_opt'))) - newprofile.replace_int('dp_opt2', int(request.attribute('dp_opt2'))) - newprofile.replace_int('gpos', int(request.attribute('gpos'))) - newprofile.replace_int('s_sorttype', int(request.attribute('s_sorttype'))) - newprofile.replace_int('d_sorttype', int(request.attribute('d_sorttype'))) - newprofile.replace_int('s_pace', int(request.attribute('s_pace'))) - newprofile.replace_int('d_pace', int(request.attribute('d_pace'))) - newprofile.replace_int('s_gno', int(request.attribute('s_gno'))) - newprofile.replace_int('d_gno', int(request.attribute('d_gno'))) - newprofile.replace_int('s_gtype', int(request.attribute('s_gtype'))) - newprofile.replace_int('d_gtype', int(request.attribute('d_gtype'))) - newprofile.replace_int('s_sdlen', int(request.attribute('s_sdlen'))) - newprofile.replace_int('d_sdlen', int(request.attribute('d_sdlen'))) - newprofile.replace_int('s_sdtype', int(request.attribute('s_sdtype'))) - newprofile.replace_int('d_sdtype', int(request.attribute('d_sdtype'))) - newprofile.replace_int('s_timing', int(request.attribute('s_timing'))) - newprofile.replace_int('d_timing', int(request.attribute('d_timing'))) - newprofile.replace_float('s_notes', float(request.attribute('s_notes'))) - newprofile.replace_float('d_notes', float(request.attribute('d_notes'))) - newprofile.replace_int('s_judge', int(request.attribute('s_judge'))) - newprofile.replace_int('d_judge', int(request.attribute('d_judge'))) - newprofile.replace_float('s_hispeed', float(request.attribute('s_hispeed'))) - newprofile.replace_float('d_hispeed', float(request.attribute('d_hispeed'))) - newprofile.replace_int('s_disp_judge', int(request.attribute('s_disp_judge'))) - newprofile.replace_int('d_disp_judge', int(request.attribute('d_disp_judge'))) - newprofile.replace_int('s_opstyle', int(request.attribute('s_opstyle'))) - newprofile.replace_int('d_opstyle', int(request.attribute('d_opstyle'))) - newprofile.replace_int('s_exscore', int(request.attribute('s_exscore'))) - newprofile.replace_int('d_exscore', int(request.attribute('d_exscore'))) - newprofile.replace_int('s_graph_score', int(request.attribute('s_graph_score'))) - newprofile.replace_int('d_graph_score', int(request.attribute('d_graph_score'))) + newprofile.replace_int("mode", int(request.attribute("mode"))) + newprofile.replace_int("pmode", int(request.attribute("pmode"))) + newprofile.replace_int("rtype", int(request.attribute("rtype"))) + newprofile.replace_int("s_lift", int(request.attribute("s_lift"))) + newprofile.replace_int("d_lift", int(request.attribute("d_lift"))) + newprofile.replace_int("sp_opt", int(request.attribute("sp_opt"))) + newprofile.replace_int("dp_opt", int(request.attribute("dp_opt"))) + newprofile.replace_int("dp_opt2", int(request.attribute("dp_opt2"))) + newprofile.replace_int("gpos", int(request.attribute("gpos"))) + newprofile.replace_int("s_sorttype", int(request.attribute("s_sorttype"))) + newprofile.replace_int("d_sorttype", int(request.attribute("d_sorttype"))) + newprofile.replace_int("s_pace", int(request.attribute("s_pace"))) + newprofile.replace_int("d_pace", int(request.attribute("d_pace"))) + newprofile.replace_int("s_gno", int(request.attribute("s_gno"))) + newprofile.replace_int("d_gno", int(request.attribute("d_gno"))) + newprofile.replace_int("s_gtype", int(request.attribute("s_gtype"))) + newprofile.replace_int("d_gtype", int(request.attribute("d_gtype"))) + newprofile.replace_int("s_sdlen", int(request.attribute("s_sdlen"))) + newprofile.replace_int("d_sdlen", int(request.attribute("d_sdlen"))) + newprofile.replace_int("s_sdtype", int(request.attribute("s_sdtype"))) + newprofile.replace_int("d_sdtype", int(request.attribute("d_sdtype"))) + newprofile.replace_int("s_timing", int(request.attribute("s_timing"))) + newprofile.replace_int("d_timing", int(request.attribute("d_timing"))) + newprofile.replace_float("s_notes", float(request.attribute("s_notes"))) + newprofile.replace_float("d_notes", float(request.attribute("d_notes"))) + newprofile.replace_int("s_judge", int(request.attribute("s_judge"))) + newprofile.replace_int("d_judge", int(request.attribute("d_judge"))) + newprofile.replace_float("s_hispeed", float(request.attribute("s_hispeed"))) + newprofile.replace_float("d_hispeed", float(request.attribute("d_hispeed"))) + newprofile.replace_int("s_disp_judge", int(request.attribute("s_disp_judge"))) + newprofile.replace_int("d_disp_judge", int(request.attribute("d_disp_judge"))) + newprofile.replace_int("s_opstyle", int(request.attribute("s_opstyle"))) + newprofile.replace_int("d_opstyle", int(request.attribute("d_opstyle"))) + newprofile.replace_int("s_exscore", int(request.attribute("s_exscore"))) + newprofile.replace_int("d_exscore", int(request.attribute("d_exscore"))) + newprofile.replace_int("s_graph_score", int(request.attribute("s_graph_score"))) + newprofile.replace_int("d_graph_score", int(request.attribute("d_graph_score"))) # Update judge window adjustments per-machine - judge_dict = newprofile.get_dict('machine_judge_adjust') + judge_dict = newprofile.get_dict("machine_judge_adjust") machine_judge = judge_dict.get_dict(self.config.machine.pcbid) - machine_judge.replace_int('single', int(request.attribute('s_judgeAdj'))) - machine_judge.replace_int('double', int(request.attribute('d_judgeAdj'))) + machine_judge.replace_int("single", int(request.attribute("s_judgeAdj"))) + machine_judge.replace_int("double", int(request.attribute("d_judgeAdj"))) judge_dict.replace_dict(self.config.machine.pcbid, machine_judge) - newprofile.replace_dict('machine_judge_adjust', judge_dict) + newprofile.replace_dict("machine_judge_adjust", judge_dict) # Secret flags saving - secret = request.child('secret') + secret = request.child("secret") if secret is not None: - secret_dict = newprofile.get_dict('secret') - secret_dict.replace_int_array('flg1', 3, secret.child_value('flg1')) - secret_dict.replace_int_array('flg2', 3, secret.child_value('flg2')) - secret_dict.replace_int_array('flg3', 3, secret.child_value('flg3')) - newprofile.replace_dict('secret', secret_dict) + secret_dict = newprofile.get_dict("secret") + secret_dict.replace_int_array("flg1", 3, secret.child_value("flg1")) + secret_dict.replace_int_array("flg2", 3, secret.child_value("flg2")) + secret_dict.replace_int_array("flg3", 3, secret.child_value("flg3")) + newprofile.replace_dict("secret", secret_dict) # Basic achievements - achievements = request.child('achievements') + achievements = request.child("achievements") if achievements is not None: - newprofile.replace_int('visit_flg', int(achievements.attribute('visit_flg'))) - newprofile.replace_int('last_weekly', int(achievements.attribute('last_weekly'))) - newprofile.replace_int('weekly_num', int(achievements.attribute('weekly_num'))) + newprofile.replace_int( + "visit_flg", int(achievements.attribute("visit_flg")) + ) + newprofile.replace_int( + "last_weekly", int(achievements.attribute("last_weekly")) + ) + newprofile.replace_int( + "weekly_num", int(achievements.attribute("weekly_num")) + ) - pack_id = int(achievements.attribute('pack_id')) + pack_id = int(achievements.attribute("pack_id")) if pack_id > 0: self.data.local.user.put_achievement( self.game, self.version, userid, pack_id, - 'daily', + "daily", { - 'pack_flg': int(achievements.attribute('pack_flg')), - 'pack_comp': int(achievements.attribute('pack_comp')), + "pack_flg": int(achievements.attribute("pack_flg")), + "pack_comp": int(achievements.attribute("pack_comp")), }, ) - trophies = achievements.child('trophy') + trophies = achievements.child("trophy") if trophies is not None: # We only load the first 10 in profile load. - newprofile.replace_int_array('trophy', 10, trophies.value[:10]) + newprofile.replace_int_array("trophy", 10, trophies.value[:10]) # Deller saving - deller = request.child('deller') + deller = request.child("deller") if deller is not None: - newprofile.replace_int('deller', newprofile.get_int('deller') + int(deller.attribute('deller'))) + newprofile.replace_int( + "deller", newprofile.get_int("deller") + int(deller.attribute("deller")) + ) # Secret course expert point saving - expert_point = request.child('expert_point') + expert_point = request.child("expert_point") if expert_point is not None: - courseid = int(expert_point.attribute('course_id')) + courseid = int(expert_point.attribute("course_id")) # Update achievement to track expert points expert_point_achievement = self.data.local.user.get_achievement( @@ -2006,21 +2233,21 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): self.version, userid, courseid, - 'expert_point', + "expert_point", ) if expert_point_achievement is None: expert_point_achievement = ValidatedDict() expert_point_achievement.replace_int( - 'normal_points', - int(expert_point.attribute('n_point')), + "normal_points", + int(expert_point.attribute("n_point")), ) expert_point_achievement.replace_int( - 'hyper_points', - int(expert_point.attribute('h_point')), + "hyper_points", + int(expert_point.attribute("h_point")), ) expert_point_achievement.replace_int( - 'another_points', - int(expert_point.attribute('a_point')), + "another_points", + int(expert_point.attribute("a_point")), ) self.data.local.user.put_achievement( @@ -2028,7 +2255,7 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): self.version, userid, courseid, - 'expert_point', + "expert_point", expert_point_achievement, ) @@ -2037,187 +2264,225 @@ class IIDXSinobuz(IIDXCourse, IIDXBase): singles = [] doubles = [] name = None - if favorite.name in ['favorite', 'extra_favorite']: - if favorite.name == 'favorite': - name = 'favorite1' - elif favorite.name == 'extra_favorite': - folder = favorite.attribute('folder_id') - if folder == '0': - name = 'favorite2' - if folder == '1': - name = 'favorite3' + if favorite.name in ["favorite", "extra_favorite"]: + if favorite.name == "favorite": + name = "favorite1" + elif favorite.name == "extra_favorite": + folder = favorite.attribute("folder_id") + if folder == "0": + name = "favorite2" + if folder == "1": + name = "favorite3" if name is None: continue - single_music_bin = favorite.child_value('sp_mlist') - single_chart_bin = favorite.child_value('sp_clist') - double_music_bin = favorite.child_value('dp_mlist') - double_chart_bin = favorite.child_value('dp_clist') + single_music_bin = favorite.child_value("sp_mlist") + single_chart_bin = favorite.child_value("sp_clist") + double_music_bin = favorite.child_value("dp_mlist") + double_chart_bin = favorite.child_value("dp_clist") for i in range(self.FAVORITE_LIST_LENGTH): - singles.append({ - 'id': struct.unpack(' List[Tuple[str, Dict[str, Any]]]: + def run_scheduled_work( + cls, data: Data, config: Dict[str, Any] + ) -> List[Tuple[str, Dict[str, Any]]]: """ Insert dailies into the DB. """ events = [] - if data.local.network.should_schedule(cls.game, cls.version, 'daily_charts', 'daily'): + if data.local.network.should_schedule( + cls.game, cls.version, "daily_charts", "daily" + ): # Generate a new list of three dailies. - start_time, end_time = data.local.network.get_schedule_duration('daily') - all_songs = list(set([song.id for song in data.local.music.get_all_songs(cls.game, cls.version)])) + start_time, end_time = data.local.network.get_schedule_duration("daily") + all_songs = list( + set( + [ + song.id + for song in data.local.music.get_all_songs( + cls.game, cls.version + ) + ] + ) + ) if len(all_songs) >= 3: daily_songs = random.sample(all_songs, 3) data.local.game.put_time_sensitive_settings( cls.game, cls.version, - 'dailies', + "dailies", { - 'start_time': start_time, - 'end_time': end_time, - 'music': daily_songs, + "start_time": start_time, + "end_time": end_time, + "music": daily_songs, }, ) - events.append(( - 'iidx_daily_charts', - { - 'version': cls.version, - 'music': daily_songs, - }, - )) + events.append( + ( + "iidx_daily_charts", + { + "version": cls.version, + "music": daily_songs, + }, + ) + ) # Mark that we did some actual work here. - data.local.network.mark_scheduled(cls.game, cls.version, 'daily_charts', 'daily') + data.local.network.mark_scheduled( + cls.game, cls.version, "daily_charts", "daily" + ) return events @classmethod @@ -133,18 +157,18 @@ class IIDXSpada(IIDXBase): Return all of our front-end modifiably settings. """ return { - 'bools': [ + "bools": [ { - 'name': 'Global Shop Ranking', - 'tip': 'Return network-wide ranking instead of shop ranking on results screen.', - 'category': 'game_config', - 'setting': 'global_shop_ranking', + "name": "Global Shop Ranking", + "tip": "Return network-wide ranking instead of shop ranking on results screen.", + "category": "game_config", + "setting": "global_shop_ranking", }, { - 'name': 'Events In Omnimix', - 'tip': 'Allow events to be enabled at all for Omnimix.', - 'category': 'game_config', - 'setting': 'omnimix_events_enabled', + "name": "Events In Omnimix", + "tip": "Allow events to be enabled at all for Omnimix.", + "category": "game_config", + "setting": "omnimix_events_enabled", }, ], } @@ -221,7 +245,7 @@ class IIDXSpada(IIDXBase): self.DAN_RANK_KAIDEN: self.GAME_DP_DAN_RANK_KAIDEN, }[db_dan] else: - raise Exception('Invalid cltype!') + raise Exception("Invalid cltype!") def game_to_db_rank(self, game_dan: int, cltype: int) -> int: # Special case for no DAN rank @@ -269,7 +293,7 @@ class IIDXSpada(IIDXBase): self.GAME_DP_DAN_RANK_KAIDEN: self.DAN_RANK_KAIDEN, }[game_dan] else: - raise Exception('Invalid cltype!') + raise Exception("Invalid cltype!") def game_to_db_chart(self, db_chart: int) -> int: return { @@ -283,57 +307,61 @@ class IIDXSpada(IIDXBase): }[db_chart] def handle_IIDX21shop_getname_request(self, request: Node) -> Node: - root = Node.void('IIDX21shop') - root.set_attribute('cls_opt', '0') + root = Node.void("IIDX21shop") + root.set_attribute("cls_opt", "0") machine = self.data.local.machine.get_machine(self.config.machine.pcbid) - root.set_attribute('opname', machine.name) - root.set_attribute('pid', str(self.get_machine_region())) + root.set_attribute("opname", machine.name) + root.set_attribute("pid", str(self.get_machine_region())) return root def handle_IIDX21shop_savename_request(self, request: Node) -> Node: - self.update_machine_name(request.attribute('opname')) - root = Node.void('IIDX21shop') + self.update_machine_name(request.attribute("opname")) + root = Node.void("IIDX21shop") return root def handle_IIDX21shop_sentinfo_request(self, request: Node) -> Node: - root = Node.void('IIDX21shop') + root = Node.void("IIDX21shop") return root def handle_IIDX21shop_getconvention_request(self, request: Node) -> Node: - root = Node.void('IIDX21shop') + root = Node.void("IIDX21shop") machine = self.data.local.machine.get_machine(self.config.machine.pcbid) if machine.arcade is not None: - course = self.data.local.machine.get_settings(machine.arcade, self.game, self.music_version, 'shop_course') + course = self.data.local.machine.get_settings( + machine.arcade, self.game, self.music_version, "shop_course" + ) else: course = None if course is None: course = ValidatedDict() - root.set_attribute('music_0', str(course.get_int('music_0', 20032))) - root.set_attribute('music_1', str(course.get_int('music_1', 20009))) - root.set_attribute('music_2', str(course.get_int('music_2', 20015))) - root.set_attribute('music_3', str(course.get_int('music_3', 20064))) - root.add_child(Node.bool('valid', course.get_bool('valid'))) + root.set_attribute("music_0", str(course.get_int("music_0", 20032))) + root.set_attribute("music_1", str(course.get_int("music_1", 20009))) + root.set_attribute("music_2", str(course.get_int("music_2", 20015))) + root.set_attribute("music_3", str(course.get_int("music_3", 20064))) + root.add_child(Node.bool("valid", course.get_bool("valid"))) return root def handle_IIDX21shop_setconvention_request(self, request: Node) -> Node: - root = Node.void('IIDX21shop') + root = Node.void("IIDX21shop") machine = self.data.local.machine.get_machine(self.config.machine.pcbid) if machine.arcade is not None: course = ValidatedDict() - course.replace_int('music_0', request.child_value('music_0')) - course.replace_int('music_1', request.child_value('music_1')) - course.replace_int('music_2', request.child_value('music_2')) - course.replace_int('music_3', request.child_value('music_3')) - course.replace_bool('valid', request.child_value('valid')) - self.data.local.machine.put_settings(machine.arcade, self.game, self.music_version, 'shop_course', course) + course.replace_int("music_0", request.child_value("music_0")) + course.replace_int("music_1", request.child_value("music_1")) + course.replace_int("music_2", request.child_value("music_2")) + course.replace_int("music_3", request.child_value("music_3")) + course.replace_bool("valid", request.child_value("valid")) + self.data.local.machine.put_settings( + machine.arcade, self.game, self.music_version, "shop_course", course + ) return root def handle_IIDX21ranking_getranker_request(self, request: Node) -> Node: - root = Node.void('IIDX21ranking') - chart = self.game_to_db_chart(int(request.attribute('clid'))) + root = Node.void("IIDX21ranking") + chart = self.game_to_db_chart(int(request.attribute("clid"))) if chart not in [ self.CHART_TYPE_N7, self.CHART_TYPE_H7, @@ -347,29 +375,31 @@ class IIDXSpada(IIDXBase): machine = self.data.local.machine.get_machine(self.config.machine.pcbid) if machine.arcade is not None: - course = self.data.local.machine.get_settings(machine.arcade, self.game, self.music_version, 'shop_course') + course = self.data.local.machine.get_settings( + machine.arcade, self.game, self.music_version, "shop_course" + ) else: course = None if course is None: course = ValidatedDict() - if not course.get_bool('valid'): + if not course.get_bool("valid"): # Shop course not enabled or not present return root - convention = Node.void('convention') + convention = Node.void("convention") root.add_child(convention) - convention.set_attribute('clid', str(chart)) - convention.set_attribute('update_date', str(Time.now() * 1000)) + convention.set_attribute("clid", str(chart)) + convention.set_attribute("update_date", str(Time.now() * 1000)) # Grab all scores for each of the four songs, filter out people who haven't # set us as their arcade and then return the top 20 scores (adding all 4 songs). songids = [ - course.get_int('music_0'), - course.get_int('music_1'), - course.get_int('music_2'), - course.get_int('music_3'), + course.get_int("music_0"), + course.get_int("music_1"), + course.get_int("music_2"), + course.get_int("music_3"), ] totalscores: Dict[UserID, int] = {} @@ -406,27 +436,36 @@ class IIDXSpada(IIDXBase): for topscore in topscores: rank = rank + 1 - detail = Node.void('detail') + detail = Node.void("detail") convention.add_child(detail) - detail.set_attribute('name', topscore[1].get_str('name')) - detail.set_attribute('rank', str(rank)) - detail.set_attribute('score', str(topscore[0])) - detail.set_attribute('pid', str(topscore[1].get_int('pid'))) + detail.set_attribute("name", topscore[1].get_str("name")) + detail.set_attribute("rank", str(rank)) + detail.set_attribute("score", str(topscore[0])) + detail.set_attribute("pid", str(topscore[1].get_int("pid"))) - qpro = topscore[1].get_dict('qpro') - detail.set_attribute('head', str(qpro.get_int('head'))) - detail.set_attribute('hair', str(qpro.get_int('hair'))) - detail.set_attribute('face', str(qpro.get_int('face'))) - detail.set_attribute('body', str(qpro.get_int('body'))) - detail.set_attribute('hand', str(qpro.get_int('hand'))) + qpro = topscore[1].get_dict("qpro") + detail.set_attribute("head", str(qpro.get_int("head"))) + detail.set_attribute("hair", str(qpro.get_int("hair"))) + detail.set_attribute("face", str(qpro.get_int("face"))) + detail.set_attribute("body", str(qpro.get_int("body"))) + detail.set_attribute("hand", str(qpro.get_int("hand"))) return root def handle_IIDX21music_crate_request(self, request: Node) -> Node: - root = Node.void('IIDX21music') + root = Node.void("IIDX21music") attempts = self.get_clear_rates() - all_songs = list(set([song.id for song in self.data.local.music.get_all_songs(self.game, self.music_version)])) + all_songs = list( + set( + [ + song.id + for song in self.data.local.music.get_all_songs( + self.game, self.music_version + ) + ] + ) + ) for song in all_songs: clears = [] fcs = [] @@ -435,33 +474,33 @@ class IIDXSpada(IIDXBase): placed = False if song in attempts and chart in attempts[song]: values = attempts[song][chart] - if values['total'] > 0: - clears.append(int((100 * values['clears']) / values['total'])) - fcs.append(int((100 * values['fcs']) / values['total'])) + if values["total"] > 0: + clears.append(int((100 * values["clears"]) / values["total"])) + fcs.append(int((100 * values["fcs"]) / values["total"])) placed = True if not placed: clears.append(101) fcs.append(101) - clearnode = Node.u8_array('c', clears + fcs) - clearnode.set_attribute('mid', str(song)) + clearnode = Node.u8_array("c", clears + fcs) + clearnode.set_attribute("mid", str(song)) root.add_child(clearnode) return root def handle_IIDX21music_getrank_request(self, request: Node) -> Node: - cltype = int(request.attribute('cltype')) + cltype = int(request.attribute("cltype")) - root = Node.void('IIDX21music') - style = Node.void('style') + root = Node.void("IIDX21music") + style = Node.void("style") root.add_child(style) - style.set_attribute('type', str(cltype)) + style.set_attribute("type", str(cltype)) for rivalid in [-1, 0, 1, 2, 3, 4]: if rivalid == -1: - attr = 'iidxid' + attr = "iidxid" else: - attr = f'iidxid{rivalid}' + attr = f"iidxid{rivalid}" try: extid = int(request.attribute(attr)) @@ -470,46 +509,52 @@ class IIDXSpada(IIDXBase): continue userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: - scores = self.data.remote.music.get_scores(self.game, self.music_version, userid) + scores = self.data.remote.music.get_scores( + self.game, self.music_version, userid + ) # Grab score data for user/rival scoredata = self.make_score_struct( scores, - self.CLEAR_TYPE_SINGLE if cltype == self.GAME_CLTYPE_SINGLE else self.CLEAR_TYPE_DOUBLE, + self.CLEAR_TYPE_SINGLE + if cltype == self.GAME_CLTYPE_SINGLE + else self.CLEAR_TYPE_DOUBLE, rivalid, ) for s in scoredata: - root.add_child(Node.s16_array('m', s)) + root.add_child(Node.s16_array("m", s)) # Grab most played for user/rival most_played = [ - play[0] for play in - self.data.local.music.get_most_played(self.game, self.music_version, userid, 20) + play[0] + for play in self.data.local.music.get_most_played( + self.game, self.music_version, userid, 20 + ) ] if len(most_played) < 20: most_played.extend([0] * (20 - len(most_played))) - best = Node.u16_array('best', most_played) - best.set_attribute('rno', str(rivalid)) + best = Node.u16_array("best", most_played) + best.set_attribute("rno", str(rivalid)) root.add_child(best) if rivalid == -1: # Grab beginner statuses for user only beginnerdata = self.make_beginner_struct(scores) for b in beginnerdata: - root.add_child(Node.u16_array('b', b)) + root.add_child(Node.u16_array("b", b)) return root def handle_IIDX21music_reg_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) - musicid = int(request.attribute('mid')) - chart = self.game_to_db_chart(int(request.attribute('clid'))) + extid = int(request.attribute("iidxid")) + musicid = int(request.attribute("mid")) + chart = self.game_to_db_chart(int(request.attribute("clid"))) userid = self.data.remote.user.from_extid(self.game, self.version, extid) # See if we need to report global or shop scores if self.machine_joined_arcade(): game_config = self.get_game_config() - global_scores = game_config.get_bool('global_shop_ranking') + global_scores = game_config.get_bool("global_shop_ranking") machine = self.data.local.machine.get_machine(self.config.machine.pcbid) else: # If we aren't in an arcade, we can only show global scores @@ -518,21 +563,27 @@ class IIDXSpada(IIDXBase): # First, determine our current ranking before saving the new score all_scores = sorted( - self.data.remote.music.get_all_scores(game=self.game, version=self.music_version, songid=musicid, songchart=chart), + self.data.remote.music.get_all_scores( + game=self.game, + version=self.music_version, + songid=musicid, + songchart=chart, + ), key=lambda s: (s[1].points, s[1].timestamp), reverse=True, ) all_players = { - uid: prof for (uid, prof) in - self.get_any_profiles([s[0] for s in all_scores]) + uid: prof + for (uid, prof) in self.get_any_profiles([s[0] for s in all_scores]) } if not global_scores: all_scores = [ - score for score in all_scores + score + for score in all_scores if ( - score[0] == userid or - self.user_joined_arcade(machine, all_players[score[0]]) + score[0] == userid + or self.user_joined_arcade(machine, all_players[score[0]]) ) ] @@ -544,12 +595,12 @@ class IIDXSpada(IIDXBase): break if userid is not None: - clear_status = self.game_to_db_status(int(request.attribute('cflg'))) - pgreats = int(request.attribute('pgnum')) - greats = int(request.attribute('gnum')) - miss_count = int(request.attribute('mnum')) - ghost = request.child_value('ghost') - shopid = ID.parse_machine_id(request.attribute('shopconvid')) + clear_status = self.game_to_db_status(int(request.attribute("cflg"))) + pgreats = int(request.attribute("pgnum")) + greats = int(request.attribute("gnum")) + miss_count = int(request.attribute("mnum")) + ghost = request.child_value("ghost") + shopid = ID.parse_machine_id(request.attribute("shopconvid")) self.update_score( userid, @@ -564,51 +615,56 @@ class IIDXSpada(IIDXBase): ) # Calculate and return statistics about this song - root = Node.void('IIDX21music') - root.set_attribute('clid', request.attribute('clid')) - root.set_attribute('mid', request.attribute('mid')) + root = Node.void("IIDX21music") + root.set_attribute("clid", request.attribute("clid")) + root.set_attribute("mid", request.attribute("mid")) attempts = self.get_clear_rates(musicid, chart) - count = attempts[musicid][chart]['total'] - clear = attempts[musicid][chart]['clears'] - full_combo = attempts[musicid][chart]['fcs'] + count = attempts[musicid][chart]["total"] + clear = attempts[musicid][chart]["clears"] + full_combo = attempts[musicid][chart]["fcs"] if count > 0: - root.set_attribute('crate', str(int((100 * clear) / count))) - root.set_attribute('frate', str(int((100 * full_combo) / count))) + root.set_attribute("crate", str(int((100 * clear) / count))) + root.set_attribute("frate", str(int((100 * full_combo) / count))) else: - root.set_attribute('crate', '0') - root.set_attribute('frate', '0') - root.set_attribute('rankside', '0') + root.set_attribute("crate", "0") + root.set_attribute("frate", "0") + root.set_attribute("rankside", "0") if userid is not None: # Shop ranking - shopdata = Node.void('shopdata') + shopdata = Node.void("shopdata") root.add_child(shopdata) - shopdata.set_attribute('rank', '-1' if oldindex is None else str(oldindex + 1)) + shopdata.set_attribute( + "rank", "-1" if oldindex is None else str(oldindex + 1) + ) # Grab the rank of some other players on this song - ranklist = Node.void('ranklist') + ranklist = Node.void("ranklist") root.add_child(ranklist) all_scores = sorted( - self.data.remote.music.get_all_scores(game=self.game, version=self.music_version, songid=musicid, songchart=chart), + self.data.remote.music.get_all_scores( + game=self.game, + version=self.music_version, + songid=musicid, + songchart=chart, + ), key=lambda s: (s[1].points, s[1].timestamp), reverse=True, ) - missing_players = [ - uid for (uid, _) in all_scores - if uid not in all_players - ] + missing_players = [uid for (uid, _) in all_scores if uid not in all_players] for (uid, prof) in self.get_any_profiles(missing_players): all_players[uid] = prof if not global_scores: all_scores = [ - score for score in all_scores + score + for score in all_scores if ( - score[0] == userid or - self.user_joined_arcade(machine, all_players[score[0]]) + score[0] == userid + or self.user_joined_arcade(machine, all_players[score[0]]) ) ] @@ -619,65 +675,80 @@ class IIDXSpada(IIDXBase): ourindex = i break if ourindex is None: - raise Exception('Cannot find our own score after saving to DB!') + raise Exception("Cannot find our own score after saving to DB!") start = ourindex - 4 end = ourindex + 4 if start < 0: start = 0 if end >= len(all_scores): end = len(all_scores) - 1 - relevant_scores = all_scores[start:(end + 1)] + relevant_scores = all_scores[start : (end + 1)] record_num = start + 1 for score in relevant_scores: profile = all_players[score[0]] - data = Node.void('data') + data = Node.void("data") ranklist.add_child(data) - data.set_attribute('iidx_id', str(profile.extid)) - data.set_attribute('name', profile.get_str('name')) + data.set_attribute("iidx_id", str(profile.extid)) + data.set_attribute("name", profile.get_str("name")) - machine_name = '' - if 'shop_location' in profile: - shop_id = profile.get_int('shop_location') + machine_name = "" + if "shop_location" in profile: + shop_id = profile.get_int("shop_location") machine = self.get_machine_by_id(shop_id) if machine is not None: machine_name = machine.name - data.set_attribute('opname', machine_name) - data.set_attribute('rnum', str(record_num)) - data.set_attribute('score', str(score[1].points)) - data.set_attribute('clflg', str(self.db_to_game_status(score[1].data.get_int('clear_status')))) - data.set_attribute('pid', str(profile.get_int('pid'))) - data.set_attribute('myFlg', '1' if score[0] == userid else '0') - data.set_attribute('update', '0') + data.set_attribute("opname", machine_name) + data.set_attribute("rnum", str(record_num)) + data.set_attribute("score", str(score[1].points)) + data.set_attribute( + "clflg", + str(self.db_to_game_status(score[1].data.get_int("clear_status"))), + ) + data.set_attribute("pid", str(profile.get_int("pid"))) + data.set_attribute("myFlg", "1" if score[0] == userid else "0") + data.set_attribute("update", "0") - data.set_attribute('sgrade', str( - self.db_to_game_rank(profile.get_int(self.DAN_RANKING_SINGLE, -1), self.GAME_CLTYPE_SINGLE), - )) - data.set_attribute('dgrade', str( - self.db_to_game_rank(profile.get_int(self.DAN_RANKING_DOUBLE, -1), self.GAME_CLTYPE_DOUBLE), - )) + data.set_attribute( + "sgrade", + str( + self.db_to_game_rank( + profile.get_int(self.DAN_RANKING_SINGLE, -1), + self.GAME_CLTYPE_SINGLE, + ), + ), + ) + data.set_attribute( + "dgrade", + str( + self.db_to_game_rank( + profile.get_int(self.DAN_RANKING_DOUBLE, -1), + self.GAME_CLTYPE_DOUBLE, + ), + ), + ) - qpro = profile.get_dict('qpro') - data.set_attribute('head', str(qpro.get_int('head'))) - data.set_attribute('hair', str(qpro.get_int('hair'))) - data.set_attribute('face', str(qpro.get_int('face'))) - data.set_attribute('body', str(qpro.get_int('body'))) - data.set_attribute('hand', str(qpro.get_int('hand'))) + qpro = profile.get_dict("qpro") + data.set_attribute("head", str(qpro.get_int("head"))) + data.set_attribute("hair", str(qpro.get_int("hair"))) + data.set_attribute("face", str(qpro.get_int("face"))) + data.set_attribute("body", str(qpro.get_int("body"))) + data.set_attribute("hand", str(qpro.get_int("hand"))) record_num = record_num + 1 return root def handle_IIDX21music_breg_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) - musicid = int(request.attribute('mid')) + extid = int(request.attribute("iidxid")) + musicid = int(request.attribute("mid")) userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: - clear_status = self.game_to_db_status(int(request.attribute('cflg'))) - pgreats = int(request.attribute('pgnum')) - greats = int(request.attribute('gnum')) + clear_status = self.game_to_db_status(int(request.attribute("cflg"))) + pgreats = int(request.attribute("pgnum")) + greats = int(request.attribute("gnum")) self.update_score( userid, @@ -687,18 +758,18 @@ class IIDXSpada(IIDXBase): pgreats, greats, -1, - b'', + b"", None, ) # Return nothing. - root = Node.void('IIDX21music') + root = Node.void("IIDX21music") return root def handle_IIDX21music_play_request(self, request: Node) -> Node: - musicid = int(request.attribute('mid')) - chart = self.game_to_db_chart(int(request.attribute('clid'))) - clear_status = self.game_to_db_status(int(request.attribute('cflg'))) + musicid = int(request.attribute("mid")) + chart = self.game_to_db_chart(int(request.attribute("clid"))) + clear_status = self.game_to_db_status(int(request.attribute("cflg"))) self.update_score( None, # No userid since its anonymous @@ -713,39 +784,41 @@ class IIDXSpada(IIDXBase): ) # Calculate and return statistics about this song - root = Node.void('IIDX21music') - root.set_attribute('clid', request.attribute('clid')) - root.set_attribute('mid', request.attribute('mid')) + root = Node.void("IIDX21music") + root.set_attribute("clid", request.attribute("clid")) + root.set_attribute("mid", request.attribute("mid")) attempts = self.get_clear_rates(musicid, chart) - count = attempts[musicid][chart]['total'] - clear = attempts[musicid][chart]['clears'] - full_combo = attempts[musicid][chart]['fcs'] + count = attempts[musicid][chart]["total"] + clear = attempts[musicid][chart]["clears"] + full_combo = attempts[musicid][chart]["fcs"] if count > 0: - root.set_attribute('crate', str(int((100 * clear) / count))) - root.set_attribute('frate', str(int((100 * full_combo) / count))) + root.set_attribute("crate", str(int((100 * clear) / count))) + root.set_attribute("frate", str(int((100 * full_combo) / count))) else: - root.set_attribute('crate', '0') - root.set_attribute('frate', '0') + root.set_attribute("crate", "0") + root.set_attribute("frate", "0") return root def handle_IIDX21music_appoint_request(self, request: Node) -> Node: - musicid = int(request.attribute('mid')) - chart = self.game_to_db_chart(int(request.attribute('clid'))) - ghost_type = int(request.attribute('ctype')) - extid = int(request.attribute('iidxid')) + musicid = int(request.attribute("mid")) + chart = self.game_to_db_chart(int(request.attribute("clid"))) + ghost_type = int(request.attribute("ctype")) + extid = int(request.attribute("iidxid")) userid = self.data.remote.user.from_extid(self.game, self.version, extid) - root = Node.void('IIDX21music') + root = Node.void("IIDX21music") if userid is not None: # Try to look up previous ghost for user - my_score = self.data.remote.music.get_score(self.game, self.music_version, userid, musicid, chart) + my_score = self.data.remote.music.get_score( + self.game, self.music_version, userid, musicid, chart + ) if my_score is not None: - mydata = Node.binary('mydata', my_score.data.get_bytes('ghost')) - mydata.set_attribute('score', str(my_score.points)) + mydata = Node.binary("mydata", my_score.data.get_bytes("ghost")) + mydata.set_attribute("score", str(my_score.points)) root.add_child(mydata) ghost_score = self.get_ghost( @@ -760,7 +833,7 @@ class IIDXSpada(IIDXBase): self.GAME_GHOST_TYPE_RIVAL_TOP: self.GHOST_TYPE_RIVAL_TOP, self.GAME_GHOST_TYPE_RIVAL_AVERAGE: self.GHOST_TYPE_RIVAL_AVERAGE, }.get(ghost_type, self.GHOST_TYPE_NONE), - request.attribute('subtype'), + request.attribute("subtype"), self.GAME_GHOST_LENGTH, musicid, chart, @@ -769,35 +842,35 @@ class IIDXSpada(IIDXBase): # Add ghost score if we support it if ghost_score is not None: - sdata = Node.binary('sdata', ghost_score['ghost']) - sdata.set_attribute('score', str(ghost_score['score'])) - if 'name' in ghost_score: - sdata.set_attribute('name', ghost_score['name']) - if 'pid' in ghost_score: - sdata.set_attribute('pid', str(ghost_score['pid'])) - if 'extid' in ghost_score: - sdata.set_attribute('riidxid', str(ghost_score['extid'])) + sdata = Node.binary("sdata", ghost_score["ghost"]) + sdata.set_attribute("score", str(ghost_score["score"])) + if "name" in ghost_score: + sdata.set_attribute("name", ghost_score["name"]) + if "pid" in ghost_score: + sdata.set_attribute("pid", str(ghost_score["pid"])) + if "extid" in ghost_score: + sdata.set_attribute("riidxid", str(ghost_score["extid"])) root.add_child(sdata) return root def handle_IIDX21pc_common_request(self, request: Node) -> Node: - root = Node.void('IIDX21pc') - root.set_attribute('expire', '600') + root = Node.void("IIDX21pc") + root.set_attribute("expire", "600") # TODO: Hook all of these up to config options I guess? - ir = Node.void('ir') + ir = Node.void("ir") root.add_child(ir) - ir.set_attribute('beat', '2') + ir.set_attribute("beat", "2") - limit = Node.void('limit') + limit = Node.void("limit") root.add_child(limit) - limit.set_attribute('phase', '24') + limit.set_attribute("phase", "24") # See if we configured event overrides if self.machine_joined_arcade(): game_config = self.get_game_config() - omni_events = game_config.get_bool('omnimix_events_enabled') + omni_events = game_config.get_bool("omnimix_events_enabled") else: # If we aren't in an arcade, we turn off events omni_events = False @@ -808,54 +881,54 @@ class IIDXSpada(IIDXBase): # TODO: Figure out what these map to boss_phase = 0 - boss = Node.void('boss') + boss = Node.void("boss") root.add_child(boss) - boss.set_attribute('phase', str(boss_phase)) + boss.set_attribute("phase", str(boss_phase)) - boss1 = Node.void('boss1') + boss1 = Node.void("boss1") root.add_child(boss1) - boss1.set_attribute('phase', '1') + boss1.set_attribute("phase", "1") - medal = Node.void('medal') + medal = Node.void("medal") root.add_child(medal) - medal.set_attribute('phase', '1') + medal.set_attribute("phase", "1") - vip_black_pass = Node.void('vip_pass_black') + vip_black_pass = Node.void("vip_pass_black") root.add_child(vip_black_pass) - cafe = Node.void('cafe') + cafe = Node.void("cafe") root.add_child(cafe) - cafe.set_attribute('open', '1') + cafe.set_attribute("open", "1") - tricolettepark = Node.void('tricolettepark') + tricolettepark = Node.void("tricolettepark") root.add_child(tricolettepark) - tricolettepark.set_attribute('open', '0') + tricolettepark.set_attribute("open", "0") - tricolettepark_skip = Node.void('tricolettepark_skip') + tricolettepark_skip = Node.void("tricolettepark_skip") root.add_child(tricolettepark_skip) - tricolettepark_skip.set_attribute('phase', '1') + tricolettepark_skip.set_attribute("phase", "1") - newsong_another = Node.void('newsong_another') + newsong_another = Node.void("newsong_another") root.add_child(newsong_another) - newsong_another.set_attribute('open', '1') + newsong_another.set_attribute("open", "1") - superstar = Node.void('superstar') + superstar = Node.void("superstar") root.add_child(superstar) - superstar.set_attribute('phase', '1') + superstar.set_attribute("phase", "1") return root def handle_IIDX21pc_delete_request(self, request: Node) -> Node: - return Node.void('IIDX21pc') + return Node.void("IIDX21pc") def handle_IIDX21pc_playstart_request(self, request: Node) -> Node: - return Node.void('IIDX21pc') + return Node.void("IIDX21pc") def handle_IIDX21pc_playend_request(self, request: Node) -> Node: - return Node.void('IIDX21pc') + return Node.void("IIDX21pc") def handle_IIDX21pc_oldget_request(self, request: Node) -> Node: - refid = request.attribute('rid') + refid = request.attribute("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: oldversion = self.previous_version() @@ -863,12 +936,12 @@ class IIDXSpada(IIDXBase): else: profile = None - root = Node.void('IIDX21pc') - root.set_attribute('status', '1' if profile is None else '0') + root = Node.void("IIDX21pc") + root.set_attribute("status", "1" if profile is None else "0") return root def handle_IIDX21pc_getname_request(self, request: Node) -> Node: - refid = request.attribute('rid') + refid = request.attribute("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: oldversion = self.previous_version() @@ -877,88 +950,88 @@ class IIDXSpada(IIDXBase): profile = None if profile is None: raise Exception( - 'Should not get here if we have no profile, we should ' + - 'have returned \'1\' in the \'oldget\' method above ' + - 'which should tell the game not to present a migration.' + "Should not get here if we have no profile, we should " + + "have returned '1' in the 'oldget' method above " + + "which should tell the game not to present a migration." ) - root = Node.void('IIDX21pc') - root.set_attribute('name', profile.get_str('name')) - root.set_attribute('idstr', ID.format_extid(profile.extid)) - root.set_attribute('pid', str(profile.get_int('pid'))) + root = Node.void("IIDX21pc") + root.set_attribute("name", profile.get_str("name")) + root.set_attribute("idstr", ID.format_extid(profile.extid)) + root.set_attribute("pid", str(profile.get_int("pid"))) return root def handle_IIDX21pc_takeover_request(self, request: Node) -> Node: - refid = request.attribute('rid') - name = request.attribute('name') - pid = int(request.attribute('pid')) + refid = request.attribute("rid") + name = request.attribute("name") + pid = int(request.attribute("pid")) newprofile = self.new_profile_by_refid(refid, name, pid) - root = Node.void('IIDX21pc') + root = Node.void("IIDX21pc") if newprofile is not None: - root.set_attribute('id', str(newprofile.extid)) + root.set_attribute("id", str(newprofile.extid)) return root def handle_IIDX21pc_reg_request(self, request: Node) -> Node: - refid = request.attribute('rid') - name = request.attribute('name') - pid = int(request.attribute('pid')) + refid = request.attribute("rid") + name = request.attribute("name") + pid = int(request.attribute("pid")) profile = self.new_profile_by_refid(refid, name, pid) - root = Node.void('IIDX21pc') + root = Node.void("IIDX21pc") if profile is not None: - root.set_attribute('id', str(profile.extid)) - root.set_attribute('id_str', ID.format_extid(profile.extid)) + root.set_attribute("id", str(profile.extid)) + root.set_attribute("id_str", ID.format_extid(profile.extid)) return root def handle_IIDX21pc_get_request(self, request: Node) -> Node: - refid = request.attribute('rid') + refid = request.attribute("rid") root = self.get_profile_by_refid(refid) if root is None: - root = Node.void('IIDX21pc') + root = Node.void("IIDX21pc") return root def handle_IIDX21pc_save_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) + extid = int(request.attribute("iidxid")) self.put_profile_by_extid(extid, request) - root = Node.void('IIDX21pc') + root = Node.void("IIDX21pc") return root def handle_IIDX21pc_visit_request(self, request: Node) -> Node: - root = Node.void('IIDX21pc') - root.set_attribute('anum', '0') - root.set_attribute('pnum', '0') - root.set_attribute('sflg', '0') - root.set_attribute('pflg', '0') - root.set_attribute('aflg', '0') - root.set_attribute('snum', '0') + root = Node.void("IIDX21pc") + root.set_attribute("anum", "0") + root.set_attribute("pnum", "0") + root.set_attribute("sflg", "0") + root.set_attribute("pflg", "0") + root.set_attribute("aflg", "0") + root.set_attribute("snum", "0") return root def handle_IIDX21pc_shopregister_request(self, request: Node) -> Node: - extid = int(request.child_value('iidx_id')) - location = ID.parse_machine_id(request.child_value('location_id')) + extid = int(request.child_value("iidx_id")) + location = ID.parse_machine_id(request.child_value("location_id")) userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: profile = self.get_profile(userid) if profile is None: profile = Profile(self.game, self.version, "", extid) - profile.replace_int('shop_location', location) + profile.replace_int("shop_location", location) self.put_profile(userid, profile) - root = Node.void('IIDX21pc') + root = Node.void("IIDX21pc") return root def handle_IIDX21grade_raised_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) - cltype = int(request.attribute('gtype')) - rank = self.game_to_db_rank(int(request.attribute('gid')), cltype) + extid = int(request.attribute("iidxid")) + cltype = int(request.attribute("gtype")) + rank = self.game_to_db_rank(int(request.attribute("gid")), cltype) userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: - percent = int(request.attribute('achi')) - stages_cleared = int(request.attribute('cflg')) + percent = int(request.attribute("achi")) + stages_cleared = int(request.attribute("cflg")) if cltype == self.GAME_CLTYPE_SINGLE: max_stages = self.DAN_STAGES_SINGLE else: @@ -980,69 +1053,73 @@ class IIDXSpada(IIDXBase): ) # Figure out number of players that played this ranking - all_achievements = self.data.local.user.get_all_achievements(self.game, self.version, achievementid=rank, achievementtype=index) - root = Node.void('IIDX21grade') - root.set_attribute('pnum', str(len(all_achievements))) + all_achievements = self.data.local.user.get_all_achievements( + self.game, self.version, achievementid=rank, achievementtype=index + ) + root = Node.void("IIDX21grade") + root.set_attribute("pnum", str(len(all_achievements))) return root def handle_IIDX21pc_eaappliresult_request(self, request: Node) -> Node: clear_map = { - self.GAME_CLEAR_STATUS_NO_PLAY: 'NO PLAY', - self.GAME_CLEAR_STATUS_FAILED: 'FAILED', - self.GAME_CLEAR_STATUS_ASSIST_CLEAR: 'ASSIST CLEAR', - self.GAME_CLEAR_STATUS_EASY_CLEAR: 'EASY CLEAR', - self.GAME_CLEAR_STATUS_CLEAR: 'CLEAR', - self.GAME_CLEAR_STATUS_HARD_CLEAR: 'HARD CLEAR', - self.GAME_CLEAR_STATUS_EX_HARD_CLEAR: 'EX HARD CLEAR', - self.GAME_CLEAR_STATUS_FULL_COMBO: 'FULL COMBO', + self.GAME_CLEAR_STATUS_NO_PLAY: "NO PLAY", + self.GAME_CLEAR_STATUS_FAILED: "FAILED", + self.GAME_CLEAR_STATUS_ASSIST_CLEAR: "ASSIST CLEAR", + self.GAME_CLEAR_STATUS_EASY_CLEAR: "EASY CLEAR", + self.GAME_CLEAR_STATUS_CLEAR: "CLEAR", + self.GAME_CLEAR_STATUS_HARD_CLEAR: "HARD CLEAR", + self.GAME_CLEAR_STATUS_EX_HARD_CLEAR: "EX HARD CLEAR", + self.GAME_CLEAR_STATUS_FULL_COMBO: "FULL COMBO", } # first we'll grab the data from the packet # did = request.child_value('did') # rid = request.child_value('rid') - name = request.child_value('name') + name = request.child_value("name") # qpro_hair = request.child_value('qpro_hair') # qpro_head = request.child_value('qpro_head') # qpro_body = request.child_value('qpro_body') # qpro_hand = request.child_value('qpro_hand') - music_id = request.child_value('music_id') - class_id = request.child_value('class_id') + music_id = request.child_value("music_id") + class_id = request.child_value("class_id") # no_save = request.child_value('no_save') # is_couple = request.child_value('is_couple') # target_graph = request.child_value('target_graph') - target_exscore = request.child_value('target_exscore') + target_exscore = request.child_value("target_exscore") # pacemaker = request.child_value('pacemaker') - best_clear = request.child_value('best_clear') + best_clear = request.child_value("best_clear") # best_djlevel = request.child_value('best_djlevel') # best_exscore = request.child_value('best_exscore') # best_misscount = request.child_value('best_misscount') - now_clear = request.child_value('now_clear') + now_clear = request.child_value("now_clear") # now_djlevel = request.child_value('now_djlevel') - now_exscore = request.child_value('now_exscore') + now_exscore = request.child_value("now_exscore") # now_misscount = request.child_value('now_misscount') - now_pgreat = request.child_value('now_pgreat') - now_great = request.child_value('now_great') - now_good = request.child_value('now_good') - now_bad = request.child_value('now_bad') - now_poor = request.child_value('now_poor') - now_combo = request.child_value('now_combo') - now_fast = request.child_value('now_fast') - now_slow = request.child_value('now_slow') - best_clear_string = clear_map.get(best_clear, 'NO PLAY') - now_clear_string = clear_map.get(now_clear, 'NO PLAY') + now_pgreat = request.child_value("now_pgreat") + now_great = request.child_value("now_great") + now_good = request.child_value("now_good") + now_bad = request.child_value("now_bad") + now_poor = request.child_value("now_poor") + now_combo = request.child_value("now_combo") + now_fast = request.child_value("now_fast") + now_slow = request.child_value("now_slow") + best_clear_string = clear_map.get(best_clear, "NO PLAY") + now_clear_string = clear_map.get(now_clear, "NO PLAY") # let's get the song info first - song = self.data.local.music.get_song(self.game, self.music_version, music_id, self.game_to_db_chart(class_id)) - notecount = song.data.get('notecount', 0) + song = self.data.local.music.get_song( + self.game, self.music_version, music_id, self.game_to_db_chart(class_id) + ) + notecount = song.data.get("notecount", 0) # Construct the dictionary for the broadcast card_data = { BroadcastConstants.DJ_NAME: name, BroadcastConstants.SONG_NAME: song.name, BroadcastConstants.ARTIST_NAME: song.artist, - BroadcastConstants.DIFFICULTY: song.data.get('difficulty', 0), + BroadcastConstants.DIFFICULTY: song.data.get("difficulty", 0), BroadcastConstants.TARGET_EXSCORE: target_exscore, BroadcastConstants.EXSCORE: now_exscore, BroadcastConstants.BEST_CLEAR_STATUS: best_clear_string, BroadcastConstants.CLEAR_STATUS: now_clear_string, - BroadcastConstants.PLAY_STATS_HEADER: 'How did you do?', + BroadcastConstants.PLAY_STATS_HEADER: "How did you do?", BroadcastConstants.PERFECT_GREATS: now_pgreat, BroadcastConstants.GREATS: now_great, BroadcastConstants.GOODS: now_good, @@ -1056,209 +1133,247 @@ class IIDXSpada(IIDXBase): max_score = notecount * 2 percent = now_exscore / max_score grade = int(9 * percent) - grades = ['F', 'F', 'E', 'D', 'C', 'B', 'A', 'AA', 'AAA', 'MAX'] + grades = ["F", "F", "E", "D", "C", "B", "A", "AA", "AAA", "MAX"] card_data[BroadcastConstants.GRADE] = grades[grade] card_data[BroadcastConstants.RATE] = str(round(percent, 2)) # Try to broadcast out the score to our webhook(s) self.data.triggers.broadcast_score(card_data, self.game, song) - return Node.void('IIDX21pc') + return Node.void("IIDX21pc") def format_profile(self, userid: UserID, profile: Profile) -> Node: - root = Node.void('IIDX21pc') + root = Node.void("IIDX21pc") # Look up play stats we bridge to every mix play_stats = self.get_play_statistics(userid) # Look up judge window adjustments - judge_dict = profile.get_dict('machine_judge_adjust') + judge_dict = profile.get_dict("machine_judge_adjust") machine_judge = judge_dict.get_dict(self.config.machine.pcbid) # Profile data - pcdata = Node.void('pcdata') + pcdata = Node.void("pcdata") root.add_child(pcdata) - pcdata.set_attribute('id', str(profile.extid)) - pcdata.set_attribute('idstr', ID.format_extid(profile.extid)) - pcdata.set_attribute('name', profile.get_str('name')) - pcdata.set_attribute('pid', str(profile.get_int('pid'))) - pcdata.set_attribute('spnum', str(play_stats.get_int('single_plays'))) - pcdata.set_attribute('dpnum', str(play_stats.get_int('double_plays'))) - pcdata.set_attribute('sach', str(play_stats.get_int('single_dj_points'))) - pcdata.set_attribute('dach', str(play_stats.get_int('double_dj_points'))) - pcdata.set_attribute('mode', str(profile.get_int('mode'))) - pcdata.set_attribute('pmode', str(profile.get_int('pmode'))) - pcdata.set_attribute('rtype', str(profile.get_int('rtype'))) - pcdata.set_attribute('sp_opt', str(profile.get_int('sp_opt'))) - pcdata.set_attribute('dp_opt', str(profile.get_int('dp_opt'))) - pcdata.set_attribute('dp_opt2', str(profile.get_int('dp_opt2'))) - pcdata.set_attribute('gpos', str(profile.get_int('gpos'))) - pcdata.set_attribute('s_sorttype', str(profile.get_int('s_sorttype'))) - pcdata.set_attribute('d_sorttype', str(profile.get_int('d_sorttype'))) - pcdata.set_attribute('s_pace', str(profile.get_int('s_pace'))) - pcdata.set_attribute('d_pace', str(profile.get_int('d_pace'))) - pcdata.set_attribute('s_gno', str(profile.get_int('s_gno'))) - pcdata.set_attribute('d_gno', str(profile.get_int('d_gno'))) - pcdata.set_attribute('s_gtype', str(profile.get_int('s_gtype'))) - pcdata.set_attribute('d_gtype', str(profile.get_int('d_gtype'))) - pcdata.set_attribute('s_sdlen', str(profile.get_int('s_sdlen'))) - pcdata.set_attribute('d_sdlen', str(profile.get_int('d_sdlen'))) - pcdata.set_attribute('s_sdtype', str(profile.get_int('s_sdtype'))) - pcdata.set_attribute('d_sdtype', str(profile.get_int('d_sdtype'))) - pcdata.set_attribute('s_timing', str(profile.get_int('s_timing'))) - pcdata.set_attribute('d_timing', str(profile.get_int('d_timing'))) - pcdata.set_attribute('s_notes', str(profile.get_float('s_notes'))) - pcdata.set_attribute('d_notes', str(profile.get_float('d_notes'))) - pcdata.set_attribute('s_judge', str(profile.get_int('s_judge'))) - pcdata.set_attribute('d_judge', str(profile.get_int('d_judge'))) - pcdata.set_attribute('s_judgeAdj', str(machine_judge.get_int('single'))) - pcdata.set_attribute('d_judgeAdj', str(machine_judge.get_int('double'))) - pcdata.set_attribute('s_hispeed', str(profile.get_float('s_hispeed'))) - pcdata.set_attribute('d_hispeed', str(profile.get_float('d_hispeed'))) - pcdata.set_attribute('s_liflen', str(profile.get_int('s_lift'))) - pcdata.set_attribute('d_liflen', str(profile.get_int('d_lift'))) - pcdata.set_attribute('s_disp_judge', str(profile.get_int('s_disp_judge'))) - pcdata.set_attribute('d_disp_judge', str(profile.get_int('d_disp_judge'))) - pcdata.set_attribute('s_opstyle', str(profile.get_int('s_opstyle'))) - pcdata.set_attribute('d_opstyle', str(profile.get_int('d_opstyle'))) + pcdata.set_attribute("id", str(profile.extid)) + pcdata.set_attribute("idstr", ID.format_extid(profile.extid)) + pcdata.set_attribute("name", profile.get_str("name")) + pcdata.set_attribute("pid", str(profile.get_int("pid"))) + pcdata.set_attribute("spnum", str(play_stats.get_int("single_plays"))) + pcdata.set_attribute("dpnum", str(play_stats.get_int("double_plays"))) + pcdata.set_attribute("sach", str(play_stats.get_int("single_dj_points"))) + pcdata.set_attribute("dach", str(play_stats.get_int("double_dj_points"))) + pcdata.set_attribute("mode", str(profile.get_int("mode"))) + pcdata.set_attribute("pmode", str(profile.get_int("pmode"))) + pcdata.set_attribute("rtype", str(profile.get_int("rtype"))) + pcdata.set_attribute("sp_opt", str(profile.get_int("sp_opt"))) + pcdata.set_attribute("dp_opt", str(profile.get_int("dp_opt"))) + pcdata.set_attribute("dp_opt2", str(profile.get_int("dp_opt2"))) + pcdata.set_attribute("gpos", str(profile.get_int("gpos"))) + pcdata.set_attribute("s_sorttype", str(profile.get_int("s_sorttype"))) + pcdata.set_attribute("d_sorttype", str(profile.get_int("d_sorttype"))) + pcdata.set_attribute("s_pace", str(profile.get_int("s_pace"))) + pcdata.set_attribute("d_pace", str(profile.get_int("d_pace"))) + pcdata.set_attribute("s_gno", str(profile.get_int("s_gno"))) + pcdata.set_attribute("d_gno", str(profile.get_int("d_gno"))) + pcdata.set_attribute("s_gtype", str(profile.get_int("s_gtype"))) + pcdata.set_attribute("d_gtype", str(profile.get_int("d_gtype"))) + pcdata.set_attribute("s_sdlen", str(profile.get_int("s_sdlen"))) + pcdata.set_attribute("d_sdlen", str(profile.get_int("d_sdlen"))) + pcdata.set_attribute("s_sdtype", str(profile.get_int("s_sdtype"))) + pcdata.set_attribute("d_sdtype", str(profile.get_int("d_sdtype"))) + pcdata.set_attribute("s_timing", str(profile.get_int("s_timing"))) + pcdata.set_attribute("d_timing", str(profile.get_int("d_timing"))) + pcdata.set_attribute("s_notes", str(profile.get_float("s_notes"))) + pcdata.set_attribute("d_notes", str(profile.get_float("d_notes"))) + pcdata.set_attribute("s_judge", str(profile.get_int("s_judge"))) + pcdata.set_attribute("d_judge", str(profile.get_int("d_judge"))) + pcdata.set_attribute("s_judgeAdj", str(machine_judge.get_int("single"))) + pcdata.set_attribute("d_judgeAdj", str(machine_judge.get_int("double"))) + pcdata.set_attribute("s_hispeed", str(profile.get_float("s_hispeed"))) + pcdata.set_attribute("d_hispeed", str(profile.get_float("d_hispeed"))) + pcdata.set_attribute("s_liflen", str(profile.get_int("s_lift"))) + pcdata.set_attribute("d_liflen", str(profile.get_int("d_lift"))) + pcdata.set_attribute("s_disp_judge", str(profile.get_int("s_disp_judge"))) + pcdata.set_attribute("d_disp_judge", str(profile.get_int("d_disp_judge"))) + pcdata.set_attribute("s_opstyle", str(profile.get_int("s_opstyle"))) + pcdata.set_attribute("d_opstyle", str(profile.get_int("d_opstyle"))) # If the user joined a particular shop, let the game know. - if 'shop_location' in profile: - shop_id = profile.get_int('shop_location') + if "shop_location" in profile: + shop_id = profile.get_int("shop_location") machine = self.get_machine_by_id(shop_id) if machine is not None: - join_shop = Node.void('join_shop') + join_shop = Node.void("join_shop") root.add_child(join_shop) - join_shop.set_attribute('joinflg', '1') - join_shop.set_attribute('join_cflg', '1') - join_shop.set_attribute('join_id', ID.format_machine_id(machine.id)) - join_shop.set_attribute('join_name', machine.name) + join_shop.set_attribute("joinflg", "1") + join_shop.set_attribute("join_cflg", "1") + join_shop.set_attribute("join_id", ID.format_machine_id(machine.id)) + join_shop.set_attribute("join_name", machine.name) # Daily recommendations - entry = self.data.local.game.get_time_sensitive_settings(self.game, self.version, 'dailies') + entry = self.data.local.game.get_time_sensitive_settings( + self.game, self.version, "dailies" + ) if entry is not None: - packinfo = Node.void('packinfo') + packinfo = Node.void("packinfo") root.add_child(packinfo) - pack_id = int(entry['start_time'] / 86400) - packinfo.set_attribute('pack_id', str(pack_id)) - packinfo.set_attribute('music_0', str(entry['music'][0])) - packinfo.set_attribute('music_1', str(entry['music'][1])) - packinfo.set_attribute('music_2', str(entry['music'][2])) + pack_id = int(entry["start_time"] / 86400) + packinfo.set_attribute("pack_id", str(pack_id)) + packinfo.set_attribute("music_0", str(entry["music"][0])) + packinfo.set_attribute("music_1", str(entry["music"][1])) + packinfo.set_attribute("music_2", str(entry["music"][2])) else: # No dailies :( pack_id = None # Track deller - deller = Node.void('deller') + deller = Node.void("deller") root.add_child(deller) - deller.set_attribute('deller', str(profile.get_int('deller'))) - deller.set_attribute('rate', '0') + deller.set_attribute("deller", str(profile.get_int("deller"))) + deller.set_attribute("rate", "0") # Secret flags (shh!) - secret_dict = profile.get_dict('secret') - secret = Node.void('secret') + secret_dict = profile.get_dict("secret") + secret = Node.void("secret") root.add_child(secret) - secret.add_child(Node.s64_array('flg1', secret_dict.get_int_array('flg1', 2))) - secret.add_child(Node.s64_array('flg2', secret_dict.get_int_array('flg2', 2))) - secret.add_child(Node.s64_array('flg3', secret_dict.get_int_array('flg3', 2))) + secret.add_child(Node.s64_array("flg1", secret_dict.get_int_array("flg1", 2))) + secret.add_child(Node.s64_array("flg2", secret_dict.get_int_array("flg2", 2))) + secret.add_child(Node.s64_array("flg3", secret_dict.get_int_array("flg3", 2))) # Tran medals and shit - achievements = Node.void('achievements') + achievements = Node.void("achievements") root.add_child(achievements) # Dailies if pack_id is None: - achievements.set_attribute('pack', '0') - achievements.set_attribute('pack_comp', '0') + achievements.set_attribute("pack", "0") + achievements.set_attribute("pack_comp", "0") else: - daily_played = self.data.local.user.get_achievement(self.game, self.version, userid, pack_id, 'daily') + daily_played = self.data.local.user.get_achievement( + self.game, self.version, userid, pack_id, "daily" + ) if daily_played is None: daily_played = ValidatedDict() - achievements.set_attribute('pack', str(daily_played.get_int('pack_flg'))) - achievements.set_attribute('pack_comp', str(daily_played.get_int('pack_comp'))) + achievements.set_attribute("pack", str(daily_played.get_int("pack_flg"))) + achievements.set_attribute( + "pack_comp", str(daily_played.get_int("pack_comp")) + ) # Weeklies - achievements.set_attribute('last_weekly', str(profile.get_int('last_weekly'))) - achievements.set_attribute('weekly_num', str(profile.get_int('weekly_num'))) + achievements.set_attribute("last_weekly", str(profile.get_int("last_weekly"))) + achievements.set_attribute("weekly_num", str(profile.get_int("weekly_num"))) # Prefecture visit flag - achievements.set_attribute('visit_flg', str(profile.get_int('visit_flg'))) + achievements.set_attribute("visit_flg", str(profile.get_int("visit_flg"))) # Number of rivals beaten - achievements.set_attribute('rival_crush', str(profile.get_int('rival_crush'))) + achievements.set_attribute("rival_crush", str(profile.get_int("rival_crush"))) # Tran medals - achievements.add_child(Node.s64_array('trophy', profile.get_int_array('trophy', 10))) + achievements.add_child( + Node.s64_array("trophy", profile.get_int_array("trophy", 10)) + ) # User settings - settings_dict = profile.get_dict('settings') + settings_dict = profile.get_dict("settings") skin = Node.s16_array( - 'skin', + "skin", [ - settings_dict.get_int('frame'), - settings_dict.get_int('turntable'), - settings_dict.get_int('burst'), - settings_dict.get_int('bgm'), - settings_dict.get_int('flags'), - settings_dict.get_int('towel'), - settings_dict.get_int('judge_pos'), - settings_dict.get_int('voice'), - settings_dict.get_int('noteskin'), - settings_dict.get_int('full_combo'), - settings_dict.get_int('beam'), - settings_dict.get_int('judge'), + settings_dict.get_int("frame"), + settings_dict.get_int("turntable"), + settings_dict.get_int("burst"), + settings_dict.get_int("bgm"), + settings_dict.get_int("flags"), + settings_dict.get_int("towel"), + settings_dict.get_int("judge_pos"), + settings_dict.get_int("voice"), + settings_dict.get_int("noteskin"), + settings_dict.get_int("full_combo"), + settings_dict.get_int("beam"), + settings_dict.get_int("judge"), 0, - settings_dict.get_int('disable_song_preview'), + settings_dict.get_int("disable_song_preview"), ], ) root.add_child(skin) # DAN rankings - grade = Node.void('grade') + grade = Node.void("grade") root.add_child(grade) - grade.set_attribute('sgid', str(self.db_to_game_rank(profile.get_int(self.DAN_RANKING_SINGLE, -1), self.GAME_CLTYPE_SINGLE))) - grade.set_attribute('dgid', str(self.db_to_game_rank(profile.get_int(self.DAN_RANKING_DOUBLE, -1), self.GAME_CLTYPE_DOUBLE))) - rankings = self.data.local.user.get_achievements(self.game, self.version, userid) + grade.set_attribute( + "sgid", + str( + self.db_to_game_rank( + profile.get_int(self.DAN_RANKING_SINGLE, -1), + self.GAME_CLTYPE_SINGLE, + ) + ), + ) + grade.set_attribute( + "dgid", + str( + self.db_to_game_rank( + profile.get_int(self.DAN_RANKING_DOUBLE, -1), + self.GAME_CLTYPE_DOUBLE, + ) + ), + ) + rankings = self.data.local.user.get_achievements( + self.game, self.version, userid + ) for rank in rankings: if rank.type == self.DAN_RANKING_SINGLE: - grade.add_child(Node.u8_array('g', [ - self.GAME_CLTYPE_SINGLE, - self.db_to_game_rank(rank.id, self.GAME_CLTYPE_SINGLE), - rank.data.get_int('stages_cleared'), - rank.data.get_int('percent'), - ])) + grade.add_child( + Node.u8_array( + "g", + [ + self.GAME_CLTYPE_SINGLE, + self.db_to_game_rank(rank.id, self.GAME_CLTYPE_SINGLE), + rank.data.get_int("stages_cleared"), + rank.data.get_int("percent"), + ], + ) + ) if rank.type == self.DAN_RANKING_DOUBLE: - grade.add_child(Node.u8_array('g', [ - self.GAME_CLTYPE_DOUBLE, - self.db_to_game_rank(rank.id, self.GAME_CLTYPE_DOUBLE), - rank.data.get_int('stages_cleared'), - rank.data.get_int('percent'), - ])) + grade.add_child( + Node.u8_array( + "g", + [ + self.GAME_CLTYPE_DOUBLE, + self.db_to_game_rank(rank.id, self.GAME_CLTYPE_DOUBLE), + rank.data.get_int("stages_cleared"), + rank.data.get_int("percent"), + ], + ) + ) # Qpro data - qpro_dict = profile.get_dict('qpro') - root.add_child(Node.u32_array( - 'qprodata', - [ - qpro_dict.get_int('head'), - qpro_dict.get_int('hair'), - qpro_dict.get_int('face'), - qpro_dict.get_int('hand'), - qpro_dict.get_int('body'), - ], - )) + qpro_dict = profile.get_dict("qpro") + root.add_child( + Node.u32_array( + "qprodata", + [ + qpro_dict.get_int("head"), + qpro_dict.get_int("hair"), + qpro_dict.get_int("face"), + qpro_dict.get_int("hand"), + qpro_dict.get_int("body"), + ], + ) + ) # Rivals - rlist = Node.void('rlist') + rlist = Node.void("rlist") root.add_child(rlist) links = self.data.local.user.get_links(self.game, self.version, userid) for link in links: rival_type = None - if link.type == 'sp_rival': - rival_type = '1' - elif link.type == 'dp_rival': - rival_type = '2' + if link.type == "sp_rival": + rival_type = "1" + elif link.type == "dp_rival": + rival_type = "2" else: # No business with this link type continue @@ -1268,427 +1383,472 @@ class IIDXSpada(IIDXBase): continue other_play_stats = self.get_play_statistics(link.other_userid) - rival = Node.void('rival') + rival = Node.void("rival") rlist.add_child(rival) - rival.set_attribute('spdp', rival_type) - rival.set_attribute('id', str(other_profile.extid)) - rival.set_attribute('id_str', ID.format_extid(other_profile.extid)) - rival.set_attribute('djname', other_profile.get_str('name')) - rival.set_attribute('pid', str(other_profile.get_int('pid'))) - rival.set_attribute('sg', str(self.db_to_game_rank(other_profile.get_int(self.DAN_RANKING_SINGLE, -1), self.GAME_CLTYPE_SINGLE))) - rival.set_attribute('dg', str(self.db_to_game_rank(other_profile.get_int(self.DAN_RANKING_DOUBLE, -1), self.GAME_CLTYPE_DOUBLE))) - rival.set_attribute('sa', str(other_play_stats.get_int('single_dj_points'))) - rival.set_attribute('da', str(other_play_stats.get_int('double_dj_points'))) + rival.set_attribute("spdp", rival_type) + rival.set_attribute("id", str(other_profile.extid)) + rival.set_attribute("id_str", ID.format_extid(other_profile.extid)) + rival.set_attribute("djname", other_profile.get_str("name")) + rival.set_attribute("pid", str(other_profile.get_int("pid"))) + rival.set_attribute( + "sg", + str( + self.db_to_game_rank( + other_profile.get_int(self.DAN_RANKING_SINGLE, -1), + self.GAME_CLTYPE_SINGLE, + ) + ), + ) + rival.set_attribute( + "dg", + str( + self.db_to_game_rank( + other_profile.get_int(self.DAN_RANKING_DOUBLE, -1), + self.GAME_CLTYPE_DOUBLE, + ) + ), + ) + rival.set_attribute("sa", str(other_play_stats.get_int("single_dj_points"))) + rival.set_attribute("da", str(other_play_stats.get_int("double_dj_points"))) # If the user joined a particular shop, let the game know. - if 'shop_location' in other_profile: - shop_id = other_profile.get_int('shop_location') + if "shop_location" in other_profile: + shop_id = other_profile.get_int("shop_location") machine = self.get_machine_by_id(shop_id) if machine is not None: - shop = Node.void('shop') + shop = Node.void("shop") rival.add_child(shop) - shop.set_attribute('name', machine.name) + shop.set_attribute("name", machine.name) - qprodata = Node.void('qprodata') + qprodata = Node.void("qprodata") rival.add_child(qprodata) - qpro = other_profile.get_dict('qpro') - qprodata.set_attribute('head', str(qpro.get_int('head'))) - qprodata.set_attribute('hair', str(qpro.get_int('hair'))) - qprodata.set_attribute('face', str(qpro.get_int('face'))) - qprodata.set_attribute('body', str(qpro.get_int('body'))) - qprodata.set_attribute('hand', str(qpro.get_int('hand'))) + qpro = other_profile.get_dict("qpro") + qprodata.set_attribute("head", str(qpro.get_int("head"))) + qprodata.set_attribute("hair", str(qpro.get_int("hair"))) + qprodata.set_attribute("face", str(qpro.get_int("face"))) + qprodata.set_attribute("body", str(qpro.get_int("body"))) + qprodata.set_attribute("hand", str(qpro.get_int("hand"))) # Step up mode - step_dict = profile.get_dict('step') - step = Node.void('step') + step_dict = profile.get_dict("step") + step = Node.void("step") root.add_child(step) - step.set_attribute('damage', str(step_dict.get_int('damage'))) - step.set_attribute('defeat', str(step_dict.get_int('defeat'))) - step.set_attribute('progress', str(step_dict.get_int('progress'))) - step.set_attribute('round', str(step_dict.get_int('round'))) - step.set_attribute('sp_mission', str(step_dict.get_int('sp_mission'))) - step.set_attribute('dp_mission', str(step_dict.get_int('dp_mission'))) - step.set_attribute('sp_level', str(step_dict.get_int('sp_level'))) - step.set_attribute('dp_level', str(step_dict.get_int('dp_level'))) - step.set_attribute('sp_mplay', str(step_dict.get_int('sp_mplay'))) - step.set_attribute('dp_mplay', str(step_dict.get_int('dp_mplay'))) - step.set_attribute('last_select', str(step_dict.get_int('last_select'))) - step.add_child(Node.binary('album', step_dict.get_bytes('album', b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'))) + step.set_attribute("damage", str(step_dict.get_int("damage"))) + step.set_attribute("defeat", str(step_dict.get_int("defeat"))) + step.set_attribute("progress", str(step_dict.get_int("progress"))) + step.set_attribute("round", str(step_dict.get_int("round"))) + step.set_attribute("sp_mission", str(step_dict.get_int("sp_mission"))) + step.set_attribute("dp_mission", str(step_dict.get_int("dp_mission"))) + step.set_attribute("sp_level", str(step_dict.get_int("sp_level"))) + step.set_attribute("dp_level", str(step_dict.get_int("dp_level"))) + step.set_attribute("sp_mplay", str(step_dict.get_int("sp_mplay"))) + step.set_attribute("dp_mplay", str(step_dict.get_int("dp_mplay"))) + step.set_attribute("last_select", str(step_dict.get_int("last_select"))) + step.add_child( + Node.binary( + "album", + step_dict.get_bytes( + "album", b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ), + ) + ) # Favorites - favorite = Node.void('favorite') + favorite = Node.void("favorite") root.add_child(favorite) - favorite_dict = profile.get_dict('favorite') + favorite_dict = profile.get_dict("favorite") - sp_mlist = b'' - sp_clist = b'' - singles_list = favorite_dict['single'] if 'single' in favorite_dict else [] + sp_mlist = b"" + sp_clist = b"" + singles_list = favorite_dict["single"] if "single" in favorite_dict else [] for single in singles_list: - sp_mlist = sp_mlist + struct.pack(' Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() play_stats = self.get_play_statistics(userid) # Track play counts - cltype = int(request.attribute('cltype')) + cltype = int(request.attribute("cltype")) if cltype == self.GAME_CLTYPE_SINGLE: - play_stats.increment_int('single_plays') + play_stats.increment_int("single_plays") if cltype == self.GAME_CLTYPE_DOUBLE: - play_stats.increment_int('double_plays') + play_stats.increment_int("double_plays") # Track DJ points - play_stats.replace_int('single_dj_points', int(request.attribute('s_achi'))) - play_stats.replace_int('double_dj_points', int(request.attribute('d_achi'))) + play_stats.replace_int("single_dj_points", int(request.attribute("s_achi"))) + play_stats.replace_int("double_dj_points", int(request.attribute("d_achi"))) # Profile settings - newprofile.replace_int('mode', int(request.attribute('mode'))) - newprofile.replace_int('pmode', int(request.attribute('pmode'))) - newprofile.replace_int('rtype', int(request.attribute('rtype'))) - newprofile.replace_int('s_lift', int(request.attribute('s_lift'))) - newprofile.replace_int('d_lift', int(request.attribute('d_lift'))) - newprofile.replace_int('sp_opt', int(request.attribute('sp_opt'))) - newprofile.replace_int('dp_opt', int(request.attribute('dp_opt'))) - newprofile.replace_int('dp_opt2', int(request.attribute('dp_opt2'))) - newprofile.replace_int('gpos', int(request.attribute('gpos'))) - newprofile.replace_int('s_sorttype', int(request.attribute('s_sorttype'))) - newprofile.replace_int('d_sorttype', int(request.attribute('d_sorttype'))) - newprofile.replace_int('s_pace', int(request.attribute('s_pace'))) - newprofile.replace_int('d_pace', int(request.attribute('d_pace'))) - newprofile.replace_int('s_gno', int(request.attribute('s_gno'))) - newprofile.replace_int('d_gno', int(request.attribute('d_gno'))) - newprofile.replace_int('s_gtype', int(request.attribute('s_gtype'))) - newprofile.replace_int('d_gtype', int(request.attribute('d_gtype'))) - newprofile.replace_int('s_sdlen', int(request.attribute('s_sdlen'))) - newprofile.replace_int('d_sdlen', int(request.attribute('d_sdlen'))) - newprofile.replace_int('s_sdtype', int(request.attribute('s_sdtype'))) - newprofile.replace_int('d_sdtype', int(request.attribute('d_sdtype'))) - newprofile.replace_int('s_timing', int(request.attribute('s_timing'))) - newprofile.replace_int('d_timing', int(request.attribute('d_timing'))) - newprofile.replace_float('s_notes', float(request.attribute('s_notes'))) - newprofile.replace_float('d_notes', float(request.attribute('d_notes'))) - newprofile.replace_int('s_judge', int(request.attribute('s_judge'))) - newprofile.replace_int('d_judge', int(request.attribute('d_judge'))) - newprofile.replace_float('s_hispeed', float(request.attribute('s_hispeed'))) - newprofile.replace_float('d_hispeed', float(request.attribute('d_hispeed'))) - newprofile.replace_int('s_disp_judge', int(request.attribute('s_disp_judge'))) - newprofile.replace_int('d_disp_judge', int(request.attribute('d_disp_judge'))) - newprofile.replace_int('s_opstyle', int(request.attribute('s_opstyle'))) - newprofile.replace_int('d_opstyle', int(request.attribute('d_opstyle'))) + newprofile.replace_int("mode", int(request.attribute("mode"))) + newprofile.replace_int("pmode", int(request.attribute("pmode"))) + newprofile.replace_int("rtype", int(request.attribute("rtype"))) + newprofile.replace_int("s_lift", int(request.attribute("s_lift"))) + newprofile.replace_int("d_lift", int(request.attribute("d_lift"))) + newprofile.replace_int("sp_opt", int(request.attribute("sp_opt"))) + newprofile.replace_int("dp_opt", int(request.attribute("dp_opt"))) + newprofile.replace_int("dp_opt2", int(request.attribute("dp_opt2"))) + newprofile.replace_int("gpos", int(request.attribute("gpos"))) + newprofile.replace_int("s_sorttype", int(request.attribute("s_sorttype"))) + newprofile.replace_int("d_sorttype", int(request.attribute("d_sorttype"))) + newprofile.replace_int("s_pace", int(request.attribute("s_pace"))) + newprofile.replace_int("d_pace", int(request.attribute("d_pace"))) + newprofile.replace_int("s_gno", int(request.attribute("s_gno"))) + newprofile.replace_int("d_gno", int(request.attribute("d_gno"))) + newprofile.replace_int("s_gtype", int(request.attribute("s_gtype"))) + newprofile.replace_int("d_gtype", int(request.attribute("d_gtype"))) + newprofile.replace_int("s_sdlen", int(request.attribute("s_sdlen"))) + newprofile.replace_int("d_sdlen", int(request.attribute("d_sdlen"))) + newprofile.replace_int("s_sdtype", int(request.attribute("s_sdtype"))) + newprofile.replace_int("d_sdtype", int(request.attribute("d_sdtype"))) + newprofile.replace_int("s_timing", int(request.attribute("s_timing"))) + newprofile.replace_int("d_timing", int(request.attribute("d_timing"))) + newprofile.replace_float("s_notes", float(request.attribute("s_notes"))) + newprofile.replace_float("d_notes", float(request.attribute("d_notes"))) + newprofile.replace_int("s_judge", int(request.attribute("s_judge"))) + newprofile.replace_int("d_judge", int(request.attribute("d_judge"))) + newprofile.replace_float("s_hispeed", float(request.attribute("s_hispeed"))) + newprofile.replace_float("d_hispeed", float(request.attribute("d_hispeed"))) + newprofile.replace_int("s_disp_judge", int(request.attribute("s_disp_judge"))) + newprofile.replace_int("d_disp_judge", int(request.attribute("d_disp_judge"))) + newprofile.replace_int("s_opstyle", int(request.attribute("s_opstyle"))) + newprofile.replace_int("d_opstyle", int(request.attribute("d_opstyle"))) # Update judge window adjustments per-machine - judge_dict = newprofile.get_dict('machine_judge_adjust') + judge_dict = newprofile.get_dict("machine_judge_adjust") machine_judge = judge_dict.get_dict(self.config.machine.pcbid) - machine_judge.replace_int('single', int(request.attribute('s_judgeAdj'))) - machine_judge.replace_int('double', int(request.attribute('d_judgeAdj'))) + machine_judge.replace_int("single", int(request.attribute("s_judgeAdj"))) + machine_judge.replace_int("double", int(request.attribute("d_judgeAdj"))) judge_dict.replace_dict(self.config.machine.pcbid, machine_judge) - newprofile.replace_dict('machine_judge_adjust', judge_dict) + newprofile.replace_dict("machine_judge_adjust", judge_dict) # Secret flags saving - secret = request.child('secret') + secret = request.child("secret") if secret is not None: - secret_dict = newprofile.get_dict('secret') - secret_dict.replace_int_array('flg1', 2, secret.child_value('flg1')) - secret_dict.replace_int_array('flg2', 2, secret.child_value('flg2')) - secret_dict.replace_int_array('flg3', 2, secret.child_value('flg3')) - newprofile.replace_dict('secret', secret_dict) + secret_dict = newprofile.get_dict("secret") + secret_dict.replace_int_array("flg1", 2, secret.child_value("flg1")) + secret_dict.replace_int_array("flg2", 2, secret.child_value("flg2")) + secret_dict.replace_int_array("flg3", 2, secret.child_value("flg3")) + newprofile.replace_dict("secret", secret_dict) # Basic achievements - achievements = request.child('achievements') + achievements = request.child("achievements") if achievements is not None: - newprofile.replace_int('visit_flg', int(achievements.attribute('visit_flg'))) - newprofile.replace_int('last_weekly', int(achievements.attribute('last_weekly'))) - newprofile.replace_int('weekly_num', int(achievements.attribute('weekly_num'))) + newprofile.replace_int( + "visit_flg", int(achievements.attribute("visit_flg")) + ) + newprofile.replace_int( + "last_weekly", int(achievements.attribute("last_weekly")) + ) + newprofile.replace_int( + "weekly_num", int(achievements.attribute("weekly_num")) + ) - pack_id = int(achievements.attribute('pack_id')) + pack_id = int(achievements.attribute("pack_id")) if pack_id > 0: self.data.local.user.put_achievement( self.game, self.version, userid, pack_id, - 'daily', + "daily", { - 'pack_flg': int(achievements.attribute('pack_flg')), - 'pack_comp': int(achievements.attribute('pack_comp')), + "pack_flg": int(achievements.attribute("pack_flg")), + "pack_comp": int(achievements.attribute("pack_comp")), }, ) - trophies = achievements.child('trophy') + trophies = achievements.child("trophy") if trophies is not None: # We only load the first 10 in profile load. - newprofile.replace_int_array('trophy', 10, trophies.value[:10]) + newprofile.replace_int_array("trophy", 10, trophies.value[:10]) # Deller saving - deller = request.child('deller') + deller = request.child("deller") if deller is not None: - newprofile.replace_int('deller', newprofile.get_int('deller') + int(deller.attribute('deller'))) + newprofile.replace_int( + "deller", newprofile.get_int("deller") + int(deller.attribute("deller")) + ) # Favorites saving - favorite = request.child('favorite') + favorite = request.child("favorite") if favorite is not None: - single_music_bin = favorite.child_value('sp_mlist') - single_chart_bin = favorite.child_value('sp_clist') - double_music_bin = favorite.child_value('dp_mlist') - double_chart_bin = favorite.child_value('dp_clist') + single_music_bin = favorite.child_value("sp_mlist") + single_chart_bin = favorite.child_value("sp_clist") + double_music_bin = favorite.child_value("dp_mlist") + double_chart_bin = favorite.child_value("dp_clist") singles = [] doubles = [] for i in range(self.FAVORITE_LIST_LENGTH): - singles.append({ - 'id': struct.unpack(' Optional[IIDXBase]: @@ -22,7 +22,7 @@ class IIDX2ndStyle(IIDXBase): class IIDX3rdStyle(IIDXBase): - name: str = 'Beatmania IIDX 3rd style' + name: str = "Beatmania IIDX 3rd style" version: int = VersionConstants.IIDX_3RD_STYLE def previous_version(self) -> Optional[IIDXBase]: @@ -31,7 +31,7 @@ class IIDX3rdStyle(IIDXBase): class IIDX4thStyle(IIDXBase): - name: str = 'Beatmania IIDX 4th style' + name: str = "Beatmania IIDX 4th style" version: int = VersionConstants.IIDX_4TH_STYLE def previous_version(self) -> Optional[IIDXBase]: @@ -40,7 +40,7 @@ class IIDX4thStyle(IIDXBase): class IIDX5thStyle(IIDXBase): - name: str = 'Beatmania IIDX 5th style' + name: str = "Beatmania IIDX 5th style" version: int = VersionConstants.IIDX_5TH_STYLE def previous_version(self) -> Optional[IIDXBase]: @@ -49,7 +49,7 @@ class IIDX5thStyle(IIDXBase): class IIDX6thStyle(IIDXBase): - name: str = 'Beatmania IIDX 6th style' + name: str = "Beatmania IIDX 6th style" version: int = VersionConstants.IIDX_6TH_STYLE def previous_version(self) -> Optional[IIDXBase]: @@ -58,7 +58,7 @@ class IIDX6thStyle(IIDXBase): class IIDX7thStyle(IIDXBase): - name: str = 'Beatmania IIDX 7th style' + name: str = "Beatmania IIDX 7th style" version: int = VersionConstants.IIDX_7TH_STYLE def previous_version(self) -> Optional[IIDXBase]: @@ -67,7 +67,7 @@ class IIDX7thStyle(IIDXBase): class IIDX8thStyle(IIDXBase): - name: str = 'Beatmania IIDX 8th style' + name: str = "Beatmania IIDX 8th style" version: int = VersionConstants.IIDX_8TH_STYLE def previous_version(self) -> Optional[IIDXBase]: @@ -76,7 +76,7 @@ class IIDX8thStyle(IIDXBase): class IIDX9thStyle(IIDXBase): - name: str = 'Beatmania IIDX 9th style' + name: str = "Beatmania IIDX 9th style" version: int = VersionConstants.IIDX_9TH_STYLE def previous_version(self) -> Optional[IIDXBase]: @@ -85,7 +85,7 @@ class IIDX9thStyle(IIDXBase): class IIDX10thStyle(IIDXBase): - name: str = 'Beatmania IIDX 10th style' + name: str = "Beatmania IIDX 10th style" version: int = VersionConstants.IIDX_10TH_STYLE def previous_version(self) -> Optional[IIDXBase]: @@ -94,7 +94,7 @@ class IIDX10thStyle(IIDXBase): class IIDXRed(IIDXBase): - name: str = 'Beatmania IIDX RED' + name: str = "Beatmania IIDX RED" version: int = VersionConstants.IIDX_RED def previous_version(self) -> Optional[IIDXBase]: @@ -103,7 +103,7 @@ class IIDXRed(IIDXBase): class IIDXHappySky(IIDXBase): - name: str = 'Beatmania IIDX HAPPY SKY' + name: str = "Beatmania IIDX HAPPY SKY" version: int = VersionConstants.IIDX_HAPPY_SKY def previous_version(self) -> Optional[IIDXBase]: @@ -112,7 +112,7 @@ class IIDXHappySky(IIDXBase): class IIDXDistorted(IIDXBase): - name: str = 'Beatmania IIDX DistorteD' + name: str = "Beatmania IIDX DistorteD" version: int = VersionConstants.IIDX_DISTORTED def previous_version(self) -> Optional[IIDXBase]: @@ -121,7 +121,7 @@ class IIDXDistorted(IIDXBase): class IIDXGold(IIDXBase): - name: str = 'Beatmania IIDX GOLD' + name: str = "Beatmania IIDX GOLD" version: int = VersionConstants.IIDX_GOLD def previous_version(self) -> Optional[IIDXBase]: @@ -130,7 +130,7 @@ class IIDXGold(IIDXBase): class IIDXDJTroopers(IIDXBase): - name: str = 'Beatmania IIDX DJ TROOPERS' + name: str = "Beatmania IIDX DJ TROOPERS" version: int = VersionConstants.IIDX_DJ_TROOPERS def previous_version(self) -> Optional[IIDXBase]: @@ -139,7 +139,7 @@ class IIDXDJTroopers(IIDXBase): class IIDXEmpress(IIDXBase): - name: str = 'Beatmania IIDX EMPRESS' + name: str = "Beatmania IIDX EMPRESS" version: int = VersionConstants.IIDX_EMPRESS def previous_version(self) -> Optional[IIDXBase]: @@ -148,7 +148,7 @@ class IIDXEmpress(IIDXBase): class IIDXSirius(IIDXBase): - name: str = 'Beatmania IIDX SIRIUS' + name: str = "Beatmania IIDX SIRIUS" version: int = VersionConstants.IIDX_SIRIUS def previous_version(self) -> Optional[IIDXBase]: @@ -157,7 +157,7 @@ class IIDXSirius(IIDXBase): class IIDXResortAnthem(IIDXBase): - name: str = 'Beatmania IIDX Resort Anthem' + name: str = "Beatmania IIDX Resort Anthem" version: int = VersionConstants.IIDX_RESORT_ANTHEM def previous_version(self) -> Optional[IIDXBase]: @@ -166,7 +166,7 @@ class IIDXResortAnthem(IIDXBase): class IIDXLincle(IIDXBase): - name: str = 'Beatmania IIDX Lincle' + name: str = "Beatmania IIDX Lincle" version: int = VersionConstants.IIDX_LINCLE def previous_version(self) -> Optional[IIDXBase]: diff --git a/bemani/backend/iidx/tricoro.py b/bemani/backend/iidx/tricoro.py index 689bccc..589783e 100644 --- a/bemani/backend/iidx/tricoro.py +++ b/bemani/backend/iidx/tricoro.py @@ -13,7 +13,7 @@ from bemani.protocol import Node class IIDXTricoro(IIDXBase): - name: str = 'Beatmania IIDX Tricoro' + name: str = "Beatmania IIDX Tricoro" version: int = VersionConstants.IIDX_TRICORO GAME_CLTYPE_SINGLE: Final[int] = 0 @@ -93,37 +93,54 @@ class IIDXTricoro(IIDXBase): return IIDXLincle(self.data, self.config, self.model) @classmethod - def run_scheduled_work(cls, data: Data, config: Dict[str, Any]) -> List[Tuple[str, Dict[str, Any]]]: + def run_scheduled_work( + cls, data: Data, config: Dict[str, Any] + ) -> List[Tuple[str, Dict[str, Any]]]: """ Insert dailies into the DB. """ events = [] - if data.local.network.should_schedule(cls.game, cls.version, 'daily_charts', 'daily'): + if data.local.network.should_schedule( + cls.game, cls.version, "daily_charts", "daily" + ): # Generate a new list of three dailies. - start_time, end_time = data.local.network.get_schedule_duration('daily') - all_songs = list(set([song.id for song in data.local.music.get_all_songs(cls.game, cls.version)])) + start_time, end_time = data.local.network.get_schedule_duration("daily") + all_songs = list( + set( + [ + song.id + for song in data.local.music.get_all_songs( + cls.game, cls.version + ) + ] + ) + ) if len(all_songs) >= 3: daily_songs = random.sample(all_songs, 3) data.local.game.put_time_sensitive_settings( cls.game, cls.version, - 'dailies', + "dailies", { - 'start_time': start_time, - 'end_time': end_time, - 'music': daily_songs, + "start_time": start_time, + "end_time": end_time, + "music": daily_songs, }, ) - events.append(( - 'iidx_daily_charts', - { - 'version': cls.version, - 'music': daily_songs, - }, - )) + events.append( + ( + "iidx_daily_charts", + { + "version": cls.version, + "music": daily_songs, + }, + ) + ) # Mark that we did some actual work here. - data.local.network.mark_scheduled(cls.game, cls.version, 'daily_charts', 'daily') + data.local.network.mark_scheduled( + cls.game, cls.version, "daily_charts", "daily" + ) return events @classmethod @@ -132,18 +149,18 @@ class IIDXTricoro(IIDXBase): Return all of our front-end modifiably settings. """ return { - 'bools': [ + "bools": [ { - 'name': 'Global Shop Ranking', - 'tip': 'Return network-wide ranking instead of shop ranking on results screen.', - 'category': 'game_config', - 'setting': 'global_shop_ranking', + "name": "Global Shop Ranking", + "tip": "Return network-wide ranking instead of shop ranking on results screen.", + "category": "game_config", + "setting": "global_shop_ranking", }, { - 'name': 'Events In Omnimix', - 'tip': 'Allow events to be enabled at all for Omnimix.', - 'category': 'game_config', - 'setting': 'omnimix_events_enabled', + "name": "Events In Omnimix", + "tip": "Allow events to be enabled at all for Omnimix.", + "category": "game_config", + "setting": "omnimix_events_enabled", }, ], } @@ -220,7 +237,7 @@ class IIDXTricoro(IIDXBase): self.DAN_RANK_KAIDEN: self.GAME_DP_DAN_RANK_KAIDEN, }[db_dan] else: - raise Exception('Invalid cltype!') + raise Exception("Invalid cltype!") def game_to_db_rank(self, game_dan: int, cltype: int) -> int: # Special case for no DAN rank @@ -268,7 +285,7 @@ class IIDXTricoro(IIDXBase): self.GAME_DP_DAN_RANK_KAIDEN: self.DAN_RANK_KAIDEN, }[game_dan] else: - raise Exception('Invalid cltype!') + raise Exception("Invalid cltype!") def game_to_db_chart(self, db_chart: int) -> int: return { @@ -282,57 +299,61 @@ class IIDXTricoro(IIDXBase): }[db_chart] def handle_shop_getname_request(self, request: Node) -> Node: - root = Node.void('shop') - root.set_attribute('cls_opt', '0') + root = Node.void("shop") + root.set_attribute("cls_opt", "0") machine = self.data.local.machine.get_machine(self.config.machine.pcbid) - root.set_attribute('opname', machine.name) - root.set_attribute('pid', str(self.get_machine_region())) + root.set_attribute("opname", machine.name) + root.set_attribute("pid", str(self.get_machine_region())) return root def handle_shop_savename_request(self, request: Node) -> Node: - self.update_machine_name(request.attribute('opname')) - root = Node.void('shop') + self.update_machine_name(request.attribute("opname")) + root = Node.void("shop") return root def handle_shop_sentinfo_request(self, request: Node) -> Node: - root = Node.void('shop') + root = Node.void("shop") return root def handle_shop_getconvention_request(self, request: Node) -> Node: - root = Node.void('shop') + root = Node.void("shop") machine = self.data.local.machine.get_machine(self.config.machine.pcbid) if machine.arcade is not None: - course = self.data.local.machine.get_settings(machine.arcade, self.game, self.music_version, 'shop_course') + course = self.data.local.machine.get_settings( + machine.arcade, self.game, self.music_version, "shop_course" + ) else: course = None if course is None: course = ValidatedDict() - root.set_attribute('music_0', str(course.get_int('music_0', 20032))) - root.set_attribute('music_1', str(course.get_int('music_1', 20009))) - root.set_attribute('music_2', str(course.get_int('music_2', 20015))) - root.set_attribute('music_3', str(course.get_int('music_3', 20064))) - root.add_child(Node.bool('valid', course.get_bool('valid'))) + root.set_attribute("music_0", str(course.get_int("music_0", 20032))) + root.set_attribute("music_1", str(course.get_int("music_1", 20009))) + root.set_attribute("music_2", str(course.get_int("music_2", 20015))) + root.set_attribute("music_3", str(course.get_int("music_3", 20064))) + root.add_child(Node.bool("valid", course.get_bool("valid"))) return root def handle_shop_setconvention_request(self, request: Node) -> Node: - root = Node.void('shop') + root = Node.void("shop") machine = self.data.local.machine.get_machine(self.config.machine.pcbid) if machine.arcade is not None: course = ValidatedDict() - course.replace_int('music_0', request.child_value('music_0')) - course.replace_int('music_1', request.child_value('music_1')) - course.replace_int('music_2', request.child_value('music_2')) - course.replace_int('music_3', request.child_value('music_3')) - course.replace_bool('valid', request.child_value('valid')) - self.data.local.machine.put_settings(machine.arcade, self.game, self.music_version, 'shop_course', course) + course.replace_int("music_0", request.child_value("music_0")) + course.replace_int("music_1", request.child_value("music_1")) + course.replace_int("music_2", request.child_value("music_2")) + course.replace_int("music_3", request.child_value("music_3")) + course.replace_bool("valid", request.child_value("valid")) + self.data.local.machine.put_settings( + machine.arcade, self.game, self.music_version, "shop_course", course + ) return root def handle_ranking_getranker_request(self, request: Node) -> Node: - root = Node.void('ranking') - chart = self.game_to_db_chart(int(request.attribute('clid'))) + root = Node.void("ranking") + chart = self.game_to_db_chart(int(request.attribute("clid"))) if chart not in [ self.CHART_TYPE_N7, self.CHART_TYPE_H7, @@ -346,29 +367,31 @@ class IIDXTricoro(IIDXBase): machine = self.data.local.machine.get_machine(self.config.machine.pcbid) if machine.arcade is not None: - course = self.data.local.machine.get_settings(machine.arcade, self.game, self.music_version, 'shop_course') + course = self.data.local.machine.get_settings( + machine.arcade, self.game, self.music_version, "shop_course" + ) else: course = None if course is None: course = ValidatedDict() - if not course.get_bool('valid'): + if not course.get_bool("valid"): # Shop course not enabled or not present return root - convention = Node.void('convention') + convention = Node.void("convention") root.add_child(convention) - convention.set_attribute('clid', str(chart)) - convention.set_attribute('update_date', str(Time.now() * 1000)) + convention.set_attribute("clid", str(chart)) + convention.set_attribute("update_date", str(Time.now() * 1000)) # Grab all scores for each of the four songs, filter out people who haven't # set us as their arcade and then return the top 20 scores (adding all 4 songs). songids = [ - course.get_int('music_0'), - course.get_int('music_1'), - course.get_int('music_2'), - course.get_int('music_3'), + course.get_int("music_0"), + course.get_int("music_1"), + course.get_int("music_2"), + course.get_int("music_3"), ] totalscores: Dict[UserID, int] = {} @@ -405,27 +428,36 @@ class IIDXTricoro(IIDXBase): for topscore in topscores: rank = rank + 1 - detail = Node.void('detail') + detail = Node.void("detail") convention.add_child(detail) - detail.set_attribute('name', topscore[1].get_str('name')) - detail.set_attribute('rank', str(rank)) - detail.set_attribute('score', str(topscore[0])) - detail.set_attribute('pid', str(topscore[1].get_int('pid'))) + detail.set_attribute("name", topscore[1].get_str("name")) + detail.set_attribute("rank", str(rank)) + detail.set_attribute("score", str(topscore[0])) + detail.set_attribute("pid", str(topscore[1].get_int("pid"))) - qpro = topscore[1].get_dict('qpro') - detail.set_attribute('head', str(qpro.get_int('head'))) - detail.set_attribute('hair', str(qpro.get_int('hair'))) - detail.set_attribute('face', str(qpro.get_int('face'))) - detail.set_attribute('body', str(qpro.get_int('body'))) - detail.set_attribute('hand', str(qpro.get_int('hand'))) + qpro = topscore[1].get_dict("qpro") + detail.set_attribute("head", str(qpro.get_int("head"))) + detail.set_attribute("hair", str(qpro.get_int("hair"))) + detail.set_attribute("face", str(qpro.get_int("face"))) + detail.set_attribute("body", str(qpro.get_int("body"))) + detail.set_attribute("hand", str(qpro.get_int("hand"))) return root def handle_music_crate_request(self, request: Node) -> Node: - root = Node.void('music') + root = Node.void("music") attempts = self.get_clear_rates() - all_songs = list(set([song.id for song in self.data.local.music.get_all_songs(self.game, self.music_version)])) + all_songs = list( + set( + [ + song.id + for song in self.data.local.music.get_all_songs( + self.game, self.music_version + ) + ] + ) + ) for song in all_songs: clears = [] fcs = [] @@ -434,33 +466,33 @@ class IIDXTricoro(IIDXBase): placed = False if song in attempts and chart in attempts[song]: values = attempts[song][chart] - if values['total'] > 0: - clears.append(int((100 * values['clears']) / values['total'])) - fcs.append(int((100 * values['fcs']) / values['total'])) + if values["total"] > 0: + clears.append(int((100 * values["clears"]) / values["total"])) + fcs.append(int((100 * values["fcs"]) / values["total"])) placed = True if not placed: clears.append(101) fcs.append(101) - clearnode = Node.u8_array('c', clears + fcs) - clearnode.set_attribute('mid', str(song)) + clearnode = Node.u8_array("c", clears + fcs) + clearnode.set_attribute("mid", str(song)) root.add_child(clearnode) return root def handle_music_getrank_request(self, request: Node) -> Node: - cltype = int(request.attribute('cltype')) + cltype = int(request.attribute("cltype")) - root = Node.void('music') - style = Node.void('style') + root = Node.void("music") + style = Node.void("style") root.add_child(style) - style.set_attribute('type', str(cltype)) + style.set_attribute("type", str(cltype)) for rivalid in [-1, 0, 1, 2, 3, 4]: if rivalid == -1: - attr = 'iidxid' + attr = "iidxid" else: - attr = f'iidxid{rivalid}' + attr = f"iidxid{rivalid}" try: extid = int(request.attribute(attr)) @@ -470,46 +502,52 @@ class IIDXTricoro(IIDXBase): userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: - scores = self.data.remote.music.get_scores(self.game, self.music_version, userid) + scores = self.data.remote.music.get_scores( + self.game, self.music_version, userid + ) # Grab score data for user/rival scoredata = self.make_score_struct( scores, - self.CLEAR_TYPE_SINGLE if cltype == self.GAME_CLTYPE_SINGLE else self.CLEAR_TYPE_DOUBLE, + self.CLEAR_TYPE_SINGLE + if cltype == self.GAME_CLTYPE_SINGLE + else self.CLEAR_TYPE_DOUBLE, rivalid, ) for s in scoredata: - root.add_child(Node.s16_array('m', s)) + root.add_child(Node.s16_array("m", s)) # Grab most played for user/rival most_played = [ - play[0] for play in - self.data.local.music.get_most_played(self.game, self.music_version, userid, 20) + play[0] + for play in self.data.local.music.get_most_played( + self.game, self.music_version, userid, 20 + ) ] if len(most_played) < 20: most_played.extend([0] * (20 - len(most_played))) - best = Node.u16_array('best', most_played) - best.set_attribute('rno', str(rivalid)) + best = Node.u16_array("best", most_played) + best.set_attribute("rno", str(rivalid)) root.add_child(best) if rivalid == -1: # Grab beginner statuses for user only beginnerdata = self.make_beginner_struct(scores) for b in beginnerdata: - root.add_child(Node.u16_array('b', b)) + root.add_child(Node.u16_array("b", b)) return root def handle_music_reg_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) - musicid = int(request.attribute('mid')) - chart = self.game_to_db_chart(int(request.attribute('clid'))) + extid = int(request.attribute("iidxid")) + musicid = int(request.attribute("mid")) + chart = self.game_to_db_chart(int(request.attribute("clid"))) userid = self.data.remote.user.from_extid(self.game, self.version, extid) # See if we need to report global or shop scores if self.machine_joined_arcade(): game_config = self.get_game_config() - global_scores = game_config.get_bool('global_shop_ranking') + global_scores = game_config.get_bool("global_shop_ranking") machine = self.data.local.machine.get_machine(self.config.machine.pcbid) else: # If we aren't in an arcade, we can only show global scores @@ -518,21 +556,27 @@ class IIDXTricoro(IIDXBase): # First, determine our current ranking before saving the new score all_scores = sorted( - self.data.remote.music.get_all_scores(game=self.game, version=self.music_version, songid=musicid, songchart=chart), + self.data.remote.music.get_all_scores( + game=self.game, + version=self.music_version, + songid=musicid, + songchart=chart, + ), key=lambda s: (s[1].points, s[1].timestamp), reverse=True, ) all_players = { - uid: prof for (uid, prof) in - self.get_any_profiles([s[0] for s in all_scores]) + uid: prof + for (uid, prof) in self.get_any_profiles([s[0] for s in all_scores]) } if not global_scores: all_scores = [ - score for score in all_scores + score + for score in all_scores if ( - score[0] == userid or - self.user_joined_arcade(machine, all_players[score[0]]) + score[0] == userid + or self.user_joined_arcade(machine, all_players[score[0]]) ) ] @@ -544,12 +588,12 @@ class IIDXTricoro(IIDXBase): break if userid is not None: - clear_status = self.game_to_db_status(int(request.attribute('cflg'))) - pgreats = int(request.attribute('pgnum')) - greats = int(request.attribute('gnum')) - miss_count = int(request.attribute('mnum')) - ghost = request.child_value('ghost') - shopid = ID.parse_machine_id(request.attribute('shopconvid')) + clear_status = self.game_to_db_status(int(request.attribute("cflg"))) + pgreats = int(request.attribute("pgnum")) + greats = int(request.attribute("gnum")) + miss_count = int(request.attribute("mnum")) + ghost = request.child_value("ghost") + shopid = ID.parse_machine_id(request.attribute("shopconvid")) self.update_score( userid, @@ -564,50 +608,55 @@ class IIDXTricoro(IIDXBase): ) # Calculate and return statistics about this song - root = Node.void('music') - root.set_attribute('clid', request.attribute('clid')) - root.set_attribute('mid', request.attribute('mid')) + root = Node.void("music") + root.set_attribute("clid", request.attribute("clid")) + root.set_attribute("mid", request.attribute("mid")) attempts = self.get_clear_rates(musicid, chart) - count = attempts[musicid][chart]['total'] - clear = attempts[musicid][chart]['clears'] - full_combo = attempts[musicid][chart]['fcs'] + count = attempts[musicid][chart]["total"] + clear = attempts[musicid][chart]["clears"] + full_combo = attempts[musicid][chart]["fcs"] if count > 0: - root.set_attribute('crate', str(int((100 * clear) / count))) - root.set_attribute('frate', str(int((100 * full_combo) / count))) + root.set_attribute("crate", str(int((100 * clear) / count))) + root.set_attribute("frate", str(int((100 * full_combo) / count))) else: - root.set_attribute('crate', '0') - root.set_attribute('frate', '0') + root.set_attribute("crate", "0") + root.set_attribute("frate", "0") if userid is not None: # Shop ranking - shopdata = Node.void('shopdata') + shopdata = Node.void("shopdata") root.add_child(shopdata) - shopdata.set_attribute('rank', '-1' if oldindex is None else str(oldindex + 1)) + shopdata.set_attribute( + "rank", "-1" if oldindex is None else str(oldindex + 1) + ) # Grab the rank of some other players on this song - ranklist = Node.void('ranklist') + ranklist = Node.void("ranklist") root.add_child(ranklist) all_scores = sorted( - self.data.remote.music.get_all_scores(game=self.game, version=self.music_version, songid=musicid, songchart=chart), + self.data.remote.music.get_all_scores( + game=self.game, + version=self.music_version, + songid=musicid, + songchart=chart, + ), key=lambda s: (s[1].points, s[1].timestamp), reverse=True, ) - missing_players = [ - uid for (uid, _) in all_scores - if uid not in all_players - ] + missing_players = [uid for (uid, _) in all_scores if uid not in all_players] for (uid, prof) in self.get_any_profiles(missing_players): all_players[uid] = prof if not global_scores: all_scores = [ - score for score in all_scores + score + for score in all_scores if ( - score[0] == userid or - self.user_joined_arcade(machine, all_players[score[0]]) + score[0] == userid + or self.user_joined_arcade(machine, all_players[score[0]]) ) ] @@ -618,64 +667,79 @@ class IIDXTricoro(IIDXBase): ourindex = i break if ourindex is None: - raise Exception('Cannot find our own score after saving to DB!') + raise Exception("Cannot find our own score after saving to DB!") start = ourindex - 4 end = ourindex + 4 if start < 0: start = 0 if end >= len(all_scores): end = len(all_scores) - 1 - relevant_scores = all_scores[start:(end + 1)] + relevant_scores = all_scores[start : (end + 1)] record_num = start + 1 for score in relevant_scores: profile = all_players[score[0]] - data = Node.void('data') + data = Node.void("data") ranklist.add_child(data) - data.set_attribute('iidx_id', str(profile.extid)) - data.set_attribute('name', profile.get_str('name')) + data.set_attribute("iidx_id", str(profile.extid)) + data.set_attribute("name", profile.get_str("name")) - machine_name = '' - if 'shop_location' in profile: - shop_id = profile.get_int('shop_location') + machine_name = "" + if "shop_location" in profile: + shop_id = profile.get_int("shop_location") machine = self.get_machine_by_id(shop_id) if machine is not None: machine_name = machine.name - data.set_attribute('opname', machine_name) - data.set_attribute('rnum', str(record_num)) - data.set_attribute('score', str(score[1].points)) - data.set_attribute('clflg', str(self.db_to_game_status(score[1].data.get_int('clear_status')))) - data.set_attribute('pid', str(profile.get_int('pid'))) - data.set_attribute('myFlg', '1' if score[0] == userid else '0') + data.set_attribute("opname", machine_name) + data.set_attribute("rnum", str(record_num)) + data.set_attribute("score", str(score[1].points)) + data.set_attribute( + "clflg", + str(self.db_to_game_status(score[1].data.get_int("clear_status"))), + ) + data.set_attribute("pid", str(profile.get_int("pid"))) + data.set_attribute("myFlg", "1" if score[0] == userid else "0") - data.set_attribute('sgrade', str( - self.db_to_game_rank(profile.get_int(self.DAN_RANKING_SINGLE, -1), self.GAME_CLTYPE_SINGLE), - )) - data.set_attribute('dgrade', str( - self.db_to_game_rank(profile.get_int(self.DAN_RANKING_DOUBLE, -1), self.GAME_CLTYPE_DOUBLE), - )) + data.set_attribute( + "sgrade", + str( + self.db_to_game_rank( + profile.get_int(self.DAN_RANKING_SINGLE, -1), + self.GAME_CLTYPE_SINGLE, + ), + ), + ) + data.set_attribute( + "dgrade", + str( + self.db_to_game_rank( + profile.get_int(self.DAN_RANKING_DOUBLE, -1), + self.GAME_CLTYPE_DOUBLE, + ), + ), + ) - qpro = profile.get_dict('qpro') - data.set_attribute('head', str(qpro.get_int('head'))) - data.set_attribute('hair', str(qpro.get_int('hair'))) - data.set_attribute('face', str(qpro.get_int('face'))) - data.set_attribute('body', str(qpro.get_int('body'))) - data.set_attribute('hand', str(qpro.get_int('hand'))) + qpro = profile.get_dict("qpro") + data.set_attribute("head", str(qpro.get_int("head"))) + data.set_attribute("hair", str(qpro.get_int("hair"))) + data.set_attribute("face", str(qpro.get_int("face"))) + data.set_attribute("body", str(qpro.get_int("body"))) + data.set_attribute("hand", str(qpro.get_int("hand"))) record_num = record_num + 1 return root def handle_music_breg_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) - musicid = int(request.attribute('mid')) + extid = int(request.attribute("iidxid")) + musicid = int(request.attribute("mid")) userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: - clear_status = self.game_to_db_status(int(request.attribute('cflg'))) - pgreats = int(request.attribute('pgnum')) - greats = int(request.attribute('gnum')) + clear_status = self.game_to_db_status(int(request.attribute("cflg"))) + pgreats = int(request.attribute("pgnum")) + greats = int(request.attribute("gnum")) self.update_score( userid, @@ -685,18 +749,18 @@ class IIDXTricoro(IIDXBase): pgreats, greats, -1, - b'', + b"", None, ) # Return nothing. - root = Node.void('music') + root = Node.void("music") return root def handle_music_play_request(self, request: Node) -> Node: - musicid = int(request.attribute('mid')) - chart = self.game_to_db_chart(int(request.attribute('clid'))) - clear_status = self.game_to_db_status(int(request.attribute('cflg'))) + musicid = int(request.attribute("mid")) + chart = self.game_to_db_chart(int(request.attribute("clid"))) + clear_status = self.game_to_db_status(int(request.attribute("cflg"))) self.update_score( None, # No userid since its anonymous @@ -711,39 +775,41 @@ class IIDXTricoro(IIDXBase): ) # Calculate and return statistics about this song - root = Node.void('music') - root.set_attribute('clid', request.attribute('clid')) - root.set_attribute('mid', request.attribute('mid')) + root = Node.void("music") + root.set_attribute("clid", request.attribute("clid")) + root.set_attribute("mid", request.attribute("mid")) attempts = self.get_clear_rates(musicid, chart) - count = attempts[musicid][chart]['total'] - clear = attempts[musicid][chart]['clears'] - full_combo = attempts[musicid][chart]['fcs'] + count = attempts[musicid][chart]["total"] + clear = attempts[musicid][chart]["clears"] + full_combo = attempts[musicid][chart]["fcs"] if count > 0: - root.set_attribute('crate', str(int((100 * clear) / count))) - root.set_attribute('frate', str(int((100 * full_combo) / count))) + root.set_attribute("crate", str(int((100 * clear) / count))) + root.set_attribute("frate", str(int((100 * full_combo) / count))) else: - root.set_attribute('crate', '0') - root.set_attribute('frate', '0') + root.set_attribute("crate", "0") + root.set_attribute("frate", "0") return root def handle_music_appoint_request(self, request: Node) -> Node: - musicid = int(request.attribute('mid')) - chart = self.game_to_db_chart(int(request.attribute('clid'))) - ghost_type = int(request.attribute('ctype')) - extid = int(request.attribute('iidxid')) + musicid = int(request.attribute("mid")) + chart = self.game_to_db_chart(int(request.attribute("clid"))) + ghost_type = int(request.attribute("ctype")) + extid = int(request.attribute("iidxid")) userid = self.data.remote.user.from_extid(self.game, self.version, extid) - root = Node.void('music') + root = Node.void("music") if userid is not None: # Try to look up previous ghost for user - my_score = self.data.remote.music.get_score(self.game, self.music_version, userid, musicid, chart) + my_score = self.data.remote.music.get_score( + self.game, self.music_version, userid, musicid, chart + ) if my_score is not None: - mydata = Node.binary('mydata', my_score.data.get_bytes('ghost')) - mydata.set_attribute('score', str(my_score.points)) + mydata = Node.binary("mydata", my_score.data.get_bytes("ghost")) + mydata.set_attribute("score", str(my_score.points)) root.add_child(mydata) ghost_score = self.get_ghost( @@ -758,7 +824,7 @@ class IIDXTricoro(IIDXBase): self.GAME_GHOST_TYPE_RIVAL_TOP: self.GHOST_TYPE_RIVAL_TOP, self.GAME_GHOST_TYPE_RIVAL_AVERAGE: self.GHOST_TYPE_RIVAL_AVERAGE, }.get(ghost_type, self.GHOST_TYPE_NONE), - request.attribute('subtype'), + request.attribute("subtype"), self.GAME_GHOST_LENGTH, musicid, chart, @@ -767,35 +833,35 @@ class IIDXTricoro(IIDXBase): # Add ghost score if we support it if ghost_score is not None: - sdata = Node.binary('sdata', ghost_score['ghost']) - sdata.set_attribute('score', str(ghost_score['score'])) - if 'name' in ghost_score: - sdata.set_attribute('name', ghost_score['name']) - if 'pid' in ghost_score: - sdata.set_attribute('pid', str(ghost_score['pid'])) - if 'extid' in ghost_score: - sdata.set_attribute('riidxid', str(ghost_score['extid'])) + sdata = Node.binary("sdata", ghost_score["ghost"]) + sdata.set_attribute("score", str(ghost_score["score"])) + if "name" in ghost_score: + sdata.set_attribute("name", ghost_score["name"]) + if "pid" in ghost_score: + sdata.set_attribute("pid", str(ghost_score["pid"])) + if "extid" in ghost_score: + sdata.set_attribute("riidxid", str(ghost_score["extid"])) root.add_child(sdata) return root def handle_pc_common_request(self, request: Node) -> Node: - root = Node.void('pc') - root.set_attribute('expire', '600') + root = Node.void("pc") + root.set_attribute("expire", "600") # TODO: Hook all of these up to config options I guess? - ir = Node.void('ir') + ir = Node.void("ir") root.add_child(ir) - ir.set_attribute('beat', '2') + ir.set_attribute("beat", "2") - limit = Node.void('limit') + limit = Node.void("limit") root.add_child(limit) - limit.set_attribute('phase', '24') + limit.set_attribute("phase", "24") # See if we configured event overrides if self.machine_joined_arcade(): game_config = self.get_game_config() - omni_events = game_config.get_bool('omnimix_events_enabled') + omni_events = game_config.get_bool("omnimix_events_enabled") else: # If we aren't in an arcade, we turn off events omni_events = False @@ -806,43 +872,43 @@ class IIDXTricoro(IIDXBase): # TODO: Figure out what these map to boss_phase = 0 - boss = Node.void('boss') + boss = Node.void("boss") root.add_child(boss) - boss.set_attribute('phase', str(boss_phase)) + boss.set_attribute("phase", str(boss_phase)) - red = Node.void('red') + red = Node.void("red") root.add_child(red) - red.set_attribute('phase', '0') + red.set_attribute("phase", "0") - yellow = Node.void('yellow') + yellow = Node.void("yellow") root.add_child(yellow) - yellow.set_attribute('phase', '0') + yellow.set_attribute("phase", "0") - medal = Node.void('medal') + medal = Node.void("medal") root.add_child(medal) - medal.set_attribute('phase', '1') + medal.set_attribute("phase", "1") - cafe = Node.void('cafe') + cafe = Node.void("cafe") root.add_child(cafe) - cafe.set_attribute('open', '1') + cafe.set_attribute("open", "1") - tricolettepark = Node.void('tricolettepark') + tricolettepark = Node.void("tricolettepark") root.add_child(tricolettepark) - tricolettepark.set_attribute('open', '0') + tricolettepark.set_attribute("open", "0") return root def handle_pc_delete_request(self, request: Node) -> Node: - return Node.void('pc') + return Node.void("pc") def handle_pc_playstart_request(self, request: Node) -> Node: - return Node.void('pc') + return Node.void("pc") def handle_pc_playend_request(self, request: Node) -> Node: - return Node.void('pc') + return Node.void("pc") def handle_pc_oldget_request(self, request: Node) -> Node: - refid = request.attribute('rid') + refid = request.attribute("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: oldversion = self.previous_version() @@ -850,12 +916,12 @@ class IIDXTricoro(IIDXBase): else: profile = None - root = Node.void('pc') - root.set_attribute('status', '1' if profile is None else '0') + root = Node.void("pc") + root.set_attribute("status", "1" if profile is None else "0") return root def handle_pc_getname_request(self, request: Node) -> Node: - refid = request.attribute('rid') + refid = request.attribute("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: oldversion = self.previous_version() @@ -864,76 +930,76 @@ class IIDXTricoro(IIDXBase): profile = None if profile is None: raise Exception( - 'Should not get here if we have no profile, we should ' + - 'have returned \'1\' in the \'oldget\' method above ' + - 'which should tell the game not to present a migration.' + "Should not get here if we have no profile, we should " + + "have returned '1' in the 'oldget' method above " + + "which should tell the game not to present a migration." ) - root = Node.void('pc') - root.set_attribute('name', profile.get_str('name')) - root.set_attribute('idstr', ID.format_extid(profile.extid)) - root.set_attribute('pid', str(profile.get_int('pid'))) + root = Node.void("pc") + root.set_attribute("name", profile.get_str("name")) + root.set_attribute("idstr", ID.format_extid(profile.extid)) + root.set_attribute("pid", str(profile.get_int("pid"))) return root def handle_pc_reg_request(self, request: Node) -> Node: - refid = request.attribute('rid') - name = request.attribute('name') - pid = int(request.attribute('pid')) + refid = request.attribute("rid") + name = request.attribute("name") + pid = int(request.attribute("pid")) profile = self.new_profile_by_refid(refid, name, pid) - root = Node.void('pc') + root = Node.void("pc") if profile is not None: - root.set_attribute('id', str(profile.extid)) - root.set_attribute('id_str', ID.format_extid(profile.extid)) + root.set_attribute("id", str(profile.extid)) + root.set_attribute("id_str", ID.format_extid(profile.extid)) return root def handle_pc_get_request(self, request: Node) -> Node: - refid = request.attribute('rid') + refid = request.attribute("rid") root = self.get_profile_by_refid(refid) if root is None: - root = Node.void('pc') + root = Node.void("pc") return root def handle_pc_save_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) + extid = int(request.attribute("iidxid")) self.put_profile_by_extid(extid, request) - root = Node.void('pc') + root = Node.void("pc") return root def handle_pc_visit_request(self, request: Node) -> Node: - root = Node.void('pc') - root.set_attribute('anum', '0') - root.set_attribute('snum', '0') - root.set_attribute('pnum', '0') - root.set_attribute('aflg', '0') - root.set_attribute('sflg', '0') - root.set_attribute('pflg', '0') + root = Node.void("pc") + root.set_attribute("anum", "0") + root.set_attribute("snum", "0") + root.set_attribute("pnum", "0") + root.set_attribute("aflg", "0") + root.set_attribute("sflg", "0") + root.set_attribute("pflg", "0") return root def handle_pc_shopregister_request(self, request: Node) -> Node: - extid = int(request.child_value('iidx_id')) - location = ID.parse_machine_id(request.child_value('location_id')) + extid = int(request.child_value("iidx_id")) + location = ID.parse_machine_id(request.child_value("location_id")) userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: profile = self.get_profile(userid) if profile is None: profile = Profile(self.game, self.version, "", extid) - profile.replace_int('shop_location', location) + profile.replace_int("shop_location", location) self.put_profile(userid, profile) - root = Node.void('pc') + root = Node.void("pc") return root def handle_grade_raised_request(self, request: Node) -> Node: - extid = int(request.attribute('iidxid')) - cltype = int(request.attribute('gtype')) - rank = self.game_to_db_rank(int(request.attribute('gid')), cltype) + extid = int(request.attribute("iidxid")) + cltype = int(request.attribute("gtype")) + rank = self.game_to_db_rank(int(request.attribute("gid")), cltype) userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: - percent = int(request.attribute('achi')) - stages_cleared = int(request.attribute('cflg')) + percent = int(request.attribute("achi")) + stages_cleared = int(request.attribute("cflg")) if cltype == self.GAME_CLTYPE_SINGLE: max_stages = self.DAN_STAGES_SINGLE else: @@ -955,127 +1021,159 @@ class IIDXTricoro(IIDXBase): ) # Figure out number of players that played this ranking - all_achievements = self.data.local.user.get_all_achievements(self.game, self.version, achievementid=rank, achievementtype=index) - root = Node.void('grade') - root.set_attribute('pnum', str(len(all_achievements))) + all_achievements = self.data.local.user.get_all_achievements( + self.game, self.version, achievementid=rank, achievementtype=index + ) + root = Node.void("grade") + root.set_attribute("pnum", str(len(all_achievements))) return root def format_profile(self, userid: UserID, profile: Profile) -> Node: - root = Node.void('pc') + root = Node.void("pc") # Look up play stats we bridge to every mix play_stats = self.get_play_statistics(userid) # Look up judge window adjustments - judge_dict = profile.get_dict('machine_judge_adjust') + judge_dict = profile.get_dict("machine_judge_adjust") machine_judge = judge_dict.get_dict(self.config.machine.pcbid) # Profile data - pcdata = Node.void('pcdata') + pcdata = Node.void("pcdata") root.add_child(pcdata) - pcdata.set_attribute('id', str(profile.extid)) - pcdata.set_attribute('idstr', ID.format_extid(profile.extid)) - pcdata.set_attribute('name', profile.get_str('name')) - pcdata.set_attribute('pid', str(profile.get_int('pid'))) - pcdata.set_attribute('spnum', str(play_stats.get_int('single_plays'))) - pcdata.set_attribute('dpnum', str(play_stats.get_int('double_plays'))) - pcdata.set_attribute('sach', str(play_stats.get_int('single_dj_points'))) - pcdata.set_attribute('dach', str(play_stats.get_int('double_dj_points'))) - pcdata.set_attribute('help', str(profile.get_int('help'))) - pcdata.set_attribute('gno', str(profile.get_int('gno'))) - pcdata.set_attribute('gpos', str(profile.get_int('gpos'))) - pcdata.set_attribute('timing', str(profile.get_int('timing'))) - pcdata.set_attribute('sdhd', str(profile.get_int('sdhd'))) - pcdata.set_attribute('sdtype', str(profile.get_int('sdtype'))) - pcdata.set_attribute('notes', str(profile.get_float('notes'))) - pcdata.set_attribute('pase', str(profile.get_int('pase'))) - pcdata.set_attribute('sp_opt', str(profile.get_int('sp_opt'))) - pcdata.set_attribute('dp_opt', str(profile.get_int('dp_opt'))) - pcdata.set_attribute('dp_opt2', str(profile.get_int('dp_opt2'))) - pcdata.set_attribute('mode', str(profile.get_int('mode'))) - pcdata.set_attribute('pmode', str(profile.get_int('pmode'))) - pcdata.set_attribute('liflen', str(profile.get_int('lift'))) - pcdata.set_attribute('judge', str(profile.get_int('judge'))) - pcdata.set_attribute('opstyle', str(profile.get_int('opstyle'))) - pcdata.set_attribute('hispeed', str(profile.get_float('hispeed'))) - pcdata.set_attribute('judgeAdj', str(machine_judge.get_int('adj'))) + pcdata.set_attribute("id", str(profile.extid)) + pcdata.set_attribute("idstr", ID.format_extid(profile.extid)) + pcdata.set_attribute("name", profile.get_str("name")) + pcdata.set_attribute("pid", str(profile.get_int("pid"))) + pcdata.set_attribute("spnum", str(play_stats.get_int("single_plays"))) + pcdata.set_attribute("dpnum", str(play_stats.get_int("double_plays"))) + pcdata.set_attribute("sach", str(play_stats.get_int("single_dj_points"))) + pcdata.set_attribute("dach", str(play_stats.get_int("double_dj_points"))) + pcdata.set_attribute("help", str(profile.get_int("help"))) + pcdata.set_attribute("gno", str(profile.get_int("gno"))) + pcdata.set_attribute("gpos", str(profile.get_int("gpos"))) + pcdata.set_attribute("timing", str(profile.get_int("timing"))) + pcdata.set_attribute("sdhd", str(profile.get_int("sdhd"))) + pcdata.set_attribute("sdtype", str(profile.get_int("sdtype"))) + pcdata.set_attribute("notes", str(profile.get_float("notes"))) + pcdata.set_attribute("pase", str(profile.get_int("pase"))) + pcdata.set_attribute("sp_opt", str(profile.get_int("sp_opt"))) + pcdata.set_attribute("dp_opt", str(profile.get_int("dp_opt"))) + pcdata.set_attribute("dp_opt2", str(profile.get_int("dp_opt2"))) + pcdata.set_attribute("mode", str(profile.get_int("mode"))) + pcdata.set_attribute("pmode", str(profile.get_int("pmode"))) + pcdata.set_attribute("liflen", str(profile.get_int("lift"))) + pcdata.set_attribute("judge", str(profile.get_int("judge"))) + pcdata.set_attribute("opstyle", str(profile.get_int("opstyle"))) + pcdata.set_attribute("hispeed", str(profile.get_float("hispeed"))) + pcdata.set_attribute("judgeAdj", str(machine_judge.get_int("adj"))) # Secret flags (shh!) - secret_dict = profile.get_dict('secret') - secret = Node.void('secret') + secret_dict = profile.get_dict("secret") + secret = Node.void("secret") root.add_child(secret) - secret.add_child(Node.s64('flg1', secret_dict.get_int('flg1'))) - secret.add_child(Node.s64('flg2', secret_dict.get_int('flg2'))) - secret.add_child(Node.s64('flg3', secret_dict.get_int('flg3'))) + secret.add_child(Node.s64("flg1", secret_dict.get_int("flg1"))) + secret.add_child(Node.s64("flg2", secret_dict.get_int("flg2"))) + secret.add_child(Node.s64("flg3", secret_dict.get_int("flg3"))) # DAN rankings - grade = Node.void('grade') + grade = Node.void("grade") root.add_child(grade) - grade.set_attribute('sgid', str(self.db_to_game_rank(profile.get_int(self.DAN_RANKING_SINGLE, -1), self.GAME_CLTYPE_SINGLE))) - grade.set_attribute('dgid', str(self.db_to_game_rank(profile.get_int(self.DAN_RANKING_DOUBLE, -1), self.GAME_CLTYPE_DOUBLE))) - rankings = self.data.local.user.get_achievements(self.game, self.version, userid) + grade.set_attribute( + "sgid", + str( + self.db_to_game_rank( + profile.get_int(self.DAN_RANKING_SINGLE, -1), + self.GAME_CLTYPE_SINGLE, + ) + ), + ) + grade.set_attribute( + "dgid", + str( + self.db_to_game_rank( + profile.get_int(self.DAN_RANKING_DOUBLE, -1), + self.GAME_CLTYPE_DOUBLE, + ) + ), + ) + rankings = self.data.local.user.get_achievements( + self.game, self.version, userid + ) for rank in rankings: if rank.type == self.DAN_RANKING_SINGLE: - grade.add_child(Node.u8_array('g', [ - self.GAME_CLTYPE_SINGLE, - self.db_to_game_rank(rank.id, self.GAME_CLTYPE_SINGLE), - rank.data.get_int('stages_cleared'), - rank.data.get_int('percent'), - ])) + grade.add_child( + Node.u8_array( + "g", + [ + self.GAME_CLTYPE_SINGLE, + self.db_to_game_rank(rank.id, self.GAME_CLTYPE_SINGLE), + rank.data.get_int("stages_cleared"), + rank.data.get_int("percent"), + ], + ) + ) if rank.type == self.DAN_RANKING_DOUBLE: - grade.add_child(Node.u8_array('g', [ - self.GAME_CLTYPE_DOUBLE, - self.db_to_game_rank(rank.id, self.GAME_CLTYPE_DOUBLE), - rank.data.get_int('stages_cleared'), - rank.data.get_int('percent'), - ])) + grade.add_child( + Node.u8_array( + "g", + [ + self.GAME_CLTYPE_DOUBLE, + self.db_to_game_rank(rank.id, self.GAME_CLTYPE_DOUBLE), + rank.data.get_int("stages_cleared"), + rank.data.get_int("percent"), + ], + ) + ) # User settings - settings_dict = profile.get_dict('settings') + settings_dict = profile.get_dict("settings") skin = Node.s16_array( - 'skin', + "skin", [ - settings_dict.get_int('frame'), - settings_dict.get_int('turntable'), - settings_dict.get_int('burst'), - settings_dict.get_int('bgm'), - settings_dict.get_int('flags'), - settings_dict.get_int('towel'), - settings_dict.get_int('judge_pos'), - settings_dict.get_int('voice'), - settings_dict.get_int('noteskin'), - settings_dict.get_int('full_combo'), - settings_dict.get_int('beam'), - settings_dict.get_int('judge'), + settings_dict.get_int("frame"), + settings_dict.get_int("turntable"), + settings_dict.get_int("burst"), + settings_dict.get_int("bgm"), + settings_dict.get_int("flags"), + settings_dict.get_int("towel"), + settings_dict.get_int("judge_pos"), + settings_dict.get_int("voice"), + settings_dict.get_int("noteskin"), + settings_dict.get_int("full_combo"), + settings_dict.get_int("beam"), + settings_dict.get_int("judge"), 0, - settings_dict.get_int('disable_song_preview'), + settings_dict.get_int("disable_song_preview"), ], ) root.add_child(skin) # Qpro data - qpro_dict = profile.get_dict('qpro') - root.add_child(Node.u32_array( - 'qprodata', - [ - qpro_dict.get_int('head'), - qpro_dict.get_int('hair'), - qpro_dict.get_int('face'), - qpro_dict.get_int('hand'), - qpro_dict.get_int('body'), - ], - )) + qpro_dict = profile.get_dict("qpro") + root.add_child( + Node.u32_array( + "qprodata", + [ + qpro_dict.get_int("head"), + qpro_dict.get_int("hair"), + qpro_dict.get_int("face"), + qpro_dict.get_int("hand"), + qpro_dict.get_int("body"), + ], + ) + ) # Rivals - rlist = Node.void('rlist') + rlist = Node.void("rlist") root.add_child(rlist) links = self.data.local.user.get_links(self.game, self.version, userid) for link in links: rival_type = None - if link.type == 'sp_rival': - rival_type = '1' - elif link.type == 'dp_rival': - rival_type = '2' + if link.type == "sp_rival": + rival_type = "1" + elif link.type == "dp_rival": + rival_type = "2" else: # No business with this link type continue @@ -1085,298 +1183,337 @@ class IIDXTricoro(IIDXBase): continue other_play_stats = self.get_play_statistics(link.other_userid) - rival = Node.void('rival') + rival = Node.void("rival") rlist.add_child(rival) - rival.set_attribute('spdp', rival_type) - rival.set_attribute('id', str(other_profile.extid)) - rival.set_attribute('id_str', ID.format_extid(other_profile.extid)) - rival.set_attribute('djname', other_profile.get_str('name')) - rival.set_attribute('pid', str(other_profile.get_int('pid'))) - rival.set_attribute('sg', str(self.db_to_game_rank(other_profile.get_int(self.DAN_RANKING_SINGLE, -1), self.GAME_CLTYPE_SINGLE))) - rival.set_attribute('dg', str(self.db_to_game_rank(other_profile.get_int(self.DAN_RANKING_DOUBLE, -1), self.GAME_CLTYPE_DOUBLE))) - rival.set_attribute('sa', str(other_play_stats.get_int('single_dj_points'))) - rival.set_attribute('da', str(other_play_stats.get_int('double_dj_points'))) + rival.set_attribute("spdp", rival_type) + rival.set_attribute("id", str(other_profile.extid)) + rival.set_attribute("id_str", ID.format_extid(other_profile.extid)) + rival.set_attribute("djname", other_profile.get_str("name")) + rival.set_attribute("pid", str(other_profile.get_int("pid"))) + rival.set_attribute( + "sg", + str( + self.db_to_game_rank( + other_profile.get_int(self.DAN_RANKING_SINGLE, -1), + self.GAME_CLTYPE_SINGLE, + ) + ), + ) + rival.set_attribute( + "dg", + str( + self.db_to_game_rank( + other_profile.get_int(self.DAN_RANKING_DOUBLE, -1), + self.GAME_CLTYPE_DOUBLE, + ) + ), + ) + rival.set_attribute("sa", str(other_play_stats.get_int("single_dj_points"))) + rival.set_attribute("da", str(other_play_stats.get_int("double_dj_points"))) # If the user joined a particular shop, let the game know. - if 'shop_location' in other_profile: - shop_id = other_profile.get_int('shop_location') + if "shop_location" in other_profile: + shop_id = other_profile.get_int("shop_location") machine = self.get_machine_by_id(shop_id) if machine is not None: - shop = Node.void('shop') + shop = Node.void("shop") rival.add_child(shop) - shop.set_attribute('name', machine.name) + shop.set_attribute("name", machine.name) - qprodata = Node.void('qprodata') + qprodata = Node.void("qprodata") rival.add_child(qprodata) - qpro = other_profile.get_dict('qpro') - qprodata.set_attribute('head', str(qpro.get_int('head'))) - qprodata.set_attribute('hair', str(qpro.get_int('hair'))) - qprodata.set_attribute('face', str(qpro.get_int('face'))) - qprodata.set_attribute('body', str(qpro.get_int('body'))) - qprodata.set_attribute('hand', str(qpro.get_int('hand'))) + qpro = other_profile.get_dict("qpro") + qprodata.set_attribute("head", str(qpro.get_int("head"))) + qprodata.set_attribute("hair", str(qpro.get_int("hair"))) + qprodata.set_attribute("face", str(qpro.get_int("face"))) + qprodata.set_attribute("body", str(qpro.get_int("body"))) + qprodata.set_attribute("hand", str(qpro.get_int("hand"))) # If the user joined a particular shop, let the game know. - if 'shop_location' in profile: - shop_id = profile.get_int('shop_location') + if "shop_location" in profile: + shop_id = profile.get_int("shop_location") machine = self.get_machine_by_id(shop_id) if machine is not None: - join_shop = Node.void('join_shop') + join_shop = Node.void("join_shop") root.add_child(join_shop) - join_shop.set_attribute('joinflg', '1') - join_shop.set_attribute('join_cflg', '1') - join_shop.set_attribute('join_id', ID.format_machine_id(machine.id)) - join_shop.set_attribute('join_name', machine.name) + join_shop.set_attribute("joinflg", "1") + join_shop.set_attribute("join_cflg", "1") + join_shop.set_attribute("join_id", ID.format_machine_id(machine.id)) + join_shop.set_attribute("join_name", machine.name) # Step up mode - step_dict = profile.get_dict('step') - step = Node.void('step') + step_dict = profile.get_dict("step") + step = Node.void("step") root.add_child(step) - step.set_attribute('sp_ach', str(step_dict.get_int('sp_ach'))) - step.set_attribute('dp_ach', str(step_dict.get_int('dp_ach'))) - step.set_attribute('sp_hdpt', str(step_dict.get_int('sp_hdpt'))) - step.set_attribute('dp_hdpt', str(step_dict.get_int('dp_hdpt'))) - step.set_attribute('sp_level', str(step_dict.get_int('sp_level'))) - step.set_attribute('dp_level', str(step_dict.get_int('dp_level'))) - step.set_attribute('sp_round', str(step_dict.get_int('sp_round'))) - step.set_attribute('dp_round', str(step_dict.get_int('dp_round'))) - step.set_attribute('sp_mplay', str(step_dict.get_int('sp_mplay'))) - step.set_attribute('dp_mplay', str(step_dict.get_int('dp_mplay'))) - step.set_attribute('review', str(step_dict.get_int('review'))) - if 'stamp' in step_dict: - step.add_child(Node.binary('stamp', step_dict.get_bytes('stamp', bytes([0] * 36)))) - if 'help' in step_dict: - step.add_child(Node.binary('help', step_dict.get_bytes('help', bytes([0] * 6)))) + step.set_attribute("sp_ach", str(step_dict.get_int("sp_ach"))) + step.set_attribute("dp_ach", str(step_dict.get_int("dp_ach"))) + step.set_attribute("sp_hdpt", str(step_dict.get_int("sp_hdpt"))) + step.set_attribute("dp_hdpt", str(step_dict.get_int("dp_hdpt"))) + step.set_attribute("sp_level", str(step_dict.get_int("sp_level"))) + step.set_attribute("dp_level", str(step_dict.get_int("dp_level"))) + step.set_attribute("sp_round", str(step_dict.get_int("sp_round"))) + step.set_attribute("dp_round", str(step_dict.get_int("dp_round"))) + step.set_attribute("sp_mplay", str(step_dict.get_int("sp_mplay"))) + step.set_attribute("dp_mplay", str(step_dict.get_int("dp_mplay"))) + step.set_attribute("review", str(step_dict.get_int("review"))) + if "stamp" in step_dict: + step.add_child( + Node.binary("stamp", step_dict.get_bytes("stamp", bytes([0] * 36))) + ) + if "help" in step_dict: + step.add_child( + Node.binary("help", step_dict.get_bytes("help", bytes([0] * 6))) + ) # Daily recommendations - entry = self.data.local.game.get_time_sensitive_settings(self.game, self.version, 'dailies') + entry = self.data.local.game.get_time_sensitive_settings( + self.game, self.version, "dailies" + ) if entry is not None: - packinfo = Node.void('packinfo') + packinfo = Node.void("packinfo") root.add_child(packinfo) - pack_id = int(entry['start_time'] / 86400) - packinfo.set_attribute('pack_id', str(pack_id)) - packinfo.set_attribute('music_0', str(entry['music'][0])) - packinfo.set_attribute('music_1', str(entry['music'][1])) - packinfo.set_attribute('music_2', str(entry['music'][2])) + pack_id = int(entry["start_time"] / 86400) + packinfo.set_attribute("pack_id", str(pack_id)) + packinfo.set_attribute("music_0", str(entry["music"][0])) + packinfo.set_attribute("music_1", str(entry["music"][1])) + packinfo.set_attribute("music_2", str(entry["music"][2])) else: # No dailies :( pack_id = None # Tran medals and shit - achievements = Node.void('achievements') + achievements = Node.void("achievements") root.add_child(achievements) # Dailies if pack_id is None: - achievements.set_attribute('pack', '0') - achievements.set_attribute('pack_comp', '0') + achievements.set_attribute("pack", "0") + achievements.set_attribute("pack_comp", "0") else: - daily_played = self.data.local.user.get_achievement(self.game, self.version, userid, pack_id, 'daily') + daily_played = self.data.local.user.get_achievement( + self.game, self.version, userid, pack_id, "daily" + ) if daily_played is None: daily_played = ValidatedDict() - achievements.set_attribute('pack', str(daily_played.get_int('pack_flg'))) - achievements.set_attribute('pack_comp', str(daily_played.get_int('pack_comp'))) + achievements.set_attribute("pack", str(daily_played.get_int("pack_flg"))) + achievements.set_attribute( + "pack_comp", str(daily_played.get_int("pack_comp")) + ) # Weeklies - achievements.set_attribute('last_weekly', str(profile.get_int('last_weekly'))) - achievements.set_attribute('weekly_num', str(profile.get_int('weekly_num'))) + achievements.set_attribute("last_weekly", str(profile.get_int("last_weekly"))) + achievements.set_attribute("weekly_num", str(profile.get_int("weekly_num"))) # Prefecture visit flag - achievements.set_attribute('visit_flg', str(profile.get_int('visit_flg'))) + achievements.set_attribute("visit_flg", str(profile.get_int("visit_flg"))) # Number of rivals beaten - achievements.set_attribute('rival_crush', str(profile.get_int('rival_crush'))) + achievements.set_attribute("rival_crush", str(profile.get_int("rival_crush"))) # Tran medals - achievements.add_child(Node.s64_array('trophy', profile.get_int_array('trophy', 10))) + achievements.add_child( + Node.s64_array("trophy", profile.get_int_array("trophy", 10)) + ) # Link5 data - if 'link5' in profile: + if "link5" in profile: # Don't provide link5 if we haven't saved it, so the game can # initialize it properly. - link5_dict = profile.get_dict('link5') - link5 = Node.void('link5') + link5_dict = profile.get_dict("link5") + link5 = Node.void("link5") root.add_child(link5) for attr in [ - 'qpro', - 'glass', - 'treasure', # not saved - 'beautiful', - 'quaver', - 'castle', - 'flip', - 'titans', - 'exusia', - 'waxing', - 'sampling', - 'beachside', - 'cuvelia', - 'reunion', - 'bad', - 'turii', - 'anisakis', - 'second', - 'whydidyou', - 'china', - 'fallen', - 'broken', - 'summer', - 'sakura', - 'wuv', - 'survival', - 'thunder', - 'qproflg', # not saved - 'glassflg', # not saved - 'reflec_data', # not saved + "qpro", + "glass", + "treasure", # not saved + "beautiful", + "quaver", + "castle", + "flip", + "titans", + "exusia", + "waxing", + "sampling", + "beachside", + "cuvelia", + "reunion", + "bad", + "turii", + "anisakis", + "second", + "whydidyou", + "china", + "fallen", + "broken", + "summer", + "sakura", + "wuv", + "survival", + "thunder", + "qproflg", # not saved + "glassflg", # not saved + "reflec_data", # not saved ]: link5.set_attribute(attr, str(link5_dict.get_int(attr))) # Track deller, orbs and baron - commonboss = Node.void('commonboss') + commonboss = Node.void("commonboss") root.add_child(commonboss) - commonboss.set_attribute('deller', str(profile.get_int('deller'))) - commonboss.set_attribute('orb', str(profile.get_int('orbs'))) - commonboss.set_attribute('baron', str(profile.get_int('baron'))) + commonboss.set_attribute("deller", str(profile.get_int("deller"))) + commonboss.set_attribute("orb", str(profile.get_int("orbs"))) + commonboss.set_attribute("baron", str(profile.get_int("baron"))) return root - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() play_stats = self.get_play_statistics(userid) # Track play counts, DJ points and options - cltype = int(request.attribute('cltype')) + cltype = int(request.attribute("cltype")) if cltype == self.GAME_CLTYPE_SINGLE: - play_stats.increment_int('single_plays') - play_stats.replace_int('single_dj_points', int(request.attribute('achi'))) - newprofile.replace_int('sp_opt', int(request.attribute('opt'))) + play_stats.increment_int("single_plays") + play_stats.replace_int("single_dj_points", int(request.attribute("achi"))) + newprofile.replace_int("sp_opt", int(request.attribute("opt"))) if cltype == self.GAME_CLTYPE_DOUBLE: - play_stats.increment_int('double_plays') - play_stats.replace_int('double_dj_points', int(request.attribute('achi'))) - newprofile.replace_int('dp_opt', int(request.attribute('opt'))) - newprofile.replace_int('dp_opt2', int(request.attribute('opt2'))) + play_stats.increment_int("double_plays") + play_stats.replace_int("double_dj_points", int(request.attribute("achi"))) + newprofile.replace_int("dp_opt", int(request.attribute("opt"))) + newprofile.replace_int("dp_opt2", int(request.attribute("opt2"))) # Profile settings - newprofile.replace_int('gno', int(request.attribute('gno'))) - newprofile.replace_int('gpos', int(request.attribute('gpos'))) - newprofile.replace_int('timing', int(request.attribute('timing'))) - newprofile.replace_int('help', int(request.attribute('help'))) - newprofile.replace_int('sdhd', int(request.attribute('sdhd'))) - newprofile.replace_int('sdtype', int(request.attribute('sdtype'))) - newprofile.replace_float('notes', float(request.attribute('notes'))) - newprofile.replace_int('pase', int(request.attribute('pase'))) - newprofile.replace_int('judge', int(request.attribute('judge'))) - newprofile.replace_int('opstyle', int(request.attribute('opstyle'))) - newprofile.replace_float('hispeed', float(request.attribute('hispeed'))) - newprofile.replace_int('mode', int(request.attribute('mode'))) - newprofile.replace_int('pmode', int(request.attribute('pmode'))) - if 'lift' in request.attributes: - newprofile.replace_int('lift', int(request.attribute('lift'))) + newprofile.replace_int("gno", int(request.attribute("gno"))) + newprofile.replace_int("gpos", int(request.attribute("gpos"))) + newprofile.replace_int("timing", int(request.attribute("timing"))) + newprofile.replace_int("help", int(request.attribute("help"))) + newprofile.replace_int("sdhd", int(request.attribute("sdhd"))) + newprofile.replace_int("sdtype", int(request.attribute("sdtype"))) + newprofile.replace_float("notes", float(request.attribute("notes"))) + newprofile.replace_int("pase", int(request.attribute("pase"))) + newprofile.replace_int("judge", int(request.attribute("judge"))) + newprofile.replace_int("opstyle", int(request.attribute("opstyle"))) + newprofile.replace_float("hispeed", float(request.attribute("hispeed"))) + newprofile.replace_int("mode", int(request.attribute("mode"))) + newprofile.replace_int("pmode", int(request.attribute("pmode"))) + if "lift" in request.attributes: + newprofile.replace_int("lift", int(request.attribute("lift"))) # Update judge window adjustments per-machine - judge_dict = newprofile.get_dict('machine_judge_adjust') + judge_dict = newprofile.get_dict("machine_judge_adjust") machine_judge = judge_dict.get_dict(self.config.machine.pcbid) - machine_judge.replace_int('adj', int(request.attribute('judgeAdj'))) + machine_judge.replace_int("adj", int(request.attribute("judgeAdj"))) judge_dict.replace_dict(self.config.machine.pcbid, machine_judge) - newprofile.replace_dict('machine_judge_adjust', judge_dict) + newprofile.replace_dict("machine_judge_adjust", judge_dict) # Secret flags saving - secret = request.child('secret') + secret = request.child("secret") if secret is not None: - secret_dict = newprofile.get_dict('secret') - secret_dict.replace_int('flg1', secret.child_value('flg1')) - secret_dict.replace_int('flg2', secret.child_value('flg2')) - secret_dict.replace_int('flg3', secret.child_value('flg3')) - newprofile.replace_dict('secret', secret_dict) + secret_dict = newprofile.get_dict("secret") + secret_dict.replace_int("flg1", secret.child_value("flg1")) + secret_dict.replace_int("flg2", secret.child_value("flg2")) + secret_dict.replace_int("flg3", secret.child_value("flg3")) + newprofile.replace_dict("secret", secret_dict) # Basic achievements - achievements = request.child('achievements') + achievements = request.child("achievements") if achievements is not None: - newprofile.replace_int('visit_flg', int(achievements.attribute('visit_flg'))) - newprofile.replace_int('last_weekly', int(achievements.attribute('last_weekly'))) - newprofile.replace_int('weekly_num', int(achievements.attribute('weekly_num'))) + newprofile.replace_int( + "visit_flg", int(achievements.attribute("visit_flg")) + ) + newprofile.replace_int( + "last_weekly", int(achievements.attribute("last_weekly")) + ) + newprofile.replace_int( + "weekly_num", int(achievements.attribute("weekly_num")) + ) - pack_id = int(achievements.attribute('pack_id')) + pack_id = int(achievements.attribute("pack_id")) if pack_id > 0: self.data.local.user.put_achievement( self.game, self.version, userid, pack_id, - 'daily', + "daily", { - 'pack_flg': int(achievements.attribute('pack_flg')), - 'pack_comp': int(achievements.attribute('pack_comp')), + "pack_flg": int(achievements.attribute("pack_flg")), + "pack_comp": int(achievements.attribute("pack_comp")), }, ) - trophies = achievements.child('trophy') + trophies = achievements.child("trophy") if trophies is not None: # We only load the first 10 in profile load. - newprofile.replace_int_array('trophy', 10, trophies.value[:10]) + newprofile.replace_int_array("trophy", 10, trophies.value[:10]) # Deller and orb saving - commonboss = request.child('commonboss') + commonboss = request.child("commonboss") if commonboss is not None: - newprofile.replace_int('deller', newprofile.get_int('deller') + int(commonboss.attribute('deller'))) - orbs = newprofile.get_int('orbs') - orbs = orbs + int(commonboss.attribute('orb')) - newprofile.replace_int('orbs', orbs) + newprofile.replace_int( + "deller", + newprofile.get_int("deller") + int(commonboss.attribute("deller")), + ) + orbs = newprofile.get_int("orbs") + orbs = orbs + int(commonboss.attribute("orb")) + newprofile.replace_int("orbs", orbs) # Step-up mode - step = request.child('step') + step = request.child("step") if step is not None: - step_dict = newprofile.get_dict('step') + step_dict = newprofile.get_dict("step") if cltype == self.GAME_CLTYPE_SINGLE: - step_dict.replace_int('sp_ach', int(step.attribute('sp_ach'))) - step_dict.replace_int('sp_hdpt', int(step.attribute('sp_hdpt'))) - step_dict.replace_int('sp_level', int(step.attribute('sp_level'))) - step_dict.replace_int('sp_round', int(step.attribute('sp_round'))) - step_dict.replace_int('sp_mplay', int(step.attribute('sp_mplay'))) + step_dict.replace_int("sp_ach", int(step.attribute("sp_ach"))) + step_dict.replace_int("sp_hdpt", int(step.attribute("sp_hdpt"))) + step_dict.replace_int("sp_level", int(step.attribute("sp_level"))) + step_dict.replace_int("sp_round", int(step.attribute("sp_round"))) + step_dict.replace_int("sp_mplay", int(step.attribute("sp_mplay"))) else: - step_dict.replace_int('dp_ach', int(step.attribute('dp_ach'))) - step_dict.replace_int('dp_hdpt', int(step.attribute('dp_hdpt'))) - step_dict.replace_int('dp_level', int(step.attribute('dp_level'))) - step_dict.replace_int('dp_round', int(step.attribute('dp_round'))) - step_dict.replace_int('dp_mplay', int(step.attribute('dp_mplay'))) - step_dict.replace_int('review', int(step.attribute('review'))) + step_dict.replace_int("dp_ach", int(step.attribute("dp_ach"))) + step_dict.replace_int("dp_hdpt", int(step.attribute("dp_hdpt"))) + step_dict.replace_int("dp_level", int(step.attribute("dp_level"))) + step_dict.replace_int("dp_round", int(step.attribute("dp_round"))) + step_dict.replace_int("dp_mplay", int(step.attribute("dp_mplay"))) + step_dict.replace_int("review", int(step.attribute("review"))) - newprofile.replace_dict('step', step_dict) + newprofile.replace_dict("step", step_dict) # Link5 data - link5 = request.child('link5') + link5 = request.child("link5") if link5 is not None: - link5_dict = newprofile.get_dict('link5') + link5_dict = newprofile.get_dict("link5") for attr in [ - 'qpro', - 'glass', - 'beautiful', - 'quaver', - 'castle', - 'flip', - 'titans', - 'exusia', - 'waxing', - 'sampling', - 'beachside', - 'cuvelia', - 'reunion', - 'bad', - 'turii', - 'anisakis', - 'second', - 'whydidyou', - 'china', - 'fallen', - 'broken', - 'summer', - 'sakura', - 'wuv', - 'survival', - 'thunder', + "qpro", + "glass", + "beautiful", + "quaver", + "castle", + "flip", + "titans", + "exusia", + "waxing", + "sampling", + "beachside", + "cuvelia", + "reunion", + "bad", + "turii", + "anisakis", + "second", + "whydidyou", + "china", + "fallen", + "broken", + "summer", + "sakura", + "wuv", + "survival", + "thunder", ]: link5_dict.replace_int(attr, int(link5.attribute(attr))) - newprofile.replace_dict('link5', link5_dict) + newprofile.replace_dict("link5", link5_dict) # Keep track of play statistics across all mixes self.update_play_statistics(userid, play_stats) diff --git a/bemani/backend/jubeat/avenue.py b/bemani/backend/jubeat/avenue.py index 77778ff..32432b6 100644 --- a/bemani/backend/jubeat/avenue.py +++ b/bemani/backend/jubeat/avenue.py @@ -8,7 +8,7 @@ from bemani.common import VersionConstants class JubeatAvenue(JubeatBase): - name: str = 'Jubeat Avenue' + name: str = "Jubeat Avenue" version: int = VersionConstants.JUBEAT_AVENUE def previous_version(self) -> Optional[JubeatBase]: diff --git a/bemani/backend/jubeat/base.py b/bemani/backend/jubeat/base.py index acf4bc3..5eb2508 100644 --- a/bemani/backend/jubeat/base.py +++ b/bemani/backend/jubeat/base.py @@ -28,9 +28,13 @@ class JubeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): PLAY_MEDAL_FAILED: Final[int] = DBConstants.JUBEAT_PLAY_MEDAL_FAILED PLAY_MEDAL_CLEARED: Final[int] = DBConstants.JUBEAT_PLAY_MEDAL_CLEARED - PLAY_MEDAL_NEARLY_FULL_COMBO: Final[int] = DBConstants.JUBEAT_PLAY_MEDAL_NEARLY_FULL_COMBO + PLAY_MEDAL_NEARLY_FULL_COMBO: Final[ + int + ] = DBConstants.JUBEAT_PLAY_MEDAL_NEARLY_FULL_COMBO PLAY_MEDAL_FULL_COMBO: Final[int] = DBConstants.JUBEAT_PLAY_MEDAL_FULL_COMBO - PLAY_MEDAL_NEARLY_EXCELLENT: Final[int] = DBConstants.JUBEAT_PLAY_MEDAL_NEARLY_EXCELLENT + PLAY_MEDAL_NEARLY_EXCELLENT: Final[ + int + ] = DBConstants.JUBEAT_PLAY_MEDAL_NEARLY_EXCELLENT PLAY_MEDAL_EXCELLENT: Final[int] = DBConstants.JUBEAT_PLAY_MEDAL_EXCELLENT CHART_TYPE_BASIC: Final[int] = 0 @@ -42,7 +46,7 @@ class JubeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): def __init__(self, data: Data, config: Config, model: Model) -> None: super().__init__(data, config, model) - if model.rev == 'X' or model.rev == 'Y': + if model.rev == "X" or model.rev == "Y": self.omnimix = True else: self.omnimix = False @@ -53,7 +57,7 @@ class JubeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): return DBConstants.OMNIMIX_VERSION_BUMP + self.version return self.version - def previous_version(self) -> Optional['JubeatBase']: + def previous_version(self) -> Optional["JubeatBase"]: """ Returns the previous version of the game, based on this game. Should be overridden. @@ -69,8 +73,8 @@ class JubeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): userid - The user ID we are saving the profile for. profile - A dictionary that should be looked up later using get_profile. """ - if 'has_old_version' in profile: - del profile['has_old_version'] + if "has_old_version" in profile: + del profile["has_old_version"] super().put_profile(userid, profile) def format_profile(self, userid: UserID, profile: Profile) -> Node: @@ -78,16 +82,20 @@ class JubeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): Base handler for a profile. Given a userid and a profile dictionary, return a Node representing a profile. Should be overridden. """ - return Node.void('gametop') + return Node.void("gametop") - def format_scores(self, userid: UserID, profile: Profile, scores: List[Score]) -> Node: + def format_scores( + self, userid: UserID, profile: Profile, scores: List[Score] + ) -> Node: """ Base handler for a score list. Given a userid, profile and a score list, return a Node representing a score list. Should be overridden. """ - return Node.void('gametop') + return Node.void("gametop") - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: """ Base handler for profile parsing. Given a request and an old profile, return a new profile that's been updated with the contents of the request. @@ -113,7 +121,7 @@ class JubeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): # Now try to find out if the profile is new or old oldversion = self.previous_version() oldprofile = oldversion.get_profile(userid) - profile['has_old_version'] = oldprofile is not None + profile["has_old_version"] = oldprofile is not None # Now, return it return self.format_profile(userid, profile) @@ -127,7 +135,7 @@ class JubeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): return None if name is None: - name = 'なし' + name = "なし" # First, create and save the default profile userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -137,7 +145,7 @@ class JubeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): refid, 0, { - 'name': name, + "name": name, }, ) self.put_profile(userid, profile) @@ -145,7 +153,7 @@ class JubeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): # Now, reload and format the profile, looking up the has old version flag oldversion = self.previous_version() oldprofile = oldversion.get_profile(userid) - profile['has_old_version'] = oldprofile is not None + profile["has_old_version"] = oldprofile is not None return self.format_profile(userid, profile) @@ -158,7 +166,9 @@ class JubeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): return None userid = self.data.remote.user.from_extid(self.game, self.version, extid) - scores = self.data.remote.music.get_scores(self.game, self.music_version, userid) + scores = self.data.remote.music.get_scores( + self.game, self.music_version, userid + ) if scores is None: return None profile = self.get_profile(userid) @@ -175,9 +185,9 @@ class JubeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): points: int, medal: int, combo: int, - ghost: Optional[List[int]]=None, - stats: Optional[Dict[str, int]]=None, - music_rate: Optional[int]=None, + ghost: Optional[List[int]] = None, + stats: Optional[Dict[str, int]] = None, + music_rate: Optional[int] = None, ) -> None: """ Given various pieces of a score, update the user's high score and score @@ -220,38 +230,38 @@ class JubeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): scoredata = oldscore.data # Replace medal with highest value - scoredata.replace_int('medal', max(scoredata.get_int('medal'), medal)) - history.replace_int('medal', medal) + scoredata.replace_int("medal", max(scoredata.get_int("medal"), medal)) + history.replace_int("medal", medal) # Increment counters based on medal if medal == self.PLAY_MEDAL_CLEARED: - scoredata.increment_int('clear_count') + scoredata.increment_int("clear_count") if medal == self.PLAY_MEDAL_FULL_COMBO: - scoredata.increment_int('full_combo_count') + scoredata.increment_int("full_combo_count") if medal == self.PLAY_MEDAL_EXCELLENT: - scoredata.increment_int('excellent_count') + scoredata.increment_int("excellent_count") # If we have a combo, replace it - scoredata.replace_int('combo', max(scoredata.get_int('combo'), combo)) - history.replace_int('combo', combo) + scoredata.replace_int("combo", max(scoredata.get_int("combo"), combo)) + history.replace_int("combo", combo) if stats is not None: if raised: # We have stats, and there's a new high score, update the stats - scoredata.replace_dict('stats', stats) - history.replace_dict('stats', stats) + scoredata.replace_dict("stats", stats) + history.replace_dict("stats", stats) if ghost is not None: # Update the ghost regardless, but don't bother with it in history - scoredata.replace_int_array('ghost', len(ghost), ghost) + scoredata.replace_int_array("ghost", len(ghost), ghost) if music_rate is not None: if oldscore is not None: - if music_rate > oldscore.data.get_int('music_rate'): - scoredata.replace_int('music_rate', music_rate) + if music_rate > oldscore.data.get_int("music_rate"): + scoredata.replace_int("music_rate", music_rate) else: - scoredata.replace_int('music_rate', music_rate) - history.replace_int('music_rate', music_rate) + scoredata.replace_int("music_rate", music_rate) + history.replace_int("music_rate", music_rate) # Look up where this score was earned lid = self.get_machine_id() diff --git a/bemani/backend/jubeat/clan.py b/bemani/backend/jubeat/clan.py index f2461c9..959087a 100644 --- a/bemani/backend/jubeat/clan.py +++ b/bemani/backend/jubeat/clan.py @@ -30,7 +30,7 @@ class JubeatClan( JubeatBase, ): - name: str = 'Jubeat Clan' + name: str = "Jubeat Clan" version: int = VersionConstants.JUBEAT_CLAN JBOX_EMBLEM_NORMAL: Final[int] = 1 @@ -41,22 +41,22 @@ class JubeatClan( EVENTS: Dict[int, Dict[str, bool]] = { 5: { - 'enabled': False, + "enabled": False, }, 6: { - 'enabled': False, + "enabled": False, }, 15: { - 'enabled': True, + "enabled": True, }, 22: { - 'enabled': False, + "enabled": False, }, 23: { - 'enabled': False, + "enabled": False, }, 34: { - 'enabled': False, + "enabled": False, }, } @@ -84,122 +84,134 @@ class JubeatClan( return JubeatQubell(self.data, self.config, self.model) @classmethod - def run_scheduled_work(cls, data: Data, config: Dict[str, Any]) -> List[Tuple[str, Dict[str, Any]]]: + def run_scheduled_work( + cls, data: Data, config: Dict[str, Any] + ) -> List[Tuple[str, Dict[str, Any]]]: """ Insert daily FC challenges into the DB. """ events = [] - if data.local.network.should_schedule(cls.game, cls.version, 'fc_challenge', 'daily'): + if data.local.network.should_schedule( + cls.game, cls.version, "fc_challenge", "daily" + ): # Generate a new list of two FC challenge songs. Skip a particular song range since these are all a single song ID. # Jubeat Clan has an unlock event where you have to play different charts for the same song, and the charts are # loaded in based on the cabinet's prefecture. So, no matter where you are, you will only see one song within this # range, but it will be a different ID depending on the prefecture set in settings. This means its not safe to send # these song IDs, so we explicitly exclude them. - start_time, end_time = data.local.network.get_schedule_duration('daily') - all_songs = set(song.id for song in data.local.music.get_all_songs(cls.game, cls.version) if song.id not in cls.FIVE_PLAYS_UNLOCK_EVENT_SONG_IDS) + start_time, end_time = data.local.network.get_schedule_duration("daily") + all_songs = set( + song.id + for song in data.local.music.get_all_songs(cls.game, cls.version) + if song.id not in cls.FIVE_PLAYS_UNLOCK_EVENT_SONG_IDS + ) if len(all_songs) >= 2: daily_songs = random.sample(all_songs, 2) data.local.game.put_time_sensitive_settings( cls.game, cls.version, - 'fc_challenge', + "fc_challenge", { - 'start_time': start_time, - 'end_time': end_time, - 'today': daily_songs[0], - 'whim': daily_songs[1], + "start_time": start_time, + "end_time": end_time, + "today": daily_songs[0], + "whim": daily_songs[1], }, ) - events.append(( - 'jubeat_fc_challenge_charts', - { - 'version': cls.version, - 'today': daily_songs[0], - 'whim': daily_songs[1], - }, - )) + events.append( + ( + "jubeat_fc_challenge_charts", + { + "version": cls.version, + "today": daily_songs[0], + "whim": daily_songs[1], + }, + ) + ) # Mark that we did some actual work here. - data.local.network.mark_scheduled(cls.game, cls.version, 'fc_challenge', 'daily') + data.local.network.mark_scheduled( + cls.game, cls.version, "fc_challenge", "daily" + ) return events def __get_course_list(self) -> List[Dict[str, Any]]: return [ # Papricapcap courses { - 'id': 1, - 'name': 'Thank You Merry Christmas', - 'course_type': self.COURSE_TYPE_TIME_BASED, - 'end_time': Time.end_of_this_week() + Time.SECONDS_IN_WEEK, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 3, - 'score': 700000, - 'music': [ + "id": 1, + "name": "Thank You Merry Christmas", + "course_type": self.COURSE_TYPE_TIME_BASED, + "end_time": Time.end_of_this_week() + Time.SECONDS_IN_WEEK, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 3, + "score": 700000, + "music": [ [(50000077, 0), (50000077, 1), (50000077, 2)], [(80000080, 0), (80000080, 1), (80000080, 2)], [(50000278, 0), (50000278, 1), (50000278, 2)], ], }, { - 'id': 2, - 'name': 'はじめての山道', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 1, - 'score': 700000, - 'music': [ + "id": 2, + "name": "はじめての山道", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 1, + "score": 700000, + "music": [ [(20000002, 0), (20000022, 0), (30000108, 0)], [(70000035, 0), (70000069, 0), (80000020, 0)], [(50000116, 0), (50000120, 0), (50000383, 0)], ], }, { - 'id': 3, - 'name': 'NOBOLOT検定 第1の山', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 1, - 'score': 700000, - 'music': [ + "id": 3, + "name": "NOBOLOT検定 第1の山", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 1, + "score": 700000, + "music": [ [(20000109, 0), (50000218, 0), (60000100, 0)], [(50000228, 0), (70000125, 0)], [(70000109, 0)], ], }, { - 'id': 4, - 'name': 'アニメハイキング', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 2, - 'score': 750000, - 'music': [ + "id": 4, + "name": "アニメハイキング", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 2, + "score": 750000, + "music": [ [(70000028, 0)], [(70000030, 0)], [(80001009, 0)], ], }, { - 'id': 5, - 'name': 'しりとり山', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 3, - 'score': 750000, - 'music': [ + "id": 5, + "name": "しりとり山", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 3, + "score": 750000, + "music": [ [(10000068, 0), (50000089, 0), (60000078, 0)], [(50000059, 0), (50000147, 0), (50000367, 0)], [(50000202, 0), (70000144, 0), (70000156, 0)], ], }, { - 'id': 6, - 'name': 'NOBOLOT検定 第2の山', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 3, - 'score': 800000, - 'music': [ + "id": 6, + "name": "NOBOLOT検定 第2の山", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 3, + "score": 800000, + "music": [ [(50000268, 0), (70000039, 0), (70000160, 0)], [(60000080, 1), (80000014, 0)], [(60000053, 0)], @@ -207,79 +219,79 @@ class JubeatClan( }, # Harapenya-na courses { - 'id': 11, - 'name': 'おためし!い~あみゅちゃん', - 'course_type': self.COURSE_TYPE_TIME_BASED, - 'end_time': Time.end_of_this_week() + Time.SECONDS_IN_WEEK, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 4, - 'score': 700000, - 'music': [ + "id": 11, + "name": "おためし!い~あみゅちゃん", + "course_type": self.COURSE_TYPE_TIME_BASED, + "end_time": Time.end_of_this_week() + Time.SECONDS_IN_WEEK, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 4, + "score": 700000, + "music": [ [(50000207, 0), (50000207, 1), (50000207, 2)], [(50000111, 0), (50000111, 1), (50000111, 2)], [(60000009, 0), (60000009, 1), (60000009, 2)], ], }, { - 'id': 12, - 'name': 'NOBOLOT検定 第3の山', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 4, - 'score': 850000, - 'music': [ + "id": 12, + "name": "NOBOLOT検定 第3の山", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 4, + "score": 850000, + "music": [ [(40000110, 1), (70000059, 1), (70000131, 1)], [(30000004, 1), (80000035, 1)], [(40000051, 1)], ], }, { - 'id': 13, - 'name': '頂上から見えるお月様', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 5, - 'score': 850000, - 'music': [ + "id": 13, + "name": "頂上から見えるお月様", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 5, + "score": 850000, + "music": [ [(50000245, 1)], [(60000051, 1)], [(80001011, 1)], ], }, { - 'id': 14, - 'name': 'ヒッチハイクでGO!GO!', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 5, - 'score': 850000, - 'music': [ + "id": 14, + "name": "ヒッチハイクでGO!GO!", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 5, + "score": 850000, + "music": [ [(10000053, 1), (80000038, 1)], [(30000123, 1), (50000086, 1), (70000119, 1)], [(50000196, 1), (60000006, 1), (70000153, 1)], ], }, { - 'id': 15, - 'name': '今日の一文字', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 6, - 'score': 2600000, - 'music': [ + "id": 15, + "name": "今日の一文字", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 6, + "score": 2600000, + "music": [ [(50000071, 1)], [(40000053, 1)], [(70000107, 1)], ], }, { - 'id': 16, - 'name': 'NOBOLOT検定 第4の山', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 6, - 'score': 2650000, - 'music': [ + "id": 16, + "name": "NOBOLOT検定 第4の山", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 6, + "score": 2650000, + "music": [ [(50000085, 2), (50000176, 2), (70000055, 2)], [(50000157, 2), (60001008, 2)], [(10000068, 2)], @@ -287,79 +299,79 @@ class JubeatClan( }, # Tillhorn courses { - 'id': 21, - 'name': 'ちくわの山', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 7, - 'score': 870000, - 'music': [ + "id": 21, + "name": "ちくわの山", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 7, + "score": 870000, + "music": [ [(70000099, 2)], [(50000282, 2), (60000106, 2), (80000041, 2)], [(50000234, 2)], ], }, { - 'id': 22, - 'name': 'NOBOLOT検定 第5の山', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 7, - 'score': 2650000, - 'music': [ + "id": 22, + "name": "NOBOLOT検定 第5の山", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 7, + "score": 2650000, + "music": [ [(50000233, 2), (50000242, 1), (80000032, 2)], [(60000027, 2), (60000045, 2)], [(20000038, 2)], ], }, { - 'id': 23, - 'name': '初めてのHARD MODE', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'hard': True, - 'difficulty': 8, - 'score': 835000, - 'music': [ + "id": 23, + "name": "初めてのHARD MODE", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "hard": True, + "difficulty": 8, + "score": 835000, + "music": [ [(50000247, 2)], [(70000071, 2)], [(20000042, 2)], ], }, { - 'id': 24, - 'name': '雪山の上のお姫様', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 8, - 'score': 2700000, - 'music': [ + "id": 24, + "name": "雪山の上のお姫様", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 8, + "score": 2700000, + "music": [ [(50000101, 2)], [(50000119, 2), (50000174, 2), (60000009, 2)], [(80001010, 2)], ], }, { - 'id': 25, - 'name': 'なが~い山', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 9, - 'score': 2800000, - 'music': [ + "id": 25, + "name": "なが~い山", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 9, + "score": 2800000, + "music": [ [(70000170, 2), (80000013, 2)], [(70000161, 2), (80000057, 2)], [(80000043, 2)], ], }, { - 'id': 26, - 'name': 'NOBOLOT検定 第6の山', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 9, - 'score': 2750000, - 'music': [ + "id": 26, + "name": "NOBOLOT検定 第6の山", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 9, + "score": 2750000, + "music": [ [(50000034, 2), (50000252, 2), (50000347, 2)], [(70000117, 2), (70000138, 2)], [(50000078, 2)], @@ -367,93 +379,93 @@ class JubeatClan( }, # Bahaneroy courses { - 'id': 31, - 'name': '挑戦!い~あみゅちゃん', - 'course_type': self.COURSE_TYPE_TIME_BASED, - 'end_time': Time.end_of_this_week() + Time.SECONDS_IN_WEEK, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 12, - 'score': 2823829, - 'music': [ + "id": 31, + "name": "挑戦!い~あみゅちゃん", + "course_type": self.COURSE_TYPE_TIME_BASED, + "end_time": Time.end_of_this_week() + Time.SECONDS_IN_WEEK, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 12, + "score": 2823829, + "music": [ [(50000207, 2)], [(50000111, 2)], [(60001006, 2)], ], }, { - 'id': 32, - 'name': '更なる高みを目指して', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 10, - 'score': 920000, - 'music': [ + "id": 32, + "name": "更なる高みを目指して", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 10, + "score": 920000, + "music": [ [(50000210, 2)], [(50000122, 2)], [(70000022, 2)], ], }, { - 'id': 33, - 'name': 'NOBOLOT検定 第7の山', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 10, - 'score': 2800000, - 'music': [ + "id": 33, + "name": "NOBOLOT検定 第7の山", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 10, + "score": 2800000, + "music": [ [(60000059, 2), (60000079, 2), (70000006, 2)], [(50000060, 2), (50000127, 2)], [(60000073, 2)], ], }, { - 'id': 34, - 'name': '崖っぷち! スリーチャレンジ!', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_HAZARD, - 'hazard_type': self.COURSE_HAZARD_FC3, - 'difficulty': 11, - 'music': [ + "id": 34, + "name": "崖っぷち! スリーチャレンジ!", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_HAZARD, + "hazard_type": self.COURSE_HAZARD_FC3, + "difficulty": 11, + "music": [ [(10000036, 2), (30000049, 2), (50000172, 2)], [(30000044, 2), (40000044, 2), (60000028, 2)], [(60000074, 2)], ], }, { - 'id': 35, - 'name': '芽吹いて咲いて', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 11, - 'score': 2800000, - 'music': [ + "id": 35, + "name": "芽吹いて咲いて", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 11, + "score": 2800000, + "music": [ [(60001003, 2)], [(70000097, 2)], [(80001013, 2)], ], }, { - 'id': 36, - 'name': '1! 2! Party Night!', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'hard': True, - 'difficulty': 12, - 'score': 2800000, - 'music': [ + "id": 36, + "name": "1! 2! Party Night!", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "hard": True, + "difficulty": 12, + "score": 2800000, + "music": [ [(70000174, 2)], [(60000081, 2)], [(30000048, 2)], ], }, { - 'id': 37, - 'name': 'NOBOLOT検定 第8の山', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 12, - 'score': 2820000, - 'music': [ + "id": 37, + "name": "NOBOLOT検定 第8の山", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 12, + "score": 2820000, + "music": [ [(50000124, 2)], [(50000291, 2)], [(60000065, 2)], @@ -461,113 +473,113 @@ class JubeatClan( }, # Jolokili courses { - 'id': 41, - 'name': 'The 7th KAC 1st Stage 個人部門', - 'course_type': self.COURSE_TYPE_TIME_BASED, - 'end_time': Time.end_of_this_week() + Time.SECONDS_IN_WEEK, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'hard': True, - 'difficulty': 13, - 'score': 700000, - 'music': [ + "id": 41, + "name": "The 7th KAC 1st Stage 個人部門", + "course_type": self.COURSE_TYPE_TIME_BASED, + "end_time": Time.end_of_this_week() + Time.SECONDS_IN_WEEK, + "clear_type": self.COURSE_CLEAR_SCORE, + "hard": True, + "difficulty": 13, + "score": 700000, + "music": [ [(80000076, 2)], [(80000025, 2)], [(60000073, 2)], ], }, { - 'id': 42, - 'name': 'The 7th KAC 2nd Stage 個人部門', - 'course_type': self.COURSE_TYPE_TIME_BASED, - 'end_time': Time.end_of_this_week() + Time.SECONDS_IN_WEEK, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'hard': True, - 'difficulty': 13, - 'score': 700000, - 'music': [ + "id": 42, + "name": "The 7th KAC 2nd Stage 個人部門", + "course_type": self.COURSE_TYPE_TIME_BASED, + "end_time": Time.end_of_this_week() + Time.SECONDS_IN_WEEK, + "clear_type": self.COURSE_CLEAR_SCORE, + "hard": True, + "difficulty": 13, + "score": 700000, + "music": [ [(80000081, 2)], [(70000145, 2)], [(80001013, 2)], ], }, { - 'id': 43, - 'name': 'The 7th KAC 団体部門', - 'course_type': self.COURSE_TYPE_TIME_BASED, - 'end_time': Time.end_of_this_week() + Time.SECONDS_IN_WEEK, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'hard': True, - 'difficulty': 13, - 'score': 700000, - 'music': [ + "id": 43, + "name": "The 7th KAC 団体部門", + "course_type": self.COURSE_TYPE_TIME_BASED, + "end_time": Time.end_of_this_week() + Time.SECONDS_IN_WEEK, + "clear_type": self.COURSE_CLEAR_SCORE, + "hard": True, + "difficulty": 13, + "score": 700000, + "music": [ [(70000162, 2)], [(70000134, 2)], [(70000173, 1)], ], }, { - 'id': 44, - 'name': 'ハードモード de ホームラン?!', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'hard': True, - 'difficulty': 13, - 'score': 2750000, - 'music': [ + "id": 44, + "name": "ハードモード de ホームラン?!", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "hard": True, + "difficulty": 13, + "score": 2750000, + "music": [ [(50000259, 2)], [(50000255, 2)], [(50000266, 2)], ], }, { - 'id': 45, - 'name': 'NOBOLOT検定 第9の山', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 13, - 'score': 2830000, - 'music': [ + "id": 45, + "name": "NOBOLOT検定 第9の山", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 13, + "score": 2830000, + "music": [ [(50000022, 2)], [(50000023, 2)], [(50000323, 2)], ], }, { - 'id': 46, - 'name': '崖っぷちスリーチャレンジ!その2', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_HAZARD, - 'hazard_type': self.COURSE_HAZARD_EXC3, - 'difficulty': 14, - 'music': [ + "id": 46, + "name": "崖っぷちスリーチャレンジ!その2", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_HAZARD, + "hazard_type": self.COURSE_HAZARD_EXC3, + "difficulty": 14, + "music": [ [(50000024, 2), (50000160, 2), (70000065, 2)], [(30000122, 2), (50000178, 2), (50000383, 2)], [(50000122, 2), (50000261, 2), (80000010, 2)], ], }, { - 'id': 47, - 'name': 'もう一つの姿を求めて', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'hard': True, - 'difficulty': 14, - 'score': 920000, - 'music': [ + "id": 47, + "name": "もう一つの姿を求めて", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "hard": True, + "difficulty": 14, + "score": 920000, + "music": [ [(60001009, 2)], [(80001006, 2)], [(80001015, 2)], ], }, { - 'id': 48, - 'name': 'NOBOLOT検定 第10の山', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'hard': True, - 'difficulty': 14, - 'score': 2820000, - 'music': [ + "id": 48, + "name": "NOBOLOT検定 第10の山", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "hard": True, + "difficulty": 14, + "score": 2820000, + "music": [ [(50000202, 2), (50000203, 2), (70000108, 2)], [(40000046, 2), (40000057, 2)], [(50000134, 2)], @@ -575,69 +587,69 @@ class JubeatClan( }, # Calorest courses { - 'id': 51, - 'name': '流れに身を任せて', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'hard': True, - 'difficulty': 15, - 'score': 2850000, - 'music': [ + "id": 51, + "name": "流れに身を任せて", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "hard": True, + "difficulty": 15, + "score": 2850000, + "music": [ [(60000001, 2)], [(80000022, 2)], [(50000108, 2)], ], }, { - 'id': 52, - 'name': '【挑戦】NOBOLOT検定 神の山', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'hard': True, - 'difficulty': 15, - 'score': 2850000, - 'music': [ + "id": 52, + "name": "【挑戦】NOBOLOT検定 神の山", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "hard": True, + "difficulty": 15, + "score": 2850000, + "music": [ [(40000057, 2)], [(60000076, 2)], [(50000102, 2)], ], }, { - 'id': 53, - 'name': '伝説の伝導師の山', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'hard': True, - 'difficulty': 16, - 'score': 2960000, - 'music': [ + "id": 53, + "name": "伝説の伝導師の山", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "hard": True, + "difficulty": 16, + "score": 2960000, + "music": [ [(80000028, 2)], [(80000023, 2)], [(80000087, 2)], ], }, { - 'id': 54, - 'name': 'EXCELLENT MASTER', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_HAZARD, - 'hazard_type': self.COURSE_HAZARD_EXC1, - 'difficulty': 16, - 'music': [ + "id": 54, + "name": "EXCELLENT MASTER", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_HAZARD, + "hazard_type": self.COURSE_HAZARD_EXC1, + "difficulty": 16, + "music": [ [(20000125, 2), (50000330, 2), (40000060, 2)], [(30000127, 2), (50000206, 2), (50000253, 2)], [(70000011, 2)], ], }, { - 'id': 55, - 'name': '【挑戦】NOBOLOT検定 英雄の山', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'hard': True, - 'difficulty': 16, - 'score': 2980000, - 'music': [ + "id": 55, + "name": "【挑戦】NOBOLOT検定 英雄の山", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "hard": True, + "difficulty": 16, + "score": 2980000, + "music": [ [(50000100, 2)], [(70000110, 2)], [(50000208, 2)], @@ -646,16 +658,16 @@ class JubeatClan( ] def __get_global_info(self) -> Node: - info = Node.void('info') + info = Node.void("info") # Event info. - event_info = Node.void('event_info') + event_info = Node.void("event_info") info.add_child(event_info) for event in self.EVENTS: - evt = Node.void('event') + evt = Node.void("event") event_info.add_child(evt) - evt.set_attribute('type', str(event)) - evt.add_child(Node.u8('state', 1 if self.EVENTS[event]['enabled'] else 0)) + evt.set_attribute("type", str(event)) + evt.add_child(Node.u8("state", 1 if self.EVENTS[event]["enabled"] else 0)) # Each of the following two sections should have zero or more child nodes (no # particular name) which look like the following: @@ -665,137 +677,365 @@ class JubeatClan( # end time? # # Share music? - share_music = Node.void('share_music') + share_music = Node.void("share_music") info.add_child(share_music) - genre_def_music = Node.void('genre_def_music') + genre_def_music = Node.void("genre_def_music") info.add_child(genre_def_music) - info.add_child(Node.s32_array( - 'black_jacket_list', - [ - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - ], - )) + info.add_child( + Node.s32_array( + "black_jacket_list", + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + ) + ) # Some sort of music DB whitelist - info.add_child(Node.s32_array( - 'white_music_list', - [ - -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, -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, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - ], - )) + info.add_child( + Node.s32_array( + "white_music_list", + [ + -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, + -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, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], + ) + ) - info.add_child(Node.s32_array( - 'white_marker_list', - [ - -1, -1, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - ], - )) + info.add_child( + Node.s32_array( + "white_marker_list", + [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], + ) + ) - info.add_child(Node.s32_array( - 'white_theme_list', - [ - -1, -1, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - ], - )) + info.add_child( + Node.s32_array( + "white_theme_list", + [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], + ) + ) - info.add_child(Node.s32_array( - 'open_music_list', - [ - -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, -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, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - ], - )) + info.add_child( + Node.s32_array( + "open_music_list", + [ + -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, + -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, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], + ) + ) - info.add_child(Node.s32_array( - 'shareable_music_list', - [ - -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, -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, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - ], - )) + info.add_child( + Node.s32_array( + "shareable_music_list", + [ + -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, + -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, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], + ) + ) - jbox = Node.void('jbox') + jbox = Node.void("jbox") info.add_child(jbox) - jbox.add_child(Node.s32('point', 0)) - emblem = Node.void('emblem') + jbox.add_child(Node.s32("point", 0)) + emblem = Node.void("emblem") jbox.add_child(emblem) - normal = Node.void('normal') + normal = Node.void("normal") emblem.add_child(normal) - premium = Node.void('premium') + premium = Node.void("premium") emblem.add_child(premium) - normal.add_child(Node.s16('index', 2)) - premium.add_child(Node.s16('index', 1)) + normal.add_child(Node.s16("index", 2)) + premium.add_child(Node.s16("index", 1)) - born = Node.void('born') + born = Node.void("born") info.add_child(born) - born.add_child(Node.s8('status', 0)) - born.add_child(Node.s16('year', 0)) + born.add_child(Node.s8("status", 0)) + born.add_child(Node.s16("year", 0)) # Collection list values should look like: # @@ -803,105 +1043,136 @@ class JubeatClan( # start time? # end time? # - collection = Node.void('collection') + collection = Node.void("collection") info.add_child(collection) - collection.add_child(Node.void('rating_s')) + collection.add_child(Node.void("rating_s")) - expert_option = Node.void('expert_option') + expert_option = Node.void("expert_option") info.add_child(expert_option) - expert_option.add_child(Node.bool('is_available', True)) + expert_option.add_child(Node.bool("is_available", True)) - all_music_matching = Node.void('all_music_matching') + all_music_matching = Node.void("all_music_matching") info.add_child(all_music_matching) - all_music_matching.add_child(Node.bool('is_available', True)) + all_music_matching.add_child(Node.bool("is_available", True)) - team = Node.void('team') + team = Node.void("team") all_music_matching.add_child(team) - team.add_child(Node.s32('default_flag', 0)) - team.add_child(Node.s32('redbelk_flag', 0)) - team.add_child(Node.s32('cyanttle_flag', 0)) - team.add_child(Node.s32('greenesia_flag', 0)) - team.add_child(Node.s32('plumpark_flag', 0)) + team.add_child(Node.s32("default_flag", 0)) + team.add_child(Node.s32("redbelk_flag", 0)) + team.add_child(Node.s32("cyanttle_flag", 0)) + team.add_child(Node.s32("greenesia_flag", 0)) + team.add_child(Node.s32("plumpark_flag", 0)) - question_list = Node.void('question_list') + question_list = Node.void("question_list") info.add_child(question_list) - drop_list = Node.void('drop_list') + drop_list = Node.void("drop_list") info.add_child(drop_list) - daily_bonus_list = Node.void('daily_bonus_list') + daily_bonus_list = Node.void("daily_bonus_list") info.add_child(daily_bonus_list) - department = Node.void('department') + department = Node.void("department") info.add_child(department) - department.add_child(Node.void('pack_list')) + department.add_child(Node.void("pack_list")) # Set up NOBOLOT course requirements - clan_course_list = Node.void('clan_course_list') + clan_course_list = Node.void("clan_course_list") info.add_child(clan_course_list) valid_courses: Set[int] = set() for course in self.__get_course_list(): - if course['id'] < 1: - raise Exception(f"Invalid course ID {course['id']} found in course list!") - if course['id'] in valid_courses: + if course["id"] < 1: + raise Exception( + f"Invalid course ID {course['id']} found in course list!" + ) + if course["id"] in valid_courses: raise Exception(f"Duplicate ID {course['id']} found in course list!") - if course['clear_type'] == self.COURSE_CLEAR_HAZARD and 'hazard_type' not in course: + if ( + course["clear_type"] == self.COURSE_CLEAR_HAZARD + and "hazard_type" not in course + ): raise Exception(f"Need 'hazard_type' set in course {course['id']}!") - if course['course_type'] == self.COURSE_TYPE_TIME_BASED and 'end_time' not in course: + if ( + course["course_type"] == self.COURSE_TYPE_TIME_BASED + and "end_time" not in course + ): raise Exception(f"Need 'end_time' set in course {course['id']}!") - if course['clear_type'] in [self.COURSE_CLEAR_SCORE, self.COURSE_CLEAR_COMBINED_SCORE] and 'score' not in course: + if ( + course["clear_type"] + in [self.COURSE_CLEAR_SCORE, self.COURSE_CLEAR_COMBINED_SCORE] + and "score" not in course + ): raise Exception(f"Need 'score' set in course {course['id']}!") - if course['clear_type'] == self.COURSE_CLEAR_SCORE and course['score'] > 1000000: + if ( + course["clear_type"] == self.COURSE_CLEAR_SCORE + and course["score"] > 1000000 + ): raise Exception(f"Invalid per-coure score in course {course['id']}!") - if course['clear_type'] == self.COURSE_CLEAR_COMBINED_SCORE and course['score'] <= 1000000: + if ( + course["clear_type"] == self.COURSE_CLEAR_COMBINED_SCORE + and course["score"] <= 1000000 + ): raise Exception(f"Invalid combined score in course {course['id']}!") - valid_courses.add(course['id']) + valid_courses.add(course["id"]) # Basics - clan_course = Node.void('clan_course') + clan_course = Node.void("clan_course") clan_course_list.add_child(clan_course) - clan_course.set_attribute('release_code', '2017062600') - clan_course.set_attribute('version_id', '0') - clan_course.set_attribute('id', str(course['id'])) - clan_course.set_attribute('course_type', str(course['course_type'])) - clan_course.add_child(Node.s32('difficulty', course['difficulty'])) - clan_course.add_child(Node.u64('etime', (course['end_time'] if 'end_time' in course else 0) * 1000)) - clan_course.add_child(Node.string('name', course['name'])) + clan_course.set_attribute("release_code", "2017062600") + clan_course.set_attribute("version_id", "0") + clan_course.set_attribute("id", str(course["id"])) + clan_course.set_attribute("course_type", str(course["course_type"])) + clan_course.add_child(Node.s32("difficulty", course["difficulty"])) + clan_course.add_child( + Node.u64( + "etime", (course["end_time"] if "end_time" in course else 0) * 1000 + ) + ) + clan_course.add_child(Node.string("name", course["name"])) # List of included songs - tune_list = Node.void('tune_list') + tune_list = Node.void("tune_list") clan_course.add_child(tune_list) - for order, charts in enumerate(course['music']): - tune = Node.void('tune') + for order, charts in enumerate(course["music"]): + tune = Node.void("tune") tune_list.add_child(tune) - tune.set_attribute('no', str(order + 1)) + tune.set_attribute("no", str(order + 1)) - seq_list = Node.void('seq_list') + seq_list = Node.void("seq_list") tune.add_child(seq_list) for songid, chart in charts: - seq = Node.void('seq') + seq = Node.void("seq") seq_list.add_child(seq) - seq.add_child(Node.s32('music_id', songid)) - seq.add_child(Node.s32('difficulty', chart)) - seq.add_child(Node.bool('is_secret', False)) + seq.add_child(Node.s32("music_id", songid)) + seq.add_child(Node.s32("difficulty", chart)) + seq.add_child(Node.bool("is_secret", False)) # Clear criteria - clear = Node.void('clear') + clear = Node.void("clear") clan_course.add_child(clear) - ex_option = Node.void('ex_option') + ex_option = Node.void("ex_option") clear.add_child(ex_option) - ex_option.add_child(Node.bool('is_hard', course['hard'] if 'hard' in course else False)) - ex_option.add_child(Node.s32('hazard_type', course['hazard_type'] if 'hazard_type' in course else 0)) - clear.set_attribute('type', str(course['clear_type'])) - clear.add_child(Node.s32('score', course['score'] if 'score' in course else 0)) + ex_option.add_child( + Node.bool("is_hard", course["hard"] if "hard" in course else False) + ) + ex_option.add_child( + Node.s32( + "hazard_type", + course["hazard_type"] if "hazard_type" in course else 0, + ) + ) + clear.set_attribute("type", str(course["clear_type"])) + clear.add_child( + Node.s32("score", course["score"] if "score" in course else 0) + ) - reward_list = Node.void('reward_list') + reward_list = Node.void("reward_list") clear.add_child(reward_list) # Set up NOBOLOT category display - category_list = Node.void('category_list') + category_list = Node.void("category_list") clan_course_list.add_child(category_list) # Each category has one of the following nodes @@ -914,156 +1185,208 @@ class JubeatClan( (15, 16), ] for categoryid, (min_level, max_level) in enumerate(categories): - category = Node.void('category') + category = Node.void("category") category_list.add_child(category) - category.set_attribute('id', str(categoryid + 1)) - category.add_child(Node.bool('is_secret', False)) - category.add_child(Node.s32('level_min', min_level)) - category.add_child(Node.s32('level_max', max_level)) + category.set_attribute("id", str(categoryid + 1)) + category.add_child(Node.bool("is_secret", False)) + category.add_child(Node.s32("level_min", min_level)) + category.add_child(Node.s32("level_max", max_level)) return info def handle_shopinfo_regist_request(self, request: Node) -> Node: # Update the name of this cab for admin purposes - self.update_machine_name(request.child_value('shop/name')) + self.update_machine_name(request.child_value("shop/name")) - shopinfo = Node.void('shopinfo') + shopinfo = Node.void("shopinfo") - data = Node.void('data') + data = Node.void("data") shopinfo.add_child(data) - data.add_child(Node.u32('cabid', 1)) - data.add_child(Node.string('locationid', 'nowhere')) - data.add_child(Node.u8('tax_phase', 1)) + data.add_child(Node.u32("cabid", 1)) + data.add_child(Node.string("locationid", "nowhere")) + data.add_child(Node.u8("tax_phase", 1)) - facility = Node.void('facility') + facility = Node.void("facility") data.add_child(facility) - facility.add_child(Node.u32('exist', 1)) + facility.add_child(Node.u32("exist", 1)) data.add_child(self.__get_global_info()) return shopinfo def handle_demodata_get_info_request(self, request: Node) -> Node: - root = Node.void('demodata') - data = Node.void('data') + root = Node.void("demodata") + data = Node.void("data") root.add_child(data) - info = Node.void('info') + info = Node.void("info") data.add_child(info) - info.add_child(Node.s32_array( - 'black_jacket_list', - [ - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - ], - )) + info.add_child( + Node.s32_array( + "black_jacket_list", + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + ) + ) return root def handle_demodata_get_jbox_list_request(self, request: Node) -> Node: - root = Node.void('demodata') + root = Node.void("demodata") return root def handle_jbox_get_agreement_request(self, request: Node) -> Node: - root = Node.void('jbox') - root.add_child(Node.bool('is_agreement', True)) + root = Node.void("jbox") + root.add_child(Node.bool("is_agreement", True)) return root def handle_jbox_get_list_request(self, request: Node) -> Node: - root = Node.void('jbox') - root.add_child(Node.void('selection_list')) + root = Node.void("jbox") + root.add_child(Node.void("selection_list")) return root def handle_recommend_get_recommend_request(self, request: Node) -> Node: - recommend = Node.void('recommend') - data = Node.void('data') + recommend = Node.void("recommend") + data = Node.void("data") recommend.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - music_list = Node.void('music_list') + music_list = Node.void("music_list") player.add_child(music_list) # TODO: Might be a way to figure out who plays what song and then offer # recommendations based on that. There should be 12 songs returned here. recommended_songs: List[Song] = [] for i, song in enumerate(recommended_songs): - music = Node.void('music') + music = Node.void("music") music_list.add_child(music) - music.set_attribute('order', str(i)) - music.add_child(Node.s32('music_id', song.id)) - music.add_child(Node.s8('seq', song.chart)) + music.set_attribute("order", str(i)) + music.add_child(Node.s32("music_id", song.id)) + music.add_child(Node.s8("seq", song.chart)) return recommend def handle_gametop_get_info_request(self, request: Node) -> Node: - root = Node.void('gametop') - data = Node.void('data') + root = Node.void("gametop") + data = Node.void("data") root.add_child(data) data.add_child(self.__get_global_info()) return root def handle_gametop_regist_request(self, request: Node) -> Node: - data = request.child('data') - player = data.child('player') - refid = player.child_value('refid') - name = player.child_value('name') + data = request.child("data") + player = data.child("player") + refid = player.child_value("refid") + name = player.child_value("name") root = self.new_profile_by_refid(refid, name) return root def handle_gametop_get_pdata_request(self, request: Node) -> Node: - data = request.child('data') - player = data.child('player') - refid = player.child_value('refid') + data = request.child("data") + player = data.child("player") + refid = player.child_value("refid") root = self.get_profile_by_refid(refid) if root is None: - root = Node.void('gametop') - root.set_attribute('status', str(Status.NO_PROFILE)) + root = Node.void("gametop") + root.set_attribute("status", str(Status.NO_PROFILE)) return root def handle_gametop_get_mdata_request(self, request: Node) -> Node: - data = request.child('data') - player = data.child('player') - extid = player.child_value('jid') - mdata_ver = player.child_value('mdata_ver') # Game requests mdata 3 times per profile for some reason + data = request.child("data") + player = data.child("player") + extid = player.child_value("jid") + mdata_ver = player.child_value( + "mdata_ver" + ) # Game requests mdata 3 times per profile for some reason if mdata_ver != 1: - root = Node.void('gametop') - datanode = Node.void('data') + root = Node.void("gametop") + datanode = Node.void("data") root.add_child(datanode) - player = Node.void('player') + player = Node.void("player") datanode.add_child(player) - player.add_child(Node.s32('jid', extid)) - playdata = Node.void('mdata_list') + player.add_child(Node.s32("jid", extid)) + playdata = Node.void("mdata_list") player.add_child(playdata) return root root = self.get_scores_by_extid(extid) if root is None: - root = Node.void('gametop') - root.set_attribute('status', str(Status.NO_PROFILE)) + root = Node.void("gametop") + root.set_attribute("status", str(Status.NO_PROFILE)) return root def handle_gameend_final_request(self, request: Node) -> Node: - data = request.child('data') - player = data.child('player') + data = request.child("data") + player = data.child("player") if player is not None: - refid = player.child_value('refid') + refid = player.child_value("refid") else: refid = None @@ -1076,88 +1399,96 @@ class JubeatClan( profile = self.get_profile(userid) # Grab unlock progress - item = player.child('item') + item = player.child("item") if item is not None: - profile.replace_int_array('emblem_list', 96, item.child_value('emblem_list')) + profile.replace_int_array( + "emblem_list", 96, item.child_value("emblem_list") + ) # jbox stuff - jbox = player.child('jbox') - jboxdict = profile.get_dict('jbox') + jbox = player.child("jbox") + jboxdict = profile.get_dict("jbox") if jbox is not None: - jboxdict.replace_int('point', jbox.child_value('point')) - emblemtype = jbox.child_value('emblem/type') - index = jbox.child_value('emblem/index') + jboxdict.replace_int("point", jbox.child_value("point")) + emblemtype = jbox.child_value("emblem/type") + index = jbox.child_value("emblem/index") if emblemtype == self.JBOX_EMBLEM_NORMAL: - jboxdict.replace_int('normal_index', index) + jboxdict.replace_int("normal_index", index) elif emblemtype == self.JBOX_EMBLEM_PREMIUM: - jboxdict.replace_int('premium_index', index) - profile.replace_dict('jbox', jboxdict) + jboxdict.replace_int("premium_index", index) + profile.replace_dict("jbox", jboxdict) # Born stuff - born = player.child('born') + born = player.child("born") if born is not None: - profile.replace_int('born_status', born.child_value('status')) - profile.replace_int('born_year', born.child_value('year')) + profile.replace_int("born_status", born.child_value("status")) + profile.replace_int("born_year", born.child_value("year")) else: profile = None if userid is not None and profile is not None: self.put_profile(userid, profile) - return Node.void('gameend') + return Node.void("gameend") - def format_scores(self, userid: UserID, profile: Profile, scores: List[Score]) -> Node: - root = Node.void('gametop') - datanode = Node.void('data') + def format_scores( + self, userid: UserID, profile: Profile, scores: List[Score] + ) -> Node: + root = Node.void("gametop") + datanode = Node.void("data") root.add_child(datanode) - player = Node.void('player') + player = Node.void("player") datanode.add_child(player) - player.add_child(Node.s32('jid', profile.extid)) - playdata = Node.void('mdata_list') + player.add_child(Node.s32("jid", profile.extid)) + playdata = Node.void("mdata_list") player.add_child(playdata) music = ValidatedDict() for score in scores: # Ignore festo-and-above chart types. - if score.chart not in {self.CHART_TYPE_BASIC, self.CHART_TYPE_ADVANCED, self.CHART_TYPE_EXTREME}: + if score.chart not in { + self.CHART_TYPE_BASIC, + self.CHART_TYPE_ADVANCED, + self.CHART_TYPE_EXTREME, + }: continue data = music.get_dict(str(score.id)) - play_cnt = data.get_int_array('play_cnt', 3) - clear_cnt = data.get_int_array('clear_cnt', 3) - clear_flags = data.get_int_array('clear_flags', 3) - fc_cnt = data.get_int_array('fc_cnt', 3) - ex_cnt = data.get_int_array('ex_cnt', 3) - points = data.get_int_array('points', 3) + play_cnt = data.get_int_array("play_cnt", 3) + clear_cnt = data.get_int_array("clear_cnt", 3) + clear_flags = data.get_int_array("clear_flags", 3) + fc_cnt = data.get_int_array("fc_cnt", 3) + ex_cnt = data.get_int_array("ex_cnt", 3) + points = data.get_int_array("points", 3) # Replace data for this chart type play_cnt[score.chart] = score.plays - clear_cnt[score.chart] = score.data.get_int('clear_count') - fc_cnt[score.chart] = score.data.get_int('full_combo_count') - ex_cnt[score.chart] = score.data.get_int('excellent_count') + clear_cnt[score.chart] = score.data.get_int("clear_count") + fc_cnt[score.chart] = score.data.get_int("full_combo_count") + ex_cnt[score.chart] = score.data.get_int("excellent_count") points[score.chart] = score.points # Format the clear flags clear_flags[score.chart] = self.GAME_FLAG_BIT_PLAYED - if score.data.get_int('clear_count') > 0: + if score.data.get_int("clear_count") > 0: clear_flags[score.chart] |= self.GAME_FLAG_BIT_CLEARED - if score.data.get_int('full_combo_count') > 0: + if score.data.get_int("full_combo_count") > 0: clear_flags[score.chart] |= self.GAME_FLAG_BIT_FULL_COMBO - if score.data.get_int('excellent_count') > 0: + if score.data.get_int("excellent_count") > 0: clear_flags[score.chart] |= self.GAME_FLAG_BIT_EXCELLENT # Save chart data back - data.replace_int_array('play_cnt', 3, play_cnt) - data.replace_int_array('clear_cnt', 3, clear_cnt) - data.replace_int_array('clear_flags', 3, clear_flags) - data.replace_int_array('fc_cnt', 3, fc_cnt) - data.replace_int_array('ex_cnt', 3, ex_cnt) - data.replace_int_array('points', 3, points) + data.replace_int_array("play_cnt", 3, play_cnt) + data.replace_int_array("clear_cnt", 3, clear_cnt) + data.replace_int_array("clear_flags", 3, clear_flags) + data.replace_int_array("fc_cnt", 3, fc_cnt) + data.replace_int_array("ex_cnt", 3, ex_cnt) + data.replace_int_array("points", 3, points) # Update the ghost (untyped) - ghost = data.get('ghost', [None, None, None]) - ghost[score.chart] = score.data.get('ghost') - data['ghost'] = ghost + ghost = data.get("ghost", [None, None, None]) + ghost[score.chart] = score.data.get("ghost") + data["ghost"] = ghost # Save it back if score.id in self.FIVE_PLAYS_UNLOCK_EVENT_SONG_IDS: @@ -1171,137 +1502,204 @@ class JubeatClan( for scoreid in music: scoredata = music.get_dict(scoreid) - musicdata = Node.void('musicdata') + musicdata = Node.void("musicdata") playdata.add_child(musicdata) - musicdata.set_attribute('music_id', scoreid) - musicdata.add_child(Node.s32_array('play_cnt', scoredata.get_int_array('play_cnt', 3))) - musicdata.add_child(Node.s32_array('clear_cnt', scoredata.get_int_array('clear_cnt', 3))) - musicdata.add_child(Node.s32_array('fc_cnt', scoredata.get_int_array('fc_cnt', 3))) - musicdata.add_child(Node.s32_array('ex_cnt', scoredata.get_int_array('ex_cnt', 3))) - musicdata.add_child(Node.s32_array('score', scoredata.get_int_array('points', 3))) - musicdata.add_child(Node.s8_array('clear', scoredata.get_int_array('clear_flags', 3))) + musicdata.set_attribute("music_id", scoreid) + musicdata.add_child( + Node.s32_array("play_cnt", scoredata.get_int_array("play_cnt", 3)) + ) + musicdata.add_child( + Node.s32_array("clear_cnt", scoredata.get_int_array("clear_cnt", 3)) + ) + musicdata.add_child( + Node.s32_array("fc_cnt", scoredata.get_int_array("fc_cnt", 3)) + ) + musicdata.add_child( + Node.s32_array("ex_cnt", scoredata.get_int_array("ex_cnt", 3)) + ) + musicdata.add_child( + Node.s32_array("score", scoredata.get_int_array("points", 3)) + ) + musicdata.add_child( + Node.s8_array("clear", scoredata.get_int_array("clear_flags", 3)) + ) - for i, ghost in enumerate(scoredata.get('ghost', [None, None, None])): + for i, ghost in enumerate(scoredata.get("ghost", [None, None, None])): if ghost is None: continue - bar = Node.u8_array('bar', ghost) + bar = Node.u8_array("bar", ghost) musicdata.add_child(bar) - bar.set_attribute('seq', str(i)) + bar.set_attribute("seq", str(i)) return root def format_profile(self, userid: UserID, profile: Profile) -> Node: - root = Node.void('gametop') - data = Node.void('data') + root = Node.void("gametop") + data = Node.void("data") root.add_child(data) # Jubeat Clan appears to allow full event overrides per-player data.add_child(self.__get_global_info()) - player = Node.void('player') + player = Node.void("player") data.add_child(player) # Basic profile info - player.add_child(Node.string('name', profile.get_str('name', 'なし'))) - player.add_child(Node.s32('jid', profile.extid)) + player.add_child(Node.string("name", profile.get_str("name", "なし"))) + player.add_child(Node.s32("jid", profile.extid)) # Miscelaneous crap - player.add_child(Node.s32('session_id', 1)) - player.add_child(Node.u64('event_flag', profile.get_int('event_flag'))) + player.add_child(Node.s32("session_id", 1)) + player.add_child(Node.u64("event_flag", profile.get_int("event_flag"))) # Player info and statistics - info = Node.void('info') + info = Node.void("info") player.add_child(info) - info.add_child(Node.s32('tune_cnt', profile.get_int('tune_cnt'))) - info.add_child(Node.s32('save_cnt', profile.get_int('save_cnt'))) - info.add_child(Node.s32('saved_cnt', profile.get_int('saved_cnt'))) - info.add_child(Node.s32('fc_cnt', profile.get_int('fc_cnt'))) - info.add_child(Node.s32('ex_cnt', profile.get_int('ex_cnt'))) - info.add_child(Node.s32('clear_cnt', profile.get_int('clear_cnt'))) - info.add_child(Node.s32('match_cnt', profile.get_int('match_cnt'))) - info.add_child(Node.s32('beat_cnt', profile.get_int('beat_cnt'))) - info.add_child(Node.s32('mynews_cnt', profile.get_int('mynews_cnt'))) - info.add_child(Node.s32('bonus_tune_points', profile.get_int('bonus_tune_points'))) - info.add_child(Node.bool('is_bonus_tune_played', profile.get_bool('is_bonus_tune_played'))) + info.add_child(Node.s32("tune_cnt", profile.get_int("tune_cnt"))) + info.add_child(Node.s32("save_cnt", profile.get_int("save_cnt"))) + info.add_child(Node.s32("saved_cnt", profile.get_int("saved_cnt"))) + info.add_child(Node.s32("fc_cnt", profile.get_int("fc_cnt"))) + info.add_child(Node.s32("ex_cnt", profile.get_int("ex_cnt"))) + info.add_child(Node.s32("clear_cnt", profile.get_int("clear_cnt"))) + info.add_child(Node.s32("match_cnt", profile.get_int("match_cnt"))) + info.add_child(Node.s32("beat_cnt", profile.get_int("beat_cnt"))) + info.add_child(Node.s32("mynews_cnt", profile.get_int("mynews_cnt"))) + info.add_child( + Node.s32("bonus_tune_points", profile.get_int("bonus_tune_points")) + ) + info.add_child( + Node.bool("is_bonus_tune_played", profile.get_bool("is_bonus_tune_played")) + ) # Looks to be set to true when there's an old profile, stops tutorial from # happening on first load. - info.add_child(Node.bool('inherit', profile.get_bool('has_old_version') and not profile.get_bool('saved'))) + info.add_child( + Node.bool( + "inherit", + profile.get_bool("has_old_version") and not profile.get_bool("saved"), + ) + ) # Not saved, but loaded - info.add_child(Node.s32('mtg_entry_cnt', 123)) - info.add_child(Node.s32('mtg_hold_cnt', 456)) - info.add_child(Node.u8('mtg_result', 10)) + info.add_child(Node.s32("mtg_entry_cnt", 123)) + info.add_child(Node.s32("mtg_hold_cnt", 456)) + info.add_child(Node.u8("mtg_result", 10)) # Last played data, for showing cursor and such - lastdict = profile.get_dict('last') - last = Node.void('last') + lastdict = profile.get_dict("last") + last = Node.void("last") player.add_child(last) - last.add_child(Node.s64('play_time', lastdict.get_int('play_time'))) - last.add_child(Node.string('shopname', lastdict.get_str('shopname'))) - last.add_child(Node.string('areaname', lastdict.get_str('areaname'))) - last.add_child(Node.s32('music_id', lastdict.get_int('music_id'))) - last.add_child(Node.s8('seq_id', lastdict.get_int('seq_id'))) - last.add_child(Node.s8('sort', lastdict.get_int('sort'))) - last.add_child(Node.s8('category', lastdict.get_int('category'))) - last.add_child(Node.s8('expert_option', lastdict.get_int('expert_option'))) + last.add_child(Node.s64("play_time", lastdict.get_int("play_time"))) + last.add_child(Node.string("shopname", lastdict.get_str("shopname"))) + last.add_child(Node.string("areaname", lastdict.get_str("areaname"))) + last.add_child(Node.s32("music_id", lastdict.get_int("music_id"))) + last.add_child(Node.s8("seq_id", lastdict.get_int("seq_id"))) + last.add_child(Node.s8("sort", lastdict.get_int("sort"))) + last.add_child(Node.s8("category", lastdict.get_int("category"))) + last.add_child(Node.s8("expert_option", lastdict.get_int("expert_option"))) - settings = Node.void('settings') + settings = Node.void("settings") last.add_child(settings) - settings.add_child(Node.s8('marker', lastdict.get_int('marker'))) - settings.add_child(Node.s8('theme', lastdict.get_int('theme'))) - settings.add_child(Node.s16('title', lastdict.get_int('title'))) - settings.add_child(Node.s16('parts', lastdict.get_int('parts'))) - settings.add_child(Node.s8('rank_sort', lastdict.get_int('rank_sort'))) - settings.add_child(Node.s8('combo_disp', lastdict.get_int('combo_disp'))) - settings.add_child(Node.s16_array('emblem', lastdict.get_int_array('emblem', 5))) - settings.add_child(Node.s8('matching', lastdict.get_int('matching'))) - settings.add_child(Node.s8('hard', lastdict.get_int('hard'))) - settings.add_child(Node.s8('hazard', lastdict.get_int('hazard'))) + settings.add_child(Node.s8("marker", lastdict.get_int("marker"))) + settings.add_child(Node.s8("theme", lastdict.get_int("theme"))) + settings.add_child(Node.s16("title", lastdict.get_int("title"))) + settings.add_child(Node.s16("parts", lastdict.get_int("parts"))) + settings.add_child(Node.s8("rank_sort", lastdict.get_int("rank_sort"))) + settings.add_child(Node.s8("combo_disp", lastdict.get_int("combo_disp"))) + settings.add_child( + Node.s16_array("emblem", lastdict.get_int_array("emblem", 5)) + ) + settings.add_child(Node.s8("matching", lastdict.get_int("matching"))) + settings.add_child(Node.s8("hard", lastdict.get_int("hard"))) + settings.add_child(Node.s8("hazard", lastdict.get_int("hazard"))) # Secret unlocks - item = Node.void('item') + item = Node.void("item") player.add_child(item) - item.add_child(Node.s32_array('music_list', profile.get_int_array('music_list', 64, [-1] * 64))) - item.add_child(Node.s32_array('secret_list', profile.get_int_array('secret_list', 64, [-1] * 64))) - item.add_child(Node.s32_array('theme_list', profile.get_int_array('theme_list', 16, [-1] * 16))) - item.add_child(Node.s32_array('marker_list', profile.get_int_array('marker_list', 16, [-1] * 16))) - item.add_child(Node.s32_array('title_list', profile.get_int_array('title_list', 160, [-1] * 160))) - item.add_child(Node.s32_array('parts_list', profile.get_int_array('parts_list', 160, [-1] * 160))) - item.add_child(Node.s32_array('emblem_list', profile.get_int_array('emblem_list', 96, [-1] * 96))) - item.add_child(Node.s32_array('commu_list', profile.get_int_array('commu_list', 16, [-1] * 16))) + item.add_child( + Node.s32_array( + "music_list", profile.get_int_array("music_list", 64, [-1] * 64) + ) + ) + item.add_child( + Node.s32_array( + "secret_list", profile.get_int_array("secret_list", 64, [-1] * 64) + ) + ) + item.add_child( + Node.s32_array( + "theme_list", profile.get_int_array("theme_list", 16, [-1] * 16) + ) + ) + item.add_child( + Node.s32_array( + "marker_list", profile.get_int_array("marker_list", 16, [-1] * 16) + ) + ) + item.add_child( + Node.s32_array( + "title_list", profile.get_int_array("title_list", 160, [-1] * 160) + ) + ) + item.add_child( + Node.s32_array( + "parts_list", profile.get_int_array("parts_list", 160, [-1] * 160) + ) + ) + item.add_child( + Node.s32_array( + "emblem_list", profile.get_int_array("emblem_list", 96, [-1] * 96) + ) + ) + item.add_child( + Node.s32_array( + "commu_list", profile.get_int_array("commu_list", 16, [-1] * 16) + ) + ) - new = Node.void('new') + new = Node.void("new") item.add_child(new) - new.add_child(Node.s32_array('secret_list', profile.get_int_array('secret_list_new', 64, [-1] * 64))) - new.add_child(Node.s32_array('theme_list', profile.get_int_array('theme_list_new', 16, [-1] * 16))) - new.add_child(Node.s32_array('marker_list', profile.get_int_array('marker_list_new', 16, [-1] * 16))) + new.add_child( + Node.s32_array( + "secret_list", profile.get_int_array("secret_list_new", 64, [-1] * 64) + ) + ) + new.add_child( + Node.s32_array( + "theme_list", profile.get_int_array("theme_list_new", 16, [-1] * 16) + ) + ) + new.add_child( + Node.s32_array( + "marker_list", profile.get_int_array("marker_list_new", 16, [-1] * 16) + ) + ) # Add rivals to profile. - rivallist = Node.void('rivallist') + rivallist = Node.void("rivallist") player.add_child(rivallist) links = self.data.local.user.get_links(self.game, self.version, userid) rivalcount = 0 for link in links: - if link.type != 'rival': + if link.type != "rival": continue rprofile = self.get_profile(link.other_userid) if rprofile is None: continue - rival = Node.void('rival') + rival = Node.void("rival") rivallist.add_child(rival) - rival.add_child(Node.s32('jid', rprofile.extid)) - rival.add_child(Node.string('name', rprofile.get_str('name'))) + rival.add_child(Node.s32("jid", rprofile.extid)) + rival.add_child(Node.string("name", rprofile.get_str("name"))) # This looks like a carry-over from prop's career and isn't displayed. - career = Node.void('career') + career = Node.void("career") rival.add_child(career) - career.add_child(Node.s16('level', 1)) + career.add_child(Node.s16("level", 1)) # Lazy way of keeping track of rivals, since we can only have 3 # or the game with throw up. @@ -1309,82 +1707,104 @@ class JubeatClan( if rivalcount >= 3: break - rivallist.set_attribute('count', str(rivalcount)) + rivallist.set_attribute("count", str(rivalcount)) - lab_edit_seq = Node.void('lab_edit_seq') + lab_edit_seq = Node.void("lab_edit_seq") player.add_child(lab_edit_seq) - lab_edit_seq.set_attribute('count', '0') + lab_edit_seq.set_attribute("count", "0") # Full combo challenge - entry = self.data.local.game.get_time_sensitive_settings(self.game, self.version, 'fc_challenge') + entry = self.data.local.game.get_time_sensitive_settings( + self.game, self.version, "fc_challenge" + ) if entry is None: entry = ValidatedDict() # Figure out if we've played these songs - start_time, end_time = self.data.local.network.get_schedule_duration('daily') - today_attempts = self.data.local.music.get_all_attempts(self.game, self.music_version, userid, entry.get_int('today', -1), timelimit=start_time) - whim_attempts = self.data.local.music.get_all_attempts(self.game, self.music_version, userid, entry.get_int('whim', -1), timelimit=start_time) + start_time, end_time = self.data.local.network.get_schedule_duration("daily") + today_attempts = self.data.local.music.get_all_attempts( + self.game, + self.music_version, + userid, + entry.get_int("today", -1), + timelimit=start_time, + ) + whim_attempts = self.data.local.music.get_all_attempts( + self.game, + self.music_version, + userid, + entry.get_int("whim", -1), + timelimit=start_time, + ) - fc_challenge = Node.void('fc_challenge') + fc_challenge = Node.void("fc_challenge") player.add_child(fc_challenge) - today = Node.void('today') + today = Node.void("today") fc_challenge.add_child(today) - today.add_child(Node.s32('music_id', entry.get_int('today', -1))) - today.add_child(Node.u8('state', 0x40 if len(today_attempts) > 0 else 0x0)) - whim = Node.void('whim') + today.add_child(Node.s32("music_id", entry.get_int("today", -1))) + today.add_child(Node.u8("state", 0x40 if len(today_attempts) > 0 else 0x0)) + whim = Node.void("whim") fc_challenge.add_child(whim) - whim.add_child(Node.s32('music_id', entry.get_int('whim', -1))) - whim.add_child(Node.u8('state', 0x40 if len(whim_attempts) > 0 else 0x0)) + whim.add_child(Node.s32("music_id", entry.get_int("whim", -1))) + whim.add_child(Node.u8("state", 0x40 if len(whim_attempts) > 0 else 0x0)) # No news, ever. - official_news = Node.void('official_news') + official_news = Node.void("official_news") player.add_child(official_news) - news_list = Node.void('news_list') + news_list = Node.void("news_list") official_news.add_child(news_list) # Sane defaults for unknown/who cares nodes - history = Node.void('history') + history = Node.void("history") player.add_child(history) - history.set_attribute('count', '0') + history.set_attribute("count", "0") - free_first_play = Node.void('free_first_play') + free_first_play = Node.void("free_first_play") player.add_child(free_first_play) - free_first_play.add_child(Node.bool('is_available', False)) + free_first_play.add_child(Node.bool("is_available", False)) # Player status for events - event_info = Node.void('event_info') + event_info = Node.void("event_info") player.add_child(event_info) - achievements = self.data.local.user.get_achievements(self.game, self.version, userid) + achievements = self.data.local.user.get_achievements( + self.game, self.version, userid + ) event_completion: Dict[int, bool] = {} course_completion: Dict[int, ValidatedDict] = {} for achievement in achievements: - if achievement.type == 'event': - event_completion[achievement.id] = achievement.data.get_bool('is_completed') - if achievement.type == 'course': + if achievement.type == "event": + event_completion[achievement.id] = achievement.data.get_bool( + "is_completed" + ) + if achievement.type == "course": course_completion[achievement.id] = achievement.data for eventid, eventdata in self.EVENTS.items(): # There are two significant bits here, bit 0 and bit 1, I think the first # one is whether the event is started, second is if its finished? - event = Node.void('event') + event = Node.void("event") event_info.add_child(event) - event.set_attribute('type', str(eventid)) + event.set_attribute("type", str(eventid)) state = 0x0 - state |= self.EVENT_STATUS_OPEN if eventdata['enabled'] else 0 - state |= self.EVENT_STATUS_COMPLETE if event_completion.get(eventid, False) else 0 - event.add_child(Node.u8('state', state)) + state |= self.EVENT_STATUS_OPEN if eventdata["enabled"] else 0 + state |= ( + self.EVENT_STATUS_COMPLETE + if event_completion.get(eventid, False) + else 0 + ) + event.add_child(Node.u8("state", state)) # JBox stuff - jbox = Node.void('jbox') - jboxdict = profile.get_dict('jbox') + jbox = Node.void("jbox") + jboxdict = profile.get_dict("jbox") player.add_child(jbox) - jbox.add_child(Node.s32('point', jboxdict.get_int('point'))) - emblem = Node.void('emblem') + jbox.add_child(Node.s32("point", jboxdict.get_int("point"))) + emblem = Node.void("emblem") jbox.add_child(emblem) - normal = Node.void('normal') + normal = Node.void("normal") emblem.add_child(normal) - premium = Node.void('premium') + premium = Node.void("premium") emblem.add_child(premium) # Calculate a random index for normal and premium to give to player @@ -1393,10 +1813,10 @@ class JubeatClan( normalemblems: Set[int] = set() premiumemblems: Set[int] = set() for gameitem in gameitems: - if gameitem.type == 'emblem': - if gameitem.data.get_int('rarity') in {1, 2, 3}: + if gameitem.type == "emblem": + if gameitem.data.get_int("rarity") in {1, 2, 3}: normalemblems.add(gameitem.id) - if gameitem.data.get_int('rarity') in {3, 4, 5}: + if gameitem.data.get_int("rarity") in {3, 4, 5}: premiumemblems.add(gameitem.id) # Default to some emblems in case the catalog is not available. @@ -1407,19 +1827,19 @@ class JubeatClan( if premiumemblems: premiumindex = random.sample(premiumemblems, 1)[0] - normal.add_child(Node.s16('index', normalindex)) - premium.add_child(Node.s16('index', premiumindex)) + normal.add_child(Node.s16("index", normalindex)) + premium.add_child(Node.s16("index", premiumindex)) # New Music stuff - new_music = Node.void('new_music') + new_music = Node.void("new_music") player.add_child(new_music) - navi = Node.void('navi') + navi = Node.void("navi") player.add_child(navi) - navi.add_child(Node.u64('flag', profile.get_int('navi_flag'))) + navi.add_child(Node.u64("flag", profile.get_int("navi_flag"))) # Gift list, maybe from other players? - gift_list = Node.void('gift_list') + gift_list = Node.void("gift_list") player.add_child(gift_list) # If we had gifts, they look like this: # @@ -1427,26 +1847,26 @@ class JubeatClan( # # Birthday event? - born = Node.void('born') + born = Node.void("born") player.add_child(born) - born.add_child(Node.s8('status', profile.get_int('born_status'))) - born.add_child(Node.s16('year', profile.get_int('born_year'))) + born.add_child(Node.s8("status", profile.get_int("born_status"))) + born.add_child(Node.s16("year", profile.get_int("born_year"))) # More crap - question_list = Node.void('question_list') + question_list = Node.void("question_list") player.add_child(question_list) # Player Jubility - jubility = Node.void('jubility') + jubility = Node.void("jubility") player.add_child(jubility) - jubility.set_attribute('param', str(profile.get_int('jubility'))) - target_music_list = Node.void('target_music_list') + jubility.set_attribute("param", str(profile.get_int("jubility"))) + target_music_list = Node.void("target_music_list") jubility.add_child(target_music_list) # Calculate top 30 songs contributing to jubility. jubeat_entries: List[ValidatedDict] = [] for achievement in achievements: - if achievement.type != 'jubility': + if achievement.type != "jubility": continue # Figure out for each song, what's the highest value jubility and @@ -1459,7 +1879,9 @@ class JubeatClan( bestentry.replace_int("songid", achievement.id) bestentry.replace_int("chart", chart) jubeat_entries.append(bestentry) - jubeat_entries = sorted(jubeat_entries, key=lambda entry: entry.get_int("value"), reverse=True) + jubeat_entries = sorted( + jubeat_entries, key=lambda entry: entry.get_int("value"), reverse=True + ) # Now, give the game the list. for i, entry in enumerate(jubeat_entries): @@ -1473,76 +1895,80 @@ class JubeatClan( target_music.add_child(Node.s8("seq", entry.get_int("chart"))) target_music.add_child(Node.s32("score", entry.get_int("score"))) target_music.add_child(Node.s32("value", entry.get_int("value"))) - target_music.add_child(Node.bool("is_hard_mode", entry.get_bool("hard_mode"))) + target_music.add_child( + Node.bool("is_hard_mode", entry.get_bool("hard_mode")) + ) # Team stuff - team = Node.void('team') - teamdict = profile.get_dict('team') + team = Node.void("team") + teamdict = profile.get_dict("team") player.add_child(team) - team.set_attribute('id', str(teamdict.get_int('id'))) - team.add_child(Node.s32("section", teamdict.get_int('section'))) - team.add_child(Node.s32("street", teamdict.get_int('street'))) - team.add_child(Node.s32("house_number_1", teamdict.get_int('house_1'))) - team.add_child(Node.s32("house_number_2", teamdict.get_int('house_2'))) + team.set_attribute("id", str(teamdict.get_int("id"))) + team.add_child(Node.s32("section", teamdict.get_int("section"))) + team.add_child(Node.s32("street", teamdict.get_int("street"))) + team.add_child(Node.s32("house_number_1", teamdict.get_int("house_1"))) + team.add_child(Node.s32("house_number_2", teamdict.get_int("house_2"))) # Set up where the player moves to (random) after their first play - move = Node.void('move') + move = Node.void("move") team.add_child(move) # 1 - Redbelk, 2 - Cyantle, 3 - Greenesia, 4 - Plumpark - move.set_attribute('id', str(random.choice([1, 2, 3, 4]))) + move.set_attribute("id", str(random.choice([1, 2, 3, 4]))) move.set_attribute("section", str(random.choice([1, 2, 3, 4, 5]))) move.set_attribute("street", str(random.choice([1, 2, 3, 4, 5, 6]))) move.set_attribute("house_number_1", str(random.choice(range(10, 100)))) move.set_attribute("house_number_2", str(random.choice(range(10, 100)))) # Union Battle - union_battle = Node.void('union_battle') + union_battle = Node.void("union_battle") player.add_child(union_battle) - union_battle.set_attribute('id', "-1") + union_battle.set_attribute("id", "-1") union_battle.add_child(Node.s32("power", 0)) # Some server node - server = Node.void('server') + server = Node.void("server") player.add_child(server) # Another unknown gift list? - eamuse_gift_list = Node.void('eamuse_gift_list') + eamuse_gift_list = Node.void("eamuse_gift_list") player.add_child(eamuse_gift_list) # Clan Course List Progress - clan_course_list = Node.void('clan_course_list') + clan_course_list = Node.void("clan_course_list") player.add_child(clan_course_list) # Each course that we have completed has one of the following nodes. for course in self.__get_course_list(): - status_dict = course_completion.get(course['id'], ValidatedDict()) + status_dict = course_completion.get(course["id"], ValidatedDict()) status = 0 - status |= self.COURSE_STATUS_SEEN if status_dict.get_bool('seen') else 0 - status |= self.COURSE_STATUS_PLAYED if status_dict.get_bool('played') else 0 - status |= self.COURSE_STATUS_CLEARED if status_dict.get_bool('cleared') else 0 + status |= self.COURSE_STATUS_SEEN if status_dict.get_bool("seen") else 0 + status |= self.COURSE_STATUS_PLAYED if status_dict.get_bool("played") else 0 + status |= ( + self.COURSE_STATUS_CLEARED if status_dict.get_bool("cleared") else 0 + ) - clan_course = Node.void('clan_course') + clan_course = Node.void("clan_course") clan_course_list.add_child(clan_course) - clan_course.set_attribute('id', str(course['id'])) - clan_course.add_child(Node.s8('status', status)) + clan_course.set_attribute("id", str(course["id"])) + clan_course.add_child(Node.s8("status", status)) - category_list = Node.void('category_list') + category_list = Node.void("category_list") player.add_child(category_list) # Each category has one of the following nodes for categoryid in range(1, 7): - category = Node.void('category') + category = Node.void("category") category_list.add_child(category) - category.set_attribute('id', str(categoryid)) - category.add_child(Node.bool('is_display', True)) + category.set_attribute("id", str(categoryid)) + category.add_child(Node.bool("is_display", True)) # Drop list - drop_list = Node.void('drop_list') + drop_list = Node.void("drop_list") player.add_child(drop_list) dropachievements: Dict[int, Achievement] = {} for achievement in achievements: - if achievement.type == 'drop': + if achievement.type == "drop": dropachievements[achievement.id] = achievement for dropid in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]: @@ -1551,185 +1977,249 @@ class JubeatClan( else: dropdata = ValidatedDict() - drop = Node.void('drop') + drop = Node.void("drop") drop_list.add_child(drop) - drop.set_attribute('id', str(dropid)) - drop.add_child(Node.s32('exp', dropdata.get_int('exp', -1))) - drop.add_child(Node.s32('flag', dropdata.get_int('flag', 0))) + drop.set_attribute("id", str(dropid)) + drop.add_child(Node.s32("exp", dropdata.get_int("exp", -1))) + drop.add_child(Node.s32("flag", dropdata.get_int("flag", 0))) - item_list = Node.void('item_list') + item_list = Node.void("item_list") drop.add_child(item_list) for itemid in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]: - item = Node.void('item') + item = Node.void("item") item_list.add_child(item) - item.set_attribute('id', str(itemid)) - item.add_child(Node.s32('num', dropdata.get_int(f'item_{itemid}'))) + item.set_attribute("id", str(itemid)) + item.add_child(Node.s32("num", dropdata.get_int(f"item_{itemid}"))) # Fill in category - fill_in_category = Node.void('fill_in_category') + fill_in_category = Node.void("fill_in_category") player.add_child(fill_in_category) - fill_in_category.add_child(Node.s32_array('no_gray_flag_list', profile.get_int_array('no_gray_flag_list', 16, [0] * 16))) - fill_in_category.add_child(Node.s32_array('all_yellow_flag_list', profile.get_int_array('all_yellow_flag_list', 16, [0] * 16))) - fill_in_category.add_child(Node.s32_array('full_combo_flag_list', profile.get_int_array('full_combo_flag_list', 16, [0] * 16))) - fill_in_category.add_child(Node.s32_array('excellent_flag_list', profile.get_int_array('excellent_flag_list', 16, [0] * 16))) + fill_in_category.add_child( + Node.s32_array( + "no_gray_flag_list", + profile.get_int_array("no_gray_flag_list", 16, [0] * 16), + ) + ) + fill_in_category.add_child( + Node.s32_array( + "all_yellow_flag_list", + profile.get_int_array("all_yellow_flag_list", 16, [0] * 16), + ) + ) + fill_in_category.add_child( + Node.s32_array( + "full_combo_flag_list", + profile.get_int_array("full_combo_flag_list", 16, [0] * 16), + ) + ) + fill_in_category.add_child( + Node.s32_array( + "excellent_flag_list", + profile.get_int_array("excellent_flag_list", 16, [0] * 16), + ) + ) # Daily Bonus - daily_bonus_list = Node.void('daily_bonus_list') + daily_bonus_list = Node.void("daily_bonus_list") player.add_child(daily_bonus_list) # Tickets - ticket_list = Node.void('ticket_list') + ticket_list = Node.void("ticket_list") player.add_child(ticket_list) return root - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() - newprofile.replace_bool('saved', True) - data = request.child('data') + newprofile.replace_bool("saved", True) + data = request.child("data") # Grab system information - sysinfo = data.child('info') + sysinfo = data.child("info") # Grab player information - player = data.child('player') + player = data.child("player") # Grab result information - result = data.child('result') + result = data.child("result") # Grab last information. Lots of this will be filled in while grabbing scores - last = newprofile.get_dict('last') + last = newprofile.get_dict("last") if sysinfo is not None: - last.replace_int('play_time', sysinfo.child_value('time_gameend')) - last.replace_str('shopname', sysinfo.child_value('shopname')) - last.replace_str('areaname', sysinfo.child_value('areaname')) + last.replace_int("play_time", sysinfo.child_value("time_gameend")) + last.replace_str("shopname", sysinfo.child_value("shopname")) + last.replace_str("areaname", sysinfo.child_value("areaname")) # Grab player info for echoing back - info = player.child('info') + info = player.child("info") if info is not None: - newprofile.replace_int('tune_cnt', info.child_value('tune_cnt')) - newprofile.replace_int('save_cnt', info.child_value('save_cnt')) - newprofile.replace_int('saved_cnt', info.child_value('saved_cnt')) - newprofile.replace_int('fc_cnt', info.child_value('fc_cnt')) - newprofile.replace_int('ex_cnt', info.child_value('ex_cnt')) - newprofile.replace_int('clear_cnt', info.child_value('clear_cnt')) - newprofile.replace_int('match_cnt', info.child_value('match_cnt')) - newprofile.replace_int('beat_cnt', info.child_value('beat_cnt')) - newprofile.replace_int('mynews_cnt', info.child_value('mynews_cnt')) + newprofile.replace_int("tune_cnt", info.child_value("tune_cnt")) + newprofile.replace_int("save_cnt", info.child_value("save_cnt")) + newprofile.replace_int("saved_cnt", info.child_value("saved_cnt")) + newprofile.replace_int("fc_cnt", info.child_value("fc_cnt")) + newprofile.replace_int("ex_cnt", info.child_value("ex_cnt")) + newprofile.replace_int("clear_cnt", info.child_value("clear_cnt")) + newprofile.replace_int("match_cnt", info.child_value("match_cnt")) + newprofile.replace_int("beat_cnt", info.child_value("beat_cnt")) + newprofile.replace_int("mynews_cnt", info.child_value("mynews_cnt")) - newprofile.replace_int('bonus_tune_points', info.child_value('bonus_tune_points')) - newprofile.replace_bool('is_bonus_tune_played', info.child_value('is_bonus_tune_played')) + newprofile.replace_int( + "bonus_tune_points", info.child_value("bonus_tune_points") + ) + newprofile.replace_bool( + "is_bonus_tune_played", info.child_value("is_bonus_tune_played") + ) # Grab last settings - lastnode = player.child('last') + lastnode = player.child("last") if lastnode is not None: - last.replace_int('expert_option', lastnode.child_value('expert_option')) - last.replace_int('sort', lastnode.child_value('sort')) - last.replace_int('category', lastnode.child_value('category')) + last.replace_int("expert_option", lastnode.child_value("expert_option")) + last.replace_int("sort", lastnode.child_value("sort")) + last.replace_int("category", lastnode.child_value("category")) - settings = lastnode.child('settings') + settings = lastnode.child("settings") if settings is not None: - last.replace_int('matching', settings.child_value('matching')) - last.replace_int('hazard', settings.child_value('hazard')) - last.replace_int('hard', settings.child_value('hard')) - last.replace_int('marker', settings.child_value('marker')) - last.replace_int('theme', settings.child_value('theme')) - last.replace_int('title', settings.child_value('title')) - last.replace_int('parts', settings.child_value('parts')) - last.replace_int('rank_sort', settings.child_value('rank_sort')) - last.replace_int('combo_disp', settings.child_value('combo_disp')) - last.replace_int_array('emblem', 5, settings.child_value('emblem')) + last.replace_int("matching", settings.child_value("matching")) + last.replace_int("hazard", settings.child_value("hazard")) + last.replace_int("hard", settings.child_value("hard")) + last.replace_int("marker", settings.child_value("marker")) + last.replace_int("theme", settings.child_value("theme")) + last.replace_int("title", settings.child_value("title")) + last.replace_int("parts", settings.child_value("parts")) + last.replace_int("rank_sort", settings.child_value("rank_sort")) + last.replace_int("combo_disp", settings.child_value("combo_disp")) + last.replace_int_array("emblem", 5, settings.child_value("emblem")) # Grab unlock progress - item = player.child('item') + item = player.child("item") if item is not None: - newprofile.replace_int_array('music_list', 64, item.child_value('music_list')) - newprofile.replace_int_array('secret_list', 64, item.child_value('secret_list')) - newprofile.replace_int_array('theme_list', 16, item.child_value('theme_list')) - newprofile.replace_int_array('marker_list', 16, item.child_value('marker_list')) - newprofile.replace_int_array('title_list', 160, item.child_value('title_list')) - newprofile.replace_int_array('parts_list', 160, item.child_value('parts_list')) - newprofile.replace_int_array('emblem_list', 96, item.child_value('emblem_list')) - newprofile.replace_int_array('commu_list', 16, item.child_value('commu_list')) + newprofile.replace_int_array( + "music_list", 64, item.child_value("music_list") + ) + newprofile.replace_int_array( + "secret_list", 64, item.child_value("secret_list") + ) + newprofile.replace_int_array( + "theme_list", 16, item.child_value("theme_list") + ) + newprofile.replace_int_array( + "marker_list", 16, item.child_value("marker_list") + ) + newprofile.replace_int_array( + "title_list", 160, item.child_value("title_list") + ) + newprofile.replace_int_array( + "parts_list", 160, item.child_value("parts_list") + ) + newprofile.replace_int_array( + "emblem_list", 96, item.child_value("emblem_list") + ) + newprofile.replace_int_array( + "commu_list", 16, item.child_value("commu_list") + ) - newitem = item.child('new') + newitem = item.child("new") if newitem is not None: - newprofile.replace_int_array('secret_list_new', 64, newitem.child_value('secret_list')) - newprofile.replace_int_array('theme_list_new', 16, newitem.child_value('theme_list')) - newprofile.replace_int_array('marker_list_new', 16, newitem.child_value('marker_list')) + newprofile.replace_int_array( + "secret_list_new", 64, newitem.child_value("secret_list") + ) + newprofile.replace_int_array( + "theme_list_new", 16, newitem.child_value("theme_list") + ) + newprofile.replace_int_array( + "marker_list_new", 16, newitem.child_value("marker_list") + ) # Grab categories stuff - fill_in_category = player.child('fill_in_category') + fill_in_category = player.child("fill_in_category") if fill_in_category is not None: - newprofile.replace_int_array('no_gray_flag_list', 16, fill_in_category.child_value('no_gray_flag_list')) - newprofile.replace_int_array('all_yellow_flag_list', 16, fill_in_category.child_value('all_yellow_flag_list')) - newprofile.replace_int_array('full_combo_flag_list', 16, fill_in_category.child_value('full_combo_flag_list')) - newprofile.replace_int_array('excellent_flag_list', 16, fill_in_category.child_value('excellent_flag_list')) + newprofile.replace_int_array( + "no_gray_flag_list", + 16, + fill_in_category.child_value("no_gray_flag_list"), + ) + newprofile.replace_int_array( + "all_yellow_flag_list", + 16, + fill_in_category.child_value("all_yellow_flag_list"), + ) + newprofile.replace_int_array( + "full_combo_flag_list", + 16, + fill_in_category.child_value("full_combo_flag_list"), + ) + newprofile.replace_int_array( + "excellent_flag_list", + 16, + fill_in_category.child_value("excellent_flag_list"), + ) # jbox stuff - jbox = player.child('jbox') - jboxdict = newprofile.get_dict('jbox') + jbox = player.child("jbox") + jboxdict = newprofile.get_dict("jbox") if jbox is not None: - jboxdict.replace_int('point', jbox.child_value('point')) - emblemtype = jbox.child_value('emblem/type') - index = jbox.child_value('emblem/index') + jboxdict.replace_int("point", jbox.child_value("point")) + emblemtype = jbox.child_value("emblem/type") + index = jbox.child_value("emblem/index") if emblemtype == self.JBOX_EMBLEM_NORMAL: - jboxdict.replace_int('normal_index', index) + jboxdict.replace_int("normal_index", index) elif emblemtype == self.JBOX_EMBLEM_PREMIUM: - jboxdict.replace_int('premium_index', index) - newprofile.replace_dict('jbox', jboxdict) + jboxdict.replace_int("premium_index", index) + newprofile.replace_dict("jbox", jboxdict) # Team stuff - team = player.child('team') - teamdict = newprofile.get_dict('team') + team = player.child("team") + teamdict = newprofile.get_dict("team") if team is not None: - teamdict.replace_int('id', int(team.attribute('id'))) - teamdict.replace_int('section', team.child_value('section')) - teamdict.replace_int('street', team.child_value('street')) - teamdict.replace_int('house_1', team.child_value('house_number_1')) - teamdict.replace_int('house_2', team.child_value('house_number_2')) - newprofile.replace_dict('team', teamdict) + teamdict.replace_int("id", int(team.attribute("id"))) + teamdict.replace_int("section", team.child_value("section")) + teamdict.replace_int("street", team.child_value("street")) + teamdict.replace_int("house_1", team.child_value("house_number_1")) + teamdict.replace_int("house_2", team.child_value("house_number_2")) + newprofile.replace_dict("team", teamdict) # Drop list - drop_list = player.child('drop_list') + drop_list = player.child("drop_list") if drop_list is not None: for drop in drop_list.children: try: - dropid = int(drop.attribute('id')) + dropid = int(drop.attribute("id")) except TypeError: # Unrecognized drop continue - exp = drop.child_value('exp') - flag = drop.child_value('flag') + exp = drop.child_value("exp") + flag = drop.child_value("flag") items: Dict[int, int] = {} - item_list = drop.child('item_list') + item_list = drop.child("item_list") if item_list is not None: for item in item_list.children: try: - itemid = int(item.attribute('id')) + itemid = int(item.attribute("id")) except TypeError: # Unrecognized item continue - items[itemid] = item.child_value('num') + items[itemid] = item.child_value("num") olddrop = self.data.local.user.get_achievement( self.game, self.version, userid, dropid, - 'drop', + "drop", ) if olddrop is None: # Create a new event structure for this olddrop = ValidatedDict() - olddrop.replace_int('exp', exp) - olddrop.replace_int('flag', flag) + olddrop.replace_int("exp", exp) + olddrop.replace_int("flag", flag) for itemid, num in items.items(): - olddrop.replace_int(f'item_{itemid}', num) + olddrop.replace_int(f"item_{itemid}", num) # Save it as an achievement self.data.local.user.put_achievement( @@ -1737,21 +2227,21 @@ class JubeatClan( self.version, userid, dropid, - 'drop', + "drop", olddrop, ) # event stuff - newprofile.replace_int('event_flag', player.child_value('event_flag')) - event_info = player.child('event_info') + newprofile.replace_int("event_flag", player.child_value("event_flag")) + event_info = player.child("event_info") if event_info is not None: for child in event_info.children: try: - eventid = int(child.attribute('type')) + eventid = int(child.attribute("type")) except TypeError: # Event is empty continue - is_completed = child.child_value('is_completed') + is_completed = child.child_value("is_completed") # Figure out if we should update the rating/scores or not oldevent = self.data.local.user.get_achievement( @@ -1759,14 +2249,14 @@ class JubeatClan( self.version, userid, eventid, - 'event', + "event", ) if oldevent is None: # Create a new event structure for this oldevent = ValidatedDict() - oldevent.replace_bool('is_completed', is_completed) + oldevent.replace_bool("is_completed", is_completed) # Save it as an achievement self.data.local.user.put_achievement( @@ -1774,44 +2264,44 @@ class JubeatClan( self.version, userid, eventid, - 'event', + "event", oldevent, ) # Still don't know what this is for lol - newprofile.replace_int('navi_flag', player.child_value('navi/flag')) + newprofile.replace_int("navi_flag", player.child_value("navi/flag")) # Grab scores and save those if result is not None: for tune in result.children: - if tune.name != 'tune': + if tune.name != "tune": continue - result = tune.child('player') + result = tune.child("player") # Fix mapping to song IDs for the song with seven billion charts # due to the prefecture unlock event. - songid = tune.child_value('music') + songid = tune.child_value("music") if songid in self.FIVE_PLAYS_UNLOCK_EVENT_SONG_IDS: songid = 80000301 - timestamp = tune.child_value('timestamp') / 1000 - chart = int(result.child('score').attribute('seq')) - points = result.child_value('score') - flags = int(result.child('score').attribute('clear')) - combo = int(result.child('score').attribute('combo')) - ghost = result.child_value('mbar') + timestamp = tune.child_value("timestamp") / 1000 + chart = int(result.child("score").attribute("seq")) + points = result.child_value("score") + flags = int(result.child("score").attribute("clear")) + combo = int(result.child("score").attribute("combo")) + ghost = result.child_value("mbar") stats = { - 'perfect': result.child_value('nr_perfect'), - 'great': result.child_value('nr_great'), - 'good': result.child_value('nr_good'), - 'poor': result.child_value('nr_poor'), - 'miss': result.child_value('nr_miss'), + "perfect": result.child_value("nr_perfect"), + "great": result.child_value("nr_great"), + "good": result.child_value("nr_good"), + "poor": result.child_value("nr_poor"), + "miss": result.child_value("nr_miss"), } # Miscelaneous last data for echoing to profile get - last.replace_int('music_id', songid) - last.replace_int('seq_id', chart) + last.replace_int("music_id", songid) + last.replace_int("seq_id", chart) mapping = { self.GAME_FLAG_BIT_CLEARED: self.PLAY_MEDAL_CLEARED, @@ -1827,19 +2317,21 @@ class JubeatClan( if flags & bit > 0: medal = max(medal, mapping[bit]) - self.update_score(userid, timestamp, songid, chart, points, medal, combo, ghost, stats) + self.update_score( + userid, timestamp, songid, chart, points, medal, combo, ghost, stats + ) # Born stuff - born = player.child('born') + born = player.child("born") if born is not None: - newprofile.replace_int('born_status', born.child_value('status')) - newprofile.replace_int('born_year', born.child_value('year')) + newprofile.replace_int("born_status", born.child_value("status")) + newprofile.replace_int("born_year", born.child_value("year")) # Save jubility - jubility = player.child('jubility') + jubility = player.child("jubility") if jubility is not None: - newprofile.replace_int('jubility', int(jubility.attribute('param'))) - target_music_list = jubility.child('target_music_list') + newprofile.replace_int("jubility", int(jubility.attribute("param"))) + target_music_list = jubility.child("target_music_list") if target_music_list is not None: for target_music in target_music_list.children: if target_music.name != "target_music": @@ -1857,7 +2349,7 @@ class JubeatClan( self.version, userid, songid, - 'jubility', + "jubility", ) if oldjubility is None: @@ -1867,9 +2359,9 @@ class JubeatClan( # Grab the entry for this sequence entry = oldjubility.get_dict(str(chart)) if value >= entry.get_int("value"): - entry.replace_int('score', score) - entry.replace_int('value', value) - entry.replace_bool('hard_mode', hard_mode) + entry.replace_int("score", score) + entry.replace_int("value", value) + entry.replace_bool("hard_mode", hard_mode) oldjubility.replace_dict(str(chart), entry) # Save it as an achievement @@ -1878,19 +2370,19 @@ class JubeatClan( self.version, userid, songid, - 'jubility', + "jubility", oldjubility, ) # Clan course saving - clan_course_list = player.child('clan_course_list') + clan_course_list = player.child("clan_course_list") if clan_course_list is not None: for course in clan_course_list.children: - if course.name != 'clan_course': + if course.name != "clan_course": continue - courseid = int(course.attribute('id')) - status = course.child_value('status') + courseid = int(course.attribute("id")) + status = course.child_value("status") is_seen = (status & self.COURSE_STATUS_SEEN) != 0 is_played = (status & self.COURSE_STATUS_PLAYED) != 0 @@ -1900,15 +2392,17 @@ class JubeatClan( self.version, userid, courseid, - 'course', + "course", ) if oldcourse is None: # Create a new course structure for this oldcourse = ValidatedDict() - oldcourse.replace_bool('seen', oldcourse.get_bool('seen') or is_seen) - oldcourse.replace_bool('played', oldcourse.get_bool('played') or is_played) + oldcourse.replace_bool("seen", oldcourse.get_bool("seen") or is_seen) + oldcourse.replace_bool( + "played", oldcourse.get_bool("played") or is_played + ) # Save it as an achievement self.data.local.user.put_achievement( @@ -1916,17 +2410,17 @@ class JubeatClan( self.version, userid, courseid, - 'course', + "course", oldcourse, ) - select_course = player.child('select_course') + select_course = player.child("select_course") if select_course is not None: try: - courseid = int(select_course.attribute('id')) + courseid = int(select_course.attribute("id")) except Exception: courseid = 0 - cleared = select_course.child_value('is_cleared') + cleared = select_course.child_value("is_cleared") if courseid > 0 and cleared: # Update course cleared status @@ -1935,14 +2429,14 @@ class JubeatClan( self.version, userid, courseid, - 'course', + "course", ) if oldcourse is None: # Create a new course structure for this oldcourse = ValidatedDict() - oldcourse.replace_bool('cleared', True) + oldcourse.replace_bool("cleared", True) # Save it as an achievement self.data.local.user.put_achievement( @@ -1950,12 +2444,12 @@ class JubeatClan( self.version, userid, courseid, - 'course', + "course", oldcourse, ) # Save back last information gleaned from results - newprofile.replace_dict('last', last) + newprofile.replace_dict("last", last) # Keep track of play statistics self.update_play_statistics(userid) diff --git a/bemani/backend/jubeat/common.py b/bemani/backend/jubeat/common.py index 347b0c4..3465067 100644 --- a/bemani/backend/jubeat/common.py +++ b/bemani/backend/jubeat/common.py @@ -5,83 +5,78 @@ from bemani.protocol import Node class JubeatLoggerReportHandler(JubeatBase): - def handle_logger_report_request(self, request: Node) -> Node: # Handle this by returning nothing, game doesn't care - root = Node.void('logger') + root = Node.void("logger") return root class JubeatDemodataGetNewsHandler(JubeatBase): - def handle_demodata_get_news_request(self, request: Node) -> Node: - demodata = Node.void('demodata') - data = Node.void('data') + demodata = Node.void("demodata") + data = Node.void("data") demodata.add_child(data) - officialnews = Node.void('officialnews') + officialnews = Node.void("officialnews") data.add_child(officialnews) - officialnews.set_attribute('count', '0') + officialnews.set_attribute("count", "0") return demodata class JubeatDemodataGetHitchartHandler(JubeatBase): - def handle_demodata_get_hitchart_request(self, request: Node) -> Node: - demodata = Node.void('demodata') - data = Node.void('data') + demodata = Node.void("demodata") + data = Node.void("data") demodata.add_child(data) # Not sure what this is, maybe date? - data.add_child(Node.string('update', time.strftime("%d/%m/%Y"))) + data.add_child(Node.string("update", time.strftime("%d/%m/%Y"))) # No idea which songs are licensed or regular, so only return hit chart # for all songs on regular mode. - hitchart_lic = Node.void('hitchart_lic') + hitchart_lic = Node.void("hitchart_lic") data.add_child(hitchart_lic) - hitchart_lic.set_attribute('count', '0') + hitchart_lic.set_attribute("count", "0") songs = self.data.local.music.get_hit_chart(self.game, self.music_version, 10) - hitchart_org = Node.void('hitchart_org') + hitchart_org = Node.void("hitchart_org") data.add_child(hitchart_org) - hitchart_org.set_attribute('count', str(len(songs))) + hitchart_org.set_attribute("count", str(len(songs))) rank = 1 for song in songs: - rankdata = Node.void('rankdata') + rankdata = Node.void("rankdata") hitchart_org.add_child(rankdata) - rankdata.add_child(Node.s32('music_id', song[0])) - rankdata.add_child(Node.s16('rank', rank)) - rankdata.add_child(Node.s16('prev', rank)) + rankdata.add_child(Node.s32("music_id", song[0])) + rankdata.add_child(Node.s16("rank", rank)) + rankdata.add_child(Node.s16("prev", rank)) rank = rank + 1 return demodata class JubeatLobbyCheckHandler(JubeatBase): - def handle_lobby_check_request(self, request: Node) -> Node: - root = Node.void('lobby') - data = Node.void('data') + root = Node.void("lobby") + data = Node.void("data") root.add_child(data) - data.add_child(Node.s16('interval', 0)) - data.add_child(Node.s16('entry_timeout', 0)) - entrant_nr = Node.u32('entrant_nr', 0) - entrant_nr.set_attribute('time', '0') + data.add_child(Node.s16("interval", 0)) + data.add_child(Node.s16("entry_timeout", 0)) + entrant_nr = Node.u32("entrant_nr", 0) + entrant_nr.set_attribute("time", "0") data.add_child(entrant_nr) return root class JubeatGamendRegisterHandler(JubeatBase): - def handle_gameend_regist_request(self, request: Node) -> Node: - data = request.child('data') - player = data.child('player') + data = request.child("data") + player = data.child("player") if player is not None: - refid = player.child_value('refid') + refid = player.child_value("refid") else: refid = None @@ -99,32 +94,31 @@ class JubeatGamendRegisterHandler(JubeatBase): if userid is not None and newprofile is not None: self.put_profile(userid, newprofile) - gameend = Node.void('gameend') - data = Node.void('data') + gameend = Node.void("gameend") + data = Node.void("data") gameend.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.s32('session_id', 1)) - player.add_child(Node.s32('end_final_session_id', 1)) + player.add_child(Node.s32("session_id", 1)) + player.add_child(Node.s32("end_final_session_id", 1)) return gameend class JubeatGametopGetMeetingHandler(JubeatBase): - def handle_gametop_get_meeting_request(self, request: Node) -> Node: - gametop = Node.void('gametop') - data = Node.void('data') + gametop = Node.void("gametop") + data = Node.void("data") gametop.add_child(data) - meeting = Node.void('meeting') + meeting = Node.void("meeting") data.add_child(meeting) - single = Node.void('single') + single = Node.void("single") meeting.add_child(single) - single.set_attribute('count', '0') - tag = Node.void('tag') + single.set_attribute("count", "0") + tag = Node.void("tag") meeting.add_child(tag) - tag.set_attribute('count', '0') - reward = Node.void('reward') + tag.set_attribute("count", "0") + reward = Node.void("reward") data.add_child(reward) - reward.add_child(Node.s32('total', -1)) - reward.add_child(Node.s32('point', -1)) + reward.add_child(Node.s32("total", -1)) + reward.add_child(Node.s32("point", -1)) return gametop diff --git a/bemani/backend/jubeat/course.py b/bemani/backend/jubeat/course.py index d4e045a..422b7cf 100644 --- a/bemani/backend/jubeat/course.py +++ b/bemani/backend/jubeat/course.py @@ -21,462 +21,462 @@ class JubeatCourse(JubeatBase): # List of base courses for Saucer Fulfill+ from BemaniWiki return [ { - 'id': 1, - 'name': '溢れ出した記憶、特別なあなたにありがとう。', - 'level': 1, - 'music': [ + "id": 1, + "name": "溢れ出した記憶、特別なあなたにありがとう。", + "level": 1, + "music": [ (50000241, 2), (10000052, 2), (30000042, 2), (50000085, 2), (50000144, 2), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_SCORE: [850000, 900000, 950000], self.COURSE_REQUIREMENT_FULL_COMBO: [0, 1, 2], }, }, { - 'id': 2, - 'name': 'コースモードが怖い?ばっかお前TAGがついてるだろ', - 'level': 1, - 'music': [ + "id": 2, + "name": "コースモードが怖い?ばっかお前TAGがついてるだろ", + "level": 1, + "music": [ (50000121, 1), (30000122, 1), (40000159, 1), (50000089, 1), (40000051, 2), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_SCORE: [800000, 850000, 900000], self.COURSE_REQUIREMENT_FULL_COMBO: [0, 1, 2], }, }, { - 'id': 3, - 'name': '満月の鐘踊り響くは虚空から成る恋の歌', - 'level': 2, - 'music': [ + "id": 3, + "name": "満月の鐘踊り響くは虚空から成る恋の歌", + "level": 2, + "music": [ (40000121, 2), (50000188, 2), (30000047, 2), (50000237, 2), (50000176, 2), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_SCORE: [850000, 900000, 950000], self.COURSE_REQUIREMENT_FULL_COMBO: [1, 2, 3], }, }, { - 'id': 4, - 'name': 'スミスゼミナール 夏の陣開講記念 基本編', - 'level': 2, - 'music': [ + "id": 4, + "name": "スミスゼミナール 夏の陣開講記念 基本編", + "level": 2, + "music": [ (50000267, 1), (50000233, 1), (50000228, 1), (50000268, 1), (50000291, 1), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_FULL_COMBO: [1, 2, 3], self.COURSE_REQUIREMENT_PERFECT_PERCENT: [85, 90, 95], }, }, { - 'id': 5, - 'name': 'HARDモードじゃないから、絶対、大丈夫だよっ!', - 'level': 2, - 'music': [ + "id": 5, + "name": "HARDモードじゃないから、絶対、大丈夫だよっ!", + "level": 2, + "music": [ (50000144, 2), (50000188, 2), (50000070, 2), (50000151, 2), (50000152, 2), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_SCORE: [850000, 900000, 950000], self.COURSE_REQUIREMENT_FULL_COMBO: [0, 1, 2], }, }, { - 'id': 6, - 'name': '星明かりの下、愛という名の日替わりランチを君と', - 'level': 3, - 'music': [ + "id": 6, + "name": "星明かりの下、愛という名の日替わりランチを君と", + "level": 3, + "music": [ (50000196, 1), (50000151, 2), (50000060, 1), (40000048, 2), (10000051, 2), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_PERFECT_PERCENT: [70, 80, 90], }, }, { - 'id': 7, - 'name': '輝く北極星と幸せなヒーロー', - 'level': 4, - 'music': [ + "id": 7, + "name": "輝く北極星と幸せなヒーロー", + "level": 4, + "music": [ (50000079, 2), (20000044, 2), (50000109, 2), (10000043, 2), (10000042, 2), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_SCORE: [900000, 950000, 980000], self.COURSE_REQUIREMENT_FULL_COMBO: [1, 2, 3], }, }, { - 'id': 8, - 'name': '花-鳥-藻-夏', - 'level': 4, - 'music': [ + "id": 8, + "name": "花-鳥-藻-夏", + "level": 4, + "music": [ (10000068, 2), (40000154, 2), (50000123, 1), (40000051, 2), (30000045, 2), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_PERFECT_PERCENT: [70, 80, 90], }, }, { - 'id': 9, - 'name': 'TAG生誕祭2014 俺の記録を抜いてみろ!', - 'level': 4, - 'music': [ + "id": 9, + "name": "TAG生誕祭2014 俺の記録を抜いてみろ!", + "level": 4, + "music": [ (30000122, 2), (50000086, 2), (50000121, 2), (50000196, 2), (40000051, 2), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_SCORE: [900000, 950000, 967252], self.COURSE_REQUIREMENT_FULL_COMBO: [0, 0, 1], }, }, { - 'id': 10, - 'name': 'さよなら、亡くした恋と蝶の舞うヒストリア', - 'level': 5, - 'music': [ + "id": 10, + "name": "さよなら、亡くした恋と蝶の舞うヒストリア", + "level": 5, + "music": [ (20000041, 2), (30000044, 2), (50000037, 2), (20000124, 2), (50000033, 2), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_PERFECT_PERCENT: [80, 85, 90], }, }, { - 'id': 11, - 'name': 'きらきらほしふるまぼろしなぎさちゃん', - 'level': 5, - 'music': [ + "id": 11, + "name": "きらきらほしふるまぼろしなぎさちゃん", + "level": 5, + "music": [ (30000050, 2), (30000049, 2), (50000235, 2), (50000157, 2), (50000038, 2), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_SCORE: [700000, 800000, 900000], }, }, { - 'id': 12, - 'name': 'The Memorial Third: 僕みたいに演奏してね', - 'level': 5, - 'music': [ + "id": 12, + "name": "The Memorial Third: 僕みたいに演奏してね", + "level": 5, + "music": [ (10000037, 2), (20000048, 1), (50000253, 1), (20000121, 2), (50000133, 2), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_PERFECT_PERCENT: [75, 80, 85], }, }, { - 'id': 13, - 'name': 'Enjoy! 4thKAC ~ Memories of saucer ~', - 'level': 5, - 'music': [ + "id": 13, + "name": "Enjoy! 4thKAC ~ Memories of saucer ~", + "level": 5, + "music": [ (50000206, 1), (50000023, 1), (50000078, 1), (50000203, 1), (50000323, 1), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_SCORE: [900000, 950000, 980000], self.COURSE_REQUIREMENT_FULL_COMBO: [1, 2, 4], }, }, { - 'id': 14, - 'name': '風に吹かれるキケンなシロクマダンス', - 'level': 6, - 'music': [ + "id": 14, + "name": "風に吹かれるキケンなシロクマダンス", + "level": 6, + "music": [ (50000059, 2), (50000197, 2), (30000037, 2), (50000182, 2), (20000038, 2), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_SCORE: [900000, 950000, 980000], self.COURSE_REQUIREMENT_FULL_COMBO: [1, 2, 3], }, }, { - 'id': 15, - 'name': '君主は視線で友との愛を語るめう', - 'level': 6, - 'music': [ + "id": 15, + "name": "君主は視線で友との愛を語るめう", + "level": 6, + "music": [ (40000052, 2), (50000152, 2), (50000090, 2), (20000040, 2), (50000184, 2), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_PERFECT_PERCENT: [85, 90, 95], }, }, { - 'id': 16, - 'name': 'スミスゼミナール 夏の陣開講記念 応用編', - 'level': 6, - 'music': [ + "id": 16, + "name": "スミスゼミナール 夏の陣開講記念 応用編", + "level": 6, + "music": [ (50000233, 2), (50000267, 2), (50000268, 2), (50000228, 2), (50000291, 2), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_SCORE: [750000, 850000, 900000], }, }, { - 'id': 17, - 'name': '天から降り注ぐ星はまるで甘いキャンディ', - 'level': 7, - 'music': [ + "id": 17, + "name": "天から降り注ぐ星はまるで甘いキャンディ", + "level": 7, + "music": [ (20000044, 2), (30000050, 2), (50000080, 2), (40000126, 2), (10000067, 2), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_PERFECT_PERCENT: [85, 90, 95], }, }, { - 'id': 18, - 'name': 'てんとう虫が囁いている「Wow Wow…」', - 'level': 7, - 'music': [ + "id": 18, + "name": "てんとう虫が囁いている「Wow Wow…」", + "level": 7, + "music": [ (50000132, 2), (40000128, 2), (10000036, 2), (50000119, 2), (50000030, 2), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_PERFECT_PERCENT: [85, 90, 95], }, }, { - 'id': 19, - 'name': 'HARDモードでも大丈夫だよ!絶対、大丈夫だよっ!', - 'level': 7, - 'music': [ + "id": 19, + "name": "HARDモードでも大丈夫だよ!絶対、大丈夫だよっ!", + "level": 7, + "music": [ (50000144, 2), (50000070, 2), (50000188, 2), (50000151, 2), (50000152, 2), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_SCORE: [850000, 900000, 950000], }, }, { - 'id': 20, - 'name': 'こんなHARDモード、滅べばいい…', - 'level': 7, - 'music': [ + "id": 20, + "name": "こんなHARDモード、滅べばいい…", + "level": 7, + "music": [ (50000294, 2), (50000295, 2), (50000234, 2), (50000245, 2), (50000282, 2), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_SCORE: [850000, 900000, 950000], }, }, { - 'id': 21, - 'name': 'Challenge! 4thKAC ~ Memories of saucer ~', - 'level': 7, - 'music': [ + "id": 21, + "name": "Challenge! 4thKAC ~ Memories of saucer ~", + "level": 7, + "music": [ (50000206, 2), (50000023, 2), (50000078, 2), (50000203, 2), (50000323, 2), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_SCORE: [900000, 950000, 980000], }, }, { - 'id': 22, - 'name': 'サヨナラ・キングコング ~ 恋のつぼみは愛の虹へ ~', - 'level': 8, - 'music': [ + "id": 22, + "name": "サヨナラ・キングコング ~ 恋のつぼみは愛の虹へ ~", + "level": 8, + "music": [ (50000148, 2), (50000101, 2), (10000064, 2), (50000171, 2), (50000070, 2), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_SCORE: [900000, 950000, 980000], }, }, { - 'id': 23, - 'name': '風に舞う白鳥の翼と花弁、さながら万華鏡のよう', - 'level': 8, - 'music': [ + "id": 23, + "name": "風に舞う白鳥の翼と花弁、さながら万華鏡のよう", + "level": 8, + "music": [ (30000036, 2), (50000122, 2), (10000062, 2), (50000199, 2), (40000153, 2), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_PERFECT_PERCENT: [90, 95, 98], }, }, { - 'id': 24, - 'name': 'The 小さなおぼろガチョウ♪', - 'level': 8, - 'music': [ + "id": 24, + "name": "The 小さなおぼろガチョウ♪", + "level": 8, + "music": [ (50000049, 2), (50000071, 2), (10000041, 2), (50000031, 2), (40000129, 2), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_SCORE: [970000, 980000, 990000], self.COURSE_REQUIREMENT_FULL_COMBO: [2, 3, 4], }, }, { - 'id': 25, - 'name': 'TAG生誕祭2014 俺の記録を抜いてみろ!~ HARD編 ~', - 'level': 8, - 'music': [ + "id": 25, + "name": "TAG生誕祭2014 俺の記録を抜いてみろ!~ HARD編 ~", + "level": 8, + "music": [ (50000089, 2), (50000083, 2), (50000210, 2), (50000030, 2), (40000159, 2), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_SCORE: [800000, 900000, 931463], }, }, { - 'id': 26, - 'name': '凍る世界で見る鳳凰の火の花', - 'level': 9, - 'music': [ + "id": 26, + "name": "凍る世界で見る鳳凰の火の花", + "level": 9, + "music": [ (30000043, 2), (10000039, 2), (20000048, 2), (50000096, 2), (20000038, 2), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_SCORE: [920000, 950000, 980000], }, }, { - 'id': 27, - 'name': '真実の桜が乱れしとき、キルト纏いし君は修羅となる', - 'level': 9, - 'music': [ + "id": 27, + "name": "真実の桜が乱れしとき、キルト纏いし君は修羅となる", + "level": 9, + "music": [ (50000113, 2), (50000184, 2), (50000177, 2), (30000124, 2), (50000078, 2), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_PERFECT_PERCENT: [80, 85, 90], }, }, { - 'id': 28, - 'name': 'THE FINAL01 ~ 雷光に月、乙女に花散る祝福を ~', - 'level': 10, - 'music': [ + "id": 28, + "name": "THE FINAL01 ~ 雷光に月、乙女に花散る祝福を ~", + "level": 10, + "music": [ (10000038, 2), (20000051, 2), (30000048, 2), (40000060, 2), (50000023, 2), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_SCORE: [920000, 950000, 980000], }, }, { - 'id': 29, - 'name': 'The Memorial Third: assimilated all into Nature', - 'level': 10, - 'music': [ + "id": 29, + "name": "The Memorial Third: assimilated all into Nature", + "level": 10, + "music": [ (50000135, 2), (50000029, 2), (40000047, 2), (40000046, 2), (50000253, 2), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_SCORE: [920000, 950000, 980000], }, }, { - 'id': 30, - 'name': '4thKAC ~ Memories of saucer ~', - 'level': 10, - 'music': [ + "id": 30, + "name": "4thKAC ~ Memories of saucer ~", + "level": 10, + "music": [ (50000206, 2), (50000023, 2), (50000078, 2), (50000203, 2), (50000323, 2), ], - 'requirements': { + "requirements": { self.COURSE_REQUIREMENT_SCORE: [920000, 950000, 980000], }, }, @@ -490,40 +490,40 @@ class JubeatCourse(JubeatBase): scores: List[int], ) -> None: if len(scores) != 5: - raise Exception('Invalid course scores list!') + raise Exception("Invalid course scores list!") if rating not in [ self.COURSE_RATING_FAILED, self.COURSE_RATING_BRONZE, self.COURSE_RATING_SILVER, self.COURSE_RATING_GOLD, ]: - raise Exception('Invalid course rating!') + raise Exception("Invalid course rating!") # Figure out if we should update the rating/scores or not oldcourse = self.data.local.game.get_achievement( self.game, userid, courseid, - 'course', + "course", ) if oldcourse is not None: # Update the rating if the user did better - rating = max(rating, oldcourse.get_int('rating')) + rating = max(rating, oldcourse.get_int("rating")) # Update the scores if the total score was better - if sum(scores) < sum(oldcourse.get_int_array('scores', 5)): - scores = oldcourse.get_int_array('scores', 5) + if sum(scores) < sum(oldcourse.get_int_array("scores", 5)): + scores = oldcourse.get_int_array("scores", 5) # Save it as an achievement self.data.local.game.put_achievement( self.game, userid, courseid, - 'course', + "course", { - 'rating': rating, - 'scores': scores, + "rating": rating, + "scores": scores, }, ) @@ -534,9 +534,9 @@ class JubeatCourse(JubeatBase): courses = {} achievements = self.data.local.game.get_achievements(self.game, userid) for achievement in achievements: - if achievement.type == 'course': + if achievement.type == "course": courses[achievement.id] = { - 'rating': achievement.data.get_int('rating'), - 'scores': achievement.data.get_int_array('scores', 5), + "rating": achievement.data.get_int("rating"), + "scores": achievement.data.get_int_array("scores", 5), } return courses diff --git a/bemani/backend/jubeat/factory.py b/bemani/backend/jubeat/factory.py index 0d807c7..44ae57a 100644 --- a/bemani/backend/jubeat/factory.py +++ b/bemani/backend/jubeat/factory.py @@ -42,29 +42,35 @@ class JubeatFactory(Factory): @classmethod def register_all(cls) -> None: - for gamecode in ['H44', 'I44', 'J44', 'K44', 'L44']: + for gamecode in ["H44", "I44", "J44", "K44", "L44"]: Base.register(gamecode, JubeatFactory) @classmethod - def create(cls, data: Data, config: Config, model: Model, parentmodel: Optional[Model]=None) -> Optional[Base]: - if model.gamecode == 'H44': + def create( + cls, + data: Data, + config: Config, + model: Model, + parentmodel: Optional[Model] = None, + ) -> Optional[Base]: + if model.gamecode == "H44": return Jubeat(data, config, model) - if model.gamecode == 'I44': + if model.gamecode == "I44": if model.version >= 2010031800: return JubeatRipplesAppend(data, config, model) else: return JubeatRipples(data, config, model) - if model.gamecode == 'J44': + if model.gamecode == "J44": if model.version >= 2011032300: return JubeatKnitAppend(data, config, model) else: return JubeatKnit(data, config, model) - if model.gamecode == 'K44': + if model.gamecode == "K44": if model.version >= 2012031400: return JubeatCopiousAppend(data, config, model) else: return JubeatCopious(data, config, model) - if model.gamecode == 'L44': + if model.gamecode == "L44": if model.version <= 2014022400: return JubeatSaucer(data, config, model) if model.version >= 2014030300 and model.version < 2015022000: diff --git a/bemani/backend/jubeat/festo.py b/bemani/backend/jubeat/festo.py index 2e000c1..933a41a 100644 --- a/bemani/backend/jubeat/festo.py +++ b/bemani/backend/jubeat/festo.py @@ -27,10 +27,10 @@ class JubeatFesto( JubeatGametopGetMeetingHandler, JubeatLobbyCheckHandler, JubeatLoggerReportHandler, - JubeatBase + JubeatBase, ): - name: str = 'Jubeat Festo' + name: str = "Jubeat Festo" version: int = VersionConstants.JUBEAT_FESTO JBOX_EMBLEM_NORMAL: Final[int] = 1 @@ -41,50 +41,50 @@ class JubeatFesto( EVENTS: Dict[int, Dict[str, bool]] = { 5: { - 'enabled': False, + "enabled": False, }, 6: { - 'enabled': False, + "enabled": False, }, # Something to do with maintenance mode? 15: { - 'enabled': True, + "enabled": True, }, 22: { - 'enabled': False, + "enabled": False, }, 23: { - 'enabled': False, + "enabled": False, }, 33: { - 'enabled': False, + "enabled": False, }, 101: { - 'enabled': False, + "enabled": False, }, 102: { - 'enabled': False, + "enabled": False, }, 103: { - 'enabled': False, + "enabled": False, }, 104: { - 'enabled': False, + "enabled": False, }, 105: { - 'enabled': False, + "enabled": False, }, 106: { - 'enabled': False, + "enabled": False, }, 107: { - 'enabled': False, + "enabled": False, }, 108: { - 'enabled': False, + "enabled": False, }, 109: { - 'enabled': False, + "enabled": False, }, } @@ -112,8 +112,8 @@ class JubeatFesto( # Return the netlog service so that Festo doesn't crash on coin-in. extra_services: List[str] = [ - 'netlog', - 'slocal', + "netlog", + "slocal", ] def previous_version(self) -> Optional[JubeatBase]: @@ -144,52 +144,69 @@ class JubeatFesto( }[db_chart] @classmethod - def run_scheduled_work(cls, data: Data, config: Dict[str, Any]) -> List[Tuple[str, Dict[str, Any]]]: + def run_scheduled_work( + cls, data: Data, config: Dict[str, Any] + ) -> List[Tuple[str, Dict[str, Any]]]: """ Insert daily FC challenges into the DB. """ events: List[Tuple[str, Dict[str, Any]]] = [] - if data.local.network.should_schedule(cls.game, cls.version, 'fc_challenge', 'daily'): + if data.local.network.should_schedule( + cls.game, cls.version, "fc_challenge", "daily" + ): # Generate a new list of two FC challenge songs. - start_time, end_time = data.local.network.get_schedule_duration('daily') - all_songs = set(song.id for song in data.local.music.get_all_songs(cls.game, cls.version)) + start_time, end_time = data.local.network.get_schedule_duration("daily") + all_songs = set( + song.id + for song in data.local.music.get_all_songs(cls.game, cls.version) + ) if len(all_songs) >= 2: daily_songs = random.sample(all_songs, 2) data.local.game.put_time_sensitive_settings( cls.game, cls.version, - 'fc_challenge', + "fc_challenge", { - 'start_time': start_time, - 'end_time': end_time, - 'today': daily_songs[0], - 'whim': daily_songs[1], + "start_time": start_time, + "end_time": end_time, + "today": daily_songs[0], + "whim": daily_songs[1], }, ) - events.append(( - 'jubeat_fc_challenge_charts', - { - 'version': cls.version, - 'today': daily_songs[0], - 'whim': daily_songs[1], - }, - )) + events.append( + ( + "jubeat_fc_challenge_charts", + { + "version": cls.version, + "today": daily_songs[0], + "whim": daily_songs[1], + }, + ) + ) # Mark that we did some actual work here. - data.local.network.mark_scheduled(cls.game, cls.version, 'fc_challenge', 'daily') - if data.local.network.should_schedule(cls.game, cls.version, 'random_course', 'daily'): + data.local.network.mark_scheduled( + cls.game, cls.version, "fc_challenge", "daily" + ) + if data.local.network.should_schedule( + cls.game, cls.version, "random_course", "daily" + ): # Generate a new list of three random songs for random course mode. - start_time, end_time = data.local.network.get_schedule_duration('daily') + start_time, end_time = data.local.network.get_schedule_duration("daily") def is_ten(song: Song) -> bool: # We only want random songs that actually have a hard chart! This should be all songs right now, # but let's be conservative in case a future game screws things up. - if song.chart not in {cls.CHART_TYPE_HARD_BASIC, cls.CHART_TYPE_HARD_ADVANCED, cls.CHART_TYPE_HARD_EXTREME}: + if song.chart not in { + cls.CHART_TYPE_HARD_BASIC, + cls.CHART_TYPE_HARD_ADVANCED, + cls.CHART_TYPE_HARD_EXTREME, + }: return False - difficulty = song.data.get_float('difficulty', 13) + difficulty = song.data.get_float("difficulty", 13) if difficulty == 13.0: - difficulty = float(song.data.get_int('difficulty', 13)) + difficulty = float(song.data.get_int("difficulty", 13)) return difficulty >= 10.0 and difficulty < 11.0 @@ -200,51 +217,59 @@ class JubeatFesto( cls.CHART_TYPE_HARD_EXTREME: cls.CHART_TYPE_EXTREME, }[chart] - all_tens = [song for song in data.local.music.get_all_songs(cls.game, cls.version) if is_ten(song)] + all_tens = [ + song + for song in data.local.music.get_all_songs(cls.game, cls.version) + if is_ten(song) + ] if len(all_tens) >= 3: course_songs = random.sample(all_tens, 3) data.local.game.put_time_sensitive_settings( cls.game, cls.version, - 'random_course', + "random_course", { - 'start_time': start_time, - 'end_time': end_time, - 'song1': { - 'id': course_songs[0].id, - 'chart': chart_lut(course_songs[0].chart), + "start_time": start_time, + "end_time": end_time, + "song1": { + "id": course_songs[0].id, + "chart": chart_lut(course_songs[0].chart), }, - 'song2': { - 'id': course_songs[1].id, - 'chart': chart_lut(course_songs[1].chart), + "song2": { + "id": course_songs[1].id, + "chart": chart_lut(course_songs[1].chart), }, - 'song3': { - 'id': course_songs[2].id, - 'chart': chart_lut(course_songs[2].chart), + "song3": { + "id": course_songs[2].id, + "chart": chart_lut(course_songs[2].chart), }, }, ) - events.append(( - 'jubeat_random_course_charts', - { - 'version': cls.version, - 'song1': { - 'id': course_songs[0].id, - 'chart': chart_lut(course_songs[0].chart), + events.append( + ( + "jubeat_random_course_charts", + { + "version": cls.version, + "song1": { + "id": course_songs[0].id, + "chart": chart_lut(course_songs[0].chart), + }, + "song2": { + "id": course_songs[1].id, + "chart": chart_lut(course_songs[1].chart), + }, + "song3": { + "id": course_songs[2].id, + "chart": chart_lut(course_songs[2].chart), + }, }, - 'song2': { - 'id': course_songs[1].id, - 'chart': chart_lut(course_songs[1].chart), - }, - 'song3': { - 'id': course_songs[2].id, - 'chart': chart_lut(course_songs[2].chart), - }, - }, - )) + ) + ) # Mark that we did some actual work here. - data.local.network.mark_scheduled(cls.game, cls.version, 'random_course', 'daily') + data.local.network.mark_scheduled( + cls.game, cls.version, "random_course", "daily" + ) return events @classmethod @@ -253,34 +278,34 @@ class JubeatFesto( Return all of our front-end modifiably settings. """ return { - 'ints': [ + "ints": [ { - 'name': 'KAC Course Phase', - 'tip': 'The KAC competition courses that should be available in Tune Run', - 'category': 'game_config', - 'setting': 'kac_phase', - 'values': { - 0: 'No KAC phase', - 1: 'The 8th KAC', - 2: 'The 9th KAC', - 3: 'The 10th KAC', - } + "name": "KAC Course Phase", + "tip": "The KAC competition courses that should be available in Tune Run", + "category": "game_config", + "setting": "kac_phase", + "values": { + 0: "No KAC phase", + 1: "The 8th KAC", + 2: "The 9th KAC", + 3: "The 10th KAC", + }, }, ], - 'bools': [ + "bools": [ { - 'name': 'Enable Stone Tablet Event', - 'tip': 'Enables the Stone Tablet event', - 'category': 'game_config', - 'setting': 'festo_dungeon', + "name": "Enable Stone Tablet Event", + "tip": "Enables the Stone Tablet event", + "category": "game_config", + "setting": "festo_dungeon", }, { - 'name': '50th Anniversary Celebration', - 'tip': 'Display the 50th anniversary screen in attract mode', - 'category': 'game_config', - 'setting': '50th_anniversary', + "name": "50th Anniversary Celebration", + "tip": "Display the 50th anniversary screen in attract mode", + "category": "game_config", + "setting": "50th_anniversary", }, - ] + ], } def __get_course_list(self) -> List[Dict[str, Any]]: @@ -294,28 +319,32 @@ class JubeatFesto( # If it is available, then grab the random course. If we haven't generated that course, then # just don't bother trying to create it. - entry = self.data.local.game.get_time_sensitive_settings(self.game, self.version, 'random_course') + entry = self.data.local.game.get_time_sensitive_settings( + self.game, self.version, "random_course" + ) random_course: List[Dict[str, Any]] = [] if entry is not None: - song1 = entry.get_dict('song1') - song2 = entry.get_dict('song2') - song3 = entry.get_dict('song3') + song1 = entry.get_dict("song1") + song2 = entry.get_dict("song2") + song3 = entry.get_dict("song3") - random_course = [{ - 'id': 57, - 'name': '腕試し!ランダムコース', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'hard': True, - 'difficulty': 15, - 'score': 2850000, - 'music': [ - [(song1.get_int('id'), song1.get_int('chart'))], - [(song2.get_int('id'), song2.get_int('chart'))], - [(song3.get_int('id'), song3.get_int('chart'))], - ], - }] + random_course = [ + { + "id": 57, + "name": "腕試し!ランダムコース", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "hard": True, + "difficulty": 15, + "score": 2850000, + "music": [ + [(song1.get_int("id"), song1.get_int("chart"))], + [(song2.get_int("id"), song2.get_int("chart"))], + [(song3.get_int("id"), song3.get_int("chart"))], + ], + } + ] # So the game has a hard limit of 60 courses, but if we include everything from the # lifetime of festo including the random course we end up with 61 courses, which means @@ -323,7 +352,7 @@ class JubeatFesto( # courses switched. Let's do the KAC courses since in real life there would never have # been multiple KAC courses available at once. game_config = self.get_game_config() - kac_phase = game_config.get_int('kac_phase') + kac_phase = game_config.get_int("kac_phase") kac_8th = [] kac_9th = [] kac_10th = [] @@ -331,30 +360,30 @@ class JubeatFesto( if kac_phase == 1: kac_8th = [ { - 'id': 41, - 'name': 'The 8th KAC 個人部門', - 'course_type': self.COURSE_TYPE_TIME_BASED, - 'end_time': Time.end_of_this_week() + Time.SECONDS_IN_WEEK, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'hard': True, - 'difficulty': 14, - 'score': 700000, - 'music': [ + "id": 41, + "name": "The 8th KAC 個人部門", + "course_type": self.COURSE_TYPE_TIME_BASED, + "end_time": Time.end_of_this_week() + Time.SECONDS_IN_WEEK, + "clear_type": self.COURSE_CLEAR_SCORE, + "hard": True, + "difficulty": 14, + "score": 700000, + "music": [ [(90000052, 2)], [(90000013, 2)], [(70000167, 2)], ], }, { - 'id': 42, - 'name': 'The 8th KAC 団体部門', - 'course_type': self.COURSE_TYPE_TIME_BASED, - 'end_time': Time.end_of_this_week() + Time.SECONDS_IN_WEEK, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'hard': True, - 'difficulty': 14, - 'score': 700000, - 'music': [ + "id": 42, + "name": "The 8th KAC 団体部門", + "course_type": self.COURSE_TYPE_TIME_BASED, + "end_time": Time.end_of_this_week() + Time.SECONDS_IN_WEEK, + "clear_type": self.COURSE_CLEAR_SCORE, + "hard": True, + "difficulty": 14, + "score": 700000, + "music": [ [(90000009, 2)], [(80000133, 2)], [(80000101, 2)], @@ -364,60 +393,60 @@ class JubeatFesto( elif kac_phase == 2: kac_9th = [ { - 'id': 201, - 'name': 'The 9th KAC 1st Stage 個人部門', - 'course_type': self.COURSE_TYPE_TIME_BASED, - 'end_time': Time.end_of_this_week() + Time.SECONDS_IN_WEEK, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'hard': True, - 'difficulty': 14, - 'score': 700000, - 'music': [ + "id": 201, + "name": "The 9th KAC 1st Stage 個人部門", + "course_type": self.COURSE_TYPE_TIME_BASED, + "end_time": Time.end_of_this_week() + Time.SECONDS_IN_WEEK, + "clear_type": self.COURSE_CLEAR_SCORE, + "hard": True, + "difficulty": 14, + "score": 700000, + "music": [ [(90000125, 2)], [(60000065, 2)], [(90000023, 2)], ], }, { - 'id': 202, - 'name': 'The 9th KAC 1st Stage 団体部門', - 'course_type': self.COURSE_TYPE_TIME_BASED, - 'end_time': Time.end_of_this_week() + Time.SECONDS_IN_WEEK, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'hard': True, - 'difficulty': 14, - 'score': 700000, - 'music': [ + "id": 202, + "name": "The 9th KAC 1st Stage 団体部門", + "course_type": self.COURSE_TYPE_TIME_BASED, + "end_time": Time.end_of_this_week() + Time.SECONDS_IN_WEEK, + "clear_type": self.COURSE_CLEAR_SCORE, + "hard": True, + "difficulty": 14, + "score": 700000, + "music": [ [(90000125, 2)], [(50000135, 2)], [(90000045, 2)], ], }, { - 'id': 203, - 'name': 'The 9th KAC 2nd Stage 個人部門', - 'course_type': self.COURSE_TYPE_TIME_BASED, - 'end_time': Time.end_of_this_week() + Time.SECONDS_IN_WEEK, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'hard': True, - 'difficulty': 14, - 'score': 700000, - 'music': [ + "id": 203, + "name": "The 9th KAC 2nd Stage 個人部門", + "course_type": self.COURSE_TYPE_TIME_BASED, + "end_time": Time.end_of_this_week() + Time.SECONDS_IN_WEEK, + "clear_type": self.COURSE_CLEAR_SCORE, + "hard": True, + "difficulty": 14, + "score": 700000, + "music": [ [(90000095, 2)], [(80000085, 2)], [(80000090, 2)], ], }, { - 'id': 204, - 'name': 'The 9th KAC 2nd Stage 団体部門', - 'course_type': self.COURSE_TYPE_TIME_BASED, - 'end_time': Time.end_of_this_week() + Time.SECONDS_IN_WEEK, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'hard': True, - 'difficulty': 14, - 'score': 700000, - 'music': [ + "id": 204, + "name": "The 9th KAC 2nd Stage 団体部門", + "course_type": self.COURSE_TYPE_TIME_BASED, + "end_time": Time.end_of_this_week() + Time.SECONDS_IN_WEEK, + "clear_type": self.COURSE_CLEAR_SCORE, + "hard": True, + "difficulty": 14, + "score": 700000, + "music": [ [(90000113, 2)], [(50000344, 2)], [(90000096, 2)], @@ -427,30 +456,30 @@ class JubeatFesto( elif kac_phase == 3: kac_10th = [ { - 'id': 205, - 'name': 'The 10th KAC 1st Stage', - 'course_type': self.COURSE_TYPE_TIME_BASED, - 'end_time': Time.end_of_this_week() + Time.SECONDS_IN_WEEK, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'hard': True, - 'difficulty': 14, - 'score': 700000, - 'music': [ + "id": 205, + "name": "The 10th KAC 1st Stage", + "course_type": self.COURSE_TYPE_TIME_BASED, + "end_time": Time.end_of_this_week() + Time.SECONDS_IN_WEEK, + "clear_type": self.COURSE_CLEAR_SCORE, + "hard": True, + "difficulty": 14, + "score": 700000, + "music": [ [(90000003, 2)], [(90000151, 2)], [(90000174, 2)], ], }, { - 'id': 206, - 'name': 'The 10th KAC 2nd Stage', - 'course_type': self.COURSE_TYPE_TIME_BASED, - 'end_time': Time.end_of_this_week() + Time.SECONDS_IN_WEEK, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'hard': True, - 'difficulty': 14, - 'score': 700000, - 'music': [ + "id": 206, + "name": "The 10th KAC 2nd Stage", + "course_type": self.COURSE_TYPE_TIME_BASED, + "end_time": Time.end_of_this_week() + Time.SECONDS_IN_WEEK, + "clear_type": self.COURSE_CLEAR_SCORE, + "hard": True, + "difficulty": 14, + "score": 700000, + "music": [ [(90000121, 2)], [(90000113, 2)], [(90000124, 2)], @@ -461,13 +490,13 @@ class JubeatFesto( return [ # ASARI CUP { - 'id': 1, - 'name': 'はじめてのビーチ', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 1, - 'score': 700000, - 'music': [ + "id": 1, + "name": "はじめてのビーチ", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 1, + "score": 700000, + "music": [ [ (60000080, 0), (90000025 if dataver < 2021081600 else 90000077, 0), @@ -478,15 +507,20 @@ class JubeatFesto( ], }, { - 'id': 2, - 'name': '【初段】超幸せハイテンション', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 1, - 'score': 700000, - 'music': [ + "id": 2, + "name": "【初段】超幸せハイテンション", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 1, + "score": 700000, + "music": [ [ - (70000057 if dataver < 2019062100 else (90000079 if dataver < 2022021600 else 20000031), 0), + ( + 70000057 + if dataver < 2019062100 + else (90000079 if dataver < 2022021600 else 20000031), + 0, + ), (60000100, 0), (90000030 if dataver < 2021081600 else 90000078, 0), ], @@ -495,20 +529,34 @@ class JubeatFesto( ], }, { - 'id': 3, - 'name': 'アニメランニング', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 2, - 'score': 750000, - 'music': [ + "id": 3, + "name": "アニメランニング", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 2, + "score": 750000, + "music": [ [ - (80000020 if dataver < 2019062100 else (90000082 if dataver < 2022021600 else 60000092), 0), + ( + 80000020 + if dataver < 2019062100 + else (90000082 if dataver < 2022021600 else 60000092), + 0, + ), (90000031, 0), (90000037 if dataver < 2021081600 else 90000172, 0), ], [ - (80000034 if dataver < 2020062900 else (30000108 if dataver < 2020091300 else (40000107 if dataver < 2021020100 else 30000004)), 0), + ( + 80000034 + if dataver < 2020062900 + else ( + 30000108 + if dataver < 2020091300 + else (40000107 if dataver < 2021020100 else 30000004) + ), + 0, + ), (80000120 if dataver < 2021020200 else 80000059, 0), ], [ @@ -517,15 +565,20 @@ class JubeatFesto( ], }, { - 'id': 4, - 'name': 'パブリックリゾート', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 2, - 'score': 750000, - 'music': [ + "id": 4, + "name": "パブリックリゾート", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 2, + "score": 750000, + "music": [ [ - (70000148 if dataver < 2020021900 else (90000040 if dataver < 2021081600 else 80000097), 0), + ( + 70000148 + if dataver < 2020021900 + else (90000040 if dataver < 2021081600 else 80000097), + 0, + ), (50000296 if dataver < 2021081600 else 90000029, 0), (90000044 if dataver < 2021081600 else 90000076, 0), ], @@ -539,27 +592,27 @@ class JubeatFesto( ], }, { - 'id': 5, - 'name': '【二段】その笑顔は甘く蕩ける', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 3, - 'score': 800000, - 'music': [ + "id": 5, + "name": "【二段】その笑顔は甘く蕩ける", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 3, + "score": 800000, + "music": [ [(50000268, 0), (70000039, 0), (90000051, 0)], [(70000091, 0), (70000042, 0)], [(60000053, 0)], ], }, { - 'id': 6, - 'name': '#オレのユビティズム', - 'course_type': self.COURSE_TYPE_TIME_BASED, - 'end_time': Time.end_of_this_week() + Time.SECONDS_IN_WEEK, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 3, - 'score': 2100000, - 'music': [ + "id": 6, + "name": "#オレのユビティズム", + "course_type": self.COURSE_TYPE_TIME_BASED, + "end_time": Time.end_of_this_week() + Time.SECONDS_IN_WEEK, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 3, + "score": 2100000, + "music": [ [(20000042, 0), (20000042, 1), (20000042, 2)], [(70000119, 0), (70000119, 1), (70000119, 2)], [(50000115, 0), (50000115, 1), (50000115, 2)], @@ -567,13 +620,13 @@ class JubeatFesto( }, # KISAGO CUP { - 'id': 11, - 'name': '電脳享受空間', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 4, - 'score': 800000, - 'music': [ + "id": 11, + "name": "電脳享受空間", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 4, + "score": 800000, + "music": [ [ (70000046, 1), (70000160, 1), @@ -584,49 +637,64 @@ class JubeatFesto( ], }, { - 'id': 12, - 'name': '孤高の少女は破滅を願う', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 4, - 'score': 850000, - 'music': [ + "id": 12, + "name": "孤高の少女は破滅を願う", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 4, + "score": 850000, + "music": [ [(50000202, 0), (70000117, 0), (70000134, 0)], [(50000212, 0), (80000124, 1)], [(90001008, 1)], ], }, { - 'id': 13, - 'name': 'スタミナアップ!', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 5, - 'score': 2600000, - 'music': [ + "id": 13, + "name": "スタミナアップ!", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 5, + "score": 2600000, + "music": [ [ (50000242, 0), - (80000034 if dataver < 2020063000 else (90000079 if dataver < 2022021600 else 50000277), 1), + ( + 80000034 + if dataver < 2020063000 + else (90000079 if dataver < 2022021600 else 50000277), + 1, + ), (90000037 if dataver < 2021081600 else 50000294, 1), ], [(50000260, 1), (50000261, 1)], [ - (70000085 if dataver < 2019062100 else (90000081 if dataver < 2022021600 else 90000143), 1), + ( + 70000085 + if dataver < 2019062100 + else (90000081 if dataver < 2022021600 else 90000143), + 1, + ), ], ], }, { - 'id': 14, - 'name': '【三段】この花を貴方へ', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 4, - 'score': 850000, - 'music': [ + "id": 14, + "name": "【三段】この花を貴方へ", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 4, + "score": 850000, + "music": [ [ (20000111 if dataver < 2019062100 else 90000034, 1), (90000037 if dataver < 2021081600 else 90000107, 1), - (70000131 if dataver < 2019111800 else (90000042 if dataver < 2021081600 else 90000140), 1), + ( + 70000131 + if dataver < 2019111800 + else (90000042 if dataver < 2021081600 else 90000140), + 1, + ), ], [ (80000120 if dataver < 2021020200 else 80000052, 1), @@ -636,144 +704,144 @@ class JubeatFesto( ], }, { - 'id': 15, - 'name': '【四段】嗚呼、大繁盛!', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 6, - 'score': 2600000, - 'music': [ + "id": 15, + "name": "【四段】嗚呼、大繁盛!", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 6, + "score": 2600000, + "music": [ [(50000085, 2), (50000237, 2), (80000080, 2)], [(50000172, 2), (50000235, 2)], - [(70000065, 2)] + [(70000065, 2)], ], }, { - 'id': 16, - 'name': '#シャレを言いなシャレ', - 'course_type': self.COURSE_TYPE_TIME_BASED, - 'end_time': Time.end_of_this_week() + Time.SECONDS_IN_WEEK, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 4, - 'score': 2400000, - 'music': [ + "id": 16, + "name": "#シャレを言いなシャレ", + "course_type": self.COURSE_TYPE_TIME_BASED, + "end_time": Time.end_of_this_week() + Time.SECONDS_IN_WEEK, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 4, + "score": 2400000, + "music": [ [(70000003, 0), (70000003, 1), (70000003, 2)], [(70000045, 0), (70000045, 1), (70000045, 2)], [(70000076, 0), (70000076, 1), (70000076, 2)], ], }, { - 'id': 101, - 'name': 'jubeat大回顧展 ROOM 1', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 4, - 'score': 950000, - 'music': [ + "id": 101, + "name": "jubeat大回顧展 ROOM 1", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 4, + "score": 950000, + "music": [ [(50000277, 0), (50000277, 1), (50000277, 2)], [(50000325, 0), (50000325, 1), (50000325, 2)], [(90000014, 0), (90000014, 1), (90000014, 2)], ], }, { - 'id': 102, - 'name': 'jubeat大回顧展 ROOM 2', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 4, - 'score': 2750000, - 'music': [ + "id": 102, + "name": "jubeat大回顧展 ROOM 2", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 4, + "score": 2750000, + "music": [ [(30000048, 0), (30000048, 1), (30000048, 2)], [(30000121, 0), (30000121, 1), (30000121, 2)], [(90000012, 0), (90000012, 1), (90000012, 2)], ], }, { - 'id': 103, - 'name': 'jubeat大回顧展 ROOM 3', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 4, - 'score': 925000, - 'music': [ + "id": 103, + "name": "jubeat大回顧展 ROOM 3", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 4, + "score": 925000, + "music": [ [(60000007, 0), (60000007, 1), (60000007, 2)], [(60000070, 0), (60000070, 1), (60000070, 2)], [(90000016, 0), (90000016, 1), (90000016, 2)], ], }, { - 'id': 104, - 'name': 'jubeat大回顧展 ROOM 4', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 4, - 'score': 2800000, - 'music': [ + "id": 104, + "name": "jubeat大回顧展 ROOM 4", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 4, + "score": 2800000, + "music": [ [(40000051, 0), (40000051, 1), (40000051, 2)], [(40000129, 0), (40000129, 1), (40000129, 2)], [(90000013, 0), (90000013, 1), (90000013, 2)], ], }, { - 'id': 105, - 'name': 'jubeat大回顧展 ROOM 5', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 4, - 'score': 2775000, - 'music': [ + "id": 105, + "name": "jubeat大回顧展 ROOM 5", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 4, + "score": 2775000, + "music": [ [(70000177, 0), (70000177, 1), (70000177, 2)], [(70000011, 0), (70000011, 1), (70000011, 2)], [(90000017, 0), (90000017, 1), (90000017, 2)], ], }, { - 'id': 106, - 'name': 'jubeat大回顧展 ROOM 6', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 4, - 'score': 940000, - 'music': [ + "id": 106, + "name": "jubeat大回顧展 ROOM 6", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 4, + "score": 940000, + "music": [ [(20000123, 0), (20000123, 1), (20000123, 2)], [(20000038, 0), (20000038, 1), (20000038, 2)], [(90000011, 0), (90000011, 1), (90000011, 2)], ], }, { - 'id': 107, - 'name': 'jubeat大回顧展 ROOM 7', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 4, - 'score': 950000, - 'music': [ + "id": 107, + "name": "jubeat大回顧展 ROOM 7", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 4, + "score": 950000, + "music": [ [(50000021, 0), (50000021, 1), (50000021, 2)], [(50000078, 0), (50000078, 1), (50000078, 2)], [(90000015, 0), (90000015, 1), (90000015, 2)], ], }, { - 'id': 108, - 'name': 'jubeat大回顧展 ROOM 8', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 4, - 'score': 2800000, - 'music': [ + "id": 108, + "name": "jubeat大回顧展 ROOM 8", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 4, + "score": 2800000, + "music": [ [(80000028, 0), (80000028, 1), (80000028, 2)], [(80000087, 0), (80000087, 1), (80000087, 2)], [(90000018, 0), (90000018, 1), (90000018, 2)], ], }, { - 'id': 109, - 'name': 'jubeat大回顧展 ROOM 9', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 4, - 'score': 930000, - 'music': [ + "id": 109, + "name": "jubeat大回顧展 ROOM 9", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 4, + "score": 930000, + "music": [ [(10000038, 0), (10000038, 1), (10000038, 2)], [(10000065, 0), (10000065, 1), (10000065, 2)], [(90000010, 0), (90000010, 1), (90000010, 2)], @@ -781,26 +849,26 @@ class JubeatFesto( }, # MURU CUP { - 'id': 21, - 'name': '黒船来航', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 7, - 'score': 850000, - 'music': [ + "id": 21, + "name": "黒船来航", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 7, + "score": 850000, + "music": [ [(50000086, 2), (60000066, 2), (80000040, 1)], [(50000096, 2), (80000048, 2)], [(50000091, 2)], ], }, { - 'id': 22, - 'name': '【五段】濁流を乗り越えて', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 7, - 'score': 2650000, - 'music': [ + "id": 22, + "name": "【五段】濁流を乗り越えて", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 7, + "score": 2650000, + "music": [ [ (50000343, 2), (60000060, 2), @@ -811,13 +879,13 @@ class JubeatFesto( ], }, { - 'id': 23, - 'name': 'のんびり。ゆったり。ほがらかに。', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 8, - 'score': 950000, - 'music': [ + "id": 23, + "name": "のんびり。ゆったり。ほがらかに。", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 8, + "score": 950000, + "music": [ [ (40000154, 2), (80000124, 2), @@ -831,68 +899,68 @@ class JubeatFesto( ], }, { - 'id': 24, - 'name': '海・KOI・スィニョーレ!!', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 8, - 'score': 2650000, - 'music': [ + "id": 24, + "name": "海・KOI・スィニョーレ!!", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 8, + "score": 2650000, + "music": [ [(50000201, 2)], [(50000339, 2)], [(50000038, 2)], ], }, { - 'id': 25, - 'name': '【六段】電柱を見ると思出す', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 9, - 'score': 2750000, - 'music': [ + "id": 25, + "name": "【六段】電柱を見ると思出す", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 9, + "score": 2750000, + "music": [ [(50000288, 2), (80000046, 2), (80001008, 2)], [(50000207, 2), (70000117, 2)], [(30000048, 2)], ], }, { - 'id': 26, - 'name': '雨上がりレインボー', - 'course_type': self.COURSE_TYPE_TIME_BASED, - 'end_time': Time.end_of_this_week() + Time.SECONDS_IN_WEEK, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 9, - 'score': 2650000, - 'music': [ + "id": 26, + "name": "雨上がりレインボー", + "course_type": self.COURSE_TYPE_TIME_BASED, + "end_time": Time.end_of_this_week() + Time.SECONDS_IN_WEEK, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 9, + "score": 2650000, + "music": [ [(50000138, 2)], [(80000057, 2)], [(90000011, 2)], ], }, { - 'id': 27, - 'name': 'Rain時々雨ノチ雨', - 'course_type': self.COURSE_TYPE_TIME_BASED, - 'end_time': Time.end_of_this_week() + Time.SECONDS_IN_WEEK, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 9, - 'score': 2650000, - 'music': [ + "id": 27, + "name": "Rain時々雨ノチ雨", + "course_type": self.COURSE_TYPE_TIME_BASED, + "end_time": Time.end_of_this_week() + Time.SECONDS_IN_WEEK, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 9, + "score": 2650000, + "music": [ [(30000050, 2)], [(80000123, 2)], [(50000092, 2)], ], }, { - 'id': 28, - 'name': '#心に残った曲', - 'course_type': self.COURSE_TYPE_TIME_BASED, - 'end_time': Time.end_of_this_week() + Time.SECONDS_IN_WEEK, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 7, - 'score': 2700000, - 'music': [ + "id": 28, + "name": "#心に残った曲", + "course_type": self.COURSE_TYPE_TIME_BASED, + "end_time": Time.end_of_this_week() + Time.SECONDS_IN_WEEK, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 7, + "score": 2700000, + "music": [ [(80000136, 0), (80000136, 1), (80000136, 2)], [(20000038, 0), (20000038, 1), (20000038, 2)], [(60000065, 0), (60000065, 1), (70000084, 1)], @@ -900,80 +968,80 @@ class JubeatFesto( }, # SAZAE CUP { - 'id': 31, - 'name': '超フェスタ!', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 10, - 'score': 930000, - 'music': [ + "id": 31, + "name": "超フェスタ!", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 10, + "score": 930000, + "music": [ [(70000076, 2), (70000077, 2)], [(20000038, 2), (40000160, 2)], [(70000145, 2)], ], }, { - 'id': 32, - 'name': '【七段】操り人形はほくそ笑む', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 10, - 'score': 2800000, - 'music': [ + "id": 32, + "name": "【七段】操り人形はほくそ笑む", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 10, + "score": 2800000, + "music": [ [(70000006, 2), (70000171, 2), (80000003, 2)], [(50000078, 2), (50000324, 2)], [(80000118, 2)], ], }, { - 'id': 33, - 'name': '絶体絶命スリーチャレンジ!', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_HAZARD, - 'hazard_type': self.COURSE_HAZARD_FC3, - 'difficulty': 11, - 'music': [ + "id": 33, + "name": "絶体絶命スリーチャレンジ!", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_HAZARD, + "hazard_type": self.COURSE_HAZARD_FC3, + "difficulty": 11, + "music": [ [(50000238, 2), (70000003, 2), (90000051, 1)], [(50000027, 2), (50000387, 2)], [(80000056, 2)], ], }, { - 'id': 34, - 'name': '天国の舞踏会', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 11, - 'score': 2800000, - 'music': [ + "id": 34, + "name": "天国の舞踏会", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 11, + "score": 2800000, + "music": [ [(60000065, 1)], [(80001007, 2)], [(90001007, 2)], ], }, { - 'id': 35, - 'name': '【八段】山の賽子', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 12, - 'score': 2820000, - 'music': [ + "id": 35, + "name": "【八段】山の賽子", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 12, + "score": 2820000, + "music": [ [(50000200, 2), (50000291, 2), (60000003, 2)], [(50000129, 2), (80000021, 2)], [(80000087, 2)], ], }, { - 'id': 36, - 'name': '#コクがある曲', - 'course_type': self.COURSE_TYPE_TIME_BASED, - 'end_time': Time.end_of_this_week() + Time.SECONDS_IN_WEEK, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'hard': True, - 'difficulty': 12, - 'score': 2400000, - 'music': [ + "id": 36, + "name": "#コクがある曲", + "course_type": self.COURSE_TYPE_TIME_BASED, + "end_time": Time.end_of_this_week() + Time.SECONDS_IN_WEEK, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "hard": True, + "difficulty": 12, + "score": 2400000, + "music": [ [(50000139, 0), (50000139, 1), (50000139, 2)], [(90000002, 0), (90000002, 1), (90000002, 2)], [(50000060, 0), (50000060, 1), (50000060, 2)], @@ -982,15 +1050,15 @@ class JubeatFesto( # HOTATE CUP *kac_8th, # Contains courses 41 and 42. { - 'id': 43, - 'name': 'BEMANI MASTER KOREA 2019', - 'course_type': self.COURSE_TYPE_TIME_BASED, - 'end_time': Time.end_of_this_week() + Time.SECONDS_IN_WEEK, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'hard': True, - 'difficulty': 14, - 'score': 700000, - 'music': [ + "id": 43, + "name": "BEMANI MASTER KOREA 2019", + "course_type": self.COURSE_TYPE_TIME_BASED, + "end_time": Time.end_of_this_week() + Time.SECONDS_IN_WEEK, + "clear_type": self.COURSE_CLEAR_SCORE, + "hard": True, + "difficulty": 14, + "score": 700000, + "music": [ [(90000003, 2)], [(80000090, 2)], [(90000009, 2)], @@ -999,123 +1067,123 @@ class JubeatFesto( *kac_9th, # Contains courses 201, 202, 203 and 204. *kac_10th, # Contains courses 205 and 206. { - 'id': 207, - 'name': '#どうやって押してる?', - 'course_type': self.COURSE_TYPE_TIME_BASED, - 'end_time': Time.end_of_this_week() + Time.SECONDS_IN_WEEK, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'hard': True, - 'difficulty': 13, - 'score': 2600000, - 'music': [ + "id": 207, + "name": "#どうやって押してる?", + "course_type": self.COURSE_TYPE_TIME_BASED, + "end_time": Time.end_of_this_week() + Time.SECONDS_IN_WEEK, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "hard": True, + "difficulty": 13, + "score": 2600000, + "music": [ [(40000127, 0)], [(50000123, 0)], [(50000126, 0)], ], }, { - 'id': 208, - 'name': 'BEMANI MASTER KOREA 2021', - 'course_type': self.COURSE_TYPE_TIME_BASED, - 'end_time': Time.end_of_this_week() + Time.SECONDS_IN_WEEK, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'hard': True, - 'difficulty': 14, - 'score': 700000, - 'music': [ + "id": 208, + "name": "BEMANI MASTER KOREA 2021", + "course_type": self.COURSE_TYPE_TIME_BASED, + "end_time": Time.end_of_this_week() + Time.SECONDS_IN_WEEK, + "clear_type": self.COURSE_CLEAR_SCORE, + "hard": True, + "difficulty": 14, + "score": 700000, + "music": [ [(90000180, 2)], [(90000095, 2)], [(90000047, 2)], ], }, { - 'id': 44, - 'name': '初めてのHARD MODE再び', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'hard': True, - 'difficulty': 13, - 'score': 2750000, - 'music': [ + "id": 44, + "name": "初めてのHARD MODE再び", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "hard": True, + "difficulty": 13, + "score": 2750000, + "music": [ [(50000096, 2), (50000263, 2), (80000119, 2)], [(60000021, 2), (60000075, 2)], [(60000039, 2)], ], }, { - 'id': 45, - 'name': '【九段】2人からの挑戦状', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 13, - 'score': 2830000, - 'music': [ + "id": 45, + "name": "【九段】2人からの挑戦状", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 13, + "score": 2830000, + "music": [ [(50000023, 2), (80000025, 2), (80000106, 2)], [(50000124, 2), (80000082, 2)], [(60000115, 2)], ], }, { - 'id': 46, - 'name': '天空の庭 太陽の園', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'difficulty': 13, - 'score': 965000, - 'music': [ + "id": 46, + "name": "天空の庭 太陽の園", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "difficulty": 13, + "score": 965000, + "music": [ [(40000153, 2)], [(80000007, 2)], [(70000173, 2)], ], }, { - 'id': 47, - 'name': '緊急!迅速!大混乱!', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 14, - 'score': 2900000, - 'music': [ + "id": 47, + "name": "緊急!迅速!大混乱!", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 14, + "score": 2900000, + "music": [ [(20000040, 2), (50000244, 2), (60000074, 2)], [(40000152, 2), (50000158, 2)], [(40000057, 2)], ], }, { - 'id': 48, - 'name': '【十段】時の超越者', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'hard': True, - 'difficulty': 14, - 'score': 2820000, - 'music': [ + "id": 48, + "name": "【十段】時の超越者", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "hard": True, + "difficulty": 14, + "score": 2820000, + "music": [ [(20000051, 2), (50000249, 2), (70000145, 2)], [(40000046, 2), (50000180, 2)], [(50000134, 2)], ], }, { - 'id': 49, - 'name': '【伝導】10代目最強に挑戦!', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 14, - 'score': 2998179, - 'music': [ + "id": 49, + "name": "【伝導】10代目最強に挑戦!", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 14, + "score": 2998179, + "music": [ [(50000100, 2)], [(90000047, 2)], [(90000057, 2)], ], }, { - 'id': 110, - 'name': 'jubeat大回顧展 ROOM 10', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'difficulty': 13, - 'score': 2850000, - 'music': [ + "id": 110, + "name": "jubeat大回顧展 ROOM 10", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "difficulty": 13, + "score": 2850000, + "music": [ [(30000127, 2)], [(60000078, 2)], [(90000047, 2)], @@ -1123,84 +1191,84 @@ class JubeatFesto( }, # OSHAKO CUP { - 'id': 51, - 'name': '【皆伝】甘味なのに甘くない', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'hard': True, - 'difficulty': 15, - 'score': 2850000, - 'music': [ + "id": 51, + "name": "【皆伝】甘味なのに甘くない", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "hard": True, + "difficulty": 15, + "score": 2850000, + "music": [ [(90000010, 2)], [(80000101, 2)], [(50000102, 2)], ], }, { - 'id': 52, - 'name': '【伝導】真の青が魅せた空', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_SCORE, - 'hard': True, - 'difficulty': 15, - 'score': 970000, - 'music': [ + "id": 52, + "name": "【伝導】真の青が魅せた空", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_SCORE, + "hard": True, + "difficulty": 15, + "score": 970000, + "music": [ [(50000332, 2)], [(70000098, 2)], [(90001005, 2)], ], }, { - 'id': 53, - 'name': '豪華絢爛高揚絶頂', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'hard': True, - 'difficulty': 16, - 'score': 2960000, - 'music': [ + "id": 53, + "name": "豪華絢爛高揚絶頂", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "hard": True, + "difficulty": 16, + "score": 2960000, + "music": [ [(10000065, 2)], [(50000323, 2)], [(50000208, 2)], ], }, { - 'id': 54, - 'name': '絢爛豪華激情無常', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'hard': True, - 'difficulty': 16, - 'score': 2960000, - 'music': [ + "id": 54, + "name": "絢爛豪華激情無常", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "hard": True, + "difficulty": 16, + "score": 2960000, + "music": [ [(60000010, 2)], [(70000110, 2)], [(90000047, 2)], ], }, { - 'id': 55, - 'name': '【指神】王の降臨', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'hard': True, - 'difficulty': 16, - 'score': 2980000, - 'music': [ + "id": 55, + "name": "【指神】王の降臨", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "hard": True, + "difficulty": 16, + "score": 2980000, + "music": [ [(70000094, 2)], [(80000088, 2)], [(70000110, 2)], ], }, { - 'id': 56, - 'name': '【伝導】1116全てを超越した日', - 'course_type': self.COURSE_TYPE_PERMANENT, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'hard': True, - 'difficulty': 16, - 'score': 2975000, - 'music': [ + "id": 56, + "name": "【伝導】1116全てを超越した日", + "course_type": self.COURSE_TYPE_PERMANENT, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "hard": True, + "difficulty": 16, + "score": 2975000, + "music": [ [(50000208, 2)], [(80000050, 2)], [(90000057, 2)], @@ -1208,15 +1276,15 @@ class JubeatFesto( }, *random_course, # Contains course 57. { - 'id': 58, - 'name': '#あなたのjubeatはどこから?', - 'course_type': self.COURSE_TYPE_TIME_BASED, - 'end_time': Time.end_of_this_week() + Time.SECONDS_IN_WEEK, - 'clear_type': self.COURSE_CLEAR_COMBINED_SCORE, - 'hard': True, - 'difficulty': 15, - 'score': 2900000, - 'music': [ + "id": 58, + "name": "#あなたのjubeatはどこから?", + "course_type": self.COURSE_TYPE_TIME_BASED, + "end_time": Time.end_of_this_week() + Time.SECONDS_IN_WEEK, + "clear_type": self.COURSE_CLEAR_COMBINED_SCORE, + "hard": True, + "difficulty": 15, + "score": 2900000, + "music": [ [(10000065, 0), (10000065, 1), (10000065, 2)], [(30000048, 0), (30000048, 1), (30000048, 2)], [(90000047, 0), (90000047, 1), (90000047, 2)], @@ -1225,41 +1293,91 @@ class JubeatFesto( ] def __get_global_info(self) -> Node: - info = Node.void('info') + info = Node.void("info") # Event info. - event_info = Node.void('event_info') + event_info = Node.void("event_info") info.add_child(event_info) for event in self.EVENTS: - evt = Node.void('event') + evt = Node.void("event") event_info.add_child(evt) - evt.set_attribute('type', str(event)) - evt.add_child(Node.u8('state', 1 if self.EVENTS[event]['enabled'] else 0)) + evt.set_attribute("type", str(event)) + evt.add_child(Node.u8("state", 1 if self.EVENTS[event]["enabled"] else 0)) - genre_def_music = Node.void('genre_def_music') + genre_def_music = Node.void("genre_def_music") info.add_child(genre_def_music) - info.add_child(Node.s32_array( - 'black_jacket_list', - [ - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - ], - )) + info.add_child( + Node.s32_array( + "black_jacket_list", + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + ) + ) # Mapping of what music is allowed by default, if this is set to all 0's # then the game will crash because it can't figure out what default song @@ -1267,221 +1385,482 @@ class JubeatFesto( # bits is for in any music_list array below is to look at the "pos_index" # field in the music_info.xml file. The entry in the array is calculated by # "(pos_index / 32)" and the bit to set is "1 << (pos_index % 32)" - info.add_child(Node.s32_array( - 'white_music_list', - [ - -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, -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, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - ], - )) + info.add_child( + Node.s32_array( + "white_music_list", + [ + -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, + -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, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], + ) + ) # Mapping of what markers and themes are allowed for profile customization # by default. If this is set to all 0's then there are no markers or themes # offered and the default marker is forced. - info.add_child(Node.s32_array( - 'white_marker_list', - [ - -1, -1, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - ], - )) + info.add_child( + Node.s32_array( + "white_marker_list", + [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], + ) + ) - info.add_child(Node.s32_array( - 'white_theme_list', - [ - -1, -1, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - ], - )) + info.add_child( + Node.s32_array( + "white_theme_list", + [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], + ) + ) # Possibly default unlocks for songs. Need to investigate further. - info.add_child(Node.s32_array( - 'open_music_list', - [ - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - ], - )) + info.add_child( + Node.s32_array( + "open_music_list", + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + ) + ) - info.add_child(Node.s32_array( - 'shareable_music_list', - [ - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - ], - )) + info.add_child( + Node.s32_array( + "shareable_music_list", + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + ) + ) # Bitfield that determines what music is considered "pick up" versus "common" on the jubility # jacket field at the end of the game. Hot music is just the music in the current mix. The # bitfield values were taken from the "pos_index" field for all songs that are in festo. - info.add_child(Node.s32_array( - 'hot_music_list', - [ - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, -4194304, -2080769, -1, - -17, -3, -33554433, -242, - -268435473, 1073741823, -1073748992, 15, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - ], - )) + info.add_child( + Node.s32_array( + "hot_music_list", + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -4194304, + -2080769, + -1, + -17, + -3, + -33554433, + -242, + -268435473, + 1073741823, + -1073748992, + 15, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + ) + ) - jbox = Node.void('jbox') + jbox = Node.void("jbox") info.add_child(jbox) - jbox.add_child(Node.s32('point', 0)) - emblem = Node.void('emblem') + jbox.add_child(Node.s32("point", 0)) + emblem = Node.void("emblem") jbox.add_child(emblem) - normal = Node.void('normal') + normal = Node.void("normal") emblem.add_child(normal) - premium = Node.void('premium') + premium = Node.void("premium") emblem.add_child(premium) - normal.add_child(Node.s16('index', 2)) - premium.add_child(Node.s16('index', 1)) + normal.add_child(Node.s16("index", 2)) + premium.add_child(Node.s16("index", 1)) - born = Node.void('born') + born = Node.void("born") info.add_child(born) - born.add_child(Node.s8('status', 0)) - born.add_child(Node.s16('year', 0)) + born.add_child(Node.s8("status", 0)) + born.add_child(Node.s16("year", 0)) game_config = self.get_game_config() - konami_logo_50th = Node.void('konami_logo_50th') + konami_logo_50th = Node.void("konami_logo_50th") info.add_child(konami_logo_50th) - konami_logo_50th.add_child(Node.bool('is_available', game_config.get_bool('50th_anniversary'))) + konami_logo_50th.add_child( + Node.bool("is_available", game_config.get_bool("50th_anniversary")) + ) - expert_option = Node.void('expert_option') + expert_option = Node.void("expert_option") info.add_child(expert_option) - expert_option.add_child(Node.bool('is_available', True)) + expert_option.add_child(Node.bool("is_available", True)) - all_music_matching = Node.void('all_music_matching') + all_music_matching = Node.void("all_music_matching") info.add_child(all_music_matching) - all_music_matching.add_child(Node.bool('is_available', True)) + all_music_matching.add_child(Node.bool("is_available", True)) - department = Node.void('department') + department = Node.void("department") info.add_child(department) - department.add_child(Node.void('shop_list')) + department.add_child(Node.void("shop_list")) - question_list = Node.void('question_list') + question_list = Node.void("question_list") info.add_child(question_list) # Set up TUNE RUN course requirements - clan_course_list = Node.void('course_list') + clan_course_list = Node.void("course_list") info.add_child(clan_course_list) valid_courses: Set[int] = set() dataver = self.model.version or 2022052400 for course in self.__get_course_list(): - if course['id'] < 1: - raise Exception(f"Invalid course ID {course['id']} found in course list!") - if course['id'] in valid_courses: + if course["id"] < 1: + raise Exception( + f"Invalid course ID {course['id']} found in course list!" + ) + if course["id"] in valid_courses: raise Exception(f"Duplicate ID {course['id']} found in course list!") - if course['clear_type'] == self.COURSE_CLEAR_HAZARD and 'hazard_type' not in course: + if ( + course["clear_type"] == self.COURSE_CLEAR_HAZARD + and "hazard_type" not in course + ): raise Exception(f"Need 'hazard_type' set in course {course['id']}!") - if course['course_type'] == self.COURSE_TYPE_TIME_BASED and 'end_time' not in course: + if ( + course["course_type"] == self.COURSE_TYPE_TIME_BASED + and "end_time" not in course + ): raise Exception(f"Need 'end_time' set in course {course['id']}!") - if course['clear_type'] in [self.COURSE_CLEAR_SCORE, self.COURSE_CLEAR_COMBINED_SCORE] and 'score' not in course: + if ( + course["clear_type"] + in [self.COURSE_CLEAR_SCORE, self.COURSE_CLEAR_COMBINED_SCORE] + and "score" not in course + ): raise Exception(f"Need 'score' set in course {course['id']}!") - if course['clear_type'] == self.COURSE_CLEAR_SCORE and course['score'] > 1000000: + if ( + course["clear_type"] == self.COURSE_CLEAR_SCORE + and course["score"] > 1000000 + ): raise Exception(f"Invalid per-coure score in course {course['id']}!") - if course['clear_type'] == self.COURSE_CLEAR_COMBINED_SCORE and course['score'] <= 1000000: + if ( + course["clear_type"] == self.COURSE_CLEAR_COMBINED_SCORE + and course["score"] <= 1000000 + ): raise Exception(f"Invalid combined score in course {course['id']}!") - valid_courses.add(course['id']) + valid_courses.add(course["id"]) # Basics - clan_course = Node.void('course') + clan_course = Node.void("course") clan_course_list.add_child(clan_course) - clan_course.set_attribute('release_code', str(dataver)) - clan_course.set_attribute('version_id', '0') - clan_course.set_attribute('id', str(course['id'])) - clan_course.set_attribute('course_type', str(course['course_type'])) - clan_course.add_child(Node.s32('difficulty', course['difficulty'])) - clan_course.add_child(Node.u64('etime', (course['end_time'] if 'end_time' in course else 0) * 1000)) - clan_course.add_child(Node.string('name', course['name'])) + clan_course.set_attribute("release_code", str(dataver)) + clan_course.set_attribute("version_id", "0") + clan_course.set_attribute("id", str(course["id"])) + clan_course.set_attribute("course_type", str(course["course_type"])) + clan_course.add_child(Node.s32("difficulty", course["difficulty"])) + clan_course.add_child( + Node.u64( + "etime", (course["end_time"] if "end_time" in course else 0) * 1000 + ) + ) + clan_course.add_child(Node.string("name", course["name"])) # List of included songs - tune_list = Node.void('tune_list') + tune_list = Node.void("tune_list") clan_course.add_child(tune_list) - for order, charts in enumerate(course['music']): - tune = Node.void('tune') + for order, charts in enumerate(course["music"]): + tune = Node.void("tune") tune_list.add_child(tune) - tune.set_attribute('no', str(order + 1)) + tune.set_attribute("no", str(order + 1)) - seq_list = Node.void('seq_list') + seq_list = Node.void("seq_list") tune.add_child(seq_list) for songid, chart in charts: - seq = Node.void('seq') + seq = Node.void("seq") seq_list.add_child(seq) - seq.add_child(Node.s32('music_id', songid)) - seq.add_child(Node.s32('difficulty', chart)) - seq.add_child(Node.bool('is_secret', False)) + seq.add_child(Node.s32("music_id", songid)) + seq.add_child(Node.s32("difficulty", chart)) + seq.add_child(Node.bool("is_secret", False)) # Clear criteria - clear = Node.void('clear') + clear = Node.void("clear") clan_course.add_child(clear) - ex_option = Node.void('ex_option') + ex_option = Node.void("ex_option") clear.add_child(ex_option) - ex_option.add_child(Node.bool('is_hard', course['hard'] if 'hard' in course else False)) - ex_option.add_child(Node.s32('hazard_type', course['hazard_type'] if 'hazard_type' in course else 0)) - clear.set_attribute('type', str(course['clear_type'])) - clear.add_child(Node.s32('score', course['score'] if 'score' in course else 0)) + ex_option.add_child( + Node.bool("is_hard", course["hard"] if "hard" in course else False) + ) + ex_option.add_child( + Node.s32( + "hazard_type", + course["hazard_type"] if "hazard_type" in course else 0, + ) + ) + clear.set_attribute("type", str(course["clear_type"])) + clear.add_child( + Node.s32("score", course["score"] if "score" in course else 0) + ) - reward_list = Node.void('reward_list') + reward_list = Node.void("reward_list") clear.add_child(reward_list) # Each of the following two sections should have zero or more child nodes (no @@ -1492,229 +1871,333 @@ class JubeatFesto( # end time? # # Share music? - share_music = Node.void('share_music') + share_music = Node.void("share_music") info.add_child(share_music) - weekly_music = Node.void('weekly_music') + weekly_music = Node.void("weekly_music") info.add_child(weekly_music) weekly_music.add_child(Node.s32("value", 0)) - info.add_child(Node.s32_array( - 'add_default_music_list', - [ - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - ], - )) + info.add_child( + Node.s32_array( + "add_default_music_list", + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + ) + ) # The following section should have zero or more child nodes (no particular # name) which look like the following, with a song ID in the node's id attribute: # - weekly_music_list = Node.void('music_list') + weekly_music_list = Node.void("music_list") weekly_music.add_child(weekly_music_list) # Enable/disable festo dungeon. - if game_config.get_bool('festo_dungeon'): - festo_dungeon = Node.void('festo_dungeon') + if game_config.get_bool("festo_dungeon"): + festo_dungeon = Node.void("festo_dungeon") info.add_child(festo_dungeon) - festo_dungeon.add_child(Node.u64('etime', (Time.now() + Time.SECONDS_IN_WEEK) * 1000)) + festo_dungeon.add_child( + Node.u64("etime", (Time.now() + Time.SECONDS_IN_WEEK) * 1000) + ) # Unsupported team_battle nodes. - info.add_child(Node.void('team_battle')) + info.add_child(Node.void("team_battle")) # Unsupported EMO list for EMO shop. - info.add_child(Node.void('emo_list')) + info.add_child(Node.void("emo_list")) # Unsupported hike_event. - info.add_child(Node.void('hike_event')) + info.add_child(Node.void("hike_event")) # Unsupported tip_list, this probably lets the server control the tips between songs. - info.add_child(Node.void('tip_list')) + info.add_child(Node.void("tip_list")) # Unsupported mission travel event, which is very server-controlled. - info.add_child(Node.void('travel')) + info.add_child(Node.void("travel")) # Unsupported stamp rally event, since this poorly undocumented. - info.add_child(Node.void('stamp')) + info.add_child(Node.void("stamp")) return info def handle_shopinfo_regist_request(self, request: Node) -> Node: # Update the name of this cab for admin purposes - self.update_machine_name(request.child_value('shop/name')) + self.update_machine_name(request.child_value("shop/name")) - shopinfo = Node.void('shopinfo') + shopinfo = Node.void("shopinfo") - data = Node.void('data') + data = Node.void("data") shopinfo.add_child(data) - data.add_child(Node.u32('cabid', 1)) - data.add_child(Node.string('locationid', 'nowhere')) - data.add_child(Node.u8('tax_phase', 1)) + data.add_child(Node.u32("cabid", 1)) + data.add_child(Node.string("locationid", "nowhere")) + data.add_child(Node.u8("tax_phase", 1)) - facility = Node.void('facility') + facility = Node.void("facility") data.add_child(facility) - facility.add_child(Node.u32('exist', 1)) + facility.add_child(Node.u32("exist", 1)) data.add_child(self.__get_global_info()) return shopinfo def handle_demodata_get_info_request(self, request: Node) -> Node: - root = Node.void('demodata') - data = Node.void('data') + root = Node.void("demodata") + data = Node.void("data") root.add_child(data) - info = Node.void('info') + info = Node.void("info") data.add_child(info) # This is the same stuff set in the common info, so if we ever do make this # configurable, I think we'll need to return the same thing in both spots. - info.add_child(Node.s32_array( - 'black_jacket_list', - [ - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - ], - )) + info.add_child( + Node.s32_array( + "black_jacket_list", + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + ) + ) return root def handle_demodata_get_jbox_list_request(self, request: Node) -> Node: - root = Node.void('demodata') + root = Node.void("demodata") return root def handle_jbox_get_agreement_request(self, request: Node) -> Node: - root = Node.void('jbox') - root.add_child(Node.bool('is_agreement', True)) + root = Node.void("jbox") + root.add_child(Node.bool("is_agreement", True)) return root def handle_jbox_get_list_request(self, request: Node) -> Node: - root = Node.void('jbox') - root.add_child(Node.void('selection_list')) + root = Node.void("jbox") + root.add_child(Node.void("selection_list")) return root def handle_ins_netlog_request(self, request: Node) -> Node: - root = Node.void('ins') + root = Node.void("ins") return root def handle_lab_get_ranking_request(self, request: Node) -> Node: - root = Node.void('lab') - root.add_child(Node.s8('category', request.child_value('category') or 0)) + root = Node.void("lab") + root.add_child(Node.s8("category", request.child_value("category") or 0)) - entries = Node.void('entries') + entries = Node.void("entries") root.add_child(entries) # The game allows up to 10 entries, each looking like this: # # XXXXXXXXX # - entries.set_attribute('count', '0') + entries.set_attribute("count", "0") return root def handle_recommend_get_recommend_request(self, request: Node) -> Node: - recommend = Node.void('recommend') - data = Node.void('data') + recommend = Node.void("recommend") + data = Node.void("data") recommend.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - music_list = Node.void('music_list') + music_list = Node.void("music_list") player.add_child(music_list) # TODO: Might be a way to figure out who plays what song and then offer # recommendations based on that. There should be 12 songs returned here. recommended_songs: List[Song] = [] for i, song in enumerate(recommended_songs): - music = Node.void('music') + music = Node.void("music") music_list.add_child(music) - music.set_attribute('order', str(i)) - music.add_child(Node.s32('music_id', song.id)) - music.add_child(Node.s8('seq', song.chart)) + music.set_attribute("order", str(i)) + music.add_child(Node.s32("music_id", song.id)) + music.add_child(Node.s8("seq", song.chart)) return recommend def handle_gametop_get_info_request(self, request: Node) -> Node: - root = Node.void('gametop') - data = Node.void('data') + root = Node.void("gametop") + data = Node.void("data") root.add_child(data) data.add_child(self.__get_global_info()) return root def handle_gametop_regist_request(self, request: Node) -> Node: - data = request.child('data') - player = data.child('player') - refid = player.child_value('refid') - name = player.child_value('name') + data = request.child("data") + player = data.child("player") + refid = player.child_value("refid") + name = player.child_value("name") root = self.new_profile_by_refid(refid, name) return root def handle_gametop_get_pdata_request(self, request: Node) -> Node: - data = request.child('data') - player = data.child('player') - refid = player.child_value('refid') + data = request.child("data") + player = data.child("player") + refid = player.child_value("refid") root = self.get_profile_by_refid(refid) if root is None: - root = Node.void('gametop') - root.set_attribute('status', str(Status.NO_PROFILE)) + root = Node.void("gametop") + root.set_attribute("status", str(Status.NO_PROFILE)) return root def handle_gametop_get_mdata_request(self, request: Node) -> Node: - data = request.child('data') - player = data.child('player') - extid = player.child_value('jid') - mdata_ver = player.child_value('mdata_ver') # Game requests mdata 3 times per profile for some reason + data = request.child("data") + player = data.child("player") + extid = player.child_value("jid") + mdata_ver = player.child_value( + "mdata_ver" + ) # Game requests mdata 3 times per profile for some reason if mdata_ver != 1: - root = Node.void('gametop') - datanode = Node.void('data') + root = Node.void("gametop") + datanode = Node.void("data") root.add_child(datanode) - player = Node.void('player') + player = Node.void("player") datanode.add_child(player) - player.add_child(Node.s32('jid', extid)) - playdata = Node.void('mdata_list') + player.add_child(Node.s32("jid", extid)) + playdata = Node.void("mdata_list") player.add_child(playdata) return root root = self.get_scores_by_extid(extid) if root is None: - root = Node.void('gametop') - root.set_attribute('status', str(Status.NO_PROFILE)) + root = Node.void("gametop") + root.set_attribute("status", str(Status.NO_PROFILE)) return root def handle_gameend_final_request(self, request: Node) -> Node: - data = request.child('data') - player = data.child('player') + data = request.child("data") + player = data.child("player") if player is not None: - refid = player.child_value('refid') + refid = player.child_value("refid") else: refid = None @@ -1727,256 +2210,371 @@ class JubeatFesto( profile = self.get_profile(userid) # Grab unlock progress - item = player.child('item') + item = player.child("item") if item is not None: - profile.replace_int_array('emblem_list', 96, item.child_value('emblem_list')) + profile.replace_int_array( + "emblem_list", 96, item.child_value("emblem_list") + ) # jbox stuff - jbox = player.child('jbox') - jboxdict = profile.get_dict('jbox') + jbox = player.child("jbox") + jboxdict = profile.get_dict("jbox") if jbox is not None: - jboxdict.replace_int('point', jbox.child_value('point')) - emblemtype = jbox.child_value('emblem/type') - index = jbox.child_value('emblem/index') + jboxdict.replace_int("point", jbox.child_value("point")) + emblemtype = jbox.child_value("emblem/type") + index = jbox.child_value("emblem/index") if emblemtype == self.JBOX_EMBLEM_NORMAL: - jboxdict.replace_int('normal_index', index) + jboxdict.replace_int("normal_index", index) elif emblemtype == self.JBOX_EMBLEM_PREMIUM: - jboxdict.replace_int('premium_index', index) - profile.replace_dict('jbox', jboxdict) + jboxdict.replace_int("premium_index", index) + profile.replace_dict("jbox", jboxdict) # Born stuff - born = player.child('born') + born = player.child("born") if born is not None: - profile.replace_int('born_status', born.child_value('status')) - profile.replace_int('born_year', born.child_value('year')) + profile.replace_int("born_status", born.child_value("status")) + profile.replace_int("born_year", born.child_value("year")) else: profile = None if userid is not None and profile is not None: self.put_profile(userid, profile) - return Node.void('gameend') + return Node.void("gameend") - def format_scores(self, userid: UserID, profile: Profile, scores: List[Score]) -> Node: - root = Node.void('gametop') - datanode = Node.void('data') + def format_scores( + self, userid: UserID, profile: Profile, scores: List[Score] + ) -> Node: + root = Node.void("gametop") + datanode = Node.void("data") root.add_child(datanode) - player = Node.void('player') + player = Node.void("player") datanode.add_child(player) - player.add_child(Node.s32('jid', profile.extid)) - playdata = Node.void('mdata_list') + player.add_child(Node.s32("jid", profile.extid)) + playdata = Node.void("mdata_list") player.add_child(playdata) music = ValidatedDict() for score in scores: chart = self.db_to_game_chart(score.chart) - if score.chart in {self.CHART_TYPE_HARD_BASIC, self.CHART_TYPE_HARD_ADVANCED, self.CHART_TYPE_HARD_EXTREME}: - prefix = 'hard' + if score.chart in { + self.CHART_TYPE_HARD_BASIC, + self.CHART_TYPE_HARD_ADVANCED, + self.CHART_TYPE_HARD_EXTREME, + }: + prefix = "hard" else: - prefix = 'normal' + prefix = "normal" data = music.get_dict(str(score.id)) - play_cnt = data.get_int_array(f'{prefix}_play_cnt', 3) - clear_cnt = data.get_int_array(f'{prefix}_clear_cnt', 3) - clear_flags = data.get_int_array(f'{prefix}_clear_flags', 3) - fc_cnt = data.get_int_array(f'{prefix}_fc_cnt', 3) - ex_cnt = data.get_int_array(f'{prefix}_ex_cnt', 3) - points = data.get_int_array(f'{prefix}_points', 3) - music_rate = data.get_int_array(f'{prefix}_music_rate', 3) + play_cnt = data.get_int_array(f"{prefix}_play_cnt", 3) + clear_cnt = data.get_int_array(f"{prefix}_clear_cnt", 3) + clear_flags = data.get_int_array(f"{prefix}_clear_flags", 3) + fc_cnt = data.get_int_array(f"{prefix}_fc_cnt", 3) + ex_cnt = data.get_int_array(f"{prefix}_ex_cnt", 3) + points = data.get_int_array(f"{prefix}_points", 3) + music_rate = data.get_int_array(f"{prefix}_music_rate", 3) # Replace data for this chart type play_cnt[chart] = score.plays - clear_cnt[chart] = score.data.get_int('clear_count') - fc_cnt[chart] = score.data.get_int('full_combo_count') - ex_cnt[chart] = score.data.get_int('excellent_count') + clear_cnt[chart] = score.data.get_int("clear_count") + fc_cnt[chart] = score.data.get_int("full_combo_count") + ex_cnt[chart] = score.data.get_int("excellent_count") points[chart] = score.points - music_rate[chart] = score.data.get_int('music_rate') + music_rate[chart] = score.data.get_int("music_rate") # Format the clear flags clear_flags[chart] = self.GAME_FLAG_BIT_PLAYED - if score.data.get_int('clear_count') > 0: + if score.data.get_int("clear_count") > 0: clear_flags[chart] |= self.GAME_FLAG_BIT_CLEARED - if score.data.get_int('full_combo_count') > 0: + if score.data.get_int("full_combo_count") > 0: clear_flags[chart] |= self.GAME_FLAG_BIT_FULL_COMBO - if score.data.get_int('excellent_count') > 0: + if score.data.get_int("excellent_count") > 0: clear_flags[chart] |= self.GAME_FLAG_BIT_EXCELLENT # Save chart data back - data.replace_int_array(f'{prefix}_play_cnt', 3, play_cnt) - data.replace_int_array(f'{prefix}_clear_cnt', 3, clear_cnt) - data.replace_int_array(f'{prefix}_clear_flags', 3, clear_flags) - data.replace_int_array(f'{prefix}_fc_cnt', 3, fc_cnt) - data.replace_int_array(f'{prefix}_ex_cnt', 3, ex_cnt) - data.replace_int_array(f'{prefix}_points', 3, points) - data.replace_int_array(f'{prefix}_music_rate', 3, music_rate) + data.replace_int_array(f"{prefix}_play_cnt", 3, play_cnt) + data.replace_int_array(f"{prefix}_clear_cnt", 3, clear_cnt) + data.replace_int_array(f"{prefix}_clear_flags", 3, clear_flags) + data.replace_int_array(f"{prefix}_fc_cnt", 3, fc_cnt) + data.replace_int_array(f"{prefix}_ex_cnt", 3, ex_cnt) + data.replace_int_array(f"{prefix}_points", 3, points) + data.replace_int_array(f"{prefix}_music_rate", 3, music_rate) # Update the ghost (untyped) - ghost = data.get(f'{prefix}_ghost', [None, None, None]) - ghost[chart] = score.data.get('ghost') - data[f'{prefix}_ghost'] = ghost + ghost = data.get(f"{prefix}_ghost", [None, None, None]) + ghost[chart] = score.data.get("ghost") + data[f"{prefix}_ghost"] = ghost # Save it back music.replace_dict(str(score.id), data) for scoreid in music: scoredata = music.get_dict(scoreid) - musicdata = Node.void('musicdata') + musicdata = Node.void("musicdata") playdata.add_child(musicdata) - musicdata.set_attribute('music_id', scoreid) + musicdata.set_attribute("music_id", scoreid) # Since in the worst case, we could be wasting a lot of data by always sending both a normal and hard mode block # we need to check if there's even a score array worth sending. This should help with performance for larger # score databases. - if scoredata.get_int_array('normal_play_cnt', 3) != [0, 0, 0]: - normalnode = Node.void('normal') + if scoredata.get_int_array("normal_play_cnt", 3) != [0, 0, 0]: + normalnode = Node.void("normal") musicdata.add_child(normalnode) - normalnode.add_child(Node.s32_array('play_cnt', scoredata.get_int_array('normal_play_cnt', 3))) - normalnode.add_child(Node.s32_array('clear_cnt', scoredata.get_int_array('normal_clear_cnt', 3))) - normalnode.add_child(Node.s32_array('fc_cnt', scoredata.get_int_array('normal_fc_cnt', 3))) - normalnode.add_child(Node.s32_array('ex_cnt', scoredata.get_int_array('normal_ex_cnt', 3))) - normalnode.add_child(Node.s32_array('score', scoredata.get_int_array('normal_points', 3))) - normalnode.add_child(Node.s8_array('clear', scoredata.get_int_array('normal_clear_flags', 3))) - normalnode.add_child(Node.s32_array('music_rate', scoredata.get_int_array('normal_music_rate', 3))) + normalnode.add_child( + Node.s32_array( + "play_cnt", scoredata.get_int_array("normal_play_cnt", 3) + ) + ) + normalnode.add_child( + Node.s32_array( + "clear_cnt", scoredata.get_int_array("normal_clear_cnt", 3) + ) + ) + normalnode.add_child( + Node.s32_array( + "fc_cnt", scoredata.get_int_array("normal_fc_cnt", 3) + ) + ) + normalnode.add_child( + Node.s32_array( + "ex_cnt", scoredata.get_int_array("normal_ex_cnt", 3) + ) + ) + normalnode.add_child( + Node.s32_array("score", scoredata.get_int_array("normal_points", 3)) + ) + normalnode.add_child( + Node.s8_array( + "clear", scoredata.get_int_array("normal_clear_flags", 3) + ) + ) + normalnode.add_child( + Node.s32_array( + "music_rate", scoredata.get_int_array("normal_music_rate", 3) + ) + ) - for i, ghost in enumerate(scoredata.get('normal_ghost', [None, None, None])): + for i, ghost in enumerate( + scoredata.get("normal_ghost", [None, None, None]) + ): if ghost is None: continue - bar = Node.u8_array('bar', ghost) + bar = Node.u8_array("bar", ghost) normalnode.add_child(bar) - bar.set_attribute('seq', str(i)) + bar.set_attribute("seq", str(i)) - if scoredata.get_int_array('hard_play_cnt', 3) != [0, 0, 0]: - hardnode = Node.void('hard') + if scoredata.get_int_array("hard_play_cnt", 3) != [0, 0, 0]: + hardnode = Node.void("hard") musicdata.add_child(hardnode) - hardnode.add_child(Node.s32_array('play_cnt', scoredata.get_int_array('hard_play_cnt', 3))) - hardnode.add_child(Node.s32_array('clear_cnt', scoredata.get_int_array('hard_clear_cnt', 3))) - hardnode.add_child(Node.s32_array('fc_cnt', scoredata.get_int_array('hard_fc_cnt', 3))) - hardnode.add_child(Node.s32_array('ex_cnt', scoredata.get_int_array('hard_ex_cnt', 3))) - hardnode.add_child(Node.s32_array('score', scoredata.get_int_array('hard_points', 3))) - hardnode.add_child(Node.s8_array('clear', scoredata.get_int_array('hard_clear_flags', 3))) - hardnode.add_child(Node.s32_array('music_rate', scoredata.get_int_array('hard_music_rate', 3))) + hardnode.add_child( + Node.s32_array( + "play_cnt", scoredata.get_int_array("hard_play_cnt", 3) + ) + ) + hardnode.add_child( + Node.s32_array( + "clear_cnt", scoredata.get_int_array("hard_clear_cnt", 3) + ) + ) + hardnode.add_child( + Node.s32_array("fc_cnt", scoredata.get_int_array("hard_fc_cnt", 3)) + ) + hardnode.add_child( + Node.s32_array("ex_cnt", scoredata.get_int_array("hard_ex_cnt", 3)) + ) + hardnode.add_child( + Node.s32_array("score", scoredata.get_int_array("hard_points", 3)) + ) + hardnode.add_child( + Node.s8_array( + "clear", scoredata.get_int_array("hard_clear_flags", 3) + ) + ) + hardnode.add_child( + Node.s32_array( + "music_rate", scoredata.get_int_array("hard_music_rate", 3) + ) + ) - for i, ghost in enumerate(scoredata.get('hard_ghost', [None, None, None])): + for i, ghost in enumerate( + scoredata.get("hard_ghost", [None, None, None]) + ): if ghost is None: continue - bar = Node.u8_array('bar', ghost) + bar = Node.u8_array("bar", ghost) hardnode.add_child(bar) - bar.set_attribute('seq', str(i)) + bar.set_attribute("seq", str(i)) return root def format_profile(self, userid: UserID, profile: Profile) -> Node: - root = Node.void('gametop') - data = Node.void('data') + root = Node.void("gametop") + data = Node.void("data") root.add_child(data) # Jubeat Clan appears to allow full event overrides per-player data.add_child(self.__get_global_info()) - player = Node.void('player') + player = Node.void("player") data.add_child(player) # Basic profile info - player.add_child(Node.string('name', profile.get_str('name', 'なし'))) - player.add_child(Node.s32('jid', profile.extid)) + player.add_child(Node.string("name", profile.get_str("name", "なし"))) + player.add_child(Node.s32("jid", profile.extid)) # Miscelaneous crap - player.add_child(Node.s32('session_id', 1)) - player.add_child(Node.u64('event_flag', profile.get_int('event_flag'))) + player.add_child(Node.s32("session_id", 1)) + player.add_child(Node.u64("event_flag", profile.get_int("event_flag"))) # Player info and statistics - info = Node.void('info') + info = Node.void("info") player.add_child(info) - info.add_child(Node.s32('tune_cnt', profile.get_int('tune_cnt'))) - info.add_child(Node.s32('save_cnt', profile.get_int('save_cnt'))) - info.add_child(Node.s32('saved_cnt', profile.get_int('saved_cnt'))) - info.add_child(Node.s32('fc_cnt', profile.get_int('fc_cnt'))) - info.add_child(Node.s32('ex_cnt', profile.get_int('ex_cnt'))) - info.add_child(Node.s32('clear_cnt', profile.get_int('clear_cnt'))) - info.add_child(Node.s32('match_cnt', profile.get_int('match_cnt'))) - info.add_child(Node.s32('beat_cnt', profile.get_int('beat_cnt'))) - info.add_child(Node.s32('mynews_cnt', profile.get_int('mynews_cnt'))) - info.add_child(Node.s32('mtg_entry_cnt', profile.get_int('mtg_entry_cnt'))) - info.add_child(Node.s32('mtg_hold_cnt', profile.get_int('mtg_hold_cnt'))) - info.add_child(Node.u8('mtg_result', profile.get_int('mtg_result'))) - info.add_child(Node.s32('bonus_tune_points', profile.get_int('bonus_tune_points'))) - info.add_child(Node.bool('is_bonus_tune_played', profile.get_bool('is_bonus_tune_played'))) + info.add_child(Node.s32("tune_cnt", profile.get_int("tune_cnt"))) + info.add_child(Node.s32("save_cnt", profile.get_int("save_cnt"))) + info.add_child(Node.s32("saved_cnt", profile.get_int("saved_cnt"))) + info.add_child(Node.s32("fc_cnt", profile.get_int("fc_cnt"))) + info.add_child(Node.s32("ex_cnt", profile.get_int("ex_cnt"))) + info.add_child(Node.s32("clear_cnt", profile.get_int("clear_cnt"))) + info.add_child(Node.s32("match_cnt", profile.get_int("match_cnt"))) + info.add_child(Node.s32("beat_cnt", profile.get_int("beat_cnt"))) + info.add_child(Node.s32("mynews_cnt", profile.get_int("mynews_cnt"))) + info.add_child(Node.s32("mtg_entry_cnt", profile.get_int("mtg_entry_cnt"))) + info.add_child(Node.s32("mtg_hold_cnt", profile.get_int("mtg_hold_cnt"))) + info.add_child(Node.u8("mtg_result", profile.get_int("mtg_result"))) + info.add_child( + Node.s32("bonus_tune_points", profile.get_int("bonus_tune_points")) + ) + info.add_child( + Node.bool("is_bonus_tune_played", profile.get_bool("is_bonus_tune_played")) + ) # Looks to be set to true when there's an old profile, stops tutorial from # happening on first load. - info.add_child(Node.bool('inherit', profile.get_bool('has_old_version') and not profile.get_bool('saved'))) + info.add_child( + Node.bool( + "inherit", + profile.get_bool("has_old_version") and not profile.get_bool("saved"), + ) + ) # Last played data, for showing cursor and such - lastdict = profile.get_dict('last') - last = Node.void('last') + lastdict = profile.get_dict("last") + last = Node.void("last") player.add_child(last) - last.add_child(Node.s64('play_time', lastdict.get_int('play_time'))) - last.add_child(Node.string('shopname', lastdict.get_str('shopname'))) - last.add_child(Node.string('areaname', lastdict.get_str('areaname'))) - last.add_child(Node.s32('music_id', lastdict.get_int('music_id'))) - last.add_child(Node.s8('seq_id', lastdict.get_int('seq_id'))) - last.add_child(Node.s8('sort', lastdict.get_int('sort'))) - last.add_child(Node.s8('category', lastdict.get_int('category'))) - last.add_child(Node.s8('expert_option', lastdict.get_int('expert_option'))) + last.add_child(Node.s64("play_time", lastdict.get_int("play_time"))) + last.add_child(Node.string("shopname", lastdict.get_str("shopname"))) + last.add_child(Node.string("areaname", lastdict.get_str("areaname"))) + last.add_child(Node.s32("music_id", lastdict.get_int("music_id"))) + last.add_child(Node.s8("seq_id", lastdict.get_int("seq_id"))) + last.add_child(Node.s8("sort", lastdict.get_int("sort"))) + last.add_child(Node.s8("category", lastdict.get_int("category"))) + last.add_child(Node.s8("expert_option", lastdict.get_int("expert_option"))) - settings = Node.void('settings') + settings = Node.void("settings") last.add_child(settings) - settings.add_child(Node.s8('marker', lastdict.get_int('marker'))) - settings.add_child(Node.s8('theme', lastdict.get_int('theme'))) - settings.add_child(Node.s16('title', lastdict.get_int('title'))) - settings.add_child(Node.s16('parts', lastdict.get_int('parts'))) - settings.add_child(Node.s8('rank_sort', lastdict.get_int('rank_sort'))) - settings.add_child(Node.s8('combo_disp', lastdict.get_int('combo_disp'))) - settings.add_child(Node.s16_array('emblem', lastdict.get_int_array('emblem', 5))) - settings.add_child(Node.s8('matching', lastdict.get_int('matching'))) - settings.add_child(Node.s8('hard', lastdict.get_int('hard'))) - settings.add_child(Node.s8('hazard', lastdict.get_int('hazard'))) + settings.add_child(Node.s8("marker", lastdict.get_int("marker"))) + settings.add_child(Node.s8("theme", lastdict.get_int("theme"))) + settings.add_child(Node.s16("title", lastdict.get_int("title"))) + settings.add_child(Node.s16("parts", lastdict.get_int("parts"))) + settings.add_child(Node.s8("rank_sort", lastdict.get_int("rank_sort"))) + settings.add_child(Node.s8("combo_disp", lastdict.get_int("combo_disp"))) + settings.add_child( + Node.s16_array("emblem", lastdict.get_int_array("emblem", 5)) + ) + settings.add_child(Node.s8("matching", lastdict.get_int("matching"))) + settings.add_child(Node.s8("hard", lastdict.get_int("hard"))) + settings.add_child(Node.s8("hazard", lastdict.get_int("hazard"))) # Secret unlocks, the game is too complicated and server-controlled to handle these correctly. - item = Node.void('item') + item = Node.void("item") player.add_child(item) - item.add_child(Node.s32_array('music_list', profile.get_int_array('music_list', 64, [-1] * 64))) - item.add_child(Node.s32_array('secret_list', profile.get_int_array('secret_list', 64, [-1] * 64))) - item.add_child(Node.s32_array('theme_list', profile.get_int_array('theme_list', 16, [-1] * 16))) - item.add_child(Node.s32_array('marker_list', profile.get_int_array('marker_list', 16, [-1] * 16))) - item.add_child(Node.s32_array('title_list', profile.get_int_array('title_list', 160, [-1] * 160))) - item.add_child(Node.s32_array('parts_list', profile.get_int_array('parts_list', 160, [-1] * 160))) - item.add_child(Node.s32_array('emblem_list', profile.get_int_array('emblem_list', 96, [-1] * 96))) - item.add_child(Node.s32_array('commu_list', profile.get_int_array('commu_list', 16, [-1] * 16))) + item.add_child( + Node.s32_array( + "music_list", profile.get_int_array("music_list", 64, [-1] * 64) + ) + ) + item.add_child( + Node.s32_array( + "secret_list", profile.get_int_array("secret_list", 64, [-1] * 64) + ) + ) + item.add_child( + Node.s32_array( + "theme_list", profile.get_int_array("theme_list", 16, [-1] * 16) + ) + ) + item.add_child( + Node.s32_array( + "marker_list", profile.get_int_array("marker_list", 16, [-1] * 16) + ) + ) + item.add_child( + Node.s32_array( + "title_list", profile.get_int_array("title_list", 160, [-1] * 160) + ) + ) + item.add_child( + Node.s32_array( + "parts_list", profile.get_int_array("parts_list", 160, [-1] * 160) + ) + ) + item.add_child( + Node.s32_array( + "emblem_list", profile.get_int_array("emblem_list", 96, [-1] * 96) + ) + ) + item.add_child( + Node.s32_array( + "commu_list", profile.get_int_array("commu_list", 16, [-1] * 16) + ) + ) - new = Node.void('new') + new = Node.void("new") item.add_child(new) - new.add_child(Node.s32_array('secret_list', profile.get_int_array('secret_list_new', 64, [-1] * 64))) - new.add_child(Node.s32_array('theme_list', profile.get_int_array('theme_list_new', 16, [-1] * 16))) - new.add_child(Node.s32_array('marker_list', profile.get_int_array('marker_list_new', 16, [-1] * 16))) + new.add_child( + Node.s32_array( + "secret_list", profile.get_int_array("secret_list_new", 64, [-1] * 64) + ) + ) + new.add_child( + Node.s32_array( + "theme_list", profile.get_int_array("theme_list_new", 16, [-1] * 16) + ) + ) + new.add_child( + Node.s32_array( + "marker_list", profile.get_int_array("marker_list_new", 16, [-1] * 16) + ) + ) # Add rivals to profile. - rivallist = Node.void('rivallist') + rivallist = Node.void("rivallist") player.add_child(rivallist) links = self.data.local.user.get_links(self.game, self.version, userid) rivalcount = 0 for link in links: - if link.type != 'rival': + if link.type != "rival": continue rprofile = self.get_profile(link.other_userid) if rprofile is None: continue - rival = Node.void('rival') + rival = Node.void("rival") rivallist.add_child(rival) - rival.add_child(Node.s32('jid', rprofile.extid)) - rival.add_child(Node.string('name', rprofile.get_str('name'))) + rival.add_child(Node.s32("jid", rprofile.extid)) + rival.add_child(Node.string("name", rprofile.get_str("name"))) # This looks like a carry-over from prop's career and isn't displayed. - career = Node.void('career') + career = Node.void("career") rival.add_child(career) - career.add_child(Node.s16('level', 1)) + career.add_child(Node.s16("level", 1)) # Lazy way of keeping track of rivals, since we can only have 3 # or the game with throw up. @@ -1984,85 +2582,103 @@ class JubeatFesto( if rivalcount >= 3: break - lab_edit_seq = Node.void('lab_edit_seq') + lab_edit_seq = Node.void("lab_edit_seq") player.add_child(lab_edit_seq) - lab_edit_seq.set_attribute('count', '0') + lab_edit_seq.set_attribute("count", "0") # Full combo challenge - entry = self.data.local.game.get_time_sensitive_settings(self.game, self.version, 'fc_challenge') + entry = self.data.local.game.get_time_sensitive_settings( + self.game, self.version, "fc_challenge" + ) if entry is None: entry = ValidatedDict() # Figure out if we've played these songs - start_time, end_time = self.data.local.network.get_schedule_duration('daily') + start_time, end_time = self.data.local.network.get_schedule_duration("daily") today_attempts = self.data.local.music.get_all_attempts( - self.game, self.music_version, userid, entry.get_int('today', -1), timelimit=start_time + self.game, + self.music_version, + userid, + entry.get_int("today", -1), + timelimit=start_time, ) whim_attempts = self.data.local.music.get_all_attempts( - self.game, self.music_version, userid, entry.get_int('whim', -1), timelimit=start_time + self.game, + self.music_version, + userid, + entry.get_int("whim", -1), + timelimit=start_time, ) # Full combo challenge and whim challenge - fc_challenge = Node.void('fc_challenge') + fc_challenge = Node.void("fc_challenge") player.add_child(fc_challenge) - today = Node.void('today') + today = Node.void("today") fc_challenge.add_child(today) - today.add_child(Node.s32('music_id', entry.get_int('today', -1))) - today.add_child(Node.u8('state', 0x40 if len(today_attempts) > 0 else 0x0)) - whim = Node.void('whim') + today.add_child(Node.s32("music_id", entry.get_int("today", -1))) + today.add_child(Node.u8("state", 0x40 if len(today_attempts) > 0 else 0x0)) + whim = Node.void("whim") fc_challenge.add_child(whim) - whim.add_child(Node.s32('music_id', entry.get_int('whim', -1))) - whim.add_child(Node.u8('state', 0x40 if len(whim_attempts) > 0 else 0x0)) + whim.add_child(Node.s32("music_id", entry.get_int("whim", -1))) + whim.add_child(Node.u8("state", 0x40 if len(whim_attempts) > 0 else 0x0)) # No news, ever. - official_news = Node.void('official_news') + official_news = Node.void("official_news") player.add_child(official_news) - news_list = Node.void('news_list') + news_list = Node.void("news_list") official_news.add_child(news_list) # Sane defaults for unknown/who cares nodes - history = Node.void('history') + history = Node.void("history") player.add_child(history) - history.set_attribute('count', '0') + history.set_attribute("count", "0") - free_first_play = Node.void('free_first_play') + free_first_play = Node.void("free_first_play") player.add_child(free_first_play) - free_first_play.add_child(Node.bool('is_available', False)) + free_first_play.add_child(Node.bool("is_available", False)) # Player status for events - event_info = Node.void('event_info') + event_info = Node.void("event_info") player.add_child(event_info) - achievements = self.data.local.user.get_achievements(self.game, self.version, userid) + achievements = self.data.local.user.get_achievements( + self.game, self.version, userid + ) event_completion: Dict[int, bool] = {} course_completion: Dict[int, ValidatedDict] = {} for achievement in achievements: - if achievement.type == 'event': - event_completion[achievement.id] = achievement.data.get_bool('is_completed') - if achievement.type == 'course': + if achievement.type == "event": + event_completion[achievement.id] = achievement.data.get_bool( + "is_completed" + ) + if achievement.type == "course": course_completion[achievement.id] = achievement.data for eventid, eventdata in self.EVENTS.items(): # There are two significant bits here, bit 0 and bit 1, I think the first # one is whether the event is started, second is if its finished? - event = Node.void('event') + event = Node.void("event") event_info.add_child(event) - event.set_attribute('type', str(eventid)) + event.set_attribute("type", str(eventid)) state = 0x0 - state |= self.EVENT_STATUS_OPEN if eventdata['enabled'] else 0 - state |= self.EVENT_STATUS_COMPLETE if event_completion.get(eventid, False) else 0 - event.add_child(Node.u8('state', state)) + state |= self.EVENT_STATUS_OPEN if eventdata["enabled"] else 0 + state |= ( + self.EVENT_STATUS_COMPLETE + if event_completion.get(eventid, False) + else 0 + ) + event.add_child(Node.u8("state", state)) # JBox stuff - jbox = Node.void('jbox') - jboxdict = profile.get_dict('jbox') + jbox = Node.void("jbox") + jboxdict = profile.get_dict("jbox") player.add_child(jbox) - jbox.add_child(Node.s32('point', jboxdict.get_int('point'))) - emblem = Node.void('emblem') + jbox.add_child(Node.s32("point", jboxdict.get_int("point"))) + emblem = Node.void("emblem") jbox.add_child(emblem) - normal = Node.void('normal') + normal = Node.void("normal") emblem.add_child(normal) - premium = Node.void('premium') + premium = Node.void("premium") emblem.add_child(premium) # Calculate a random index for normal and premium to give to player @@ -2071,10 +2687,10 @@ class JubeatFesto( normalemblems: Set[int] = set() premiumemblems: Set[int] = set() for gameitem in gameitems: - if gameitem.type == 'emblem': - if gameitem.data.get_int('rarity') in {1, 2, 3}: + if gameitem.type == "emblem": + if gameitem.data.get_int("rarity") in {1, 2, 3}: normalemblems.add(gameitem.id) - if gameitem.data.get_int('rarity') in {3, 4, 5}: + if gameitem.data.get_int("rarity") in {3, 4, 5}: premiumemblems.add(gameitem.id) # Default to some emblems in case the catalog is not available. @@ -2085,19 +2701,19 @@ class JubeatFesto( if premiumemblems: premiumindex = random.sample(premiumemblems, 1)[0] - normal.add_child(Node.s16('index', normalindex)) - premium.add_child(Node.s16('index', premiumindex)) + normal.add_child(Node.s16("index", normalindex)) + premium.add_child(Node.s16("index", premiumindex)) # New Music stuff - new_music = Node.void('new_music') + new_music = Node.void("new_music") player.add_child(new_music) - navi = Node.void('navi') + navi = Node.void("navi") player.add_child(navi) - navi.add_child(Node.u64('flag', profile.get_int('navi_flag'))) + navi.add_child(Node.u64("flag", profile.get_int("navi_flag"))) # Gift list, maybe from other players? - gift_list = Node.void('gift_list') + gift_list = Node.void("gift_list") player.add_child(gift_list) # If we had gifts, they look like this. This is incomplete, however, # because I never bothered to find the virtual function to decode "detail". @@ -2108,91 +2724,117 @@ class JubeatFesto( # # Birthday event? - born = Node.void('born') + born = Node.void("born") player.add_child(born) - born.add_child(Node.s8('status', profile.get_int('born_status'))) - born.add_child(Node.s16('year', profile.get_int('born_year'))) + born.add_child(Node.s8("status", profile.get_int("born_status"))) + born.add_child(Node.s16("year", profile.get_int("born_year"))) # More crap - question_list = Node.void('question_list') + question_list = Node.void("question_list") player.add_child(question_list) - emo_list = Node.void('emo_list') + emo_list = Node.void("emo_list") player.add_child(emo_list) # Some server node - server = Node.void('server') + server = Node.void("server") player.add_child(server) # Course List Progress - course_list = Node.void('course_list') + course_list = Node.void("course_list") player.add_child(course_list) # Each course that we have completed has one of the following nodes. for course in self.__get_course_list(): - status_dict = course_completion.get(course['id'], ValidatedDict()) + status_dict = course_completion.get(course["id"], ValidatedDict()) status = 0 - status |= self.COURSE_STATUS_SEEN if status_dict.get_bool('seen') else 0 - status |= self.COURSE_STATUS_PLAYED if status_dict.get_bool('played') else 0 - status |= self.COURSE_STATUS_CLEARED if status_dict.get_bool('cleared') else 0 + status |= self.COURSE_STATUS_SEEN if status_dict.get_bool("seen") else 0 + status |= self.COURSE_STATUS_PLAYED if status_dict.get_bool("played") else 0 + status |= ( + self.COURSE_STATUS_CLEARED if status_dict.get_bool("cleared") else 0 + ) - coursenode = Node.void('course') + coursenode = Node.void("course") course_list.add_child(coursenode) - coursenode.set_attribute('id', str(course['id'])) - coursenode.add_child(Node.s8('status', status)) + coursenode.set_attribute("id", str(course["id"])) + coursenode.add_child(Node.s8("status", status)) # For some reason, this is on the course list node this time around. - category_list = Node.void('category_list') + category_list = Node.void("category_list") course_list.add_child(category_list) for categoryid in range(1, 7): - category = Node.void('category') + category = Node.void("category") category_list.add_child(category) - category.set_attribute('id', str(categoryid)) - category.add_child(Node.bool('is_display', True)) + category.set_attribute("id", str(categoryid)) + category.add_child(Node.bool("is_display", True)) # Fill in category - fill_in_category = Node.void('fill_in_category') + fill_in_category = Node.void("fill_in_category") player.add_child(fill_in_category) - normal = Node.void('normal') + normal = Node.void("normal") fill_in_category.add_child(normal) normal.add_child( - Node.s32_array('no_gray_flag_list', profile.get_int_array('normal_no_gray_flag_list', 16, [0] * 16)) + Node.s32_array( + "no_gray_flag_list", + profile.get_int_array("normal_no_gray_flag_list", 16, [0] * 16), + ) ) normal.add_child( - Node.s32_array('all_yellow_flag_list', profile.get_int_array('normal_all_yellow_flag_list', 16, [0] * 16)) + Node.s32_array( + "all_yellow_flag_list", + profile.get_int_array("normal_all_yellow_flag_list", 16, [0] * 16), + ) ) normal.add_child( - Node.s32_array('full_combo_flag_list', profile.get_int_array('normal_full_combo_flag_list', 16, [0] * 16)) + Node.s32_array( + "full_combo_flag_list", + profile.get_int_array("normal_full_combo_flag_list", 16, [0] * 16), + ) ) normal.add_child( - Node.s32_array('excellent_flag_list', profile.get_int_array('normal_excellent_flag_list', 16, [0] * 16)) + Node.s32_array( + "excellent_flag_list", + profile.get_int_array("normal_excellent_flag_list", 16, [0] * 16), + ) ) - hard = Node.void('hard') + hard = Node.void("hard") fill_in_category.add_child(hard) hard.add_child( - Node.s32_array('no_gray_flag_list', profile.get_int_array('hard_no_gray_flag_list', 16, [0] * 16)) + Node.s32_array( + "no_gray_flag_list", + profile.get_int_array("hard_no_gray_flag_list", 16, [0] * 16), + ) ) hard.add_child( - Node.s32_array('all_yellow_flag_list', profile.get_int_array('hard_all_yellow_flag_list', 16, [0] * 16)) + Node.s32_array( + "all_yellow_flag_list", + profile.get_int_array("hard_all_yellow_flag_list", 16, [0] * 16), + ) ) hard.add_child( - Node.s32_array('full_combo_flag_list', profile.get_int_array('hard_full_combo_flag_list', 16, [0] * 16)) + Node.s32_array( + "full_combo_flag_list", + profile.get_int_array("hard_full_combo_flag_list", 16, [0] * 16), + ) ) hard.add_child( - Node.s32_array('excellent_flag_list', profile.get_int_array('hard_excellent_flag_list', 16, [0] * 16)) + Node.s32_array( + "excellent_flag_list", + profile.get_int_array("hard_excellent_flag_list", 16, [0] * 16), + ) ) # Unknown department shop stuff, I think this handles the EMO shop. - department = Node.void('department') + department = Node.void("department") player.add_child(department) - department.add_child(Node.void('shop_list')) + department.add_child(Node.void("shop_list")) # Stamp rally stuff, this is too server-controlled and not documented on BemaniWiki. - stamp = Node.void('stamp') + stamp = Node.void("stamp") player.add_child(stamp) - stamp.add_child(Node.void('sheet_list')) + stamp.add_child(Node.void("sheet_list")) # Missing team_battle, which we do not support. @@ -2201,130 +2843,194 @@ class JubeatFesto( # Missing hike_event, which I can't find any info on. # Festo dungeon - festo_dungeon = Node.void('festo_dungeon') + festo_dungeon = Node.void("festo_dungeon") player.add_child(festo_dungeon) - festo_dungeon.add_child(Node.s32('phase', profile.get_int('festo_dungeon_phase'))) - festo_dungeon.add_child(Node.s32('clear_flag', profile.get_int('festo_dungeon_clear_flag'))) + festo_dungeon.add_child( + Node.s32("phase", profile.get_int("festo_dungeon_phase")) + ) + festo_dungeon.add_child( + Node.s32("clear_flag", profile.get_int("festo_dungeon_clear_flag")) + ) # Missing travel event, which I do not want to implement. return root - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() - newprofile.replace_bool('saved', True) - data = request.child('data') + newprofile.replace_bool("saved", True) + data = request.child("data") # Grab system information - sysinfo = data.child('info') + sysinfo = data.child("info") # Grab player information - player = data.child('player') + player = data.child("player") # Grab result information - result = data.child('result') + result = data.child("result") # Grab last information. Lots of this will be filled in while grabbing scores - last = newprofile.get_dict('last') + last = newprofile.get_dict("last") if sysinfo is not None: - last.replace_int('play_time', sysinfo.child_value('time_gameend')) - last.replace_str('shopname', sysinfo.child_value('shopname')) - last.replace_str('areaname', sysinfo.child_value('areaname')) + last.replace_int("play_time", sysinfo.child_value("time_gameend")) + last.replace_str("shopname", sysinfo.child_value("shopname")) + last.replace_str("areaname", sysinfo.child_value("areaname")) # Grab player info for echoing back - info = player.child('info') + info = player.child("info") if info is not None: - newprofile.replace_int('tune_cnt', info.child_value('tune_cnt')) - newprofile.replace_int('save_cnt', info.child_value('save_cnt')) - newprofile.replace_int('saved_cnt', info.child_value('saved_cnt')) - newprofile.replace_int('fc_cnt', info.child_value('fc_cnt')) - newprofile.replace_int('ex_cnt', info.child_value('ex_cnt')) - newprofile.replace_int('clear_cnt', info.child_value('clear_cnt')) - newprofile.replace_int('match_cnt', info.child_value('match_cnt')) - newprofile.replace_int('beat_cnt', info.child_value('beat_cnt')) - newprofile.replace_int('mynews_cnt', info.child_value('mynews_cnt')) + newprofile.replace_int("tune_cnt", info.child_value("tune_cnt")) + newprofile.replace_int("save_cnt", info.child_value("save_cnt")) + newprofile.replace_int("saved_cnt", info.child_value("saved_cnt")) + newprofile.replace_int("fc_cnt", info.child_value("fc_cnt")) + newprofile.replace_int("ex_cnt", info.child_value("ex_cnt")) + newprofile.replace_int("clear_cnt", info.child_value("clear_cnt")) + newprofile.replace_int("match_cnt", info.child_value("match_cnt")) + newprofile.replace_int("beat_cnt", info.child_value("beat_cnt")) + newprofile.replace_int("mynews_cnt", info.child_value("mynews_cnt")) - newprofile.replace_int('bonus_tune_points', info.child_value('bonus_tune_points')) - newprofile.replace_bool('is_bonus_tune_played', info.child_value('is_bonus_tune_played')) + newprofile.replace_int( + "bonus_tune_points", info.child_value("bonus_tune_points") + ) + newprofile.replace_bool( + "is_bonus_tune_played", info.child_value("is_bonus_tune_played") + ) # Grab last settings - lastnode = player.child('last') + lastnode = player.child("last") if lastnode is not None: - last.replace_int('expert_option', lastnode.child_value('expert_option')) - last.replace_int('sort', lastnode.child_value('sort')) - last.replace_int('category', lastnode.child_value('category')) + last.replace_int("expert_option", lastnode.child_value("expert_option")) + last.replace_int("sort", lastnode.child_value("sort")) + last.replace_int("category", lastnode.child_value("category")) - settings = lastnode.child('settings') + settings = lastnode.child("settings") if settings is not None: - last.replace_int('matching', settings.child_value('matching')) - last.replace_int('hazard', settings.child_value('hazard')) - last.replace_int('hard', settings.child_value('hard')) - last.replace_int('marker', settings.child_value('marker')) - last.replace_int('theme', settings.child_value('theme')) - last.replace_int('title', settings.child_value('title')) - last.replace_int('parts', settings.child_value('parts')) - last.replace_int('rank_sort', settings.child_value('rank_sort')) - last.replace_int('combo_disp', settings.child_value('combo_disp')) - last.replace_int_array('emblem', 5, settings.child_value('emblem')) + last.replace_int("matching", settings.child_value("matching")) + last.replace_int("hazard", settings.child_value("hazard")) + last.replace_int("hard", settings.child_value("hard")) + last.replace_int("marker", settings.child_value("marker")) + last.replace_int("theme", settings.child_value("theme")) + last.replace_int("title", settings.child_value("title")) + last.replace_int("parts", settings.child_value("parts")) + last.replace_int("rank_sort", settings.child_value("rank_sort")) + last.replace_int("combo_disp", settings.child_value("combo_disp")) + last.replace_int_array("emblem", 5, settings.child_value("emblem")) # Grab unlock progress - item = player.child('item') + item = player.child("item") if item is not None: - newprofile.replace_int_array('music_list', 64, item.child_value('music_list')) - newprofile.replace_int_array('secret_list', 64, item.child_value('secret_list')) - newprofile.replace_int_array('theme_list', 16, item.child_value('theme_list')) - newprofile.replace_int_array('marker_list', 16, item.child_value('marker_list')) - newprofile.replace_int_array('title_list', 160, item.child_value('title_list')) - newprofile.replace_int_array('parts_list', 160, item.child_value('parts_list')) - newprofile.replace_int_array('emblem_list', 96, item.child_value('emblem_list')) - newprofile.replace_int_array('commu_list', 16, item.child_value('commu_list')) + newprofile.replace_int_array( + "music_list", 64, item.child_value("music_list") + ) + newprofile.replace_int_array( + "secret_list", 64, item.child_value("secret_list") + ) + newprofile.replace_int_array( + "theme_list", 16, item.child_value("theme_list") + ) + newprofile.replace_int_array( + "marker_list", 16, item.child_value("marker_list") + ) + newprofile.replace_int_array( + "title_list", 160, item.child_value("title_list") + ) + newprofile.replace_int_array( + "parts_list", 160, item.child_value("parts_list") + ) + newprofile.replace_int_array( + "emblem_list", 96, item.child_value("emblem_list") + ) + newprofile.replace_int_array( + "commu_list", 16, item.child_value("commu_list") + ) - newitem = item.child('new') + newitem = item.child("new") if newitem is not None: - newprofile.replace_int_array('secret_list_new', 64, newitem.child_value('secret_list')) - newprofile.replace_int_array('theme_list_new', 16, newitem.child_value('theme_list')) - newprofile.replace_int_array('marker_list_new', 16, newitem.child_value('marker_list')) + newprofile.replace_int_array( + "secret_list_new", 64, newitem.child_value("secret_list") + ) + newprofile.replace_int_array( + "theme_list_new", 16, newitem.child_value("theme_list") + ) + newprofile.replace_int_array( + "marker_list_new", 16, newitem.child_value("marker_list") + ) # Grab categories stuff - fill_in_category = player.child('fill_in_category') + fill_in_category = player.child("fill_in_category") if fill_in_category is not None: - fill_in_category_normal = fill_in_category.child('normal') + fill_in_category_normal = fill_in_category.child("normal") if fill_in_category_normal is not None: - newprofile.replace_int_array('normal_no_gray_flag_list', 16, fill_in_category_normal.child_value('no_gray_flag_list')) - newprofile.replace_int_array('normal_all_yellow_flag_list', 16, fill_in_category_normal.child_value('all_yellow_flag_list')) - newprofile.replace_int_array('normal_full_combo_flag_list', 16, fill_in_category_normal.child_value('full_combo_flag_list')) - newprofile.replace_int_array('normal_excellent_flag_list', 16, fill_in_category_normal.child_value('excellent_flag_list')) - fill_in_category_hard = fill_in_category.child('hard') + newprofile.replace_int_array( + "normal_no_gray_flag_list", + 16, + fill_in_category_normal.child_value("no_gray_flag_list"), + ) + newprofile.replace_int_array( + "normal_all_yellow_flag_list", + 16, + fill_in_category_normal.child_value("all_yellow_flag_list"), + ) + newprofile.replace_int_array( + "normal_full_combo_flag_list", + 16, + fill_in_category_normal.child_value("full_combo_flag_list"), + ) + newprofile.replace_int_array( + "normal_excellent_flag_list", + 16, + fill_in_category_normal.child_value("excellent_flag_list"), + ) + fill_in_category_hard = fill_in_category.child("hard") if fill_in_category_hard is not None: - newprofile.replace_int_array('hard_no_gray_flag_list', 16, fill_in_category_hard.child_value('no_gray_flag_list')) - newprofile.replace_int_array('hard_all_yellow_flag_list', 16, fill_in_category_hard.child_value('all_yellow_flag_list')) - newprofile.replace_int_array('hard_full_combo_flag_list', 16, fill_in_category_hard.child_value('full_combo_flag_list')) - newprofile.replace_int_array('hard_excellent_flag_list', 16, fill_in_category_hard.child_value('excellent_flag_list')) + newprofile.replace_int_array( + "hard_no_gray_flag_list", + 16, + fill_in_category_hard.child_value("no_gray_flag_list"), + ) + newprofile.replace_int_array( + "hard_all_yellow_flag_list", + 16, + fill_in_category_hard.child_value("all_yellow_flag_list"), + ) + newprofile.replace_int_array( + "hard_full_combo_flag_list", + 16, + fill_in_category_hard.child_value("full_combo_flag_list"), + ) + newprofile.replace_int_array( + "hard_excellent_flag_list", + 16, + fill_in_category_hard.child_value("excellent_flag_list"), + ) # jbox stuff - jbox = player.child('jbox') - jboxdict = newprofile.get_dict('jbox') + jbox = player.child("jbox") + jboxdict = newprofile.get_dict("jbox") if jbox is not None: - jboxdict.replace_int('point', jbox.child_value('point')) - emblemtype = jbox.child_value('emblem/type') - index = jbox.child_value('emblem/index') + jboxdict.replace_int("point", jbox.child_value("point")) + emblemtype = jbox.child_value("emblem/type") + index = jbox.child_value("emblem/index") if emblemtype == self.JBOX_EMBLEM_NORMAL: - jboxdict.replace_int('normal_index', index) + jboxdict.replace_int("normal_index", index) elif emblemtype == self.JBOX_EMBLEM_PREMIUM: - jboxdict.replace_int('premium_index', index) - newprofile.replace_dict('jbox', jboxdict) + jboxdict.replace_int("premium_index", index) + newprofile.replace_dict("jbox", jboxdict) # event stuff - newprofile.replace_int('event_flag', player.child_value('event_flag')) - event_info = player.child('event_info') + newprofile.replace_int("event_flag", player.child_value("event_flag")) + event_info = player.child("event_info") if event_info is not None: for child in event_info.children: try: - eventid = int(child.attribute('type')) + eventid = int(child.attribute("type")) except TypeError: # Event is empty continue - is_completed = child.child_value('is_completed') + is_completed = child.child_value("is_completed") # Figure out if we should update the rating/scores or not oldevent = self.data.local.user.get_achievement( @@ -2332,14 +3038,14 @@ class JubeatFesto( self.version, userid, eventid, - 'event', + "event", ) if oldevent is None: # Create a new event structure for this oldevent = ValidatedDict() - oldevent.replace_bool('is_completed', is_completed) + oldevent.replace_bool("is_completed", is_completed) # Save it as an achievement self.data.local.user.put_achievement( @@ -2347,39 +3053,42 @@ class JubeatFesto( self.version, userid, eventid, - 'event', + "event", oldevent, ) # Still don't know what this is for lol - newprofile.replace_int('navi_flag', player.child_value('navi/flag')) + newprofile.replace_int("navi_flag", player.child_value("navi/flag")) # Grab scores and save those if result is not None: for tune in result.children: - if tune.name != 'tune': + if tune.name != "tune": continue - result = tune.child('player') - songid = tune.child_value('music') - timestamp = tune.child_value('timestamp') / 1000 - chart = self.game_to_db_chart(int(result.child('score').attribute('seq')), bool(result.child_value('is_hard_mode'))) - points = result.child_value('score') - flags = int(result.child('score').attribute('clear')) - combo = int(result.child('score').attribute('combo')) - ghost = result.child_value('mbar') - music_rate = result.child_value('music_rate') + result = tune.child("player") + songid = tune.child_value("music") + timestamp = tune.child_value("timestamp") / 1000 + chart = self.game_to_db_chart( + int(result.child("score").attribute("seq")), + bool(result.child_value("is_hard_mode")), + ) + points = result.child_value("score") + flags = int(result.child("score").attribute("clear")) + combo = int(result.child("score").attribute("combo")) + ghost = result.child_value("mbar") + music_rate = result.child_value("music_rate") stats = { - 'perfect': result.child_value('nr_perfect'), - 'great': result.child_value('nr_great'), - 'good': result.child_value('nr_good'), - 'poor': result.child_value('nr_poor'), - 'miss': result.child_value('nr_miss'), + "perfect": result.child_value("nr_perfect"), + "great": result.child_value("nr_great"), + "good": result.child_value("nr_good"), + "poor": result.child_value("nr_poor"), + "miss": result.child_value("nr_miss"), } # Miscelaneous last data for echoing to profile get - last.replace_int('music_id', songid) - last.replace_int('seq_id', int(result.child('score').attribute('seq'))) + last.replace_int("music_id", songid) + last.replace_int("seq_id", int(result.child("score").attribute("seq"))) mapping = { self.GAME_FLAG_BIT_CLEARED: self.PLAY_MEDAL_CLEARED, @@ -2395,14 +3104,24 @@ class JubeatFesto( if flags & bit > 0: medal = max(medal, mapping[bit]) - self.update_score(userid, timestamp, songid, chart, points, medal, - combo, ghost, stats, music_rate) + self.update_score( + userid, + timestamp, + songid, + chart, + points, + medal, + combo, + ghost, + stats, + music_rate, + ) # Born stuff - born = player.child('born') + born = player.child("born") if born is not None: - newprofile.replace_int('born_status', born.child_value('status')) - newprofile.replace_int('born_year', born.child_value('year')) + newprofile.replace_int("born_status", born.child_value("status")) + newprofile.replace_int("born_year", born.child_value("year")) # jubility list sent looks like this # @@ -2419,59 +3138,69 @@ class JubeatFesto( # # Grab jubility - jubility = player.child('jubility') + jubility = player.child("jubility") if jubility is not None: - target_music = jubility.child('target_music') + target_music = jubility.child("target_music") # Pick up jubility stuff - hot_music_list = target_music.child('hot_music_list') + hot_music_list = target_music.child("hot_music_list") pick_up_chart = ValidatedDict() for music in hot_music_list.children: - music_id = music.child_value('music_id') - chart = self.game_to_db_chart(int(music.child_value('seq')), bool(music.child_value('is_hard_mode'))) - music_rate = float(music.child_value('rate')) / 10 - value = float(music.child_value('value')) / 10 + music_id = music.child_value("music_id") + chart = self.game_to_db_chart( + int(music.child_value("seq")), + bool(music.child_value("is_hard_mode")), + ) + music_rate = float(music.child_value("rate")) / 10 + value = float(music.child_value("value")) / 10 entry = { - 'music_id': music_id, - 'seq': chart, - 'music_rate': music_rate, - 'value': value, + "music_id": music_id, + "seq": chart, + "music_rate": music_rate, + "value": value, } - pick_up_chart.replace_dict(f'{music_id}_{chart}', entry) + pick_up_chart.replace_dict(f"{music_id}_{chart}", entry) # Save it back - newprofile.replace_dict('pick_up_chart', pick_up_chart) - newprofile.replace_float('pick_up_jubility', float(hot_music_list.attribute('param')) / 10) + newprofile.replace_dict("pick_up_chart", pick_up_chart) + newprofile.replace_float( + "pick_up_jubility", float(hot_music_list.attribute("param")) / 10 + ) # Common jubility stuff - other_music_list = target_music.child('other_music_list') + other_music_list = target_music.child("other_music_list") common_chart = ValidatedDict() for music in other_music_list.children: - music_id = music.child_value('music_id') - chart = self.game_to_db_chart(int(music.child_value('seq')), bool(music.child_value('is_hard_mode'))) - music_rate = float(music.child_value('rate')) / 10 - value = float(music.child_value('value')) / 10 + music_id = music.child_value("music_id") + chart = self.game_to_db_chart( + int(music.child_value("seq")), + bool(music.child_value("is_hard_mode")), + ) + music_rate = float(music.child_value("rate")) / 10 + value = float(music.child_value("value")) / 10 entry = { - 'music_id': music_id, - 'seq': chart, - 'music_rate': music_rate, - 'value': value, + "music_id": music_id, + "seq": chart, + "music_rate": music_rate, + "value": value, } - common_chart.replace_dict(f'{music_id}_{chart}', entry) + common_chart.replace_dict(f"{music_id}_{chart}", entry) # Save it back - newprofile.replace_dict('common_chart', common_chart) - newprofile.replace_float('common_jubility', float(other_music_list.attribute('param')) / 10) + newprofile.replace_dict("common_chart", common_chart) + newprofile.replace_float( + "common_jubility", float(other_music_list.attribute("param")) / 10 + ) # Clan course saving - clan_course_list = player.child('course_list') + clan_course_list = player.child("course_list") if clan_course_list is not None: for course in clan_course_list.children: - if course.name != 'course': + if course.name != "course": continue - courseid = int(course.attribute('id')) - status = course.child_value('status') + courseid = int(course.attribute("id")) + status = course.child_value("status") is_seen = (status & self.COURSE_STATUS_SEEN) != 0 is_played = (status & self.COURSE_STATUS_PLAYED) != 0 @@ -2481,15 +3210,17 @@ class JubeatFesto( self.version, userid, courseid, - 'course', + "course", ) if oldcourse is None: # Create a new course structure for this oldcourse = ValidatedDict() - oldcourse.replace_bool('seen', oldcourse.get_bool('seen') or is_seen) - oldcourse.replace_bool('played', oldcourse.get_bool('played') or is_played) + oldcourse.replace_bool("seen", oldcourse.get_bool("seen") or is_seen) + oldcourse.replace_bool( + "played", oldcourse.get_bool("played") or is_played + ) # Save it as an achievement self.data.local.user.put_achievement( @@ -2497,18 +3228,18 @@ class JubeatFesto( self.version, userid, courseid, - 'course', + "course", oldcourse, ) # If they played a course, figure out if they cleared it. - select_course = player.child('select_course') + select_course = player.child("select_course") if select_course is not None: try: - courseid = int(select_course.attribute('id')) + courseid = int(select_course.attribute("id")) except Exception: courseid = 0 - cleared = select_course.child_value('is_cleared') + cleared = select_course.child_value("is_cleared") if courseid > 0 and cleared: # Update course cleared status @@ -2517,14 +3248,14 @@ class JubeatFesto( self.version, userid, courseid, - 'course', + "course", ) if oldcourse is None: # Create a new course structure for this oldcourse = ValidatedDict() - oldcourse.replace_bool('cleared', True) + oldcourse.replace_bool("cleared", True) # Save it as an achievement self.data.local.user.put_achievement( @@ -2532,17 +3263,21 @@ class JubeatFesto( self.version, userid, courseid, - 'course', + "course", oldcourse, ) # Save back last information gleaned from results - newprofile.replace_dict('last', last) + newprofile.replace_dict("last", last) - festo_dungeon = player.child('festo_dungeon') + festo_dungeon = player.child("festo_dungeon") if festo_dungeon is not None: - newprofile.replace_int('festo_dungeon_phase', festo_dungeon.child_value('phase')) - newprofile.replace_int('festo_dungeon_clear_flag', festo_dungeon.child_value('clear_flag')) + newprofile.replace_int( + "festo_dungeon_phase", festo_dungeon.child_value("phase") + ) + newprofile.replace_int( + "festo_dungeon_clear_flag", festo_dungeon.child_value("clear_flag") + ) # Keep track of play statistics self.update_play_statistics(userid) diff --git a/bemani/backend/jubeat/prop.py b/bemani/backend/jubeat/prop.py index dd144b6..d6c6a37 100644 --- a/bemani/backend/jubeat/prop.py +++ b/bemani/backend/jubeat/prop.py @@ -32,7 +32,7 @@ class JubeatProp( JubeatBase, ): - name: str = 'Jubeat Prop' + name: str = "Jubeat Prop" version: int = VersionConstants.JUBEAT_PROP GAME_COURSE_REQUIREMENT_SCORE: Final[int] = 1 @@ -49,31 +49,31 @@ class JubeatProp( EVENTS: Dict[int, Dict[str, bool]] = { 5: { - 'enabled': False, + "enabled": False, }, 6: { - 'enabled': False, + "enabled": False, }, 9: { - 'enabled': False, + "enabled": False, }, 14: { - 'enabled': False, + "enabled": False, }, 15: { - 'enabled': False, + "enabled": False, }, 16: { - 'enabled': False, + "enabled": False, }, 17: { - 'enabled': False, + "enabled": False, }, 18: { - 'enabled': False, + "enabled": False, }, 19: { - 'enabled': False, + "enabled": False, }, } @@ -184,7 +184,9 @@ class JubeatProp( return cls.__rank_to_class(cls.__class_to_rank(cur_class, cur_subclass) - 1) @classmethod - def _get_league_buckets(cls, scores: List[Tuple[UserID, int]]) -> Tuple[List[UserID], List[UserID], List[UserID]]: + def _get_league_buckets( + cls, scores: List[Tuple[UserID, int]] + ) -> Tuple[List[UserID], List[UserID], List[UserID]]: """ Given a list of userid, score tuples, return a tuple containing three lists. The first list is the top 30% scorer IDs, the next list is the middle 40% @@ -205,7 +207,9 @@ class JubeatProp( return (promotions, neutrals, demotions) @classmethod - def _get_league_scores(cls, data: Data, current_id: int, profiles: List[Tuple[UserID, Profile]]) -> Tuple[List[Tuple[UserID, int]], List[UserID]]: + def _get_league_scores( + cls, data: Data, current_id: int, profiles: List[Tuple[UserID, Profile]] + ) -> Tuple[List[Tuple[UserID, int]], List[UserID]]: """ Given the current League ID (calculated based on the date range) and a list of all user profiles for this game/version, return a uple containing two lists. @@ -225,25 +229,29 @@ class JubeatProp( cls.version, userid, last_id, - 'league', + "league", ) # If they played, grab their total score so we can figure out if we should # promote, demote or leave alone if league_score is not None: - scores.append(( - userid, - league_score['score'][0] + - league_score['score'][1] + - league_score['score'][2], - )) + scores.append( + ( + userid, + league_score["score"][0] + + league_score["score"][1] + + league_score["score"][2], + ) + ) else: absentees.append(userid) return scores, absentees @classmethod - def _get_league_absentees(cls, data: Data, current_id: int, absentees: List[UserID]) -> List[UserID]: + def _get_league_absentees( + cls, data: Data, current_id: int, absentees: List[UserID] + ) -> List[UserID]: """ Given a list of user IDs that didn't play for some number of weeks, return a subset of those IDs that have been absent enough weeks to get a demotion. @@ -260,7 +268,7 @@ class JubeatProp( cls.version, userid, ): - if achievement.type == 'league': + if achievement.type == "league": last_league_id = max(achievement.id, last_league_id) if last_league_id != 0: @@ -286,58 +294,67 @@ class JubeatProp( promotions/demotions. """ profile = data.local.user.get_profile(cls.game, cls.version, userid) - cur_class = profile.get_int('league_class', 1) - cur_subclass = profile.get_int('league_subclass', 5) + cur_class = profile.get_int("league_class", 1) + cur_subclass = profile.get_int("league_subclass", 5) - if direction == 'promote': + if direction == "promote": new_class, new_subclass = cls._increment_class(cur_class, cur_subclass) - elif direction == 'demote': + elif direction == "demote": new_class, new_subclass = cls._decrement_class(cur_class, cur_subclass) else: - raise Exception(f'Logic error, unknown direction {direction}!') + raise Exception(f"Logic error, unknown direction {direction}!") if new_class != cur_class or new_subclass != cur_subclass: # If they've checked last time, set up the new old class. - if profile.get_bool('league_is_checked'): - last = profile.get_dict('last') - last.replace_int('league_class', cur_class) - last.replace_int('league_subclass', cur_subclass) - profile.replace_dict('last', last) + if profile.get_bool("league_is_checked"): + last = profile.get_dict("last") + last.replace_int("league_class", cur_class) + last.replace_int("league_subclass", cur_subclass) + profile.replace_dict("last", last) # We actually changed a level, let the user know! - profile.replace_int('league_class', new_class) - profile.replace_int('league_subclass', new_subclass) - profile.replace_bool('league_is_checked', False) + profile.replace_int("league_class", new_class) + profile.replace_int("league_subclass", new_subclass) + profile.replace_bool("league_is_checked", False) data.local.user.put_profile(cls.game, cls.version, userid, profile) @classmethod - def run_scheduled_work(cls, data: Data, config: Dict[str, Any]) -> List[Tuple[str, Dict[str, Any]]]: + def run_scheduled_work( + cls, data: Data, config: Dict[str, Any] + ) -> List[Tuple[str, Dict[str, Any]]]: """ Once a week, insert a new league course. Every day, insert new FC challenge courses. """ events = [] - if data.local.network.should_schedule(cls.game, cls.version, 'league_course', 'weekly'): + if data.local.network.should_schedule( + cls.game, cls.version, "league_course", "weekly" + ): # Generate a new league course list, save it to the DB. - start_time, end_time = data.local.network.get_schedule_duration('weekly') - all_songs = set(song.id for song in data.local.music.get_all_songs(cls.game, cls.version)) + start_time, end_time = data.local.network.get_schedule_duration("weekly") + all_songs = set( + song.id + for song in data.local.music.get_all_songs(cls.game, cls.version) + ) if len(all_songs) >= 3: league_songs = random.sample(all_songs, 3) data.local.game.put_time_sensitive_settings( cls.game, cls.version, - 'league', + "league", { - 'start_time': start_time, - 'end_time': end_time, - 'music': league_songs, + "start_time": start_time, + "end_time": end_time, + "music": league_songs, }, ) - events.append(( - 'jubeat_league_course', - { - 'version': cls.version, - 'songs': league_songs, - }, - )) + events.append( + ( + "jubeat_league_course", + { + "version": cls.version, + "songs": league_songs, + }, + ) + ) # League ID for the current league we just added. leagueid = int(start_time / 604800) @@ -353,55 +370,66 @@ class JubeatProp( # Actually modify the profiles so the game knows to tell the user. for userid in promote: - cls._modify_profile(data, userid, 'promote') + cls._modify_profile(data, userid, "promote") for userid in demote: - cls._modify_profile(data, userid, 'demote') + cls._modify_profile(data, userid, "demote") # Mark that we did some actual work here. - data.local.network.mark_scheduled(cls.game, cls.version, 'league_course', 'weekly') + data.local.network.mark_scheduled( + cls.game, cls.version, "league_course", "weekly" + ) - if data.local.network.should_schedule(cls.game, cls.version, 'fc_challenge', 'daily'): + if data.local.network.should_schedule( + cls.game, cls.version, "fc_challenge", "daily" + ): # Generate a new list of two FC challenge songs. - start_time, end_time = data.local.network.get_schedule_duration('daily') - all_songs = set(song.id for song in data.local.music.get_all_songs(cls.game, cls.version)) + start_time, end_time = data.local.network.get_schedule_duration("daily") + all_songs = set( + song.id + for song in data.local.music.get_all_songs(cls.game, cls.version) + ) if len(all_songs) >= 2: daily_songs = random.sample(all_songs, 2) data.local.game.put_time_sensitive_settings( cls.game, cls.version, - 'fc_challenge', + "fc_challenge", { - 'start_time': start_time, - 'end_time': end_time, - 'today': daily_songs[0], - 'whim': daily_songs[1], + "start_time": start_time, + "end_time": end_time, + "today": daily_songs[0], + "whim": daily_songs[1], }, ) - events.append(( - 'jubeat_fc_challenge_charts', - { - 'version': cls.version, - 'today': daily_songs[0], - 'whim': daily_songs[1], - }, - )) + events.append( + ( + "jubeat_fc_challenge_charts", + { + "version": cls.version, + "today": daily_songs[0], + "whim": daily_songs[1], + }, + ) + ) # Mark that we did some actual work here. - data.local.network.mark_scheduled(cls.game, cls.version, 'fc_challenge', 'daily') + data.local.network.mark_scheduled( + cls.game, cls.version, "fc_challenge", "daily" + ) return events def __get_global_info(self) -> Node: - info = Node.void('info') + info = Node.void("info") # Event info. Valid event IDs are 5, 6, 9, 14, 15, 16, 17, 18, 19 - event_info = Node.void('event_info') + event_info = Node.void("event_info") info.add_child(event_info) for event in self.EVENTS: - evt = Node.void('event') + evt = Node.void("event") event_info.add_child(evt) - evt.set_attribute('type', str(event)) - evt.add_child(Node.u8('state', 1 if self.EVENTS[event]['enabled'] else 0)) + evt.set_attribute("type", str(event)) + evt.add_child(Node.u8("state", 1 if self.EVENTS[event]["enabled"] else 0)) # Each of the following three sections should have zero or more child nodes (no # particular name) which look like the following: @@ -411,230 +439,288 @@ class JubeatProp( # end time? # # Share music? - share_music = Node.void('share_music') + share_music = Node.void("share_music") info.add_child(share_music) # Bonus music? - bonus_music = Node.void('bonus_music') + bonus_music = Node.void("bonus_music") info.add_child(bonus_music) # Only now music? - only_now_music = Node.void('only_now_music') + only_now_music = Node.void("only_now_music") info.add_child(only_now_music) # Full combo challenge? - entry = self.data.local.game.get_time_sensitive_settings(self.game, self.version, 'fc_challenge') + entry = self.data.local.game.get_time_sensitive_settings( + self.game, self.version, "fc_challenge" + ) if entry is None: entry = ValidatedDict() - fc_challenge = Node.void('fc_challenge') + fc_challenge = Node.void("fc_challenge") info.add_child(fc_challenge) - today = Node.void('today') + today = Node.void("today") fc_challenge.add_child(today) - today.add_child(Node.s32('music_id', entry.get_int('today', -1))) + today.add_child(Node.s32("music_id", entry.get_int("today", -1))) # Some sort of music DB whitelist - info.add_child(Node.s32_array( - 'white_music_list', - [ - -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, -1, -1, -1, - -1, -1, -1, -1, - ], - )) + info.add_child( + Node.s32_array( + "white_music_list", + [ + -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, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], + ) + ) - info.add_child(Node.s32_array( - 'open_music_list', - [ - -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, -1, -1, -1, - -1, -1, -1, -1, - ], - )) + info.add_child( + Node.s32_array( + "open_music_list", + [ + -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, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], + ) + ) - cabinet_survey = Node.void('cabinet_survey') + cabinet_survey = Node.void("cabinet_survey") info.add_child(cabinet_survey) - cabinet_survey.add_child(Node.s32('id', -1)) - cabinet_survey.add_child(Node.s32('status', 0)) + cabinet_survey.add_child(Node.s32("id", -1)) + cabinet_survey.add_child(Node.s32("status", 0)) - kaitou_bisco = Node.void('kaitou_bisco') + kaitou_bisco = Node.void("kaitou_bisco") info.add_child(kaitou_bisco) - kaitou_bisco.add_child(Node.s32('remaining_days', 0)) + kaitou_bisco.add_child(Node.s32("remaining_days", 0)) - league = Node.void('league') + league = Node.void("league") info.add_child(league) - league.add_child(Node.u8('status', 1)) + league.add_child(Node.u8("status", 1)) - bistro = Node.void('bistro') + bistro = Node.void("bistro") info.add_child(bistro) - bistro.add_child(Node.u16('bistro_id', 0)) + bistro.add_child(Node.u16("bistro_id", 0)) - jbox = Node.void('jbox') + jbox = Node.void("jbox") info.add_child(jbox) - jbox.add_child(Node.s32('point', 0)) - emblem = Node.void('emblem') + jbox.add_child(Node.s32("point", 0)) + emblem = Node.void("emblem") jbox.add_child(emblem) - normal = Node.void('normal') + normal = Node.void("normal") emblem.add_child(normal) - premium = Node.void('premium') + premium = Node.void("premium") emblem.add_child(premium) - normal.add_child(Node.s16('index', 2)) - premium.add_child(Node.s16('index', 1)) + normal.add_child(Node.s16("index", 2)) + premium.add_child(Node.s16("index", 1)) return info def handle_shopinfo_regist_request(self, request: Node) -> Node: # Update the name of this cab for admin purposes - self.update_machine_name(request.child_value('shop/name')) + self.update_machine_name(request.child_value("shop/name")) - shopinfo = Node.void('shopinfo') + shopinfo = Node.void("shopinfo") - data = Node.void('data') + data = Node.void("data") shopinfo.add_child(data) - data.add_child(Node.u32('cabid', 1)) - data.add_child(Node.string('locationid', 'nowhere')) - data.add_child(Node.u8('tax_phase', 1)) + data.add_child(Node.u32("cabid", 1)) + data.add_child(Node.string("locationid", "nowhere")) + data.add_child(Node.u8("tax_phase", 1)) - facility = Node.void('facility') + facility = Node.void("facility") data.add_child(facility) - facility.add_child(Node.u32('exist', 1)) + facility.add_child(Node.u32("exist", 1)) data.add_child(self.__get_global_info()) return shopinfo def handle_gametop_regist_request(self, request: Node) -> Node: - data = request.child('data') - player = data.child('player') - refid = player.child_value('refid') - name = player.child_value('name') + data = request.child("data") + player = data.child("player") + refid = player.child_value("refid") + name = player.child_value("name") root = self.new_profile_by_refid(refid, name) return root def handle_gametop_get_pdata_request(self, request: Node) -> Node: - data = request.child('data') - player = data.child('player') - refid = player.child_value('refid') + data = request.child("data") + player = data.child("player") + refid = player.child_value("refid") root = self.get_profile_by_refid(refid) if root is None: - root = Node.void('gametop') - root.set_attribute('status', str(Status.NO_PROFILE)) + root = Node.void("gametop") + root.set_attribute("status", str(Status.NO_PROFILE)) return root def handle_gametop_get_mdata_request(self, request: Node) -> Node: - data = request.child('data') - player = data.child('player') - extid = player.child_value('jid') - mdata_ver = player.child_value('mdata_ver') # Game requests mdata 3 times per profile for some reason + data = request.child("data") + player = data.child("player") + extid = player.child_value("jid") + mdata_ver = player.child_value( + "mdata_ver" + ) # Game requests mdata 3 times per profile for some reason if mdata_ver != 1: - root = Node.void('gametop') - datanode = Node.void('data') + root = Node.void("gametop") + datanode = Node.void("data") root.add_child(datanode) - player = Node.void('player') + player = Node.void("player") datanode.add_child(player) - player.add_child(Node.s32('jid', extid)) - playdata = Node.void('mdata_list') + player.add_child(Node.s32("jid", extid)) + playdata = Node.void("mdata_list") player.add_child(playdata) return root root = self.get_scores_by_extid(extid) if root is None: - root = Node.void('gametop') - root.set_attribute('status', str(Status.NO_PROFILE)) + root = Node.void("gametop") + root.set_attribute("status", str(Status.NO_PROFILE)) return root def handle_gametop_get_info_request(self, request: Node) -> Node: - root = Node.void('gametop') - data = Node.void('data') + root = Node.void("gametop") + data = Node.void("data") root.add_child(data) data.add_child(self.__get_global_info()) return root def handle_gametop_get_course_request(self, request: Node) -> Node: - data = request.child('data') - player = data.child('player') - extid = player.child_value('jid') + data = request.child("data") + player = data.child("player") + extid = player.child_value("jid") - gametop = Node.void('gametop') - data = Node.void('data') + gametop = Node.void("gametop") + data = Node.void("data") gametop.add_child(data) # Course list available - course_list = Node.void('course_list') + course_list = Node.void("course_list") data.add_child(course_list) validcourses: List[int] = [] courses = self.get_all_courses() - courses.extend([ - { - 'id': 31, - 'name': 'Enjoy! The 5th KAC ~ tracks of prop ~', - 'level': 5, - 'music': [ - (60000065, 1), - (60000008, 1), - (60000001, 1), - (60001009, 1), - (60000010, 1), - ], - 'requirements': { - self.COURSE_REQUIREMENT_SCORE: [900000, 950000, 980000], - self.COURSE_REQUIREMENT_FULL_COMBO: [1, 2, 4], + courses.extend( + [ + { + "id": 31, + "name": "Enjoy! The 5th KAC ~ tracks of prop ~", + "level": 5, + "music": [ + (60000065, 1), + (60000008, 1), + (60000001, 1), + (60001009, 1), + (60000010, 1), + ], + "requirements": { + self.COURSE_REQUIREMENT_SCORE: [900000, 950000, 980000], + self.COURSE_REQUIREMENT_FULL_COMBO: [1, 2, 4], + }, }, - }, - { - 'id': 32, - 'name': 'Challenge! The 5th KAC ~ tracks of prop ~', - 'level': 7, - 'music': [ - (60000065, 2), - (60000008, 2), - (60000001, 2), - (60001009, 2), - (60000010, 2), - ], - 'requirements': { - self.COURSE_REQUIREMENT_SCORE: [900000, 950000, 980000], + { + "id": 32, + "name": "Challenge! The 5th KAC ~ tracks of prop ~", + "level": 7, + "music": [ + (60000065, 2), + (60000008, 2), + (60000001, 2), + (60001009, 2), + (60000010, 2), + ], + "requirements": { + self.COURSE_REQUIREMENT_SCORE: [900000, 950000, 980000], + }, }, - }, - { - 'id': 33, - 'name': 'The 5th KAC ~ tracks of prop ~', - 'level': 10, - 'music': [ - (60000065, 2), - (60000008, 2), - (60000001, 2), - (60001009, 2), - (60000010, 2), - ], - 'requirements': { - self.COURSE_REQUIREMENT_SCORE: [920000, 950000, 980000], + { + "id": 33, + "name": "The 5th KAC ~ tracks of prop ~", + "level": 10, + "music": [ + (60000065, 2), + (60000008, 2), + (60000001, 2), + (60001009, 2), + (60000010, 2), + ], + "requirements": { + self.COURSE_REQUIREMENT_SCORE: [920000, 950000, 980000], + }, }, - }, - ]) + ] + ) for course in courses: - coursenode = Node.void('course') + coursenode = Node.void("course") course_list.add_child(coursenode) # Basic course info - if course['id'] in validcourses: - raise Exception('Cannot have same course ID specified twice!') - validcourses.append(course['id']) - coursenode.add_child(Node.s32('id', course['id'])) - coursenode.add_child(Node.string('name', course['name'])) - coursenode.add_child(Node.u8('level', course['level'])) + if course["id"] in validcourses: + raise Exception("Cannot have same course ID specified twice!") + validcourses.append(course["id"]) + coursenode.add_child(Node.s32("id", course["id"])) + coursenode.add_child(Node.string("name", course["name"])) + coursenode.add_child(Node.u8("level", course["level"])) # Translate internal to game def translate_req(internal_req: int) -> int: @@ -650,8 +736,8 @@ class JubeatProp( silver_values = [0] * 3 gold_values = [0] * 3 slot = 0 - for req in course['requirements']: - req_values = course['requirements'][req] + for req in course["requirements"]: + req_values = course["requirements"][req] ids[slot] = translate_req(req) bronze_values[slot] = req_values[0] @@ -659,24 +745,24 @@ class JubeatProp( gold_values[slot] = req_values[2] slot = slot + 1 - norma = Node.void('norma') + norma = Node.void("norma") coursenode.add_child(norma) - norma.add_child(Node.s32_array('norma_id', ids)) - norma.add_child(Node.s32_array('bronze_value', bronze_values)) - norma.add_child(Node.s32_array('silver_value', silver_values)) - norma.add_child(Node.s32_array('gold_value', gold_values)) + norma.add_child(Node.s32_array("norma_id", ids)) + norma.add_child(Node.s32_array("bronze_value", bronze_values)) + norma.add_child(Node.s32_array("silver_value", silver_values)) + norma.add_child(Node.s32_array("gold_value", gold_values)) # Music list for course music_index = 0 - music_list = Node.void('music_list') + music_list = Node.void("music_list") coursenode.add_child(music_list) - for entry in course['music']: - music = Node.void('music') - music.set_attribute('index', str(music_index)) + for entry in course["music"]: + music = Node.void("music") + music.set_attribute("index", str(music_index)) music_list.add_child(music) - music.add_child(Node.s32('music_id', entry[0])) - music.add_child(Node.u8('seq', entry[1])) + music.add_child(Node.s32("music_id", entry[0])) + music.add_child(Node.u8("seq", entry[1])) music_index = music_index + 1 # Look up profile so we can load the last course played @@ -686,13 +772,13 @@ class JubeatProp( profile = Profile(self.game, self.version, "", extid) # Player scores for courses - player_list = Node.void('player_list') + player_list = Node.void("player_list") data.add_child(player_list) - player = Node.void('player') + player = Node.void("player") player_list.add_child(player) - player.add_child(Node.s32('jid', extid)) + player.add_child(Node.s32("jid", extid)) - result_list = Node.void('result_list') + result_list = Node.void("result_list") player.add_child(result_list) playercourses = self.get_courses(userid) for courseid in playercourses: @@ -704,24 +790,28 @@ class JubeatProp( self.COURSE_RATING_BRONZE: self.GAME_COURSE_RATING_BRONZE, self.COURSE_RATING_SILVER: self.GAME_COURSE_RATING_SILVER, self.COURSE_RATING_GOLD: self.GAME_COURSE_RATING_GOLD, - }[playercourses[courseid]['rating']] - scores = playercourses[courseid]['scores'] + }[playercourses[courseid]["rating"]] + scores = playercourses[courseid]["scores"] - result = Node.void('result') + result = Node.void("result") result_list.add_child(result) - result.add_child(Node.s32('id', courseid)) - result.add_child(Node.u8('rating', rating)) - result.add_child(Node.s32_array('score', scores)) + result.add_child(Node.s32("id", courseid)) + result.add_child(Node.u8("rating", rating)) + result.add_child(Node.s32_array("score", scores)) # Last course ID - data.add_child(Node.s32('last_course_id', profile.get_dict('last').get_int('last_course_id', -1))) + data.add_child( + Node.s32( + "last_course_id", profile.get_dict("last").get_int("last_course_id", -1) + ) + ) return gametop def handle_gametop_get_league_request(self, request: Node) -> Node: - data = request.child('data') - player = data.child('player') - extid = player.child_value('jid') + data = request.child("data") + player = data.child("player") + extid = player.child_value("jid") # Look up profile so we can load the last course played userid = self.data.remote.user.from_extid(self.game, self.version, extid) @@ -729,242 +819,325 @@ class JubeatProp( if profile is None: profile = Profile(self.game, self.version, "", extid) - gametop = Node.void('gametop') - data = Node.void('data') + gametop = Node.void("gametop") + data = Node.void("data") gametop.add_child(data) - league_list = Node.void('league_list') + league_list = Node.void("league_list") data.add_child(league_list) # Look up the current league charts in the DB - entry = self.data.local.game.get_time_sensitive_settings(self.game, self.version, 'league') + entry = self.data.local.game.get_time_sensitive_settings( + self.game, self.version, "league" + ) if entry is not None: # Just get the week number, use that as the ID - leagueid = int(entry['start_time'] / 604800) + leagueid = int(entry["start_time"] / 604800) - league = Node.void('league') + league = Node.void("league") league_list.add_child(league) - league.set_attribute('index', '0') + league.set_attribute("index", "0") - league.add_child(Node.s32('id', leagueid)) - league.add_child(Node.u64('stime', entry['start_time'] * 1000)) - league.add_child(Node.u64('etime', entry['end_time'] * 1000)) + league.add_child(Node.s32("id", leagueid)) + league.add_child(Node.u64("stime", entry["start_time"] * 1000)) + league.add_child(Node.u64("etime", entry["end_time"] * 1000)) - music_list = Node.void('music_list') + music_list = Node.void("music_list") league.add_child(music_list) # We need to know the player class so we can determine what chart to present. - current_class = profile.get_int('league_class', 1) + current_class = profile.get_int("league_class", 1) song_index = 0 - for song in entry['music']: - music = Node.void('music') + for song in entry["music"]: + music = Node.void("music") music_list.add_child(music) - music.set_attribute('index', str(song_index)) + music.set_attribute("index", str(song_index)) song_index = song_index + 1 - music.add_child(Node.s32('music_id', song)) - music.add_child(Node.u8('seq', 1 if current_class == 1 else 2)) + music.add_child(Node.s32("music_id", song)) + music.add_child(Node.u8("seq", 1 if current_class == 1 else 2)) - player_list = Node.void('player_list') + player_list = Node.void("player_list") league.add_child(player_list) - player = Node.void('player') + player = Node.void("player") player_list.add_child(player) - player.add_child(Node.s32('jid', extid)) - result = Node.void('result') + player.add_child(Node.s32("jid", extid)) + result = Node.void("result") player.add_child(result) - league_score = self.data.local.user.get_achievement(self.game, self.version, userid, leagueid, 'league') + league_score = self.data.local.user.get_achievement( + self.game, self.version, userid, leagueid, "league" + ) if league_score is None: league_score = ValidatedDict() - result.add_child(Node.s32_array('score', league_score.get_int_array('score', 3, [0] * 3))) - result.add_child(Node.s8_array('clear', league_score.get_int_array('clear', 3, [0] * 3))) + result.add_child( + Node.s32_array("score", league_score.get_int_array("score", 3, [0] * 3)) + ) + result.add_child( + Node.s8_array("clear", league_score.get_int_array("clear", 3, [0] * 3)) + ) - data.add_child(Node.s32('last_class', profile.get_dict('last').get_int('league_class', 1))) - data.add_child(Node.s32('last_subclass', profile.get_dict('last').get_int('league_subclass', 5))) - data.add_child(Node.bool('is_checked', profile.get_bool('league_is_checked'))) + data.add_child( + Node.s32("last_class", profile.get_dict("last").get_int("league_class", 1)) + ) + data.add_child( + Node.s32( + "last_subclass", profile.get_dict("last").get_int("league_subclass", 5) + ) + ) + data.add_child(Node.bool("is_checked", profile.get_bool("league_is_checked"))) return gametop def format_profile(self, userid: UserID, profile: Profile) -> Node: - root = Node.void('gametop') - data = Node.void('data') + root = Node.void("gametop") + data = Node.void("data") root.add_child(data) # Jubeat Prop appears to allow full event overrides per-player data.add_child(self.__get_global_info()) - player = Node.void('player') + player = Node.void("player") data.add_child(player) # Basic profile info - player.add_child(Node.string('name', profile.get_str('name', 'なし'))) - player.add_child(Node.s32('jid', profile.extid)) + player.add_child(Node.string("name", profile.get_str("name", "なし"))) + player.add_child(Node.s32("jid", profile.extid)) # Miscelaneous crap - player.add_child(Node.s32('session_id', 1)) - player.add_child(Node.u64('event_flag', 0)) + player.add_child(Node.s32("session_id", 1)) + player.add_child(Node.u64("event_flag", 0)) # Player info and statistics - info = Node.void('info') + info = Node.void("info") player.add_child(info) - info.add_child(Node.s16('jubility', profile.get_int('jubility'))) - info.add_child(Node.s16('jubility_yday', profile.get_int('jubility_yday'))) - info.add_child(Node.s32('tune_cnt', profile.get_int('tune_cnt'))) - info.add_child(Node.s32('save_cnt', profile.get_int('save_cnt'))) - info.add_child(Node.s32('saved_cnt', profile.get_int('saved_cnt'))) - info.add_child(Node.s32('fc_cnt', profile.get_int('fc_cnt'))) - info.add_child(Node.s32('ex_cnt', profile.get_int('ex_cnt'))) - info.add_child(Node.s32('clear_cnt', profile.get_int('clear_cnt'))) - info.add_child(Node.s32('pf_cnt', profile.get_int('pf_cnt'))) - info.add_child(Node.s32('match_cnt', profile.get_int('match_cnt'))) - info.add_child(Node.s32('beat_cnt', profile.get_int('beat_cnt'))) - info.add_child(Node.s32('mynews_cnt', profile.get_int('mynews_cnt'))) - info.add_child(Node.s32('bonus_tune_points', profile.get_int('bonus_tune_points'))) - info.add_child(Node.bool('is_bonus_tune_played', profile.get_bool('is_bonus_tune_played'))) + info.add_child(Node.s16("jubility", profile.get_int("jubility"))) + info.add_child(Node.s16("jubility_yday", profile.get_int("jubility_yday"))) + info.add_child(Node.s32("tune_cnt", profile.get_int("tune_cnt"))) + info.add_child(Node.s32("save_cnt", profile.get_int("save_cnt"))) + info.add_child(Node.s32("saved_cnt", profile.get_int("saved_cnt"))) + info.add_child(Node.s32("fc_cnt", profile.get_int("fc_cnt"))) + info.add_child(Node.s32("ex_cnt", profile.get_int("ex_cnt"))) + info.add_child(Node.s32("clear_cnt", profile.get_int("clear_cnt"))) + info.add_child(Node.s32("pf_cnt", profile.get_int("pf_cnt"))) + info.add_child(Node.s32("match_cnt", profile.get_int("match_cnt"))) + info.add_child(Node.s32("beat_cnt", profile.get_int("beat_cnt"))) + info.add_child(Node.s32("mynews_cnt", profile.get_int("mynews_cnt"))) + info.add_child( + Node.s32("bonus_tune_points", profile.get_int("bonus_tune_points")) + ) + info.add_child( + Node.bool("is_bonus_tune_played", profile.get_bool("is_bonus_tune_played")) + ) # Looks to be set to true when there's an old profile, stops tutorial from # happening on first load. - info.add_child(Node.bool('inherit', profile.get_bool('has_old_version') and not profile.get_bool('saved'))) + info.add_child( + Node.bool( + "inherit", + profile.get_bool("has_old_version") and not profile.get_bool("saved"), + ) + ) # Not saved, but loaded - info.add_child(Node.s32('mtg_entry_cnt', 123)) - info.add_child(Node.s32('mtg_hold_cnt', 456)) - info.add_child(Node.u8('mtg_result', 10)) + info.add_child(Node.s32("mtg_entry_cnt", 123)) + info.add_child(Node.s32("mtg_hold_cnt", 456)) + info.add_child(Node.u8("mtg_result", 10)) # Last played data, for showing cursor and such - lastdict = profile.get_dict('last') - last = Node.void('last') + lastdict = profile.get_dict("last") + last = Node.void("last") player.add_child(last) - last.add_child(Node.s64('play_time', lastdict.get_int('play_time'))) - last.add_child(Node.string('shopname', lastdict.get_str('shopname'))) - last.add_child(Node.string('areaname', lastdict.get_str('areaname'))) - last.add_child(Node.s8('expert_option', lastdict.get_int('expert_option'))) - last.add_child(Node.s8('category', lastdict.get_int('category'))) - last.add_child(Node.s8('sort', lastdict.get_int('sort'))) - last.add_child(Node.s32('music_id', lastdict.get_int('music_id'))) - last.add_child(Node.s8('seq_id', lastdict.get_int('seq_id'))) + last.add_child(Node.s64("play_time", lastdict.get_int("play_time"))) + last.add_child(Node.string("shopname", lastdict.get_str("shopname"))) + last.add_child(Node.string("areaname", lastdict.get_str("areaname"))) + last.add_child(Node.s8("expert_option", lastdict.get_int("expert_option"))) + last.add_child(Node.s8("category", lastdict.get_int("category"))) + last.add_child(Node.s8("sort", lastdict.get_int("sort"))) + last.add_child(Node.s32("music_id", lastdict.get_int("music_id"))) + last.add_child(Node.s8("seq_id", lastdict.get_int("seq_id"))) - settings = Node.void('settings') + settings = Node.void("settings") last.add_child(settings) - settings.add_child(Node.s8('marker', lastdict.get_int('marker'))) - settings.add_child(Node.s8('theme', lastdict.get_int('theme'))) - settings.add_child(Node.s16('title', lastdict.get_int('title'))) - settings.add_child(Node.s16('parts', lastdict.get_int('parts'))) - settings.add_child(Node.s8('rank_sort', lastdict.get_int('rank_sort'))) - settings.add_child(Node.s8('combo_disp', lastdict.get_int('combo_disp'))) - settings.add_child(Node.s16_array('emblem', lastdict.get_int_array('emblem', 5))) - settings.add_child(Node.s8('matching', lastdict.get_int('matching'))) - settings.add_child(Node.s8('hazard', lastdict.get_int('hazard'))) - settings.add_child(Node.s8('hard', lastdict.get_int('hard'))) + settings.add_child(Node.s8("marker", lastdict.get_int("marker"))) + settings.add_child(Node.s8("theme", lastdict.get_int("theme"))) + settings.add_child(Node.s16("title", lastdict.get_int("title"))) + settings.add_child(Node.s16("parts", lastdict.get_int("parts"))) + settings.add_child(Node.s8("rank_sort", lastdict.get_int("rank_sort"))) + settings.add_child(Node.s8("combo_disp", lastdict.get_int("combo_disp"))) + settings.add_child( + Node.s16_array("emblem", lastdict.get_int_array("emblem", 5)) + ) + settings.add_child(Node.s8("matching", lastdict.get_int("matching"))) + settings.add_child(Node.s8("hazard", lastdict.get_int("hazard"))) + settings.add_child(Node.s8("hard", lastdict.get_int("hard"))) # Secret unlocks - item = Node.void('item') + item = Node.void("item") player.add_child(item) - item.add_child(Node.s32_array('music_list', profile.get_int_array('music_list', 32, [-1] * 32))) - item.add_child(Node.s32_array('secret_list', profile.get_int_array('secret_list', 32, [-1] * 32))) - item.add_child(Node.s16('theme_list', profile.get_int('theme_list', -1))) - item.add_child(Node.s32_array('marker_list', profile.get_int_array('marker_list', 2, [-1] * 2))) - item.add_child(Node.s32_array('title_list', profile.get_int_array('title_list', 160, [-1] * 160))) - item.add_child(Node.s32_array('parts_list', profile.get_int_array('parts_list', 160, [-1] * 160))) - item.add_child(Node.s32_array('emblem_list', profile.get_int_array('emblem_list', 96, [-1] * 96))) + item.add_child( + Node.s32_array( + "music_list", profile.get_int_array("music_list", 32, [-1] * 32) + ) + ) + item.add_child( + Node.s32_array( + "secret_list", profile.get_int_array("secret_list", 32, [-1] * 32) + ) + ) + item.add_child(Node.s16("theme_list", profile.get_int("theme_list", -1))) + item.add_child( + Node.s32_array( + "marker_list", profile.get_int_array("marker_list", 2, [-1] * 2) + ) + ) + item.add_child( + Node.s32_array( + "title_list", profile.get_int_array("title_list", 160, [-1] * 160) + ) + ) + item.add_child( + Node.s32_array( + "parts_list", profile.get_int_array("parts_list", 160, [-1] * 160) + ) + ) + item.add_child( + Node.s32_array( + "emblem_list", profile.get_int_array("emblem_list", 96, [-1] * 96) + ) + ) - new = Node.void('new') + new = Node.void("new") item.add_child(new) - new.add_child(Node.s32_array('secret_list', profile.get_int_array('secret_list_new', 32, [-1] * 32))) - new.add_child(Node.s16('theme_list', profile.get_int('theme_list_new', -1))) - new.add_child(Node.s32_array('marker_list', profile.get_int_array('marker_list_new', 2, [-1] * 2))) + new.add_child( + Node.s32_array( + "secret_list", profile.get_int_array("secret_list_new", 32, [-1] * 32) + ) + ) + new.add_child(Node.s16("theme_list", profile.get_int("theme_list_new", -1))) + new.add_child( + Node.s32_array( + "marker_list", profile.get_int_array("marker_list_new", 2, [-1] * 2) + ) + ) # Sane defaults for unknown/who cares nodes - history = Node.void('history') + history = Node.void("history") player.add_child(history) - history.set_attribute('count', '0') - lab_edit_seq = Node.void('lab_edit_seq') + history.set_attribute("count", "0") + lab_edit_seq = Node.void("lab_edit_seq") player.add_child(lab_edit_seq) - lab_edit_seq.set_attribute('count', '0') - cabinet_survey = Node.void('cabinet_survey') + lab_edit_seq.set_attribute("count", "0") + cabinet_survey = Node.void("cabinet_survey") player.add_child(cabinet_survey) - cabinet_survey.add_child(Node.u32('read_flag', 0)) - kaitou_bisco = Node.void('kaitou_bisco') + cabinet_survey.add_child(Node.u32("read_flag", 0)) + kaitou_bisco = Node.void("kaitou_bisco") player.add_child(kaitou_bisco) - kaitou_bisco.add_child(Node.u32('read_flag', profile.get_int('kaitou_bisco_read_flag'))) - navi = Node.void('navi') + kaitou_bisco.add_child( + Node.u32("read_flag", profile.get_int("kaitou_bisco_read_flag")) + ) + navi = Node.void("navi") player.add_child(navi) - navi.add_child(Node.u32('flag', profile.get_int('navi_flag'))) + navi.add_child(Node.u32("flag", profile.get_int("navi_flag"))) # Player status for events - event_info = Node.void('event_info') + event_info = Node.void("event_info") player.add_child(event_info) - achievements = self.data.local.user.get_achievements(self.game, self.version, userid) + achievements = self.data.local.user.get_achievements( + self.game, self.version, userid + ) for achievement in achievements: - if achievement.type == 'event': + if achievement.type == "event": # There are two significant bits here, 0x1 and 0x2, I think the first # one is whether the event is started, second is if its finished? - event = Node.void('event') + event = Node.void("event") event_info.add_child(event) - event.set_attribute('type', str(achievement.id)) + event.set_attribute("type", str(achievement.id)) state = 0x0 - state = state + 0x2 if achievement.data.get_bool('is_completed') else 0x0 - event.add_child(Node.u8('state', state)) + state = ( + state + 0x2 if achievement.data.get_bool("is_completed") else 0x0 + ) + event.add_child(Node.u8("state", state)) # Full combo challenge - entry = self.data.local.game.get_time_sensitive_settings(self.game, self.version, 'fc_challenge') + entry = self.data.local.game.get_time_sensitive_settings( + self.game, self.version, "fc_challenge" + ) if entry is None: entry = ValidatedDict() # Figure out if we've played these songs - start_time, end_time = self.data.local.network.get_schedule_duration('daily') - today_attempts = self.data.local.music.get_all_attempts(self.game, self.music_version, userid, entry.get_int('today', -1), timelimit=start_time) - whim_attempts = self.data.local.music.get_all_attempts(self.game, self.music_version, userid, entry.get_int('whim', -1), timelimit=start_time) + start_time, end_time = self.data.local.network.get_schedule_duration("daily") + today_attempts = self.data.local.music.get_all_attempts( + self.game, + self.music_version, + userid, + entry.get_int("today", -1), + timelimit=start_time, + ) + whim_attempts = self.data.local.music.get_all_attempts( + self.game, + self.music_version, + userid, + entry.get_int("whim", -1), + timelimit=start_time, + ) - fc_challenge = Node.void('fc_challenge') + fc_challenge = Node.void("fc_challenge") player.add_child(fc_challenge) - today = Node.void('today') + today = Node.void("today") fc_challenge.add_child(today) - today.add_child(Node.s32('music_id', entry.get_int('today', -1))) - today.add_child(Node.u8('state', 0x40 if len(today_attempts) > 0 else 0x0)) - whim = Node.void('whim') + today.add_child(Node.s32("music_id", entry.get_int("today", -1))) + today.add_child(Node.u8("state", 0x40 if len(today_attempts) > 0 else 0x0)) + whim = Node.void("whim") fc_challenge.add_child(whim) - whim.add_child(Node.s32('music_id', entry.get_int('whim', -1))) - whim.add_child(Node.u8('state', 0x40 if len(whim_attempts) > 0 else 0x0)) + whim.add_child(Node.s32("music_id", entry.get_int("whim", -1))) + whim.add_child(Node.u8("state", 0x40 if len(whim_attempts) > 0 else 0x0)) # No news, ever. - news = Node.void('news') + news = Node.void("news") player.add_child(news) - news.add_child(Node.s16('checked', 0)) - news.add_child(Node.u32('checked_flag', 0)) + news.add_child(Node.s16("checked", 0)) + news.add_child(Node.u32("checked_flag", 0)) # Add rivals to profile. - rivallist = Node.void('rivallist') + rivallist = Node.void("rivallist") player.add_child(rivallist) links = self.data.local.user.get_links(self.game, self.version, userid) rivalcount = 0 for link in links: - if link.type != 'rival': + if link.type != "rival": continue rprofile = self.get_profile(link.other_userid) if rprofile is None: continue - rival = Node.void('rival') + rival = Node.void("rival") rivallist.add_child(rival) - rival.add_child(Node.s32('jid', rprofile.extid)) - rival.add_child(Node.string('name', rprofile.get_str('name'))) + rival.add_child(Node.s32("jid", rprofile.extid)) + rival.add_child(Node.string("name", rprofile.get_str("name"))) - rcareerdict = rprofile.get_dict('career') - career = Node.void('career') + rcareerdict = rprofile.get_dict("career") + career = Node.void("career") rival.add_child(career) - career.add_child(Node.s16('level', rcareerdict.get_int('level', 1))) + career.add_child(Node.s16("level", rcareerdict.get_int("level", 1))) - league = Node.void('league') + league = Node.void("league") rival.add_child(league) - league.add_child(Node.bool('is_first_play', rprofile.get_bool('league_is_first_play', True))) - league.add_child(Node.s32('class', rprofile.get_int('league_class', 1))) - league.add_child(Node.s32('subclass', rprofile.get_int('league_subclass', 5))) + league.add_child( + Node.bool( + "is_first_play", rprofile.get_bool("league_is_first_play", True) + ) + ) + league.add_child(Node.s32("class", rprofile.get_int("league_class", 1))) + league.add_child( + Node.s32("subclass", rprofile.get_int("league_subclass", 5)) + ) # Lazy way of keeping track of rivals, since we can only have 3 # or the game with throw up. @@ -972,29 +1145,29 @@ class JubeatProp( if rivalcount >= 3: break - rivallist.set_attribute('count', str(rivalcount)) + rivallist.set_attribute("count", str(rivalcount)) # Nothing in life is free, WTF? - free_first_play = Node.void('free_first_play') + free_first_play = Node.void("free_first_play") player.add_child(free_first_play) - free_first_play.add_child(Node.bool('is_available', False)) - free_first_play.add_child(Node.s32('point', 0)) - free_first_play.add_child(Node.s32('point_used', 0)) - come_come_jbox = Node.void('come_come_jbox') + free_first_play.add_child(Node.bool("is_available", False)) + free_first_play.add_child(Node.s32("point", 0)) + free_first_play.add_child(Node.s32("point_used", 0)) + come_come_jbox = Node.void("come_come_jbox") free_first_play.add_child(come_come_jbox) - come_come_jbox.add_child(Node.bool('is_valid', False)) - come_come_jbox.add_child(Node.s64('end_time_if_paired', 0)) + come_come_jbox.add_child(Node.bool("is_valid", False)) + come_come_jbox.add_child(Node.s64("end_time_if_paired", 0)) # JBox stuff - jbox = Node.void('jbox') - jboxdict = profile.get_dict('jbox') + jbox = Node.void("jbox") + jboxdict = profile.get_dict("jbox") player.add_child(jbox) - jbox.add_child(Node.s32('point', jboxdict.get_int('point'))) - emblem = Node.void('emblem') + jbox.add_child(Node.s32("point", jboxdict.get_int("point"))) + emblem = Node.void("emblem") jbox.add_child(emblem) - normal = Node.void('normal') + normal = Node.void("normal") emblem.add_child(normal) - premium = Node.void('premium') + premium = Node.void("premium") emblem.add_child(premium) # Calculate a random index for normal and premium to give to player @@ -1003,10 +1176,10 @@ class JubeatProp( normalemblems: Set[int] = set() premiumemblems: Set[int] = set() for gameitem in gameitems: - if gameitem.type == 'emblem': - if gameitem.data.get_int('rarity') in {1, 2, 3}: + if gameitem.type == "emblem": + if gameitem.data.get_int("rarity") in {1, 2, 3}: normalemblems.add(gameitem.id) - if gameitem.data.get_int('rarity') in {3, 4, 5}: + if gameitem.data.get_int("rarity") in {3, 4, 5}: premiumemblems.add(gameitem.id) # Default to some emblems in case the catalog is not available. @@ -1017,52 +1190,56 @@ class JubeatProp( if premiumemblems: premiumindex = random.sample(premiumemblems, 1)[0] - normal.add_child(Node.s16('index', normalindex)) - premium.add_child(Node.s16('index', premiumindex)) + normal.add_child(Node.s16("index", normalindex)) + premium.add_child(Node.s16("index", premiumindex)) # Career stuff - career = Node.void('career') - careerdict = profile.get_dict('career') + career = Node.void("career") + careerdict = profile.get_dict("career") player.add_child(career) - career.add_child(Node.s16('level', careerdict.get_int('level', 1))) - career.add_child(Node.s32('point', careerdict.get_int('point'))) - career.add_child(Node.s32_array('param', careerdict.get_int_array('param', 10, [-1] * 10))) - career.add_child(Node.bool('is_unlocked', careerdict.get_bool('is_unlocked'))) + career.add_child(Node.s16("level", careerdict.get_int("level", 1))) + career.add_child(Node.s32("point", careerdict.get_int("point"))) + career.add_child( + Node.s32_array("param", careerdict.get_int_array("param", 10, [-1] * 10)) + ) + career.add_child(Node.bool("is_unlocked", careerdict.get_bool("is_unlocked"))) # League stuff - league = Node.void('league') + league = Node.void("league") player.add_child(league) - league.add_child(Node.bool('is_first_play', profile.get_bool('league_is_first_play', True))) - league.add_child(Node.s32('class', profile.get_int('league_class', 1))) - league.add_child(Node.s32('subclass', profile.get_int('league_subclass', 5))) + league.add_child( + Node.bool("is_first_play", profile.get_bool("league_is_first_play", True)) + ) + league.add_child(Node.s32("class", profile.get_int("league_class", 1))) + league.add_child(Node.s32("subclass", profile.get_int("league_subclass", 5))) # New Music stuff - new_music = Node.void('new_music') + new_music = Node.void("new_music") player.add_child(new_music) # Emblem list stuff? - eapass_privilege = Node.void('eapass_privilege') + eapass_privilege = Node.void("eapass_privilege") player.add_child(eapass_privilege) - emblem_list = Node.void('emblem_list') + emblem_list = Node.void("emblem_list") eapass_privilege.add_child(emblem_list) # Bonus music stuff? - bonus_music = Node.void('bonus_music') + bonus_music = Node.void("bonus_music") player.add_child(bonus_music) - bonus_music.add_child(Node.void('music')) - bonus_music.add_child(Node.s32('event_id', -1)) - bonus_music.add_child(Node.string('till_time', '')) + bonus_music.add_child(Node.void("music")) + bonus_music.add_child(Node.s32("event_id", -1)) + bonus_music.add_child(Node.string("till_time", "")) # Bistro stuff is back? - bistro = Node.void('bistro') + bistro = Node.void("bistro") player.add_child(bistro) - chef = Node.void('chef') + chef = Node.void("chef") bistro.add_child(chef) - chef.add_child(Node.s32('id', 1)) - bistro.add_child(Node.s32('carry_over', 0)) - route_list = Node.void('route_list') + chef.add_child(Node.s32("id", 1)) + bistro.add_child(Node.s32("carry_over", 0)) + route_list = Node.void("route_list") bistro.add_child(route_list) - route_list.add_child(Node.u8('route_count', 0)) + route_list.add_child(Node.u8("route_count", 0)) # If we have routes, they look like this: # # # @@ -1072,10 +1249,10 @@ class JubeatProp( # # ?? # - bistro.add_child(Node.bool('extension', False)) + bistro.add_child(Node.bool("extension", False)) # Gift list, maybe from other players? - gift_list = Node.void('gift_list') + gift_list = Node.void("gift_list") player.add_child(gift_list) # If we had gifts, they look like this: # @@ -1084,117 +1261,143 @@ class JubeatProp( return root - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() - newprofile.replace_bool('saved', True) - data = request.child('data') + newprofile.replace_bool("saved", True) + data = request.child("data") # Grab system information - sysinfo = data.child('info') + sysinfo = data.child("info") # Grab player information - player = data.child('player') + player = data.child("player") # Grab result information - result = data.child('result') + result = data.child("result") # Grab last information. Lots of this will be filled in while grabbing scores - last = newprofile.get_dict('last') + last = newprofile.get_dict("last") if sysinfo is not None: - last.replace_int('play_time', sysinfo.child_value('time_gameend')) - last.replace_str('shopname', sysinfo.child_value('shopname')) - last.replace_str('areaname', sysinfo.child_value('areaname')) + last.replace_int("play_time", sysinfo.child_value("time_gameend")) + last.replace_str("shopname", sysinfo.child_value("shopname")) + last.replace_str("areaname", sysinfo.child_value("areaname")) # Grab player info for echoing back - info = player.child('info') + info = player.child("info") if info is not None: - newprofile.replace_int('jubility', info.child_value('jubility')) - newprofile.replace_int('jubility_yday', info.child_value('jubility_yday')) - newprofile.replace_int('tune_cnt', info.child_value('tune_cnt')) - newprofile.replace_int('save_cnt', info.child_value('save_cnt')) - newprofile.replace_int('saved_cnt', info.child_value('saved_cnt')) - newprofile.replace_int('fc_cnt', info.child_value('fc_cnt')) - newprofile.replace_int('ex_cnt', info.child_value('ex_cnt')) - newprofile.replace_int('pf_cnt', info.child_value('pf_cnt')) - newprofile.replace_int('clear_cnt', info.child_value('clear_cnt')) - newprofile.replace_int('match_cnt', info.child_value('match_cnt')) - newprofile.replace_int('beat_cnt', info.child_value('beat_cnt')) - newprofile.replace_int('total_best_score', info.child_value('total_best_score')) - newprofile.replace_int('mynews_cnt', info.child_value('mynews_cnt')) + newprofile.replace_int("jubility", info.child_value("jubility")) + newprofile.replace_int("jubility_yday", info.child_value("jubility_yday")) + newprofile.replace_int("tune_cnt", info.child_value("tune_cnt")) + newprofile.replace_int("save_cnt", info.child_value("save_cnt")) + newprofile.replace_int("saved_cnt", info.child_value("saved_cnt")) + newprofile.replace_int("fc_cnt", info.child_value("fc_cnt")) + newprofile.replace_int("ex_cnt", info.child_value("ex_cnt")) + newprofile.replace_int("pf_cnt", info.child_value("pf_cnt")) + newprofile.replace_int("clear_cnt", info.child_value("clear_cnt")) + newprofile.replace_int("match_cnt", info.child_value("match_cnt")) + newprofile.replace_int("beat_cnt", info.child_value("beat_cnt")) + newprofile.replace_int( + "total_best_score", info.child_value("total_best_score") + ) + newprofile.replace_int("mynews_cnt", info.child_value("mynews_cnt")) - newprofile.replace_int('bonus_tune_points', info.child_value('bonus_tune_points')) - newprofile.replace_bool('is_bonus_tune_played', info.child_value('is_bonus_tune_played')) + newprofile.replace_int( + "bonus_tune_points", info.child_value("bonus_tune_points") + ) + newprofile.replace_bool( + "is_bonus_tune_played", info.child_value("is_bonus_tune_played") + ) # Grab last settings (finally mostly in its own node!) - lastnode = player.child('last') + lastnode = player.child("last") if lastnode is not None: - last.replace_int('expert_option', lastnode.child_value('expert_option')) - last.replace_int('sort', lastnode.child_value('sort')) - last.replace_int('category', lastnode.child_value('category')) + last.replace_int("expert_option", lastnode.child_value("expert_option")) + last.replace_int("sort", lastnode.child_value("sort")) + last.replace_int("category", lastnode.child_value("category")) - settings = lastnode.child('settings') + settings = lastnode.child("settings") if settings is not None: - last.replace_int('matching', settings.child_value('matching')) - last.replace_int('hazard', settings.child_value('hazard')) - last.replace_int('hard', settings.child_value('hard')) - last.replace_int('marker', settings.child_value('marker')) - last.replace_int('theme', settings.child_value('theme')) - last.replace_int('title', settings.child_value('title')) - last.replace_int('parts', settings.child_value('parts')) - last.replace_int('rank_sort', settings.child_value('rank_sort')) - last.replace_int('combo_disp', settings.child_value('combo_disp')) - last.replace_int_array('emblem', 5, settings.child_value('emblem')) + last.replace_int("matching", settings.child_value("matching")) + last.replace_int("hazard", settings.child_value("hazard")) + last.replace_int("hard", settings.child_value("hard")) + last.replace_int("marker", settings.child_value("marker")) + last.replace_int("theme", settings.child_value("theme")) + last.replace_int("title", settings.child_value("title")) + last.replace_int("parts", settings.child_value("parts")) + last.replace_int("rank_sort", settings.child_value("rank_sort")) + last.replace_int("combo_disp", settings.child_value("combo_disp")) + last.replace_int_array("emblem", 5, settings.child_value("emblem")) # Grab unlock progress - item = player.child('item') + item = player.child("item") if item is not None: - newprofile.replace_int_array('secret_list', 32, item.child_value('secret_list')) - newprofile.replace_int_array('title_list', 160, item.child_value('title_list')) - newprofile.replace_int('theme_list', item.child_value('theme_list')) - newprofile.replace_int_array('marker_list', 2, item.child_value('marker_list')) - newprofile.replace_int_array('parts_list', 160, item.child_value('parts_list')) - newprofile.replace_int_array('music_list', 32, item.child_value('music_list')) - newprofile.replace_int_array('emblem_list', 96, item.child_value('emblem_list')) + newprofile.replace_int_array( + "secret_list", 32, item.child_value("secret_list") + ) + newprofile.replace_int_array( + "title_list", 160, item.child_value("title_list") + ) + newprofile.replace_int("theme_list", item.child_value("theme_list")) + newprofile.replace_int_array( + "marker_list", 2, item.child_value("marker_list") + ) + newprofile.replace_int_array( + "parts_list", 160, item.child_value("parts_list") + ) + newprofile.replace_int_array( + "music_list", 32, item.child_value("music_list") + ) + newprofile.replace_int_array( + "emblem_list", 96, item.child_value("emblem_list") + ) - newitem = item.child('new') + newitem = item.child("new") if newitem is not None: - newprofile.replace_int_array('secret_list_new', 32, newitem.child_value('secret_list')) - newprofile.replace_int('theme_list_new', newitem.child_value('theme_list')) - newprofile.replace_int_array('marker_list_new', 2, newitem.child_value('marker_list')) + newprofile.replace_int_array( + "secret_list_new", 32, newitem.child_value("secret_list") + ) + newprofile.replace_int( + "theme_list_new", newitem.child_value("theme_list") + ) + newprofile.replace_int_array( + "marker_list_new", 2, newitem.child_value("marker_list") + ) # Career progression - career = player.child('career') - careerdict = newprofile.get_dict('career') + career = player.child("career") + careerdict = newprofile.get_dict("career") if career is not None: - careerdict.replace_int('level', career.child_value('level')) - careerdict.replace_int('point', career.child_value('point')) - careerdict.replace_int_array('param', 10, career.child_value('param')) - careerdict.replace_bool('is_unlocked', career.child_value('is_unlocked')) - newprofile.replace_dict('career', careerdict) + careerdict.replace_int("level", career.child_value("level")) + careerdict.replace_int("point", career.child_value("point")) + careerdict.replace_int_array("param", 10, career.child_value("param")) + careerdict.replace_bool("is_unlocked", career.child_value("is_unlocked")) + newprofile.replace_dict("career", careerdict) # jbox stuff - jbox = player.child('jbox') - jboxdict = newprofile.get_dict('jbox') + jbox = player.child("jbox") + jboxdict = newprofile.get_dict("jbox") if jbox is not None: - jboxdict.replace_int('point', jbox.child_value('point')) - emblemtype = jbox.child_value('emblem/type') - index = jbox.child_value('emblem/index') + jboxdict.replace_int("point", jbox.child_value("point")) + emblemtype = jbox.child_value("emblem/type") + index = jbox.child_value("emblem/index") if emblemtype == self.JBOX_EMBLEM_NORMAL: - jboxdict.replace_int('normal_index', index) + jboxdict.replace_int("normal_index", index) elif emblemtype == self.JBOX_EMBLEM_PREMIUM: - jboxdict.replace_int('premium_index', index) - newprofile.replace_dict('jbox', jboxdict) + jboxdict.replace_int("premium_index", index) + newprofile.replace_dict("jbox", jboxdict) # event stuff - event_info = player.child('event_info') + event_info = player.child("event_info") if event_info is not None: for child in event_info.children: try: - eventid = int(child.attribute('type')) + eventid = int(child.attribute("type")) except TypeError: # Event is empty continue - is_completed = child.child_value('is_completed') + is_completed = child.child_value("is_completed") # Figure out if we should update the rating/scores or not oldevent = self.data.local.user.get_achievement( @@ -1202,14 +1405,14 @@ class JubeatProp( self.version, userid, eventid, - 'event', + "event", ) if oldevent is None: # Create a new event structure for this oldevent = ValidatedDict() - oldevent.replace_bool('is_completed', is_completed) + oldevent.replace_bool("is_completed", is_completed) # Save it as an achievement self.data.local.user.put_achievement( @@ -1217,44 +1420,46 @@ class JubeatProp( self.version, userid, eventid, - 'event', + "event", oldevent, ) # A whole bunch of miscelaneous shit - newprofile.replace_int('navi_flag', player.child_value('navi/flag')) - newprofile.replace_int('kaitou_bisco_read_flag', player.child_value('kaitou_bisco/read_flag')) + newprofile.replace_int("navi_flag", player.child_value("navi/flag")) + newprofile.replace_int( + "kaitou_bisco_read_flag", player.child_value("kaitou_bisco/read_flag") + ) # Get timestamps for played songs timestamps: Dict[int, int] = {} - history = player.child('history') + history = player.child("history") if history is not None: for tune in history.children: - if tune.name != 'tune': + if tune.name != "tune": continue - entry = int(tune.attribute('log_id')) - ts = int(tune.child_value('timestamp') / 1000) + entry = int(tune.attribute("log_id")) + ts = int(tune.child_value("timestamp") / 1000) timestamps[entry] = ts # Grab scores and save those if result is not None: for tune in result.children: - if tune.name != 'tune': + if tune.name != "tune": continue - result = tune.child('player') + result = tune.child("player") - entry = int(tune.attribute('id')) - songid = tune.child_value('music') + entry = int(tune.attribute("id")) + songid = tune.child_value("music") timestamp = timestamps.get(entry, Time.now()) - chart = int(result.child('score').attribute('seq')) - points = result.child_value('score') - flags = int(result.child('score').attribute('clear')) - combo = int(result.child('score').attribute('combo')) - ghost = result.child_value('mbar') + chart = int(result.child("score").attribute("seq")) + points = result.child_value("score") + flags = int(result.child("score").attribute("clear")) + combo = int(result.child("score").attribute("combo")) + ghost = result.child_value("mbar") # Miscelaneous last data for echoing to profile get - last.replace_int('music_id', songid) - last.replace_int('seq_id', chart) + last.replace_int("music_id", songid) + last.replace_int("seq_id", chart) mapping = { self.GAME_FLAG_BIT_CLEARED: self.PLAY_MEDAL_CLEARED, @@ -1270,47 +1475,53 @@ class JubeatProp( if flags & bit > 0: medal = max(medal, mapping[bit]) - self.update_score(userid, timestamp, songid, chart, points, medal, combo, ghost) + self.update_score( + userid, timestamp, songid, chart, points, medal, combo, ghost + ) # If this was a course save, grab and save that info too - course = player.child('course') + course = player.child("course") if course is not None: - courseid = course.child_value('course_id') + courseid = course.child_value("course_id") rating = { self.GAME_COURSE_RATING_FAILED: self.COURSE_RATING_FAILED, self.GAME_COURSE_RATING_BRONZE: self.COURSE_RATING_BRONZE, self.GAME_COURSE_RATING_SILVER: self.COURSE_RATING_SILVER, self.GAME_COURSE_RATING_GOLD: self.COURSE_RATING_GOLD, - }[course.child_value('rating')] + }[course.child_value("rating")] scores = [0] * 5 for music in course.children: - if music.name != 'music': + if music.name != "music": continue - index = int(music.attribute('index')) - scores[index] = music.child_value('score') + index = int(music.attribute("index")) + scores[index] = music.child_value("score") # Save course itself self.save_course(userid, courseid, rating, scores) # Save the last course ID - last.replace_int('last_course_id', courseid) + last.replace_int("last_course_id", courseid) # If this was a league save, grab and save that info too - league = player.child('league') + league = player.child("league") if league is not None: - leagueid = league.child_value('league_id') - newprofile.replace_bool('league_is_checked', league.child_value('is_checked')) - newprofile.replace_bool('league_is_first_play', league.child_value('is_first_play')) + leagueid = league.child_value("league_id") + newprofile.replace_bool( + "league_is_checked", league.child_value("is_checked") + ) + newprofile.replace_bool( + "league_is_first_play", league.child_value("is_first_play") + ) # Extract scores score = [0] * 3 clear = [0] * 3 for music in league.children: - if music.name != 'music': + if music.name != "music": continue - index = int(music.attribute('index')) - scorenode = music.child('score') - clear[index] = int(scorenode.attribute('clear')) + index = int(music.attribute("index")) + scorenode = music.child("score") + clear[index] = int(scorenode.attribute("clear")) score[index] = scorenode.value # Update score if it is higher @@ -1319,107 +1530,125 @@ class JubeatProp( self.version, userid, leagueid, - 'league', + "league", ) if oldleague is None: oldleague = ValidatedDict() - oldscore = oldleague.get_int_array('score', 3) + oldscore = oldleague.get_int_array("score", 3) if sum(oldscore) < sum(score): self.data.local.user.put_achievement( self.game, self.version, userid, leagueid, - 'league', - {'score': score, 'clear': clear}, + "league", + {"score": score, "clear": clear}, ) # Save back last information gleaned from results - newprofile.replace_dict('last', last) + newprofile.replace_dict("last", last) # Keep track of play statistics self.update_play_statistics(userid) return newprofile - def format_scores(self, userid: UserID, profile: Profile, scores: List[Score]) -> Node: + def format_scores( + self, userid: UserID, profile: Profile, scores: List[Score] + ) -> Node: - root = Node.void('gametop') - datanode = Node.void('data') + root = Node.void("gametop") + datanode = Node.void("data") root.add_child(datanode) - player = Node.void('player') + player = Node.void("player") datanode.add_child(player) - player.add_child(Node.s32('jid', profile.extid)) - playdata = Node.void('mdata_list') + player.add_child(Node.s32("jid", profile.extid)) + playdata = Node.void("mdata_list") player.add_child(playdata) music = ValidatedDict() for score in scores: # Ignore festo-and-above chart types. - if score.chart not in {self.CHART_TYPE_BASIC, self.CHART_TYPE_ADVANCED, self.CHART_TYPE_EXTREME}: + if score.chart not in { + self.CHART_TYPE_BASIC, + self.CHART_TYPE_ADVANCED, + self.CHART_TYPE_EXTREME, + }: continue data = music.get_dict(str(score.id)) - play_cnt = data.get_int_array('play_cnt', 3) - clear_cnt = data.get_int_array('clear_cnt', 3) - clear_flags = data.get_int_array('clear_flags', 3) - fc_cnt = data.get_int_array('fc_cnt', 3) - ex_cnt = data.get_int_array('ex_cnt', 3) - points = data.get_int_array('points', 3) + play_cnt = data.get_int_array("play_cnt", 3) + clear_cnt = data.get_int_array("clear_cnt", 3) + clear_flags = data.get_int_array("clear_flags", 3) + fc_cnt = data.get_int_array("fc_cnt", 3) + ex_cnt = data.get_int_array("ex_cnt", 3) + points = data.get_int_array("points", 3) # Replace data for this chart type play_cnt[score.chart] = score.plays - clear_cnt[score.chart] = score.data.get_int('clear_count') - fc_cnt[score.chart] = score.data.get_int('full_combo_count') - ex_cnt[score.chart] = score.data.get_int('excellent_count') + clear_cnt[score.chart] = score.data.get_int("clear_count") + fc_cnt[score.chart] = score.data.get_int("full_combo_count") + ex_cnt[score.chart] = score.data.get_int("excellent_count") points[score.chart] = score.points # Format the clear flags clear_flags[score.chart] = self.GAME_FLAG_BIT_PLAYED - if score.data.get_int('clear_count') > 0: + if score.data.get_int("clear_count") > 0: clear_flags[score.chart] |= self.GAME_FLAG_BIT_CLEARED - if score.data.get_int('full_combo_count') > 0: + if score.data.get_int("full_combo_count") > 0: clear_flags[score.chart] |= self.GAME_FLAG_BIT_FULL_COMBO - if score.data.get_int('excellent_count') > 0: + if score.data.get_int("excellent_count") > 0: clear_flags[score.chart] |= self.GAME_FLAG_BIT_EXCELLENT # Save chart data back - data.replace_int_array('play_cnt', 3, play_cnt) - data.replace_int_array('clear_cnt', 3, clear_cnt) - data.replace_int_array('clear_flags', 3, clear_flags) - data.replace_int_array('fc_cnt', 3, fc_cnt) - data.replace_int_array('ex_cnt', 3, ex_cnt) - data.replace_int_array('points', 3, points) + data.replace_int_array("play_cnt", 3, play_cnt) + data.replace_int_array("clear_cnt", 3, clear_cnt) + data.replace_int_array("clear_flags", 3, clear_flags) + data.replace_int_array("fc_cnt", 3, fc_cnt) + data.replace_int_array("ex_cnt", 3, ex_cnt) + data.replace_int_array("points", 3, points) # Update the ghost (untyped) - ghost = data.get('ghost', [None, None, None]) - ghost[score.chart] = score.data.get('ghost') - data['ghost'] = ghost + ghost = data.get("ghost", [None, None, None]) + ghost[score.chart] = score.data.get("ghost") + data["ghost"] = ghost # Save it back music.replace_dict(str(score.id), data) for scoreid in music: scoredata = music[scoreid] - musicdata = Node.void('musicdata') + musicdata = Node.void("musicdata") playdata.add_child(musicdata) - musicdata.set_attribute('music_id', scoreid) - musicdata.add_child(Node.s32_array('play_cnt', scoredata.get_int_array('play_cnt', 3))) - musicdata.add_child(Node.s32_array('clear_cnt', scoredata.get_int_array('clear_cnt', 3))) - musicdata.add_child(Node.s32_array('fc_cnt', scoredata.get_int_array('fc_cnt', 3))) - musicdata.add_child(Node.s32_array('ex_cnt', scoredata.get_int_array('ex_cnt', 3))) - musicdata.add_child(Node.s32_array('score', scoredata.get_int_array('points', 3))) - musicdata.add_child(Node.s8_array('clear', scoredata.get_int_array('clear_flags', 3))) + musicdata.set_attribute("music_id", scoreid) + musicdata.add_child( + Node.s32_array("play_cnt", scoredata.get_int_array("play_cnt", 3)) + ) + musicdata.add_child( + Node.s32_array("clear_cnt", scoredata.get_int_array("clear_cnt", 3)) + ) + musicdata.add_child( + Node.s32_array("fc_cnt", scoredata.get_int_array("fc_cnt", 3)) + ) + musicdata.add_child( + Node.s32_array("ex_cnt", scoredata.get_int_array("ex_cnt", 3)) + ) + musicdata.add_child( + Node.s32_array("score", scoredata.get_int_array("points", 3)) + ) + musicdata.add_child( + Node.s8_array("clear", scoredata.get_int_array("clear_flags", 3)) + ) - ghosts = scoredata.get('ghost', [None, None, None]) + ghosts = scoredata.get("ghost", [None, None, None]) for i in range(len(ghosts)): ghost = ghosts[i] if ghost is None: continue - bar = Node.u8_array('bar', ghost) + bar = Node.u8_array("bar", ghost) musicdata.add_child(bar) - bar.set_attribute('seq', str(i)) + bar.set_attribute("seq", str(i)) return root diff --git a/bemani/backend/jubeat/qubell.py b/bemani/backend/jubeat/qubell.py index c7d2eec..451e738 100644 --- a/bemani/backend/jubeat/qubell.py +++ b/bemani/backend/jubeat/qubell.py @@ -30,7 +30,7 @@ class JubeatQubell( JubeatBase, ): - name: str = 'Jubeat Qubell' + name: str = "Jubeat Qubell" version: int = VersionConstants.JUBEAT_QUBELL JBOX_EMBLEM_NORMAL: Final[int] = 1 @@ -40,16 +40,16 @@ class JubeatQubell( EVENTS: Dict[int, Dict[str, bool]] = { 5: { - 'enabled': False, + "enabled": False, }, 6: { - 'enabled': False, + "enabled": False, }, 15: { - 'enabled': False, + "enabled": False, }, 19: { - 'enabled': False, + "enabled": False, }, } @@ -57,52 +57,63 @@ class JubeatQubell( return JubeatProp(self.data, self.config, self.model) @classmethod - def run_scheduled_work(cls, data: Data, config: Dict[str, Any]) -> List[Tuple[str, Dict[str, Any]]]: + def run_scheduled_work( + cls, data: Data, config: Dict[str, Any] + ) -> List[Tuple[str, Dict[str, Any]]]: """ Insert daily FC challenges into the DB. """ events = [] - if data.local.network.should_schedule(cls.game, cls.version, 'fc_challenge', 'daily'): + if data.local.network.should_schedule( + cls.game, cls.version, "fc_challenge", "daily" + ): # Generate a new list of two FC challenge songs. - start_time, end_time = data.local.network.get_schedule_duration('daily') - all_songs = set(song.id for song in data.local.music.get_all_songs(cls.game, cls.version)) + start_time, end_time = data.local.network.get_schedule_duration("daily") + all_songs = set( + song.id + for song in data.local.music.get_all_songs(cls.game, cls.version) + ) if len(all_songs) >= 2: daily_songs = random.sample(all_songs, 2) data.local.game.put_time_sensitive_settings( cls.game, cls.version, - 'fc_challenge', + "fc_challenge", { - 'start_time': start_time, - 'end_time': end_time, - 'today': daily_songs[0], - 'whim': daily_songs[1], + "start_time": start_time, + "end_time": end_time, + "today": daily_songs[0], + "whim": daily_songs[1], }, ) - events.append(( - 'jubeat_fc_challenge_charts', - { - 'version': cls.version, - 'today': daily_songs[0], - 'whim': daily_songs[1], - }, - )) + events.append( + ( + "jubeat_fc_challenge_charts", + { + "version": cls.version, + "today": daily_songs[0], + "whim": daily_songs[1], + }, + ) + ) # Mark that we did some actual work here. - data.local.network.mark_scheduled(cls.game, cls.version, 'fc_challenge', 'daily') + data.local.network.mark_scheduled( + cls.game, cls.version, "fc_challenge", "daily" + ) return events def __get_global_info(self) -> Node: - info = Node.void('info') + info = Node.void("info") # Event info. Valid event IDs are 5, 6, 15, 19 - event_info = Node.void('event_info') + event_info = Node.void("event_info") info.add_child(event_info) for event in self.EVENTS: - evt = Node.void('event') + evt = Node.void("event") event_info.add_child(evt) - evt.set_attribute('type', str(event)) - evt.add_child(Node.u8('state', 1 if self.EVENTS[event]['enabled'] else 0)) + evt.set_attribute("type", str(event)) + evt.add_child(Node.u8("state", 1 if self.EVENTS[event]["enabled"] else 0)) # Each of the following two sections should have zero or more child nodes (no # particular name) which look like the following: @@ -112,127 +123,305 @@ class JubeatQubell( # end time? # # Share music? - share_music = Node.void('share_music') + share_music = Node.void("share_music") info.add_child(share_music) # Bonus music? - bonus_music = Node.void('bonus_music') + bonus_music = Node.void("bonus_music") info.add_child(bonus_music) # Some sort of music DB whitelist - info.add_child(Node.s32_array( - 'white_music_list', - [ - -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, -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, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - ], - )) + info.add_child( + Node.s32_array( + "white_music_list", + [ + -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, + -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, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], + ) + ) - info.add_child(Node.s32_array( - 'white_marker_list', - [ - -1, -1, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - ], - )) + info.add_child( + Node.s32_array( + "white_marker_list", + [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], + ) + ) - info.add_child(Node.s32_array( - 'white_theme_list', - [ - -1, -1, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - ], - )) + info.add_child( + Node.s32_array( + "white_theme_list", + [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], + ) + ) - info.add_child(Node.s32_array( - 'open_music_list', - [ - -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, -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, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - ], - )) + info.add_child( + Node.s32_array( + "open_music_list", + [ + -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, + -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, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], + ) + ) - info.add_child(Node.s32_array( - 'shareable_music_list', - [ - -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, -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, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - ], - )) + info.add_child( + Node.s32_array( + "shareable_music_list", + [ + -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, + -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, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], + ) + ) - jbox = Node.void('jbox') + jbox = Node.void("jbox") info.add_child(jbox) - jbox.add_child(Node.s32('point', 0)) - emblem = Node.void('emblem') + jbox.add_child(Node.s32("point", 0)) + emblem = Node.void("emblem") jbox.add_child(emblem) - normal = Node.void('normal') + normal = Node.void("normal") emblem.add_child(normal) - premium = Node.void('premium') + premium = Node.void("premium") emblem.add_child(premium) - normal.add_child(Node.s16('index', 2)) - premium.add_child(Node.s16('index', 1)) + normal.add_child(Node.s16("index", 2)) + premium.add_child(Node.s16("index", 1)) - born = Node.void('born') + born = Node.void("born") info.add_child(born) - born.add_child(Node.s8('status', 0)) - born.add_child(Node.s16('year', 0)) + born.add_child(Node.s8("status", 0)) + born.add_child(Node.s16("year", 0)) - digdig = Node.void('digdig') + digdig = Node.void("digdig") info.add_child(digdig) - stage_list = Node.void('stage_list') + stage_list = Node.void("stage_list") digdig.add_child(stage_list) # Stage numbers are between 1 and 13 inclusive. for i in range(1, 14): - stage = Node.void('stage') + stage = Node.void("stage") stage_list.add_child(stage) - stage.set_attribute('number', str(i)) - stage.add_child(Node.u8('state', 0x1)) + stage.set_attribute("number", str(i)) + stage.add_child(Node.u8("state", 0x1)) # Collection list values should look like: # @@ -240,14 +429,14 @@ class JubeatQubell( # start time? # end time? # - collection = Node.void('collection') + collection = Node.void("collection") info.add_child(collection) - collection.add_child(Node.void('rating_s')) + collection.add_child(Node.void("rating_s")) # Additional digdig nodes that aren't the main event - generic_dig = Node.void('generic_dig') + generic_dig = Node.void("generic_dig") info.add_child(generic_dig) - map_list = Node.void('map_list') + map_list = Node.void("map_list") generic_dig.add_child(map_list) # DigDig nodes here have the following format: # @@ -350,122 +539,124 @@ class JubeatQubell( # # - expert_option = Node.void('expert_option') + expert_option = Node.void("expert_option") info.add_child(expert_option) - expert_option.add_child(Node.bool('is_available', True)) + expert_option.add_child(Node.bool("is_available", True)) - tsumtsum = Node.void('tsumtsum') + tsumtsum = Node.void("tsumtsum") info.add_child(tsumtsum) - tsumtsum.add_child(Node.bool('is_available', True)) + tsumtsum.add_child(Node.bool("is_available", True)) - nagatanien = Node.void('nagatanien') + nagatanien = Node.void("nagatanien") info.add_child(nagatanien) - nagatanien.add_child(Node.bool('is_available', True)) + nagatanien.add_child(Node.bool("is_available", True)) - all_music_matching = Node.void('all_music_matching') + all_music_matching = Node.void("all_music_matching") info.add_child(all_music_matching) - all_music_matching.add_child(Node.bool('is_available', True)) + all_music_matching.add_child(Node.bool("is_available", True)) - question_list = Node.void('question_list') + question_list = Node.void("question_list") info.add_child(question_list) return info def handle_shopinfo_regist_request(self, request: Node) -> Node: # Update the name of this cab for admin purposes - self.update_machine_name(request.child_value('shop/name')) + self.update_machine_name(request.child_value("shop/name")) - shopinfo = Node.void('shopinfo') + shopinfo = Node.void("shopinfo") - data = Node.void('data') + data = Node.void("data") shopinfo.add_child(data) - data.add_child(Node.u32('cabid', 1)) - data.add_child(Node.string('locationid', 'nowhere')) - data.add_child(Node.u8('tax_phase', 1)) + data.add_child(Node.u32("cabid", 1)) + data.add_child(Node.string("locationid", "nowhere")) + data.add_child(Node.u8("tax_phase", 1)) - facility = Node.void('facility') + facility = Node.void("facility") data.add_child(facility) - facility.add_child(Node.u32('exist', 1)) + facility.add_child(Node.u32("exist", 1)) data.add_child(self.__get_global_info()) return shopinfo def handle_recommend_get_recommend_request(self, request: Node) -> Node: - recommend = Node.void('recommend') - data = Node.void('data') + recommend = Node.void("recommend") + data = Node.void("data") recommend.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - music_list = Node.void('music_list') + music_list = Node.void("music_list") player.add_child(music_list) # TODO: Might be a way to figure out who plays what song and then offer # recommendations based on that. There should be 12 songs returned here. recommended_songs: List[Song] = [] for i, song in enumerate(recommended_songs): - music = Node.void('music') + music = Node.void("music") music_list.add_child(music) - music.set_attribute('order', str(i)) - music.add_child(Node.s32('music_id', song.id)) - music.add_child(Node.s8('seq', song.chart)) + music.set_attribute("order", str(i)) + music.add_child(Node.s32("music_id", song.id)) + music.add_child(Node.s8("seq", song.chart)) return recommend def handle_gametop_regist_request(self, request: Node) -> Node: - data = request.child('data') - player = data.child('player') - refid = player.child_value('refid') - name = player.child_value('name') + data = request.child("data") + player = data.child("player") + refid = player.child_value("refid") + name = player.child_value("name") root = self.new_profile_by_refid(refid, name) return root def handle_gametop_get_pdata_request(self, request: Node) -> Node: - data = request.child('data') - player = data.child('player') - refid = player.child_value('refid') + data = request.child("data") + player = data.child("player") + refid = player.child_value("refid") root = self.get_profile_by_refid(refid) if root is None: - root = Node.void('gametop') - root.set_attribute('status', str(Status.NO_PROFILE)) + root = Node.void("gametop") + root.set_attribute("status", str(Status.NO_PROFILE)) return root def handle_gametop_get_mdata_request(self, request: Node) -> Node: - data = request.child('data') - player = data.child('player') - extid = player.child_value('jid') - mdata_ver = player.child_value('mdata_ver') # Game requests mdata 3 times per profile for some reason + data = request.child("data") + player = data.child("player") + extid = player.child_value("jid") + mdata_ver = player.child_value( + "mdata_ver" + ) # Game requests mdata 3 times per profile for some reason if mdata_ver != 1: - root = Node.void('gametop') - datanode = Node.void('data') + root = Node.void("gametop") + datanode = Node.void("data") root.add_child(datanode) - player = Node.void('player') + player = Node.void("player") datanode.add_child(player) - player.add_child(Node.s32('jid', extid)) - playdata = Node.void('mdata_list') + player.add_child(Node.s32("jid", extid)) + playdata = Node.void("mdata_list") player.add_child(playdata) return root root = self.get_scores_by_extid(extid) if root is None: - root = Node.void('gametop') - root.set_attribute('status', str(Status.NO_PROFILE)) + root = Node.void("gametop") + root.set_attribute("status", str(Status.NO_PROFILE)) return root def handle_gametop_get_info_request(self, request: Node) -> Node: - root = Node.void('gametop') - data = Node.void('data') + root = Node.void("gametop") + data = Node.void("data") root.add_child(data) data.add_child(self.__get_global_info()) return root def handle_gameend_final_request(self, request: Node) -> Node: - data = request.child('data') - player = data.child('player') + data = request.child("data") + player = data.child("player") if player is not None: - refid = player.child_value('refid') + refid = player.child_value("refid") else: refid = None @@ -478,151 +669,204 @@ class JubeatQubell( profile = self.get_profile(userid) # Grab unlock progress - item = player.child('item') + item = player.child("item") if item is not None: - profile.replace_int_array('emblem_list', 96, item.child_value('emblem_list')) + profile.replace_int_array( + "emblem_list", 96, item.child_value("emblem_list") + ) # jbox stuff - jbox = player.child('jbox') - jboxdict = profile.get_dict('jbox') + jbox = player.child("jbox") + jboxdict = profile.get_dict("jbox") if jbox is not None: - jboxdict.replace_int('point', jbox.child_value('point')) - emblemtype = jbox.child_value('emblem/type') - index = jbox.child_value('emblem/index') + jboxdict.replace_int("point", jbox.child_value("point")) + emblemtype = jbox.child_value("emblem/type") + index = jbox.child_value("emblem/index") if emblemtype == self.JBOX_EMBLEM_NORMAL: - jboxdict.replace_int('normal_index', index) + jboxdict.replace_int("normal_index", index) elif emblemtype == self.JBOX_EMBLEM_PREMIUM: - jboxdict.replace_int('premium_index', index) - profile.replace_dict('jbox', jboxdict) + jboxdict.replace_int("premium_index", index) + profile.replace_dict("jbox", jboxdict) # Born stuff - born = player.child('born') + born = player.child("born") if born is not None: - profile.replace_int('born_status', born.child_value('status')) - profile.replace_int('born_year', born.child_value('year')) + profile.replace_int("born_status", born.child_value("status")) + profile.replace_int("born_year", born.child_value("year")) else: profile = None if userid is not None and profile is not None: self.put_profile(userid, profile) - return Node.void('gameend') + return Node.void("gameend") def format_profile(self, userid: UserID, profile: Profile) -> Node: - root = Node.void('gametop') - data = Node.void('data') + root = Node.void("gametop") + data = Node.void("data") root.add_child(data) # Jubeat Prop appears to allow full event overrides per-player data.add_child(self.__get_global_info()) - player = Node.void('player') + player = Node.void("player") data.add_child(player) # Some server node - server = Node.void('server') + server = Node.void("server") player.add_child(server) # Basic profile info - player.add_child(Node.string('name', profile.get_str('name', 'なし'))) - player.add_child(Node.s32('jid', profile.extid)) + player.add_child(Node.string("name", profile.get_str("name", "なし"))) + player.add_child(Node.s32("jid", profile.extid)) # Miscelaneous crap - player.add_child(Node.s32('session_id', 1)) - player.add_child(Node.u64('event_flag', 0)) + player.add_child(Node.s32("session_id", 1)) + player.add_child(Node.u64("event_flag", 0)) # Player info and statistics - info = Node.void('info') + info = Node.void("info") player.add_child(info) - info.add_child(Node.s16('jubility', profile.get_int('jubility'))) - info.add_child(Node.s16('jubility_yday', profile.get_int('jubility_yday'))) - info.add_child(Node.s32('tune_cnt', profile.get_int('tune_cnt'))) - info.add_child(Node.s32('save_cnt', profile.get_int('save_cnt'))) - info.add_child(Node.s32('saved_cnt', profile.get_int('saved_cnt'))) - info.add_child(Node.s32('fc_cnt', profile.get_int('fc_cnt'))) - info.add_child(Node.s32('ex_cnt', profile.get_int('ex_cnt'))) - info.add_child(Node.s32('clear_cnt', profile.get_int('clear_cnt'))) - info.add_child(Node.s32('match_cnt', profile.get_int('match_cnt'))) - info.add_child(Node.s32('beat_cnt', profile.get_int('beat_cnt'))) - info.add_child(Node.s32('mynews_cnt', profile.get_int('mynews_cnt'))) - info.add_child(Node.s32('bonus_tune_points', profile.get_int('bonus_tune_points'))) - info.add_child(Node.bool('is_bonus_tune_played', profile.get_bool('is_bonus_tune_played'))) + info.add_child(Node.s16("jubility", profile.get_int("jubility"))) + info.add_child(Node.s16("jubility_yday", profile.get_int("jubility_yday"))) + info.add_child(Node.s32("tune_cnt", profile.get_int("tune_cnt"))) + info.add_child(Node.s32("save_cnt", profile.get_int("save_cnt"))) + info.add_child(Node.s32("saved_cnt", profile.get_int("saved_cnt"))) + info.add_child(Node.s32("fc_cnt", profile.get_int("fc_cnt"))) + info.add_child(Node.s32("ex_cnt", profile.get_int("ex_cnt"))) + info.add_child(Node.s32("clear_cnt", profile.get_int("clear_cnt"))) + info.add_child(Node.s32("match_cnt", profile.get_int("match_cnt"))) + info.add_child(Node.s32("beat_cnt", profile.get_int("beat_cnt"))) + info.add_child(Node.s32("mynews_cnt", profile.get_int("mynews_cnt"))) + info.add_child( + Node.s32("bonus_tune_points", profile.get_int("bonus_tune_points")) + ) + info.add_child( + Node.bool("is_bonus_tune_played", profile.get_bool("is_bonus_tune_played")) + ) # Looks to be set to true when there's an old profile, stops tutorial from # happening on first load. - info.add_child(Node.bool('inherit', profile.get_bool('has_old_version') and not profile.get_bool('saved'))) + info.add_child( + Node.bool( + "inherit", + profile.get_bool("has_old_version") and not profile.get_bool("saved"), + ) + ) # Not saved, but loaded - info.add_child(Node.s32('mtg_entry_cnt', 123)) - info.add_child(Node.s32('mtg_hold_cnt', 456)) - info.add_child(Node.u8('mtg_result', 10)) + info.add_child(Node.s32("mtg_entry_cnt", 123)) + info.add_child(Node.s32("mtg_hold_cnt", 456)) + info.add_child(Node.u8("mtg_result", 10)) # Last played data, for showing cursor and such - lastdict = profile.get_dict('last') - last = Node.void('last') + lastdict = profile.get_dict("last") + last = Node.void("last") player.add_child(last) - last.add_child(Node.s64('play_time', lastdict.get_int('play_time'))) - last.add_child(Node.string('shopname', lastdict.get_str('shopname'))) - last.add_child(Node.string('areaname', lastdict.get_str('areaname'))) - last.add_child(Node.s32('music_id', lastdict.get_int('music_id'))) - last.add_child(Node.s8('seq_id', lastdict.get_int('seq_id'))) - last.add_child(Node.s8('sort', lastdict.get_int('sort'))) - last.add_child(Node.s8('category', lastdict.get_int('category'))) - last.add_child(Node.s8('expert_option', lastdict.get_int('expert_option'))) - last.add_child(Node.s32('dig_select', lastdict.get_int('dig_select'))) + last.add_child(Node.s64("play_time", lastdict.get_int("play_time"))) + last.add_child(Node.string("shopname", lastdict.get_str("shopname"))) + last.add_child(Node.string("areaname", lastdict.get_str("areaname"))) + last.add_child(Node.s32("music_id", lastdict.get_int("music_id"))) + last.add_child(Node.s8("seq_id", lastdict.get_int("seq_id"))) + last.add_child(Node.s8("sort", lastdict.get_int("sort"))) + last.add_child(Node.s8("category", lastdict.get_int("category"))) + last.add_child(Node.s8("expert_option", lastdict.get_int("expert_option"))) + last.add_child(Node.s32("dig_select", lastdict.get_int("dig_select"))) - settings = Node.void('settings') + settings = Node.void("settings") last.add_child(settings) - settings.add_child(Node.s8('marker', lastdict.get_int('marker'))) - settings.add_child(Node.s8('theme', lastdict.get_int('theme'))) - settings.add_child(Node.s16('title', lastdict.get_int('title'))) - settings.add_child(Node.s16('parts', lastdict.get_int('parts'))) - settings.add_child(Node.s8('rank_sort', lastdict.get_int('rank_sort'))) - settings.add_child(Node.s8('combo_disp', lastdict.get_int('combo_disp'))) - settings.add_child(Node.s16_array('emblem', lastdict.get_int_array('emblem', 5))) - settings.add_child(Node.s8('matching', lastdict.get_int('matching'))) - settings.add_child(Node.s8('hazard', lastdict.get_int('hazard'))) - settings.add_child(Node.s8('hard', lastdict.get_int('hard'))) + settings.add_child(Node.s8("marker", lastdict.get_int("marker"))) + settings.add_child(Node.s8("theme", lastdict.get_int("theme"))) + settings.add_child(Node.s16("title", lastdict.get_int("title"))) + settings.add_child(Node.s16("parts", lastdict.get_int("parts"))) + settings.add_child(Node.s8("rank_sort", lastdict.get_int("rank_sort"))) + settings.add_child(Node.s8("combo_disp", lastdict.get_int("combo_disp"))) + settings.add_child( + Node.s16_array("emblem", lastdict.get_int_array("emblem", 5)) + ) + settings.add_child(Node.s8("matching", lastdict.get_int("matching"))) + settings.add_child(Node.s8("hazard", lastdict.get_int("hazard"))) + settings.add_child(Node.s8("hard", lastdict.get_int("hard"))) # Secret unlocks - item = Node.void('item') + item = Node.void("item") player.add_child(item) - item.add_child(Node.s32_array('music_list', profile.get_int_array('music_list', 64, [-1] * 64))) - item.add_child(Node.s32_array('secret_list', profile.get_int_array('secret_list', 64, [-1] * 64))) - item.add_child(Node.s32_array('theme_list', profile.get_int_array('theme_list', 16, [-1] * 16))) - item.add_child(Node.s32_array('marker_list', profile.get_int_array('marker_list', 16, [-1] * 16))) - item.add_child(Node.s32_array('title_list', profile.get_int_array('title_list', 160, [-1] * 160))) - item.add_child(Node.s32_array('parts_list', profile.get_int_array('parts_list', 160, [-1] * 160))) - item.add_child(Node.s32_array('emblem_list', profile.get_int_array('emblem_list', 96, [-1] * 96))) + item.add_child( + Node.s32_array( + "music_list", profile.get_int_array("music_list", 64, [-1] * 64) + ) + ) + item.add_child( + Node.s32_array( + "secret_list", profile.get_int_array("secret_list", 64, [-1] * 64) + ) + ) + item.add_child( + Node.s32_array( + "theme_list", profile.get_int_array("theme_list", 16, [-1] * 16) + ) + ) + item.add_child( + Node.s32_array( + "marker_list", profile.get_int_array("marker_list", 16, [-1] * 16) + ) + ) + item.add_child( + Node.s32_array( + "title_list", profile.get_int_array("title_list", 160, [-1] * 160) + ) + ) + item.add_child( + Node.s32_array( + "parts_list", profile.get_int_array("parts_list", 160, [-1] * 160) + ) + ) + item.add_child( + Node.s32_array( + "emblem_list", profile.get_int_array("emblem_list", 96, [-1] * 96) + ) + ) - new = Node.void('new') + new = Node.void("new") item.add_child(new) - new.add_child(Node.s32_array('secret_list', profile.get_int_array('secret_list_new', 64, [-1] * 64))) - new.add_child(Node.s32_array('theme_list', profile.get_int_array('theme_list_new', 16, [-1] * 16))) - new.add_child(Node.s32_array('marker_list', profile.get_int_array('marker_list_new', 16, [-1] * 16))) + new.add_child( + Node.s32_array( + "secret_list", profile.get_int_array("secret_list_new", 64, [-1] * 64) + ) + ) + new.add_child( + Node.s32_array( + "theme_list", profile.get_int_array("theme_list_new", 16, [-1] * 16) + ) + ) + new.add_child( + Node.s32_array( + "marker_list", profile.get_int_array("marker_list_new", 16, [-1] * 16) + ) + ) # Add rivals to profile. - rivallist = Node.void('rivallist') + rivallist = Node.void("rivallist") player.add_child(rivallist) links = self.data.local.user.get_links(self.game, self.version, userid) rivalcount = 0 for link in links: - if link.type != 'rival': + if link.type != "rival": continue rprofile = self.get_profile(link.other_userid) if rprofile is None: continue - rival = Node.void('rival') + rival = Node.void("rival") rivallist.add_child(rival) - rival.add_child(Node.s32('jid', rprofile.extid)) - rival.add_child(Node.string('name', rprofile.get_str('name'))) + rival.add_child(Node.s32("jid", rprofile.extid)) + rival.add_child(Node.string("name", rprofile.get_str("name"))) # This looks like a carry-over from prop's career and isn't displayed. - career = Node.void('career') + career = Node.void("career") rival.add_child(career) - career.add_child(Node.s16('level', 1)) + career.add_child(Node.s16("level", 1)) # Lazy way of keeping track of rivals, since we can only have 3 # or the game with throw up. @@ -630,76 +874,94 @@ class JubeatQubell( if rivalcount >= 3: break - rivallist.set_attribute('count', str(rivalcount)) + rivallist.set_attribute("count", str(rivalcount)) - lab_edit_seq = Node.void('lab_edit_seq') + lab_edit_seq = Node.void("lab_edit_seq") player.add_child(lab_edit_seq) - lab_edit_seq.set_attribute('count', '0') + lab_edit_seq.set_attribute("count", "0") # Full combo challenge - entry = self.data.local.game.get_time_sensitive_settings(self.game, self.version, 'fc_challenge') + entry = self.data.local.game.get_time_sensitive_settings( + self.game, self.version, "fc_challenge" + ) if entry is None: entry = ValidatedDict() # Figure out if we've played these songs - start_time, end_time = self.data.local.network.get_schedule_duration('daily') - today_attempts = self.data.local.music.get_all_attempts(self.game, self.music_version, userid, entry.get_int('today', -1), timelimit=start_time) - whim_attempts = self.data.local.music.get_all_attempts(self.game, self.music_version, userid, entry.get_int('whim', -1), timelimit=start_time) + start_time, end_time = self.data.local.network.get_schedule_duration("daily") + today_attempts = self.data.local.music.get_all_attempts( + self.game, + self.music_version, + userid, + entry.get_int("today", -1), + timelimit=start_time, + ) + whim_attempts = self.data.local.music.get_all_attempts( + self.game, + self.music_version, + userid, + entry.get_int("whim", -1), + timelimit=start_time, + ) - fc_challenge = Node.void('fc_challenge') + fc_challenge = Node.void("fc_challenge") player.add_child(fc_challenge) - today = Node.void('today') + today = Node.void("today") fc_challenge.add_child(today) - today.add_child(Node.s32('music_id', entry.get_int('today', -1))) - today.add_child(Node.u8('state', 0x40 if len(today_attempts) > 0 else 0x0)) - whim = Node.void('whim') + today.add_child(Node.s32("music_id", entry.get_int("today", -1))) + today.add_child(Node.u8("state", 0x40 if len(today_attempts) > 0 else 0x0)) + whim = Node.void("whim") fc_challenge.add_child(whim) - whim.add_child(Node.s32('music_id', entry.get_int('whim', -1))) - whim.add_child(Node.u8('state', 0x40 if len(whim_attempts) > 0 else 0x0)) + whim.add_child(Node.s32("music_id", entry.get_int("whim", -1))) + whim.add_child(Node.u8("state", 0x40 if len(whim_attempts) > 0 else 0x0)) # No news, ever. - news = Node.void('news') + news = Node.void("news") player.add_child(news) - news.add_child(Node.s16('checked', 0)) - news.add_child(Node.u32('checked_flag', 0)) + news.add_child(Node.s16("checked", 0)) + news.add_child(Node.u32("checked_flag", 0)) # Sane defaults for unknown/who cares nodes - history = Node.void('history') + history = Node.void("history") player.add_child(history) - history.set_attribute('count', '0') - free_first_play = Node.void('free_first_play') + history.set_attribute("count", "0") + free_first_play = Node.void("free_first_play") player.add_child(free_first_play) - free_first_play.add_child(Node.bool('is_available', False)) - navi = Node.void('navi') + free_first_play.add_child(Node.bool("is_available", False)) + navi = Node.void("navi") player.add_child(navi) - navi.add_child(Node.u64('flag', profile.get_int('navi_flag'))) + navi.add_child(Node.u64("flag", profile.get_int("navi_flag"))) # Player status for events - event_info = Node.void('event_info') + event_info = Node.void("event_info") player.add_child(event_info) - achievements = self.data.local.user.get_achievements(self.game, self.version, userid) + achievements = self.data.local.user.get_achievements( + self.game, self.version, userid + ) for achievement in achievements: - if achievement.type == 'event': + if achievement.type == "event": # There are two significant bits here, 0x1 and 0x2, I think the first # one is whether the event is started, second is if its finished? - event = Node.void('event') + event = Node.void("event") event_info.add_child(event) - event.set_attribute('type', str(achievement.id)) + event.set_attribute("type", str(achievement.id)) state = 0x0 - state = state + 0x2 if achievement.data.get_bool('is_completed') else 0x0 - event.add_child(Node.u8('state', state)) + state = ( + state + 0x2 if achievement.data.get_bool("is_completed") else 0x0 + ) + event.add_child(Node.u8("state", state)) # JBox stuff - jbox = Node.void('jbox') - jboxdict = profile.get_dict('jbox') + jbox = Node.void("jbox") + jboxdict = profile.get_dict("jbox") player.add_child(jbox) - jbox.add_child(Node.s32('point', jboxdict.get_int('point'))) - emblem = Node.void('emblem') + jbox.add_child(Node.s32("point", jboxdict.get_int("point"))) + emblem = Node.void("emblem") jbox.add_child(emblem) - normal = Node.void('normal') + normal = Node.void("normal") emblem.add_child(normal) - premium = Node.void('premium') + premium = Node.void("premium") emblem.add_child(premium) # Calculate a random index for normal and premium to give to player @@ -708,10 +970,10 @@ class JubeatQubell( normalemblems: Set[int] = set() premiumemblems: Set[int] = set() for gameitem in gameitems: - if gameitem.type == 'emblem': - if gameitem.data.get_int('rarity') in {1, 2, 3}: + if gameitem.type == "emblem": + if gameitem.data.get_int("rarity") in {1, 2, 3}: normalemblems.add(gameitem.id) - if gameitem.data.get_int('rarity') in {3, 4, 5}: + if gameitem.data.get_int("rarity") in {3, 4, 5}: premiumemblems.add(gameitem.id) # Default to some emblems in case the catalog is not available. @@ -722,56 +984,91 @@ class JubeatQubell( if premiumemblems: premiumindex = random.sample(premiumemblems, 1)[0] - normal.add_child(Node.s16('index', normalindex)) - premium.add_child(Node.s16('index', premiumindex)) + normal.add_child(Node.s16("index", normalindex)) + premium.add_child(Node.s16("index", premiumindex)) # Digdig stuff - digdig = Node.void('digdig') - digdigdict = profile.get_dict('digdig') - eternaldict = digdigdict.get_dict('eternal') - olddict = digdigdict.get_dict('old') + digdig = Node.void("digdig") + digdigdict = profile.get_dict("digdig") + eternaldict = digdigdict.get_dict("eternal") + olddict = digdigdict.get_dict("old") player.add_child(digdig) - digdig.add_child(Node.u64('flag', digdigdict.get_int('flag'))) + digdig.add_child(Node.u64("flag", digdigdict.get_int("flag"))) # Emerald main stages - main = Node.void('main') + main = Node.void("main") digdig.add_child(main) - stage = Node.void('stage') + stage = Node.void("stage") main.add_child(stage) - stage.set_attribute('number', str(digdigdict.get_int('stage_number', 1))) - stage.add_child(Node.s32('point', digdigdict.get_int('point'))) - stage.add_child(Node.s32_array('param', digdigdict.get_int_array('param', 12, [0] * 12))) + stage.set_attribute("number", str(digdigdict.get_int("stage_number", 1))) + stage.add_child(Node.s32("point", digdigdict.get_int("point"))) + stage.add_child( + Node.s32_array("param", digdigdict.get_int_array("param", 12, [0] * 12)) + ) # Emerald eternal stages - eternal = Node.void('eternal') + eternal = Node.void("eternal") digdig.add_child(eternal) - eternal.add_child(Node.s32('ratio', 1)) - eternal.add_child(Node.s64('used_point', eternaldict.get_int('used_point'))) - eternal.add_child(Node.s64('point', eternaldict.get_int('point'))) - eternal.add_child(Node.s64('excavated_point', eternaldict.get_int('excavated_point'))) - cube = Node.void('cube') + eternal.add_child(Node.s32("ratio", 1)) + eternal.add_child(Node.s64("used_point", eternaldict.get_int("used_point"))) + eternal.add_child(Node.s64("point", eternaldict.get_int("point"))) + eternal.add_child( + Node.s64("excavated_point", eternaldict.get_int("excavated_point")) + ) + cube = Node.void("cube") eternal.add_child(cube) - cube.add_child(Node.s8_array('state', eternaldict.get_int_array('state', 12, [0] * 12))) - item = Node.void('item') + cube.add_child( + Node.s8_array("state", eternaldict.get_int_array("state", 12, [0] * 12)) + ) + item = Node.void("item") cube.add_child(item) - item.add_child(Node.s32_array('kind', eternaldict.get_int_array('item_kind', 12, [0] * 12))) - item.add_child(Node.s32_array('value', eternaldict.get_int_array('item_value', 12, [0] * 12))) - norma = Node.void('norma') + item.add_child( + Node.s32_array("kind", eternaldict.get_int_array("item_kind", 12, [0] * 12)) + ) + item.add_child( + Node.s32_array( + "value", eternaldict.get_int_array("item_value", 12, [0] * 12) + ) + ) + norma = Node.void("norma") cube.add_child(norma) - norma.add_child(Node.s64_array('till_time', [0] * 12)) - norma.add_child(Node.s32_array('kind', eternaldict.get_int_array('norma_kind', 12, [0] * 12))) - norma.add_child(Node.s32_array('value', eternaldict.get_int_array('norma_value', 12, [0] * 12))) - norma.add_child(Node.s32_array('param', eternaldict.get_int_array('norma_param', 12, [0] * 12))) + norma.add_child(Node.s64_array("till_time", [0] * 12)) + norma.add_child( + Node.s32_array( + "kind", eternaldict.get_int_array("norma_kind", 12, [0] * 12) + ) + ) + norma.add_child( + Node.s32_array( + "value", eternaldict.get_int_array("norma_value", 12, [0] * 12) + ) + ) + norma.add_child( + Node.s32_array( + "param", eternaldict.get_int_array("norma_param", 12, [0] * 12) + ) + ) if self.ENABLE_GARNET: # Garnet - old = Node.void('old') + old = Node.void("old") digdig.add_child(old) - old.add_child(Node.s32('need_point', olddict.get_int('need_point'))) - old.add_child(Node.s32('point', olddict.get_int('point'))) - old.add_child(Node.s32_array('excavated_point', olddict.get_int_array('excavated_point', 5, [0] * 5))) - old.add_child(Node.s32_array('excavated', olddict.get_int_array('excavated', 5, [0] * 5))) - old.add_child(Node.s32_array('param', olddict.get_int_array('param', 5, [0] * 5))) + old.add_child(Node.s32("need_point", olddict.get_int("need_point"))) + old.add_child(Node.s32("point", olddict.get_int("point"))) + old.add_child( + Node.s32_array( + "excavated_point", + olddict.get_int_array("excavated_point", 5, [0] * 5), + ) + ) + old.add_child( + Node.s32_array( + "excavated", olddict.get_int_array("excavated", 5, [0] * 5) + ) + ) + old.add_child( + Node.s32_array("param", olddict.get_int_array("param", 5, [0] * 5)) + ) # This should have a bunch of sub-nodes with the following format. Note that only # the first ten nodes are saved even if more are read. Presumably this is the list # of old songs we are allowing the player to unlock? Doesn't matter, we're disabling @@ -779,30 +1076,32 @@ class JubeatQubell( # # id # - old.add_child(Node.void('music_list')) + old.add_child(Node.void("music_list")) # Unlock event, turns on unlock challenge for a particular stage. - unlock = Node.void('unlock') + unlock = Node.void("unlock") player.add_child(unlock) - main = Node.void('main') + main = Node.void("main") unlock.add_child(main) - stage_list = Node.void('stage_list') + stage_list = Node.void("stage_list") main.add_child(stage_list) # Stage numbers are between 1 and 13 inclusive. for i in range(1, 14): - stage_flags = self.data.local.user.get_achievement(self.game, self.version, userid, i, 'stage') + stage_flags = self.data.local.user.get_achievement( + self.game, self.version, userid, i, "stage" + ) if stage_flags is None: stage_flags = ValidatedDict() - stage = Node.void('stage') + stage = Node.void("stage") stage_list.add_child(stage) - stage.set_attribute('number', str(i)) - stage.add_child(Node.u8('state', stage_flags.get_int('state'))) + stage.set_attribute("number", str(i)) + stage.add_child(Node.u8("state", stage_flags.get_int("state"))) # DigDig event for server-controlled cubes (basically anything not Garnet or Emerald) - generic_dig = Node.void('generic_dig') + generic_dig = Node.void("generic_dig") player.add_child(generic_dig) - map_list = Node.void('map_list') + map_list = Node.void("map_list") generic_dig.add_child(map_list) # Map list consists of up to 9 of the following structures: # @@ -822,11 +1121,11 @@ class JubeatQubell( # # New Music stuff - new_music = Node.void('new_music') + new_music = Node.void("new_music") player.add_child(new_music) # Gift list, maybe from other players? - gift_list = Node.void('gift_list') + gift_list = Node.void("gift_list") player.add_child(gift_list) # If we had gifts, they look like this: # @@ -834,199 +1133,243 @@ class JubeatQubell( # # Birthday event? - born = Node.void('born') + born = Node.void("born") player.add_child(born) - born.add_child(Node.s8('status', profile.get_int('born_status'))) - born.add_child(Node.s16('year', profile.get_int('born_year'))) + born.add_child(Node.s8("status", profile.get_int("born_status"))) + born.add_child(Node.s16("year", profile.get_int("born_year"))) # More crap - question_list = Node.void('question_list') + question_list = Node.void("question_list") player.add_child(question_list) return root - def format_scores(self, userid: UserID, profile: Profile, scores: List[Score]) -> Node: + def format_scores( + self, userid: UserID, profile: Profile, scores: List[Score] + ) -> Node: - root = Node.void('gametop') - datanode = Node.void('data') + root = Node.void("gametop") + datanode = Node.void("data") root.add_child(datanode) - player = Node.void('player') + player = Node.void("player") datanode.add_child(player) - player.add_child(Node.s32('jid', profile.extid)) - playdata = Node.void('mdata_list') + player.add_child(Node.s32("jid", profile.extid)) + playdata = Node.void("mdata_list") player.add_child(playdata) music = ValidatedDict() for score in scores: # Ignore festo-and-above chart types. - if score.chart not in {self.CHART_TYPE_BASIC, self.CHART_TYPE_ADVANCED, self.CHART_TYPE_EXTREME}: + if score.chart not in { + self.CHART_TYPE_BASIC, + self.CHART_TYPE_ADVANCED, + self.CHART_TYPE_EXTREME, + }: continue data = music.get_dict(str(score.id)) - play_cnt = data.get_int_array('play_cnt', 3) - clear_cnt = data.get_int_array('clear_cnt', 3) - clear_flags = data.get_int_array('clear_flags', 3) - fc_cnt = data.get_int_array('fc_cnt', 3) - ex_cnt = data.get_int_array('ex_cnt', 3) - points = data.get_int_array('points', 3) + play_cnt = data.get_int_array("play_cnt", 3) + clear_cnt = data.get_int_array("clear_cnt", 3) + clear_flags = data.get_int_array("clear_flags", 3) + fc_cnt = data.get_int_array("fc_cnt", 3) + ex_cnt = data.get_int_array("ex_cnt", 3) + points = data.get_int_array("points", 3) # Replace data for this chart type play_cnt[score.chart] = score.plays - clear_cnt[score.chart] = score.data.get_int('clear_count') - fc_cnt[score.chart] = score.data.get_int('full_combo_count') - ex_cnt[score.chart] = score.data.get_int('excellent_count') + clear_cnt[score.chart] = score.data.get_int("clear_count") + fc_cnt[score.chart] = score.data.get_int("full_combo_count") + ex_cnt[score.chart] = score.data.get_int("excellent_count") points[score.chart] = score.points # Format the clear flags clear_flags[score.chart] = self.GAME_FLAG_BIT_PLAYED - if score.data.get_int('clear_count') > 0: + if score.data.get_int("clear_count") > 0: clear_flags[score.chart] |= self.GAME_FLAG_BIT_CLEARED - if score.data.get_int('full_combo_count') > 0: + if score.data.get_int("full_combo_count") > 0: clear_flags[score.chart] |= self.GAME_FLAG_BIT_FULL_COMBO - if score.data.get_int('excellent_count') > 0: + if score.data.get_int("excellent_count") > 0: clear_flags[score.chart] |= self.GAME_FLAG_BIT_EXCELLENT # Save chart data back - data.replace_int_array('play_cnt', 3, play_cnt) - data.replace_int_array('clear_cnt', 3, clear_cnt) - data.replace_int_array('clear_flags', 3, clear_flags) - data.replace_int_array('fc_cnt', 3, fc_cnt) - data.replace_int_array('ex_cnt', 3, ex_cnt) - data.replace_int_array('points', 3, points) + data.replace_int_array("play_cnt", 3, play_cnt) + data.replace_int_array("clear_cnt", 3, clear_cnt) + data.replace_int_array("clear_flags", 3, clear_flags) + data.replace_int_array("fc_cnt", 3, fc_cnt) + data.replace_int_array("ex_cnt", 3, ex_cnt) + data.replace_int_array("points", 3, points) # Update the ghost (untyped) - ghost = data.get('ghost', [None, None, None]) - ghost[score.chart] = score.data.get('ghost') - data['ghost'] = ghost + ghost = data.get("ghost", [None, None, None]) + ghost[score.chart] = score.data.get("ghost") + data["ghost"] = ghost # Save it back music.replace_dict(str(score.id), data) for scoreid in music: scoredata = music[scoreid] - musicdata = Node.void('musicdata') + musicdata = Node.void("musicdata") playdata.add_child(musicdata) - musicdata.set_attribute('music_id', scoreid) - musicdata.add_child(Node.s32_array('play_cnt', scoredata.get_int_array('play_cnt', 3))) - musicdata.add_child(Node.s32_array('clear_cnt', scoredata.get_int_array('clear_cnt', 3))) - musicdata.add_child(Node.s32_array('fc_cnt', scoredata.get_int_array('fc_cnt', 3))) - musicdata.add_child(Node.s32_array('ex_cnt', scoredata.get_int_array('ex_cnt', 3))) - musicdata.add_child(Node.s32_array('score', scoredata.get_int_array('points', 3))) - musicdata.add_child(Node.s8_array('clear', scoredata.get_int_array('clear_flags', 3))) + musicdata.set_attribute("music_id", scoreid) + musicdata.add_child( + Node.s32_array("play_cnt", scoredata.get_int_array("play_cnt", 3)) + ) + musicdata.add_child( + Node.s32_array("clear_cnt", scoredata.get_int_array("clear_cnt", 3)) + ) + musicdata.add_child( + Node.s32_array("fc_cnt", scoredata.get_int_array("fc_cnt", 3)) + ) + musicdata.add_child( + Node.s32_array("ex_cnt", scoredata.get_int_array("ex_cnt", 3)) + ) + musicdata.add_child( + Node.s32_array("score", scoredata.get_int_array("points", 3)) + ) + musicdata.add_child( + Node.s8_array("clear", scoredata.get_int_array("clear_flags", 3)) + ) - ghosts = scoredata.get('ghost', [None, None, None]) + ghosts = scoredata.get("ghost", [None, None, None]) for i in range(len(ghosts)): ghost = ghosts[i] if ghost is None: continue - bar = Node.u8_array('bar', ghost) + bar = Node.u8_array("bar", ghost) musicdata.add_child(bar) - bar.set_attribute('seq', str(i)) + bar.set_attribute("seq", str(i)) return root - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() - newprofile.replace_bool('saved', True) - data = request.child('data') + newprofile.replace_bool("saved", True) + data = request.child("data") # Grab system information - sysinfo = data.child('info') + sysinfo = data.child("info") # Grab player information - player = data.child('player') + player = data.child("player") # Grab result information - result = data.child('result') + result = data.child("result") # Grab last information. Lots of this will be filled in while grabbing scores - last = newprofile.get_dict('last') + last = newprofile.get_dict("last") if sysinfo is not None: - last.replace_int('play_time', sysinfo.child_value('time_gameend')) - last.replace_str('shopname', sysinfo.child_value('shopname')) - last.replace_str('areaname', sysinfo.child_value('areaname')) + last.replace_int("play_time", sysinfo.child_value("time_gameend")) + last.replace_str("shopname", sysinfo.child_value("shopname")) + last.replace_str("areaname", sysinfo.child_value("areaname")) # Grab player info for echoing back - info = player.child('info') + info = player.child("info") if info is not None: - newprofile.replace_int('jubility', info.child_value('jubility')) - newprofile.replace_int('jubility_yday', info.child_value('jubility_yday')) - newprofile.replace_int('tune_cnt', info.child_value('tune_cnt')) - newprofile.replace_int('save_cnt', info.child_value('save_cnt')) - newprofile.replace_int('saved_cnt', info.child_value('saved_cnt')) - newprofile.replace_int('fc_cnt', info.child_value('fc_cnt')) - newprofile.replace_int('ex_cnt', info.child_value('ex_cnt')) - newprofile.replace_int('clear_cnt', info.child_value('clear_cnt')) - newprofile.replace_int('match_cnt', info.child_value('match_cnt')) - newprofile.replace_int('beat_cnt', info.child_value('beat_cnt')) - newprofile.replace_int('mynews_cnt', info.child_value('mynews_cnt')) + newprofile.replace_int("jubility", info.child_value("jubility")) + newprofile.replace_int("jubility_yday", info.child_value("jubility_yday")) + newprofile.replace_int("tune_cnt", info.child_value("tune_cnt")) + newprofile.replace_int("save_cnt", info.child_value("save_cnt")) + newprofile.replace_int("saved_cnt", info.child_value("saved_cnt")) + newprofile.replace_int("fc_cnt", info.child_value("fc_cnt")) + newprofile.replace_int("ex_cnt", info.child_value("ex_cnt")) + newprofile.replace_int("clear_cnt", info.child_value("clear_cnt")) + newprofile.replace_int("match_cnt", info.child_value("match_cnt")) + newprofile.replace_int("beat_cnt", info.child_value("beat_cnt")) + newprofile.replace_int("mynews_cnt", info.child_value("mynews_cnt")) - newprofile.replace_int('bonus_tune_points', info.child_value('bonus_tune_points')) - newprofile.replace_bool('is_bonus_tune_played', info.child_value('is_bonus_tune_played')) + newprofile.replace_int( + "bonus_tune_points", info.child_value("bonus_tune_points") + ) + newprofile.replace_bool( + "is_bonus_tune_played", info.child_value("is_bonus_tune_played") + ) # Grab last settings - lastnode = player.child('last') + lastnode = player.child("last") if lastnode is not None: - last.replace_int('expert_option', lastnode.child_value('expert_option')) - last.replace_int('dig_select', lastnode.child_value('dig_select')) - last.replace_int('sort', lastnode.child_value('sort')) - last.replace_int('category', lastnode.child_value('category')) + last.replace_int("expert_option", lastnode.child_value("expert_option")) + last.replace_int("dig_select", lastnode.child_value("dig_select")) + last.replace_int("sort", lastnode.child_value("sort")) + last.replace_int("category", lastnode.child_value("category")) - settings = lastnode.child('settings') + settings = lastnode.child("settings") if settings is not None: - last.replace_int('matching', settings.child_value('matching')) - last.replace_int('hazard', settings.child_value('hazard')) - last.replace_int('hard', settings.child_value('hard')) - last.replace_int('marker', settings.child_value('marker')) - last.replace_int('theme', settings.child_value('theme')) - last.replace_int('title', settings.child_value('title')) - last.replace_int('parts', settings.child_value('parts')) - last.replace_int('rank_sort', settings.child_value('rank_sort')) - last.replace_int('combo_disp', settings.child_value('combo_disp')) - last.replace_int_array('emblem', 5, settings.child_value('emblem')) + last.replace_int("matching", settings.child_value("matching")) + last.replace_int("hazard", settings.child_value("hazard")) + last.replace_int("hard", settings.child_value("hard")) + last.replace_int("marker", settings.child_value("marker")) + last.replace_int("theme", settings.child_value("theme")) + last.replace_int("title", settings.child_value("title")) + last.replace_int("parts", settings.child_value("parts")) + last.replace_int("rank_sort", settings.child_value("rank_sort")) + last.replace_int("combo_disp", settings.child_value("combo_disp")) + last.replace_int_array("emblem", 5, settings.child_value("emblem")) # Grab unlock progress - item = player.child('item') + item = player.child("item") if item is not None: - newprofile.replace_int_array('music_list', 64, item.child_value('music_list')) - newprofile.replace_int_array('secret_list', 64, item.child_value('secret_list')) - newprofile.replace_int_array('theme_list', 16, item.child_value('theme_list')) - newprofile.replace_int_array('marker_list', 16, item.child_value('marker_list')) - newprofile.replace_int_array('title_list', 160, item.child_value('title_list')) - newprofile.replace_int_array('parts_list', 160, item.child_value('parts_list')) - newprofile.replace_int_array('emblem_list', 96, item.child_value('emblem_list')) + newprofile.replace_int_array( + "music_list", 64, item.child_value("music_list") + ) + newprofile.replace_int_array( + "secret_list", 64, item.child_value("secret_list") + ) + newprofile.replace_int_array( + "theme_list", 16, item.child_value("theme_list") + ) + newprofile.replace_int_array( + "marker_list", 16, item.child_value("marker_list") + ) + newprofile.replace_int_array( + "title_list", 160, item.child_value("title_list") + ) + newprofile.replace_int_array( + "parts_list", 160, item.child_value("parts_list") + ) + newprofile.replace_int_array( + "emblem_list", 96, item.child_value("emblem_list") + ) - newitem = item.child('new') + newitem = item.child("new") if newitem is not None: - newprofile.replace_int_array('secret_list_new', 64, newitem.child_value('secret_list')) - newprofile.replace_int_array('theme_list_new', 16, newitem.child_value('theme_list')) - newprofile.replace_int_array('marker_list_new', 16, newitem.child_value('marker_list')) + newprofile.replace_int_array( + "secret_list_new", 64, newitem.child_value("secret_list") + ) + newprofile.replace_int_array( + "theme_list_new", 16, newitem.child_value("theme_list") + ) + newprofile.replace_int_array( + "marker_list_new", 16, newitem.child_value("marker_list") + ) # jbox stuff - jbox = player.child('jbox') - jboxdict = newprofile.get_dict('jbox') + jbox = player.child("jbox") + jboxdict = newprofile.get_dict("jbox") if jbox is not None: - jboxdict.replace_int('point', jbox.child_value('point')) - emblemtype = jbox.child_value('emblem/type') - index = jbox.child_value('emblem/index') + jboxdict.replace_int("point", jbox.child_value("point")) + emblemtype = jbox.child_value("emblem/type") + index = jbox.child_value("emblem/index") if emblemtype == self.JBOX_EMBLEM_NORMAL: - jboxdict.replace_int('normal_index', index) + jboxdict.replace_int("normal_index", index) elif emblemtype == self.JBOX_EMBLEM_PREMIUM: - jboxdict.replace_int('premium_index', index) - newprofile.replace_dict('jbox', jboxdict) + jboxdict.replace_int("premium_index", index) + newprofile.replace_dict("jbox", jboxdict) # event stuff - event_info = player.child('event_info') + event_info = player.child("event_info") if event_info is not None: for child in event_info.children: try: - eventid = int(child.attribute('type')) + eventid = int(child.attribute("type")) except TypeError: # Event is empty continue - is_completed = child.child_value('is_completed') + is_completed = child.child_value("is_completed") # Figure out if we should update the rating/scores or not oldevent = self.data.local.user.get_achievement( @@ -1034,14 +1377,14 @@ class JubeatQubell( self.version, userid, eventid, - 'event', + "event", ) if oldevent is None: # Create a new event structure for this oldevent = ValidatedDict() - oldevent.replace_bool('is_completed', is_completed) + oldevent.replace_bool("is_completed", is_completed) # Save it as an achievement self.data.local.user.put_achievement( @@ -1049,72 +1392,92 @@ class JubeatQubell( self.version, userid, eventid, - 'event', + "event", oldevent, ) # DigDig stuff - digdig = player.child('digdig') - digdigdict = newprofile.get_dict('digdig') + digdig = player.child("digdig") + digdigdict = newprofile.get_dict("digdig") if digdig is not None: - digdigdict.replace_int('flag', digdig.child_value('flag')) + digdigdict.replace_int("flag", digdig.child_value("flag")) - main = digdig.child('main') + main = digdig.child("main") if main is not None: - stage = main.child('stage') - stage_num = int(stage.attribute('number')) - digdigdict.replace_int('stage_number', stage_num) - digdigdict.replace_int('point', stage.child_value('point')) - digdigdict.replace_int_array('param', 12, stage.child_value('param')) + stage = main.child("stage") + stage_num = int(stage.attribute("number")) + digdigdict.replace_int("stage_number", stage_num) + digdigdict.replace_int("point", stage.child_value("point")) + digdigdict.replace_int_array("param", 12, stage.child_value("param")) - if stage.child_value('uc_available') is True: + if stage.child_value("uc_available") is True: # We should enable unlock challenge for this node because the game # doesn't do it for us automatically. - stage_flags = self.data.local.user.get_achievement(self.game, self.version, userid, stage_num, 'stage') + stage_flags = self.data.local.user.get_achievement( + self.game, self.version, userid, stage_num, "stage" + ) if stage_flags is None: stage_flags = ValidatedDict() - stage_flags.replace_int('state', stage_flags.get_int('state') | 0x2) + stage_flags.replace_int("state", stage_flags.get_int("state") | 0x2) self.data.local.user.put_achievement( self.game, self.version, userid, stage_num, - 'stage', + "stage", stage_flags, ) - eternal = digdig.child('eternal') - eternaldict = digdigdict.get_dict('eternal') + eternal = digdig.child("eternal") + eternaldict = digdigdict.get_dict("eternal") if eternal is not None: - eternaldict.replace_int('used_point', eternal.child_value('used_point')) - eternaldict.replace_int('point', eternal.child_value('point')) - eternaldict.replace_int('excavated_point', eternal.child_value('excavated_point')) - eternaldict.replace_int_array('state', 12, eternal.child_value('cube/state')) - eternaldict.replace_int_array('item_kind', 12, eternal.child_value('cube/item/kind')) - eternaldict.replace_int_array('item_value', 12, eternal.child_value('cube/item/value')) - eternaldict.replace_int_array('norma_kind', 12, eternal.child_value('cube/norma/kind')) - eternaldict.replace_int_array('norma_value', 12, eternal.child_value('cube/norma/value')) - eternaldict.replace_int_array('norma_param', 12, eternal.child_value('cube/norma/param')) - digdigdict.replace_dict('eternal', eternaldict) + eternaldict.replace_int("used_point", eternal.child_value("used_point")) + eternaldict.replace_int("point", eternal.child_value("point")) + eternaldict.replace_int( + "excavated_point", eternal.child_value("excavated_point") + ) + eternaldict.replace_int_array( + "state", 12, eternal.child_value("cube/state") + ) + eternaldict.replace_int_array( + "item_kind", 12, eternal.child_value("cube/item/kind") + ) + eternaldict.replace_int_array( + "item_value", 12, eternal.child_value("cube/item/value") + ) + eternaldict.replace_int_array( + "norma_kind", 12, eternal.child_value("cube/norma/kind") + ) + eternaldict.replace_int_array( + "norma_value", 12, eternal.child_value("cube/norma/value") + ) + eternaldict.replace_int_array( + "norma_param", 12, eternal.child_value("cube/norma/param") + ) + digdigdict.replace_dict("eternal", eternaldict) if self.ENABLE_GARNET: - old = digdig.child('old') - olddict = digdigdict.get_dict('old') + old = digdig.child("old") + olddict = digdigdict.get_dict("old") if old is not None: - olddict.replace_int('need_point', old.child_value('need_point')) - olddict.replace_int('point', old.child_value('point')) - olddict.replace_int_array('excavated_point', 5, old.child_value('excavated_point')) - olddict.replace_int_array('excavated', 5, old.child_value('excavated')) - olddict.replace_int_array('param', 5, old.child_value('param')) - digdigdict.replace_dict('old', olddict) + olddict.replace_int("need_point", old.child_value("need_point")) + olddict.replace_int("point", old.child_value("point")) + olddict.replace_int_array( + "excavated_point", 5, old.child_value("excavated_point") + ) + olddict.replace_int_array( + "excavated", 5, old.child_value("excavated") + ) + olddict.replace_int_array("param", 5, old.child_value("param")) + digdigdict.replace_dict("old", olddict) # DigDig unlock event - unlock = player.child('unlock') + unlock = player.child("unlock") if unlock is not None: - stage = unlock.child('main/stage') + stage = unlock.child("main/stage") if stage is not None: - stage_num = int(stage.attribute('number')) - state = stage.child_value('state') + stage_num = int(stage.attribute("number")) + state = stage.child_value("state") # Just overwrite the state with this value self.data.local.user.put_achievement( @@ -1122,46 +1485,46 @@ class JubeatQubell( self.version, userid, stage_num, - 'stage', - {'state': state}, + "stage", + {"state": state}, ) # If they cleared stage 13, we need to unlock eternal mode if stage_num == 13 and (state & 0x18) > 0: - digdigdict.replace_int('flag', digdigdict.get_int('flag') | 0x2) + digdigdict.replace_int("flag", digdigdict.get_int("flag") | 0x2) # Save this back now that we've parsed everything - newprofile.replace_dict('digdig', digdigdict) + newprofile.replace_dict("digdig", digdigdict) # Still don't know what this is for lol - newprofile.replace_int('navi_flag', player.child_value('navi/flag')) + newprofile.replace_int("navi_flag", player.child_value("navi/flag")) # Grab scores and save those if result is not None: for tune in result.children: - if tune.name != 'tune': + if tune.name != "tune": continue - result = tune.child('player') + result = tune.child("player") - songid = tune.child_value('music') - timestamp = tune.child_value('timestamp') / 1000 - chart = int(result.child('score').attribute('seq')) - points = result.child_value('score') - flags = int(result.child('score').attribute('clear')) - combo = int(result.child('score').attribute('combo')) - ghost = result.child_value('mbar') + songid = tune.child_value("music") + timestamp = tune.child_value("timestamp") / 1000 + chart = int(result.child("score").attribute("seq")) + points = result.child_value("score") + flags = int(result.child("score").attribute("clear")) + combo = int(result.child("score").attribute("combo")) + ghost = result.child_value("mbar") stats = { - 'perfect': result.child_value('nr_perfect'), - 'great': result.child_value('nr_great'), - 'good': result.child_value('nr_good'), - 'poor': result.child_value('nr_poor'), - 'miss': result.child_value('nr_miss'), + "perfect": result.child_value("nr_perfect"), + "great": result.child_value("nr_great"), + "good": result.child_value("nr_good"), + "poor": result.child_value("nr_poor"), + "miss": result.child_value("nr_miss"), } # Miscelaneous last data for echoing to profile get - last.replace_int('music_id', songid) - last.replace_int('seq_id', chart) + last.replace_int("music_id", songid) + last.replace_int("seq_id", chart) mapping = { self.GAME_FLAG_BIT_CLEARED: self.PLAY_MEDAL_CLEARED, @@ -1177,16 +1540,18 @@ class JubeatQubell( if flags & bit > 0: medal = max(medal, mapping[bit]) - self.update_score(userid, timestamp, songid, chart, points, medal, combo, ghost, stats) + self.update_score( + userid, timestamp, songid, chart, points, medal, combo, ghost, stats + ) # Born stuff - born = player.child('born') + born = player.child("born") if born is not None: - newprofile.replace_int('born_status', born.child_value('status')) - newprofile.replace_int('born_year', born.child_value('year')) + newprofile.replace_int("born_status", born.child_value("status")) + newprofile.replace_int("born_year", born.child_value("year")) # Save back last information gleaned from results - newprofile.replace_dict('last', last) + newprofile.replace_dict("last", last) # Keep track of play statistics self.update_play_statistics(userid) diff --git a/bemani/backend/jubeat/saucer.py b/bemani/backend/jubeat/saucer.py index 448ad56..2925d5f 100644 --- a/bemani/backend/jubeat/saucer.py +++ b/bemani/backend/jubeat/saucer.py @@ -28,267 +28,332 @@ class JubeatSaucer( JubeatBase, ): - name: str = 'Jubeat Saucer' + name: str = "Jubeat Saucer" version: int = VersionConstants.JUBEAT_SAUCER def previous_version(self) -> Optional[JubeatBase]: return JubeatCopiousAppend(self.data, self.config, self.model) @classmethod - def run_scheduled_work(cls, data: Data, config: Dict[str, Any]) -> List[Tuple[str, Dict[str, Any]]]: + def run_scheduled_work( + cls, data: Data, config: Dict[str, Any] + ) -> List[Tuple[str, Dict[str, Any]]]: """ Insert daily FC challenges into the DB. """ events = [] - if data.local.network.should_schedule(cls.game, cls.version, 'fc_challenge', 'daily'): + if data.local.network.should_schedule( + cls.game, cls.version, "fc_challenge", "daily" + ): # Generate a new list of two FC challenge songs. - start_time, end_time = data.local.network.get_schedule_duration('daily') - all_songs = set(song.id for song in data.local.music.get_all_songs(cls.game, cls.version)) + start_time, end_time = data.local.network.get_schedule_duration("daily") + all_songs = set( + song.id + for song in data.local.music.get_all_songs(cls.game, cls.version) + ) if all_songs: today_song = random.sample(all_songs, 1)[0] data.local.game.put_time_sensitive_settings( cls.game, cls.version, - 'fc_challenge', + "fc_challenge", { - 'start_time': start_time, - 'end_time': end_time, - 'today': today_song, + "start_time": start_time, + "end_time": end_time, + "today": today_song, }, ) - events.append(( - 'jubeat_fc_challenge_charts', - { - 'version': cls.version, - 'today': today_song, - }, - )) + events.append( + ( + "jubeat_fc_challenge_charts", + { + "version": cls.version, + "today": today_song, + }, + ) + ) # Mark that we did some actual work here. - data.local.network.mark_scheduled(cls.game, cls.version, 'fc_challenge', 'daily') + data.local.network.mark_scheduled( + cls.game, cls.version, "fc_challenge", "daily" + ) return events def handle_shopinfo_regist_request(self, request: Node) -> Node: # Update the name of this cab for admin purposes - self.update_machine_name(request.child_value('shop/name')) + self.update_machine_name(request.child_value("shop/name")) - shopinfo = Node.void('shopinfo') + shopinfo = Node.void("shopinfo") - data = Node.void('data') + data = Node.void("data") shopinfo.add_child(data) - data.add_child(Node.u32('cabid', 1)) - data.add_child(Node.string('locationid', 'nowhere')) - data.add_child(Node.u8('is_send', 1)) - data.add_child(Node.s32_array( - 'white_music_list', - [ - -1, -1, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - -16385, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - -1, -1, -1, -1, - -1, - ], - )) - data.add_child(Node.u8('tax_phase', 1)) + data.add_child(Node.u32("cabid", 1)) + data.add_child(Node.string("locationid", "nowhere")) + data.add_child(Node.u8("is_send", 1)) + data.add_child( + Node.s32_array( + "white_music_list", + [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -16385, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], + ) + ) + data.add_child(Node.u8("tax_phase", 1)) - lab = Node.void('lab') + lab = Node.void("lab") data.add_child(lab) - lab.add_child(Node.bool('is_open', False)) + lab.add_child(Node.bool("is_open", False)) - vocaloid_event = Node.void('vocaloid_event') + vocaloid_event = Node.void("vocaloid_event") data.add_child(vocaloid_event) - vocaloid_event.add_child(Node.u8('state', 0)) - vocaloid_event.add_child(Node.s32('music_id', 0)) + vocaloid_event.add_child(Node.u8("state", 0)) + vocaloid_event.add_child(Node.s32("music_id", 0)) - matching_off = Node.void('matching_off') + matching_off = Node.void("matching_off") data.add_child(matching_off) - matching_off.add_child(Node.bool('is_open', True)) + matching_off.add_child(Node.bool("is_open", True)) return shopinfo def handle_gametop_regist_request(self, request: Node) -> Node: - data = request.child('data') - player = data.child('player') - passnode = player.child('pass') - refid = passnode.child_value('refid') - name = player.child_value('name') + data = request.child("data") + player = data.child("player") + passnode = player.child("pass") + refid = passnode.child_value("refid") + name = player.child_value("name") root = self.new_profile_by_refid(refid, name) return root def handle_gametop_get_pdata_request(self, request: Node) -> Node: - data = request.child('data') - player = data.child('player') - passnode = player.child('pass') - refid = passnode.child_value('refid') + data = request.child("data") + player = data.child("player") + passnode = player.child("pass") + refid = passnode.child_value("refid") root = self.get_profile_by_refid(refid) if root is None: - root = Node.void('gametop') - root.set_attribute('status', str(Status.NO_PROFILE)) + root = Node.void("gametop") + root.set_attribute("status", str(Status.NO_PROFILE)) return root def handle_gametop_get_mdata_request(self, request: Node) -> Node: - data = request.child('data') - player = data.child('player') - extid = player.child_value('jid') - mdata_ver = player.child_value('mdata_ver') # Game requests mdata 3 times per profile for some reason + data = request.child("data") + player = data.child("player") + extid = player.child_value("jid") + mdata_ver = player.child_value( + "mdata_ver" + ) # Game requests mdata 3 times per profile for some reason if mdata_ver != 1: - root = Node.void('gametop') - datanode = Node.void('data') + root = Node.void("gametop") + datanode = Node.void("data") root.add_child(datanode) - player = Node.void('player') + player = Node.void("player") datanode.add_child(player) - player.add_child(Node.s32('jid', extid)) - playdata = Node.void('playdata') + player.add_child(Node.s32("jid", extid)) + playdata = Node.void("playdata") player.add_child(playdata) - playdata.set_attribute('count', '0') + playdata.set_attribute("count", "0") return root root = self.get_scores_by_extid(extid) if root is None: - root = Node.void('gametop') - root.set_attribute('status', str(Status.NO_PROFILE)) + root = Node.void("gametop") + root.set_attribute("status", str(Status.NO_PROFILE)) return root def handle_gametop_get_rival_mdata_request(self, request: Node) -> Node: - data = request.child('data') - player = data.child('player') - extid = player.child_value('rival') + data = request.child("data") + player = data.child("player") + extid = player.child_value("rival") root = self.get_scores_by_extid(extid) if root is None: - root = Node.void('gametop') - root.set_attribute('status', str(Status.NO_PROFILE)) + root = Node.void("gametop") + root.set_attribute("status", str(Status.NO_PROFILE)) return root def format_profile(self, userid: UserID, profile: Profile) -> Node: - root = Node.void('gametop') - data = Node.void('data') + root = Node.void("gametop") + data = Node.void("data") root.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) # Player info and statistics - info = Node.void('info') + info = Node.void("info") player.add_child(info) - info.add_child(Node.s16('jubility', profile.get_int('jubility'))) - info.add_child(Node.s16('jubility_yday', profile.get_int('jubility_yday'))) - info.add_child(Node.s32('tune_cnt', profile.get_int('tune_cnt'))) - info.add_child(Node.s32('save_cnt', profile.get_int('save_cnt'))) - info.add_child(Node.s32('saved_cnt', profile.get_int('saved_cnt'))) - info.add_child(Node.s32('fc_cnt', profile.get_int('fc_cnt'))) - info.add_child(Node.s32('ex_cnt', profile.get_int('ex_cnt'))) - info.add_child(Node.s32('pf_cnt', profile.get_int('pf_cnt'))) - info.add_child(Node.s32('clear_cnt', profile.get_int('clear_cnt'))) - info.add_child(Node.s32('match_cnt', profile.get_int('match_cnt'))) - info.add_child(Node.s32('beat_cnt', profile.get_int('beat_cnt'))) - info.add_child(Node.s32('mynews_cnt', profile.get_int('mynews_cnt'))) - if 'total_best_score' in profile: - info.add_child(Node.s32('total_best_score', profile.get_int('total_best_score'))) + info.add_child(Node.s16("jubility", profile.get_int("jubility"))) + info.add_child(Node.s16("jubility_yday", profile.get_int("jubility_yday"))) + info.add_child(Node.s32("tune_cnt", profile.get_int("tune_cnt"))) + info.add_child(Node.s32("save_cnt", profile.get_int("save_cnt"))) + info.add_child(Node.s32("saved_cnt", profile.get_int("saved_cnt"))) + info.add_child(Node.s32("fc_cnt", profile.get_int("fc_cnt"))) + info.add_child(Node.s32("ex_cnt", profile.get_int("ex_cnt"))) + info.add_child(Node.s32("pf_cnt", profile.get_int("pf_cnt"))) + info.add_child(Node.s32("clear_cnt", profile.get_int("clear_cnt"))) + info.add_child(Node.s32("match_cnt", profile.get_int("match_cnt"))) + info.add_child(Node.s32("beat_cnt", profile.get_int("beat_cnt"))) + info.add_child(Node.s32("mynews_cnt", profile.get_int("mynews_cnt"))) + if "total_best_score" in profile: + info.add_child( + Node.s32("total_best_score", profile.get_int("total_best_score")) + ) # Looks to be set to true when there's an old profile, stops tutorial from # happening on first load. - info.add_child(Node.bool('inherit', profile.get_bool('has_old_version') and not profile.get_bool('saved'))) + info.add_child( + Node.bool( + "inherit", + profile.get_bool("has_old_version") and not profile.get_bool("saved"), + ) + ) # Not saved, but loaded - info.add_child(Node.s32('mtg_entry_cnt', 123)) - info.add_child(Node.s32('mtg_hold_cnt', 456)) - info.add_child(Node.u8('mtg_result', 10)) + info.add_child(Node.s32("mtg_entry_cnt", 123)) + info.add_child(Node.s32("mtg_hold_cnt", 456)) + info.add_child(Node.u8("mtg_result", 10)) # Secret unlocks - item = Node.void('item') + item = Node.void("item") player.add_child(item) - item.add_child(Node.s32_array( - 'secret_list', - profile.get_int_array( - 'secret_list', - 32, - [-1] * 32, - ), - )) - item.add_child(Node.s32_array( - 'title_list', - profile.get_int_array( - 'title_list', - 96, - [-1] * 96, - ), - )) - item.add_child(Node.s16('theme_list', profile.get_int('theme_list', -1))) - item.add_child(Node.s32_array('marker_list', profile.get_int_array('marker_list', 2, [-1] * 2))) - item.add_child(Node.s32_array('parts_list', profile.get_int_array('parts_list', 96, [-1] * 96))) + item.add_child( + Node.s32_array( + "secret_list", + profile.get_int_array( + "secret_list", + 32, + [-1] * 32, + ), + ) + ) + item.add_child( + Node.s32_array( + "title_list", + profile.get_int_array( + "title_list", + 96, + [-1] * 96, + ), + ) + ) + item.add_child(Node.s16("theme_list", profile.get_int("theme_list", -1))) + item.add_child( + Node.s32_array( + "marker_list", profile.get_int_array("marker_list", 2, [-1] * 2) + ) + ) + item.add_child( + Node.s32_array( + "parts_list", profile.get_int_array("parts_list", 96, [-1] * 96) + ) + ) - new = Node.void('new') + new = Node.void("new") item.add_child(new) - new.add_child(Node.s32_array( - 'secret_list', - profile.get_int_array( - 'secret_list_new', - 32, - [-1] * 32, - ), - )) - new.add_child(Node.s32_array( - 'title_list', - profile.get_int_array( - 'title_list_new', - 96, - [-1] * 96, - ), - )) - new.add_child(Node.s16('theme_list', profile.get_int('theme_list_new', -1))) - new.add_child(Node.s32_array('marker_list', profile.get_int_array('marker_list_new', 2, [-1] * 2))) + new.add_child( + Node.s32_array( + "secret_list", + profile.get_int_array( + "secret_list_new", + 32, + [-1] * 32, + ), + ) + ) + new.add_child( + Node.s32_array( + "title_list", + profile.get_int_array( + "title_list_new", + 96, + [-1] * 96, + ), + ) + ) + new.add_child(Node.s16("theme_list", profile.get_int("theme_list_new", -1))) + new.add_child( + Node.s32_array( + "marker_list", profile.get_int_array("marker_list_new", 2, [-1] * 2) + ) + ) # Last played data, for showing cursor and such - lastdict = profile.get_dict('last') - last = Node.void('last') + lastdict = profile.get_dict("last") + last = Node.void("last") player.add_child(last) - last.add_child(Node.s32('music_id', lastdict.get_int('music_id'))) - last.add_child(Node.s8('marker', lastdict.get_int('marker'))) - last.add_child(Node.s16('title', lastdict.get_int('title'))) - last.add_child(Node.s8('theme', lastdict.get_int('theme'))) - last.add_child(Node.s8('sort', lastdict.get_int('sort'))) - last.add_child(Node.s8('rank_sort', lastdict.get_int('rank_sort'))) - last.add_child(Node.s8('combo_disp', lastdict.get_int('combo_disp'))) - last.add_child(Node.s8('seq_id', lastdict.get_int('seq_id'))) - last.add_child(Node.s16('parts', lastdict.get_int('parts'))) - last.add_child(Node.s8('category', lastdict.get_int('category'))) - last.add_child(Node.s64('play_time', lastdict.get_int('play_time'))) - last.add_child(Node.string('shopname', lastdict.get_str('shopname'))) - last.add_child(Node.string('areaname', lastdict.get_str('areaname'))) + last.add_child(Node.s32("music_id", lastdict.get_int("music_id"))) + last.add_child(Node.s8("marker", lastdict.get_int("marker"))) + last.add_child(Node.s16("title", lastdict.get_int("title"))) + last.add_child(Node.s8("theme", lastdict.get_int("theme"))) + last.add_child(Node.s8("sort", lastdict.get_int("sort"))) + last.add_child(Node.s8("rank_sort", lastdict.get_int("rank_sort"))) + last.add_child(Node.s8("combo_disp", lastdict.get_int("combo_disp"))) + last.add_child(Node.s8("seq_id", lastdict.get_int("seq_id"))) + last.add_child(Node.s16("parts", lastdict.get_int("parts"))) + last.add_child(Node.s8("category", lastdict.get_int("category"))) + last.add_child(Node.s64("play_time", lastdict.get_int("play_time"))) + last.add_child(Node.string("shopname", lastdict.get_str("shopname"))) + last.add_child(Node.string("areaname", lastdict.get_str("areaname"))) # Miscelaneous crap - player.add_child(Node.s32('session_id', 1)) + player.add_child(Node.s32("session_id", 1)) # Maybe hook this up? Unsure what it does, is it like IIDX dailies? - today_music = Node.void('today_music') + today_music = Node.void("today_music") player.add_child(today_music) - today_music.add_child(Node.s32('music_id', 0)) + today_music.add_child(Node.s32("music_id", 0)) # No news, ever. - news = Node.void('news') + news = Node.void("news") player.add_child(news) - news.add_child(Node.s16('checked', 0)) + news.add_child(Node.s16("checked", 0)) # Add rivals to profile. - rivallist = Node.void('rivallist') + rivallist = Node.void("rivallist") player.add_child(rivallist) links = self.data.local.user.get_links(self.game, self.version, userid) rivalcount = 0 for link in links: - if link.type != 'rival': + if link.type != "rival": continue rprofile = self.get_profile(link.other_userid) if rprofile is None: continue - rival = Node.void('rival') + rival = Node.void("rival") rivallist.add_child(rival) - rival.add_child(Node.s32('jid', rprofile.extid)) - rival.add_child(Node.string('name', rprofile.get_str('name'))) + rival.add_child(Node.s32("jid", rprofile.extid)) + rival.add_child(Node.string("name", rprofile.get_str("name"))) # Lazy way of keeping track of rivals, since we can only have 4 # or the game with throw up. At least, I think Fulfill can have @@ -299,340 +364,462 @@ class JubeatSaucer( if rivalcount >= 3: break - rivallist.set_attribute('count', str(rivalcount)) + rivallist.set_attribute("count", str(rivalcount)) # Unclear what this is. Looks related to Jubeat lab. - mylist = Node.void('mylist') + mylist = Node.void("mylist") player.add_child(mylist) - mylist.set_attribute('count', '0') + mylist.set_attribute("count", "0") # No collaboration support yet. - collabo = Node.void('collabo') + collabo = Node.void("collabo") player.add_child(collabo) - collabo.add_child(Node.bool('success', False)) - collabo.add_child(Node.bool('completed', False)) + collabo.add_child(Node.bool("success", False)) + collabo.add_child(Node.bool("completed", False)) # Daily FC challenge. - entry = self.data.local.game.get_time_sensitive_settings(self.game, self.version, 'fc_challenge') + entry = self.data.local.game.get_time_sensitive_settings( + self.game, self.version, "fc_challenge" + ) if entry is None: entry = ValidatedDict() # Figure out if we've played these songs - start_time, end_time = self.data.local.network.get_schedule_duration('daily') - today_attempts = self.data.local.music.get_all_attempts(self.game, self.music_version, userid, entry.get_int('today', -1), timelimit=start_time) + start_time, end_time = self.data.local.network.get_schedule_duration("daily") + today_attempts = self.data.local.music.get_all_attempts( + self.game, + self.music_version, + userid, + entry.get_int("today", -1), + timelimit=start_time, + ) - challenge = Node.void('challenge') + challenge = Node.void("challenge") player.add_child(challenge) - today = Node.void('today') + today = Node.void("today") challenge.add_child(today) - today.add_child(Node.s32('music_id', entry.get_int('today', -1))) - today.add_child(Node.u8('state', 0x40 if len(today_attempts) > 0 else 0x0)) - onlynow = Node.void('onlynow') + today.add_child(Node.s32("music_id", entry.get_int("today", -1))) + today.add_child(Node.u8("state", 0x40 if len(today_attempts) > 0 else 0x0)) + onlynow = Node.void("onlynow") challenge.add_child(onlynow) - onlynow.add_child(Node.s32('magic_no', 0)) - onlynow.add_child(Node.s16('cycle', 0)) + onlynow.add_child(Node.s32("magic_no", 0)) + onlynow.add_child(Node.s16("cycle", 0)) # Bistro event - bistro = Node.void('bistro') + bistro = Node.void("bistro") player.add_child(bistro) # Presumably these can affect the speed of the event - info_1 = Node.void('info') + info_1 = Node.void("info") bistro.add_child(info_1) - info_1.add_child(Node.float('delicious_rate', 1.0)) - info_1.add_child(Node.float('favorite_rate', 1.0)) - bistro.add_child(Node.s32('carry_over', profile.get_int('bistro_carry_over'))) + info_1.add_child(Node.float("delicious_rate", 1.0)) + info_1.add_child(Node.float("favorite_rate", 1.0)) + bistro.add_child(Node.s32("carry_over", profile.get_int("bistro_carry_over"))) # Your chef dude, I guess? - chefdict = profile.get_dict('chef') - chef = Node.void('chef') + chefdict = profile.get_dict("chef") + chef = Node.void("chef") bistro.add_child(chef) - chef.add_child(Node.s32('id', chefdict.get_int('id', 1))) - chef.add_child(Node.u8('ability', chefdict.get_int('ability', 2))) - chef.add_child(Node.u8('remain', chefdict.get_int('remain', 30))) - chef.add_child(Node.u8('rate', chefdict.get_int('rate', 1))) + chef.add_child(Node.s32("id", chefdict.get_int("id", 1))) + chef.add_child(Node.u8("ability", chefdict.get_int("ability", 2))) + chef.add_child(Node.u8("remain", chefdict.get_int("remain", 30))) + chef.add_child(Node.u8("rate", chefdict.get_int("rate", 1))) # Routes, similar to story mode in Pop'n I guess? routes = [ { - 'id': 50000284, - 'price': 20, - 'satisfaction': 10, - 'favorite': True, + "id": 50000284, + "price": 20, + "satisfaction": 10, + "favorite": True, }, { - 'id': 50000283, - 'price': 20, - 'satisfaction': 20, - 'favorite': False, + "id": 50000283, + "price": 20, + "satisfaction": 20, + "favorite": False, }, { - 'id': 50000282, - 'price': 30, - 'satisfaction': 10, - 'favorite': False, + "id": 50000282, + "price": 30, + "satisfaction": 10, + "favorite": False, }, { - 'id': 50000275, - 'price': 10, - 'satisfaction': 55, - 'favorite': False, + "id": 50000275, + "price": 10, + "satisfaction": 55, + "favorite": False, }, { - 'id': 50000274, - 'price': 40, - 'satisfaction': 40, - 'favorite': False, + "id": 50000274, + "price": 40, + "satisfaction": 40, + "favorite": False, }, { - 'id': 50000273, - 'price': 80, - 'satisfaction': 60, - 'favorite': False, + "id": 50000273, + "price": 80, + "satisfaction": 60, + "favorite": False, }, { - 'id': 50000272, - 'price': 70, - 'satisfaction': 60, - 'favorite': False, + "id": 50000272, + "price": 70, + "satisfaction": 60, + "favorite": False, }, { - 'id': 50000271, - 'price': 90, - 'satisfaction': 80, - 'favorite': False, + "id": 50000271, + "price": 90, + "satisfaction": 80, + "favorite": False, }, { - 'id': 50000270, - 'price': 90, - 'satisfaction': 20, - 'favorite': False, + "id": 50000270, + "price": 90, + "satisfaction": 20, + "favorite": False, }, ] for route_no in range(len(routes)): routedata = routes[route_no] - route = Node.void('route') + route = Node.void("route") bistro.add_child(route) - route.set_attribute('no', str(route_no)) + route.set_attribute("no", str(route_no)) - music = Node.void('music') + music = Node.void("music") route.add_child(music) - music.add_child(Node.s32('id', routedata['id'])) - music.add_child(Node.u16('price', routedata['price'])) - music.add_child(Node.s32('price_s32', routedata['price'])) + music.add_child(Node.s32("id", routedata["id"])) + music.add_child(Node.u16("price", routedata["price"])) + music.add_child(Node.s32("price_s32", routedata["price"])) # Look up any updated satisfaction stored by the game - routesaved = self.data.local.user.get_achievement(self.game, self.version, userid, route_no + 1, 'route') + routesaved = self.data.local.user.get_achievement( + self.game, self.version, userid, route_no + 1, "route" + ) if routesaved is None: routesaved = ValidatedDict() - satisfaction = routesaved.get_int('satisfaction', routedata['satisfaction']) + satisfaction = routesaved.get_int("satisfaction", routedata["satisfaction"]) - gourmates = Node.void('gourmates') + gourmates = Node.void("gourmates") route.add_child(gourmates) - gourmates.add_child(Node.s32('id', route_no + 1)) - gourmates.add_child(Node.u8('favorite', 1 if routedata['favorite'] else 0)) - gourmates.add_child(Node.u16('satisfaction', satisfaction)) - gourmates.add_child(Node.s32('satisfaction_s32', satisfaction)) + gourmates.add_child(Node.s32("id", route_no + 1)) + gourmates.add_child(Node.u8("favorite", 1 if routedata["favorite"] else 0)) + gourmates.add_child(Node.u16("satisfaction", satisfaction)) + gourmates.add_child(Node.s32("satisfaction_s32", satisfaction)) # Sane defaults for unknown nodes - only_now_music = Node.void('only_now_music') + only_now_music = Node.void("only_now_music") player.add_child(only_now_music) - only_now_music.set_attribute('count', '0') - requested_music = Node.void('requested_music') + only_now_music.set_attribute("count", "0") + requested_music = Node.void("requested_music") player.add_child(requested_music) - requested_music.set_attribute('count', '0') - kac_music = Node.void('kac_music') + requested_music.set_attribute("count", "0") + kac_music = Node.void("kac_music") player.add_child(kac_music) - kac_music.set_attribute('count', '0') - history = Node.void('history') + kac_music.set_attribute("count", "0") + history = Node.void("history") player.add_child(history) - history.set_attribute('count', '0') + history.set_attribute("count", "0") # Basic profile info - player.add_child(Node.string('name', profile.get_str('name', 'なし'))) - player.add_child(Node.s32('jid', profile.extid)) - player.add_child(Node.string('refid', profile.refid)) + player.add_child(Node.string("name", profile.get_str("name", "なし"))) + player.add_child(Node.s32("jid", profile.extid)) + player.add_child(Node.string("refid", profile.refid)) # Miscelaneous history stuff - data.add_child(Node.u8('termver', 16)) - data.add_child(Node.u32('season_etime', 0)) - data.add_child(Node.s32('bistro_last_music_id', 0)) - data.add_child(Node.s32_array( - 'white_music_list', - [ - -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, -1, -1, -1, -1, -1, -1, -1, - ], - )) - data.add_child(Node.s32_array( - 'old_music_list', - [ - -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, -1, -1, -1, -1, -1, -1, -1, - ], - )) - data.add_child(Node.s32_array( - 'open_music_list', - [ - -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, -1, -1, -1, -1, -1, -1, -1, - ], - )) + data.add_child(Node.u8("termver", 16)) + data.add_child(Node.u32("season_etime", 0)) + data.add_child(Node.s32("bistro_last_music_id", 0)) + data.add_child( + Node.s32_array( + "white_music_list", + [ + -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, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], + ) + ) + data.add_child( + Node.s32_array( + "old_music_list", + [ + -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, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], + ) + ) + data.add_child( + Node.s32_array( + "open_music_list", + [ + -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, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], + ) + ) # Unsupported collaboration events with other games - collabo_info = Node.void('collabo_info') + collabo_info = Node.void("collabo_info") data.add_child(collabo_info) # Unsupported marathon stuff - run_run_marathon = Node.void('run_run_marathon') + run_run_marathon = Node.void("run_run_marathon") collabo_info.add_child(run_run_marathon) - run_run_marathon.set_attribute('type', '1') - run_run_marathon.add_child(Node.u8('state', 1)) - run_run_marathon.add_child(Node.bool('is_report_end', True)) + run_run_marathon.set_attribute("type", "1") + run_run_marathon.add_child(Node.u8("state", 1)) + run_run_marathon.add_child(Node.bool("is_report_end", True)) # Unsupported policy break stuff - policy_break = Node.void('policy_break') + policy_break = Node.void("policy_break") collabo_info.add_child(policy_break) - policy_break.set_attribute('type', '1') - policy_break.add_child(Node.u8('state', 1)) - policy_break.add_child(Node.bool('is_report_end', False)) + policy_break.set_attribute("type", "1") + policy_break.add_child(Node.u8("state", 1)) + policy_break.add_child(Node.bool("is_report_end", False)) # Unsupported vocaloid stuff - vocaloid_event = Node.void('vocaloid_event') + vocaloid_event = Node.void("vocaloid_event") collabo_info.add_child(vocaloid_event) - vocaloid_event.set_attribute('type', '1') - vocaloid_event.add_child(Node.u8('state', 0)) - vocaloid_event.add_child(Node.s32('music_id', 0)) + vocaloid_event.set_attribute("type", "1") + vocaloid_event.add_child(Node.u8("state", 0)) + vocaloid_event.add_child(Node.s32("music_id", 0)) # No obnoxious 30 second wait to play. - matching_off = Node.void('matching_off') + matching_off = Node.void("matching_off") data.add_child(matching_off) - matching_off.add_child(Node.bool('is_open', True)) + matching_off.add_child(Node.bool("is_open", True)) return root - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() - newprofile.replace_bool('saved', True) - data = request.child('data') + newprofile.replace_bool("saved", True) + data = request.child("data") # Grab player information - player = data.child('player') + player = data.child("player") # Grab last information. Lots of this will be filled in while grabbing scores - last = newprofile.get_dict('last') - last.replace_int('play_time', player.child_value('time_gameend')) - last.replace_str('shopname', player.child_value('shopname')) - last.replace_str('areaname', player.child_value('areaname')) + last = newprofile.get_dict("last") + last.replace_int("play_time", player.child_value("time_gameend")) + last.replace_str("shopname", player.child_value("shopname")) + last.replace_str("areaname", player.child_value("areaname")) # Grab player info for echoing back - info = player.child('info') + info = player.child("info") if info is not None: - newprofile.replace_int('jubility', info.child_value('jubility')) - newprofile.replace_int('jubility_yday', info.child_value('jubility_yday')) - newprofile.replace_int('tune_cnt', info.child_value('tune_cnt')) - newprofile.replace_int('save_cnt', info.child_value('save_cnt')) - newprofile.replace_int('saved_cnt', info.child_value('saved_cnt')) - newprofile.replace_int('fc_cnt', info.child_value('fc_cnt')) - newprofile.replace_int('ex_cnt', info.child_value('exc_cnt')) # Not a mistake, Jubeat is weird - newprofile.replace_int('pf_cnt', info.child_value('pf_cnt')) - newprofile.replace_int('clear_cnt', info.child_value('clear_cnt')) - newprofile.replace_int('match_cnt', info.child_value('match_cnt')) - newprofile.replace_int('beat_cnt', info.child_value('beat_cnt')) - newprofile.replace_int('total_best_score', info.child_value('total_best_score')) - newprofile.replace_int('mynews_cnt', info.child_value('mynews_cnt')) + newprofile.replace_int("jubility", info.child_value("jubility")) + newprofile.replace_int("jubility_yday", info.child_value("jubility_yday")) + newprofile.replace_int("tune_cnt", info.child_value("tune_cnt")) + newprofile.replace_int("save_cnt", info.child_value("save_cnt")) + newprofile.replace_int("saved_cnt", info.child_value("saved_cnt")) + newprofile.replace_int("fc_cnt", info.child_value("fc_cnt")) + newprofile.replace_int( + "ex_cnt", info.child_value("exc_cnt") + ) # Not a mistake, Jubeat is weird + newprofile.replace_int("pf_cnt", info.child_value("pf_cnt")) + newprofile.replace_int("clear_cnt", info.child_value("clear_cnt")) + newprofile.replace_int("match_cnt", info.child_value("match_cnt")) + newprofile.replace_int("beat_cnt", info.child_value("beat_cnt")) + newprofile.replace_int( + "total_best_score", info.child_value("total_best_score") + ) + newprofile.replace_int("mynews_cnt", info.child_value("mynews_cnt")) # Grab unlock progress - item = player.child('item') + item = player.child("item") if item is not None: - newprofile.replace_int_array('secret_list', 32, item.child_value('secret_list')) - newprofile.replace_int_array('title_list', 96, item.child_value('title_list')) - newprofile.replace_int('theme_list', item.child_value('theme_list')) - newprofile.replace_int_array('marker_list', 2, item.child_value('marker_list')) - newprofile.replace_int_array('parts_list', 96, item.child_value('parts_list')) - newprofile.replace_int_array('secret_list_new', 32, item.child_value('secret_new')) - newprofile.replace_int_array('title_list_new', 96, item.child_value('title_new')) - newprofile.replace_int('theme_list_new', item.child_value('theme_new')) - newprofile.replace_int_array('marker_list_new', 2, item.child_value('marker_new')) + newprofile.replace_int_array( + "secret_list", 32, item.child_value("secret_list") + ) + newprofile.replace_int_array( + "title_list", 96, item.child_value("title_list") + ) + newprofile.replace_int("theme_list", item.child_value("theme_list")) + newprofile.replace_int_array( + "marker_list", 2, item.child_value("marker_list") + ) + newprofile.replace_int_array( + "parts_list", 96, item.child_value("parts_list") + ) + newprofile.replace_int_array( + "secret_list_new", 32, item.child_value("secret_new") + ) + newprofile.replace_int_array( + "title_list_new", 96, item.child_value("title_new") + ) + newprofile.replace_int("theme_list_new", item.child_value("theme_new")) + newprofile.replace_int_array( + "marker_list_new", 2, item.child_value("marker_new") + ) # Grab bistro progress - bistro = player.child('bistro') + bistro = player.child("bistro") if bistro is not None: - newprofile.replace_int('bistro_carry_over', bistro.child_value('carry_over')) + newprofile.replace_int( + "bistro_carry_over", bistro.child_value("carry_over") + ) - chefdata = newprofile.get_dict('chef') - chef = bistro.child('chef') + chefdata = newprofile.get_dict("chef") + chef = bistro.child("chef") if chef is not None: - chefdata.replace_int('id', chef.child_value('id')) - chefdata.replace_int('ability', chef.child_value('ability')) - chefdata.replace_int('remain', chef.child_value('remain')) - chefdata.replace_int('rate', chef.child_value('rate')) - newprofile.replace_dict('chef', chefdata) + chefdata.replace_int("id", chef.child_value("id")) + chefdata.replace_int("ability", chef.child_value("ability")) + chefdata.replace_int("remain", chef.child_value("remain")) + chefdata.replace_int("rate", chef.child_value("rate")) + newprofile.replace_dict("chef", chefdata) for route in bistro.children: - if route.name != 'route': + if route.name != "route": continue - gourmates = route.child('gourmates') - routeid = gourmates.child_value('id') - satisfaction = gourmates.child_value('satisfaction_s32') + gourmates = route.child("gourmates") + routeid = gourmates.child_value("id") + satisfaction = gourmates.child_value("satisfaction_s32") self.data.local.user.put_achievement( self.game, self.version, userid, routeid, - 'route', + "route", { - 'satisfaction': satisfaction, + "satisfaction": satisfaction, }, ) # Get timestamps for played songs timestamps: Dict[int, int] = {} - history = player.child('history') + history = player.child("history") if history is not None: for tune in history.children: - if tune.name != 'tune': + if tune.name != "tune": continue - entry = int(tune.attribute('log_id')) - ts = int(tune.child_value('timestamp') / 1000) + entry = int(tune.attribute("log_id")) + ts = int(tune.child_value("timestamp") / 1000) timestamps[entry] = ts # Grab scores and save those - result = data.child('result') + result = data.child("result") if result is not None: for tune in result.children: - if tune.name != 'tune': + if tune.name != "tune": continue - result = tune.child('player') + result = tune.child("player") - last.replace_int('marker', tune.child_value('marker')) - last.replace_int('title', tune.child_value('title')) - last.replace_int('parts', tune.child_value('parts')) - last.replace_int('theme', tune.child_value('theme')) - last.replace_int('sort', tune.child_value('sort')) - last.replace_int('category', tune.child_value('category')) - last.replace_int('rank_sort', tune.child_value('rank_sort')) - last.replace_int('combo_disp', tune.child_value('combo_disp')) + last.replace_int("marker", tune.child_value("marker")) + last.replace_int("title", tune.child_value("title")) + last.replace_int("parts", tune.child_value("parts")) + last.replace_int("theme", tune.child_value("theme")) + last.replace_int("sort", tune.child_value("sort")) + last.replace_int("category", tune.child_value("category")) + last.replace_int("rank_sort", tune.child_value("rank_sort")) + last.replace_int("combo_disp", tune.child_value("combo_disp")) - songid = tune.child_value('music') - entry = int(tune.attribute('id')) + songid = tune.child_value("music") + entry = int(tune.attribute("id")) timestamp = timestamps.get(entry, Time.now()) - chart = int(result.child('score').attribute('seq')) - points = result.child_value('score') - flags = int(result.child('score').attribute('clear')) - combo = int(result.child('score').attribute('combo')) - ghost = result.child_value('mbar') + chart = int(result.child("score").attribute("seq")) + points = result.child_value("score") + flags = int(result.child("score").attribute("clear")) + combo = int(result.child("score").attribute("combo")) + ghost = result.child_value("mbar") # Miscelaneous last data for echoing to profile get - last.replace_int('music_id', songid) - last.replace_int('seq_id', chart) + last.replace_int("music_id", songid) + last.replace_int("seq_id", chart) mapping = { self.GAME_FLAG_BIT_CLEARED: self.PLAY_MEDAL_CLEARED, @@ -648,95 +835,115 @@ class JubeatSaucer( if flags & bit > 0: medal = max(medal, mapping[bit]) - self.update_score(userid, timestamp, songid, chart, points, medal, combo, ghost) + self.update_score( + userid, timestamp, songid, chart, points, medal, combo, ghost + ) # Save back last information gleaned from results - newprofile.replace_dict('last', last) + newprofile.replace_dict("last", last) # Keep track of play statistics self.update_play_statistics(userid) return newprofile - def format_scores(self, userid: UserID, profile: Profile, scores: List[Score]) -> Node: + def format_scores( + self, userid: UserID, profile: Profile, scores: List[Score] + ) -> Node: - root = Node.void('gametop') - datanode = Node.void('data') + root = Node.void("gametop") + datanode = Node.void("data") root.add_child(datanode) - player = Node.void('player') + player = Node.void("player") datanode.add_child(player) - player.add_child(Node.s32('jid', profile.extid)) - playdata = Node.void('playdata') + player.add_child(Node.s32("jid", profile.extid)) + playdata = Node.void("playdata") player.add_child(playdata) - playdata.set_attribute('count', str(len(scores))) + playdata.set_attribute("count", str(len(scores))) music = ValidatedDict() for score in scores: # Ignore festo-and-above chart types. - if score.chart not in {self.CHART_TYPE_BASIC, self.CHART_TYPE_ADVANCED, self.CHART_TYPE_EXTREME}: + if score.chart not in { + self.CHART_TYPE_BASIC, + self.CHART_TYPE_ADVANCED, + self.CHART_TYPE_EXTREME, + }: continue data = music.get_dict(str(score.id)) - play_cnt = data.get_int_array('play_cnt', 3) - clear_cnt = data.get_int_array('clear_cnt', 3) - clear_flags = data.get_int_array('clear_flags', 3) - fc_cnt = data.get_int_array('fc_cnt', 3) - ex_cnt = data.get_int_array('ex_cnt', 3) - points = data.get_int_array('points', 3) + play_cnt = data.get_int_array("play_cnt", 3) + clear_cnt = data.get_int_array("clear_cnt", 3) + clear_flags = data.get_int_array("clear_flags", 3) + fc_cnt = data.get_int_array("fc_cnt", 3) + ex_cnt = data.get_int_array("ex_cnt", 3) + points = data.get_int_array("points", 3) # Replace data for this chart type play_cnt[score.chart] = score.plays - clear_cnt[score.chart] = score.data.get_int('clear_count') - fc_cnt[score.chart] = score.data.get_int('full_combo_count') - ex_cnt[score.chart] = score.data.get_int('excellent_count') + clear_cnt[score.chart] = score.data.get_int("clear_count") + fc_cnt[score.chart] = score.data.get_int("full_combo_count") + ex_cnt[score.chart] = score.data.get_int("excellent_count") points[score.chart] = score.points # Format the clear flags clear_flags[score.chart] = self.GAME_FLAG_BIT_PLAYED - if score.data.get_int('clear_count') > 0: + if score.data.get_int("clear_count") > 0: clear_flags[score.chart] |= self.GAME_FLAG_BIT_CLEARED - if score.data.get_int('full_combo_count') > 0: + if score.data.get_int("full_combo_count") > 0: clear_flags[score.chart] |= self.GAME_FLAG_BIT_FULL_COMBO - if score.data.get_int('excellent_count') > 0: + if score.data.get_int("excellent_count") > 0: clear_flags[score.chart] |= self.GAME_FLAG_BIT_EXCELLENT # Save chart data back - data.replace_int_array('play_cnt', 3, play_cnt) - data.replace_int_array('clear_cnt', 3, clear_cnt) - data.replace_int_array('clear_flags', 3, clear_flags) - data.replace_int_array('fc_cnt', 3, fc_cnt) - data.replace_int_array('ex_cnt', 3, ex_cnt) - data.replace_int_array('points', 3, points) + data.replace_int_array("play_cnt", 3, play_cnt) + data.replace_int_array("clear_cnt", 3, clear_cnt) + data.replace_int_array("clear_flags", 3, clear_flags) + data.replace_int_array("fc_cnt", 3, fc_cnt) + data.replace_int_array("ex_cnt", 3, ex_cnt) + data.replace_int_array("points", 3, points) # Update the ghost (untyped) - ghost = data.get('ghost', [None, None, None]) - ghost[score.chart] = score.data.get('ghost') - data['ghost'] = ghost + ghost = data.get("ghost", [None, None, None]) + ghost[score.chart] = score.data.get("ghost") + data["ghost"] = ghost # Save it back music.replace_dict(str(score.id), data) for scoreid in music: scoredata = music[scoreid] - musicdata = Node.void('musicdata') + musicdata = Node.void("musicdata") playdata.add_child(musicdata) - musicdata.set_attribute('music_id', scoreid) - musicdata.add_child(Node.s32_array('play_cnt', scoredata.get_int_array('play_cnt', 3))) - musicdata.add_child(Node.s32_array('clear_cnt', scoredata.get_int_array('clear_cnt', 3))) - musicdata.add_child(Node.s32_array('fc_cnt', scoredata.get_int_array('fc_cnt', 3))) - musicdata.add_child(Node.s32_array('ex_cnt', scoredata.get_int_array('ex_cnt', 3))) - musicdata.add_child(Node.s32_array('score', scoredata.get_int_array('points', 3))) - musicdata.add_child(Node.s8_array('clear', scoredata.get_int_array('clear_flags', 3))) + musicdata.set_attribute("music_id", scoreid) + musicdata.add_child( + Node.s32_array("play_cnt", scoredata.get_int_array("play_cnt", 3)) + ) + musicdata.add_child( + Node.s32_array("clear_cnt", scoredata.get_int_array("clear_cnt", 3)) + ) + musicdata.add_child( + Node.s32_array("fc_cnt", scoredata.get_int_array("fc_cnt", 3)) + ) + musicdata.add_child( + Node.s32_array("ex_cnt", scoredata.get_int_array("ex_cnt", 3)) + ) + musicdata.add_child( + Node.s32_array("score", scoredata.get_int_array("points", 3)) + ) + musicdata.add_child( + Node.s8_array("clear", scoredata.get_int_array("clear_flags", 3)) + ) - ghosts = scoredata.get('ghost', [None, None, None]) + ghosts = scoredata.get("ghost", [None, None, None]) for i in range(len(ghosts)): ghost = ghosts[i] if ghost is None: continue - bar = Node.u8_array('bar', ghost) + bar = Node.u8_array("bar", ghost) musicdata.add_child(bar) - bar.set_attribute('seq', str(i)) + bar.set_attribute("seq", str(i)) return root diff --git a/bemani/backend/jubeat/saucerfulfill.py b/bemani/backend/jubeat/saucerfulfill.py index 6894549..1483562 100644 --- a/bemani/backend/jubeat/saucerfulfill.py +++ b/bemani/backend/jubeat/saucerfulfill.py @@ -31,7 +31,7 @@ class JubeatSaucerFulfill( JubeatBase, ): - name: str = 'Jubeat Saucer Fulfill' + name: str = "Jubeat Saucer Fulfill" version: int = VersionConstants.JUBEAT_SAUCER_FULFILL GAME_COURSE_REQUIREMENT_SCORE: Final[int] = 1 @@ -47,117 +47,154 @@ class JubeatSaucerFulfill( return JubeatSaucer(self.data, self.config, self.model) @classmethod - def run_scheduled_work(cls, data: Data, config: Dict[str, Any]) -> List[Tuple[str, Dict[str, Any]]]: + def run_scheduled_work( + cls, data: Data, config: Dict[str, Any] + ) -> List[Tuple[str, Dict[str, Any]]]: """ Insert daily FC challenges into the DB. """ events = [] - if data.local.network.should_schedule(cls.game, cls.version, 'fc_challenge', 'daily'): + if data.local.network.should_schedule( + cls.game, cls.version, "fc_challenge", "daily" + ): # Generate a new list of two FC challenge songs. - start_time, end_time = data.local.network.get_schedule_duration('daily') - all_songs = set(song.id for song in data.local.music.get_all_songs(cls.game, cls.version)) + start_time, end_time = data.local.network.get_schedule_duration("daily") + all_songs = set( + song.id + for song in data.local.music.get_all_songs(cls.game, cls.version) + ) if len(all_songs) >= 2: daily_songs = random.sample(all_songs, 2) data.local.game.put_time_sensitive_settings( cls.game, cls.version, - 'fc_challenge', + "fc_challenge", { - 'start_time': start_time, - 'end_time': end_time, - 'today': daily_songs[0], - 'whim': daily_songs[1], + "start_time": start_time, + "end_time": end_time, + "today": daily_songs[0], + "whim": daily_songs[1], }, ) - events.append(( - 'jubeat_fc_challenge_charts', - { - 'version': cls.version, - 'today': daily_songs[0], - 'whim': daily_songs[1], - }, - )) + events.append( + ( + "jubeat_fc_challenge_charts", + { + "version": cls.version, + "today": daily_songs[0], + "whim": daily_songs[1], + }, + ) + ) # Mark that we did some actual work here. - data.local.network.mark_scheduled(cls.game, cls.version, 'fc_challenge', 'daily') + data.local.network.mark_scheduled( + cls.game, cls.version, "fc_challenge", "daily" + ) return events def handle_shopinfo_regist_request(self, request: Node) -> Node: # Update the name of this cab for admin purposes - self.update_machine_name(request.child_value('shop/name')) + self.update_machine_name(request.child_value("shop/name")) - shopinfo = Node.void('shopinfo') + shopinfo = Node.void("shopinfo") - data = Node.void('data') + data = Node.void("data") shopinfo.add_child(data) - data.add_child(Node.u32('cabid', 1)) - data.add_child(Node.string('locationid', 'nowhere')) - data.add_child(Node.u8('is_send', 1)) - data.add_child(Node.s32_array( - 'white_music_list', - [ - -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, -1, -1, -1, - -1, -1, -1, -1, - ], - )) - data.add_child(Node.u8('tax_phase', 1)) + data.add_child(Node.u32("cabid", 1)) + data.add_child(Node.string("locationid", "nowhere")) + data.add_child(Node.u8("is_send", 1)) + data.add_child( + Node.s32_array( + "white_music_list", + [ + -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, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], + ) + ) + data.add_child(Node.u8("tax_phase", 1)) - lab = Node.void('lab') + lab = Node.void("lab") data.add_child(lab) - lab.add_child(Node.bool('is_open', False)) + lab.add_child(Node.bool("is_open", False)) - vocaloid_event = Node.void('vocaloid_event') + vocaloid_event = Node.void("vocaloid_event") data.add_child(vocaloid_event) - vocaloid_event.add_child(Node.u8('state', 0)) - vocaloid_event.add_child(Node.s32('music_id', 0)) + vocaloid_event.add_child(Node.u8("state", 0)) + vocaloid_event.add_child(Node.s32("music_id", 0)) - vocaloid_event2 = Node.void('vocaloid_event2') + vocaloid_event2 = Node.void("vocaloid_event2") data.add_child(vocaloid_event2) - vocaloid_event2.add_child(Node.u8('state', 0)) - vocaloid_event2.add_child(Node.s32('music_id', 0)) + vocaloid_event2.add_child(Node.u8("state", 0)) + vocaloid_event2.add_child(Node.s32("music_id", 0)) # No obnoxious 30 second wait to play. - matching_off = Node.void('matching_off') + matching_off = Node.void("matching_off") data.add_child(matching_off) - matching_off.add_child(Node.bool('is_open', True)) + matching_off.add_child(Node.bool("is_open", True)) - tenka = Node.void('tenka') + tenka = Node.void("tenka") data.add_child(tenka) - tenka.add_child(Node.bool('is_participant', False)) + tenka.add_child(Node.bool("is_participant", False)) return shopinfo def handle_gametop_get_course_request(self, request: Node) -> Node: - data = request.child('data') - player = data.child('player') - extid = player.child_value('jid') + data = request.child("data") + player = data.child("player") + extid = player.child_value("jid") - gametop = Node.void('gametop') - data = Node.void('data') + gametop = Node.void("gametop") + data = Node.void("data") gametop.add_child(data) # Course list available - course_list = Node.void('course_list') + course_list = Node.void("course_list") data.add_child(course_list) validcourses: List[int] = [] for course in self.get_all_courses(): - coursenode = Node.void('course') + coursenode = Node.void("course") course_list.add_child(coursenode) # Basic course info - if course['id'] in validcourses: - raise Exception('Cannot have same course ID specified twice!') - validcourses.append(course['id']) - coursenode.add_child(Node.s32('id', course['id'])) - coursenode.add_child(Node.string('name', course['name'])) - coursenode.add_child(Node.u8('level', course['level'])) + if course["id"] in validcourses: + raise Exception("Cannot have same course ID specified twice!") + validcourses.append(course["id"]) + coursenode.add_child(Node.s32("id", course["id"])) + coursenode.add_child(Node.string("name", course["name"])) + coursenode.add_child(Node.u8("level", course["level"])) # Translate internal to game def translate_req(internal_req: int) -> int: @@ -173,8 +210,8 @@ class JubeatSaucerFulfill( silver_values = [0] * 3 gold_values = [0] * 3 slot = 0 - for req in course['requirements']: - req_values = course['requirements'][req] + for req in course["requirements"]: + req_values = course["requirements"][req] ids[slot] = translate_req(req) bronze_values[slot] = req_values[0] @@ -182,24 +219,24 @@ class JubeatSaucerFulfill( gold_values[slot] = req_values[2] slot = slot + 1 - norma = Node.void('norma') + norma = Node.void("norma") coursenode.add_child(norma) - norma.add_child(Node.s32_array('norma_id', ids)) - norma.add_child(Node.s32_array('bronze_value', bronze_values)) - norma.add_child(Node.s32_array('silver_value', silver_values)) - norma.add_child(Node.s32_array('gold_value', gold_values)) + norma.add_child(Node.s32_array("norma_id", ids)) + norma.add_child(Node.s32_array("bronze_value", bronze_values)) + norma.add_child(Node.s32_array("silver_value", silver_values)) + norma.add_child(Node.s32_array("gold_value", gold_values)) # Music list for course music_index = 0 - music_list = Node.void('music_list') + music_list = Node.void("music_list") coursenode.add_child(music_list) - for entry in course['music']: - music = Node.void('music') - music.set_attribute('index', str(music_index)) + for entry in course["music"]: + music = Node.void("music") + music.set_attribute("index", str(music_index)) music_list.add_child(music) - music.add_child(Node.s32('music_id', entry[0])) - music.add_child(Node.u8('seq', entry[1])) + music.add_child(Node.s32("music_id", entry[0])) + music.add_child(Node.u8("seq", entry[1])) music_index = music_index + 1 # Look up profile so we can load the last course played @@ -209,13 +246,13 @@ class JubeatSaucerFulfill( profile = Profile(self.game, self.version, "", extid) # Player scores for courses - player_list = Node.void('player_list') + player_list = Node.void("player_list") data.add_child(player_list) - player = Node.void('player') + player = Node.void("player") player_list.add_child(player) - player.add_child(Node.s32('jid', extid)) + player.add_child(Node.s32("jid", extid)) - result_list = Node.void('result_list') + result_list = Node.void("result_list") player.add_child(result_list) playercourses = self.get_courses(userid) for courseid in playercourses: @@ -227,253 +264,307 @@ class JubeatSaucerFulfill( self.COURSE_RATING_BRONZE: self.GAME_COURSE_RATING_BRONZE, self.COURSE_RATING_SILVER: self.GAME_COURSE_RATING_SILVER, self.COURSE_RATING_GOLD: self.GAME_COURSE_RATING_GOLD, - }[playercourses[courseid]['rating']] - scores = playercourses[courseid]['scores'] + }[playercourses[courseid]["rating"]] + scores = playercourses[courseid]["scores"] - result = Node.void('result') + result = Node.void("result") result_list.add_child(result) - result.add_child(Node.s32('id', courseid)) - result.add_child(Node.u8('rating', rating)) - result.add_child(Node.s32_array('score', scores)) + result.add_child(Node.s32("id", courseid)) + result.add_child(Node.u8("rating", rating)) + result.add_child(Node.s32_array("score", scores)) # Last course ID - last_course_id = Node.s32('last_course_id', profile.get_dict('last').get_int('last_course_id', -1)) + last_course_id = Node.s32( + "last_course_id", profile.get_dict("last").get_int("last_course_id", -1) + ) data.add_child(last_course_id) return gametop def handle_gametop_regist_request(self, request: Node) -> Node: - data = request.child('data') - player = data.child('player') - passnode = player.child('pass') - refid = passnode.child_value('refid') - name = player.child_value('name') + data = request.child("data") + player = data.child("player") + passnode = player.child("pass") + refid = passnode.child_value("refid") + name = player.child_value("name") root = self.new_profile_by_refid(refid, name) return root def handle_gametop_get_pdata_request(self, request: Node) -> Node: - data = request.child('data') - player = data.child('player') - passnode = player.child('pass') - refid = passnode.child_value('refid') + data = request.child("data") + player = data.child("player") + passnode = player.child("pass") + refid = passnode.child_value("refid") root = self.get_profile_by_refid(refid) if root is None: - root = Node.void('gametop') - root.set_attribute('status', str(Status.NO_PROFILE)) + root = Node.void("gametop") + root.set_attribute("status", str(Status.NO_PROFILE)) return root def handle_gametop_get_mdata_request(self, request: Node) -> Node: - data = request.child('data') - player = data.child('player') - extid = player.child_value('jid') - mdata_ver = player.child_value('mdata_ver') # Game requests mdata 3 times per profile for some reason + data = request.child("data") + player = data.child("player") + extid = player.child_value("jid") + mdata_ver = player.child_value( + "mdata_ver" + ) # Game requests mdata 3 times per profile for some reason if mdata_ver != 1: - root = Node.void('gametop') - datanode = Node.void('data') + root = Node.void("gametop") + datanode = Node.void("data") root.add_child(datanode) - player = Node.void('player') + player = Node.void("player") datanode.add_child(player) - player.add_child(Node.s32('jid', extid)) - playdata = Node.void('playdata') + player.add_child(Node.s32("jid", extid)) + playdata = Node.void("playdata") player.add_child(playdata) - playdata.set_attribute('count', '0') + playdata.set_attribute("count", "0") return root root = self.get_scores_by_extid(extid) if root is None: - root = Node.void('gametop') - root.set_attribute('status', str(Status.NO_PROFILE)) + root = Node.void("gametop") + root.set_attribute("status", str(Status.NO_PROFILE)) return root def handle_gametop_get_rival_mdata_request(self, request: Node) -> Node: - data = request.child('data') - player = data.child('player') - extid = player.child_value('rival') + data = request.child("data") + player = data.child("player") + extid = player.child_value("rival") root = self.get_scores_by_extid(extid) if root is None: - root = Node.void('gametop') - root.set_attribute('status', str(Status.NO_PROFILE)) + root = Node.void("gametop") + root.set_attribute("status", str(Status.NO_PROFILE)) return root def format_profile(self, userid: UserID, profile: Profile) -> Node: - root = Node.void('gametop') - data = Node.void('data') + root = Node.void("gametop") + data = Node.void("data") root.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) # Player info and statistics - info = Node.void('info') + info = Node.void("info") player.add_child(info) - info.add_child(Node.s16('jubility', profile.get_int('jubility'))) - info.add_child(Node.s16('jubility_yday', profile.get_int('jubility_yday'))) - info.add_child(Node.s32('tune_cnt', profile.get_int('tune_cnt'))) - info.add_child(Node.s32('save_cnt', profile.get_int('save_cnt'))) - info.add_child(Node.s32('saved_cnt', profile.get_int('saved_cnt'))) - info.add_child(Node.s32('fc_cnt', profile.get_int('fc_cnt'))) - info.add_child(Node.s32('ex_cnt', profile.get_int('ex_cnt'))) - info.add_child(Node.s32('pf_cnt', profile.get_int('pf_cnt'))) - info.add_child(Node.s32('clear_cnt', profile.get_int('clear_cnt'))) - info.add_child(Node.s32('match_cnt', profile.get_int('match_cnt'))) - info.add_child(Node.s32('beat_cnt', profile.get_int('beat_cnt'))) - info.add_child(Node.s32('mynews_cnt', profile.get_int('mynews_cnt'))) - info.add_child(Node.s32('extra_point', profile.get_int('extra_point'))) - info.add_child(Node.bool('is_extra_played', profile.get_bool('is_extra_played'))) - if 'total_best_score' in profile: - info.add_child(Node.s32('total_best_score', profile.get_int('total_best_score'))) + info.add_child(Node.s16("jubility", profile.get_int("jubility"))) + info.add_child(Node.s16("jubility_yday", profile.get_int("jubility_yday"))) + info.add_child(Node.s32("tune_cnt", profile.get_int("tune_cnt"))) + info.add_child(Node.s32("save_cnt", profile.get_int("save_cnt"))) + info.add_child(Node.s32("saved_cnt", profile.get_int("saved_cnt"))) + info.add_child(Node.s32("fc_cnt", profile.get_int("fc_cnt"))) + info.add_child(Node.s32("ex_cnt", profile.get_int("ex_cnt"))) + info.add_child(Node.s32("pf_cnt", profile.get_int("pf_cnt"))) + info.add_child(Node.s32("clear_cnt", profile.get_int("clear_cnt"))) + info.add_child(Node.s32("match_cnt", profile.get_int("match_cnt"))) + info.add_child(Node.s32("beat_cnt", profile.get_int("beat_cnt"))) + info.add_child(Node.s32("mynews_cnt", profile.get_int("mynews_cnt"))) + info.add_child(Node.s32("extra_point", profile.get_int("extra_point"))) + info.add_child( + Node.bool("is_extra_played", profile.get_bool("is_extra_played")) + ) + if "total_best_score" in profile: + info.add_child( + Node.s32("total_best_score", profile.get_int("total_best_score")) + ) # Looks to be set to true when there's an old profile, stops tutorial from # happening on first load. - info.add_child(Node.bool('inherit', profile.get_bool('has_old_version') and not profile.get_bool('saved'))) + info.add_child( + Node.bool( + "inherit", + profile.get_bool("has_old_version") and not profile.get_bool("saved"), + ) + ) # Not saved, but loaded - info.add_child(Node.s32('mtg_entry_cnt', 123)) - info.add_child(Node.s32('mtg_hold_cnt', 456)) - info.add_child(Node.u8('mtg_result', 10)) + info.add_child(Node.s32("mtg_entry_cnt", 123)) + info.add_child(Node.s32("mtg_hold_cnt", 456)) + info.add_child(Node.u8("mtg_result", 10)) # First play stuff we don't support - free_first_play = Node.void('free_first_play') + free_first_play = Node.void("free_first_play") player.add_child(free_first_play) - free_first_play.add_child(Node.bool('is_available', False)) - free_first_play.add_child(Node.s32('point', 0)) - free_first_play.add_child(Node.s32('point_used', 0)) + free_first_play.add_child(Node.bool("is_available", False)) + free_first_play.add_child(Node.s32("point", 0)) + free_first_play.add_child(Node.s32("point_used", 0)) # Secret unlocks - item = Node.void('item') + item = Node.void("item") player.add_child(item) - item.add_child(Node.s32_array( - 'secret_list', - profile.get_int_array( - 'secret_list', - 32, - [-1] * 32, - ), - )) - item.add_child(Node.s32_array( - 'title_list', - profile.get_int_array( - 'title_list', - 96, - [-1] * 96, - ), - )) - item.add_child(Node.s16('theme_list', profile.get_int('theme_list', -1))) - item.add_child(Node.s32_array('marker_list', profile.get_int_array('marker_list', 2, [-1] * 2))) - item.add_child(Node.s32_array('parts_list', profile.get_int_array('parts_list', 96, [-1] * 96))) + item.add_child( + Node.s32_array( + "secret_list", + profile.get_int_array( + "secret_list", + 32, + [-1] * 32, + ), + ) + ) + item.add_child( + Node.s32_array( + "title_list", + profile.get_int_array( + "title_list", + 96, + [-1] * 96, + ), + ) + ) + item.add_child(Node.s16("theme_list", profile.get_int("theme_list", -1))) + item.add_child( + Node.s32_array( + "marker_list", profile.get_int_array("marker_list", 2, [-1] * 2) + ) + ) + item.add_child( + Node.s32_array( + "parts_list", profile.get_int_array("parts_list", 96, [-1] * 96) + ) + ) - new = Node.void('new') + new = Node.void("new") item.add_child(new) - new.add_child(Node.s32_array( - 'secret_list', - profile.get_int_array( - 'secret_list_new', - 32, - [-1] * 32, - ), - )) - new.add_child(Node.s32_array( - 'title_list', - profile.get_int_array( - 'title_list_new', - 96, - [-1] * 96, - ), - )) - new.add_child(Node.s16('theme_list', profile.get_int('theme_list_new', -1))) - new.add_child(Node.s32_array('marker_list', profile.get_int_array('marker_list_new', 2, [-1] * 2))) + new.add_child( + Node.s32_array( + "secret_list", + profile.get_int_array( + "secret_list_new", + 32, + [-1] * 32, + ), + ) + ) + new.add_child( + Node.s32_array( + "title_list", + profile.get_int_array( + "title_list_new", + 96, + [-1] * 96, + ), + ) + ) + new.add_child(Node.s16("theme_list", profile.get_int("theme_list_new", -1))) + new.add_child( + Node.s32_array( + "marker_list", profile.get_int_array("marker_list_new", 2, [-1] * 2) + ) + ) # Last played data, for showing cursor and such - lastdict = profile.get_dict('last') - last = Node.void('last') + lastdict = profile.get_dict("last") + last = Node.void("last") player.add_child(last) - last.add_child(Node.s32('music_id', lastdict.get_int('music_id'))) - last.add_child(Node.s8('marker', lastdict.get_int('marker'))) - last.add_child(Node.s16('title', lastdict.get_int('title'))) - last.add_child(Node.s8('theme', lastdict.get_int('theme'))) - last.add_child(Node.s8('sort', lastdict.get_int('sort'))) - last.add_child(Node.s8('rank_sort', lastdict.get_int('rank_sort'))) - last.add_child(Node.s8('combo_disp', lastdict.get_int('combo_disp'))) - last.add_child(Node.s8('seq_id', lastdict.get_int('seq_id'))) - last.add_child(Node.s16('parts', lastdict.get_int('parts'))) - last.add_child(Node.s8('category', lastdict.get_int('category'))) - last.add_child(Node.s64('play_time', lastdict.get_int('play_time'))) - last.add_child(Node.string('shopname', lastdict.get_str('shopname'))) - last.add_child(Node.string('areaname', lastdict.get_str('areaname'))) - last.add_child(Node.s8('expert_option', lastdict.get_int('expert_option'))) - last.add_child(Node.s8('matching', lastdict.get_int('matching'))) - last.add_child(Node.s8('hazard', lastdict.get_int('hazard'))) - last.add_child(Node.s8('hard', lastdict.get_int('hard'))) + last.add_child(Node.s32("music_id", lastdict.get_int("music_id"))) + last.add_child(Node.s8("marker", lastdict.get_int("marker"))) + last.add_child(Node.s16("title", lastdict.get_int("title"))) + last.add_child(Node.s8("theme", lastdict.get_int("theme"))) + last.add_child(Node.s8("sort", lastdict.get_int("sort"))) + last.add_child(Node.s8("rank_sort", lastdict.get_int("rank_sort"))) + last.add_child(Node.s8("combo_disp", lastdict.get_int("combo_disp"))) + last.add_child(Node.s8("seq_id", lastdict.get_int("seq_id"))) + last.add_child(Node.s16("parts", lastdict.get_int("parts"))) + last.add_child(Node.s8("category", lastdict.get_int("category"))) + last.add_child(Node.s64("play_time", lastdict.get_int("play_time"))) + last.add_child(Node.string("shopname", lastdict.get_str("shopname"))) + last.add_child(Node.string("areaname", lastdict.get_str("areaname"))) + last.add_child(Node.s8("expert_option", lastdict.get_int("expert_option"))) + last.add_child(Node.s8("matching", lastdict.get_int("matching"))) + last.add_child(Node.s8("hazard", lastdict.get_int("hazard"))) + last.add_child(Node.s8("hard", lastdict.get_int("hard"))) # Miscelaneous crap - player.add_child(Node.s32('session_id', 1)) - player.add_child(Node.u64('event_flag', 0)) + player.add_child(Node.s32("session_id", 1)) + player.add_child(Node.u64("event_flag", 0)) # Macchiato event - macchiatodict = profile.get_dict('macchiato') - macchiato = Node.void('macchiato') + macchiatodict = profile.get_dict("macchiato") + macchiato = Node.void("macchiato") player.add_child(macchiato) - macchiato.add_child(Node.s32('pack_id', macchiatodict.get_int('pack_id'))) - macchiato.add_child(Node.u16('bean_num', macchiatodict.get_int('bean_num'))) - macchiato.add_child(Node.s32('daily_milk_num', macchiatodict.get_int('daily_milk_num'))) - macchiato.add_child(Node.bool('is_received_daily_milk', macchiatodict.get_bool('is_received_daily_milk'))) - macchiato.add_child(Node.s32('today_tune_cnt', macchiatodict.get_int('today_tune_cnt'))) - macchiato.add_child(Node.s32_array( - 'daily_milk_bonus', - macchiatodict.get_int_array('daily_milk_bonus', 9, [-1, -1, -1, -1, -1, -1, -1, -1, -1]), - )) - macchiato.add_child(Node.s32('daily_play_burst', macchiatodict.get_int('daily_play_burst'))) - macchiato.add_child(Node.bool('sub_menu_is_completed', macchiatodict.get_bool('sub_menu_is_completed'))) - macchiato.add_child(Node.s32('compensation_milk', macchiatodict.get_int('compensation_milk'))) - macchiato.add_child(Node.s32('match_cnt', macchiatodict.get_int('match_cnt'))) + macchiato.add_child(Node.s32("pack_id", macchiatodict.get_int("pack_id"))) + macchiato.add_child(Node.u16("bean_num", macchiatodict.get_int("bean_num"))) + macchiato.add_child( + Node.s32("daily_milk_num", macchiatodict.get_int("daily_milk_num")) + ) + macchiato.add_child( + Node.bool( + "is_received_daily_milk", + macchiatodict.get_bool("is_received_daily_milk"), + ) + ) + macchiato.add_child( + Node.s32("today_tune_cnt", macchiatodict.get_int("today_tune_cnt")) + ) + macchiato.add_child( + Node.s32_array( + "daily_milk_bonus", + macchiatodict.get_int_array( + "daily_milk_bonus", 9, [-1, -1, -1, -1, -1, -1, -1, -1, -1] + ), + ) + ) + macchiato.add_child( + Node.s32("daily_play_burst", macchiatodict.get_int("daily_play_burst")) + ) + macchiato.add_child( + Node.bool( + "sub_menu_is_completed", macchiatodict.get_bool("sub_menu_is_completed") + ) + ) + macchiato.add_child( + Node.s32("compensation_milk", macchiatodict.get_int("compensation_milk")) + ) + macchiato.add_child(Node.s32("match_cnt", macchiatodict.get_int("match_cnt"))) # Probably never will support this - macchiato_music_list = Node.void('macchiato_music_list') + macchiato_music_list = Node.void("macchiato_music_list") macchiato.add_child(macchiato_music_list) - macchiato_music_list.set_attribute('count', '0') + macchiato_music_list.set_attribute("count", "0") # Same with this comment - macchiato.add_child(Node.s32('sub_pack_id', 0)) - sub_macchiato_music_list = Node.void('sub_macchiato_music_list') + macchiato.add_child(Node.s32("sub_pack_id", 0)) + sub_macchiato_music_list = Node.void("sub_macchiato_music_list") macchiato.add_child(sub_macchiato_music_list) - sub_macchiato_music_list.set_attribute('count', '0') + sub_macchiato_music_list.set_attribute("count", "0") # And this - season_music_list = Node.void('season_music_list') + season_music_list = Node.void("season_music_list") macchiato.add_child(season_music_list) - season_music_list.set_attribute('count', '0') + season_music_list.set_attribute("count", "0") # Weird, this is sent as a void with a bunch of subnodes, but returned as an int array. - achievement_list = Node.void('achievement_list') + achievement_list = Node.void("achievement_list") macchiato.add_child(achievement_list) - achievement_list.set_attribute('count', '0') + achievement_list.set_attribute("count", "0") # Also probably never supporting this either - cow_list = Node.void('cow_list') + cow_list = Node.void("cow_list") macchiato.add_child(cow_list) - cow_list.set_attribute('count', '0') + cow_list.set_attribute("count", "0") # No news, ever. - news = Node.void('news') + news = Node.void("news") player.add_child(news) - news.add_child(Node.s16('checked', 0)) + news.add_child(Node.s16("checked", 0)) # Add rivals to profile. - rivallist = Node.void('rivallist') + rivallist = Node.void("rivallist") player.add_child(rivallist) links = self.data.local.user.get_links(self.game, self.version, userid) rivalcount = 0 for link in links: - if link.type != 'rival': + if link.type != "rival": continue rprofile = self.get_profile(link.other_userid) if rprofile is None: continue - rival = Node.void('rival') + rival = Node.void("rival") rivallist.add_child(rival) - rival.add_child(Node.s32('jid', rprofile.extid)) - rival.add_child(Node.string('name', rprofile.get_str('name'))) + rival.add_child(Node.s32("jid", rprofile.extid)) + rival.add_child(Node.string("name", rprofile.get_str("name"))) # Lazy way of keeping track of rivals, since we can only have 4 # or the game with throw up. At least, I think Fulfill can have @@ -484,230 +575,341 @@ class JubeatSaucerFulfill( if rivalcount >= 3: break - rivallist.set_attribute('count', str(rivalcount)) + rivallist.set_attribute("count", str(rivalcount)) # Full combo daily challenge. - entry = self.data.local.game.get_time_sensitive_settings(self.game, self.version, 'fc_challenge') + entry = self.data.local.game.get_time_sensitive_settings( + self.game, self.version, "fc_challenge" + ) if entry is None: entry = ValidatedDict() # Figure out if we've played these songs - start_time, end_time = self.data.local.network.get_schedule_duration('daily') - today_attempts = self.data.local.music.get_all_attempts(self.game, self.music_version, userid, entry.get_int('today', -1), timelimit=start_time) - whim_attempts = self.data.local.music.get_all_attempts(self.game, self.music_version, userid, entry.get_int('whim', -1), timelimit=start_time) + start_time, end_time = self.data.local.network.get_schedule_duration("daily") + today_attempts = self.data.local.music.get_all_attempts( + self.game, + self.music_version, + userid, + entry.get_int("today", -1), + timelimit=start_time, + ) + whim_attempts = self.data.local.music.get_all_attempts( + self.game, + self.music_version, + userid, + entry.get_int("whim", -1), + timelimit=start_time, + ) - challenge = Node.void('challenge') + challenge = Node.void("challenge") player.add_child(challenge) - today = Node.void('today') + today = Node.void("today") challenge.add_child(today) - today.add_child(Node.s32('music_id', entry.get_int('today', -1))) - today.add_child(Node.u8('state', 0x40 if len(today_attempts) > 0 else 0x0)) - whim = Node.void('whim') + today.add_child(Node.s32("music_id", entry.get_int("today", -1))) + today.add_child(Node.u8("state", 0x40 if len(today_attempts) > 0 else 0x0)) + whim = Node.void("whim") challenge.add_child(whim) - whim.add_child(Node.s32('music_id', entry.get_int('whim', -1))) - whim.add_child(Node.u8('state', 0x40 if len(whim_attempts) > 0 else 0x0)) + whim.add_child(Node.s32("music_id", entry.get_int("whim", -1))) + whim.add_child(Node.u8("state", 0x40 if len(whim_attempts) > 0 else 0x0)) # Sane defaults for unknown nodes - only_now_music = Node.void('only_now_music') + only_now_music = Node.void("only_now_music") player.add_child(only_now_music) - only_now_music.set_attribute('count', '0') - lab_edit_seq = Node.void('lab_edit_seq') + only_now_music.set_attribute("count", "0") + lab_edit_seq = Node.void("lab_edit_seq") player.add_child(lab_edit_seq) - lab_edit_seq.set_attribute('count', '0') - kac_music = Node.void('kac_music') + lab_edit_seq.set_attribute("count", "0") + kac_music = Node.void("kac_music") player.add_child(kac_music) - kac_music.set_attribute('count', '0') - history = Node.void('history') + kac_music.set_attribute("count", "0") + history = Node.void("history") player.add_child(history) - history.set_attribute('count', '0') - share_music = Node.void('share_music') + history.set_attribute("count", "0") + share_music = Node.void("share_music") player.add_child(share_music) - share_music.set_attribute('count', '0') - bonus_music = Node.void('bonus_music') + share_music.set_attribute("count", "0") + bonus_music = Node.void("bonus_music") player.add_child(bonus_music) - bonus_music.set_attribute('count', '0') + bonus_music.set_attribute("count", "0") - bingo = Node.void('bingo') + bingo = Node.void("bingo") player.add_child(bingo) - reward = Node.void('reward') + reward = Node.void("reward") bingo.add_child(reward) - reward.add_child(Node.s32('total', 0)) - reward.add_child(Node.s32('point', 0)) - group = Node.void('group') + reward.add_child(Node.s32("total", 0)) + reward.add_child(Node.s32("point", 0)) + group = Node.void("group") player.add_child(group) - group.add_child(Node.s32('group_id', 0)) + group.add_child(Node.s32("group_id", 0)) # Basic profile info - player.add_child(Node.string('name', profile.get_str('name', 'なし'))) - player.add_child(Node.s32('jid', profile.extid)) - player.add_child(Node.string('refid', profile.refid)) + player.add_child(Node.string("name", profile.get_str("name", "なし"))) + player.add_child(Node.s32("jid", profile.extid)) + player.add_child(Node.string("refid", profile.refid)) # Miscelaneous history stuff - data.add_child(Node.u8('termver', 16)) - data.add_child(Node.u32('season_etime', 0)) - data.add_child(Node.s32_array( - 'white_music_list', - [ - -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, -1, -1, -1, -1, -1, -1, -1, - ], - )) - data.add_child(Node.s32_array( - 'open_music_list', - [ - -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, -1, -1, -1, -1, -1, -1, -1, - ], - )) + data.add_child(Node.u8("termver", 16)) + data.add_child(Node.u32("season_etime", 0)) + data.add_child( + Node.s32_array( + "white_music_list", + [ + -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, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], + ) + ) + data.add_child( + Node.s32_array( + "open_music_list", + [ + -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, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], + ) + ) # Unsupported collaboration events with other games - collabo_info = Node.void('collabo_info') + collabo_info = Node.void("collabo_info") data.add_child(collabo_info) # Unsupported policy break stuff - policy_break = Node.void('policy_break') + policy_break = Node.void("policy_break") collabo_info.add_child(policy_break) - policy_break.set_attribute('type', '1') - policy_break.add_child(Node.u8('state', 1)) - policy_break.add_child(Node.bool('is_report_end', False)) + policy_break.set_attribute("type", "1") + policy_break.add_child(Node.u8("state", 1)) + policy_break.add_child(Node.bool("is_report_end", False)) # Unsupported vocaloid stuff - vocaloid_event = Node.void('vocaloid_event') + vocaloid_event = Node.void("vocaloid_event") collabo_info.add_child(vocaloid_event) - vocaloid_event.set_attribute('type', '1') - vocaloid_event.add_child(Node.u8('state', 0)) - vocaloid_event.add_child(Node.s32('music_id', 0)) + vocaloid_event.set_attribute("type", "1") + vocaloid_event.add_child(Node.u8("state", 0)) + vocaloid_event.add_child(Node.s32("music_id", 0)) # Unsupported vocaloid stuff - vocaloid_event2 = Node.void('vocaloid_event2') + vocaloid_event2 = Node.void("vocaloid_event2") collabo_info.add_child(vocaloid_event2) - vocaloid_event2.set_attribute('type', '1') - vocaloid_event2.add_child(Node.u8('state', 0)) - vocaloid_event2.add_child(Node.s32('music_id', 0)) + vocaloid_event2.set_attribute("type", "1") + vocaloid_event2.add_child(Node.u8("state", 0)) + vocaloid_event2.add_child(Node.s32("music_id", 0)) # Maybe it is possible to turn off internet matching here? - lab = Node.void('lab') + lab = Node.void("lab") data.add_child(lab) - lab.add_child(Node.bool('is_open', False)) - matching_off = Node.void('matching_off') + lab.add_child(Node.bool("is_open", False)) + matching_off = Node.void("matching_off") data.add_child(matching_off) - matching_off.add_child(Node.bool('is_open', True)) + matching_off.add_child(Node.bool("is_open", True)) return root - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() - newprofile.replace_bool('saved', True) - data = request.child('data') + newprofile.replace_bool("saved", True) + data = request.child("data") # Grab player information - player = data.child('player') + player = data.child("player") # Grab last information. Lots of this will be filled in while grabbing scores - last = newprofile.get_dict('last') - last.replace_int('play_time', player.child_value('time_gameend')) - last.replace_str('shopname', player.child_value('shopname')) - last.replace_str('areaname', player.child_value('areaname')) + last = newprofile.get_dict("last") + last.replace_int("play_time", player.child_value("time_gameend")) + last.replace_str("shopname", player.child_value("shopname")) + last.replace_str("areaname", player.child_value("areaname")) # Grab player info for echoing back - info = player.child('info') + info = player.child("info") if info is not None: - newprofile.replace_int('jubility', info.child_value('jubility')) - newprofile.replace_int('jubility_yday', info.child_value('jubility_yday')) - newprofile.replace_int('tune_cnt', info.child_value('tune_cnt')) - newprofile.replace_int('save_cnt', info.child_value('save_cnt')) - newprofile.replace_int('saved_cnt', info.child_value('saved_cnt')) - newprofile.replace_int('fc_cnt', info.child_value('fc_cnt')) - newprofile.replace_int('ex_cnt', info.child_value('exc_cnt')) # Not a mistake, Jubeat is weird - newprofile.replace_int('pf_cnt', info.child_value('pf_cnt')) - newprofile.replace_int('clear_cnt', info.child_value('clear_cnt')) - newprofile.replace_int('match_cnt', info.child_value('match_cnt')) - newprofile.replace_int('beat_cnt', info.child_value('beat_cnt')) - newprofile.replace_int('total_best_score', info.child_value('total_best_score')) - newprofile.replace_int('mynews_cnt', info.child_value('mynews_cnt')) - newprofile.replace_int('extra_point', info.child_value('extra_point')) - newprofile.replace_bool('is_extra_played', info.child_value('is_extra_played')) + newprofile.replace_int("jubility", info.child_value("jubility")) + newprofile.replace_int("jubility_yday", info.child_value("jubility_yday")) + newprofile.replace_int("tune_cnt", info.child_value("tune_cnt")) + newprofile.replace_int("save_cnt", info.child_value("save_cnt")) + newprofile.replace_int("saved_cnt", info.child_value("saved_cnt")) + newprofile.replace_int("fc_cnt", info.child_value("fc_cnt")) + newprofile.replace_int( + "ex_cnt", info.child_value("exc_cnt") + ) # Not a mistake, Jubeat is weird + newprofile.replace_int("pf_cnt", info.child_value("pf_cnt")) + newprofile.replace_int("clear_cnt", info.child_value("clear_cnt")) + newprofile.replace_int("match_cnt", info.child_value("match_cnt")) + newprofile.replace_int("beat_cnt", info.child_value("beat_cnt")) + newprofile.replace_int( + "total_best_score", info.child_value("total_best_score") + ) + newprofile.replace_int("mynews_cnt", info.child_value("mynews_cnt")) + newprofile.replace_int("extra_point", info.child_value("extra_point")) + newprofile.replace_bool( + "is_extra_played", info.child_value("is_extra_played") + ) - last.replace_int('expert_option', info.child_value('expert_option')) - last.replace_int('matching', info.child_value('matching')) - last.replace_int('hazard', info.child_value('hazard')) - last.replace_int('hard', info.child_value('hard')) + last.replace_int("expert_option", info.child_value("expert_option")) + last.replace_int("matching", info.child_value("matching")) + last.replace_int("hazard", info.child_value("hazard")) + last.replace_int("hard", info.child_value("hard")) # Grab unlock progress - item = player.child('item') + item = player.child("item") if item is not None: - newprofile.replace_int_array('secret_list', 32, item.child_value('secret_list')) - newprofile.replace_int_array('title_list', 96, item.child_value('title_list')) - newprofile.replace_int('theme_list', item.child_value('theme_list')) - newprofile.replace_int_array('marker_list', 2, item.child_value('marker_list')) - newprofile.replace_int_array('parts_list', 96, item.child_value('parts_list')) - newprofile.replace_int_array('secret_list_new', 32, item.child_value('secret_new')) - newprofile.replace_int_array('title_list_new', 96, item.child_value('title_new')) - newprofile.replace_int('theme_list_new', item.child_value('theme_new')) - newprofile.replace_int_array('marker_list_new', 2, item.child_value('marker_new')) + newprofile.replace_int_array( + "secret_list", 32, item.child_value("secret_list") + ) + newprofile.replace_int_array( + "title_list", 96, item.child_value("title_list") + ) + newprofile.replace_int("theme_list", item.child_value("theme_list")) + newprofile.replace_int_array( + "marker_list", 2, item.child_value("marker_list") + ) + newprofile.replace_int_array( + "parts_list", 96, item.child_value("parts_list") + ) + newprofile.replace_int_array( + "secret_list_new", 32, item.child_value("secret_new") + ) + newprofile.replace_int_array( + "title_list_new", 96, item.child_value("title_new") + ) + newprofile.replace_int("theme_list_new", item.child_value("theme_new")) + newprofile.replace_int_array( + "marker_list_new", 2, item.child_value("marker_new") + ) # Grab macchiato event - macchiatodict = newprofile.get_dict('macchiato') - macchiato = player.child('macchiato') + macchiatodict = newprofile.get_dict("macchiato") + macchiato = player.child("macchiato") if macchiato is not None: - macchiatodict.replace_int('pack_id', macchiato.child_value('pack_id')) - macchiatodict.replace_int('bean_num', macchiato.child_value('bean_num')) - macchiatodict.replace_int('daily_milk_num', macchiato.child_value('daily_milk_num')) - macchiatodict.replace_bool('is_received_daily_milk', macchiato.child_value('is_received_daily_milk')) - macchiatodict.replace_bool('sub_menu_is_completed', macchiato.child_value('sub_menu_is_completed')) - macchiatodict.replace_int('today_tune_cnt', macchiato.child_value('today_tune_cnt')) - macchiatodict.replace_int_array('daily_milk_bonus', 9, macchiato.child_value('daily_milk_bonus')) - macchiatodict.replace_int('compensation_milk', macchiato.child_value('compensation_milk')) - macchiatodict.replace_int('match_cnt', macchiato.child_value('match_cnt')) - macchiatodict.replace_int('used_bean', macchiato.child_value('used_bean')) - macchiatodict.replace_int('used_milk', macchiato.child_value('used_milk')) - macchiatodict.replace_int('daily_play_burst', macchiato.child_value('daily_play_burst')) - newprofile.replace_dict('macchiato', macchiatodict) + macchiatodict.replace_int("pack_id", macchiato.child_value("pack_id")) + macchiatodict.replace_int("bean_num", macchiato.child_value("bean_num")) + macchiatodict.replace_int( + "daily_milk_num", macchiato.child_value("daily_milk_num") + ) + macchiatodict.replace_bool( + "is_received_daily_milk", + macchiato.child_value("is_received_daily_milk"), + ) + macchiatodict.replace_bool( + "sub_menu_is_completed", macchiato.child_value("sub_menu_is_completed") + ) + macchiatodict.replace_int( + "today_tune_cnt", macchiato.child_value("today_tune_cnt") + ) + macchiatodict.replace_int_array( + "daily_milk_bonus", 9, macchiato.child_value("daily_milk_bonus") + ) + macchiatodict.replace_int( + "compensation_milk", macchiato.child_value("compensation_milk") + ) + macchiatodict.replace_int("match_cnt", macchiato.child_value("match_cnt")) + macchiatodict.replace_int("used_bean", macchiato.child_value("used_bean")) + macchiatodict.replace_int("used_milk", macchiato.child_value("used_milk")) + macchiatodict.replace_int( + "daily_play_burst", macchiato.child_value("daily_play_burst") + ) + newprofile.replace_dict("macchiato", macchiatodict) # Get timestamps for played songs timestamps: Dict[int, int] = {} - history = player.child('history') + history = player.child("history") if history is not None: for tune in history.children: - if tune.name != 'tune': + if tune.name != "tune": continue - entry = int(tune.attribute('log_id')) - ts = int(tune.child_value('timestamp') / 1000) + entry = int(tune.attribute("log_id")) + ts = int(tune.child_value("timestamp") / 1000) timestamps[entry] = ts # Grab scores and save those - result = data.child('result') + result = data.child("result") if result is not None: for tune in result.children: - if tune.name != 'tune': + if tune.name != "tune": continue - result = tune.child('player') + result = tune.child("player") - last.replace_int('marker', tune.child_value('marker')) - last.replace_int('title', tune.child_value('title')) - last.replace_int('parts', tune.child_value('parts')) - last.replace_int('theme', tune.child_value('theme')) - last.replace_int('sort', tune.child_value('sort')) - last.replace_int('category', tune.child_value('category')) - last.replace_int('rank_sort', tune.child_value('rank_sort')) - last.replace_int('combo_disp', tune.child_value('combo_disp')) + last.replace_int("marker", tune.child_value("marker")) + last.replace_int("title", tune.child_value("title")) + last.replace_int("parts", tune.child_value("parts")) + last.replace_int("theme", tune.child_value("theme")) + last.replace_int("sort", tune.child_value("sort")) + last.replace_int("category", tune.child_value("category")) + last.replace_int("rank_sort", tune.child_value("rank_sort")) + last.replace_int("combo_disp", tune.child_value("combo_disp")) - songid = tune.child_value('music') - entry = int(tune.attribute('id')) + songid = tune.child_value("music") + entry = int(tune.attribute("id")) timestamp = timestamps.get(entry, Time.now()) - chart = int(result.child('score').attribute('seq')) - points = result.child_value('score') - flags = int(result.child('score').attribute('clear')) - combo = int(result.child('score').attribute('combo')) - ghost = result.child_value('mbar') + chart = int(result.child("score").attribute("seq")) + points = result.child_value("score") + flags = int(result.child("score").attribute("clear")) + combo = int(result.child("score").attribute("combo")) + ghost = result.child_value("mbar") # Miscelaneous last data for echoing to profile get - last.replace_int('music_id', songid) - last.replace_int('seq_id', chart) + last.replace_int("music_id", songid) + last.replace_int("seq_id", chart) mapping = { self.GAME_FLAG_BIT_CLEARED: self.PLAY_MEDAL_CLEARED, @@ -723,118 +925,138 @@ class JubeatSaucerFulfill( if flags & bit > 0: medal = max(medal, mapping[bit]) - self.update_score(userid, timestamp, songid, chart, points, medal, combo, ghost) + self.update_score( + userid, timestamp, songid, chart, points, medal, combo, ghost + ) # Grab the course results as well - course = data.child('course') + course = data.child("course") if course is not None: - courseid = course.child_value('course_id') + courseid = course.child_value("course_id") rating = { self.GAME_COURSE_RATING_FAILED: self.COURSE_RATING_FAILED, self.GAME_COURSE_RATING_BRONZE: self.COURSE_RATING_BRONZE, self.GAME_COURSE_RATING_SILVER: self.COURSE_RATING_SILVER, self.GAME_COURSE_RATING_GOLD: self.COURSE_RATING_GOLD, - }[course.child_value('rating')] + }[course.child_value("rating")] scores = [0] * 5 for music in course.children: - if music.name != 'music': + if music.name != "music": continue - index = int(music.attribute('index')) - scores[index] = music.child_value('score') + index = int(music.attribute("index")) + scores[index] = music.child_value("score") # Save course itself self.save_course(userid, courseid, rating, scores) # Save the last course ID - last.replace_int('last_course_id', courseid) + last.replace_int("last_course_id", courseid) # Save back last information gleaned from results - newprofile.replace_dict('last', last) + newprofile.replace_dict("last", last) # Keep track of play statistics self.update_play_statistics(userid) return newprofile - def format_scores(self, userid: UserID, profile: Profile, scores: List[Score]) -> Node: + def format_scores( + self, userid: UserID, profile: Profile, scores: List[Score] + ) -> Node: - root = Node.void('gametop') - datanode = Node.void('data') + root = Node.void("gametop") + datanode = Node.void("data") root.add_child(datanode) - player = Node.void('player') + player = Node.void("player") datanode.add_child(player) - player.add_child(Node.s32('jid', profile.extid)) - playdata = Node.void('playdata') + player.add_child(Node.s32("jid", profile.extid)) + playdata = Node.void("playdata") player.add_child(playdata) - playdata.set_attribute('count', str(len(scores))) + playdata.set_attribute("count", str(len(scores))) music = ValidatedDict() for score in scores: # Ignore festo-and-above chart types. - if score.chart not in {self.CHART_TYPE_BASIC, self.CHART_TYPE_ADVANCED, self.CHART_TYPE_EXTREME}: + if score.chart not in { + self.CHART_TYPE_BASIC, + self.CHART_TYPE_ADVANCED, + self.CHART_TYPE_EXTREME, + }: continue data = music.get_dict(str(score.id)) - play_cnt = data.get_int_array('play_cnt', 3) - clear_cnt = data.get_int_array('clear_cnt', 3) - clear_flags = data.get_int_array('clear_flags', 3) - fc_cnt = data.get_int_array('fc_cnt', 3) - ex_cnt = data.get_int_array('ex_cnt', 3) - points = data.get_int_array('points', 3) + play_cnt = data.get_int_array("play_cnt", 3) + clear_cnt = data.get_int_array("clear_cnt", 3) + clear_flags = data.get_int_array("clear_flags", 3) + fc_cnt = data.get_int_array("fc_cnt", 3) + ex_cnt = data.get_int_array("ex_cnt", 3) + points = data.get_int_array("points", 3) # Replace data for this chart type play_cnt[score.chart] = score.plays - clear_cnt[score.chart] = score.data.get_int('clear_count') - fc_cnt[score.chart] = score.data.get_int('full_combo_count') - ex_cnt[score.chart] = score.data.get_int('excellent_count') + clear_cnt[score.chart] = score.data.get_int("clear_count") + fc_cnt[score.chart] = score.data.get_int("full_combo_count") + ex_cnt[score.chart] = score.data.get_int("excellent_count") points[score.chart] = score.points # Format the clear flags clear_flags[score.chart] = self.GAME_FLAG_BIT_PLAYED - if score.data.get_int('clear_count') > 0: + if score.data.get_int("clear_count") > 0: clear_flags[score.chart] |= self.GAME_FLAG_BIT_CLEARED - if score.data.get_int('full_combo_count') > 0: + if score.data.get_int("full_combo_count") > 0: clear_flags[score.chart] |= self.GAME_FLAG_BIT_FULL_COMBO - if score.data.get_int('excellent_count') > 0: + if score.data.get_int("excellent_count") > 0: clear_flags[score.chart] |= self.GAME_FLAG_BIT_EXCELLENT # Save chart data back - data.replace_int_array('play_cnt', 3, play_cnt) - data.replace_int_array('clear_cnt', 3, clear_cnt) - data.replace_int_array('clear_flags', 3, clear_flags) - data.replace_int_array('fc_cnt', 3, fc_cnt) - data.replace_int_array('ex_cnt', 3, ex_cnt) - data.replace_int_array('points', 3, points) + data.replace_int_array("play_cnt", 3, play_cnt) + data.replace_int_array("clear_cnt", 3, clear_cnt) + data.replace_int_array("clear_flags", 3, clear_flags) + data.replace_int_array("fc_cnt", 3, fc_cnt) + data.replace_int_array("ex_cnt", 3, ex_cnt) + data.replace_int_array("points", 3, points) # Update the ghost (untyped) - ghost = data.get('ghost', [None, None, None]) - ghost[score.chart] = score.data.get('ghost') - data['ghost'] = ghost + ghost = data.get("ghost", [None, None, None]) + ghost[score.chart] = score.data.get("ghost") + data["ghost"] = ghost # Save it back music.replace_dict(str(score.id), data) for scoreid in music: scoredata = music[scoreid] - musicdata = Node.void('musicdata') + musicdata = Node.void("musicdata") playdata.add_child(musicdata) - musicdata.set_attribute('music_id', scoreid) - musicdata.add_child(Node.s32_array('play_cnt', scoredata.get_int_array('play_cnt', 3))) - musicdata.add_child(Node.s32_array('clear_cnt', scoredata.get_int_array('clear_cnt', 3))) - musicdata.add_child(Node.s32_array('fc_cnt', scoredata.get_int_array('fc_cnt', 3))) - musicdata.add_child(Node.s32_array('ex_cnt', scoredata.get_int_array('ex_cnt', 3))) - musicdata.add_child(Node.s32_array('score', scoredata.get_int_array('points', 3))) - musicdata.add_child(Node.s8_array('clear', scoredata.get_int_array('clear_flags', 3))) + musicdata.set_attribute("music_id", scoreid) + musicdata.add_child( + Node.s32_array("play_cnt", scoredata.get_int_array("play_cnt", 3)) + ) + musicdata.add_child( + Node.s32_array("clear_cnt", scoredata.get_int_array("clear_cnt", 3)) + ) + musicdata.add_child( + Node.s32_array("fc_cnt", scoredata.get_int_array("fc_cnt", 3)) + ) + musicdata.add_child( + Node.s32_array("ex_cnt", scoredata.get_int_array("ex_cnt", 3)) + ) + musicdata.add_child( + Node.s32_array("score", scoredata.get_int_array("points", 3)) + ) + musicdata.add_child( + Node.s8_array("clear", scoredata.get_int_array("clear_flags", 3)) + ) - ghosts = scoredata.get('ghost', [None, None, None]) + ghosts = scoredata.get("ghost", [None, None, None]) for i in range(len(ghosts)): ghost = ghosts[i] if ghost is None: continue - bar = Node.u8_array('bar', ghost) + bar = Node.u8_array("bar", ghost) musicdata.add_child(bar) - bar.set_attribute('seq', str(i)) + bar.set_attribute("seq", str(i)) return root diff --git a/bemani/backend/jubeat/stubs.py b/bemani/backend/jubeat/stubs.py index aff5ec6..ae5aea3 100644 --- a/bemani/backend/jubeat/stubs.py +++ b/bemani/backend/jubeat/stubs.py @@ -7,13 +7,13 @@ from bemani.common import VersionConstants class Jubeat(JubeatBase): - name: str = 'Jubeat' + name: str = "Jubeat" version: int = VersionConstants.JUBEAT class JubeatRipples(JubeatBase): - name: str = 'Jubeat Ripples' + name: str = "Jubeat Ripples" version: int = VersionConstants.JUBEAT_RIPPLES def previous_version(self) -> Optional[JubeatBase]: @@ -22,7 +22,7 @@ class JubeatRipples(JubeatBase): class JubeatRipplesAppend(JubeatBase): - name: str = 'Jubeat Ripples Append' + name: str = "Jubeat Ripples Append" version: int = VersionConstants.JUBEAT_RIPPLES_APPEND def previous_version(self) -> Optional[JubeatBase]: @@ -31,7 +31,7 @@ class JubeatRipplesAppend(JubeatBase): class JubeatKnit(JubeatBase): - name: str = 'Jubeat Knit' + name: str = "Jubeat Knit" version: int = VersionConstants.JUBEAT_KNIT def previous_version(self) -> Optional[JubeatBase]: @@ -40,7 +40,7 @@ class JubeatKnit(JubeatBase): class JubeatKnitAppend(JubeatBase): - name: str = 'Jubeat Knit Append' + name: str = "Jubeat Knit Append" version: int = VersionConstants.JUBEAT_KNIT_APPEND def previous_version(self) -> Optional[JubeatBase]: @@ -49,7 +49,7 @@ class JubeatKnitAppend(JubeatBase): class JubeatCopious(JubeatBase): - name: str = 'Jubeat Copious' + name: str = "Jubeat Copious" version: int = VersionConstants.JUBEAT_COPIOUS def previous_version(self) -> Optional[JubeatBase]: @@ -58,7 +58,7 @@ class JubeatCopious(JubeatBase): class JubeatCopiousAppend(JubeatBase): - name: str = 'Jubeat Copious Append' + name: str = "Jubeat Copious Append" version: int = VersionConstants.JUBEAT_COPIOUS_APPEND def previous_version(self) -> Optional[JubeatBase]: diff --git a/bemani/backend/mga/base.py b/bemani/backend/mga/base.py index 0be3b52..0f6ab1a 100644 --- a/bemani/backend/mga/base.py +++ b/bemani/backend/mga/base.py @@ -13,7 +13,7 @@ class MetalGearArcadeBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): game: GameConstants = GameConstants.MGA - def previous_version(self) -> Optional['MetalGearArcadeBase']: + def previous_version(self) -> Optional["MetalGearArcadeBase"]: """ Returns the previous version of the game, based on this game. Should be overridden. diff --git a/bemani/backend/mga/factory.py b/bemani/backend/mga/factory.py index c704bd2..ee9dffc 100644 --- a/bemani/backend/mga/factory.py +++ b/bemani/backend/mga/factory.py @@ -14,13 +14,19 @@ class MetalGearArcadeFactory(Factory): @classmethod def register_all(cls) -> None: - for gamecode in ['I36']: + for gamecode in ["I36"]: Base.register(gamecode, MetalGearArcadeFactory) @classmethod - def create(cls, data: Data, config: Config, model: Model, parentmodel: Optional[Model]=None) -> Optional[Base]: + def create( + cls, + data: Data, + config: Config, + model: Model, + parentmodel: Optional[Model] = None, + ) -> Optional[Base]: - if model.gamecode == 'I36': + if model.gamecode == "I36": return MetalGearArcade(data, config, model) # Unknown game version diff --git a/bemani/backend/mga/mga.py b/bemani/backend/mga/mga.py index 89def3f..9aed698 100644 --- a/bemani/backend/mga/mga.py +++ b/bemani/backend/mga/mga.py @@ -19,36 +19,36 @@ class MetalGearArcade( def __update_shop_name(self, profiledata: bytes) -> None: # Figure out the profile type - csvs = profiledata.split(b',') + csvs = profiledata.split(b",") if len(csvs) < 2: # Not long enough to care about return - datatype = csvs[1].decode('ascii') - if datatype != 'PLAYDATA': + datatype = csvs[1].decode("ascii") + if datatype != "PLAYDATA": # Not the right profile type requested return # Grab the shop name try: - shopname = csvs[30].decode('shift-jis') + shopname = csvs[30].decode("shift-jis") except Exception: return self.update_machine_name(shopname) def handle_system_getmaster_request(self, request: Node) -> Node: # See if we can grab the request - data = request.child('data') + data = request.child("data") if not data: - root = Node.void('system') - root.add_child(Node.s32('result', 0)) + root = Node.void("system") + root.add_child(Node.s32("result", 0)) return root # Figure out what type of messsage this is - reqtype = data.child_value('datatype') - reqkey = data.child_value('datakey') + reqtype = data.child_value("datatype") + reqkey = data.child_value("datakey") # System message - root = Node.void('system') + root = Node.void("system") if reqtype == "S_SRVMSG" and reqkey == "INFO": # Generate system message @@ -56,23 +56,35 @@ class MetalGearArcade( settings2_str = "1,1,1,1,1,1,1,1,1,1,1,1,1,1" # Send it to the client, making sure to inform the client that it was valid. - root.add_child(Node.string('strdata1', base64.b64encode(settings1_str.encode('ascii')).decode('ascii'))) - root.add_child(Node.string('strdata2', base64.b64encode(settings2_str.encode('ascii')).decode('ascii'))) - root.add_child(Node.u64('updatedate', Time.now() * 1000)) - root.add_child(Node.s32('result', 1)) + root.add_child( + Node.string( + "strdata1", + base64.b64encode(settings1_str.encode("ascii")).decode("ascii"), + ) + ) + root.add_child( + Node.string( + "strdata2", + base64.b64encode(settings2_str.encode("ascii")).decode("ascii"), + ) + ) + root.add_child(Node.u64("updatedate", Time.now() * 1000)) + root.add_child(Node.s32("result", 1)) else: # Unknown message. - root.add_child(Node.s32('result', 0)) + root.add_child(Node.s32("result", 0)) return root def handle_playerdata_usergamedata_send_request(self, request: Node) -> Node: # Look up user by refid - refid = request.child_value('data/eaid') + refid = request.child_value("data/eaid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is None: - root = Node.void('playerdata') - root.add_child(Node.s32('result', 1)) # Unclear if this is the right thing to do here. + root = Node.void("playerdata") + root.add_child( + Node.s32("result", 1) + ) # Unclear if this is the right thing to do here. return root # Extract new profile info from old profile @@ -87,14 +99,14 @@ class MetalGearArcade( self.put_profile(userid, newprofile) # Return success! - root = Node.void('playerdata') - root.add_child(Node.s32('result', 0)) + root = Node.void("playerdata") + root.add_child(Node.s32("result", 0)) return root def handle_playerdata_usergamedata_recv_request(self, request: Node) -> Node: # Look up user by refid - refid = request.child_value('data/eaid') - profiletypes = request.child_value('data/recv_csv').split(',') + refid = request.child_value("data/eaid") + profiletypes = request.child_value("data/recv_csv").split(",") profile = None userid = None if refid is not None: @@ -104,58 +116,66 @@ class MetalGearArcade( if profile is not None: return self.format_profile(userid, profiletypes, profile) else: - root = Node.void('playerdata') - root.add_child(Node.s32('result', 1)) # Unclear if this is the right thing to do here. + root = Node.void("playerdata") + root.add_child( + Node.s32("result", 1) + ) # Unclear if this is the right thing to do here. return root - def format_profile(self, userid: UserID, profiletypes: List[str], profile: Profile) -> Node: - root = Node.void('playerdata') - root.add_child(Node.s32('result', 0)) - player = Node.void('player') + def format_profile( + self, userid: UserID, profiletypes: List[str], profile: Profile + ) -> Node: + root = Node.void("playerdata") + root.add_child(Node.s32("result", 0)) + player = Node.void("player") root.add_child(player) records = 0 - record = Node.void('record') + record = Node.void("record") player.add_child(record) for profiletype in profiletypes: if profiletype == "3fffffffff": continue - for j in range(len(profile['strdatas'])): - strdata = profile['strdatas'][j] - bindata = profile['bindatas'][j] + for j in range(len(profile["strdatas"])): + strdata = profile["strdatas"][j] + bindata = profile["bindatas"][j] # Figure out the profile type - csvs = strdata.split(b',') + csvs = strdata.split(b",") if len(csvs) < 2: # Not long enough to care about continue - datatype = csvs[1].decode('ascii') + datatype = csvs[1].decode("ascii") if datatype != profiletype: # Not the right profile type requested continue # This is a valid profile node for this type, lets return only the profile values - strdata = b','.join(csvs[2:]) - d = Node.string('d', base64.b64encode(strdata).decode('ascii')) + strdata = b",".join(csvs[2:]) + d = Node.string("d", base64.b64encode(strdata).decode("ascii")) record.add_child(d) - d.add_child(Node.string('bin1', base64.b64encode(bindata).decode('ascii'))) + d.add_child( + Node.string("bin1", base64.b64encode(bindata).decode("ascii")) + ) # Remember that we had this record records = records + 1 - player.add_child(Node.u32('record_num', records)) + player.add_child(Node.u32("record_num", records)) return root - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile, is_new: bool) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile, is_new: bool + ) -> Profile: # Profile save request, data values are base64 encoded. # d is a CSV, and bin1 is binary data. newprofile = oldprofile.clone() strdatas: List[bytes] = [] bindatas: List[bytes] = [] - record = request.child('data/record') + record = request.child("data/record") for node in record.children: - if node.name != 'd': + if node.name != "d": continue profile = base64.b64decode(node.value) @@ -165,10 +185,10 @@ class MetalGearArcade( if is_new: self.__update_shop_name(profile) strdatas.append(profile) - bindatas.append(base64.b64decode(node.child_value('bin1'))) + bindatas.append(base64.b64decode(node.child_value("bin1"))) - newprofile['strdatas'] = strdatas - newprofile['bindatas'] = bindatas + newprofile["strdatas"] = strdatas + newprofile["bindatas"] = bindatas # Keep track of play statistics across all versions self.update_play_statistics(userid) diff --git a/bemani/backend/museca/base.py b/bemani/backend/museca/base.py index d305175..17e9787 100644 --- a/bemani/backend/museca/base.py +++ b/bemani/backend/museca/base.py @@ -4,7 +4,14 @@ from typing_extensions import Final from bemani.backend.base import Base from bemani.backend.core import CoreHandler, CardManagerHandler, PASELIHandler -from bemani.common import Profile, ValidatedDict, GameConstants, DBConstants, Parallel, Model +from bemani.common import ( + Profile, + ValidatedDict, + GameConstants, + DBConstants, + Parallel, + Model, +) from bemani.data import UserID, Config, Data from bemani.protocol import Node @@ -36,7 +43,7 @@ class MusecaBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): def __init__(self, data: Data, config: Config, model: Model) -> None: super().__init__(data, config, model) - if model.rev == 'X': + if model.rev == "X": self.omnimix = True else: self.omnimix = False @@ -47,7 +54,7 @@ class MusecaBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): return DBConstants.OMNIMIX_VERSION_BUMP + self.version return self.version - def previous_version(self) -> Optional['MusecaBase']: + def previous_version(self) -> Optional["MusecaBase"]: """ Returns the previous version of the game, based on this game. Should be overridden. @@ -56,19 +63,19 @@ class MusecaBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): def game_to_db_clear_type(self, clear_type: int) -> int: # Given a game clear type, return the canonical database identifier. - raise Exception('Implement in subclass!') + raise Exception("Implement in subclass!") def db_to_game_clear_type(self, clear_type: int) -> int: # Given a database clear type, return the game's identifier. - raise Exception('Implement in subclass!') + raise Exception("Implement in subclass!") def game_to_db_grade(self, grade: int) -> int: # Given a game grade, return the canonical database identifier. - raise Exception('Implement in subclass!') + raise Exception("Implement in subclass!") def db_to_game_grade(self, grade: int) -> int: # Given a database grade, return the game's identifier. - raise Exception('Implement in subclass!') + raise Exception("Implement in subclass!") def get_profile_by_refid(self, refid: Optional[str]) -> Optional[Node]: """ @@ -88,7 +95,9 @@ class MusecaBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): # Now, return it return self.format_profile(userid, profile) - def new_profile_by_refid(self, refid: Optional[str], name: Optional[str], locid: Optional[int]) -> Node: + def new_profile_by_refid( + self, refid: Optional[str], name: Optional[str], locid: Optional[int] + ) -> Node: """ Given a RefID and an optional name, create a profile and then return a formatted profile node. Similar rationale to get_profile_by_refid. @@ -97,7 +106,7 @@ class MusecaBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): return None if name is None: - name = 'NONAME' + name = "NONAME" # First, create and save the default profile userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -107,8 +116,8 @@ class MusecaBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): refid, 0, { - 'name': name, - 'loc': locid, + "name": name, + "loc": locid, }, ) self.put_profile(userid, profile) @@ -119,9 +128,11 @@ class MusecaBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): Base handler for a profile. Given a userid and a profile dictionary, return a Node representing a profile. Should be overridden. """ - return Node.void('game') + return Node.void("game") - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: """ Base handler for profile parsing. Given a request and an old profile, return a new profile that's been updated with the contents of the request. @@ -142,16 +153,18 @@ class MusecaBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): }, } """ - all_attempts, remote_attempts = Parallel.execute([ - lambda: self.data.local.music.get_all_attempts( - game=self.game, - version=self.music_version, - ), - lambda: self.data.remote.music.get_clear_rates( - game=self.game, - version=self.music_version, - ) - ]) + all_attempts, remote_attempts = Parallel.execute( + [ + lambda: self.data.local.music.get_all_attempts( + game=self.game, + version=self.music_version, + ), + lambda: self.data.remote.music.get_clear_rates( + game=self.game, + version=self.music_version, + ), + ] + ) attempts: Dict[int, Dict[int, Dict[str, int]]] = {} for (_, attempt) in all_attempts: # Terrible temporary structure is terrible. @@ -159,19 +172,26 @@ class MusecaBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): attempts[attempt.id] = {} if attempt.chart not in attempts[attempt.id]: attempts[attempt.id][attempt.chart] = { - 'total': 0, - 'clears': 0, + "total": 0, + "clears": 0, } # We saw an attempt, keep the total attempts in sync. - attempts[attempt.id][attempt.chart]['total'] = attempts[attempt.id][attempt.chart]['total'] + 1 + attempts[attempt.id][attempt.chart]["total"] = ( + attempts[attempt.id][attempt.chart]["total"] + 1 + ) - if attempt.data.get_int('clear_type', self.CLEAR_TYPE_FAILED) != self.CLEAR_TYPE_FAILED: + if ( + attempt.data.get_int("clear_type", self.CLEAR_TYPE_FAILED) + != self.CLEAR_TYPE_FAILED + ): # This attempt was a failure, so don't count it against clears of full combos continue # It was at least a clear - attempts[attempt.id][attempt.chart]['clears'] = attempts[attempt.id][attempt.chart]['clears'] + 1 + attempts[attempt.id][attempt.chart]["clears"] = ( + attempts[attempt.id][attempt.chart]["clears"] + 1 + ) # Merge in remote attempts for songid in remote_attempts: @@ -181,12 +201,16 @@ class MusecaBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): for songchart in remote_attempts[songid]: if songchart not in attempts[songid]: attempts[songid][songchart] = { - 'total': 0, - 'clears': 0, + "total": 0, + "clears": 0, } - attempts[songid][songchart]['total'] += remote_attempts[songid][songchart]['plays'] - attempts[songid][songchart]['clears'] += remote_attempts[songid][songchart]['clears'] + attempts[songid][songchart]["total"] += remote_attempts[songid][ + songchart + ]["plays"] + attempts[songid][songchart]["clears"] += remote_attempts[songid][ + songchart + ]["clears"] return attempts @@ -199,7 +223,7 @@ class MusecaBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): clear_type: int, grade: int, combo: int, - stats: Optional[Dict[str, int]]=None, + stats: Optional[Dict[str, int]] = None, ) -> None: """ Given various pieces of a score, update the user's high score and score @@ -256,21 +280,23 @@ class MusecaBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): scoredata = oldscore.data # Replace grade and clear type - scoredata.replace_int('clear_type', max(scoredata.get_int('clear_type'), clear_type)) - history.replace_int('clear_type', clear_type) - scoredata.replace_int('grade', max(scoredata.get_int('grade'), grade)) - history.replace_int('grade', grade) + scoredata.replace_int( + "clear_type", max(scoredata.get_int("clear_type"), clear_type) + ) + history.replace_int("clear_type", clear_type) + scoredata.replace_int("grade", max(scoredata.get_int("grade"), grade)) + history.replace_int("grade", grade) # If we have a combo, replace it - scoredata.replace_int('combo', max(scoredata.get_int('combo'), combo)) - history.replace_int('combo', combo) + scoredata.replace_int("combo", max(scoredata.get_int("combo"), combo)) + history.replace_int("combo", combo) # If we have play stats, replace it if stats is not None: if raised: # We have stats, and there's a new high score, update the stats - scoredata.replace_dict('stats', stats) - history.replace_dict('stats', stats) + scoredata.replace_dict("stats", stats) + history.replace_dict("stats", stats) # Look up where this score was earned lid = self.get_machine_id() diff --git a/bemani/backend/museca/common.py b/bemani/backend/museca/common.py index ab3d987..384fa5c 100644 --- a/bemani/backend/museca/common.py +++ b/bemani/backend/museca/common.py @@ -4,138 +4,136 @@ from bemani.protocol import Node class MusecaGameShopHandler(MusecaBase): - def handle_game_3_shop_request(self, request: Node) -> Node: - self.update_machine_name(request.child_value('shopname')) + self.update_machine_name(request.child_value("shopname")) # Respond with number of milliseconds until next request - game = Node.void('game_3') - game.add_child(Node.u32('nxt_time', 1000 * 5 * 60)) + game = Node.void("game_3") + game.add_child(Node.u32("nxt_time", 1000 * 5 * 60)) return game class MusecaGameHiscoreHandler(MusecaBase): - def handle_game_3_hiscore_request(self, request: Node) -> Node: # Grab location for local scores - locid = ID.parse_machine_id(request.child_value('locid')) + locid = ID.parse_machine_id(request.child_value("locid")) # Start the response packet - game = Node.void('game_3') + game = Node.void("game_3") # First, grab hit chart playcounts = self.data.local.music.get_hit_chart(self.game, self.version, 1024) - hitchart = Node.void('hitchart') + hitchart = Node.void("hitchart") game.add_child(hitchart) for (songid, count) in playcounts: - info = Node.void('info') + info = Node.void("info") hitchart.add_child(info) - info.add_child(Node.u32('id', songid)) - info.add_child(Node.u32('cnt', count)) + info.add_child(Node.u32("id", songid)) + info.add_child(Node.u32("cnt", count)) # Now, grab user records records = self.data.remote.music.get_all_records(self.game, self.version) users = { - uid: prof for (uid, prof) in - self.get_any_profiles([r[0] for r in records]) + uid: prof for (uid, prof) in self.get_any_profiles([r[0] for r in records]) } - hiscore_allover = Node.void('hiscore_allover') + hiscore_allover = Node.void("hiscore_allover") game.add_child(hiscore_allover) # Output records for (userid, score) in records: - info = Node.void('info') + info = Node.void("info") if userid not in users: - raise Exception('Logic error, could not find profile for user!') + raise Exception("Logic error, could not find profile for user!") profile = users[userid] - info.add_child(Node.u32('id', score.id)) - info.add_child(Node.u32('type', score.chart)) - info.add_child(Node.string('name', profile.get_str('name'))) - info.add_child(Node.string('seq', ID.format_extid(profile.extid))) - info.add_child(Node.u32('score', score.points)) + info.add_child(Node.u32("id", score.id)) + info.add_child(Node.u32("type", score.chart)) + info.add_child(Node.string("name", profile.get_str("name"))) + info.add_child(Node.string("seq", ID.format_extid(profile.extid))) + info.add_child(Node.u32("score", score.points)) # Add to global scores hiscore_allover.add_child(info) # Now, grab local records area_users = [ - uid for (uid, prof) in self.data.local.user.get_all_profiles(self.game, self.version) - if prof.get_int('loc', -1) == locid - ] - records = self.data.local.music.get_all_records(self.game, self.version, userlist=area_users) - missing_players = [ - uid for (uid, _) in records - if uid not in users + uid + for (uid, prof) in self.data.local.user.get_all_profiles( + self.game, self.version + ) + if prof.get_int("loc", -1) == locid ] + records = self.data.local.music.get_all_records( + self.game, self.version, userlist=area_users + ) + missing_players = [uid for (uid, _) in records if uid not in users] for (uid, prof) in self.get_any_profiles(missing_players): users[uid] = prof - hiscore_location = Node.void('hiscore_location') + hiscore_location = Node.void("hiscore_location") game.add_child(hiscore_location) # Output records for (userid, score) in records: - info = Node.void('info') + info = Node.void("info") if userid not in users: - raise Exception('Logic error, could not find profile for user!') + raise Exception("Logic error, could not find profile for user!") profile = users[userid] - info.add_child(Node.u32('id', score.id)) - info.add_child(Node.u32('type', score.chart)) - info.add_child(Node.string('name', profile.get_str('name'))) - info.add_child(Node.string('seq', ID.format_extid(profile.extid))) - info.add_child(Node.u32('score', score.points)) + info.add_child(Node.u32("id", score.id)) + info.add_child(Node.u32("type", score.chart)) + info.add_child(Node.string("name", profile.get_str("name"))) + info.add_child(Node.string("seq", ID.format_extid(profile.extid))) + info.add_child(Node.u32("score", score.points)) # Add to global scores hiscore_location.add_child(info) # Now, grab clear rates - clear_rate = Node.void('clear_rate') + clear_rate = Node.void("clear_rate") game.add_child(clear_rate) clears = self.get_clear_rates() for songid in clears: for chart in clears[songid]: - if clears[songid][chart]['total'] > 0: - rate = float(clears[songid][chart]['clears']) / float(clears[songid][chart]['total']) - dnode = Node.void('d') + if clears[songid][chart]["total"] > 0: + rate = float(clears[songid][chart]["clears"]) / float( + clears[songid][chart]["total"] + ) + dnode = Node.void("d") clear_rate.add_child(dnode) - dnode.add_child(Node.u32('id', songid)) - dnode.add_child(Node.u32('type', chart)) - dnode.add_child(Node.s16('cr', int(rate * 10000))) + dnode.add_child(Node.u32("id", songid)) + dnode.add_child(Node.u32("type", chart)) + dnode.add_child(Node.s16("cr", int(rate * 10000))) return game class MusecaGameFrozenHandler(MusecaBase): - def handle_game_3_frozen_request(self, request: Node) -> Node: - game = Node.void('game_3') - game.add_child(Node.u8('result', 0)) + game = Node.void("game_3") + game.add_child(Node.u8("result", 0)) return game class MusecaGameNewHandler(MusecaBase): - def handle_game_3_new_request(self, request: Node) -> Node: - refid = request.child_value('refid') - name = request.child_value('name') - loc = ID.parse_machine_id(request.child_value('locid')) + refid = request.child_value("refid") + name = request.child_value("name") + loc = ID.parse_machine_id(request.child_value("locid")) self.new_profile_by_refid(refid, name, loc) - root = Node.void('game_3') + root = Node.void("game_3") return root class MusecaGameSaveMusicHandler(MusecaBase): - def handle_game_3_save_m_request(self, request: Node) -> Node: - refid = request.child_value('refid') + refid = request.child_value("refid") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -143,19 +141,19 @@ class MusecaGameSaveMusicHandler(MusecaBase): userid = None # Doesn't matter if userid is None here, that's an anonymous score - musicid = request.child_value('music_id') - chart = request.child_value('music_type') - points = request.child_value('score') - combo = request.child_value('max_chain') - clear_type = self.game_to_db_clear_type(request.child_value('clear_type')) - grade = self.game_to_db_grade(request.child_value('score_grade')) + musicid = request.child_value("music_id") + chart = request.child_value("music_type") + points = request.child_value("score") + combo = request.child_value("max_chain") + clear_type = self.game_to_db_clear_type(request.child_value("clear_type")) + grade = self.game_to_db_grade(request.child_value("score_grade")) stats = { - 'btn_rate': request.child_value('btn_rate'), - 'long_rate': request.child_value('long_rate'), - 'vol_rate': request.child_value('vol_rate'), - 'critical': request.child_value('critical'), - 'near': request.child_value('near'), - 'error': request.child_value('error'), + "btn_rate": request.child_value("btn_rate"), + "long_rate": request.child_value("long_rate"), + "vol_rate": request.child_value("vol_rate"), + "critical": request.child_value("critical"), + "near": request.child_value("near"), + "error": request.child_value("error"), } # Save the score @@ -171,19 +169,17 @@ class MusecaGameSaveMusicHandler(MusecaBase): ) # Return a blank response - return Node.void('game_3') + return Node.void("game_3") class MusecaGamePlayEndHandler(MusecaBase): - def handle_game_3_play_e_request(self, request: Node) -> Node: - return Node.void('game_3') + return Node.void("game_3") class MusecaGameSaveHandler(MusecaBase): - def handle_game_3_save_request(self, request: Node) -> Node: - refid = request.child_value('refid') + refid = request.child_value("refid") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -199,4 +195,4 @@ class MusecaGameSaveHandler(MusecaBase): if userid is not None and newprofile is not None: self.put_profile(userid, newprofile) - return Node.void('game_3') + return Node.void("game_3") diff --git a/bemani/backend/museca/factory.py b/bemani/backend/museca/factory.py index 4149565..667cafe 100644 --- a/bemani/backend/museca/factory.py +++ b/bemani/backend/museca/factory.py @@ -16,12 +16,17 @@ class MusecaFactory(Factory): @classmethod def register_all(cls) -> None: - for gamecode in ['PIX']: + for gamecode in ["PIX"]: Base.register(gamecode, MusecaFactory) @classmethod - def create(cls, data: Data, config: Config, model: Model, parentmodel: Optional[Model]=None) -> Optional[Base]: - + def create( + cls, + data: Data, + config: Config, + model: Model, + parentmodel: Optional[Model] = None, + ) -> Optional[Base]: def version_from_date(date: int) -> Optional[int]: if date <= 2016072600: return VersionConstants.MUSECA @@ -29,7 +34,7 @@ class MusecaFactory(Factory): return VersionConstants.MUSECA_1_PLUS return None - if model.gamecode == 'PIX': + if model.gamecode == "PIX": version = version_from_date(model.version) if version == VersionConstants.MUSECA: return Museca1(data, config, model) diff --git a/bemani/backend/museca/museca1.py b/bemani/backend/museca/museca1.py index d56e14b..96d7e8d 100644 --- a/bemani/backend/museca/museca1.py +++ b/bemani/backend/museca/museca1.py @@ -59,12 +59,12 @@ class Museca1( Return all of our front-end modifiably settings. """ return { - 'bools': [ + "bools": [ { - 'name': 'Force Song Unlock', - 'tip': 'Force unlock all songs.', - 'category': 'game_config', - 'setting': 'force_unlock_songs', + "name": "Force Song Unlock", + "tip": "Force unlock all songs.", + "category": "game_config", + "setting": "force_unlock_songs", }, ], } @@ -109,34 +109,37 @@ class Museca1( }[grade] def handle_game_3_common_request(self, request: Node) -> Node: - game = Node.void('game_3') - limited = Node.void('music_limited') + game = Node.void("game_3") + limited = Node.void("music_limited") game.add_child(limited) # Song unlock config game_config = self.get_game_config() - if game_config.get_bool('force_unlock_songs'): + if game_config.get_bool("force_unlock_songs"): ids = set() songs = self.data.local.music.get_all_songs(self.game, self.music_version) for song in songs: - if song.data.get_int('limited') in (self.GAME_LIMITED_LOCKED, self.GAME_LIMITED_UNLOCKABLE): + if song.data.get_int("limited") in ( + self.GAME_LIMITED_LOCKED, + self.GAME_LIMITED_UNLOCKABLE, + ): ids.add((song.id, song.chart)) for (songid, chart) in ids: - info = Node.void('info') + info = Node.void("info") limited.add_child(info) - info.add_child(Node.s32('music_id', songid)) - info.add_child(Node.u8('music_type', chart)) - info.add_child(Node.u8('limited', self.GAME_LIMITED_UNLOCKED)) + info.add_child(Node.s32("music_id", songid)) + info.add_child(Node.u8("music_type", chart)) + info.add_child(Node.u8("limited", self.GAME_LIMITED_UNLOCKED)) # Event config - event = Node.void('event') + event = Node.void("event") game.add_child(event) def enable_event(eid: int) -> None: - evt = Node.void('info') + evt = Node.void("info") event.add_child(evt) - evt.add_child(Node.u32('event_id', eid)) + evt.add_child(Node.u32("event_id", eid)) # Allow PASELI light start enable_event(83) @@ -153,21 +156,21 @@ class Museca1( return game def handle_game_3_exception_request(self, request: Node) -> Node: - return Node.void('game_3') + return Node.void("game_3") def handle_game_3_load_request(self, request: Node) -> Node: - refid = request.child_value('refid') + refid = request.child_value("refid") root = self.get_profile_by_refid(refid) if root is not None: return root # No data succession, there's nothing older than this! - root = Node.void('game_3') - root.add_child(Node.u8('result', 1)) + root = Node.void("game_3") + root.add_child(Node.u8("result", 1)) return root def handle_game_3_load_m_request(self, request: Node) -> Node: - refid = request.child_value('dataid') + refid = request.child_value("dataid") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -175,154 +178,186 @@ class Museca1( userid = None if userid is not None: - scores = self.data.remote.music.get_scores(self.game, self.music_version, userid) + scores = self.data.remote.music.get_scores( + self.game, self.music_version, userid + ) else: scores = [] # Output to the game - game = Node.void('game_3') - new = Node.void('new') + game = Node.void("game_3") + new = Node.void("new") game.add_child(new) for score in scores: - music = Node.void('music') + music = Node.void("music") new.add_child(music) - music.add_child(Node.u32('music_id', score.id)) - music.add_child(Node.u32('music_type', score.chart)) - music.add_child(Node.u32('score', score.points)) - music.add_child(Node.u32('cnt', score.plays)) - music.add_child(Node.u32('clear_type', self.db_to_game_clear_type(score.data.get_int('clear_type')))) - music.add_child(Node.u32('score_grade', self.db_to_game_grade(score.data.get_int('grade')))) - stats = score.data.get_dict('stats') - music.add_child(Node.u32('btn_rate', stats.get_int('btn_rate'))) - music.add_child(Node.u32('long_rate', stats.get_int('long_rate'))) - music.add_child(Node.u32('vol_rate', stats.get_int('vol_rate'))) + music.add_child(Node.u32("music_id", score.id)) + music.add_child(Node.u32("music_type", score.chart)) + music.add_child(Node.u32("score", score.points)) + music.add_child(Node.u32("cnt", score.plays)) + music.add_child( + Node.u32( + "clear_type", + self.db_to_game_clear_type(score.data.get_int("clear_type")), + ) + ) + music.add_child( + Node.u32( + "score_grade", self.db_to_game_grade(score.data.get_int("grade")) + ) + ) + stats = score.data.get_dict("stats") + music.add_child(Node.u32("btn_rate", stats.get_int("btn_rate"))) + music.add_child(Node.u32("long_rate", stats.get_int("long_rate"))) + music.add_child(Node.u32("vol_rate", stats.get_int("vol_rate"))) return game def format_profile(self, userid: UserID, profile: Profile) -> Node: - game = Node.void('game_3') + game = Node.void("game_3") # Generic profile stuff - game.add_child(Node.string('name', profile.get_str('name'))) - game.add_child(Node.string('code', ID.format_extid(profile.extid))) - game.add_child(Node.u32('gamecoin_packet', profile.get_int('packet'))) - game.add_child(Node.u32('gamecoin_block', profile.get_int('block'))) - game.add_child(Node.s16('skill_name_id', profile.get_int('skill_name_id', -1))) - game.add_child(Node.s32_array('hidden_param', profile.get_int_array('hidden_param', 20))) - game.add_child(Node.u32('blaster_energy', profile.get_int('blaster_energy'))) - game.add_child(Node.u32('blaster_count', profile.get_int('blaster_count'))) + game.add_child(Node.string("name", profile.get_str("name"))) + game.add_child(Node.string("code", ID.format_extid(profile.extid))) + game.add_child(Node.u32("gamecoin_packet", profile.get_int("packet"))) + game.add_child(Node.u32("gamecoin_block", profile.get_int("block"))) + game.add_child(Node.s16("skill_name_id", profile.get_int("skill_name_id", -1))) + game.add_child( + Node.s32_array("hidden_param", profile.get_int_array("hidden_param", 20)) + ) + game.add_child(Node.u32("blaster_energy", profile.get_int("blaster_energy"))) + game.add_child(Node.u32("blaster_count", profile.get_int("blaster_count"))) # Play statistics statistics = self.get_play_statistics(userid) - game.add_child(Node.u32('play_count', statistics.total_plays)) - game.add_child(Node.u32('daily_count', statistics.today_plays)) - game.add_child(Node.u32('play_chain', statistics.consecutive_days)) + game.add_child(Node.u32("play_count", statistics.total_plays)) + game.add_child(Node.u32("daily_count", statistics.today_plays)) + game.add_child(Node.u32("play_chain", statistics.consecutive_days)) # Last played stuff - if 'last' in profile: - lastdict = profile.get_dict('last') - last = Node.void('last') + if "last" in profile: + lastdict = profile.get_dict("last") + last = Node.void("last") game.add_child(last) - last.add_child(Node.s32('music_id', lastdict.get_int('music_id', -1))) - last.add_child(Node.u8('music_type', lastdict.get_int('music_type'))) - last.add_child(Node.u8('sort_type', lastdict.get_int('sort_type'))) - last.add_child(Node.u8('narrow_down', lastdict.get_int('narrow_down'))) - last.add_child(Node.u8('headphone', lastdict.get_int('headphone'))) - last.add_child(Node.u16('appeal_id', lastdict.get_int('appeal_id', 1001))) - last.add_child(Node.u16('comment_id', lastdict.get_int('comment_id'))) - last.add_child(Node.u8('gauge_option', lastdict.get_int('gauge_option'))) + last.add_child(Node.s32("music_id", lastdict.get_int("music_id", -1))) + last.add_child(Node.u8("music_type", lastdict.get_int("music_type"))) + last.add_child(Node.u8("sort_type", lastdict.get_int("sort_type"))) + last.add_child(Node.u8("narrow_down", lastdict.get_int("narrow_down"))) + last.add_child(Node.u8("headphone", lastdict.get_int("headphone"))) + last.add_child(Node.u16("appeal_id", lastdict.get_int("appeal_id", 1001))) + last.add_child(Node.u16("comment_id", lastdict.get_int("comment_id"))) + last.add_child(Node.u8("gauge_option", lastdict.get_int("gauge_option"))) # Item unlocks - itemnode = Node.void('item') + itemnode = Node.void("item") game.add_child(itemnode) game_config = self.get_game_config() - achievements = self.data.local.user.get_achievements(self.game, self.version, userid) + achievements = self.data.local.user.get_achievements( + self.game, self.version, userid + ) for item in achievements: - if item.type[:5] != 'item_': + if item.type[:5] != "item_": continue itemtype = int(item.type[5:]) - if game_config.get_bool('force_unlock_songs') and itemtype == self.GAME_CATALOG_TYPE_SONG: + if ( + game_config.get_bool("force_unlock_songs") + and itemtype == self.GAME_CATALOG_TYPE_SONG + ): # Don't echo unlocked songs, we will add all of them later continue - info = Node.void('info') + info = Node.void("info") itemnode.add_child(info) - info.add_child(Node.u8('type', itemtype)) - info.add_child(Node.u32('id', item.id)) - info.add_child(Node.u32('param', item.data.get_int('param'))) - if 'diff_param' in item.data: - info.add_child(Node.s32('diff_param', item.data.get_int('diff_param'))) + info.add_child(Node.u8("type", itemtype)) + info.add_child(Node.u32("id", item.id)) + info.add_child(Node.u32("param", item.data.get_int("param"))) + if "diff_param" in item.data: + info.add_child(Node.s32("diff_param", item.data.get_int("diff_param"))) - if game_config.get_bool('force_unlock_songs'): + if game_config.get_bool("force_unlock_songs"): ids: Dict[int, int] = {} songs = self.data.local.music.get_all_songs(self.game, self.music_version) for song in songs: if song.id not in ids: ids[song.id] = 0 - if song.data.get_int('difficulty') > 0: + if song.data.get_int("difficulty") > 0: ids[song.id] = ids[song.id] | (1 << song.chart) for itemid in ids: if ids[itemid] == 0: continue - info = Node.void('info') + info = Node.void("info") itemnode.add_child(info) - info.add_child(Node.u8('type', self.GAME_CATALOG_TYPE_SONG)) - info.add_child(Node.u32('id', itemid)) - info.add_child(Node.u32('param', ids[itemid])) + info.add_child(Node.u8("type", self.GAME_CATALOG_TYPE_SONG)) + info.add_child(Node.u32("id", itemid)) + info.add_child(Node.u32("param", ids[itemid])) return game - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() # Update blaster energy and in-game currencies - earned_gamecoin_packet = request.child_value('earned_gamecoin_packet') + earned_gamecoin_packet = request.child_value("earned_gamecoin_packet") if earned_gamecoin_packet is not None: - newprofile.replace_int('packet', newprofile.get_int('packet') + earned_gamecoin_packet) - earned_gamecoin_block = request.child_value('earned_gamecoin_block') + newprofile.replace_int( + "packet", newprofile.get_int("packet") + earned_gamecoin_packet + ) + earned_gamecoin_block = request.child_value("earned_gamecoin_block") if earned_gamecoin_block is not None: - newprofile.replace_int('block', newprofile.get_int('block') + earned_gamecoin_block) - earned_blaster_energy = request.child_value('earned_blaster_energy') + newprofile.replace_int( + "block", newprofile.get_int("block") + earned_gamecoin_block + ) + earned_blaster_energy = request.child_value("earned_blaster_energy") if earned_blaster_energy is not None: - newprofile.replace_int('blaster_energy', newprofile.get_int('blaster_energy') + earned_blaster_energy) + newprofile.replace_int( + "blaster_energy", + newprofile.get_int("blaster_energy") + earned_blaster_energy, + ) # Miscelaneous stuff - newprofile.replace_int('blaster_count', request.child_value('blaster_count')) - newprofile.replace_int('skill_name_id', request.child_value('skill_name_id')) - newprofile.replace_int_array('hidden_param', 20, request.child_value('hidden_param')) + newprofile.replace_int("blaster_count", request.child_value("blaster_count")) + newprofile.replace_int("skill_name_id", request.child_value("skill_name_id")) + newprofile.replace_int_array( + "hidden_param", 20, request.child_value("hidden_param") + ) # Update user's unlock status if we aren't force unlocked game_config = self.get_game_config() - if request.child('item') is not None: - for child in request.child('item').children: - if child.name != 'info': + if request.child("item") is not None: + for child in request.child("item").children: + if child.name != "info": continue - item_id = child.child_value('id') - item_type = child.child_value('type') - param = child.child_value('param') - diff_param = child.child_value('diff_param') + item_id = child.child_value("id") + item_type = child.child_value("type") + param = child.child_value("param") + diff_param = child.child_value("diff_param") - if game_config.get_bool('force_unlock_songs') and item_type == self.GAME_CATALOG_TYPE_SONG: + if ( + game_config.get_bool("force_unlock_songs") + and item_type == self.GAME_CATALOG_TYPE_SONG + ): # Don't save back songs, because they were force unlocked continue if diff_param is not None: paramvals = { - 'diff_param': diff_param, - 'param': param, + "diff_param": diff_param, + "param": param, } else: paramvals = { - 'param': param, + "param": param, } self.data.local.user.put_achievement( @@ -330,23 +365,23 @@ class Museca1( self.version, userid, item_id, - f'item_{item_type}', + f"item_{item_type}", paramvals, ) # Grab last information. - lastdict = newprofile.get_dict('last') - lastdict.replace_int('headphone', request.child_value('headphone')) - lastdict.replace_int('appeal_id', request.child_value('appeal_id')) - lastdict.replace_int('comment_id', request.child_value('comment_id')) - lastdict.replace_int('music_id', request.child_value('music_id')) - lastdict.replace_int('music_type', request.child_value('music_type')) - lastdict.replace_int('sort_type', request.child_value('sort_type')) - lastdict.replace_int('narrow_down', request.child_value('narrow_down')) - lastdict.replace_int('gauge_option', request.child_value('gauge_option')) + lastdict = newprofile.get_dict("last") + lastdict.replace_int("headphone", request.child_value("headphone")) + lastdict.replace_int("appeal_id", request.child_value("appeal_id")) + lastdict.replace_int("comment_id", request.child_value("comment_id")) + lastdict.replace_int("music_id", request.child_value("music_id")) + lastdict.replace_int("music_type", request.child_value("music_type")) + lastdict.replace_int("sort_type", request.child_value("sort_type")) + lastdict.replace_int("narrow_down", request.child_value("narrow_down")) + lastdict.replace_int("gauge_option", request.child_value("gauge_option")) # Save back last information gleaned from results - newprofile.replace_dict('last', lastdict) + newprofile.replace_dict("last", lastdict) # Keep track of play statistics self.update_play_statistics(userid) diff --git a/bemani/backend/museca/museca1plus.py b/bemani/backend/museca/museca1plus.py index fafd1fc..1ce45ad 100644 --- a/bemani/backend/museca/museca1plus.py +++ b/bemani/backend/museca/museca1plus.py @@ -64,18 +64,18 @@ class Museca1Plus( Return all of our front-end modifiably settings. """ return { - 'bools': [ + "bools": [ { - 'name': 'Disable Online Matching', - 'tip': 'Disable online matching between games.', - 'category': 'game_config', - 'setting': 'disable_matching', + "name": "Disable Online Matching", + "tip": "Disable online matching between games.", + "category": "game_config", + "setting": "disable_matching", }, { - 'name': 'Force Song Unlock', - 'tip': 'Force unlock all songs.', - 'category': 'game_config', - 'setting': 'force_unlock_songs', + "name": "Force Song Unlock", + "tip": "Force unlock all songs.", + "category": "game_config", + "setting": "force_unlock_songs", }, ], } @@ -121,36 +121,39 @@ class Museca1Plus( }[grade] def handle_game_3_common_request(self, request: Node) -> Node: - game = Node.void('game_3') - limited = Node.void('music_limited') + game = Node.void("game_3") + limited = Node.void("music_limited") game.add_child(limited) # Song unlock config game_config = self.get_game_config() - if game_config.get_bool('force_unlock_songs'): + if game_config.get_bool("force_unlock_songs"): ids = set() songs = self.data.local.music.get_all_songs(self.game, self.music_version) for song in songs: - if song.data.get_int('limited') in (self.GAME_LIMITED_LOCKED, self.GAME_LIMITED_UNLOCKABLE): + if song.data.get_int("limited") in ( + self.GAME_LIMITED_LOCKED, + self.GAME_LIMITED_UNLOCKABLE, + ): ids.add((song.id, song.chart)) for (songid, chart) in ids: - info = Node.void('info') + info = Node.void("info") limited.add_child(info) - info.add_child(Node.s32('music_id', songid)) - info.add_child(Node.u8('music_type', chart)) - info.add_child(Node.u8('limited', self.GAME_LIMITED_UNLOCKED)) + info.add_child(Node.s32("music_id", songid)) + info.add_child(Node.u8("music_type", chart)) + info.add_child(Node.u8("limited", self.GAME_LIMITED_UNLOCKED)) # Event config - event = Node.void('event') + event = Node.void("event") game.add_child(event) def enable_event(eid: int) -> None: - evt = Node.void('info') + evt = Node.void("info") event.add_child(evt) - evt.add_child(Node.u32('event_id', eid)) + evt.add_child(Node.u32("event_id", eid)) - if not game_config.get_bool('disable_matching'): + if not game_config.get_bool("disable_matching"): enable_event(143) # Matching enabled # These events are meant specifically for Museca Plus @@ -160,11 +163,11 @@ class Museca1Plus( 212, # News 2 ] event_ids = [ - 1, # Extended pedal options (no effect on Museca 1+1/2) - 56, # Generator grafica icon - 83, # Paseli Light Start - 86, # Generator grafica icon - 98, # Caption 2 notice (grs_grafica_caption_2.png) + 1, # Extended pedal options (no effect on Museca 1+1/2) + 56, # Generator grafica icon + 83, # Paseli Light Start + 86, # Generator grafica icon + 98, # Caption 2 notice (grs_grafica_caption_2.png) 105, # Makes the "Number of Layers" option visible in game settings 130, # Curator Rank 141, # Coconatsu & Mukipara grafica effects @@ -183,96 +186,96 @@ class Museca1Plus( enable_event(evtid) # List of known event IDs: - # 56, # Generator grafica icon - # 83, # Paseli Light Start - # 86, # Generator grafica icon - # 98, # Caption 2 notice (grs_grafica_caption_2.png) - # 100, # DJ YOSHITAKA EXHIBITION 2016 - # 103, # HATSUNE MIKU EXHIBITION 2016 - PART 1 - # 104, # HATSUNE MIKU EXHIBITION 2016 - PART 2 - # 105, # Makes the "Number of Layers" option visible in game settings - # 106, # HATSUNE MIKU EXHIBITION 2016 - PART 3 - # 117, # NEW GENERATION METEOR DIFFUSE FESTA 2016 / RYUSEI FESTA TRIGGER - # 129, # COCONATSU EXHIBITION 2016 - # 130, # Curator Rank - # 97, # Agetta Moratta (vmlink_phase 1 in musicdb) - # 114, # Agetta Moratta (vmlink_phase 2 in musicdb) - # 140, # Agetta Moratta (vmlink_phase 3 in musicdb) - # 141, # Coconatsu & Mukipara grafica effects - # 143, # Matching - # 144, # BEMANI ARCHAEOLOGICAL EXHIBITION - # 163, # TUTORIAL SNOW - # 169, # SHIORI FUJISAKI EXHIBITION 2017 - PART 1 - # 174, # SHIORI FUJISAKI EXHIBITION 2017 - PART 2 - # 182, # Mute illil's voice? - # 192, # GREAT REPRINT FESTIVAL: MIKU + DJ YOSHITAKA - # 194, # Continue - # 195, # Fictional Curator (foot pedal options) - # 211, #News 1 - # 212, #News 2 - # 213, #News 3 - # 214, #News 4 - # 217, #News 5 - # 218, #News 6 - # 219, #News 7 - # 220, #News 8 - # 221, # GRAFICA PRESENTATION CAMPAIGN “THE PRIMITIVE LIFE EXHIBITION” - # 222, # GRAFICA PRESENTATION CAMPAIGN "NOISE" - # 223, # GRAFICA PRESENTATION CAMPAIGN "PATISSERIE ROUGE" - # 224, # GRAFICA PRESENTATION CAMPAIGN "GUNSLINGER" - # 145, # MUKIPARA UNLOCKS - # 146, # MUKIPARA UNLOCKS - # 147, # MUKIPARA UNLOCKS - # 148, # MUKIPARA UNLOCKS - # 149, # MUKIPARA UNLOCKS + # 56, # Generator grafica icon + # 83, # Paseli Light Start + # 86, # Generator grafica icon + # 98, # Caption 2 notice (grs_grafica_caption_2.png) + # 100, # DJ YOSHITAKA EXHIBITION 2016 + # 103, # HATSUNE MIKU EXHIBITION 2016 - PART 1 + # 104, # HATSUNE MIKU EXHIBITION 2016 - PART 2 + # 105, # Makes the "Number of Layers" option visible in game settings + # 106, # HATSUNE MIKU EXHIBITION 2016 - PART 3 + # 117, # NEW GENERATION METEOR DIFFUSE FESTA 2016 / RYUSEI FESTA TRIGGER + # 129, # COCONATSU EXHIBITION 2016 + # 130, # Curator Rank + # 97, # Agetta Moratta (vmlink_phase 1 in musicdb) + # 114, # Agetta Moratta (vmlink_phase 2 in musicdb) + # 140, # Agetta Moratta (vmlink_phase 3 in musicdb) + # 141, # Coconatsu & Mukipara grafica effects + # 143, # Matching + # 144, # BEMANI ARCHAEOLOGICAL EXHIBITION + # 163, # TUTORIAL SNOW + # 169, # SHIORI FUJISAKI EXHIBITION 2017 - PART 1 + # 174, # SHIORI FUJISAKI EXHIBITION 2017 - PART 2 + # 182, # Mute illil's voice? + # 192, # GREAT REPRINT FESTIVAL: MIKU + DJ YOSHITAKA + # 194, # Continue + # 195, # Fictional Curator (foot pedal options) + # 211, #News 1 + # 212, #News 2 + # 213, #News 3 + # 214, #News 4 + # 217, #News 5 + # 218, #News 6 + # 219, #News 7 + # 220, #News 8 + # 221, # GRAFICA PRESENTATION CAMPAIGN “THE PRIMITIVE LIFE EXHIBITION” + # 222, # GRAFICA PRESENTATION CAMPAIGN "NOISE" + # 223, # GRAFICA PRESENTATION CAMPAIGN "PATISSERIE ROUGE" + # 224, # GRAFICA PRESENTATION CAMPAIGN "GUNSLINGER" + # 145, # MUKIPARA UNLOCKS + # 146, # MUKIPARA UNLOCKS + # 147, # MUKIPARA UNLOCKS + # 148, # MUKIPARA UNLOCKS + # 149, # MUKIPARA UNLOCKS # Makes special missions available on grafica that have them. - extend = Node.void('extend') + extend = Node.void("extend") game.add_child(extend) - info = Node.void('info') + info = Node.void("info") extend.add_child(info) - info.add_child(Node.u32('extend_id', 1)) - info.add_child(Node.u32('extend_type', 9)) - info.add_child(Node.s32('param_num_1', 2)) - info.add_child(Node.s32('param_num_2', 50)) - info.add_child(Node.s32('param_num_3', 59)) - info.add_child(Node.s32('param_num_4', 64)) - info.add_child(Node.s32('param_num_5', 86)) - info.add_child(Node.string('param_str_1', 'available_ex: 1')) - info.add_child(Node.string('param_str_2', 'available_ex: 1')) - info.add_child(Node.string('param_str_3', 'available_ex: 1')) - info.add_child(Node.string('param_str_4', 'available_ex: 1')) - info.add_child(Node.string('param_str_5', 'available_ex: 1')) + info.add_child(Node.u32("extend_id", 1)) + info.add_child(Node.u32("extend_type", 9)) + info.add_child(Node.s32("param_num_1", 2)) + info.add_child(Node.s32("param_num_2", 50)) + info.add_child(Node.s32("param_num_3", 59)) + info.add_child(Node.s32("param_num_4", 64)) + info.add_child(Node.s32("param_num_5", 86)) + info.add_child(Node.string("param_str_1", "available_ex: 1")) + info.add_child(Node.string("param_str_2", "available_ex: 1")) + info.add_child(Node.string("param_str_3", "available_ex: 1")) + info.add_child(Node.string("param_str_4", "available_ex: 1")) + info.add_child(Node.string("param_str_5", "available_ex: 1")) if self.omnimix: - info = Node.void('info') + info = Node.void("info") extend.add_child(info) - info.add_child(Node.u32('extend_id', 2)) - info.add_child(Node.u32('extend_type', 9)) - info.add_child(Node.s32('param_num_1', 210)) - info.add_child(Node.s32('param_num_2', 0)) - info.add_child(Node.s32('param_num_3', 0)) - info.add_child(Node.s32('param_num_4', 0)) - info.add_child(Node.s32('param_num_5', 0)) - info.add_child(Node.string('param_str_1', '')) - info.add_child(Node.string('param_str_2', '')) - info.add_child(Node.string('param_str_3', '')) - info.add_child(Node.string('param_str_4', '')) - info.add_child(Node.string('param_str_5', '')) + info.add_child(Node.u32("extend_id", 2)) + info.add_child(Node.u32("extend_type", 9)) + info.add_child(Node.s32("param_num_1", 210)) + info.add_child(Node.s32("param_num_2", 0)) + info.add_child(Node.s32("param_num_3", 0)) + info.add_child(Node.s32("param_num_4", 0)) + info.add_child(Node.s32("param_num_5", 0)) + info.add_child(Node.string("param_str_1", "")) + info.add_child(Node.string("param_str_2", "")) + info.add_child(Node.string("param_str_3", "")) + info.add_child(Node.string("param_str_4", "")) + info.add_child(Node.string("param_str_5", "")) return game def handle_game_3_lounge_request(self, request: Node) -> Node: - game = Node.void('game_3') + game = Node.void("game_3") # Refresh interval in seconds. - game.add_child(Node.u32('interval', 10)) + game.add_child(Node.u32("interval", 10)) return game def handle_game_3_exception_request(self, request: Node) -> Node: - return Node.void('game_3') + return Node.void("game_3") def handle_game_3_load_request(self, request: Node) -> Node: - refid = request.child_value('refid') + refid = request.child_value("refid") root = self.get_profile_by_refid(refid) if root is not None: return root @@ -294,12 +297,12 @@ class Museca1Plus( # Return the previous formatted profile to the game. return previous_game.format_profile(userid, profile) else: - root = Node.void('game_3') - root.add_child(Node.u8('result', 1)) + root = Node.void("game_3") + root.add_child(Node.u8("result", 1)) return root def handle_game_3_load_m_request(self, request: Node) -> Node: - refid = request.child_value('dataid') + refid = request.child_value("dataid") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -307,160 +310,192 @@ class Museca1Plus( userid = None if userid is not None: - scores = self.data.remote.music.get_scores(self.game, self.music_version, userid) + scores = self.data.remote.music.get_scores( + self.game, self.music_version, userid + ) else: scores = [] # Output to the game - game = Node.void('game_3') - new = Node.void('new') + game = Node.void("game_3") + new = Node.void("new") game.add_child(new) for score in scores: - music = Node.void('music') + music = Node.void("music") new.add_child(music) - music.add_child(Node.u32('music_id', score.id)) - music.add_child(Node.u32('music_type', score.chart)) - music.add_child(Node.u32('score', score.points)) - music.add_child(Node.u32('cnt', score.plays)) - music.add_child(Node.u32('combo', score.data.get_int('combo'))) - music.add_child(Node.u32('clear_type', self.db_to_game_clear_type(score.data.get_int('clear_type')))) - music.add_child(Node.u32('score_grade', self.db_to_game_grade(score.data.get_int('grade')))) - stats = score.data.get_dict('stats') - music.add_child(Node.u32('btn_rate', stats.get_int('btn_rate'))) - music.add_child(Node.u32('long_rate', stats.get_int('long_rate'))) - music.add_child(Node.u32('vol_rate', stats.get_int('vol_rate'))) + music.add_child(Node.u32("music_id", score.id)) + music.add_child(Node.u32("music_type", score.chart)) + music.add_child(Node.u32("score", score.points)) + music.add_child(Node.u32("cnt", score.plays)) + music.add_child(Node.u32("combo", score.data.get_int("combo"))) + music.add_child( + Node.u32( + "clear_type", + self.db_to_game_clear_type(score.data.get_int("clear_type")), + ) + ) + music.add_child( + Node.u32( + "score_grade", self.db_to_game_grade(score.data.get_int("grade")) + ) + ) + stats = score.data.get_dict("stats") + music.add_child(Node.u32("btn_rate", stats.get_int("btn_rate"))) + music.add_child(Node.u32("long_rate", stats.get_int("long_rate"))) + music.add_child(Node.u32("vol_rate", stats.get_int("vol_rate"))) return game def format_profile(self, userid: UserID, profile: Profile) -> Node: - game = Node.void('game_3') + game = Node.void("game_3") # Generic profile stuff - game.add_child(Node.string('name', profile.get_str('name'))) - game.add_child(Node.string('code', ID.format_extid(profile.extid))) - game.add_child(Node.u32('gamecoin_packet', profile.get_int('packet'))) - game.add_child(Node.u32('gamecoin_block', profile.get_int('block'))) - game.add_child(Node.s16('skill_name_id', profile.get_int('skill_name_id', -1))) - game.add_child(Node.s32_array('hidden_param', profile.get_int_array('hidden_param', 20))) - game.add_child(Node.u32('blaster_energy', profile.get_int('blaster_energy'))) - game.add_child(Node.u32('blaster_count', profile.get_int('blaster_count'))) + game.add_child(Node.string("name", profile.get_str("name"))) + game.add_child(Node.string("code", ID.format_extid(profile.extid))) + game.add_child(Node.u32("gamecoin_packet", profile.get_int("packet"))) + game.add_child(Node.u32("gamecoin_block", profile.get_int("block"))) + game.add_child(Node.s16("skill_name_id", profile.get_int("skill_name_id", -1))) + game.add_child( + Node.s32_array("hidden_param", profile.get_int_array("hidden_param", 20)) + ) + game.add_child(Node.u32("blaster_energy", profile.get_int("blaster_energy"))) + game.add_child(Node.u32("blaster_count", profile.get_int("blaster_count"))) # Enable Ryusei Festa - ryusei_festa = Node.void('ryusei_festa') + ryusei_festa = Node.void("ryusei_festa") game.add_child(ryusei_festa) - ryusei_festa.add_child(Node.bool('ryusei_festa_trigger', True)) + ryusei_festa.add_child(Node.bool("ryusei_festa_trigger", True)) # Play statistics statistics = self.get_play_statistics(userid) - game.add_child(Node.u32('play_count', statistics.total_plays)) - game.add_child(Node.u32('daily_count', statistics.today_plays)) - game.add_child(Node.u32('play_chain', statistics.consecutive_days)) + game.add_child(Node.u32("play_count", statistics.total_plays)) + game.add_child(Node.u32("daily_count", statistics.today_plays)) + game.add_child(Node.u32("play_chain", statistics.consecutive_days)) # Last played stuff - if 'last' in profile: - lastdict = profile.get_dict('last') - last = Node.void('last') + if "last" in profile: + lastdict = profile.get_dict("last") + last = Node.void("last") game.add_child(last) - last.add_child(Node.s32('music_id', lastdict.get_int('music_id', -1))) - last.add_child(Node.u8('music_type', lastdict.get_int('music_type'))) - last.add_child(Node.u8('sort_type', lastdict.get_int('sort_type'))) - last.add_child(Node.u8('narrow_down', lastdict.get_int('narrow_down'))) - last.add_child(Node.u8('headphone', lastdict.get_int('headphone'))) - last.add_child(Node.u16('appeal_id', lastdict.get_int('appeal_id', 1001))) - last.add_child(Node.u16('comment_id', lastdict.get_int('comment_id'))) - last.add_child(Node.u8('gauge_option', lastdict.get_int('gauge_option'))) + last.add_child(Node.s32("music_id", lastdict.get_int("music_id", -1))) + last.add_child(Node.u8("music_type", lastdict.get_int("music_type"))) + last.add_child(Node.u8("sort_type", lastdict.get_int("sort_type"))) + last.add_child(Node.u8("narrow_down", lastdict.get_int("narrow_down"))) + last.add_child(Node.u8("headphone", lastdict.get_int("headphone"))) + last.add_child(Node.u16("appeal_id", lastdict.get_int("appeal_id", 1001))) + last.add_child(Node.u16("comment_id", lastdict.get_int("comment_id"))) + last.add_child(Node.u8("gauge_option", lastdict.get_int("gauge_option"))) # Item unlocks - itemnode = Node.void('item') + itemnode = Node.void("item") game.add_child(itemnode) game_config = self.get_game_config() - achievements = self.data.local.user.get_achievements(self.game, self.version, userid) + achievements = self.data.local.user.get_achievements( + self.game, self.version, userid + ) for item in achievements: - if item.type[:5] != 'item_': + if item.type[:5] != "item_": continue itemtype = int(item.type[5:]) - if game_config.get_bool('force_unlock_songs') and itemtype == self.GAME_CATALOG_TYPE_SONG: + if ( + game_config.get_bool("force_unlock_songs") + and itemtype == self.GAME_CATALOG_TYPE_SONG + ): # Don't echo unlocked songs, we will add all of them later continue - info = Node.void('info') + info = Node.void("info") itemnode.add_child(info) - info.add_child(Node.u8('type', itemtype)) - info.add_child(Node.u32('id', item.id)) - info.add_child(Node.u32('param', item.data.get_int('param'))) - if 'diff_param' in item.data: - info.add_child(Node.s32('diff_param', item.data.get_int('diff_param'))) + info.add_child(Node.u8("type", itemtype)) + info.add_child(Node.u32("id", item.id)) + info.add_child(Node.u32("param", item.data.get_int("param"))) + if "diff_param" in item.data: + info.add_child(Node.s32("diff_param", item.data.get_int("diff_param"))) - if game_config.get_bool('force_unlock_songs'): + if game_config.get_bool("force_unlock_songs"): ids: Dict[int, int] = {} songs = self.data.local.music.get_all_songs(self.game, self.music_version) for song in songs: if song.id not in ids: ids[song.id] = 0 - if song.data.get_int('difficulty') > 0: + if song.data.get_int("difficulty") > 0: ids[song.id] = ids[song.id] | (1 << song.chart) for itemid in ids: if ids[itemid] == 0: continue - info = Node.void('info') + info = Node.void("info") itemnode.add_child(info) - info.add_child(Node.u8('type', self.GAME_CATALOG_TYPE_SONG)) - info.add_child(Node.u32('id', itemid)) - info.add_child(Node.u32('param', ids[itemid])) + info.add_child(Node.u8("type", self.GAME_CATALOG_TYPE_SONG)) + info.add_child(Node.u32("id", itemid)) + info.add_child(Node.u32("param", ids[itemid])) return game - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() # Update blaster energy and in-game currencies - earned_gamecoin_packet = request.child_value('earned_gamecoin_packet') + earned_gamecoin_packet = request.child_value("earned_gamecoin_packet") if earned_gamecoin_packet is not None: - newprofile.replace_int('packet', newprofile.get_int('packet') + earned_gamecoin_packet) - earned_gamecoin_block = request.child_value('earned_gamecoin_block') + newprofile.replace_int( + "packet", newprofile.get_int("packet") + earned_gamecoin_packet + ) + earned_gamecoin_block = request.child_value("earned_gamecoin_block") if earned_gamecoin_block is not None: - newprofile.replace_int('block', newprofile.get_int('block') + earned_gamecoin_block) - earned_blaster_energy = request.child_value('earned_blaster_energy') + newprofile.replace_int( + "block", newprofile.get_int("block") + earned_gamecoin_block + ) + earned_blaster_energy = request.child_value("earned_blaster_energy") if earned_blaster_energy is not None: - newprofile.replace_int('blaster_energy', newprofile.get_int('blaster_energy') + earned_blaster_energy) + newprofile.replace_int( + "blaster_energy", + newprofile.get_int("blaster_energy") + earned_blaster_energy, + ) # Miscelaneous stuff - newprofile.replace_int('blaster_count', request.child_value('blaster_count')) - newprofile.replace_int('skill_name_id', request.child_value('skill_name_id')) - newprofile.replace_int_array('hidden_param', 20, request.child_value('hidden_param')) + newprofile.replace_int("blaster_count", request.child_value("blaster_count")) + newprofile.replace_int("skill_name_id", request.child_value("skill_name_id")) + newprofile.replace_int_array( + "hidden_param", 20, request.child_value("hidden_param") + ) # Update user's unlock status if we aren't force unlocked game_config = self.get_game_config() - if request.child('item') is not None: - for child in request.child('item').children: - if child.name != 'info': + if request.child("item") is not None: + for child in request.child("item").children: + if child.name != "info": continue - item_id = child.child_value('id') - item_type = child.child_value('type') - param = child.child_value('param') - diff_param = child.child_value('diff_param') + item_id = child.child_value("id") + item_type = child.child_value("type") + param = child.child_value("param") + diff_param = child.child_value("diff_param") - if game_config.get_bool('force_unlock_songs') and item_type == self.GAME_CATALOG_TYPE_SONG: + if ( + game_config.get_bool("force_unlock_songs") + and item_type == self.GAME_CATALOG_TYPE_SONG + ): # Don't save back songs, because they were force unlocked continue if diff_param is not None: paramvals = { - 'diff_param': diff_param, - 'param': param, + "diff_param": diff_param, + "param": param, } else: paramvals = { - 'param': param, + "param": param, } self.data.local.user.put_achievement( @@ -468,23 +503,23 @@ class Museca1Plus( self.version, userid, item_id, - f'item_{item_type}', + f"item_{item_type}", paramvals, ) # Grab last information. - lastdict = newprofile.get_dict('last') - lastdict.replace_int('headphone', request.child_value('headphone')) - lastdict.replace_int('appeal_id', request.child_value('appeal_id')) - lastdict.replace_int('comment_id', request.child_value('comment_id')) - lastdict.replace_int('music_id', request.child_value('music_id')) - lastdict.replace_int('music_type', request.child_value('music_type')) - lastdict.replace_int('sort_type', request.child_value('sort_type')) - lastdict.replace_int('narrow_down', request.child_value('narrow_down')) - lastdict.replace_int('gauge_option', request.child_value('gauge_option')) + lastdict = newprofile.get_dict("last") + lastdict.replace_int("headphone", request.child_value("headphone")) + lastdict.replace_int("appeal_id", request.child_value("appeal_id")) + lastdict.replace_int("comment_id", request.child_value("comment_id")) + lastdict.replace_int("music_id", request.child_value("music_id")) + lastdict.replace_int("music_type", request.child_value("music_type")) + lastdict.replace_int("sort_type", request.child_value("sort_type")) + lastdict.replace_int("narrow_down", request.child_value("narrow_down")) + lastdict.replace_int("gauge_option", request.child_value("gauge_option")) # Save back last information gleaned from results - newprofile.replace_dict('last', lastdict) + newprofile.replace_dict("last", lastdict) # Keep track of play statistics self.update_play_statistics(userid) diff --git a/bemani/backend/popn/base.py b/bemani/backend/popn/base.py index c703c19..9c01fe8 100644 --- a/bemani/backend/popn/base.py +++ b/bemani/backend/popn/base.py @@ -4,7 +4,14 @@ from typing_extensions import Final from bemani.backend.base import Base from bemani.backend.core import CoreHandler, CardManagerHandler, PASELIHandler -from bemani.common import Profile, ValidatedDict, Time, GameConstants, DBConstants, BroadcastConstants +from bemani.common import ( + Profile, + ValidatedDict, + Time, + GameConstants, + DBConstants, + BroadcastConstants, +) from bemani.data import UserID, Achievement, ScoreSaveException from bemani.protocol import Node @@ -20,16 +27,30 @@ class PopnMusicBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): # Play medals, as saved into/loaded from the DB PLAY_MEDAL_NO_PLAY: Final[int] = DBConstants.POPN_MUSIC_PLAY_MEDAL_NO_PLAY - PLAY_MEDAL_CIRCLE_FAILED: Final[int] = DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_FAILED - PLAY_MEDAL_DIAMOND_FAILED: Final[int] = DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_FAILED + PLAY_MEDAL_CIRCLE_FAILED: Final[ + int + ] = DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_FAILED + PLAY_MEDAL_DIAMOND_FAILED: Final[ + int + ] = DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_FAILED PLAY_MEDAL_STAR_FAILED: Final[int] = DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_FAILED PLAY_MEDAL_EASY_CLEAR: Final[int] = DBConstants.POPN_MUSIC_PLAY_MEDAL_EASY_CLEAR - PLAY_MEDAL_CIRCLE_CLEARED: Final[int] = DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_CLEARED - PLAY_MEDAL_DIAMOND_CLEARED: Final[int] = DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_CLEARED + PLAY_MEDAL_CIRCLE_CLEARED: Final[ + int + ] = DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_CLEARED + PLAY_MEDAL_DIAMOND_CLEARED: Final[ + int + ] = DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_CLEARED PLAY_MEDAL_STAR_CLEARED: Final[int] = DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_CLEARED - PLAY_MEDAL_CIRCLE_FULL_COMBO: Final[int] = DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_FULL_COMBO - PLAY_MEDAL_DIAMOND_FULL_COMBO: Final[int] = DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_FULL_COMBO - PLAY_MEDAL_STAR_FULL_COMBO: Final[int] = DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_FULL_COMBO + PLAY_MEDAL_CIRCLE_FULL_COMBO: Final[ + int + ] = DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_FULL_COMBO + PLAY_MEDAL_DIAMOND_FULL_COMBO: Final[ + int + ] = DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_FULL_COMBO + PLAY_MEDAL_STAR_FULL_COMBO: Final[ + int + ] = DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_FULL_COMBO PLAY_MEDAL_PERFECT: Final[int] = DBConstants.POPN_MUSIC_PLAY_MEDAL_PERFECT # Chart type, as saved into/loaded from the DB, and returned to game @@ -47,7 +68,7 @@ class PopnMusicBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): # properly. supports_expired_profiles: bool = False - def previous_version(self) -> Optional['PopnMusicBase']: + def previous_version(self) -> Optional["PopnMusicBase"]: """ Returns the previous version of the game, based on this game. Should be overridden. @@ -59,7 +80,7 @@ class PopnMusicBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): Base handler for a profile. Given a userid and a profile dictionary, return a Node representing a profile. Should be overridden. """ - return Node.void('playerdata') + return Node.void("playerdata") def format_conversion(self, userid: UserID, profile: Profile) -> Node: """ @@ -70,9 +91,11 @@ class PopnMusicBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): format_conversion on that previous version to get the profile to migrate. """ - return Node.void('playerdata') + return Node.void("playerdata") - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: """ Base handler for profile parsing. Given a request and an old profile, return a new profile that's been updated with the contents of the request. @@ -80,7 +103,9 @@ class PopnMusicBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): """ return oldprofile - def get_profile_by_refid(self, refid: Optional[str], load_mode: int) -> Optional[Node]: + def get_profile_by_refid( + self, refid: Optional[str], load_mode: int + ) -> Optional[Node]: """ Given a RefID, return a formatted profile node. Basically every game needs a profile lookup, even if it handles where that happens in @@ -127,7 +152,7 @@ class PopnMusicBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): self, refid: Optional[str], name: Optional[str], - chara: Optional[int]=None, + chara: Optional[int] = None, achievements: Sequence[Achievement] = (), ) -> Node: """ @@ -138,7 +163,7 @@ class PopnMusicBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): return None if name is None: - name = 'なし' + name = "なし" userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is None: @@ -149,11 +174,11 @@ class PopnMusicBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): refid, 0, { - 'name': name, + "name": name, }, ) if chara is not None: - profile.replace_int('chara', chara) + profile.replace_int("chara", chara) self.put_profile(userid, profile) for achievement in achievements: self.data.local.user.put_achievement( @@ -173,8 +198,8 @@ class PopnMusicBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): chart: int, points: int, medal: int, - combo: Optional[int]=None, - stats: Optional[Dict[str, int]]=None, + combo: Optional[int] = None, + stats: Optional[Dict[str, int]] = None, ) -> None: """ Given various pieces of a score, update the user's high score and score @@ -227,19 +252,19 @@ class PopnMusicBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): scoredata = oldscore.data # Replace medal with highest value - scoredata.replace_int('medal', max(scoredata.get_int('medal'), medal)) - history.replace_int('medal', medal) + scoredata.replace_int("medal", max(scoredata.get_int("medal"), medal)) + history.replace_int("medal", medal) if stats is not None: if raised: # We have stats, and there's a new high score, update the stats - scoredata.replace_dict('stats', stats) - history.replace_dict('stats', stats) + scoredata.replace_dict("stats", stats) + history.replace_dict("stats", stats) if combo is not None: # If we have a combo, replace it - scoredata.replace_int('combo', max(scoredata.get_int('combo'), combo)) - history.replace_int('combo', combo) + scoredata.replace_int("combo", max(scoredata.get_int("combo"), combo)) + history.replace_int("combo", combo) # Look up where this score was earned lid = self.get_machine_id() @@ -290,44 +315,53 @@ class PopnMusicBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): # We saved successfully break - def broadcast_score(self, userid: UserID, songid: int, chart: int, medal: int, points: int, combo: int, stats: Dict[str, int]) -> None: + def broadcast_score( + self, + userid: UserID, + songid: int, + chart: int, + medal: int, + points: int, + combo: int, + stats: Dict[str, int], + ) -> None: # Generate scorecard profile = self.get_profile(userid) song = self.data.local.music.get_song(self.game, self.version, songid, chart) card_medal = { - self.PLAY_MEDAL_CIRCLE_FAILED: 'Failed', - self.PLAY_MEDAL_DIAMOND_FAILED: 'Failed', - self.PLAY_MEDAL_STAR_FAILED: 'Failed', - self.PLAY_MEDAL_EASY_CLEAR: 'Cleared', - self.PLAY_MEDAL_CIRCLE_CLEARED: 'Cleared', - self.PLAY_MEDAL_DIAMOND_CLEARED: 'Cleared', - self.PLAY_MEDAL_STAR_CLEARED: 'Cleared', - self.PLAY_MEDAL_CIRCLE_FULL_COMBO: 'Full Combo', - self.PLAY_MEDAL_DIAMOND_FULL_COMBO: 'Full Combo', - self.PLAY_MEDAL_STAR_FULL_COMBO: 'Full Combo', - self.PLAY_MEDAL_PERFECT: 'Perfect', + self.PLAY_MEDAL_CIRCLE_FAILED: "Failed", + self.PLAY_MEDAL_DIAMOND_FAILED: "Failed", + self.PLAY_MEDAL_STAR_FAILED: "Failed", + self.PLAY_MEDAL_EASY_CLEAR: "Cleared", + self.PLAY_MEDAL_CIRCLE_CLEARED: "Cleared", + self.PLAY_MEDAL_DIAMOND_CLEARED: "Cleared", + self.PLAY_MEDAL_STAR_CLEARED: "Cleared", + self.PLAY_MEDAL_CIRCLE_FULL_COMBO: "Full Combo", + self.PLAY_MEDAL_DIAMOND_FULL_COMBO: "Full Combo", + self.PLAY_MEDAL_STAR_FULL_COMBO: "Full Combo", + self.PLAY_MEDAL_PERFECT: "Perfect", }[medal] card_chart = { - self.CHART_TYPE_EASY: 'Easy', - self.CHART_TYPE_NORMAL: 'Normal', - self.CHART_TYPE_HYPER: 'Hyper', - self.CHART_TYPE_EX: 'Ex', + self.CHART_TYPE_EASY: "Easy", + self.CHART_TYPE_NORMAL: "Normal", + self.CHART_TYPE_HYPER: "Hyper", + self.CHART_TYPE_EX: "Ex", }[chart] # Construct the dictionary for the broadcast card_data = { - BroadcastConstants.PLAYER_NAME: profile.get_str('name', 'なし'), + BroadcastConstants.PLAYER_NAME: profile.get_str("name", "なし"), BroadcastConstants.SONG_NAME: song.name, BroadcastConstants.ARTIST_NAME: song.artist, BroadcastConstants.DIFFICULTY: card_chart, BroadcastConstants.SCORE: str(points), BroadcastConstants.MEDAL: card_medal, - BroadcastConstants.COOLS: str(stats['cool']), - BroadcastConstants.GREATS: str(stats['great']), - BroadcastConstants.GOODS: str(stats['good']), - BroadcastConstants.BADS: str(stats['bad']), + BroadcastConstants.COOLS: str(stats["cool"]), + BroadcastConstants.GREATS: str(stats["great"]), + BroadcastConstants.GOODS: str(stats["good"]), + BroadcastConstants.BADS: str(stats["bad"]), BroadcastConstants.COMBO: str(combo), } diff --git a/bemani/backend/popn/common.py b/bemani/backend/popn/common.py index c748be7..6979887 100644 --- a/bemani/backend/popn/common.py +++ b/bemani/backend/popn/common.py @@ -51,42 +51,53 @@ class PopnMusicModernBase(PopnMusicBase, ABC): # Return the local2 and lobby2 service so that Pop'n Music 24+ will # send game packets. extra_services: List[str] = [ - 'local2', - 'lobby2', + "local2", + "lobby2", ] @classmethod - def run_scheduled_work(cls, data: Data, config: Dict[str, Any]) -> List[Tuple[str, Dict[str, Any]]]: + def run_scheduled_work( + cls, data: Data, config: Dict[str, Any] + ) -> List[Tuple[str, Dict[str, Any]]]: """ Once a week, insert a new course. """ events = [] - if data.local.network.should_schedule(cls.game, cls.version, 'course', 'weekly'): + if data.local.network.should_schedule( + cls.game, cls.version, "course", "weekly" + ): # Generate a new course list, save it to the DB. - start_time, end_time = data.local.network.get_schedule_duration('weekly') - all_songs = [song.id for song in data.local.music.get_all_songs(cls.game, cls.version)] + start_time, end_time = data.local.network.get_schedule_duration("weekly") + all_songs = [ + song.id + for song in data.local.music.get_all_songs(cls.game, cls.version) + ] if all_songs: course_song = random.choice(all_songs) data.local.game.put_time_sensitive_settings( cls.game, cls.version, - 'course', + "course", { - 'start_time': start_time, - 'end_time': end_time, - 'music': course_song, + "start_time": start_time, + "end_time": end_time, + "music": course_song, }, ) - events.append(( - 'pnm_course', - { - 'version': cls.version, - 'song': course_song, - }, - )) + events.append( + ( + "pnm_course", + { + "version": cls.version, + "song": course_song, + }, + ) + ) # Mark that we did some actual work here. - data.local.network.mark_scheduled(cls.game, cls.version, 'course', 'weekly') + data.local.network.mark_scheduled( + cls.game, cls.version, "course", "weekly" + ) return events def __score_to_rank(self, score: int) -> int: @@ -108,18 +119,18 @@ class PopnMusicModernBase(PopnMusicBase, ABC): def handle_lobby24_requests(self, request: Node) -> Node: # Stub out the entire lobby24 service - return Node.void('lobby24') + return Node.void("lobby24") def handle_pcb24_error_request(self, request: Node) -> Node: - return Node.void('pcb24') + return Node.void("pcb24") def handle_pcb24_boot_request(self, request: Node) -> Node: - return Node.void('pcb24') + return Node.void("pcb24") def handle_pcb24_write_request(self, request: Node) -> Node: # Update the name of this cab for admin purposes - self.update_machine_name(request.child_value('pcb_setting/name')) - return Node.void('pcb24') + self.update_machine_name(request.child_value("pcb_setting/name")) + return Node.void("pcb24") @abstractmethod def get_common_config(self) -> Tuple[Dict[int, int], bool]: @@ -134,33 +145,41 @@ class PopnMusicModernBase(PopnMusicBase, ABC): phases, send_areas = self.get_common_config() for phaseid, phase_value in phases.items(): - phase = Node.void('phase') + phase = Node.void("phase") root.add_child(phase) - phase.add_child(Node.s16('event_id', phaseid)) - phase.add_child(Node.s16('phase', phase_value)) + phase.add_child(Node.s16("event_id", phaseid)) + phase.add_child(Node.s16("phase", phase_value)) # Gather course information and course ranking for users. - course_infos, achievements, profiles = Parallel.execute([ - lambda: self.data.local.game.get_all_time_sensitive_settings(self.game, self.version, 'course'), - lambda: self.data.local.user.get_all_achievements(self.game, self.version), - lambda: self.data.local.user.get_all_profiles(self.game, self.version), - ]) + course_infos, achievements, profiles = Parallel.execute( + [ + lambda: self.data.local.game.get_all_time_sensitive_settings( + self.game, self.version, "course" + ), + lambda: self.data.local.user.get_all_achievements( + self.game, self.version + ), + lambda: self.data.local.user.get_all_profiles(self.game, self.version), + ] + ) # Sort courses by newest to oldest so we can grab the newest 256. course_infos = sorted( course_infos, - key=lambda c: c['start_time'], + key=lambda c: c["start_time"], reverse=True, ) # Sort achievements within course ID from best to worst ranking. - achievements_by_course_id: Dict[int, Dict[str, List[Tuple[UserID, Achievement]]]] = {} + achievements_by_course_id: Dict[ + int, Dict[str, List[Tuple[UserID, Achievement]]] + ] = {} type_to_chart_lut: Dict[str, str] = { - f'course_{self.GAME_CHART_TYPE_EASY}': "loc_ranking_e", - f'course_{self.GAME_CHART_TYPE_NORMAL}': "loc_ranking_n", - f'course_{self.GAME_CHART_TYPE_HYPER}': "loc_ranking_h", - f'course_{self.GAME_CHART_TYPE_EX}': "loc_ranking_ex", + f"course_{self.GAME_CHART_TYPE_EASY}": "loc_ranking_e", + f"course_{self.GAME_CHART_TYPE_NORMAL}": "loc_ranking_n", + f"course_{self.GAME_CHART_TYPE_HYPER}": "loc_ranking_h", + f"course_{self.GAME_CHART_TYPE_EX}": "loc_ranking_ex", } for uid, ach in achievements: - if ach.type[:7] != 'course_': + if ach.type[:7] != "course_": continue if ach.id not in achievements_by_course_id: achievements_by_course_id[ach.id] = { @@ -169,60 +188,86 @@ class PopnMusicModernBase(PopnMusicBase, ABC): "loc_ranking_h": [], "loc_ranking_ex": [], } - achievements_by_course_id[ach.id][type_to_chart_lut[ach.type]].append((uid, ach)) + achievements_by_course_id[ach.id][type_to_chart_lut[ach.type]].append( + (uid, ach) + ) for courseid in achievements_by_course_id: - for chart in ["loc_ranking_e", "loc_ranking_n", "loc_ranking_h", "loc_ranking_ex"]: + for chart in [ + "loc_ranking_e", + "loc_ranking_n", + "loc_ranking_h", + "loc_ranking_ex", + ]: achievements_by_course_id[courseid][chart] = sorted( achievements_by_course_id[courseid][chart], - key=lambda uid_and_ach: uid_and_ach[1].data.get_int('score'), + key=lambda uid_and_ach: uid_and_ach[1].data.get_int("score"), reverse=True, ) # Cache of userID to profile - userid_to_profile: Dict[UserID, Profile] = {uid: profile for (uid, profile) in profiles} + userid_to_profile: Dict[UserID, Profile] = { + uid: profile for (uid, profile) in profiles + } # Course ranking info for the last 256 courses for course_info in course_infos[:256]: - course_id = int(course_info['start_time'] / 604800) + course_id = int(course_info["start_time"] / 604800) course_rankings = achievements_by_course_id.get(course_id, {}) - ranking_info = Node.void('ranking_info') + ranking_info = Node.void("ranking_info") root.add_child(ranking_info) - ranking_info.add_child(Node.s16('course_id', course_id)) - ranking_info.add_child(Node.u64('start_date', course_info['start_time'] * 1000)) - ranking_info.add_child(Node.u64('end_date', course_info['end_time'] * 1000)) - ranking_info.add_child(Node.s32('music_id', course_info['music'])) + ranking_info.add_child(Node.s16("course_id", course_id)) + ranking_info.add_child( + Node.u64("start_date", course_info["start_time"] * 1000) + ) + ranking_info.add_child(Node.u64("end_date", course_info["end_time"] * 1000)) + ranking_info.add_child(Node.s32("music_id", course_info["music"])) # Top 20 rankings for each particular chart. - for name in ["loc_ranking_e", "loc_ranking_n", "loc_ranking_h", "loc_ranking_ex"]: + for name in [ + "loc_ranking_e", + "loc_ranking_n", + "loc_ranking_h", + "loc_ranking_ex", + ]: chart_rankings = course_rankings.get(name, []) for pos, (uid, ach) in enumerate(chart_rankings[:20]): - profile = userid_to_profile.get(uid, Profile(self.game, self.version, "", 0)) + profile = userid_to_profile.get( + uid, Profile(self.game, self.version, "", 0) + ) subnode = Node.void(name) ranking_info.add_child(subnode) - subnode.add_child(Node.s16('rank', pos + 1)) - subnode.add_child(Node.string('name', profile.get_str('name'))) - subnode.add_child(Node.s16('chara_num', profile.get_int('chara', -1))) - subnode.add_child(Node.s32('total_score', ach.data.get_int('score'))) - subnode.add_child(Node.u8('clear_type', ach.data.get_int('clear_type'))) - subnode.add_child(Node.u8('clear_rank', ach.data.get_int('clear_rank'))) + subnode.add_child(Node.s16("rank", pos + 1)) + subnode.add_child(Node.string("name", profile.get_str("name"))) + subnode.add_child( + Node.s16("chara_num", profile.get_int("chara", -1)) + ) + subnode.add_child( + Node.s32("total_score", ach.data.get_int("score")) + ) + subnode.add_child( + Node.u8("clear_type", ach.data.get_int("clear_type")) + ) + subnode.add_child( + Node.u8("clear_rank", ach.data.get_int("clear_rank")) + ) if send_areas: for area_id in range(1, 16): - area = Node.void('area') + area = Node.void("area") root.add_child(area) - area.add_child(Node.s16('area_id', area_id)) - area.add_child(Node.u64('end_date', 0)) - area.add_child(Node.s16('medal_id', area_id)) - area.add_child(Node.bool('is_limit', False)) + area.add_child(Node.s16("area_id", area_id)) + area.add_child(Node.u64("end_date", 0)) + area.add_child(Node.s16("medal_id", area_id)) + area.add_child(Node.bool("is_limit", False)) for choco_id in range(0, 5): - choco = Node.void('choco') + choco = Node.void("choco") root.add_child(choco) - choco.add_child(Node.s16('choco_id', choco_id)) - choco.add_child(Node.s32('param', -1)) + choco.add_child(Node.s16("choco_id", choco_id)) + choco.add_child(Node.s32("param", -1)) # Set up goods, educated guess here. for goods_id in range(self.GAME_MAX_DECO_ID): @@ -238,12 +283,12 @@ class PopnMusicModernBase(PopnMusicBase, ABC): price = 200 else: price = 250 - goods = Node.void('goods') + goods = Node.void("goods") root.add_child(goods) - goods.add_child(Node.s32('item_id', goods_id + 1)) - goods.add_child(Node.s16('item_type', 3)) - goods.add_child(Node.s32('price', price)) - goods.add_child(Node.s16('goods_type', 0)) + goods.add_child(Node.s32("item_id", goods_id + 1)) + goods.add_child(Node.s16("item_type", 3)) + goods.add_child(Node.s32("price", price)) + goods.add_child(Node.s16("goods_type", 0)) # Ignoring NAVIfes node, we don't set these. # fes = Node.void('fes') @@ -260,7 +305,7 @@ class PopnMusicModernBase(PopnMusicBase, ABC): profiles = self.data.remote.user.get_all_profiles(self.game, self.version) charas: Dict[int, int] = {} for (_userid, profile) in profiles: - chara = profile.get_int('chara', -1) + chara = profile.get_int("chara", -1) if chara <= 0: continue if chara not in charas: @@ -277,16 +322,18 @@ class PopnMusicModernBase(PopnMusicBase, ABC): # Top 20 Popular characters for rank, (charaid, _usecount) in enumerate(charamap[:20]): - popular = Node.void('popular') + popular = Node.void("popular") root.add_child(popular) - popular.add_child(Node.s16('rank', rank + 1)) - popular.add_child(Node.s16('chara_num', charaid)) + popular.add_child(Node.s16("rank", rank + 1)) + popular.add_child(Node.s16("chara_num", charaid)) # Top 500 Popular music - for (songid, _plays) in self.data.local.music.get_hit_chart(self.game, self.version, 500): - popular_music = Node.void('popular_music') + for (songid, _plays) in self.data.local.music.get_hit_chart( + self.game, self.version, 500 + ): + popular_music = Node.void("popular_music") root.add_child(popular_music) - popular_music.add_child(Node.s16('music_num', songid)) + popular_music.add_child(Node.s16("music_num", songid)) # Ignoring recommended music, we don't set this # recommend = Node.void('recommend') @@ -311,60 +358,60 @@ class PopnMusicModernBase(PopnMusicBase, ABC): # chara_ranking.add_child(Node.s32('month', -1)) def handle_info24_common_request(self, root: Node) -> Node: - root = Node.void('info24') + root = Node.void("info24") self.__construct_common_info(root) return root def handle_player24_new_request(self, request: Node) -> Node: - refid = request.child_value('ref_id') - name = request.child_value('name') + refid = request.child_value("ref_id") + name = request.child_value("name") root = self.new_profile_by_refid(refid, name) if root is None: - root = Node.void('player24') - root.add_child(Node.s8('result', 2)) + root = Node.void("player24") + root.add_child(Node.s8("result", 2)) return root def handle_player24_conversion_request(self, request: Node) -> Node: - refid = request.child_value('ref_id') - name = request.child_value('name') - chara = request.child_value('chara') + refid = request.child_value("ref_id") + name = request.child_value("name") + chara = request.child_value("chara") achievements: List[Achievement] = [] for node in request.children: - if node.name == 'item': - itemid = node.child_value('id') - itemtype = node.child_value('type') - param = node.child_value('param') - is_new = node.child_value('is_new') - get_time = node.child_value('get_time') + if node.name == "item": + itemid = node.child_value("id") + itemtype = node.child_value("type") + param = node.child_value("param") + is_new = node.child_value("is_new") + get_time = node.child_value("get_time") achievements.append( Achievement( itemid, - f'item_{itemtype}', + f"item_{itemtype}", 0, { - 'param': param, - 'is_new': is_new, - 'get_time': get_time, + "param": param, + "is_new": is_new, + "get_time": get_time, }, ) ) root = self.new_profile_by_refid(refid, name, chara, achievements=achievements) if root is None: - root = Node.void('player24') - root.add_child(Node.s8('result', 2)) + root = Node.void("player24") + root.add_child(Node.s8("result", 2)) return root def handle_player24_read_request(self, request: Node) -> Node: - refid = request.child_value('ref_id') + refid = request.child_value("ref_id") root = self.get_profile_by_refid(refid, self.OLD_PROFILE_FALLTHROUGH) if root is None: - root = Node.void('player24') - root.add_child(Node.s8('result', 2)) + root = Node.void("player24") + root.add_child(Node.s8("result", 2)) return root def handle_player24_write_request(self, request: Node) -> Node: - refid = request.child_value('ref_id') + refid = request.child_value("ref_id") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -372,17 +419,19 @@ class PopnMusicModernBase(PopnMusicBase, ABC): userid = None if userid is not None: - oldprofile = self.get_profile(userid) or Profile(self.game, self.version, refid, 0) + oldprofile = self.get_profile(userid) or Profile( + self.game, self.version, refid, 0 + ) newprofile = self.unformat_profile(userid, request, oldprofile) if newprofile is not None: self.put_profile(userid, newprofile) - return Node.void('player24') + return Node.void("player24") def handle_player24_update_ranking_request(self, request: Node) -> Node: - refid = request.child_value('ref_id') - root = Node.void('player24') + refid = request.child_value("ref_id") + root = Node.void("player24") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -390,13 +439,13 @@ class PopnMusicModernBase(PopnMusicBase, ABC): userid = None if userid is not None: - course_id = request.child_value('course_id') - chart = request.child_value('sheet_num') - score = request.child_value('total_score') - clear_type = request.child_value('clear_type') - clear_rank = request.child_value('clear_rank') - prefecture = request.child_value('pref') - loc_id = ID.parse_machine_id(request.child_value('location_id')) + course_id = request.child_value("course_id") + chart = request.child_value("sheet_num") + score = request.child_value("total_score") + clear_type = request.child_value("clear_type") + clear_rank = request.child_value("clear_rank") + prefecture = request.child_value("pref") + loc_id = ID.parse_machine_id(request.child_value("location_id")) course_type = f"course_{chart}" old_course = self.data.local.user.get_achievement( @@ -409,14 +458,16 @@ class PopnMusicModernBase(PopnMusicBase, ABC): if old_course is None: old_course = ValidatedDict() - new_course = ValidatedDict({ - 'score': max(score, old_course.get_int('score')), - 'clear_type': max(clear_type, old_course.get_int('clear_type')), - 'clear_rank': max(clear_rank, old_course.get_int('clear_rank')), - 'pref': prefecture, - 'lid': loc_id, - 'count': old_course.get_int('count') + 1, - }) + new_course = ValidatedDict( + { + "score": max(score, old_course.get_int("score")), + "clear_type": max(clear_type, old_course.get_int("clear_type")), + "clear_rank": max(clear_rank, old_course.get_int("clear_rank")), + "pref": prefecture, + "lid": loc_id, + "count": old_course.get_int("count") + 1, + } + ) self.data.local.user.put_achievement( self.game, @@ -428,20 +479,37 @@ class PopnMusicModernBase(PopnMusicBase, ABC): ) # Handle fetching all scores - uids_and_courses, profile = Parallel.execute([ - lambda: self.data.local.user.get_all_achievements(self.game, self.version), - lambda: self.get_profile(userid) or Profile(self.game, self.version, "", 0) - ]) + uids_and_courses, profile = Parallel.execute( + [ + lambda: self.data.local.user.get_all_achievements( + self.game, self.version + ), + lambda: self.get_profile(userid) + or Profile(self.game, self.version, "", 0), + ] + ) # Grab a sorted list of all scores for this course and chart global_uids_and_courses = sorted( - [(uid, ach) for (uid, ach) in uids_and_courses if ach.type == course_type and ach.id == course_id], - key=lambda uid_and_course: uid_and_course[1].data.get_int('score'), + [ + (uid, ach) + for (uid, ach) in uids_and_courses + if ach.type == course_type and ach.id == course_id + ], + key=lambda uid_and_course: uid_and_course[1].data.get_int("score"), reverse=True, ) # Grab smaller lists that contain only sorted for our prefecture/location - pref_uids_and_courses = [(uid, ach) for (uid, ach) in global_uids_and_courses if ach.data.get_int('pref') == prefecture] - loc_uids_and_courses = [(uid, ach) for (uid, ach) in global_uids_and_courses if ach.data.get_int('lid') == loc_id] + pref_uids_and_courses = [ + (uid, ach) + for (uid, ach) in global_uids_and_courses + if ach.data.get_int("pref") == prefecture + ] + loc_uids_and_courses = [ + (uid, ach) + for (uid, ach) in global_uids_and_courses + if ach.data.get_int("lid") == loc_id + ] def _get_rank(uac: List[Tuple[UserID, Achievement]]) -> Optional[int]: for rank, (uid, _) in enumerate(uac): @@ -463,29 +531,29 @@ class PopnMusicModernBase(PopnMusicBase, ABC): # Send back the data for this ranking. node = Node.void(nodename) root.add_child(node) - node.add_child(Node.string("name", profile.get_str('name', 'なし'))) - node.add_child(Node.s16("chara_num", profile.get_int('chara', -1))) - node.add_child(Node.s32("total_score", new_course.get_int('score'))) - node.add_child(Node.u8("clear_type", new_course.get_int('clear_type'))) - node.add_child(Node.u8("clear_rank", new_course.get_int('clear_rank'))) + node.add_child(Node.string("name", profile.get_str("name", "なし"))) + node.add_child(Node.s16("chara_num", profile.get_int("chara", -1))) + node.add_child(Node.s32("total_score", new_course.get_int("score"))) + node.add_child(Node.u8("clear_type", new_course.get_int("clear_type"))) + node.add_child(Node.u8("clear_rank", new_course.get_int("clear_rank"))) node.add_child(Node.s16("player_count", len(ranklist))) node.add_child(Node.s16("player_rank", rank)) return root def handle_player24_friend_request(self, request: Node) -> Node: - refid = request.attribute('ref_id') - no = int(request.attribute('no', '-1')) + refid = request.attribute("ref_id") + no = int(request.attribute("no", "-1")) - root = Node.void('player24') + root = Node.void("player24") if no < 0: - root.add_child(Node.s8('result', 2)) + root.add_child(Node.s8("result", 2)) return root # Look up our own user ID based on the RefID provided. userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is None: - root.add_child(Node.s8('result', 2)) + root.add_child(Node.s8("result", 2)) return root # Grab the links that we care about. @@ -493,7 +561,7 @@ class PopnMusicModernBase(PopnMusicBase, ABC): profiles: Dict[UserID, Profile] = {} rivals: List[Link] = [] for link in links: - if link.type != 'rival': + if link.type != "rival": continue other_profile = self.get_profile(link.other_userid) @@ -504,22 +572,24 @@ class PopnMusicModernBase(PopnMusicBase, ABC): # Somehow requested an invalid profile. if no >= len(rivals): - root.add_child(Node.s8('result', 2)) + root.add_child(Node.s8("result", 2)) return root rivalid = links[no].other_userid rivalprofile = profiles[rivalid] scores = self.data.remote.music.get_scores(self.game, self.version, rivalid) # First, output general profile info. - friend = Node.void('friend') + friend = Node.void("friend") root.add_child(friend) - friend.add_child(Node.s16('no', no)) - friend.add_child(Node.string('g_pm_id', self.format_extid(rivalprofile.extid))) # UsaNeko formats on its own - friend.add_child(Node.string('name', rivalprofile.get_str('name', 'なし'))) - friend.add_child(Node.s16('chara_num', rivalprofile.get_int('chara', -1))) + friend.add_child(Node.s16("no", no)) + friend.add_child( + Node.string("g_pm_id", self.format_extid(rivalprofile.extid)) + ) # UsaNeko formats on its own + friend.add_child(Node.string("name", rivalprofile.get_str("name", "なし"))) + friend.add_child(Node.s16("chara_num", rivalprofile.get_int("chara", -1))) # This might be for having non-active or non-confirmed friends, but setting to 0 makes the # ranking numbers disappear and the player icon show a questionmark. - friend.add_child(Node.s8('is_open', 1)) + friend.add_child(Node.s8("is_open", 1)) for score in scores: # Skip any scores for chart types we don't support @@ -530,60 +600,80 @@ class PopnMusicModernBase(PopnMusicBase, ABC): self.CHART_TYPE_EX, ]: continue - if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY: + if score.data.get_int("medal") == self.PLAY_MEDAL_NO_PLAY: continue points = score.points - medal = score.data.get_int('medal') + medal = score.data.get_int("medal") - music = Node.void('music') + music = Node.void("music") friend.add_child(music) - music.set_attribute('music_num', str(score.id)) - music.set_attribute('sheet_num', str({ - self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY, - self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL, - self.CHART_TYPE_HYPER: self.GAME_CHART_TYPE_HYPER, - self.CHART_TYPE_EX: self.GAME_CHART_TYPE_EX, - }[score.chart])) - music.set_attribute('score', str(points)) - music.set_attribute('clearrank', str(self.__score_to_rank(score.points))) - music.set_attribute('cleartype', str({ - self.PLAY_MEDAL_CIRCLE_FAILED: self.GAME_PLAY_MEDAL_CIRCLE_FAILED, - self.PLAY_MEDAL_DIAMOND_FAILED: self.GAME_PLAY_MEDAL_DIAMOND_FAILED, - self.PLAY_MEDAL_STAR_FAILED: self.GAME_PLAY_MEDAL_STAR_FAILED, - self.PLAY_MEDAL_EASY_CLEAR: self.GAME_PLAY_MEDAL_EASY_CLEAR, - self.PLAY_MEDAL_CIRCLE_CLEARED: self.GAME_PLAY_MEDAL_CIRCLE_CLEARED, - self.PLAY_MEDAL_DIAMOND_CLEARED: self.GAME_PLAY_MEDAL_DIAMOND_CLEARED, - self.PLAY_MEDAL_STAR_CLEARED: self.GAME_PLAY_MEDAL_STAR_CLEARED, - self.PLAY_MEDAL_CIRCLE_FULL_COMBO: self.GAME_PLAY_MEDAL_CIRCLE_FULL_COMBO, - self.PLAY_MEDAL_DIAMOND_FULL_COMBO: self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO, - self.PLAY_MEDAL_STAR_FULL_COMBO: self.GAME_PLAY_MEDAL_STAR_FULL_COMBO, - self.PLAY_MEDAL_PERFECT: self.GAME_PLAY_MEDAL_PERFECT, - }[medal])) + music.set_attribute("music_num", str(score.id)) + music.set_attribute( + "sheet_num", + str( + { + self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY, + self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL, + self.CHART_TYPE_HYPER: self.GAME_CHART_TYPE_HYPER, + self.CHART_TYPE_EX: self.GAME_CHART_TYPE_EX, + }[score.chart] + ), + ) + music.set_attribute("score", str(points)) + music.set_attribute("clearrank", str(self.__score_to_rank(score.points))) + music.set_attribute( + "cleartype", + str( + { + self.PLAY_MEDAL_CIRCLE_FAILED: self.GAME_PLAY_MEDAL_CIRCLE_FAILED, + self.PLAY_MEDAL_DIAMOND_FAILED: self.GAME_PLAY_MEDAL_DIAMOND_FAILED, + self.PLAY_MEDAL_STAR_FAILED: self.GAME_PLAY_MEDAL_STAR_FAILED, + self.PLAY_MEDAL_EASY_CLEAR: self.GAME_PLAY_MEDAL_EASY_CLEAR, + self.PLAY_MEDAL_CIRCLE_CLEARED: self.GAME_PLAY_MEDAL_CIRCLE_CLEARED, + self.PLAY_MEDAL_DIAMOND_CLEARED: self.GAME_PLAY_MEDAL_DIAMOND_CLEARED, + self.PLAY_MEDAL_STAR_CLEARED: self.GAME_PLAY_MEDAL_STAR_CLEARED, + self.PLAY_MEDAL_CIRCLE_FULL_COMBO: self.GAME_PLAY_MEDAL_CIRCLE_FULL_COMBO, + self.PLAY_MEDAL_DIAMOND_FULL_COMBO: self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO, + self.PLAY_MEDAL_STAR_FULL_COMBO: self.GAME_PLAY_MEDAL_STAR_FULL_COMBO, + self.PLAY_MEDAL_PERFECT: self.GAME_PLAY_MEDAL_PERFECT, + }[medal] + ), + ) - achievements = self.data.local.user.get_achievements(self.game, self.version, rivalid) + achievements = self.data.local.user.get_achievements( + self.game, self.version, rivalid + ) for achievement in achievements: - if achievement.type[:7] == 'course_': + if achievement.type[:7] == "course_": sheet = int(achievement.type[7:]) - course_data = Node.void('course_data') + course_data = Node.void("course_data") root.add_child(course_data) - course_data.add_child(Node.s16('course_id', achievement.id)) - course_data.add_child(Node.u8('clear_type', achievement.data.get_int('clear_type'))) - course_data.add_child(Node.u8('clear_rank', achievement.data.get_int('clear_rank'))) - course_data.add_child(Node.s32('total_score', achievement.data.get_int('score'))) - course_data.add_child(Node.s32('update_count', achievement.data.get_int('count'))) - course_data.add_child(Node.u8('sheet_num', sheet)) + course_data.add_child(Node.s16("course_id", achievement.id)) + course_data.add_child( + Node.u8("clear_type", achievement.data.get_int("clear_type")) + ) + course_data.add_child( + Node.u8("clear_rank", achievement.data.get_int("clear_rank")) + ) + course_data.add_child( + Node.s32("total_score", achievement.data.get_int("score")) + ) + course_data.add_child( + Node.s32("update_count", achievement.data.get_int("count")) + ) + course_data.add_child(Node.u8("sheet_num", sheet)) return root def handle_player24_read_score_request(self, request: Node) -> Node: - refid = request.child_value('ref_id') + refid = request.child_value("ref_id") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is None: - return Node.void('player24') + return Node.void("player24") - root = Node.void('player24') + root = Node.void("player24") scores = self.data.remote.music.get_scores(self.game, self.version, userid) for score in scores: # Skip any scores for chart types we don't support @@ -594,41 +684,51 @@ class PopnMusicModernBase(PopnMusicBase, ABC): self.CHART_TYPE_EX, ]: continue - if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY: + if score.data.get_int("medal") == self.PLAY_MEDAL_NO_PLAY: continue - music = Node.void('music') + music = Node.void("music") root.add_child(music) - music.add_child(Node.s16('music_num', score.id)) - music.add_child(Node.u8('sheet_num', { - self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY, - self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL, - self.CHART_TYPE_HYPER: self.GAME_CHART_TYPE_HYPER, - self.CHART_TYPE_EX: self.GAME_CHART_TYPE_EX, - }[score.chart])) - music.add_child(Node.s32('score', score.points)) - music.add_child(Node.u8('clear_type', { - self.PLAY_MEDAL_CIRCLE_FAILED: self.GAME_PLAY_MEDAL_CIRCLE_FAILED, - self.PLAY_MEDAL_DIAMOND_FAILED: self.GAME_PLAY_MEDAL_DIAMOND_FAILED, - self.PLAY_MEDAL_STAR_FAILED: self.GAME_PLAY_MEDAL_STAR_FAILED, - self.PLAY_MEDAL_EASY_CLEAR: self.GAME_PLAY_MEDAL_EASY_CLEAR, - self.PLAY_MEDAL_CIRCLE_CLEARED: self.GAME_PLAY_MEDAL_CIRCLE_CLEARED, - self.PLAY_MEDAL_DIAMOND_CLEARED: self.GAME_PLAY_MEDAL_DIAMOND_CLEARED, - self.PLAY_MEDAL_STAR_CLEARED: self.GAME_PLAY_MEDAL_STAR_CLEARED, - self.PLAY_MEDAL_CIRCLE_FULL_COMBO: self.GAME_PLAY_MEDAL_CIRCLE_FULL_COMBO, - self.PLAY_MEDAL_DIAMOND_FULL_COMBO: self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO, - self.PLAY_MEDAL_STAR_FULL_COMBO: self.GAME_PLAY_MEDAL_STAR_FULL_COMBO, - self.PLAY_MEDAL_PERFECT: self.GAME_PLAY_MEDAL_PERFECT, - }[score.data.get_int('medal')])) - music.add_child(Node.u8('clear_rank', self.__score_to_rank(score.points))) - music.add_child(Node.s16('cnt', score.plays)) + music.add_child(Node.s16("music_num", score.id)) + music.add_child( + Node.u8( + "sheet_num", + { + self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY, + self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL, + self.CHART_TYPE_HYPER: self.GAME_CHART_TYPE_HYPER, + self.CHART_TYPE_EX: self.GAME_CHART_TYPE_EX, + }[score.chart], + ) + ) + music.add_child(Node.s32("score", score.points)) + music.add_child( + Node.u8( + "clear_type", + { + self.PLAY_MEDAL_CIRCLE_FAILED: self.GAME_PLAY_MEDAL_CIRCLE_FAILED, + self.PLAY_MEDAL_DIAMOND_FAILED: self.GAME_PLAY_MEDAL_DIAMOND_FAILED, + self.PLAY_MEDAL_STAR_FAILED: self.GAME_PLAY_MEDAL_STAR_FAILED, + self.PLAY_MEDAL_EASY_CLEAR: self.GAME_PLAY_MEDAL_EASY_CLEAR, + self.PLAY_MEDAL_CIRCLE_CLEARED: self.GAME_PLAY_MEDAL_CIRCLE_CLEARED, + self.PLAY_MEDAL_DIAMOND_CLEARED: self.GAME_PLAY_MEDAL_DIAMOND_CLEARED, + self.PLAY_MEDAL_STAR_CLEARED: self.GAME_PLAY_MEDAL_STAR_CLEARED, + self.PLAY_MEDAL_CIRCLE_FULL_COMBO: self.GAME_PLAY_MEDAL_CIRCLE_FULL_COMBO, + self.PLAY_MEDAL_DIAMOND_FULL_COMBO: self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO, + self.PLAY_MEDAL_STAR_FULL_COMBO: self.GAME_PLAY_MEDAL_STAR_FULL_COMBO, + self.PLAY_MEDAL_PERFECT: self.GAME_PLAY_MEDAL_PERFECT, + }[score.data.get_int("medal")], + ) + ) + music.add_child(Node.u8("clear_rank", self.__score_to_rank(score.points))) + music.add_child(Node.s16("cnt", score.plays)) return root def handle_player24_write_music_request(self, request: Node) -> Node: - refid = request.child_value('ref_id') + refid = request.child_value("ref_id") - root = Node.void('player24') + root = Node.void("player24") if refid is None: return root @@ -636,21 +736,21 @@ class PopnMusicModernBase(PopnMusicBase, ABC): if userid is None: return root - songid = request.child_value('music_num') + songid = request.child_value("music_num") chart = { self.GAME_CHART_TYPE_EASY: self.CHART_TYPE_EASY, self.GAME_CHART_TYPE_NORMAL: self.CHART_TYPE_NORMAL, self.GAME_CHART_TYPE_HYPER: self.CHART_TYPE_HYPER, self.GAME_CHART_TYPE_EX: self.CHART_TYPE_EX, - }[request.child_value('sheet_num')] - medal = request.child_value('clear_type') - points = request.child_value('score') - combo = request.child_value('combo') + }[request.child_value("sheet_num")] + medal = request.child_value("clear_type") + points = request.child_value("score") + combo = request.child_value("combo") stats = { - 'cool': request.child_value('cool'), - 'great': request.child_value('great'), - 'good': request.child_value('good'), - 'bad': request.child_value('bad') + "cool": request.child_value("cool"), + "great": request.child_value("great"), + "good": request.child_value("good"), + "bad": request.child_value("bad"), } medal = { self.GAME_PLAY_MEDAL_CIRCLE_FAILED: self.PLAY_MEDAL_CIRCLE_FAILED, @@ -665,24 +765,26 @@ class PopnMusicModernBase(PopnMusicBase, ABC): self.GAME_PLAY_MEDAL_STAR_FULL_COMBO: self.PLAY_MEDAL_STAR_FULL_COMBO, self.GAME_PLAY_MEDAL_PERFECT: self.PLAY_MEDAL_PERFECT, }[medal] - self.update_score(userid, songid, chart, points, medal, combo=combo, stats=stats) + self.update_score( + userid, songid, chart, points, medal, combo=combo, stats=stats + ) - if request.child_value('is_image_store') == 1: + if request.child_value("is_image_store") == 1: self.broadcast_score(userid, songid, chart, medal, points, combo, stats) return root def handle_player24_start_request(self, request: Node) -> Node: - root = Node.void('player24') - root.add_child(Node.s32('play_id', 0)) + root = Node.void("player24") + root.add_child(Node.s32("play_id", 0)) self.__construct_common_info(root) return root def handle_player24_logout_request(self, request: Node) -> Node: - return Node.void('player24') + return Node.void("player24") def handle_player24_buy_request(self, request: Node) -> Node: - refid = request.child_value('ref_id') + refid = request.child_value("ref_id") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -690,17 +792,19 @@ class PopnMusicModernBase(PopnMusicBase, ABC): userid = None if userid is not None: - itemid = request.child_value('id') - itemtype = request.child_value('type') - itemparam = request.child_value('param') + itemid = request.child_value("id") + itemtype = request.child_value("type") + itemparam = request.child_value("param") - price = request.child_value('price') - lumina = request.child_value('lumina') + price = request.child_value("price") + lumina = request.child_value("lumina") if lumina >= price: # Update player lumina balance - profile = self.get_profile(userid) or Profile(self.game, self.version, refid, 0) - profile.replace_int('player_point', lumina - price) + profile = self.get_profile(userid) or Profile( + self.game, self.version, refid, 0 + ) + profile.replace_int("player_point", lumina - price) self.put_profile(userid, profile) # Grant the object @@ -709,21 +813,21 @@ class PopnMusicModernBase(PopnMusicBase, ABC): self.version, userid, itemid, - f'item_{itemtype}', + f"item_{itemtype}", { - 'param': itemparam, - 'is_new': True, + "param": itemparam, + "is_new": True, }, ) - return Node.void('player24') + return Node.void("player24") def format_conversion(self, userid: UserID, profile: Profile) -> Node: - root = Node.void('player24') - root.add_child(Node.string('name', profile.get_str('name', 'なし'))) - root.add_child(Node.s16('chara', profile.get_int('chara', -1))) - root.add_child(Node.s8('con_type', 0)) - root.add_child(Node.s8('result', 1)) + root = Node.void("player24") + root.add_child(Node.string("name", profile.get_str("name", "なし"))) + root.add_child(Node.s16("chara", profile.get_int("chara", -1))) + root.add_child(Node.s8("con_type", 0)) + root.add_child(Node.s8("result", 1)) # Scores scores = self.data.remote.music.get_scores(self.game, self.version, userid) @@ -736,72 +840,123 @@ class PopnMusicModernBase(PopnMusicBase, ABC): self.CHART_TYPE_EX, ]: continue - if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY: + if score.data.get_int("medal") == self.PLAY_MEDAL_NO_PLAY: continue - music = Node.void('music') + music = Node.void("music") root.add_child(music) - music.add_child(Node.s16('music_num', score.id)) - music.add_child(Node.u8('sheet_num', { - self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY, - self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL, - self.CHART_TYPE_HYPER: self.GAME_CHART_TYPE_HYPER, - self.CHART_TYPE_EX: self.GAME_CHART_TYPE_EX, - }[score.chart])) - music.add_child(Node.s32('score', score.points)) - music.add_child(Node.u8('clear_type', { - self.PLAY_MEDAL_CIRCLE_FAILED: self.GAME_PLAY_MEDAL_CIRCLE_FAILED, - self.PLAY_MEDAL_DIAMOND_FAILED: self.GAME_PLAY_MEDAL_DIAMOND_FAILED, - self.PLAY_MEDAL_STAR_FAILED: self.GAME_PLAY_MEDAL_STAR_FAILED, - self.PLAY_MEDAL_EASY_CLEAR: self.GAME_PLAY_MEDAL_EASY_CLEAR, - self.PLAY_MEDAL_CIRCLE_CLEARED: self.GAME_PLAY_MEDAL_CIRCLE_CLEARED, - self.PLAY_MEDAL_DIAMOND_CLEARED: self.GAME_PLAY_MEDAL_DIAMOND_CLEARED, - self.PLAY_MEDAL_STAR_CLEARED: self.GAME_PLAY_MEDAL_STAR_CLEARED, - self.PLAY_MEDAL_CIRCLE_FULL_COMBO: self.GAME_PLAY_MEDAL_CIRCLE_FULL_COMBO, - self.PLAY_MEDAL_DIAMOND_FULL_COMBO: self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO, - self.PLAY_MEDAL_STAR_FULL_COMBO: self.GAME_PLAY_MEDAL_STAR_FULL_COMBO, - self.PLAY_MEDAL_PERFECT: self.GAME_PLAY_MEDAL_PERFECT, - }[score.data.get_int('medal')])) - music.add_child(Node.u8('clear_rank', self.__score_to_rank(score.points))) - music.add_child(Node.s16('cnt', score.plays)) + music.add_child(Node.s16("music_num", score.id)) + music.add_child( + Node.u8( + "sheet_num", + { + self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY, + self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL, + self.CHART_TYPE_HYPER: self.GAME_CHART_TYPE_HYPER, + self.CHART_TYPE_EX: self.GAME_CHART_TYPE_EX, + }[score.chart], + ) + ) + music.add_child(Node.s32("score", score.points)) + music.add_child( + Node.u8( + "clear_type", + { + self.PLAY_MEDAL_CIRCLE_FAILED: self.GAME_PLAY_MEDAL_CIRCLE_FAILED, + self.PLAY_MEDAL_DIAMOND_FAILED: self.GAME_PLAY_MEDAL_DIAMOND_FAILED, + self.PLAY_MEDAL_STAR_FAILED: self.GAME_PLAY_MEDAL_STAR_FAILED, + self.PLAY_MEDAL_EASY_CLEAR: self.GAME_PLAY_MEDAL_EASY_CLEAR, + self.PLAY_MEDAL_CIRCLE_CLEARED: self.GAME_PLAY_MEDAL_CIRCLE_CLEARED, + self.PLAY_MEDAL_DIAMOND_CLEARED: self.GAME_PLAY_MEDAL_DIAMOND_CLEARED, + self.PLAY_MEDAL_STAR_CLEARED: self.GAME_PLAY_MEDAL_STAR_CLEARED, + self.PLAY_MEDAL_CIRCLE_FULL_COMBO: self.GAME_PLAY_MEDAL_CIRCLE_FULL_COMBO, + self.PLAY_MEDAL_DIAMOND_FULL_COMBO: self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO, + self.PLAY_MEDAL_STAR_FULL_COMBO: self.GAME_PLAY_MEDAL_STAR_FULL_COMBO, + self.PLAY_MEDAL_PERFECT: self.GAME_PLAY_MEDAL_PERFECT, + }[score.data.get_int("medal")], + ) + ) + music.add_child(Node.u8("clear_rank", self.__score_to_rank(score.points))) + music.add_child(Node.s16("cnt", score.plays)) return root def format_extid(self, extid: int) -> str: data = str(extid) - crc = abs(binascii.crc32(data.encode('ascii'))) % 10000 - return f'{data}{crc:04d}' + crc = abs(binascii.crc32(data.encode("ascii"))) % 10000 + return f"{data}{crc:04d}" def format_profile(self, userid: UserID, profile: Profile) -> Node: - root = Node.void('player24') + root = Node.void("player24") # Mark this as a current profile - root.add_child(Node.s8('result', 0)) + root.add_child(Node.s8("result", 0)) # Basic account info - account = Node.void('account') + account = Node.void("account") root.add_child(account) - account.add_child(Node.string('g_pm_id', self.format_extid(profile.extid))) - account.add_child(Node.string('name', profile.get_str('name', 'なし'))) - account.add_child(Node.s16('area_id', profile.get_int('area_id'))) - account.add_child(Node.s16('use_navi', profile.get_int('use_navi'))) - account.add_child(Node.s16('read_news', profile.get_int('read_news'))) - account.add_child(Node.s16_array('nice', profile.get_int_array('nice', 30, [-1] * 30))) - account.add_child(Node.s16_array('favorite_chara', profile.get_int_array('favorite_chara', 20, [-1] * 20))) - account.add_child(Node.s16_array('special_area', profile.get_int_array('special_area', 8, [-1] * 8))) - account.add_child(Node.s16_array('chocolate_charalist', profile.get_int_array('chocolate_charalist', 5, [-1] * 5))) - account.add_child(Node.s32('chocolate_sp_chara', profile.get_int('chocolate_sp_chara', -1))) - account.add_child(Node.s32('chocolate_pass_cnt', profile.get_int('chocolate_pass_cnt'))) - account.add_child(Node.s32('chocolate_hon_cnt', profile.get_int('chocolate_hon_cnt'))) - account.add_child(Node.s16_array('teacher_setting', profile.get_int_array('teacher_setting', 10, [-1] * 10))) - account.add_child(Node.bool('welcom_pack', False)) # Set to true to grant extra stage no matter what. - account.add_child(Node.s32('ranking_node', profile.get_int('ranking_node'))) - account.add_child(Node.s32('chara_ranking_kind_id', profile.get_int('chara_ranking_kind_id'))) - account.add_child(Node.s8('navi_evolution_flg', profile.get_int('navi_evolution_flg'))) - account.add_child(Node.s32('ranking_news_last_no', profile.get_int('ranking_news_last_no'))) - account.add_child(Node.s32('power_point', profile.get_int('power_point'))) - account.add_child(Node.s32('player_point', profile.get_int('player_point', 300))) - account.add_child(Node.s32_array('power_point_list', profile.get_int_array('power_point_list', 20, [-1] * 20))) + account.add_child(Node.string("g_pm_id", self.format_extid(profile.extid))) + account.add_child(Node.string("name", profile.get_str("name", "なし"))) + account.add_child(Node.s16("area_id", profile.get_int("area_id"))) + account.add_child(Node.s16("use_navi", profile.get_int("use_navi"))) + account.add_child(Node.s16("read_news", profile.get_int("read_news"))) + account.add_child( + Node.s16_array("nice", profile.get_int_array("nice", 30, [-1] * 30)) + ) + account.add_child( + Node.s16_array( + "favorite_chara", profile.get_int_array("favorite_chara", 20, [-1] * 20) + ) + ) + account.add_child( + Node.s16_array( + "special_area", profile.get_int_array("special_area", 8, [-1] * 8) + ) + ) + account.add_child( + Node.s16_array( + "chocolate_charalist", + profile.get_int_array("chocolate_charalist", 5, [-1] * 5), + ) + ) + account.add_child( + Node.s32("chocolate_sp_chara", profile.get_int("chocolate_sp_chara", -1)) + ) + account.add_child( + Node.s32("chocolate_pass_cnt", profile.get_int("chocolate_pass_cnt")) + ) + account.add_child( + Node.s32("chocolate_hon_cnt", profile.get_int("chocolate_hon_cnt")) + ) + account.add_child( + Node.s16_array( + "teacher_setting", + profile.get_int_array("teacher_setting", 10, [-1] * 10), + ) + ) + account.add_child( + Node.bool("welcom_pack", False) + ) # Set to true to grant extra stage no matter what. + account.add_child(Node.s32("ranking_node", profile.get_int("ranking_node"))) + account.add_child( + Node.s32("chara_ranking_kind_id", profile.get_int("chara_ranking_kind_id")) + ) + account.add_child( + Node.s8("navi_evolution_flg", profile.get_int("navi_evolution_flg")) + ) + account.add_child( + Node.s32("ranking_news_last_no", profile.get_int("ranking_news_last_no")) + ) + account.add_child(Node.s32("power_point", profile.get_int("power_point"))) + account.add_child( + Node.s32("player_point", profile.get_int("player_point", 300)) + ) + account.add_child( + Node.s32_array( + "power_point_list", + profile.get_int_array("power_point_list", 20, [-1] * 20), + ) + ) # Tutorial handling is all sorts of crazy in UsaNeko. the tutorial flag # is split into two values. The game uses the flag modulo 100 for navigation @@ -864,38 +1019,53 @@ class PopnMusicModernBase(PopnMusicBase, ABC): # displayed. # 3 - All hold note tutorials are finished, this is a terminal state. statistics = self.get_play_statistics(userid) - account.add_child(Node.s16('tutorial', profile.get_int('tutorial', 100 if statistics.total_plays > 1 else 0))) + account.add_child( + Node.s16( + "tutorial", + profile.get_int("tutorial", 100 if statistics.total_plays > 1 else 0), + ) + ) # Stuff we never change - account.add_child(Node.s8('staff', 0)) - account.add_child(Node.s16('item_type', 0)) - account.add_child(Node.s16('item_id', 0)) - account.add_child(Node.s8('is_conv', 0)) - account.add_child(Node.s16_array('license_data', [-1] * 20)) + account.add_child(Node.s8("staff", 0)) + account.add_child(Node.s16("item_type", 0)) + account.add_child(Node.s16("item_id", 0)) + account.add_child(Node.s8("is_conv", 0)) + account.add_child(Node.s16_array("license_data", [-1] * 20)) # Song statistics - last_played = [x[0] for x in self.data.local.music.get_last_played(self.game, self.version, userid, 10)] - most_played = [x[0] for x in self.data.local.music.get_most_played(self.game, self.version, userid, 20)] + last_played = [ + x[0] + for x in self.data.local.music.get_last_played( + self.game, self.version, userid, 10 + ) + ] + most_played = [ + x[0] + for x in self.data.local.music.get_most_played( + self.game, self.version, userid, 20 + ) + ] while len(last_played) < 10: last_played.append(-1) while len(most_played) < 20: most_played.append(-1) - account.add_child(Node.s16_array('my_best', most_played)) - account.add_child(Node.s16_array('latest_music', last_played)) + account.add_child(Node.s16_array("my_best", most_played)) + account.add_child(Node.s16_array("latest_music", last_played)) # Player statistics - account.add_child(Node.s16('total_play_cnt', statistics.total_plays)) - account.add_child(Node.s16('today_play_cnt', statistics.today_plays)) - account.add_child(Node.s16('consecutive_days', statistics.consecutive_days)) - account.add_child(Node.s16('total_days', statistics.total_days)) - account.add_child(Node.s16('interval_day', 0)) + account.add_child(Node.s16("total_play_cnt", statistics.total_plays)) + account.add_child(Node.s16("today_play_cnt", statistics.today_plays)) + account.add_child(Node.s16("consecutive_days", statistics.consecutive_days)) + account.add_child(Node.s16("total_days", statistics.total_days)) + account.add_child(Node.s16("interval_day", 0)) # Number of rivals that are active for this version. links = self.data.local.user.get_links(self.game, self.version, userid) rivalcount = 0 for link in links: - if link.type != 'rival': + if link.type != "rival": continue if not self.has_profile(link.other_userid): @@ -903,168 +1073,194 @@ class PopnMusicModernBase(PopnMusicBase, ABC): # This profile is valid. rivalcount += 1 - account.add_child(Node.u8('active_fr_num', rivalcount)) + account.add_child(Node.u8("active_fr_num", rivalcount)) # eAmuse account link - eaappli = Node.void('eaappli') + eaappli = Node.void("eaappli") root.add_child(eaappli) - eaappli.add_child(Node.s8('relation', 1 if self.data.triggers.has_broadcast_destination(self.game) else -1)) + eaappli.add_child( + Node.s8( + "relation", + 1 if self.data.triggers.has_broadcast_destination(self.game) else -1, + ) + ) # Player info - info = Node.void('info') + info = Node.void("info") root.add_child(info) - info.add_child(Node.u16('ep', profile.get_int('ep'))) + info.add_child(Node.u16("ep", profile.get_int("ep"))) # Player config - config = Node.void('config') + config = Node.void("config") root.add_child(config) - config.add_child(Node.u8('mode', profile.get_int('mode'))) - config.add_child(Node.s16('chara', profile.get_int('chara', -1))) - config.add_child(Node.s16('music', profile.get_int('music', -1))) - config.add_child(Node.u8('sheet', profile.get_int('sheet'))) - config.add_child(Node.s8('category', profile.get_int('category', -1))) - config.add_child(Node.s8('sub_category', profile.get_int('sub_category', -1))) - config.add_child(Node.s8('chara_category', profile.get_int('chara_category', -1))) - config.add_child(Node.s16('course_id', profile.get_int('course_id', -1))) - config.add_child(Node.s8('course_folder', profile.get_int('course_folder', -1))) - config.add_child(Node.s8('ms_banner_disp', profile.get_int('ms_banner_disp', -1))) - config.add_child(Node.s8('ms_down_info', profile.get_int('ms_down_info', -1))) - config.add_child(Node.s8('ms_side_info', profile.get_int('ms_side_info', -1))) - config.add_child(Node.s8('ms_raise_type', profile.get_int('ms_raise_type', -1))) - config.add_child(Node.s8('ms_rnd_type', profile.get_int('ms_rnd_type', -1))) - config.add_child(Node.s8('banner_sort', profile.get_int('banner_sort', -1))) + config.add_child(Node.u8("mode", profile.get_int("mode"))) + config.add_child(Node.s16("chara", profile.get_int("chara", -1))) + config.add_child(Node.s16("music", profile.get_int("music", -1))) + config.add_child(Node.u8("sheet", profile.get_int("sheet"))) + config.add_child(Node.s8("category", profile.get_int("category", -1))) + config.add_child(Node.s8("sub_category", profile.get_int("sub_category", -1))) + config.add_child( + Node.s8("chara_category", profile.get_int("chara_category", -1)) + ) + config.add_child(Node.s16("course_id", profile.get_int("course_id", -1))) + config.add_child(Node.s8("course_folder", profile.get_int("course_folder", -1))) + config.add_child( + Node.s8("ms_banner_disp", profile.get_int("ms_banner_disp", -1)) + ) + config.add_child(Node.s8("ms_down_info", profile.get_int("ms_down_info", -1))) + config.add_child(Node.s8("ms_side_info", profile.get_int("ms_side_info", -1))) + config.add_child(Node.s8("ms_raise_type", profile.get_int("ms_raise_type", -1))) + config.add_child(Node.s8("ms_rnd_type", profile.get_int("ms_rnd_type", -1))) + config.add_child(Node.s8("banner_sort", profile.get_int("banner_sort", -1))) # Player options - option = Node.void('option') - option_dict = profile.get_dict('option') + option = Node.void("option") + option_dict = profile.get_dict("option") root.add_child(option) - option.add_child(Node.s16('hispeed', option_dict.get_int('hispeed'))) - option.add_child(Node.u8('popkun', option_dict.get_int('popkun'))) - option.add_child(Node.bool('hidden', option_dict.get_bool('hidden'))) - option.add_child(Node.s16('hidden_rate', option_dict.get_int('hidden_rate'))) - option.add_child(Node.bool('sudden', option_dict.get_bool('sudden'))) - option.add_child(Node.s16('sudden_rate', option_dict.get_int('sudden_rate'))) - option.add_child(Node.s8('randmir', option_dict.get_int('randmir'))) - option.add_child(Node.s8('gauge_type', option_dict.get_int('gauge_type'))) - option.add_child(Node.u8('ojama_0', option_dict.get_int('ojama_0'))) - option.add_child(Node.u8('ojama_1', option_dict.get_int('ojama_1'))) - option.add_child(Node.bool('forever_0', option_dict.get_bool('forever_0'))) - option.add_child(Node.bool('forever_1', option_dict.get_bool('forever_1'))) - option.add_child(Node.bool('full_setting', option_dict.get_bool('full_setting'))) - option.add_child(Node.u8('judge', option_dict.get_int('judge'))) - option.add_child(Node.s8('guide_se', option_dict.get_int('guide_se'))) + option.add_child(Node.s16("hispeed", option_dict.get_int("hispeed"))) + option.add_child(Node.u8("popkun", option_dict.get_int("popkun"))) + option.add_child(Node.bool("hidden", option_dict.get_bool("hidden"))) + option.add_child(Node.s16("hidden_rate", option_dict.get_int("hidden_rate"))) + option.add_child(Node.bool("sudden", option_dict.get_bool("sudden"))) + option.add_child(Node.s16("sudden_rate", option_dict.get_int("sudden_rate"))) + option.add_child(Node.s8("randmir", option_dict.get_int("randmir"))) + option.add_child(Node.s8("gauge_type", option_dict.get_int("gauge_type"))) + option.add_child(Node.u8("ojama_0", option_dict.get_int("ojama_0"))) + option.add_child(Node.u8("ojama_1", option_dict.get_int("ojama_1"))) + option.add_child(Node.bool("forever_0", option_dict.get_bool("forever_0"))) + option.add_child(Node.bool("forever_1", option_dict.get_bool("forever_1"))) + option.add_child( + Node.bool("full_setting", option_dict.get_bool("full_setting")) + ) + option.add_child(Node.u8("judge", option_dict.get_int("judge"))) + option.add_child(Node.s8("guide_se", option_dict.get_int("guide_se"))) # Player custom category - custom_cate = Node.void('custom_cate') + custom_cate = Node.void("custom_cate") root.add_child(custom_cate) - custom_cate.add_child(Node.s8('valid', 0)) - custom_cate.add_child(Node.s8('lv_min', -1)) - custom_cate.add_child(Node.s8('lv_max', -1)) - custom_cate.add_child(Node.s8('medal_min', -1)) - custom_cate.add_child(Node.s8('medal_max', -1)) - custom_cate.add_child(Node.s8('friend_no', -1)) - custom_cate.add_child(Node.s8('score_flg', -1)) + custom_cate.add_child(Node.s8("valid", 0)) + custom_cate.add_child(Node.s8("lv_min", -1)) + custom_cate.add_child(Node.s8("lv_max", -1)) + custom_cate.add_child(Node.s8("medal_min", -1)) + custom_cate.add_child(Node.s8("medal_max", -1)) + custom_cate.add_child(Node.s8("friend_no", -1)) + custom_cate.add_child(Node.s8("score_flg", -1)) # Navi data - navi_data = Node.void('navi_data') + navi_data = Node.void("navi_data") root.add_child(navi_data) - if 'navi_points' in profile: - navi_data.add_child(Node.s32_array('raisePoint', profile.get_int_array('navi_points', 5))) + if "navi_points" in profile: + navi_data.add_child( + Node.s32_array("raisePoint", profile.get_int_array("navi_points", 5)) + ) game_config = self.get_game_config() - if game_config.get_bool('force_unlock_songs'): - songs = {song.id for song in self.data.local.music.get_all_songs(self.game, self.version)} + if game_config.get_bool("force_unlock_songs"): + songs = { + song.id + for song in self.data.local.music.get_all_songs(self.game, self.version) + } for song in songs: - item = Node.void('item') + item = Node.void("item") root.add_child(item) - item.add_child(Node.u8('type', 0)) - item.add_child(Node.u16('id', song)) - item.add_child(Node.u16('param', 15)) - item.add_child(Node.bool('is_new', False)) - item.add_child(Node.u64('get_time', 0)) + item.add_child(Node.u8("type", 0)) + item.add_child(Node.u16("id", song)) + item.add_child(Node.u16("param", 15)) + item.add_child(Node.bool("is_new", False)) + item.add_child(Node.u64("get_time", 0)) # Set up achievements - achievements = self.data.local.user.get_achievements(self.game, self.version, userid) + achievements = self.data.local.user.get_achievements( + self.game, self.version, userid + ) for achievement in achievements: - if achievement.type[:5] == 'item_': + if achievement.type[:5] == "item_": itemtype = int(achievement.type[5:]) - param = achievement.data.get_int('param') - is_new = achievement.data.get_bool('is_new') - get_time = achievement.data.get_int('get_time') + param = achievement.data.get_int("param") + is_new = achievement.data.get_bool("is_new") + get_time = achievement.data.get_int("get_time") # Item type can be 0-6 inclusive and is the type of the unlock/item. # Item 0 is music unlocks. In this case, the id is the song ID according # to the game. Unclear what the param is supposed to be, but i've seen # seen 8 and 0. Might be what chart is available? - if game_config.get_bool('force_unlock_songs') and itemtype == 0: + if game_config.get_bool("force_unlock_songs") and itemtype == 0: # We already sent song unlocks in the force unlock section above. continue - item = Node.void('item') + item = Node.void("item") root.add_child(item) - item.add_child(Node.u8('type', itemtype)) - item.add_child(Node.u16('id', achievement.id)) - item.add_child(Node.u16('param', param)) - item.add_child(Node.bool('is_new', is_new)) - item.add_child(Node.u64('get_time', get_time)) + item.add_child(Node.u8("type", itemtype)) + item.add_child(Node.u16("id", achievement.id)) + item.add_child(Node.u16("param", param)) + item.add_child(Node.bool("is_new", is_new)) + item.add_child(Node.u64("get_time", get_time)) - elif achievement.type == 'chara': - friendship = achievement.data.get_int('friendship') + elif achievement.type == "chara": + friendship = achievement.data.get_int("friendship") - chara = Node.void('chara_param') + chara = Node.void("chara_param") root.add_child(chara) - chara.add_child(Node.u16('chara_id', achievement.id)) - chara.add_child(Node.u16('friendship', friendship)) + chara.add_child(Node.u16("chara_id", achievement.id)) + chara.add_child(Node.u16("friendship", friendship)) - elif achievement.type == 'navi': + elif achievement.type == "navi": # There should only be 12 of these. - friendship = achievement.data.get_int('friendship') + friendship = achievement.data.get_int("friendship") # This relies on the above Navi data section to ensure the navi_param # node is created. - navi_param = Node.void('navi_param') + navi_param = Node.void("navi_param") navi_data.add_child(navi_param) - navi_param.add_child(Node.u16('navi_id', achievement.id)) - navi_param.add_child(Node.s32('friendship', friendship)) + navi_param.add_child(Node.u16("navi_id", achievement.id)) + navi_param.add_child(Node.s32("friendship", friendship)) - elif achievement.type == 'area': + elif achievement.type == "area": # There should only be 16 of these. - index = achievement.data.get_int('index') - points = achievement.data.get_int('points') - cleared = achievement.data.get_bool('cleared') - diary = achievement.data.get_int('diary') + index = achievement.data.get_int("index") + points = achievement.data.get_int("points") + cleared = achievement.data.get_bool("cleared") + diary = achievement.data.get_int("diary") - area = Node.void('area') + area = Node.void("area") root.add_child(area) - area.add_child(Node.u32('area_id', achievement.id)) - area.add_child(Node.u8('chapter_index', index)) - area.add_child(Node.u16('gauge_point', points)) - area.add_child(Node.bool('is_cleared', cleared)) - area.add_child(Node.u32('diary', diary)) + area.add_child(Node.u32("area_id", achievement.id)) + area.add_child(Node.u8("chapter_index", index)) + area.add_child(Node.u16("gauge_point", points)) + area.add_child(Node.bool("is_cleared", cleared)) + area.add_child(Node.u32("diary", diary)) - elif achievement.type[:7] == 'course_': + elif achievement.type[:7] == "course_": sheet = int(achievement.type[7:]) - course_data = Node.void('course_data') + course_data = Node.void("course_data") root.add_child(course_data) - course_data.add_child(Node.s16('course_id', achievement.id)) - course_data.add_child(Node.u8('clear_type', achievement.data.get_int('clear_type'))) - course_data.add_child(Node.u8('clear_rank', achievement.data.get_int('clear_rank'))) - course_data.add_child(Node.s32('total_score', achievement.data.get_int('score'))) - course_data.add_child(Node.s32('update_count', achievement.data.get_int('count'))) - course_data.add_child(Node.u8('sheet_num', sheet)) + course_data.add_child(Node.s16("course_id", achievement.id)) + course_data.add_child( + Node.u8("clear_type", achievement.data.get_int("clear_type")) + ) + course_data.add_child( + Node.u8("clear_rank", achievement.data.get_int("clear_rank")) + ) + course_data.add_child( + Node.s32("total_score", achievement.data.get_int("score")) + ) + course_data.add_child( + Node.s32("update_count", achievement.data.get_int("count")) + ) + course_data.add_child(Node.u8("sheet_num", sheet)) - elif achievement.type == 'fes': - index = achievement.data.get_int('index') - points = achievement.data.get_int('points') - cleared = achievement.data.get_bool('cleared') + elif achievement.type == "fes": + index = achievement.data.get_int("index") + points = achievement.data.get_int("points") + cleared = achievement.data.get_bool("cleared") - fes = Node.void('fes') + fes = Node.void("fes") root.add_child(fes) - fes.add_child(Node.u32('fes_id', achievement.id)) - fes.add_child(Node.u8('chapter_index', index)) - fes.add_child(Node.u16('gauge_point', points)) - fes.add_child(Node.bool('is_cleared', cleared)) + fes.add_child(Node.u32("fes_id", achievement.id)) + fes.add_child(Node.u8("chapter_index", index)) + fes.add_child(Node.u16("gauge_point", points)) + fes.add_child(Node.bool("is_cleared", cleared)) # Handle daily mission. Note that we should be presenting 3 random IDs # in the range of 1-228 inclusive, and presenting three new ones per day. @@ -1083,7 +1279,7 @@ class PopnMusicModernBase(PopnMusicBase, ABC): # today, the defaults will be set after this loop so we at least # give the game the right ID. for achievement in achievements: - if achievement.type == 'mission': + if achievement.type == "mission": daily_missions[achievement.id] = achievement.data while len(daily_missions) < 3: @@ -1095,164 +1291,202 @@ class PopnMusicModernBase(PopnMusicBase, ABC): if i >= 3: break - points = data.get_int('points') - complete = data.get_int('complete') + points = data.get_int("points") + complete = data.get_int("complete") - mission = Node.void('mission') + mission = Node.void("mission") root.add_child(mission) - mission.add_child(Node.u32('mission_id', daily_id)) - mission.add_child(Node.u32('gauge_point', points)) - mission.add_child(Node.u32('mission_comp', complete)) + mission.add_child(Node.u32("mission_id", daily_id)) + mission.add_child(Node.u32("gauge_point", points)) + mission.add_child(Node.u32("mission_comp", complete)) # Player netvs section - netvs = Node.void('netvs') + netvs = Node.void("netvs") root.add_child(netvs) - netvs.add_child(Node.s16_array('record', [0] * 6)) - netvs.add_child(Node.string('dialog', '')) - netvs.add_child(Node.string('dialog', '')) - netvs.add_child(Node.string('dialog', '')) - netvs.add_child(Node.string('dialog', '')) - netvs.add_child(Node.string('dialog', '')) - netvs.add_child(Node.string('dialog', '')) - netvs.add_child(Node.s8_array('ojama_condition', [0] * 74)) - netvs.add_child(Node.s8_array('set_ojama', [0] * 3)) - netvs.add_child(Node.s8_array('set_recommend', [0] * 3)) - netvs.add_child(Node.u32('netvs_play_cnt', 0)) + netvs.add_child(Node.s16_array("record", [0] * 6)) + netvs.add_child(Node.string("dialog", "")) + netvs.add_child(Node.string("dialog", "")) + netvs.add_child(Node.string("dialog", "")) + netvs.add_child(Node.string("dialog", "")) + netvs.add_child(Node.string("dialog", "")) + netvs.add_child(Node.string("dialog", "")) + netvs.add_child(Node.s8_array("ojama_condition", [0] * 74)) + netvs.add_child(Node.s8_array("set_ojama", [0] * 3)) + netvs.add_child(Node.s8_array("set_recommend", [0] * 3)) + netvs.add_child(Node.u32("netvs_play_cnt", 0)) # Character customizations - customize = Node.void('customize') + customize = Node.void("customize") root.add_child(customize) - customize.add_child(Node.u16('effect_left', profile.get_int('effect_left'))) - customize.add_child(Node.u16('effect_center', profile.get_int('effect_center'))) - customize.add_child(Node.u16('effect_right', profile.get_int('effect_right'))) - customize.add_child(Node.u16('hukidashi', profile.get_int('hukidashi'))) - customize.add_child(Node.u16('comment_1', profile.get_int('comment_1'))) - customize.add_child(Node.u16('comment_2', profile.get_int('comment_2'))) + customize.add_child(Node.u16("effect_left", profile.get_int("effect_left"))) + customize.add_child(Node.u16("effect_center", profile.get_int("effect_center"))) + customize.add_child(Node.u16("effect_right", profile.get_int("effect_right"))) + customize.add_child(Node.u16("hukidashi", profile.get_int("hukidashi"))) + customize.add_child(Node.u16("comment_1", profile.get_int("comment_1"))) + customize.add_child(Node.u16("comment_2", profile.get_int("comment_2"))) # Stamp stuff - stamp = Node.void('stamp') + stamp = Node.void("stamp") root.add_child(stamp) - stamp.add_child(Node.s16('stamp_id', profile.get_int('stamp_id'))) - stamp.add_child(Node.s16('cnt', profile.get_int('stamp_cnt'))) + stamp.add_child(Node.s16("stamp_id", profile.get_int("stamp_id"))) + stamp.add_child(Node.s16("cnt", profile.get_int("stamp_cnt"))) return root - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() - account = request.child('account') + account = request.child("account") if account is not None: - newprofile.replace_int('tutorial', account.child_value('tutorial')) - newprofile.replace_int('read_news', account.child_value('read_news')) - newprofile.replace_int('area_id', account.child_value('area_id')) - newprofile.replace_int('use_navi', account.child_value('use_navi')) - newprofile.replace_int('ranking_node', account.child_value('ranking_node')) - newprofile.replace_int('chara_ranking_kind_id', account.child_value('chara_ranking_kind_id')) - newprofile.replace_int('navi_evolution_flg', account.child_value('navi_evolution_flg')) - newprofile.replace_int('ranking_news_last_no', account.child_value('ranking_news_last_no')) - newprofile.replace_int('power_point', account.child_value('power_point')) - newprofile.replace_int('player_point', account.child_value('player_point')) + newprofile.replace_int("tutorial", account.child_value("tutorial")) + newprofile.replace_int("read_news", account.child_value("read_news")) + newprofile.replace_int("area_id", account.child_value("area_id")) + newprofile.replace_int("use_navi", account.child_value("use_navi")) + newprofile.replace_int("ranking_node", account.child_value("ranking_node")) + newprofile.replace_int( + "chara_ranking_kind_id", account.child_value("chara_ranking_kind_id") + ) + newprofile.replace_int( + "navi_evolution_flg", account.child_value("navi_evolution_flg") + ) + newprofile.replace_int( + "ranking_news_last_no", account.child_value("ranking_news_last_no") + ) + newprofile.replace_int("power_point", account.child_value("power_point")) + newprofile.replace_int("player_point", account.child_value("player_point")) - newprofile.replace_int_array('nice', 30, account.child_value('nice')) - newprofile.replace_int_array('favorite_chara', 20, account.child_value('favorite_chara')) - newprofile.replace_int_array('special_area', 8, account.child_value('special_area')) - newprofile.replace_int_array('chocolate_charalist', 5, account.child_value('chocolate_charalist')) - newprofile.replace_int('chocolate_sp_chara', account.child_value('chocolate_sp_chara')) - newprofile.replace_int('chocolate_pass_cnt', account.child_value('chocolate_pass_cnt')) - newprofile.replace_int('chocolate_hon_cnt', account.child_value('chocolate_hon_cnt')) - newprofile.replace_int('chocolate_giri_cnt', account.child_value('chocolate_giri_cnt')) - newprofile.replace_int('chocolate_kokyu_cnt', account.child_value('chocolate_kokyu_cnt')) - newprofile.replace_int_array('teacher_setting', 10, account.child_value('teacher_setting')) - newprofile.replace_int_array('power_point_list', 20, account.child_value('power_point_list')) + newprofile.replace_int_array("nice", 30, account.child_value("nice")) + newprofile.replace_int_array( + "favorite_chara", 20, account.child_value("favorite_chara") + ) + newprofile.replace_int_array( + "special_area", 8, account.child_value("special_area") + ) + newprofile.replace_int_array( + "chocolate_charalist", 5, account.child_value("chocolate_charalist") + ) + newprofile.replace_int( + "chocolate_sp_chara", account.child_value("chocolate_sp_chara") + ) + newprofile.replace_int( + "chocolate_pass_cnt", account.child_value("chocolate_pass_cnt") + ) + newprofile.replace_int( + "chocolate_hon_cnt", account.child_value("chocolate_hon_cnt") + ) + newprofile.replace_int( + "chocolate_giri_cnt", account.child_value("chocolate_giri_cnt") + ) + newprofile.replace_int( + "chocolate_kokyu_cnt", account.child_value("chocolate_kokyu_cnt") + ) + newprofile.replace_int_array( + "teacher_setting", 10, account.child_value("teacher_setting") + ) + newprofile.replace_int_array( + "power_point_list", 20, account.child_value("power_point_list") + ) - info = request.child('info') + info = request.child("info") if info is not None: - newprofile.replace_int('ep', info.child_value('ep')) + newprofile.replace_int("ep", info.child_value("ep")) - stamp = request.child('stamp') + stamp = request.child("stamp") if stamp is not None: - newprofile.replace_int('stamp_id', stamp.child_value('stamp_id')) - newprofile.replace_int('stamp_cnt', stamp.child_value('cnt')) + newprofile.replace_int("stamp_id", stamp.child_value("stamp_id")) + newprofile.replace_int("stamp_cnt", stamp.child_value("cnt")) - config = request.child('config') + config = request.child("config") if config is not None: - newprofile.replace_int('mode', config.child_value('mode')) - newprofile.replace_int('chara', config.child_value('chara')) - newprofile.replace_int('music', config.child_value('music')) - newprofile.replace_int('sheet', config.child_value('sheet')) - newprofile.replace_int('category', config.child_value('category')) - newprofile.replace_int('sub_category', config.child_value('sub_category')) - newprofile.replace_int('chara_category', config.child_value('chara_category')) - newprofile.replace_int('course_id', config.child_value('course_id')) - newprofile.replace_int('course_folder', config.child_value('course_folder')) - newprofile.replace_int('ms_banner_disp', config.child_value('ms_banner_disp')) - newprofile.replace_int('ms_down_info', config.child_value('ms_down_info')) - newprofile.replace_int('ms_side_info', config.child_value('ms_side_info')) - newprofile.replace_int('ms_raise_type', config.child_value('ms_raise_type')) - newprofile.replace_int('ms_rnd_type', config.child_value('ms_rnd_type')) - newprofile.replace_int('banner_sort', config.child_value('banner_sort')) + newprofile.replace_int("mode", config.child_value("mode")) + newprofile.replace_int("chara", config.child_value("chara")) + newprofile.replace_int("music", config.child_value("music")) + newprofile.replace_int("sheet", config.child_value("sheet")) + newprofile.replace_int("category", config.child_value("category")) + newprofile.replace_int("sub_category", config.child_value("sub_category")) + newprofile.replace_int( + "chara_category", config.child_value("chara_category") + ) + newprofile.replace_int("course_id", config.child_value("course_id")) + newprofile.replace_int("course_folder", config.child_value("course_folder")) + newprofile.replace_int( + "ms_banner_disp", config.child_value("ms_banner_disp") + ) + newprofile.replace_int("ms_down_info", config.child_value("ms_down_info")) + newprofile.replace_int("ms_side_info", config.child_value("ms_side_info")) + newprofile.replace_int("ms_raise_type", config.child_value("ms_raise_type")) + newprofile.replace_int("ms_rnd_type", config.child_value("ms_rnd_type")) + newprofile.replace_int("banner_sort", config.child_value("banner_sort")) - option_dict = newprofile.get_dict('option') - option = request.child('option') + option_dict = newprofile.get_dict("option") + option = request.child("option") if option is not None: - option_dict.replace_int('hispeed', option.child_value('hispeed')) - option_dict.replace_int('popkun', option.child_value('popkun')) - option_dict.replace_bool('hidden', option.child_value('hidden')) - option_dict.replace_int('hidden_rate', option.child_value('hidden_rate')) - option_dict.replace_bool('sudden', option.child_value('sudden')) - option_dict.replace_int('sudden_rate', option.child_value('sudden_rate')) - option_dict.replace_int('randmir', option.child_value('randmir')) - option_dict.replace_int('gauge_type', option.child_value('gauge_type')) - option_dict.replace_int('ojama_0', option.child_value('ojama_0')) - option_dict.replace_int('ojama_1', option.child_value('ojama_1')) - option_dict.replace_bool('forever_0', option.child_value('forever_0')) - option_dict.replace_bool('forever_1', option.child_value('forever_1')) - option_dict.replace_bool('full_setting', option.child_value('full_setting')) - option_dict.replace_int('judge', option.child_value('judge')) - option_dict.replace_int('guide_se', option.child_value('guide_se')) - newprofile.replace_dict('option', option_dict) + option_dict.replace_int("hispeed", option.child_value("hispeed")) + option_dict.replace_int("popkun", option.child_value("popkun")) + option_dict.replace_bool("hidden", option.child_value("hidden")) + option_dict.replace_int("hidden_rate", option.child_value("hidden_rate")) + option_dict.replace_bool("sudden", option.child_value("sudden")) + option_dict.replace_int("sudden_rate", option.child_value("sudden_rate")) + option_dict.replace_int("randmir", option.child_value("randmir")) + option_dict.replace_int("gauge_type", option.child_value("gauge_type")) + option_dict.replace_int("ojama_0", option.child_value("ojama_0")) + option_dict.replace_int("ojama_1", option.child_value("ojama_1")) + option_dict.replace_bool("forever_0", option.child_value("forever_0")) + option_dict.replace_bool("forever_1", option.child_value("forever_1")) + option_dict.replace_bool("full_setting", option.child_value("full_setting")) + option_dict.replace_int("judge", option.child_value("judge")) + option_dict.replace_int("guide_se", option.child_value("guide_se")) + newprofile.replace_dict("option", option_dict) - customize = request.child('customize') + customize = request.child("customize") if customize is not None: - newprofile.replace_int('effect_left', customize.child_value('effect_left')) - newprofile.replace_int('effect_center', customize.child_value('effect_center')) - newprofile.replace_int('effect_right', customize.child_value('effect_right')) - newprofile.replace_int('hukidashi', customize.child_value('hukidashi')) - newprofile.replace_int('comment_1', customize.child_value('comment_1')) - newprofile.replace_int('comment_2', customize.child_value('comment_2')) + newprofile.replace_int("effect_left", customize.child_value("effect_left")) + newprofile.replace_int( + "effect_center", customize.child_value("effect_center") + ) + newprofile.replace_int( + "effect_right", customize.child_value("effect_right") + ) + newprofile.replace_int("hukidashi", customize.child_value("hukidashi")) + newprofile.replace_int("comment_1", customize.child_value("comment_1")) + newprofile.replace_int("comment_2", customize.child_value("comment_2")) - navi_data = request.child('navi_data') + navi_data = request.child("navi_data") if navi_data is not None: - newprofile.replace_int_array('navi_points', 5, navi_data.child_value('raisePoint')) + newprofile.replace_int_array( + "navi_points", 5, navi_data.child_value("raisePoint") + ) # Extract navi achievements for node in navi_data.children: - if node.name == 'navi_param': - navi_id = node.child_value('navi_id') - friendship = node.child_value('friendship') + if node.name == "navi_param": + navi_id = node.child_value("navi_id") + friendship = node.child_value("friendship") self.data.local.user.put_achievement( self.game, self.version, userid, navi_id, - 'navi', + "navi", { - 'friendship': friendship, + "friendship": friendship, }, ) # Extract achievements game_config = self.get_game_config() for node in request.children: - if node.name == 'item': - itemid = node.child_value('id') - itemtype = node.child_value('type') - param = node.child_value('param') - is_new = node.child_value('is_new') - get_time = node.child_value('get_time') + if node.name == "item": + itemid = node.child_value("id") + itemtype = node.child_value("type") + param = node.child_value("param") + is_new = node.child_value("is_new") + get_time = node.child_value("get_time") - if game_config.get_bool('force_unlock_songs') and itemtype == 0: + if game_config.get_bool("force_unlock_songs") and itemtype == 0: # If we enabled force song unlocks, don't save songs to the profile. continue @@ -1261,68 +1495,68 @@ class PopnMusicModernBase(PopnMusicBase, ABC): self.version, userid, itemid, - f'item_{itemtype}', + f"item_{itemtype}", { - 'param': param, - 'is_new': is_new, - 'get_time': get_time, + "param": param, + "is_new": is_new, + "get_time": get_time, }, ) - elif node.name == 'chara_param': - charaid = node.child_value('chara_id') - friendship = node.child_value('friendship') + elif node.name == "chara_param": + charaid = node.child_value("chara_id") + friendship = node.child_value("friendship") self.data.local.user.put_achievement( self.game, self.version, userid, charaid, - 'chara', + "chara", { - 'friendship': friendship, + "friendship": friendship, }, ) - elif node.name == 'area': - area_id = node.child_value('area_id') - index = node.child_value('chapter_index') - points = node.child_value('gauge_point') - cleared = node.child_value('is_cleared') - diary = node.child_value('diary') + elif node.name == "area": + area_id = node.child_value("area_id") + index = node.child_value("chapter_index") + points = node.child_value("gauge_point") + cleared = node.child_value("is_cleared") + diary = node.child_value("diary") self.data.local.user.put_achievement( self.game, self.version, userid, area_id, - 'area', + "area", { - 'index': index, - 'points': points, - 'cleared': cleared, - 'diary': diary, + "index": index, + "points": points, + "cleared": cleared, + "diary": diary, }, ) - elif node.name == 'mission': + elif node.name == "mission": # If you don't send the right values on login, then # the game sends 0 for mission_id three times. Skip # those values since they're bogus. - mission_id = node.child_value('mission_id') + mission_id = node.child_value("mission_id") if mission_id > 0: - points = node.child_value('gauge_point') - complete = node.child_value('mission_comp') + points = node.child_value("gauge_point") + complete = node.child_value("mission_comp") self.data.local.user.put_time_based_achievement( self.game, self.version, userid, mission_id, - 'mission', + "mission", { - 'points': points, - 'complete': complete, + "points": points, + "complete": complete, }, ) @@ -1333,11 +1567,11 @@ class PopnMusicModernBase(PopnMusicBase, ABC): self.version, userid, songid, - 'item_0', + "item_0", { - 'param': 0xF, - 'is_new': False, - 'get_time': Time.now(), + "param": 0xF, + "is_new": False, + "get_time": Time.now(), }, ) diff --git a/bemani/backend/popn/eclale.py b/bemani/backend/popn/eclale.py index 6420f0b..5f5555d 100644 --- a/bemani/backend/popn/eclale.py +++ b/bemani/backend/popn/eclale.py @@ -47,51 +47,51 @@ class PopnMusicEclale(PopnMusicBase): Return all of our front-end modifiably settings. """ return { - 'ints': [ + "ints": [ { - 'name': 'Music Open Phase', - 'tip': 'Default music phase for all players.', - 'category': 'game_config', - 'setting': 'music_phase', - 'values': { - 0: 'No music unlocks', - 1: 'Phase 1', - 2: 'Phase 2', - 3: 'Phase 3', - 4: 'Phase 4', - 5: 'Phase 5', - 6: 'Phase 6', - 7: 'Phase 7', - 8: 'Phase 8', - 9: 'Phase 9', - 10: 'Phase 10', - 11: 'Phase 11', - 12: 'Phase 12', - 13: 'Phase 13', - 14: 'Phase 14', - 15: 'Phase 15', - 16: 'Phase MAX', - } + "name": "Music Open Phase", + "tip": "Default music phase for all players.", + "category": "game_config", + "setting": "music_phase", + "values": { + 0: "No music unlocks", + 1: "Phase 1", + 2: "Phase 2", + 3: "Phase 3", + 4: "Phase 4", + 5: "Phase 5", + 6: "Phase 6", + 7: "Phase 7", + 8: "Phase 8", + 9: "Phase 9", + 10: "Phase 10", + 11: "Phase 11", + 12: "Phase 12", + 13: "Phase 13", + 14: "Phase 14", + 15: "Phase 15", + 16: "Phase MAX", + }, }, { - 'name': 'Additional Music Unlock Phase', - 'tip': 'Additional music unlock phase for all players.', - 'category': 'game_config', - 'setting': 'music_sub_phase', - 'values': { - 0: 'No additional unlocks', - 1: 'Phase 1', - 2: 'Phase 2', - 3: 'Phase MAX', + "name": "Additional Music Unlock Phase", + "tip": "Additional music unlock phase for all players.", + "category": "game_config", + "setting": "music_sub_phase", + "values": { + 0: "No additional unlocks", + 1: "Phase 1", + 2: "Phase 2", + 3: "Phase MAX", }, }, ], - 'bools': [ + "bools": [ { - 'name': 'Enable Starmaker Event', - 'tip': 'Enable Starmaker event as well as song shop.', - 'category': 'game_config', - 'setting': 'starmaker_enable', + "name": "Enable Starmaker Event", + "tip": "Enable Starmaker event as well as song shop.", + "category": "game_config", + "setting": "starmaker_enable", }, # We don't currently support lobbies or anything, so this is commented out until # somebody gets around to implementing it. @@ -102,18 +102,18 @@ class PopnMusicEclale(PopnMusicBase): # 'setting': 'enable_net_taisen', # }, { - 'name': 'Force Song Unlock', - 'tip': 'Force unlock all songs.', - 'category': 'game_config', - 'setting': 'force_unlock_songs', + "name": "Force Song Unlock", + "tip": "Force unlock all songs.", + "category": "game_config", + "setting": "force_unlock_songs", }, ], } def __construct_common_info(self, root: Node) -> None: game_config = self.get_game_config() - music_phase = game_config.get_int('music_phase') - music_sub_phase = game_config.get_int('music_sub_phase') + music_phase = game_config.get_int("music_phase") + music_sub_phase = game_config.get_int("music_sub_phase") enable_net_taisen = False # game_config.get_bool('enable_net_taisen') # Event phases. Eclale seems to be so basic that there is no way to disable/enable @@ -167,25 +167,25 @@ class PopnMusicEclale(PopnMusicBase): } for phaseid in phases: - phase = Node.void('phase') + phase = Node.void("phase") root.add_child(phase) - phase.add_child(Node.s16('event_id', phaseid)) - phase.add_child(Node.s16('phase', phases[phaseid])) + phase.add_child(Node.s16("event_id", phaseid)) + phase.add_child(Node.s16("phase", phases[phaseid])) - if game_config.get_bool('starmaker_enable'): + if game_config.get_bool("starmaker_enable"): for areaid in range(1, 51): - area = Node.void('area') + area = Node.void("area") root.add_child(area) - area.add_child(Node.s16('area_id', areaid)) - area.add_child(Node.u64('end_date', 0)) - area.add_child(Node.s16('medal_id', areaid)) - area.add_child(Node.bool('is_limit', False)) + area.add_child(Node.s16("area_id", areaid)) + area.add_child(Node.u64("end_date", 0)) + area.add_child(Node.s16("medal_id", areaid)) + area.add_child(Node.bool("is_limit", False)) # Calculate most popular characters profiles = self.data.remote.user.get_all_profiles(self.game, self.version) charas: Dict[int, int] = {} for (_userid, profile) in profiles: - chara = profile.get_int('chara', -1) + chara = profile.get_int("chara", -1) if chara <= 0: continue if chara not in charas: @@ -203,17 +203,19 @@ class PopnMusicEclale(PopnMusicBase): # Output the top 20 of them rank = 1 for (charaid, _usecount) in charamap[:20]: - popular = Node.void('popular') + popular = Node.void("popular") root.add_child(popular) - popular.add_child(Node.s16('rank', rank)) - popular.add_child(Node.s16('chara_num', charaid)) + popular.add_child(Node.s16("rank", rank)) + popular.add_child(Node.s16("chara_num", charaid)) rank = rank + 1 # Output the hit chart - for (songid, _plays) in self.data.local.music.get_hit_chart(self.game, self.version, 500): - popular_music = Node.void('popular_music') + for (songid, _plays) in self.data.local.music.get_hit_chart( + self.game, self.version, 500 + ): + popular_music = Node.void("popular_music") root.add_child(popular_music) - popular_music.add_child(Node.s16('music_num', songid)) + popular_music.add_child(Node.s16("music_num", songid)) # Output goods prices for goodsid in range(1, 421): @@ -228,55 +230,55 @@ class PopnMusicEclale(PopnMusicBase): elif goodsid >= 301 and goodsid <= 420: price = 150 else: - raise Exception('Invalid goods ID!') - goods = Node.void('goods') + raise Exception("Invalid goods ID!") + goods = Node.void("goods") root.add_child(goods) - goods.add_child(Node.s16('goods_id', goodsid)) - goods.add_child(Node.s32('price', price)) - goods.add_child(Node.s16('goods_type', 0)) + goods.add_child(Node.s16("goods_id", goodsid)) + goods.add_child(Node.s32("price", price)) + goods.add_child(Node.s16("goods_type", 0)) def handle_pcb23_boot_request(self, request: Node) -> Node: - return Node.void('pcb23') + return Node.void("pcb23") def handle_pcb23_error_request(self, request: Node) -> Node: - return Node.void('pcb23') + return Node.void("pcb23") def handle_pcb23_dlstatus_request(self, request: Node) -> Node: - return Node.void('pcb23') + return Node.void("pcb23") def handle_pcb23_write_request(self, request: Node) -> Node: # Update the name of this cab for admin purposes - self.update_machine_name(request.child_value('pcb_setting/name')) - return Node.void('pcb23') + self.update_machine_name(request.child_value("pcb_setting/name")) + return Node.void("pcb23") def handle_info23_common_request(self, request: Node) -> Node: - info = Node.void('info23') + info = Node.void("info23") self.__construct_common_info(info) return info def handle_lobby22_requests(self, request: Node) -> Node: # Stub out the entire lobby22 service (yes, its lobby22 in Pop'n 23) - return Node.void('lobby22') + return Node.void("lobby22") def handle_player23_start_request(self, request: Node) -> Node: - root = Node.void('player23') - root.add_child(Node.s32('play_id', 0)) + root = Node.void("player23") + root.add_child(Node.s32("play_id", 0)) self.__construct_common_info(root) return root def handle_player23_logout_request(self, request: Node) -> Node: - return Node.void('player23') + return Node.void("player23") def handle_player23_read_request(self, request: Node) -> Node: - refid = request.child_value('ref_id') + refid = request.child_value("ref_id") root = self.get_profile_by_refid(refid, self.OLD_PROFILE_FALLTHROUGH) if root is None: - root = Node.void('player23') - root.add_child(Node.s8('result', 2)) + root = Node.void("player23") + root.add_child(Node.s8("result", 2)) return root def handle_player23_write_request(self, request: Node) -> Node: - refid = request.child_value('ref_id') + refid = request.child_value("ref_id") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -284,35 +286,37 @@ class PopnMusicEclale(PopnMusicBase): userid = None if userid is not None: - oldprofile = self.get_profile(userid) or Profile(self.game, self.version, refid, 0) + oldprofile = self.get_profile(userid) or Profile( + self.game, self.version, refid, 0 + ) newprofile = self.unformat_profile(userid, request, oldprofile) if newprofile is not None: self.put_profile(userid, newprofile) - return Node.void('player23') + return Node.void("player23") def handle_player23_new_request(self, request: Node) -> Node: - refid = request.child_value('ref_id') - name = request.child_value('name') + refid = request.child_value("ref_id") + name = request.child_value("name") root = self.new_profile_by_refid(refid, name) if root is None: - root = Node.void('player23') - root.add_child(Node.s8('result', 2)) + root = Node.void("player23") + root.add_child(Node.s8("result", 2)) return root def handle_player23_conversion_request(self, request: Node) -> Node: - refid = request.child_value('ref_id') - name = request.child_value('name') - chara = request.child_value('chara') + refid = request.child_value("ref_id") + name = request.child_value("name") + chara = request.child_value("chara") root = self.new_profile_by_refid(refid, name, chara) if root is None: - root = Node.void('player23') - root.add_child(Node.s8('result', 2)) + root = Node.void("player23") + root.add_child(Node.s8("result", 2)) return root def handle_player23_buy_request(self, request: Node) -> Node: - refid = request.child_value('ref_id') + refid = request.child_value("ref_id") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -320,17 +324,19 @@ class PopnMusicEclale(PopnMusicBase): userid = None if userid is not None: - itemid = request.child_value('id') - itemtype = request.child_value('type') - itemparam = request.child_value('param') + itemid = request.child_value("id") + itemtype = request.child_value("type") + itemparam = request.child_value("param") - price = request.child_value('price') - lumina = request.child_value('lumina') + price = request.child_value("price") + lumina = request.child_value("lumina") if lumina >= price: # Update player lumina balance - profile = self.get_profile(userid) or Profile(self.game, self.version, refid, 0) - profile.replace_int('lumina', lumina - price) + profile = self.get_profile(userid) or Profile( + self.game, self.version, refid, 0 + ) + profile.replace_int("lumina", lumina - price) self.put_profile(userid, profile) # Grant the object @@ -339,19 +345,19 @@ class PopnMusicEclale(PopnMusicBase): self.version, userid, itemid, - f'item_{itemtype}', + f"item_{itemtype}", { - 'param': itemparam, - 'is_new': True, + "param": itemparam, + "is_new": True, }, ) - return Node.void('player23') + return Node.void("player23") def handle_player23_read_score_request(self, request: Node) -> Node: - refid = request.child_value('ref_id') + refid = request.child_value("ref_id") - root = Node.void('player23') + root = Node.void("player23") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: @@ -368,52 +374,62 @@ class PopnMusicEclale(PopnMusicBase): self.CHART_TYPE_EX, ]: continue - if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY: + if score.data.get_int("medal") == self.PLAY_MEDAL_NO_PLAY: continue points = score.points - medal = score.data.get_int('medal') + medal = score.data.get_int("medal") - music = Node.void('music') + music = Node.void("music") root.add_child(music) - music.add_child(Node.s16('music_num', score.id)) - music.add_child(Node.u8('sheet_num', { - self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY, - self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL, - self.CHART_TYPE_HYPER: self.GAME_CHART_TYPE_HYPER, - self.CHART_TYPE_EX: self.GAME_CHART_TYPE_EX, - }[score.chart])) - music.add_child(Node.s32('score', points)) - music.add_child(Node.u8('clear_type', { - self.PLAY_MEDAL_CIRCLE_FAILED: self.GAME_PLAY_MEDAL_CIRCLE_FAILED, - self.PLAY_MEDAL_DIAMOND_FAILED: self.GAME_PLAY_MEDAL_DIAMOND_FAILED, - self.PLAY_MEDAL_STAR_FAILED: self.GAME_PLAY_MEDAL_STAR_FAILED, - self.PLAY_MEDAL_EASY_CLEAR: self.GAME_PLAY_MEDAL_EASY_CLEAR, - self.PLAY_MEDAL_CIRCLE_CLEARED: self.GAME_PLAY_MEDAL_CIRCLE_CLEARED, - self.PLAY_MEDAL_DIAMOND_CLEARED: self.GAME_PLAY_MEDAL_DIAMOND_CLEARED, - self.PLAY_MEDAL_STAR_CLEARED: self.GAME_PLAY_MEDAL_STAR_CLEARED, - self.PLAY_MEDAL_CIRCLE_FULL_COMBO: self.GAME_PLAY_MEDAL_CIRCLE_FULL_COMBO, - self.PLAY_MEDAL_DIAMOND_FULL_COMBO: self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO, - self.PLAY_MEDAL_STAR_FULL_COMBO: self.GAME_PLAY_MEDAL_STAR_FULL_COMBO, - self.PLAY_MEDAL_PERFECT: self.GAME_PLAY_MEDAL_PERFECT, - }[medal])) - music.add_child(Node.s16('cnt', score.plays)) + music.add_child(Node.s16("music_num", score.id)) + music.add_child( + Node.u8( + "sheet_num", + { + self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY, + self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL, + self.CHART_TYPE_HYPER: self.GAME_CHART_TYPE_HYPER, + self.CHART_TYPE_EX: self.GAME_CHART_TYPE_EX, + }[score.chart], + ) + ) + music.add_child(Node.s32("score", points)) + music.add_child( + Node.u8( + "clear_type", + { + self.PLAY_MEDAL_CIRCLE_FAILED: self.GAME_PLAY_MEDAL_CIRCLE_FAILED, + self.PLAY_MEDAL_DIAMOND_FAILED: self.GAME_PLAY_MEDAL_DIAMOND_FAILED, + self.PLAY_MEDAL_STAR_FAILED: self.GAME_PLAY_MEDAL_STAR_FAILED, + self.PLAY_MEDAL_EASY_CLEAR: self.GAME_PLAY_MEDAL_EASY_CLEAR, + self.PLAY_MEDAL_CIRCLE_CLEARED: self.GAME_PLAY_MEDAL_CIRCLE_CLEARED, + self.PLAY_MEDAL_DIAMOND_CLEARED: self.GAME_PLAY_MEDAL_DIAMOND_CLEARED, + self.PLAY_MEDAL_STAR_CLEARED: self.GAME_PLAY_MEDAL_STAR_CLEARED, + self.PLAY_MEDAL_CIRCLE_FULL_COMBO: self.GAME_PLAY_MEDAL_CIRCLE_FULL_COMBO, + self.PLAY_MEDAL_DIAMOND_FULL_COMBO: self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO, + self.PLAY_MEDAL_STAR_FULL_COMBO: self.GAME_PLAY_MEDAL_STAR_FULL_COMBO, + self.PLAY_MEDAL_PERFECT: self.GAME_PLAY_MEDAL_PERFECT, + }[medal], + ) + ) + music.add_child(Node.s16("cnt", score.plays)) return root def handle_player23_friend_request(self, request: Node) -> Node: - refid = request.attribute('ref_id') - no = int(request.attribute('no', '-1')) + refid = request.attribute("ref_id") + no = int(request.attribute("no", "-1")) - root = Node.void('player23') + root = Node.void("player23") if no < 0: - root.add_child(Node.s8('result', 2)) + root.add_child(Node.s8("result", 2)) return root # Look up our own user ID based on the RefID provided. userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is None: - root.add_child(Node.s8('result', 2)) + root.add_child(Node.s8("result", 2)) return root # Grab the links that we care about. @@ -421,7 +437,7 @@ class PopnMusicEclale(PopnMusicBase): profiles: Dict[UserID, Profile] = {} rivals: List[Link] = [] for link in links: - if link.type != 'rival': + if link.type != "rival": continue other_profile = self.get_profile(link.other_userid) @@ -432,22 +448,24 @@ class PopnMusicEclale(PopnMusicBase): # Somehow requested an invalid profile. if no >= len(rivals): - root.add_child(Node.s8('result', 2)) + root.add_child(Node.s8("result", 2)) return root rivalid = links[no].other_userid rivalprofile = profiles[rivalid] scores = self.data.remote.music.get_scores(self.game, self.version, rivalid) # First, output general profile info. - friend = Node.void('friend') + friend = Node.void("friend") root.add_child(friend) - friend.add_child(Node.s16('no', no)) - friend.add_child(Node.string('g_pm_id', self.format_extid(rivalprofile.extid))) # Eclale formats on its own - friend.add_child(Node.string('name', rivalprofile.get_str('name', 'なし'))) - friend.add_child(Node.s16('chara_num', rivalprofile.get_int('chara', -1))) + friend.add_child(Node.s16("no", no)) + friend.add_child( + Node.string("g_pm_id", self.format_extid(rivalprofile.extid)) + ) # Eclale formats on its own + friend.add_child(Node.string("name", rivalprofile.get_str("name", "なし"))) + friend.add_child(Node.s16("chara_num", rivalprofile.get_int("chara", -1))) # This might be for having non-active or non-confirmed friends, but setting to 0 makes the # ranking numbers disappear and the player icon show a questionmark. - friend.add_child(Node.s8('is_open', 1)) + friend.add_child(Node.s8("is_open", 1)) for score in scores: # Skip any scores for chart types we don't support @@ -458,42 +476,52 @@ class PopnMusicEclale(PopnMusicBase): self.CHART_TYPE_EX, ]: continue - if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY: + if score.data.get_int("medal") == self.PLAY_MEDAL_NO_PLAY: continue points = score.points - medal = score.data.get_int('medal') + medal = score.data.get_int("medal") - music = Node.void('music') + music = Node.void("music") friend.add_child(music) - music.set_attribute('music_num', str(score.id)) - music.set_attribute('sheet_num', str({ - self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY, - self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL, - self.CHART_TYPE_HYPER: self.GAME_CHART_TYPE_HYPER, - self.CHART_TYPE_EX: self.GAME_CHART_TYPE_EX, - }[score.chart])) - music.set_attribute('score', str(points)) - music.set_attribute('clearmedal', str({ - self.PLAY_MEDAL_CIRCLE_FAILED: self.GAME_PLAY_MEDAL_CIRCLE_FAILED, - self.PLAY_MEDAL_DIAMOND_FAILED: self.GAME_PLAY_MEDAL_DIAMOND_FAILED, - self.PLAY_MEDAL_STAR_FAILED: self.GAME_PLAY_MEDAL_STAR_FAILED, - self.PLAY_MEDAL_EASY_CLEAR: self.GAME_PLAY_MEDAL_EASY_CLEAR, - self.PLAY_MEDAL_CIRCLE_CLEARED: self.GAME_PLAY_MEDAL_CIRCLE_CLEARED, - self.PLAY_MEDAL_DIAMOND_CLEARED: self.GAME_PLAY_MEDAL_DIAMOND_CLEARED, - self.PLAY_MEDAL_STAR_CLEARED: self.GAME_PLAY_MEDAL_STAR_CLEARED, - self.PLAY_MEDAL_CIRCLE_FULL_COMBO: self.GAME_PLAY_MEDAL_CIRCLE_FULL_COMBO, - self.PLAY_MEDAL_DIAMOND_FULL_COMBO: self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO, - self.PLAY_MEDAL_STAR_FULL_COMBO: self.GAME_PLAY_MEDAL_STAR_FULL_COMBO, - self.PLAY_MEDAL_PERFECT: self.GAME_PLAY_MEDAL_PERFECT, - }[medal])) + music.set_attribute("music_num", str(score.id)) + music.set_attribute( + "sheet_num", + str( + { + self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY, + self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL, + self.CHART_TYPE_HYPER: self.GAME_CHART_TYPE_HYPER, + self.CHART_TYPE_EX: self.GAME_CHART_TYPE_EX, + }[score.chart] + ), + ) + music.set_attribute("score", str(points)) + music.set_attribute( + "clearmedal", + str( + { + self.PLAY_MEDAL_CIRCLE_FAILED: self.GAME_PLAY_MEDAL_CIRCLE_FAILED, + self.PLAY_MEDAL_DIAMOND_FAILED: self.GAME_PLAY_MEDAL_DIAMOND_FAILED, + self.PLAY_MEDAL_STAR_FAILED: self.GAME_PLAY_MEDAL_STAR_FAILED, + self.PLAY_MEDAL_EASY_CLEAR: self.GAME_PLAY_MEDAL_EASY_CLEAR, + self.PLAY_MEDAL_CIRCLE_CLEARED: self.GAME_PLAY_MEDAL_CIRCLE_CLEARED, + self.PLAY_MEDAL_DIAMOND_CLEARED: self.GAME_PLAY_MEDAL_DIAMOND_CLEARED, + self.PLAY_MEDAL_STAR_CLEARED: self.GAME_PLAY_MEDAL_STAR_CLEARED, + self.PLAY_MEDAL_CIRCLE_FULL_COMBO: self.GAME_PLAY_MEDAL_CIRCLE_FULL_COMBO, + self.PLAY_MEDAL_DIAMOND_FULL_COMBO: self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO, + self.PLAY_MEDAL_STAR_FULL_COMBO: self.GAME_PLAY_MEDAL_STAR_FULL_COMBO, + self.PLAY_MEDAL_PERFECT: self.GAME_PLAY_MEDAL_PERFECT, + }[medal] + ), + ) return root def handle_player23_write_music_request(self, request: Node) -> Node: - refid = request.child_value('ref_id') + refid = request.child_value("ref_id") - root = Node.void('player23') + root = Node.void("player23") if refid is None: return root @@ -501,21 +529,21 @@ class PopnMusicEclale(PopnMusicBase): if userid is None: return root - songid = request.child_value('music_num') + songid = request.child_value("music_num") chart = { self.GAME_CHART_TYPE_EASY: self.CHART_TYPE_EASY, self.GAME_CHART_TYPE_NORMAL: self.CHART_TYPE_NORMAL, self.GAME_CHART_TYPE_HYPER: self.CHART_TYPE_HYPER, self.GAME_CHART_TYPE_EX: self.CHART_TYPE_EX, - }[request.child_value('sheet_num')] - medal = request.child_value('clearmedal') - points = request.child_value('score') - combo = request.child_value('combo') + }[request.child_value("sheet_num")] + medal = request.child_value("clearmedal") + points = request.child_value("score") + combo = request.child_value("combo") stats = { - 'cool': request.child_value('cool'), - 'great': request.child_value('great'), - 'good': request.child_value('good'), - 'bad': request.child_value('bad') + "cool": request.child_value("cool"), + "great": request.child_value("great"), + "good": request.child_value("good"), + "bad": request.child_value("bad"), } medal = { self.GAME_PLAY_MEDAL_CIRCLE_FAILED: self.PLAY_MEDAL_CIRCLE_FAILED, @@ -530,18 +558,20 @@ class PopnMusicEclale(PopnMusicBase): self.GAME_PLAY_MEDAL_STAR_FULL_COMBO: self.PLAY_MEDAL_STAR_FULL_COMBO, self.GAME_PLAY_MEDAL_PERFECT: self.PLAY_MEDAL_PERFECT, }[medal] - self.update_score(userid, songid, chart, points, medal, combo=combo, stats=stats) + self.update_score( + userid, songid, chart, points, medal, combo=combo, stats=stats + ) - if request.child_value('is_image_store') == 1: + if request.child_value("is_image_store") == 1: self.broadcast_score(userid, songid, chart, medal, points, combo, stats) return root def format_conversion(self, userid: UserID, profile: Profile) -> Node: - root = Node.void('player23') - root.add_child(Node.string('name', profile.get_str('name', 'なし'))) - root.add_child(Node.s16('chara', profile.get_int('chara', -1))) - root.add_child(Node.s8('result', 1)) + root = Node.void("player23") + root.add_child(Node.string("name", profile.get_str("name", "なし"))) + root.add_child(Node.s16("chara", profile.get_int("chara", -1))) + root.add_child(Node.s8("result", 1)) # Scores scores = self.data.remote.music.get_scores(self.game, self.version, userid) @@ -554,88 +584,129 @@ class PopnMusicEclale(PopnMusicBase): self.CHART_TYPE_EX, ]: continue - if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY: + if score.data.get_int("medal") == self.PLAY_MEDAL_NO_PLAY: continue - music = Node.void('music') + music = Node.void("music") root.add_child(music) - music.add_child(Node.s16('music_num', score.id)) - music.add_child(Node.u8('sheet_num', { - self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY, - self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL, - self.CHART_TYPE_HYPER: self.GAME_CHART_TYPE_HYPER, - self.CHART_TYPE_EX: self.GAME_CHART_TYPE_EX, - }[score.chart])) - music.add_child(Node.s32('score', score.points)) - music.add_child(Node.u8('clear_type', { - self.PLAY_MEDAL_CIRCLE_FAILED: self.GAME_PLAY_MEDAL_CIRCLE_FAILED, - self.PLAY_MEDAL_DIAMOND_FAILED: self.GAME_PLAY_MEDAL_DIAMOND_FAILED, - self.PLAY_MEDAL_STAR_FAILED: self.GAME_PLAY_MEDAL_STAR_FAILED, - self.PLAY_MEDAL_EASY_CLEAR: self.GAME_PLAY_MEDAL_EASY_CLEAR, - self.PLAY_MEDAL_CIRCLE_CLEARED: self.GAME_PLAY_MEDAL_CIRCLE_CLEARED, - self.PLAY_MEDAL_DIAMOND_CLEARED: self.GAME_PLAY_MEDAL_DIAMOND_CLEARED, - self.PLAY_MEDAL_STAR_CLEARED: self.GAME_PLAY_MEDAL_STAR_CLEARED, - self.PLAY_MEDAL_CIRCLE_FULL_COMBO: self.GAME_PLAY_MEDAL_CIRCLE_FULL_COMBO, - self.PLAY_MEDAL_DIAMOND_FULL_COMBO: self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO, - self.PLAY_MEDAL_STAR_FULL_COMBO: self.GAME_PLAY_MEDAL_STAR_FULL_COMBO, - self.PLAY_MEDAL_PERFECT: self.GAME_PLAY_MEDAL_PERFECT, - }[score.data.get_int('medal')])) - music.add_child(Node.s16('cnt', score.plays)) + music.add_child(Node.s16("music_num", score.id)) + music.add_child( + Node.u8( + "sheet_num", + { + self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY, + self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL, + self.CHART_TYPE_HYPER: self.GAME_CHART_TYPE_HYPER, + self.CHART_TYPE_EX: self.GAME_CHART_TYPE_EX, + }[score.chart], + ) + ) + music.add_child(Node.s32("score", score.points)) + music.add_child( + Node.u8( + "clear_type", + { + self.PLAY_MEDAL_CIRCLE_FAILED: self.GAME_PLAY_MEDAL_CIRCLE_FAILED, + self.PLAY_MEDAL_DIAMOND_FAILED: self.GAME_PLAY_MEDAL_DIAMOND_FAILED, + self.PLAY_MEDAL_STAR_FAILED: self.GAME_PLAY_MEDAL_STAR_FAILED, + self.PLAY_MEDAL_EASY_CLEAR: self.GAME_PLAY_MEDAL_EASY_CLEAR, + self.PLAY_MEDAL_CIRCLE_CLEARED: self.GAME_PLAY_MEDAL_CIRCLE_CLEARED, + self.PLAY_MEDAL_DIAMOND_CLEARED: self.GAME_PLAY_MEDAL_DIAMOND_CLEARED, + self.PLAY_MEDAL_STAR_CLEARED: self.GAME_PLAY_MEDAL_STAR_CLEARED, + self.PLAY_MEDAL_CIRCLE_FULL_COMBO: self.GAME_PLAY_MEDAL_CIRCLE_FULL_COMBO, + self.PLAY_MEDAL_DIAMOND_FULL_COMBO: self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO, + self.PLAY_MEDAL_STAR_FULL_COMBO: self.GAME_PLAY_MEDAL_STAR_FULL_COMBO, + self.PLAY_MEDAL_PERFECT: self.GAME_PLAY_MEDAL_PERFECT, + }[score.data.get_int("medal")], + ) + ) + music.add_child(Node.s16("cnt", score.plays)) return root def format_extid(self, extid: int) -> str: data = str(extid) - crc = abs(binascii.crc32(data.encode('ascii'))) % 10000 - return f'{data}{crc:04d}' + crc = abs(binascii.crc32(data.encode("ascii"))) % 10000 + return f"{data}{crc:04d}" def format_profile(self, userid: UserID, profile: Profile) -> Node: - root = Node.void('player23') + root = Node.void("player23") # Mark this as a current profile - root.add_child(Node.s8('result', 0)) + root.add_child(Node.s8("result", 0)) # Account stuff - account = Node.void('account') + account = Node.void("account") root.add_child(account) - account.add_child(Node.string('g_pm_id', self.format_extid(profile.extid))) - account.add_child(Node.string('name', profile.get_str('name', 'なし'))) - account.add_child(Node.s8('tutorial', profile.get_int('tutorial'))) - account.add_child(Node.s16('area_id', profile.get_int('area_id'))) - account.add_child(Node.s16('lumina', profile.get_int('lumina', 300))) - account.add_child(Node.s16('read_news', profile.get_int('read_news'))) - account.add_child(Node.bool('welcom_pack', False)) # Set this to true to grant extra stage no matter what. - account.add_child(Node.s16_array('medal_set', profile.get_int_array('medal_set', 4))) - account.add_child(Node.s16_array('nice', profile.get_int_array('nice', 30, [-1] * 30))) - account.add_child(Node.s16_array('favorite_chara', profile.get_int_array('favorite_chara', 20, [-1] * 20))) - account.add_child(Node.s16_array('special_area', profile.get_int_array('special_area', 8))) - account.add_child(Node.s16_array('chocolate_charalist', profile.get_int_array('chocolate_charalist', 5, [-1] * 5))) - account.add_child(Node.s16_array('teacher_setting', profile.get_int_array('teacher_setting', 10))) + account.add_child(Node.string("g_pm_id", self.format_extid(profile.extid))) + account.add_child(Node.string("name", profile.get_str("name", "なし"))) + account.add_child(Node.s8("tutorial", profile.get_int("tutorial"))) + account.add_child(Node.s16("area_id", profile.get_int("area_id"))) + account.add_child(Node.s16("lumina", profile.get_int("lumina", 300))) + account.add_child(Node.s16("read_news", profile.get_int("read_news"))) + account.add_child( + Node.bool("welcom_pack", False) + ) # Set this to true to grant extra stage no matter what. + account.add_child( + Node.s16_array("medal_set", profile.get_int_array("medal_set", 4)) + ) + account.add_child( + Node.s16_array("nice", profile.get_int_array("nice", 30, [-1] * 30)) + ) + account.add_child( + Node.s16_array( + "favorite_chara", profile.get_int_array("favorite_chara", 20, [-1] * 20) + ) + ) + account.add_child( + Node.s16_array("special_area", profile.get_int_array("special_area", 8)) + ) + account.add_child( + Node.s16_array( + "chocolate_charalist", + profile.get_int_array("chocolate_charalist", 5, [-1] * 5), + ) + ) + account.add_child( + Node.s16_array( + "teacher_setting", profile.get_int_array("teacher_setting", 10) + ) + ) # Stuff we never change - account.add_child(Node.s8('staff', 0)) - account.add_child(Node.s16('item_type', 0)) - account.add_child(Node.s16('item_id', 0)) - account.add_child(Node.s8('is_conv', 0)) - account.add_child(Node.bool('meteor_flg', True)) - account.add_child(Node.s16_array('license_data', [-1] * 20)) + account.add_child(Node.s8("staff", 0)) + account.add_child(Node.s16("item_type", 0)) + account.add_child(Node.s16("item_id", 0)) + account.add_child(Node.s8("is_conv", 0)) + account.add_child(Node.bool("meteor_flg", True)) + account.add_child(Node.s16_array("license_data", [-1] * 20)) # Add statistics section - last_played = [x[0] for x in self.data.local.music.get_last_played(self.game, self.version, userid, 5)] - most_played = [x[0] for x in self.data.local.music.get_most_played(self.game, self.version, userid, 10)] + last_played = [ + x[0] + for x in self.data.local.music.get_last_played( + self.game, self.version, userid, 5 + ) + ] + most_played = [ + x[0] + for x in self.data.local.music.get_most_played( + self.game, self.version, userid, 10 + ) + ] while len(last_played) < 5: last_played.append(-1) while len(most_played) < 10: most_played.append(-1) - account.add_child(Node.s16_array('my_best', most_played)) - account.add_child(Node.s16_array('latest_music', last_played)) + account.add_child(Node.s16_array("my_best", most_played)) + account.add_child(Node.s16_array("latest_music", last_played)) # Number of rivals that are active for this version. links = self.data.local.user.get_links(self.game, self.version, userid) rivalcount = 0 for link in links: - if link.type != 'rival': + if link.type != "rival": continue if not self.has_profile(link.other_userid): @@ -643,254 +714,290 @@ class PopnMusicEclale(PopnMusicBase): # This profile is valid. rivalcount += 1 - account.add_child(Node.u8('active_fr_num', rivalcount)) + account.add_child(Node.u8("active_fr_num", rivalcount)) # player statistics statistics = self.get_play_statistics(userid) - account.add_child(Node.s16('total_play_cnt', statistics.total_plays)) - account.add_child(Node.s16('today_play_cnt', statistics.today_plays)) - account.add_child(Node.s16('consecutive_days', statistics.consecutive_days)) - account.add_child(Node.s16('total_days', statistics.total_days)) - account.add_child(Node.s16('interval_day', 0)) + account.add_child(Node.s16("total_play_cnt", statistics.total_plays)) + account.add_child(Node.s16("today_play_cnt", statistics.today_plays)) + account.add_child(Node.s16("consecutive_days", statistics.consecutive_days)) + account.add_child(Node.s16("total_days", statistics.total_days)) + account.add_child(Node.s16("interval_day", 0)) # eAmuse account link - eaappli = Node.void('eaappli') + eaappli = Node.void("eaappli") root.add_child(eaappli) - eaappli.add_child(Node.s8('relation', 1 if self.data.triggers.has_broadcast_destination(self.game) else -1)) + eaappli.add_child( + Node.s8( + "relation", + 1 if self.data.triggers.has_broadcast_destination(self.game) else -1, + ) + ) # Set up info node - info = Node.void('info') + info = Node.void("info") root.add_child(info) - info.add_child(Node.u16('ep', profile.get_int('ep'))) + info.add_child(Node.u16("ep", profile.get_int("ep"))) # Set up last information - config = Node.void('config') + config = Node.void("config") root.add_child(config) - config.add_child(Node.u8('mode', profile.get_int('mode'))) - config.add_child(Node.s16('chara', profile.get_int('chara', -1))) - config.add_child(Node.s16('music', profile.get_int('music', -1))) - config.add_child(Node.u8('sheet', profile.get_int('sheet'))) - config.add_child(Node.s8('category', profile.get_int('category', -1))) - config.add_child(Node.s8('sub_category', profile.get_int('sub_category', -1))) - config.add_child(Node.s8('chara_category', profile.get_int('chara_category', -1))) - config.add_child(Node.s16('course_id', profile.get_int('course_id', -1))) - config.add_child(Node.s8('course_folder', profile.get_int('course_folder', -1))) - config.add_child(Node.s8('ms_banner_disp', profile.get_int('ms_banner_disp'))) - config.add_child(Node.s8('ms_down_info', profile.get_int('ms_down_info'))) - config.add_child(Node.s8('ms_side_info', profile.get_int('ms_side_info'))) - config.add_child(Node.s8('ms_raise_type', profile.get_int('ms_raise_type'))) - config.add_child(Node.s8('ms_rnd_type', profile.get_int('ms_rnd_type'))) + config.add_child(Node.u8("mode", profile.get_int("mode"))) + config.add_child(Node.s16("chara", profile.get_int("chara", -1))) + config.add_child(Node.s16("music", profile.get_int("music", -1))) + config.add_child(Node.u8("sheet", profile.get_int("sheet"))) + config.add_child(Node.s8("category", profile.get_int("category", -1))) + config.add_child(Node.s8("sub_category", profile.get_int("sub_category", -1))) + config.add_child( + Node.s8("chara_category", profile.get_int("chara_category", -1)) + ) + config.add_child(Node.s16("course_id", profile.get_int("course_id", -1))) + config.add_child(Node.s8("course_folder", profile.get_int("course_folder", -1))) + config.add_child(Node.s8("ms_banner_disp", profile.get_int("ms_banner_disp"))) + config.add_child(Node.s8("ms_down_info", profile.get_int("ms_down_info"))) + config.add_child(Node.s8("ms_side_info", profile.get_int("ms_side_info"))) + config.add_child(Node.s8("ms_raise_type", profile.get_int("ms_raise_type"))) + config.add_child(Node.s8("ms_rnd_type", profile.get_int("ms_rnd_type"))) # Player options - option = Node.void('option') - option_dict = profile.get_dict('option') + option = Node.void("option") + option_dict = profile.get_dict("option") root.add_child(option) - option.add_child(Node.s16('hispeed', option_dict.get_int('hispeed'))) - option.add_child(Node.u8('popkun', option_dict.get_int('popkun'))) - option.add_child(Node.bool('hidden', option_dict.get_bool('hidden'))) - option.add_child(Node.s16('hidden_rate', option_dict.get_int('hidden_rate'))) - option.add_child(Node.bool('sudden', option_dict.get_bool('sudden'))) - option.add_child(Node.s16('sudden_rate', option_dict.get_int('sudden_rate'))) - option.add_child(Node.s8('randmir', option_dict.get_int('randmir'))) - option.add_child(Node.s8('gauge_type', option_dict.get_int('gauge_type'))) - option.add_child(Node.u8('ojama_0', option_dict.get_int('ojama_0'))) - option.add_child(Node.u8('ojama_1', option_dict.get_int('ojama_1'))) - option.add_child(Node.bool('forever_0', option_dict.get_bool('forever_0'))) - option.add_child(Node.bool('forever_1', option_dict.get_bool('forever_1'))) - option.add_child(Node.bool('full_setting', option_dict.get_bool('full_setting'))) - option.add_child(Node.u8('judge', option_dict.get_int('judge'))) + option.add_child(Node.s16("hispeed", option_dict.get_int("hispeed"))) + option.add_child(Node.u8("popkun", option_dict.get_int("popkun"))) + option.add_child(Node.bool("hidden", option_dict.get_bool("hidden"))) + option.add_child(Node.s16("hidden_rate", option_dict.get_int("hidden_rate"))) + option.add_child(Node.bool("sudden", option_dict.get_bool("sudden"))) + option.add_child(Node.s16("sudden_rate", option_dict.get_int("sudden_rate"))) + option.add_child(Node.s8("randmir", option_dict.get_int("randmir"))) + option.add_child(Node.s8("gauge_type", option_dict.get_int("gauge_type"))) + option.add_child(Node.u8("ojama_0", option_dict.get_int("ojama_0"))) + option.add_child(Node.u8("ojama_1", option_dict.get_int("ojama_1"))) + option.add_child(Node.bool("forever_0", option_dict.get_bool("forever_0"))) + option.add_child(Node.bool("forever_1", option_dict.get_bool("forever_1"))) + option.add_child( + Node.bool("full_setting", option_dict.get_bool("full_setting")) + ) + option.add_child(Node.u8("judge", option_dict.get_int("judge"))) # Unknown custom category stuff? - custom_cate = Node.void('custom_cate') + custom_cate = Node.void("custom_cate") root.add_child(custom_cate) - custom_cate.add_child(Node.s8('valid', 0)) - custom_cate.add_child(Node.s8('lv_min', -1)) - custom_cate.add_child(Node.s8('lv_max', -1)) - custom_cate.add_child(Node.s8('medal_min', -1)) - custom_cate.add_child(Node.s8('medal_max', -1)) - custom_cate.add_child(Node.s8('friend_no', -1)) - custom_cate.add_child(Node.s8('score_flg', -1)) + custom_cate.add_child(Node.s8("valid", 0)) + custom_cate.add_child(Node.s8("lv_min", -1)) + custom_cate.add_child(Node.s8("lv_max", -1)) + custom_cate.add_child(Node.s8("medal_min", -1)) + custom_cate.add_child(Node.s8("medal_max", -1)) + custom_cate.add_child(Node.s8("friend_no", -1)) + custom_cate.add_child(Node.s8("score_flg", -1)) game_config = self.get_game_config() - if game_config.get_bool('force_unlock_songs'): - songs = {song.id for song in self.data.local.music.get_all_songs(self.game, self.version)} + if game_config.get_bool("force_unlock_songs"): + songs = { + song.id + for song in self.data.local.music.get_all_songs(self.game, self.version) + } for song in songs: - item = Node.void('item') + item = Node.void("item") root.add_child(item) - item.add_child(Node.u8('type', 0)) - item.add_child(Node.u16('id', song)) - item.add_child(Node.u16('param', 15)) - item.add_child(Node.bool('is_new', False)) + item.add_child(Node.u8("type", 0)) + item.add_child(Node.u16("id", song)) + item.add_child(Node.u16("param", 15)) + item.add_child(Node.bool("is_new", False)) # Set up achievements - achievements = self.data.local.user.get_achievements(self.game, self.version, userid) + achievements = self.data.local.user.get_achievements( + self.game, self.version, userid + ) for achievement in achievements: - if achievement.type[:5] == 'item_': + if achievement.type[:5] == "item_": itemtype = int(achievement.type[5:]) - param = achievement.data.get_int('param') - is_new = achievement.data.get_bool('is_new') + param = achievement.data.get_int("param") + is_new = achievement.data.get_bool("is_new") # Type is the type of unlock/item. Type 0 is song unlock in Eclale. # In this case, the id is the song ID according to the game. Unclear # what the param is supposed to be, but i've seen 8 and 0. Might be # what chart is available? - if game_config.get_bool('force_unlock_songs') and itemtype == 0: + if game_config.get_bool("force_unlock_songs") and itemtype == 0: # We already sent song unlocks in the force unlock section above. continue - item = Node.void('item') + item = Node.void("item") root.add_child(item) - item.add_child(Node.u8('type', itemtype)) - item.add_child(Node.u16('id', achievement.id)) - item.add_child(Node.u16('param', param)) - item.add_child(Node.bool('is_new', is_new)) + item.add_child(Node.u8("type", itemtype)) + item.add_child(Node.u16("id", achievement.id)) + item.add_child(Node.u16("param", param)) + item.add_child(Node.bool("is_new", is_new)) - elif achievement.type == 'chara': - friendship = achievement.data.get_int('friendship') + elif achievement.type == "chara": + friendship = achievement.data.get_int("friendship") - chara = Node.void('chara_param') + chara = Node.void("chara_param") root.add_child(chara) - chara.add_child(Node.u16('chara_id', achievement.id)) - chara.add_child(Node.u16('friendship', friendship)) + chara.add_child(Node.u16("chara_id", achievement.id)) + chara.add_child(Node.u16("friendship", friendship)) - elif achievement.type == 'medal': - level = achievement.data.get_int('level') - exp = achievement.data.get_int('exp') - set_count = achievement.data.get_int('set_count') - get_count = achievement.data.get_int('get_count') + elif achievement.type == "medal": + level = achievement.data.get_int("level") + exp = achievement.data.get_int("exp") + set_count = achievement.data.get_int("set_count") + get_count = achievement.data.get_int("get_count") - medal = Node.void('medal') + medal = Node.void("medal") root.add_child(medal) - medal.add_child(Node.s16('medal_id', achievement.id)) - medal.add_child(Node.s16('level', level)) - medal.add_child(Node.s32('exp', exp)) - medal.add_child(Node.s32('set_count', set_count)) - medal.add_child(Node.s32('get_count', get_count)) + medal.add_child(Node.s16("medal_id", achievement.id)) + medal.add_child(Node.s16("level", level)) + medal.add_child(Node.s32("exp", exp)) + medal.add_child(Node.s32("set_count", set_count)) + medal.add_child(Node.s32("get_count", get_count)) # Character customizations - customize = Node.void('customize') + customize = Node.void("customize") root.add_child(customize) - customize.add_child(Node.u16('effect_left', profile.get_int('effect_left'))) - customize.add_child(Node.u16('effect_center', profile.get_int('effect_center'))) - customize.add_child(Node.u16('effect_right', profile.get_int('effect_right'))) - customize.add_child(Node.u16('hukidashi', profile.get_int('hukidashi'))) - customize.add_child(Node.u16('comment_1', profile.get_int('comment_1'))) - customize.add_child(Node.u16('comment_2', profile.get_int('comment_2'))) + customize.add_child(Node.u16("effect_left", profile.get_int("effect_left"))) + customize.add_child(Node.u16("effect_center", profile.get_int("effect_center"))) + customize.add_child(Node.u16("effect_right", profile.get_int("effect_right"))) + customize.add_child(Node.u16("hukidashi", profile.get_int("hukidashi"))) + customize.add_child(Node.u16("comment_1", profile.get_int("comment_1"))) + customize.add_child(Node.u16("comment_2", profile.get_int("comment_2"))) # NetVS section - netvs = Node.void('netvs') + netvs = Node.void("netvs") root.add_child(netvs) - netvs.add_child(Node.s16_array('record', [0] * 6)) - netvs.add_child(Node.string('dialog', '')) - netvs.add_child(Node.string('dialog', '')) - netvs.add_child(Node.string('dialog', '')) - netvs.add_child(Node.string('dialog', '')) - netvs.add_child(Node.string('dialog', '')) - netvs.add_child(Node.string('dialog', '')) - netvs.add_child(Node.s8_array('ojama_condition', [0] * 74)) - netvs.add_child(Node.s8_array('set_ojama', [0] * 3)) - netvs.add_child(Node.s8_array('set_recommend', [0] * 3)) - netvs.add_child(Node.u32('netvs_play_cnt', 0)) + netvs.add_child(Node.s16_array("record", [0] * 6)) + netvs.add_child(Node.string("dialog", "")) + netvs.add_child(Node.string("dialog", "")) + netvs.add_child(Node.string("dialog", "")) + netvs.add_child(Node.string("dialog", "")) + netvs.add_child(Node.string("dialog", "")) + netvs.add_child(Node.string("dialog", "")) + netvs.add_child(Node.s8_array("ojama_condition", [0] * 74)) + netvs.add_child(Node.s8_array("set_ojama", [0] * 3)) + netvs.add_child(Node.s8_array("set_recommend", [0] * 3)) + netvs.add_child(Node.u32("netvs_play_cnt", 0)) # Event stuff - event = Node.void('event') + event = Node.void("event") root.add_child(event) - event.add_child(Node.s16('enemy_medal', profile.get_int('event_enemy_medal'))) - event.add_child(Node.s16('hp', profile.get_int('event_hp'))) + event.add_child(Node.s16("enemy_medal", profile.get_int("event_enemy_medal"))) + event.add_child(Node.s16("hp", profile.get_int("event_hp"))) # Stamp stuff - stamp = Node.void('stamp') + stamp = Node.void("stamp") root.add_child(stamp) - stamp.add_child(Node.s16('stamp_id', profile.get_int('stamp_id'))) - stamp.add_child(Node.s16('cnt', profile.get_int('stamp_cnt'))) + stamp.add_child(Node.s16("stamp_id", profile.get_int("stamp_id"))) + stamp.add_child(Node.s16("cnt", profile.get_int("stamp_cnt"))) return root - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() - account = request.child('account') + account = request.child("account") if account is not None: - newprofile.replace_int('tutorial', account.child_value('tutorial')) - newprofile.replace_int('read_news', account.child_value('read_news')) - newprofile.replace_int('area_id', account.child_value('area_id')) - newprofile.replace_int('lumina', account.child_value('lumina')) - newprofile.replace_int_array('medal_set', 4, account.child_value('medal_set')) - newprofile.replace_int_array('nice', 30, account.child_value('nice')) - newprofile.replace_int_array('favorite_chara', 20, account.child_value('favorite_chara')) - newprofile.replace_int_array('special_area', 8, account.child_value('special_area')) - newprofile.replace_int_array('chocolate_charalist', 5, account.child_value('chocolate_charalist')) - newprofile.replace_int_array('teacher_setting', 10, account.child_value('teacher_setting')) + newprofile.replace_int("tutorial", account.child_value("tutorial")) + newprofile.replace_int("read_news", account.child_value("read_news")) + newprofile.replace_int("area_id", account.child_value("area_id")) + newprofile.replace_int("lumina", account.child_value("lumina")) + newprofile.replace_int_array( + "medal_set", 4, account.child_value("medal_set") + ) + newprofile.replace_int_array("nice", 30, account.child_value("nice")) + newprofile.replace_int_array( + "favorite_chara", 20, account.child_value("favorite_chara") + ) + newprofile.replace_int_array( + "special_area", 8, account.child_value("special_area") + ) + newprofile.replace_int_array( + "chocolate_charalist", 5, account.child_value("chocolate_charalist") + ) + newprofile.replace_int_array( + "teacher_setting", 10, account.child_value("teacher_setting") + ) - info = request.child('info') + info = request.child("info") if info is not None: - newprofile.replace_int('ep', info.child_value('ep')) + newprofile.replace_int("ep", info.child_value("ep")) - config = request.child('config') + config = request.child("config") if config is not None: - newprofile.replace_int('mode', config.child_value('mode')) - newprofile.replace_int('chara', config.child_value('chara')) - newprofile.replace_int('music', config.child_value('music')) - newprofile.replace_int('sheet', config.child_value('sheet')) - newprofile.replace_int('category', config.child_value('category')) - newprofile.replace_int('sub_category', config.child_value('sub_category')) - newprofile.replace_int('chara_category', config.child_value('chara_category')) - newprofile.replace_int('course_id', config.child_value('course_id')) - newprofile.replace_int('course_folder', config.child_value('course_folder')) - newprofile.replace_int('ms_banner_disp', config.child_value('ms_banner_disp')) - newprofile.replace_int('ms_down_info', config.child_value('ms_down_info')) - newprofile.replace_int('ms_side_info', config.child_value('ms_side_info')) - newprofile.replace_int('ms_raise_type', config.child_value('ms_raise_type')) - newprofile.replace_int('ms_rnd_type', config.child_value('ms_rnd_type')) + newprofile.replace_int("mode", config.child_value("mode")) + newprofile.replace_int("chara", config.child_value("chara")) + newprofile.replace_int("music", config.child_value("music")) + newprofile.replace_int("sheet", config.child_value("sheet")) + newprofile.replace_int("category", config.child_value("category")) + newprofile.replace_int("sub_category", config.child_value("sub_category")) + newprofile.replace_int( + "chara_category", config.child_value("chara_category") + ) + newprofile.replace_int("course_id", config.child_value("course_id")) + newprofile.replace_int("course_folder", config.child_value("course_folder")) + newprofile.replace_int( + "ms_banner_disp", config.child_value("ms_banner_disp") + ) + newprofile.replace_int("ms_down_info", config.child_value("ms_down_info")) + newprofile.replace_int("ms_side_info", config.child_value("ms_side_info")) + newprofile.replace_int("ms_raise_type", config.child_value("ms_raise_type")) + newprofile.replace_int("ms_rnd_type", config.child_value("ms_rnd_type")) - option_dict = newprofile.get_dict('option') - option = request.child('option') + option_dict = newprofile.get_dict("option") + option = request.child("option") if option is not None: - option_dict.replace_int('hispeed', option.child_value('hispeed')) - option_dict.replace_int('popkun', option.child_value('popkun')) - option_dict.replace_bool('hidden', option.child_value('hidden')) - option_dict.replace_int('hidden_rate', option.child_value('hidden_rate')) - option_dict.replace_bool('sudden', option.child_value('sudden')) - option_dict.replace_int('sudden_rate', option.child_value('sudden_rate')) - option_dict.replace_int('randmir', option.child_value('randmir')) - option_dict.replace_int('gauge_type', option.child_value('gauge_type')) - option_dict.replace_int('ojama_0', option.child_value('ojama_0')) - option_dict.replace_int('ojama_1', option.child_value('ojama_1')) - option_dict.replace_bool('forever_0', option.child_value('forever_0')) - option_dict.replace_bool('forever_1', option.child_value('forever_1')) - option_dict.replace_bool('full_setting', option.child_value('full_setting')) - option_dict.replace_int('judge', option.child_value('judge')) - newprofile.replace_dict('option', option_dict) + option_dict.replace_int("hispeed", option.child_value("hispeed")) + option_dict.replace_int("popkun", option.child_value("popkun")) + option_dict.replace_bool("hidden", option.child_value("hidden")) + option_dict.replace_int("hidden_rate", option.child_value("hidden_rate")) + option_dict.replace_bool("sudden", option.child_value("sudden")) + option_dict.replace_int("sudden_rate", option.child_value("sudden_rate")) + option_dict.replace_int("randmir", option.child_value("randmir")) + option_dict.replace_int("gauge_type", option.child_value("gauge_type")) + option_dict.replace_int("ojama_0", option.child_value("ojama_0")) + option_dict.replace_int("ojama_1", option.child_value("ojama_1")) + option_dict.replace_bool("forever_0", option.child_value("forever_0")) + option_dict.replace_bool("forever_1", option.child_value("forever_1")) + option_dict.replace_bool("full_setting", option.child_value("full_setting")) + option_dict.replace_int("judge", option.child_value("judge")) + newprofile.replace_dict("option", option_dict) - customize = request.child('customize') + customize = request.child("customize") if customize is not None: - newprofile.replace_int('effect_left', customize.child_value('effect_left')) - newprofile.replace_int('effect_center', customize.child_value('effect_center')) - newprofile.replace_int('effect_right', customize.child_value('effect_right')) - newprofile.replace_int('hukidashi', customize.child_value('hukidashi')) - newprofile.replace_int('comment_1', customize.child_value('comment_1')) - newprofile.replace_int('comment_2', customize.child_value('comment_2')) + newprofile.replace_int("effect_left", customize.child_value("effect_left")) + newprofile.replace_int( + "effect_center", customize.child_value("effect_center") + ) + newprofile.replace_int( + "effect_right", customize.child_value("effect_right") + ) + newprofile.replace_int("hukidashi", customize.child_value("hukidashi")) + newprofile.replace_int("comment_1", customize.child_value("comment_1")) + newprofile.replace_int("comment_2", customize.child_value("comment_2")) - event = request.child('event') + event = request.child("event") if event is not None: - newprofile.replace_int('event_enemy_medal', event.child_value('enemy_medal')) - newprofile.replace_int('event_hp', event.child_value('hp')) + newprofile.replace_int( + "event_enemy_medal", event.child_value("enemy_medal") + ) + newprofile.replace_int("event_hp", event.child_value("hp")) - stamp = request.child('stamp') + stamp = request.child("stamp") if stamp is not None: - newprofile.replace_int('stamp_id', stamp.child_value('stamp_id')) - newprofile.replace_int('stamp_cnt', stamp.child_value('cnt')) + newprofile.replace_int("stamp_id", stamp.child_value("stamp_id")) + newprofile.replace_int("stamp_cnt", stamp.child_value("cnt")) # Extract achievements game_config = self.get_game_config() for node in request.children: - if node.name == 'item': - itemid = node.child_value('id') - itemtype = node.child_value('type') - param = node.child_value('param') - is_new = node.child_value('is_new') + if node.name == "item": + itemid = node.child_value("id") + itemtype = node.child_value("type") + param = node.child_value("param") + is_new = node.child_value("is_new") - if game_config.get_bool('force_unlock_songs') and itemtype == 0: + if game_config.get_bool("force_unlock_songs") and itemtype == 0: # If we enabled force song unlocks, don't save songs to the profile. continue @@ -899,46 +1006,46 @@ class PopnMusicEclale(PopnMusicBase): self.version, userid, itemid, - f'item_{itemtype}', + f"item_{itemtype}", { - 'param': param, - 'is_new': is_new, + "param": param, + "is_new": is_new, }, ) - elif node.name == 'chara_param': - charaid = node.child_value('chara_id') - friendship = node.child_value('friendship') + elif node.name == "chara_param": + charaid = node.child_value("chara_id") + friendship = node.child_value("friendship") self.data.local.user.put_achievement( self.game, self.version, userid, charaid, - 'chara', + "chara", { - 'friendship': friendship, + "friendship": friendship, }, ) - elif node.name == 'medal': - medalid = node.child_value('medal_id') - level = node.child_value('level') - exp = node.child_value('exp') - set_count = node.child_value('set_count') - get_count = node.child_value('get_count') + elif node.name == "medal": + medalid = node.child_value("medal_id") + level = node.child_value("level") + exp = node.child_value("exp") + set_count = node.child_value("set_count") + get_count = node.child_value("get_count") self.data.local.user.put_achievement( self.game, self.version, userid, medalid, - 'medal', + "medal", { - 'level': level, - 'exp': exp, - 'set_count': set_count, - 'get_count': get_count, + "level": level, + "exp": exp, + "set_count": set_count, + "get_count": get_count, }, ) diff --git a/bemani/backend/popn/factory.py b/bemani/backend/popn/factory.py index 2fa2b6d..1a22f79 100644 --- a/bemani/backend/popn/factory.py +++ b/bemani/backend/popn/factory.py @@ -66,12 +66,17 @@ class PopnMusicFactory(Factory): @classmethod def register_all(cls) -> None: - for gamecode in ['G15', 'H16', 'I17', 'J39', 'K39', 'L39', 'M39']: + for gamecode in ["G15", "H16", "I17", "J39", "K39", "L39", "M39"]: Base.register(gamecode, PopnMusicFactory) @classmethod - def create(cls, data: Data, config: Config, model: Model, parentmodel: Optional[Model]=None) -> Optional[Base]: - + def create( + cls, + data: Data, + config: Config, + model: Model, + parentmodel: Optional[Model] = None, + ) -> Optional[Base]: def version_from_date(date: int) -> Optional[int]: if date <= 2014061900: return VersionConstants.POPN_MUSIC_SUNNY_PARK @@ -87,26 +92,34 @@ class PopnMusicFactory(Factory): return VersionConstants.POPN_MUSIC_KAIMEI_RIDDLES return None - if model.gamecode == 'G15': + if model.gamecode == "G15": return PopnMusicAdventure(data, config, model) - if model.gamecode == 'H16': + if model.gamecode == "H16": return PopnMusicParty(data, config, model) - if model.gamecode == 'I17': + if model.gamecode == "I17": return PopnMusicTheMovie(data, config, model) - if model.gamecode == 'J39': + if model.gamecode == "J39": return PopnMusicSengokuRetsuden(data, config, model) - if model.gamecode == 'K39': + if model.gamecode == "K39": return PopnMusicTuneStreet(data, config, model) - if model.gamecode == 'L39': + if model.gamecode == "L39": return PopnMusicFantasia(data, config, model) - if model.gamecode == 'M39': + if model.gamecode == "M39": if model.version is None: if parentmodel is None: return None # We have no way to tell apart newer versions. However, we can make # an educated guess if we happen to be summoned for old profile lookup. - if parentmodel.gamecode not in ['G15', 'H16', 'I17', 'J39', 'K39', 'L39', 'M39']: + if parentmodel.gamecode not in [ + "G15", + "H16", + "I17", + "J39", + "K39", + "L39", + "M39", + ]: return None parentversion = version_from_date(parentmodel.version) if parentversion == VersionConstants.POPN_MUSIC_LAPISTORIA: diff --git a/bemani/backend/popn/fantasia.py b/bemani/backend/popn/fantasia.py index 0b58c99..4b124de 100644 --- a/bemani/backend/popn/fantasia.py +++ b/bemani/backend/popn/fantasia.py @@ -52,31 +52,31 @@ class PopnMusicFantasia(PopnMusicBase): Return all of our front-end modifiably settings. """ return { - 'ints': [ + "ints": [ { - 'name': 'Game Phase', - 'tip': 'Game unlock phase for all players.', - 'category': 'game_config', - 'setting': 'game_phase', - 'values': { - 0: 'NO PHASE', - 1: 'SECRET DATA RELEASE', - 2: 'MAX: ALL DATA RELEASE', - } + "name": "Game Phase", + "tip": "Game unlock phase for all players.", + "category": "game_config", + "setting": "game_phase", + "values": { + 0: "NO PHASE", + 1: "SECRET DATA RELEASE", + 2: "MAX: ALL DATA RELEASE", + }, }, { - 'name': 'Pop\'n Quest Event Phase', - 'tip': 'Event phase for all players.', - 'category': 'game_config', - 'setting': 'event_phase', - 'values': { - 0: 'No event', - 1: 'Phase 1', - 2: 'Phase 2', - 3: 'Phase 3', - 4: 'Phase 4', - 5: 'Phase MAX', - } + "name": "Pop'n Quest Event Phase", + "tip": "Event phase for all players.", + "category": "game_config", + "setting": "event_phase", + "values": { + 0: "No event", + 1: "Phase 1", + 2: "Phase 2", + 3: "Phase 3", + 4: "Phase 4", + 5: "Phase MAX", + }, }, ] } @@ -94,7 +94,7 @@ class PopnMusicFantasia(PopnMusicBase): self.PLAY_MEDAL_DIAMOND_FULL_COMBO: self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO, self.PLAY_MEDAL_STAR_FULL_COMBO: self.GAME_PLAY_MEDAL_STAR_FULL_COMBO, self.PLAY_MEDAL_PERFECT: self.GAME_PLAY_MEDAL_PERFECT, - }[score.data.get_int('medal')] + }[score.data.get_int("medal")] position = { self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY_POSITION, self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL_POSITION, @@ -104,67 +104,95 @@ class PopnMusicFantasia(PopnMusicBase): return medal << (position * 4) def format_profile(self, userid: UserID, profile: Profile) -> Node: - root = Node.void('playerdata') + root = Node.void("playerdata") # Set up the base profile - base = Node.void('base') + base = Node.void("base") root.add_child(base) - base.add_child(Node.string('name', profile.get_str('name', 'なし'))) - base.add_child(Node.string('g_pm_id', ID.format_extid(profile.extid))) - base.add_child(Node.u8('mode', profile.get_int('mode', 0))) - base.add_child(Node.s8('button', profile.get_int('button', 0))) - base.add_child(Node.s8('last_play_flag', profile.get_int('last_play_flag', -1))) - base.add_child(Node.u8('medal_and_friend', profile.get_int('medal_and_friend', 0))) - base.add_child(Node.s8('category', profile.get_int('category', -1))) - base.add_child(Node.s8('sub_category', profile.get_int('sub_category', -1))) - base.add_child(Node.s16('chara', profile.get_int('chara', -1))) - base.add_child(Node.s8('chara_category', profile.get_int('chara_category', -1))) - base.add_child(Node.u8('collabo', profile.get_int('collabo', 255))) - base.add_child(Node.u8('sheet', profile.get_int('sheet', 0))) - base.add_child(Node.s8('tutorial', profile.get_int('tutorial', 0))) - base.add_child(Node.s32('music_open_pt', profile.get_int('music_open_pt', 0))) - base.add_child(Node.s8('is_conv', -1)) - base.add_child(Node.s32('option', profile.get_int('option', 0))) - base.add_child(Node.s16('music', profile.get_int('music', -1))) - base.add_child(Node.u16('ep', profile.get_int('ep', 0))) - base.add_child(Node.s32_array('sp_color_flg', profile.get_int_array('sp_color_flg', 2))) - base.add_child(Node.s32('read_news', profile.get_int('read_news', 0))) - base.add_child(Node.s16('consecutive_days_coupon', profile.get_int('consecutive_days_coupon', 0))) - base.add_child(Node.s8('staff', 0)) + base.add_child(Node.string("name", profile.get_str("name", "なし"))) + base.add_child(Node.string("g_pm_id", ID.format_extid(profile.extid))) + base.add_child(Node.u8("mode", profile.get_int("mode", 0))) + base.add_child(Node.s8("button", profile.get_int("button", 0))) + base.add_child(Node.s8("last_play_flag", profile.get_int("last_play_flag", -1))) + base.add_child( + Node.u8("medal_and_friend", profile.get_int("medal_and_friend", 0)) + ) + base.add_child(Node.s8("category", profile.get_int("category", -1))) + base.add_child(Node.s8("sub_category", profile.get_int("sub_category", -1))) + base.add_child(Node.s16("chara", profile.get_int("chara", -1))) + base.add_child(Node.s8("chara_category", profile.get_int("chara_category", -1))) + base.add_child(Node.u8("collabo", profile.get_int("collabo", 255))) + base.add_child(Node.u8("sheet", profile.get_int("sheet", 0))) + base.add_child(Node.s8("tutorial", profile.get_int("tutorial", 0))) + base.add_child(Node.s32("music_open_pt", profile.get_int("music_open_pt", 0))) + base.add_child(Node.s8("is_conv", -1)) + base.add_child(Node.s32("option", profile.get_int("option", 0))) + base.add_child(Node.s16("music", profile.get_int("music", -1))) + base.add_child(Node.u16("ep", profile.get_int("ep", 0))) + base.add_child( + Node.s32_array("sp_color_flg", profile.get_int_array("sp_color_flg", 2)) + ) + base.add_child(Node.s32("read_news", profile.get_int("read_news", 0))) + base.add_child( + Node.s16( + "consecutive_days_coupon", profile.get_int("consecutive_days_coupon", 0) + ) + ) + base.add_child(Node.s8("staff", 0)) # Player card section - player_card_dict = profile.get_dict('player_card') - player_card = Node.void('player_card') + player_card_dict = profile.get_dict("player_card") + player_card = Node.void("player_card") root.add_child(player_card) - player_card.add_child(Node.u8_array('title', player_card_dict.get_int_array('title', 2, [0, 1]))) - player_card.add_child(Node.u8('frame', player_card_dict.get_int('frame'))) - player_card.add_child(Node.u8('base', player_card_dict.get_int('base'))) - player_card.add_child(Node.u8_array('seal', player_card_dict.get_int_array('seal', 2))) - player_card.add_child(Node.s32_array('get_title', player_card_dict.get_int_array('get_title', 4))) - player_card.add_child(Node.s32('get_frame', player_card_dict.get_int('get_frame'))) - player_card.add_child(Node.s32('get_base', player_card_dict.get_int('get_base'))) - player_card.add_child(Node.s32_array('get_seal', player_card_dict.get_int_array('get_seal', 2))) - player_card.add_child(Node.s8('is_open', 1)) + player_card.add_child( + Node.u8_array("title", player_card_dict.get_int_array("title", 2, [0, 1])) + ) + player_card.add_child(Node.u8("frame", player_card_dict.get_int("frame"))) + player_card.add_child(Node.u8("base", player_card_dict.get_int("base"))) + player_card.add_child( + Node.u8_array("seal", player_card_dict.get_int_array("seal", 2)) + ) + player_card.add_child( + Node.s32_array("get_title", player_card_dict.get_int_array("get_title", 4)) + ) + player_card.add_child( + Node.s32("get_frame", player_card_dict.get_int("get_frame")) + ) + player_card.add_child( + Node.s32("get_base", player_card_dict.get_int("get_base")) + ) + player_card.add_child( + Node.s32_array("get_seal", player_card_dict.get_int_array("get_seal", 2)) + ) + player_card.add_child(Node.s8("is_open", 1)) # Player card EX section - player_card_ex = Node.void('player_card_ex') + player_card_ex = Node.void("player_card_ex") root.add_child(player_card_ex) - player_card_ex.add_child(Node.s32('get_title_ex', player_card_dict.get_int('get_title_ex'))) - player_card_ex.add_child(Node.s32('get_frame_ex', player_card_dict.get_int('get_frame_ex'))) - player_card_ex.add_child(Node.s32('get_base_ex', player_card_dict.get_int('get_base_ex'))) - player_card_ex.add_child(Node.s32('get_seal_ex', player_card_dict.get_int('get_seal_ex'))) + player_card_ex.add_child( + Node.s32("get_title_ex", player_card_dict.get_int("get_title_ex")) + ) + player_card_ex.add_child( + Node.s32("get_frame_ex", player_card_dict.get_int("get_frame_ex")) + ) + player_card_ex.add_child( + Node.s32("get_base_ex", player_card_dict.get_int("get_base_ex")) + ) + player_card_ex.add_child( + Node.s32("get_seal_ex", player_card_dict.get_int("get_seal_ex")) + ) # Statistics section and scores section statistics = self.get_play_statistics(userid) - base.add_child(Node.s32('total_play_cnt', statistics.total_plays)) - base.add_child(Node.s16('today_play_cnt', statistics.today_plays)) - base.add_child(Node.s16('consecutive_days', statistics.consecutive_days)) + base.add_child(Node.s32("total_play_cnt", statistics.total_plays)) + base.add_child(Node.s16("today_play_cnt", statistics.today_plays)) + base.add_child(Node.s16("consecutive_days", statistics.consecutive_days)) # Number of rivals that are active for this version. links = self.data.local.user.get_links(self.game, self.version, userid) rivalcount = 0 for link in links: - if link.type != 'rival': + if link.type != "rival": continue if not self.has_profile(link.other_userid): @@ -172,10 +200,20 @@ class PopnMusicFantasia(PopnMusicBase): # This profile is valid. rivalcount += 1 - base.add_child(Node.u8('active_fr_num', rivalcount)) + base.add_child(Node.u8("active_fr_num", rivalcount)) - last_played = [x[0] for x in self.data.local.music.get_last_played(self.game, self.version, userid, 3)] - most_played = [x[0] for x in self.data.local.music.get_most_played(self.game, self.version, userid, 20)] + last_played = [ + x[0] + for x in self.data.local.music.get_last_played( + self.game, self.version, userid, 3 + ) + ] + most_played = [ + x[0] + for x in self.data.local.music.get_most_played( + self.game, self.version, userid, 20 + ) + ] while len(last_played) < 3: last_played.append(-1) while len(most_played) < 20: @@ -198,10 +236,12 @@ class PopnMusicFantasia(PopnMusicBase): self.CHART_TYPE_EX, ]: continue - if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY: + if score.data.get_int("medal") == self.PLAY_MEDAL_NO_PLAY: continue - clear_medal[score.id] = clear_medal[score.id] | self.__format_medal_for_score(score) + clear_medal[score.id] = clear_medal[ + score.id + ] | self.__format_medal_for_score(score) hiscore_index = (score.id * 4) + { self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY_POSITION, self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL_POSITION, @@ -211,69 +251,81 @@ class PopnMusicFantasia(PopnMusicBase): hiscore_byte_pos = int((hiscore_index * 17) / 8) hiscore_bit_pos = int((hiscore_index * 17) % 8) hiscore_value = score.points << hiscore_bit_pos - hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (hiscore_value & 0xFF) - hiscore_array[hiscore_byte_pos + 1] = hiscore_array[hiscore_byte_pos + 1] | ((hiscore_value >> 8) & 0xFF) - hiscore_array[hiscore_byte_pos + 2] = hiscore_array[hiscore_byte_pos + 2] | ((hiscore_value >> 16) & 0xFF) + hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | ( + hiscore_value & 0xFF + ) + hiscore_array[hiscore_byte_pos + 1] = hiscore_array[ + hiscore_byte_pos + 1 + ] | ((hiscore_value >> 8) & 0xFF) + hiscore_array[hiscore_byte_pos + 2] = hiscore_array[ + hiscore_byte_pos + 2 + ] | ((hiscore_value >> 16) & 0xFF) hiscore = bytes(hiscore_array) - player_card.add_child(Node.s16_array('best_music', most_played[0:3])) - base.add_child(Node.s16_array('my_best', most_played)) - base.add_child(Node.s16_array('latest_music', last_played)) - base.add_child(Node.u16_array('clear_medal', clear_medal)) - base.add_child(Node.u8_array('clear_medal_sub', clear_medal_sub)) + player_card.add_child(Node.s16_array("best_music", most_played[0:3])) + base.add_child(Node.s16_array("my_best", most_played)) + base.add_child(Node.s16_array("latest_music", last_played)) + base.add_child(Node.u16_array("clear_medal", clear_medal)) + base.add_child(Node.u8_array("clear_medal_sub", clear_medal_sub)) # Goes outside of base for some reason - root.add_child(Node.binary('hiscore', hiscore)) + root.add_child(Node.binary("hiscore", hiscore)) # Net VS section - netvs = Node.void('netvs') + netvs = Node.void("netvs") root.add_child(netvs) - netvs.add_child(Node.s32_array('get_ojama', [0, 0])) - netvs.add_child(Node.s32('rank_point', 0)) - netvs.add_child(Node.s32('play_point', 0)) - netvs.add_child(Node.s16_array('record', [0, 0, 0, 0, 0, 0])) - netvs.add_child(Node.u8('rank', 0)) - netvs.add_child(Node.s8_array('ojama_condition', [0] * 74)) - netvs.add_child(Node.s8_array('set_ojama', [0, 0, 0])) - netvs.add_child(Node.s8_array('set_recommend', [0, 0, 0])) - netvs.add_child(Node.s8_array('jewelry', [0] * 15)) + netvs.add_child(Node.s32_array("get_ojama", [0, 0])) + netvs.add_child(Node.s32("rank_point", 0)) + netvs.add_child(Node.s32("play_point", 0)) + netvs.add_child(Node.s16_array("record", [0, 0, 0, 0, 0, 0])) + netvs.add_child(Node.u8("rank", 0)) + netvs.add_child(Node.s8_array("ojama_condition", [0] * 74)) + netvs.add_child(Node.s8_array("set_ojama", [0, 0, 0])) + netvs.add_child(Node.s8_array("set_recommend", [0, 0, 0])) + netvs.add_child(Node.s8_array("jewelry", [0] * 15)) for dialog in [0, 1, 2, 3, 4, 5]: - netvs.add_child(Node.string('dialog', f'dialog#{dialog}')) + netvs.add_child(Node.string("dialog", f"dialog#{dialog}")) - sp_data = Node.void('sp_data') + sp_data = Node.void("sp_data") root.add_child(sp_data) - sp_data.add_child(Node.s32('sp', profile.get_int('sp', 0))) + sp_data.add_child(Node.s32("sp", profile.get_int("sp", 0))) - reflec_data = Node.void('reflec_data') + reflec_data = Node.void("reflec_data") root.add_child(reflec_data) - reflec_data.add_child(Node.s8_array('reflec', profile.get_int_array('reflec', 2))) + reflec_data.add_child( + Node.s8_array("reflec", profile.get_int_array("reflec", 2)) + ) # Navigate section for i in range(3): - navigate_dict = profile.get_dict(f'navigate_{i}') - navigate = Node.void('navigate') + navigate_dict = profile.get_dict(f"navigate_{i}") + navigate = Node.void("navigate") root.add_child(navigate) - navigate.add_child(Node.s8('genre', navigate_dict.get_int('genre', -1))) - navigate.add_child(Node.s8('image', navigate_dict.get_int('image', -1))) - navigate.add_child(Node.s8('level', navigate_dict.get_int('level', -1))) - navigate.add_child(Node.s8('ojama', navigate_dict.get_int('ojama', -1))) - navigate.add_child(Node.s16('limit_num', navigate_dict.get_int('limit_num', -1))) - navigate.add_child(Node.s8('button', navigate_dict.get_int('button', -1))) - navigate.add_child(Node.s8('life', navigate_dict.get_int('life', -1))) - navigate.add_child(Node.s16('progress', navigate_dict.get_int('progress', -1))) + navigate.add_child(Node.s8("genre", navigate_dict.get_int("genre", -1))) + navigate.add_child(Node.s8("image", navigate_dict.get_int("image", -1))) + navigate.add_child(Node.s8("level", navigate_dict.get_int("level", -1))) + navigate.add_child(Node.s8("ojama", navigate_dict.get_int("ojama", -1))) + navigate.add_child( + Node.s16("limit_num", navigate_dict.get_int("limit_num", -1)) + ) + navigate.add_child(Node.s8("button", navigate_dict.get_int("button", -1))) + navigate.add_child(Node.s8("life", navigate_dict.get_int("life", -1))) + navigate.add_child( + Node.s16("progress", navigate_dict.get_int("progress", -1)) + ) return root def format_conversion(self, userid: UserID, profile: Profile) -> Node: - root = Node.void('playerdata') + root = Node.void("playerdata") - root.add_child(Node.string('name', profile.get_str('name', 'なし'))) - root.add_child(Node.s16('chara', profile.get_int('chara', -1))) - root.add_child(Node.s32('option', profile.get_int('option', 0))) - root.add_child(Node.u8('version', 0)) - root.add_child(Node.u8('kind', 0)) - root.add_child(Node.u8('season', 0)) + root.add_child(Node.string("name", profile.get_str("name", "なし"))) + root.add_child(Node.s16("chara", profile.get_int("chara", -1))) + root.add_child(Node.s32("option", profile.get_int("option", 0))) + root.add_child(Node.u8("version", 0)) + root.add_child(Node.u8("kind", 0)) + root.add_child(Node.u8("season", 0)) clear_medal = [0] * self.GAME_MAX_MUSIC_ID hiscore_array = [0] * int((((self.GAME_MAX_MUSIC_ID * 4) * 17) + 7) / 8) @@ -291,10 +343,12 @@ class PopnMusicFantasia(PopnMusicBase): self.CHART_TYPE_EX, ]: continue - if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY: + if score.data.get_int("medal") == self.PLAY_MEDAL_NO_PLAY: continue - clear_medal[score.id] = clear_medal[score.id] | self.__format_medal_for_score(score) + clear_medal[score.id] = clear_medal[ + score.id + ] | self.__format_medal_for_score(score) hiscore_index = (score.id * 4) + { self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY_POSITION, self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL_POSITION, @@ -304,86 +358,114 @@ class PopnMusicFantasia(PopnMusicBase): hiscore_byte_pos = int((hiscore_index * 17) / 8) hiscore_bit_pos = int((hiscore_index * 17) % 8) hiscore_value = score.points << hiscore_bit_pos - hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (hiscore_value & 0xFF) - hiscore_array[hiscore_byte_pos + 1] = hiscore_array[hiscore_byte_pos + 1] | ((hiscore_value >> 8) & 0xFF) - hiscore_array[hiscore_byte_pos + 2] = hiscore_array[hiscore_byte_pos + 2] | ((hiscore_value >> 16) & 0xFF) + hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | ( + hiscore_value & 0xFF + ) + hiscore_array[hiscore_byte_pos + 1] = hiscore_array[ + hiscore_byte_pos + 1 + ] | ((hiscore_value >> 8) & 0xFF) + hiscore_array[hiscore_byte_pos + 2] = hiscore_array[ + hiscore_byte_pos + 2 + ] | ((hiscore_value >> 16) & 0xFF) - root.add_child(Node.u16_array('clear_medal', clear_medal)) - root.add_child(Node.binary('hiscore', bytes(hiscore_array))) + root.add_child(Node.u16_array("clear_medal", clear_medal)) + root.add_child(Node.binary("hiscore", bytes(hiscore_array))) return root - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: # For some reason, Pop'n 20 sends us two profile saves, one with 'not done yet' # so we only want to process the done yet node. The 'not gameover' save has # jubeat collabo stuff set in it, but we don't use that so it doesn't matter. - if request.child_value('is_not_gameover') == 1: + if request.child_value("is_not_gameover") == 1: return oldprofile newprofile = oldprofile.clone() - newprofile.replace_int('option', request.child_value('option')) - newprofile.replace_int('chara', request.child_value('chara')) - newprofile.replace_int('mode', request.child_value('mode')) - newprofile.replace_int('button', request.child_value('button')) - newprofile.replace_int('music', request.child_value('music')) - newprofile.replace_int('sheet', request.child_value('sheet')) - newprofile.replace_int('last_play_flag', request.child_value('last_play_flag')) - newprofile.replace_int('category', request.child_value('category')) - newprofile.replace_int('sub_category', request.child_value('sub_category')) - newprofile.replace_int('chara_category', request.child_value('chara_category')) - newprofile.replace_int('medal_and_friend', request.child_value('medal_and_friend')) - newprofile.replace_int('ep', request.child_value('ep')) - newprofile.replace_int_array('sp_color_flg', 2, request.child_value('sp_color_flg')) - newprofile.replace_int('read_news', request.child_value('read_news')) - newprofile.replace_int('consecutive_days_coupon', request.child_value('consecutive_days_coupon')) - newprofile.replace_int('tutorial', request.child_value('tutorial')) - newprofile.replace_int('music_open_pt', request.child_value('music_open_pt')) - newprofile.replace_int('collabo', request.child_value('collabo')) + newprofile.replace_int("option", request.child_value("option")) + newprofile.replace_int("chara", request.child_value("chara")) + newprofile.replace_int("mode", request.child_value("mode")) + newprofile.replace_int("button", request.child_value("button")) + newprofile.replace_int("music", request.child_value("music")) + newprofile.replace_int("sheet", request.child_value("sheet")) + newprofile.replace_int("last_play_flag", request.child_value("last_play_flag")) + newprofile.replace_int("category", request.child_value("category")) + newprofile.replace_int("sub_category", request.child_value("sub_category")) + newprofile.replace_int("chara_category", request.child_value("chara_category")) + newprofile.replace_int( + "medal_and_friend", request.child_value("medal_and_friend") + ) + newprofile.replace_int("ep", request.child_value("ep")) + newprofile.replace_int_array( + "sp_color_flg", 2, request.child_value("sp_color_flg") + ) + newprofile.replace_int("read_news", request.child_value("read_news")) + newprofile.replace_int( + "consecutive_days_coupon", request.child_value("consecutive_days_coupon") + ) + newprofile.replace_int("tutorial", request.child_value("tutorial")) + newprofile.replace_int("music_open_pt", request.child_value("music_open_pt")) + newprofile.replace_int("collabo", request.child_value("collabo")) - sp_node = request.child('sp_data') + sp_node = request.child("sp_data") if sp_node is not None: - newprofile.replace_int('sp', sp_node.child_value('sp')) + newprofile.replace_int("sp", sp_node.child_value("sp")) - reflec_node = request.child('reflec_data') + reflec_node = request.child("reflec_data") if reflec_node is not None: - newprofile.replace_int_array('reflec', 2, reflec_node.child_value('reflec')) + newprofile.replace_int_array("reflec", 2, reflec_node.child_value("reflec")) # Keep track of play statistics self.update_play_statistics(userid) # Extract player card stuff - player_card_dict = newprofile.get_dict('player_card') - player_card_dict.replace_int_array('title', 2, request.child_value('title')) - player_card_dict.replace_int('frame', request.child_value('frame')) - player_card_dict.replace_int('base', request.child_value('base')) - player_card_dict.replace_int_array('seal', 2, request.child_value('seal')) - player_card_dict.replace_int_array('get_title', 4, request.child_value('get_title')) - player_card_dict.replace_int('get_frame', request.child_value('get_frame')) - player_card_dict.replace_int('get_base', request.child_value('get_base')) - player_card_dict.replace_int_array('get_seal', 2, request.child_value('get_seal')) + player_card_dict = newprofile.get_dict("player_card") + player_card_dict.replace_int_array("title", 2, request.child_value("title")) + player_card_dict.replace_int("frame", request.child_value("frame")) + player_card_dict.replace_int("base", request.child_value("base")) + player_card_dict.replace_int_array("seal", 2, request.child_value("seal")) + player_card_dict.replace_int_array( + "get_title", 4, request.child_value("get_title") + ) + player_card_dict.replace_int("get_frame", request.child_value("get_frame")) + player_card_dict.replace_int("get_base", request.child_value("get_base")) + player_card_dict.replace_int_array( + "get_seal", 2, request.child_value("get_seal") + ) - player_card_ex = request.child('player_card_ex') + player_card_ex = request.child("player_card_ex") if player_card_ex is not None: - player_card_dict.replace_int('get_title_ex', player_card_ex.child_value('get_title_ex')) - player_card_dict.replace_int('get_frame_ex', player_card_ex.child_value('get_frame_ex')) - player_card_dict.replace_int('get_base_ex', player_card_ex.child_value('get_base_ex')) - player_card_dict.replace_int('get_seal_ex', player_card_ex.child_value('get_seal_ex')) - newprofile.replace_dict('player_card', player_card_dict) + player_card_dict.replace_int( + "get_title_ex", player_card_ex.child_value("get_title_ex") + ) + player_card_dict.replace_int( + "get_frame_ex", player_card_ex.child_value("get_frame_ex") + ) + player_card_dict.replace_int( + "get_base_ex", player_card_ex.child_value("get_base_ex") + ) + player_card_dict.replace_int( + "get_seal_ex", player_card_ex.child_value("get_seal_ex") + ) + newprofile.replace_dict("player_card", player_card_dict) # Extract navigate stuff nav_id = 0 for navigate in request.children: - if navigate.name == 'navigate': - navigate_dict = newprofile.get_dict(f'navigate_{nav_id}') - navigate_dict.replace_int('genre', navigate.child_value('genre')) - navigate_dict.replace_int('image', navigate.child_value('image')) - navigate_dict.replace_int('level', navigate.child_value('level')) - navigate_dict.replace_int('ojama', navigate.child_value('ojama')) - navigate_dict.replace_int('limit_num', navigate.child_value('limit_num')) - navigate_dict.replace_int('button', navigate.child_value('button')) - navigate_dict.replace_int('life', navigate.child_value('life')) - navigate_dict.replace_int('progress', navigate.child_value('progress')) - newprofile.replace_dict(f'navigate_{nav_id}', navigate_dict) + if navigate.name == "navigate": + navigate_dict = newprofile.get_dict(f"navigate_{nav_id}") + navigate_dict.replace_int("genre", navigate.child_value("genre")) + navigate_dict.replace_int("image", navigate.child_value("image")) + navigate_dict.replace_int("level", navigate.child_value("level")) + navigate_dict.replace_int("ojama", navigate.child_value("ojama")) + navigate_dict.replace_int( + "limit_num", navigate.child_value("limit_num") + ) + navigate_dict.replace_int("button", navigate.child_value("button")) + navigate_dict.replace_int("life", navigate.child_value("life")) + navigate_dict.replace_int("progress", navigate.child_value("progress")) + newprofile.replace_dict(f"navigate_{nav_id}", navigate_dict) nav_id += 1 if nav_id >= 3: @@ -391,15 +473,15 @@ class PopnMusicFantasia(PopnMusicBase): # Extract scores for node in request.children: - if node.name == 'stage': - songid = node.child_value('no') + if node.name == "stage": + songid = node.child_value("no") chart = { self.GAME_CHART_TYPE_EASY: self.CHART_TYPE_EASY, self.GAME_CHART_TYPE_NORMAL: self.CHART_TYPE_NORMAL, self.GAME_CHART_TYPE_HYPER: self.CHART_TYPE_HYPER, self.GAME_CHART_TYPE_EX: self.CHART_TYPE_EX, - }[node.child_value('sheet')] - medal = (node.child_value('n_data') >> (chart * 4)) & 0x000F + }[node.child_value("sheet")] + medal = (node.child_value("n_data") >> (chart * 4)) & 0x000F medal = { self.GAME_PLAY_MEDAL_CIRCLE_FAILED: self.PLAY_MEDAL_CIRCLE_FAILED, self.GAME_PLAY_MEDAL_DIAMOND_FAILED: self.PLAY_MEDAL_DIAMOND_FAILED, @@ -412,116 +494,132 @@ class PopnMusicFantasia(PopnMusicBase): self.GAME_PLAY_MEDAL_STAR_FULL_COMBO: self.PLAY_MEDAL_STAR_FULL_COMBO, self.GAME_PLAY_MEDAL_PERFECT: self.PLAY_MEDAL_PERFECT, }[medal] - points = node.child_value('score') + points = node.child_value("score") self.update_score(userid, songid, chart, points, medal) return newprofile def handle_playerdata_expire_request(self, request: Node) -> Node: - return Node.void('playerdata') + return Node.void("playerdata") def handle_playerdata_logout_request(self, request: Node) -> Node: - return Node.void('playerdata') + return Node.void("playerdata") def handle_playerdata_get_request(self, request: Node) -> Node: - modelstring = request.attribute('model') - refid = request.child_value('ref_id') + modelstring = request.attribute("model") + refid = request.child_value("ref_id") root = self.get_profile_by_refid( refid, self.NEW_PROFILE_ONLY if modelstring is None else self.OLD_PROFILE_ONLY, ) if root is None: - root = Node.void('playerdata') - root.set_attribute('status', str(Status.NO_PROFILE)) + root = Node.void("playerdata") + root.set_attribute("status", str(Status.NO_PROFILE)) return root def handle_playerdata_conversion_request(self, request: Node) -> Node: - refid = request.child_value('ref_id') - name = request.child_value('name') - chara = request.child_value('chara') + refid = request.child_value("ref_id") + name = request.child_value("name") + chara = request.child_value("chara") root = self.new_profile_by_refid(refid, name, chara) if root is None: - root = Node.void('playerdata') - root.set_attribute('status', str(Status.NO_PROFILE)) + root = Node.void("playerdata") + root.set_attribute("status", str(Status.NO_PROFILE)) return root def handle_playerdata_new_request(self, request: Node) -> Node: - refid = request.child_value('ref_id') - name = request.child_value('name') + refid = request.child_value("ref_id") + name = request.child_value("name") root = self.new_profile_by_refid(refid, name) if root is None: - root = Node.void('playerdata') - root.set_attribute('status', str(Status.NO_PROFILE)) + root = Node.void("playerdata") + root.set_attribute("status", str(Status.NO_PROFILE)) return root def handle_playerdata_set_request(self, request: Node) -> Node: - refid = request.attribute('ref_id') + refid = request.attribute("ref_id") machine = self.get_machine() - root = Node.void('playerdata') - root.add_child(Node.s8('pref', machine.data.get_int('pref', self.get_machine_region()))) + root = Node.void("playerdata") + root.add_child( + Node.s8("pref", machine.data.get_int("pref", self.get_machine_region())) + ) if refid is None: - root.add_child(Node.string('name', '')) - root.add_child(Node.s16('chara', -1)) - root.add_child(Node.u8('frame', 0)) - root.add_child(Node.u8('base', 0)) - root.add_child(Node.u8('seal_1', 0)) - root.add_child(Node.u8('seal_2', 0)) - root.add_child(Node.u8('title_1', 0)) - root.add_child(Node.u8('title_2', 0)) - root.add_child(Node.s16('recommend_1', -1)) - root.add_child(Node.s16('recommend_2', -1)) - root.add_child(Node.s16('recommend_3', -1)) - root.add_child(Node.string('message', '')) + root.add_child(Node.string("name", "")) + root.add_child(Node.s16("chara", -1)) + root.add_child(Node.u8("frame", 0)) + root.add_child(Node.u8("base", 0)) + root.add_child(Node.u8("seal_1", 0)) + root.add_child(Node.u8("seal_2", 0)) + root.add_child(Node.u8("title_1", 0)) + root.add_child(Node.u8("title_2", 0)) + root.add_child(Node.s16("recommend_1", -1)) + root.add_child(Node.s16("recommend_2", -1)) + root.add_child(Node.s16("recommend_3", -1)) + root.add_child(Node.string("message", "")) return root userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is None: - root.add_child(Node.string('name', '')) - root.add_child(Node.s16('chara', -1)) - root.add_child(Node.u8('frame', 0)) - root.add_child(Node.u8('base', 0)) - root.add_child(Node.u8('seal_1', 0)) - root.add_child(Node.u8('seal_2', 0)) - root.add_child(Node.u8('title_1', 0)) - root.add_child(Node.u8('title_2', 0)) - root.add_child(Node.s16('recommend_1', -1)) - root.add_child(Node.s16('recommend_2', -1)) - root.add_child(Node.s16('recommend_3', -1)) - root.add_child(Node.string('message', '')) + root.add_child(Node.string("name", "")) + root.add_child(Node.s16("chara", -1)) + root.add_child(Node.u8("frame", 0)) + root.add_child(Node.u8("base", 0)) + root.add_child(Node.u8("seal_1", 0)) + root.add_child(Node.u8("seal_2", 0)) + root.add_child(Node.u8("title_1", 0)) + root.add_child(Node.u8("title_2", 0)) + root.add_child(Node.s16("recommend_1", -1)) + root.add_child(Node.s16("recommend_2", -1)) + root.add_child(Node.s16("recommend_3", -1)) + root.add_child(Node.string("message", "")) return root - oldprofile = self.get_profile(userid) or Profile(self.game, self.version, refid, 0) + oldprofile = self.get_profile(userid) or Profile( + self.game, self.version, refid, 0 + ) newprofile = self.unformat_profile(userid, request, oldprofile) if newprofile is not None: - player_card_dict = newprofile.get_dict('player_card') + player_card_dict = newprofile.get_dict("player_card") self.put_profile(userid, newprofile) - root.add_child(Node.string('name', newprofile.get_str('name', 'なし'))) - root.add_child(Node.s16('chara', newprofile.get_int('chara', -1))) - root.add_child(Node.u8('frame', player_card_dict.get_int('frame'))) - root.add_child(Node.u8('base', player_card_dict.get_int('base'))) - root.add_child(Node.u8('seal_1', player_card_dict.get_int_array('seal', 2)[0])) - root.add_child(Node.u8('seal_2', player_card_dict.get_int_array('seal', 2)[1])) - root.add_child(Node.u8('title_1', player_card_dict.get_int_array('title', 2, [0, 1])[0])) - root.add_child(Node.u8('title_2', player_card_dict.get_int_array('title', 2, [0, 1])[1])) - root.add_child(Node.s16('recommend_1', -1)) - root.add_child(Node.s16('recommend_2', -1)) - root.add_child(Node.s16('recommend_3', -1)) - root.add_child(Node.string('message', '')) + root.add_child(Node.string("name", newprofile.get_str("name", "なし"))) + root.add_child(Node.s16("chara", newprofile.get_int("chara", -1))) + root.add_child(Node.u8("frame", player_card_dict.get_int("frame"))) + root.add_child(Node.u8("base", player_card_dict.get_int("base"))) + root.add_child( + Node.u8("seal_1", player_card_dict.get_int_array("seal", 2)[0]) + ) + root.add_child( + Node.u8("seal_2", player_card_dict.get_int_array("seal", 2)[1]) + ) + root.add_child( + Node.u8( + "title_1", player_card_dict.get_int_array("title", 2, [0, 1])[0] + ) + ) + root.add_child( + Node.u8( + "title_2", player_card_dict.get_int_array("title", 2, [0, 1])[1] + ) + ) + root.add_child(Node.s16("recommend_1", -1)) + root.add_child(Node.s16("recommend_2", -1)) + root.add_child(Node.s16("recommend_3", -1)) + root.add_child(Node.string("message", "")) return root def handle_playerdata_friend_request(self, request: Node) -> Node: - refid = request.attribute('ref_id') - root = Node.void('playerdata') + refid = request.attribute("ref_id") + root = Node.void("playerdata") # Look up our own user ID based on the RefID provided. userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is None: - root.set_attribute('status', str(Status.NO_PROFILE)) + root.set_attribute("status", str(Status.NO_PROFILE)) return root # Grab the links that we care about. @@ -529,7 +627,7 @@ class PopnMusicFantasia(PopnMusicBase): profiles: Dict[UserID, Profile] = {} rivals: List[Link] = [] for link in links: - if link.type != 'rival': + if link.type != "rival": continue other_profile = self.get_profile(link.other_userid) @@ -544,17 +642,19 @@ class PopnMusicFantasia(PopnMusicBase): scores = self.data.remote.music.get_scores(self.game, self.version, rivalid) # First, output general profile info. - friend = Node.void('friend') + friend = Node.void("friend") root.add_child(friend) # This might be for having non-active or non-confirmed friends, but setting to 0 makes the # ranking numbers disappear and the player icon show a questionmark. - friend.add_child(Node.s8('open', 1)) + friend.add_child(Node.s8("open", 1)) # Set up some sane defaults. - friend.add_child(Node.string('name', rivalprofile.get_str('name', 'なし'))) - friend.add_child(Node.string('g_pm_id', ID.format_extid(rivalprofile.extid))) - friend.add_child(Node.s16('chara', rivalprofile.get_int('chara', -1))) + friend.add_child(Node.string("name", rivalprofile.get_str("name", "なし"))) + friend.add_child( + Node.string("g_pm_id", ID.format_extid(rivalprofile.extid)) + ) + friend.add_child(Node.s16("chara", rivalprofile.get_int("chara", -1))) # Perform hiscore/medal conversion. hiscore_array = [0] * int((((self.GAME_MAX_MUSIC_ID * 4) * 17) + 7) / 8) @@ -571,10 +671,12 @@ class PopnMusicFantasia(PopnMusicBase): self.CHART_TYPE_EX, ]: continue - if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY: + if score.data.get_int("medal") == self.PLAY_MEDAL_NO_PLAY: continue - clear_medal[score.id] = clear_medal[score.id] | self.__format_medal_for_score(score) + clear_medal[score.id] = clear_medal[ + score.id + ] | self.__format_medal_for_score(score) hiscore_index = (score.id * 4) + { self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY_POSITION, self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL_POSITION, @@ -584,13 +686,19 @@ class PopnMusicFantasia(PopnMusicBase): hiscore_byte_pos = int((hiscore_index * 17) / 8) hiscore_bit_pos = int((hiscore_index * 17) % 8) hiscore_value = score.points << hiscore_bit_pos - hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (hiscore_value & 0xFF) - hiscore_array[hiscore_byte_pos + 1] = hiscore_array[hiscore_byte_pos + 1] | ((hiscore_value >> 8) & 0xFF) - hiscore_array[hiscore_byte_pos + 2] = hiscore_array[hiscore_byte_pos + 2] | ((hiscore_value >> 16) & 0xFF) + hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | ( + hiscore_value & 0xFF + ) + hiscore_array[hiscore_byte_pos + 1] = hiscore_array[ + hiscore_byte_pos + 1 + ] | ((hiscore_value >> 8) & 0xFF) + hiscore_array[hiscore_byte_pos + 2] = hiscore_array[ + hiscore_byte_pos + 2 + ] | ((hiscore_value >> 16) & 0xFF) hiscore = bytes(hiscore_array) - friend.add_child(Node.u16_array('clear_medal', clear_medal)) - friend.add_child(Node.binary('hiscore', hiscore)) + friend.add_child(Node.u16_array("clear_medal", clear_medal)) + friend.add_child(Node.binary("hiscore", hiscore)) # Note that if we ever support internet ranking mode, there's an 'ir_hiscore' node here as well. @@ -601,41 +709,45 @@ class PopnMusicFantasia(PopnMusicBase): def handle_game_get_request(self, request: Node) -> Node: game_config = self.get_game_config() - game_phase = game_config.get_int('game_phase') - event_phase = game_config.get_int('event_phase') + game_phase = game_config.get_int("game_phase") + event_phase = game_config.get_int("event_phase") - root = Node.void('game') - root.add_child(Node.s32('game_phase', game_phase)) - root.add_child(Node.s32('ir_phase', 0)) - root.add_child(Node.s32('event_phase', event_phase)) - root.add_child(Node.s32('netvs_phase', 0)) # Net taisen mode, we don't support lobbies. - root.add_child(Node.s32('card_phase', 6)) - root.add_child(Node.s32('illust_phase', 2)) - root.add_child(Node.s32('psp_phase', 5)) # Unlock songs from Pop'n Music Portable. - root.add_child(Node.s32('other_phase', 1)) - root.add_child(Node.s32('jubeat_phase', 1)) - root.add_child(Node.s32('public_phase', 3)) - root.add_child(Node.s32('kac_phase', 2)) - root.add_child(Node.s32('local_matching_enable', 1)) - root.add_child(Node.s32('n_matching_sec', 60)) - root.add_child(Node.s32('l_matching_sec', 60)) - root.add_child(Node.s32('is_check_cpu', 0)) - root.add_child(Node.s32('week_no', 0)) - root.add_child(Node.s32('team_day', 0)) - root.add_child(Node.s32_array('ng_illust', [-1] * 64)) - root.add_child(Node.s16_array('sel_ranking', [-1] * 10)) - root.add_child(Node.s16_array('up_ranking', [-1] * 10)) + root = Node.void("game") + root.add_child(Node.s32("game_phase", game_phase)) + root.add_child(Node.s32("ir_phase", 0)) + root.add_child(Node.s32("event_phase", event_phase)) + root.add_child( + Node.s32("netvs_phase", 0) + ) # Net taisen mode, we don't support lobbies. + root.add_child(Node.s32("card_phase", 6)) + root.add_child(Node.s32("illust_phase", 2)) + root.add_child( + Node.s32("psp_phase", 5) + ) # Unlock songs from Pop'n Music Portable. + root.add_child(Node.s32("other_phase", 1)) + root.add_child(Node.s32("jubeat_phase", 1)) + root.add_child(Node.s32("public_phase", 3)) + root.add_child(Node.s32("kac_phase", 2)) + root.add_child(Node.s32("local_matching_enable", 1)) + root.add_child(Node.s32("n_matching_sec", 60)) + root.add_child(Node.s32("l_matching_sec", 60)) + root.add_child(Node.s32("is_check_cpu", 0)) + root.add_child(Node.s32("week_no", 0)) + root.add_child(Node.s32("team_day", 0)) + root.add_child(Node.s32_array("ng_illust", [-1] * 64)) + root.add_child(Node.s16_array("sel_ranking", [-1] * 10)) + root.add_child(Node.s16_array("up_ranking", [-1] * 10)) return root def handle_game_active_request(self, request: Node) -> Node: # Update the name of this cab for admin purposes. Also store the prefecture. machine = self.get_machine() - machine.name = request.child_value('shop_name') or machine.name - machine.data.replace_int('pref', request.child_value('pref')) + machine.name = request.child_value("shop_name") or machine.name + machine.data.replace_int("pref", request.child_value("pref")) self.update_machine(machine) - return Node.void('game') + return Node.void("game") def handle_lobby_requests(self, request: Node) -> Node: # Stub out the entire lobby service - return Node.void('lobby') + return Node.void("lobby") diff --git a/bemani/backend/popn/kaimei.py b/bemani/backend/popn/kaimei.py index ad09b37..3f536b9 100644 --- a/bemani/backend/popn/kaimei.py +++ b/bemani/backend/popn/kaimei.py @@ -41,164 +41,164 @@ class PopnMusicKaimei(PopnMusicModernBase): Return all of our front-end modifiably settings. """ return { - 'ints': [ + "ints": [ { - 'name': 'Music Open Phase', - 'tip': 'Default music phase for all players.', - 'category': 'game_config', - 'setting': 'music_phase', - 'values': { + "name": "Music Open Phase", + "tip": "Default music phase for all players.", + "category": "game_config", + "setting": "music_phase", + "values": { # The value goes to 30 now, but it starts where usaneko left off at 23 # Unlocks a total of 10 songs - 23: 'No music unlocks', - 24: 'Phase 1', - 25: 'Phase 2', - 26: 'Phase 3', - 27: 'Phase 4', - 28: 'Phase 5', - 29: 'Phase 6', - 30: 'Phase MAX', - } + 23: "No music unlocks", + 24: "Phase 1", + 25: "Phase 2", + 26: "Phase 3", + 27: "Phase 4", + 28: "Phase 5", + 29: "Phase 6", + 30: "Phase MAX", + }, }, { - 'name': 'Kaimei! MN tanteisha event Phase', - 'tip': 'Kaimei! MN tanteisha event phase for all players.', - 'category': 'game_config', - 'setting': 'mn_tanteisha_phase', - 'values': { - 0: 'Disabled', - 1: 'Roki', - 2: 'shiroro', - 3: 'PIERRE&JILL', - 4: 'ROSA', - 5: 'taoxiang', - 6: 'TangTang', - 7: 'OTOBEAR', - 8: 'kaorin', - 9: 'CHARLY', - 10: 'ALOE', - 11: 'RIE♥chan', - 12: 'hina', - 13: 'PAPYRUS', - 14: '雷蔵, miho, RIE♥chan, Ryusei Honey', - 15: 'Murasaki', - 16: 'Lucifelle', - 17: '六', - 18: 'stella', - 19: 'ちせ', - 20: 'LISA', - 21: 'SUMIRE', - 22: 'SHISHITUGU', - 23: 'WALKER', - 24: 'Candy', - 25: 'Jade', - 26: 'AYA', - 27: 'kaorin', - 28: 'Lord Meh', - 29: 'HAMANOV', - 30: 'Agent', - 31: 'Yima', - 32: 'ikkei', - 33: 'echidna', - 34: 'lithos', - 35: 'SMOKE', - 36: 'the KING', - 37: 'Kicoro', - 38: 'DEBORAH', - 39: 'Teruo', - 40: 'the TOWER', - 41: 'Mamoru-kun', - 42: 'Canopus', - 43: 'Mimi Nyami', - 44: 'iO-LOWER', - 45: 'BOY', - 46: 'Sergei', - 47: 'SAPPHIRE', - 48: 'Chocky', - 49: 'HAPPPY', - 50: 'SHOLLKEE', - 51: 'CHARA-O', - 52: 'Hugh, GRIM, SUMIKO', - 53: 'Peetan', - 54: 'SHARK', - 55: 'Nakajima-san', - 56: 'KIKYO', - 57: 'SUMIRE', - 58: 'NAKAJI', - 59: 'moi moi', - 60: 'TITICACA', - 61: 'MASAMUNE', - 62: 'YUMMY' + "name": "Kaimei! MN tanteisha event Phase", + "tip": "Kaimei! MN tanteisha event phase for all players.", + "category": "game_config", + "setting": "mn_tanteisha_phase", + "values": { + 0: "Disabled", + 1: "Roki", + 2: "shiroro", + 3: "PIERRE&JILL", + 4: "ROSA", + 5: "taoxiang", + 6: "TangTang", + 7: "OTOBEAR", + 8: "kaorin", + 9: "CHARLY", + 10: "ALOE", + 11: "RIE♥chan", + 12: "hina", + 13: "PAPYRUS", + 14: "雷蔵, miho, RIE♥chan, Ryusei Honey", + 15: "Murasaki", + 16: "Lucifelle", + 17: "六", + 18: "stella", + 19: "ちせ", + 20: "LISA", + 21: "SUMIRE", + 22: "SHISHITUGU", + 23: "WALKER", + 24: "Candy", + 25: "Jade", + 26: "AYA", + 27: "kaorin", + 28: "Lord Meh", + 29: "HAMANOV", + 30: "Agent", + 31: "Yima", + 32: "ikkei", + 33: "echidna", + 34: "lithos", + 35: "SMOKE", + 36: "the KING", + 37: "Kicoro", + 38: "DEBORAH", + 39: "Teruo", + 40: "the TOWER", + 41: "Mamoru-kun", + 42: "Canopus", + 43: "Mimi Nyami", + 44: "iO-LOWER", + 45: "BOY", + 46: "Sergei", + 47: "SAPPHIRE", + 48: "Chocky", + 49: "HAPPPY", + 50: "SHOLLKEE", + 51: "CHARA-O", + 52: "Hugh, GRIM, SUMIKO", + 53: "Peetan", + 54: "SHARK", + 55: "Nakajima-san", + 56: "KIKYO", + 57: "SUMIRE", + 58: "NAKAJI", + 59: "moi moi", + 60: "TITICACA", + 61: "MASAMUNE", + 62: "YUMMY", }, }, { # For festive times, it's possible to change the welcome greeting. I'm not sure why you would want to change this, but now you can. - 'name': 'Holiday Greeting', - 'tip': 'Changes the payment selection confirmation sound.', - 'category': 'game_config', - 'setting': 'holiday_greeting', - 'values': { - 0: 'Okay!', - 1: 'Merry Christmas!', - 2: 'Happy New Year!', - } + "name": "Holiday Greeting", + "tip": "Changes the payment selection confirmation sound.", + "category": "game_config", + "setting": "holiday_greeting", + "values": { + 0: "Okay!", + 1: "Merry Christmas!", + 2: "Happy New Year!", + }, }, { # peace soundtrack hatsubai kinen SP event, 0 = off, 1 = active, 2 = off (0-2) - 'name': 'peace soundtrack hatsubai kinen SP', - 'tip': 'peace soundtrack hatsubai kinen SP for all players.', - 'category': 'game_config', - 'setting': 'peace_soundtrack', - 'values': { - 0: 'Not stated', - 1: 'Active', - 2: 'Ended', - } + "name": "peace soundtrack hatsubai kinen SP", + "tip": "peace soundtrack hatsubai kinen SP for all players.", + "category": "game_config", + "setting": "peace_soundtrack", + "values": { + 0: "Not stated", + 1: "Active", + 2: "Ended", + }, }, { - 'name': 'MZD no kimagure tanteisha joshu', - 'tip': 'Boost increasing the Clarification Level, if four or more Requests still unresolved.', - 'category': 'game_config', - 'setting': 'tanteisha_joshu', - 'values': { - 0: 'Not stated', - 1: 'Active', - 2: 'Ended', - } + "name": "MZD no kimagure tanteisha joshu", + "tip": "Boost increasing the Clarification Level, if four or more Requests still unresolved.", + "category": "game_config", + "setting": "tanteisha_joshu", + "values": { + 0: "Not stated", + 1: "Active", + 2: "Ended", + }, }, { # Shutchou! pop'n quest Lively II event - 'name': 'Shutchou! pop\'n quest Lively phase', - 'tip': 'Shutchou! pop\'n quest Lively phase for all players.', - 'category': 'game_config', - 'setting': 'popn_quest_lively', - 'values': { - 0: 'Not started', - 1: 'fes 1', - 2: 'fes 2', - 3: 'fes FINAL', - 4: 'fes EXTRA', - 5: 'Ended', - } + "name": "Shutchou! pop'n quest Lively phase", + "tip": "Shutchou! pop'n quest Lively phase for all players.", + "category": "game_config", + "setting": "popn_quest_lively", + "values": { + 0: "Not started", + 1: "fes 1", + 2: "fes 2", + 3: "fes FINAL", + 4: "fes EXTRA", + 5: "Ended", + }, }, { # Shutchou! pop'n quest Lively II event - 'name': 'Shutchou! pop\'n quest Lively II phase', - 'tip': 'Shutchou! pop\'n quest Lively II phase for all players.', - 'category': 'game_config', - 'setting': 'popn_quest_lively_2', - 'values': { - 0: 'Not started', - 1: 'fes 1', - 2: 'fes 2', - 3: 'fes FINAL', - 4: 'fes EXTRA', - 5: 'fes THE END', - 6: 'Ended', - } + "name": "Shutchou! pop'n quest Lively II phase", + "tip": "Shutchou! pop'n quest Lively II phase for all players.", + "category": "game_config", + "setting": "popn_quest_lively_2", + "values": { + 0: "Not started", + 1: "fes 1", + 2: "fes 2", + 3: "fes FINAL", + 4: "fes EXTRA", + 5: "fes THE END", + 6: "Ended", + }, }, ], - 'bools': [ + "bools": [ # We don't currently support lobbies or anything, so this is commented out until # somebody gets around to implementing it. # { @@ -208,24 +208,24 @@ class PopnMusicKaimei(PopnMusicModernBase): # 'setting': 'enable_net_taisen', # }, { - 'name': 'Force Song Unlock', - 'tip': 'Force unlock all songs.', - 'category': 'game_config', - 'setting': 'force_unlock_songs', + "name": "Force Song Unlock", + "tip": "Force unlock all songs.", + "category": "game_config", + "setting": "force_unlock_songs", }, ], } def get_common_config(self) -> Tuple[Dict[int, int], bool]: game_config = self.get_game_config() - music_phase = game_config.get_int('music_phase') - holiday_greeting = game_config.get_int('holiday_greeting') + music_phase = game_config.get_int("music_phase") + holiday_greeting = game_config.get_int("holiday_greeting") enable_net_taisen = False # game_config.get_bool('enable_net_taisen') - mn_tanteisha_phase = game_config.get_int('mn_tanteisha_phase') - peace_soundtrack = game_config.get_int('peace_soundtrack') - tanteisha_joshu = game_config.get_int('tanteisha_joshu') - popn_quest_lively = game_config.get_int('popn_quest_lively') - popn_quest_lively_2 = game_config.get_int('popn_quest_lively_2') + mn_tanteisha_phase = game_config.get_int("mn_tanteisha_phase") + peace_soundtrack = game_config.get_int("peace_soundtrack") + tanteisha_joshu = game_config.get_int("tanteisha_joshu") + popn_quest_lively = game_config.get_int("popn_quest_lively") + popn_quest_lively_2 = game_config.get_int("popn_quest_lively_2") # Event phases return ( @@ -384,20 +384,31 @@ class PopnMusicKaimei(PopnMusicModernBase): def format_profile(self, userid: UserID, profile: Profile) -> Node: root = super().format_profile(userid, profile) - account = root.child('account') - account.add_child(Node.s16('card_again_count', profile.get_int('card_again_count'))) - account.add_child(Node.s16('sp_riddles_id', profile.get_int('sp_riddles_id'))) + account = root.child("account") + account.add_child( + Node.s16("card_again_count", profile.get_int("card_again_count")) + ) + account.add_child(Node.s16("sp_riddles_id", profile.get_int("sp_riddles_id"))) # Kaimei riddles events - event2021 = Node.void('event2021') + event2021 = Node.void("event2021") root.add_child(event2021) - event2021.add_child(Node.u32('point', profile.get_int('point'))) - event2021.add_child(Node.u8('step', profile.get_int('step'))) - event2021.add_child(Node.u32_array('quest_point', profile.get_int_array('quest_point', 8, [0] * 8))) - event2021.add_child(Node.u8('step_nos', profile.get_int('step_nos'))) - event2021.add_child(Node.u32_array('quest_point_nos', profile.get_int_array('quest_point_nos', 13, [0] * 13))) + event2021.add_child(Node.u32("point", profile.get_int("point"))) + event2021.add_child(Node.u8("step", profile.get_int("step"))) + event2021.add_child( + Node.u32_array( + "quest_point", profile.get_int_array("quest_point", 8, [0] * 8) + ) + ) + event2021.add_child(Node.u8("step_nos", profile.get_int("step_nos"))) + event2021.add_child( + Node.u32_array( + "quest_point_nos", + profile.get_int_array("quest_point_nos", 13, [0] * 13), + ) + ) - riddles_data = Node.void('riddles_data') + riddles_data = Node.void("riddles_data") root.add_child(riddles_data) # Generate Short Riddles for MN tanteisha @@ -413,62 +424,74 @@ class PopnMusicKaimei(PopnMusicModernBase): randomRiddles.append(riddle) - sh_riddles = Node.void('sh_riddles') + sh_riddles = Node.void("sh_riddles") riddles_data.add_child(sh_riddles) - sh_riddles.add_child(Node.u32('sh_riddles_id', riddle)) + sh_riddles.add_child(Node.u32("sh_riddles_id", riddle)) # Set up kaimei riddles achievements - achievements = self.data.local.user.get_achievements(self.game, self.version, userid) + achievements = self.data.local.user.get_achievements( + self.game, self.version, userid + ) for achievement in achievements: - if achievement.type == 'riddle': - kaimei_gauge = achievement.data.get_int('kaimei_gauge') - is_cleared = achievement.data.get_bool('is_cleared') - riddles_cleared = achievement.data.get_bool('riddles_cleared') - select_count = achievement.data.get_int('select_count') - other_count = achievement.data.get_int('other_count') + if achievement.type == "riddle": + kaimei_gauge = achievement.data.get_int("kaimei_gauge") + is_cleared = achievement.data.get_bool("is_cleared") + riddles_cleared = achievement.data.get_bool("riddles_cleared") + select_count = achievement.data.get_int("select_count") + other_count = achievement.data.get_int("other_count") - sp_riddles = Node.void('sp_riddles') + sp_riddles = Node.void("sp_riddles") riddles_data.add_child(sp_riddles) - sp_riddles.add_child(Node.u16('kaimei_gauge', kaimei_gauge)) - sp_riddles.add_child(Node.bool('is_cleared', is_cleared)) - sp_riddles.add_child(Node.bool('riddles_cleared', riddles_cleared)) - sp_riddles.add_child(Node.u8('select_count', select_count)) - sp_riddles.add_child(Node.u32('other_count', other_count)) + sp_riddles.add_child(Node.u16("kaimei_gauge", kaimei_gauge)) + sp_riddles.add_child(Node.bool("is_cleared", is_cleared)) + sp_riddles.add_child(Node.bool("riddles_cleared", riddles_cleared)) + sp_riddles.add_child(Node.u8("select_count", select_count)) + sp_riddles.add_child(Node.u32("other_count", other_count)) return root - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = super().unformat_profile(userid, request, oldprofile) - account = request.child('account') + account = request.child("account") if account is not None: - newprofile.replace_int('card_again_count', account.child_value('card_again_count')) - newprofile.replace_int('sp_riddles_id', account.child_value('sp_riddles_id')) + newprofile.replace_int( + "card_again_count", account.child_value("card_again_count") + ) + newprofile.replace_int( + "sp_riddles_id", account.child_value("sp_riddles_id") + ) # Kaimei riddles events - event2021 = request.child('event2021') + event2021 = request.child("event2021") if event2021 is not None: - newprofile.replace_int('point', event2021.child_value('point')) - newprofile.replace_int('step', event2021.child_value('step')) - newprofile.replace_int_array('quest_point', 8, event2021.child_value('quest_point')) - newprofile.replace_int('step_nos', event2021.child_value('step_nos')) - newprofile.replace_int_array('quest_point_nos', 13, event2021.child_value('quest_point_nos')) + newprofile.replace_int("point", event2021.child_value("point")) + newprofile.replace_int("step", event2021.child_value("step")) + newprofile.replace_int_array( + "quest_point", 8, event2021.child_value("quest_point") + ) + newprofile.replace_int("step_nos", event2021.child_value("step_nos")) + newprofile.replace_int_array( + "quest_point_nos", 13, event2021.child_value("quest_point_nos") + ) # Extract kaimei riddles achievements for node in request.children: - if node.name == 'riddles_data': + if node.name == "riddles_data": riddle_id = 0 - playedRiddle = request.child('account').child_value('sp_riddles_id') + playedRiddle = request.child("account").child_value("sp_riddles_id") for riddle in node.children: - kaimei_gauge = riddle.child_value('kaimei_gauge') - is_cleared = riddle.child_value('is_cleared') - riddles_cleared = riddle.child_value('riddles_cleared') - select_count = riddle.child_value('select_count') - other_count = riddle.child_value('other_count') + kaimei_gauge = riddle.child_value("kaimei_gauge") + is_cleared = riddle.child_value("is_cleared") + riddles_cleared = riddle.child_value("riddles_cleared") + select_count = riddle.child_value("select_count") + other_count = riddle.child_value("other_count") - if (riddles_cleared or select_count >= 3): + if riddles_cleared or select_count >= 3: select_count = 3 - elif (playedRiddle == riddle_id): + elif playedRiddle == riddle_id: select_count += 1 self.data.local.user.put_achievement( @@ -476,13 +499,13 @@ class PopnMusicKaimei(PopnMusicModernBase): self.version, userid, riddle_id, - 'riddle', + "riddle", { - 'kaimei_gauge': kaimei_gauge, - 'is_cleared': is_cleared, - 'riddles_cleared': riddles_cleared, - 'select_count': select_count, - 'other_count': other_count, + "kaimei_gauge": kaimei_gauge, + "is_cleared": is_cleared, + "riddles_cleared": riddles_cleared, + "select_count": select_count, + "other_count": other_count, }, ) diff --git a/bemani/backend/popn/lapistoria.py b/bemani/backend/popn/lapistoria.py index cb53530..aa891f0 100644 --- a/bemani/backend/popn/lapistoria.py +++ b/bemani/backend/popn/lapistoria.py @@ -48,67 +48,67 @@ class PopnMusicLapistoria(PopnMusicBase): Return all of our front-end modifiably settings. """ return { - 'ints': [ + "ints": [ { - 'name': 'Music Open Phase', - 'tip': 'Default music phase for all players.', - 'category': 'game_config', - 'setting': 'music_phase', - 'values': { - 0: 'No music unlocks', - 1: 'Phase 1', - 2: 'Phase 2', - 3: 'Phase 3', - 4: 'Phase 4', - 5: 'Phase 5', - 6: 'Phase 6', - 7: 'Phase 7', - 8: 'Phase 8', - 9: 'Phase 9', - 10: 'Phase 10', - 11: 'Phase 11', - 12: 'Phase 12', - 13: 'Phase 13', - 14: 'Phase 14', - 15: 'Phase 15', - 16: 'Phase MAX', - } + "name": "Music Open Phase", + "tip": "Default music phase for all players.", + "category": "game_config", + "setting": "music_phase", + "values": { + 0: "No music unlocks", + 1: "Phase 1", + 2: "Phase 2", + 3: "Phase 3", + 4: "Phase 4", + 5: "Phase 5", + 6: "Phase 6", + 7: "Phase 7", + 8: "Phase 8", + 9: "Phase 9", + 10: "Phase 10", + 11: "Phase 11", + 12: "Phase 12", + 13: "Phase 13", + 14: "Phase 14", + 15: "Phase 15", + 16: "Phase MAX", + }, }, { - 'name': 'Story Mode', - 'tip': 'Story mode phase for all players.', - 'category': 'game_config', - 'setting': 'story_phase', - 'values': { - 0: 'Disabled', - 1: 'Phase 1', - 2: 'Phase 2', - 3: 'Phase 3', - 4: 'Phase 4', - 5: 'Phase 5', - 6: 'Phase 6', - 7: 'Phase 7', - 8: 'Phase 8', - 9: 'Phase 9', - 10: 'Phase 10', - 11: 'Phase 11', - 12: 'Phase 12', - 13: 'Phase 13', - 14: 'Phase 14', - 15: 'Phase 15', - 16: 'Phase 16', - 17: 'Phase 17', - 18: 'Phase 18', - 19: 'Phase 19', - 20: 'Phase 20', - 21: 'Phase 21', - 22: 'Phase 22', - 23: 'Phase 23', - 24: 'Phase 24', + "name": "Story Mode", + "tip": "Story mode phase for all players.", + "category": "game_config", + "setting": "story_phase", + "values": { + 0: "Disabled", + 1: "Phase 1", + 2: "Phase 2", + 3: "Phase 3", + 4: "Phase 4", + 5: "Phase 5", + 6: "Phase 6", + 7: "Phase 7", + 8: "Phase 8", + 9: "Phase 9", + 10: "Phase 10", + 11: "Phase 11", + 12: "Phase 12", + 13: "Phase 13", + 14: "Phase 14", + 15: "Phase 15", + 16: "Phase 16", + 17: "Phase 17", + 18: "Phase 18", + 19: "Phase 19", + 20: "Phase 20", + 21: "Phase 21", + 22: "Phase 22", + 23: "Phase 23", + 24: "Phase 24", }, }, ], - 'bools': [ + "bools": [ # We don't currently support lobbies or anything, so this is commented out until # somebody gets around to implementing it. # { @@ -118,18 +118,18 @@ class PopnMusicLapistoria(PopnMusicBase): # 'setting': 'enable_net_taisen', # }, { - 'name': 'Force Song Unlock', - 'tip': 'Force unlock all songs.', - 'category': 'game_config', - 'setting': 'force_unlock_songs', + "name": "Force Song Unlock", + "tip": "Force unlock all songs.", + "category": "game_config", + "setting": "force_unlock_songs", }, ], } def handle_info22_common_request(self, request: Node) -> Node: game_config = self.get_game_config() - story_phase = game_config.get_int('story_phase') - music_phase = game_config.get_int('music_phase') + story_phase = game_config.get_int("story_phase") + music_phase = game_config.get_int("music_phase") enable_net_taisen = False # game_config.get_bool('enable_net_taisen') phases = { @@ -222,67 +222,67 @@ class PopnMusicLapistoria(PopnMusicBase): 19: 7, } - root = Node.void('info22') + root = Node.void("info22") for phaseid in phases: - phase = Node.void('phase') + phase = Node.void("phase") root.add_child(phase) - phase.add_child(Node.s16('event_id', phaseid)) - phase.add_child(Node.s16('phase', phases[phaseid])) + phase.add_child(Node.s16("event_id", phaseid)) + phase.add_child(Node.s16("phase", phases[phaseid])) for storyid in range(173): - story = Node.void('story') + story = Node.void("story") root.add_child(story) - story.add_child(Node.u32('story_id', storyid)) - story.add_child(Node.bool('is_limited', False)) - story.add_child(Node.u64('limit_date', 0)) + story.add_child(Node.u32("story_id", storyid)) + story.add_child(Node.bool("is_limited", False)) + story.add_child(Node.u64("limit_date", 0)) return root def handle_pcb22_boot_request(self, request: Node) -> Node: - return Node.void('pcb22') + return Node.void("pcb22") def handle_pcb22_error_request(self, request: Node) -> Node: - return Node.void('pcb22') + return Node.void("pcb22") def handle_pcb22_write_request(self, request: Node) -> Node: # Update the name of this cab for admin purposes - self.update_machine_name(request.child_value('pcb_setting/name')) - return Node.void('pcb22') + self.update_machine_name(request.child_value("pcb_setting/name")) + return Node.void("pcb22") def handle_lobby22_requests(self, request: Node) -> Node: # Stub out the entire lobby22 service - return Node.void('lobby22') + return Node.void("lobby22") def handle_player22_read_request(self, request: Node) -> Node: - refid = request.child_value('ref_id') + refid = request.child_value("ref_id") # Pop'n Music 22 doesn't send a modelstring to load old profiles, # it just expects us to know. So always look for old profiles in # Pop'n 22 land. root = self.get_profile_by_refid(refid, self.OLD_PROFILE_FALLTHROUGH) if root is None: - root = Node.void('player22') - root.set_attribute('status', str(Status.NO_PROFILE)) + root = Node.void("player22") + root.set_attribute("status", str(Status.NO_PROFILE)) return root def handle_player22_new_request(self, request: Node) -> Node: - refid = request.child_value('ref_id') - name = request.child_value('name') + refid = request.child_value("ref_id") + name = request.child_value("name") root = self.new_profile_by_refid(refid, name) if root is None: - root = Node.void('player22') - root.set_attribute('status', str(Status.NO_PROFILE)) + root = Node.void("player22") + root.set_attribute("status", str(Status.NO_PROFILE)) return root def handle_player22_start_request(self, request: Node) -> Node: - return Node.void('player22') + return Node.void("player22") def handle_player22_logout_request(self, request: Node) -> Node: - return Node.void('player22') + return Node.void("player22") def handle_player22_write_request(self, request: Node) -> Node: - refid = request.child_value('ref_id') + refid = request.child_value("ref_id") - root = Node.void('player22') + root = Node.void("player22") if refid is None: return root @@ -290,7 +290,9 @@ class PopnMusicLapistoria(PopnMusicBase): if userid is None: return root - oldprofile = self.get_profile(userid) or Profile(self.game, self.version, refid, 0) + oldprofile = self.get_profile(userid) or Profile( + self.game, self.version, refid, 0 + ) newprofile = self.unformat_profile(userid, request, oldprofile) if newprofile is not None: @@ -299,18 +301,18 @@ class PopnMusicLapistoria(PopnMusicBase): return root def handle_player22_friend_request(self, request: Node) -> Node: - refid = request.attribute('ref_id') - no = int(request.attribute('no', '-1')) + refid = request.attribute("ref_id") + no = int(request.attribute("no", "-1")) - root = Node.void('player22') + root = Node.void("player22") if no < 0: - root.add_child(Node.s8('result', 2)) + root.add_child(Node.s8("result", 2)) return root # Look up our own user ID based on the RefID provided. userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is None: - root.add_child(Node.s8('result', 2)) + root.add_child(Node.s8("result", 2)) return root # Grab the links that we care about. @@ -318,7 +320,7 @@ class PopnMusicLapistoria(PopnMusicBase): profiles: Dict[UserID, Profile] = {} rivals: List[Link] = [] for link in links: - if link.type != 'rival': + if link.type != "rival": continue other_profile = self.get_profile(link.other_userid) @@ -329,23 +331,25 @@ class PopnMusicLapistoria(PopnMusicBase): # Somehow requested an invalid profile. if no >= len(rivals): - root.add_child(Node.s8('result', 2)) + root.add_child(Node.s8("result", 2)) return root rivalid = links[no].other_userid rivalprofile = profiles[rivalid] scores = self.data.remote.music.get_scores(self.game, self.version, rivalid) - achievements = self.data.local.user.get_achievements(self.game, self.version, rivalid) + achievements = self.data.local.user.get_achievements( + self.game, self.version, rivalid + ) # First, output general profile info. - friend = Node.void('friend') + friend = Node.void("friend") root.add_child(friend) - friend.add_child(Node.s16('no', no)) - friend.add_child(Node.string('g_pm_id', ID.format_extid(rivalprofile.extid))) - friend.add_child(Node.string('name', rivalprofile.get_str('name', 'なし'))) - friend.add_child(Node.s16('chara_num', rivalprofile.get_int('chara', -1))) + friend.add_child(Node.s16("no", no)) + friend.add_child(Node.string("g_pm_id", ID.format_extid(rivalprofile.extid))) + friend.add_child(Node.string("name", rivalprofile.get_str("name", "なし"))) + friend.add_child(Node.s16("chara_num", rivalprofile.get_int("chara", -1))) # This might be for having non-active or non-confirmed friends, but setting to 0 makes the # ranking numbers disappear and the player icon show a questionmark. - friend.add_child(Node.s8('is_open', 1)) + friend.add_child(Node.s8("is_open", 1)) for score in scores: # Skip any scores for chart types we don't support @@ -358,70 +362,80 @@ class PopnMusicLapistoria(PopnMusicBase): continue points = score.points - medal = score.data.get_int('medal') + medal = score.data.get_int("medal") - music = Node.void('music') + music = Node.void("music") friend.add_child(music) - music.set_attribute('music_num', str(score.id)) - music.set_attribute('sheet_num', str({ - self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY, - self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL, - self.CHART_TYPE_HYPER: self.GAME_CHART_TYPE_HYPER, - self.CHART_TYPE_EX: self.GAME_CHART_TYPE_EX, - }[score.chart])) - music.set_attribute('score', str(points)) - music.set_attribute('clearmedal', str({ - self.PLAY_MEDAL_NO_PLAY: self.GAME_PLAY_MEDAL_NO_PLAY, - self.PLAY_MEDAL_CIRCLE_FAILED: self.GAME_PLAY_MEDAL_CIRCLE_FAILED, - self.PLAY_MEDAL_DIAMOND_FAILED: self.GAME_PLAY_MEDAL_DIAMOND_FAILED, - self.PLAY_MEDAL_STAR_FAILED: self.GAME_PLAY_MEDAL_STAR_FAILED, - self.PLAY_MEDAL_EASY_CLEAR: self.GAME_PLAY_MEDAL_EASY_CLEAR, - self.PLAY_MEDAL_CIRCLE_CLEARED: self.GAME_PLAY_MEDAL_CIRCLE_CLEARED, - self.PLAY_MEDAL_DIAMOND_CLEARED: self.GAME_PLAY_MEDAL_DIAMOND_CLEARED, - self.PLAY_MEDAL_STAR_CLEARED: self.GAME_PLAY_MEDAL_STAR_CLEARED, - self.PLAY_MEDAL_CIRCLE_FULL_COMBO: self.GAME_PLAY_MEDAL_CIRCLE_FULL_COMBO, - self.PLAY_MEDAL_DIAMOND_FULL_COMBO: self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO, - self.PLAY_MEDAL_STAR_FULL_COMBO: self.GAME_PLAY_MEDAL_STAR_FULL_COMBO, - self.PLAY_MEDAL_PERFECT: self.GAME_PLAY_MEDAL_PERFECT, - }[medal])) + music.set_attribute("music_num", str(score.id)) + music.set_attribute( + "sheet_num", + str( + { + self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY, + self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL, + self.CHART_TYPE_HYPER: self.GAME_CHART_TYPE_HYPER, + self.CHART_TYPE_EX: self.GAME_CHART_TYPE_EX, + }[score.chart] + ), + ) + music.set_attribute("score", str(points)) + music.set_attribute( + "clearmedal", + str( + { + self.PLAY_MEDAL_NO_PLAY: self.GAME_PLAY_MEDAL_NO_PLAY, + self.PLAY_MEDAL_CIRCLE_FAILED: self.GAME_PLAY_MEDAL_CIRCLE_FAILED, + self.PLAY_MEDAL_DIAMOND_FAILED: self.GAME_PLAY_MEDAL_DIAMOND_FAILED, + self.PLAY_MEDAL_STAR_FAILED: self.GAME_PLAY_MEDAL_STAR_FAILED, + self.PLAY_MEDAL_EASY_CLEAR: self.GAME_PLAY_MEDAL_EASY_CLEAR, + self.PLAY_MEDAL_CIRCLE_CLEARED: self.GAME_PLAY_MEDAL_CIRCLE_CLEARED, + self.PLAY_MEDAL_DIAMOND_CLEARED: self.GAME_PLAY_MEDAL_DIAMOND_CLEARED, + self.PLAY_MEDAL_STAR_CLEARED: self.GAME_PLAY_MEDAL_STAR_CLEARED, + self.PLAY_MEDAL_CIRCLE_FULL_COMBO: self.GAME_PLAY_MEDAL_CIRCLE_FULL_COMBO, + self.PLAY_MEDAL_DIAMOND_FULL_COMBO: self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO, + self.PLAY_MEDAL_STAR_FULL_COMBO: self.GAME_PLAY_MEDAL_STAR_FULL_COMBO, + self.PLAY_MEDAL_PERFECT: self.GAME_PLAY_MEDAL_PERFECT, + }[medal] + ), + ) for course in achievements: - if course.type == 'course': - total_score = course.data.get_int('total_score') - clear_medal = course.data.get_int('clear_medal') - clear_norma = course.data.get_int('clear_norma') - stage1_score = course.data.get_int('stage1_score') - stage2_score = course.data.get_int('stage2_score') - stage3_score = course.data.get_int('stage3_score') - stage4_score = course.data.get_int('stage4_score') + if course.type == "course": + total_score = course.data.get_int("total_score") + clear_medal = course.data.get_int("clear_medal") + clear_norma = course.data.get_int("clear_norma") + stage1_score = course.data.get_int("stage1_score") + stage2_score = course.data.get_int("stage2_score") + stage3_score = course.data.get_int("stage3_score") + stage4_score = course.data.get_int("stage4_score") - coursenode = Node.void('course') + coursenode = Node.void("course") friend.add_child(coursenode) - coursenode.set_attribute('course_id', str(course.id)) - coursenode.set_attribute('clear_medal', str(clear_medal)) - coursenode.set_attribute('clear_norma', str(clear_norma)) - coursenode.set_attribute('stage1_score', str(stage1_score)) - coursenode.set_attribute('stage2_score', str(stage2_score)) - coursenode.set_attribute('stage3_score', str(stage3_score)) - coursenode.set_attribute('stage4_score', str(stage4_score)) - coursenode.set_attribute('total_score', str(total_score)) + coursenode.set_attribute("course_id", str(course.id)) + coursenode.set_attribute("clear_medal", str(clear_medal)) + coursenode.set_attribute("clear_norma", str(clear_norma)) + coursenode.set_attribute("stage1_score", str(stage1_score)) + coursenode.set_attribute("stage2_score", str(stage2_score)) + coursenode.set_attribute("stage3_score", str(stage3_score)) + coursenode.set_attribute("stage4_score", str(stage4_score)) + coursenode.set_attribute("total_score", str(total_score)) return root def handle_player22_conversion_request(self, request: Node) -> Node: - refid = request.child_value('ref_id') - name = request.child_value('name') - chara = request.child_value('chara') + refid = request.child_value("ref_id") + name = request.child_value("name") + chara = request.child_value("chara") root = self.new_profile_by_refid(refid, name, chara) if root is None: - root = Node.void('playerdata') - root.set_attribute('status', str(Status.NO_PROFILE)) + root = Node.void("playerdata") + root.set_attribute("status", str(Status.NO_PROFILE)) return root def handle_player22_write_music_request(self, request: Node) -> Node: - refid = request.child_value('ref_id') + refid = request.child_value("ref_id") - root = Node.void('player22') + root = Node.void("player22") if refid is None: return root @@ -429,21 +443,21 @@ class PopnMusicLapistoria(PopnMusicBase): if userid is None: return root - songid = request.child_value('music_num') + songid = request.child_value("music_num") chart = { self.GAME_CHART_TYPE_EASY: self.CHART_TYPE_EASY, self.GAME_CHART_TYPE_NORMAL: self.CHART_TYPE_NORMAL, self.GAME_CHART_TYPE_HYPER: self.CHART_TYPE_HYPER, self.GAME_CHART_TYPE_EX: self.CHART_TYPE_EX, - }[request.child_value('sheet_num')] - medal = request.child_value('clearmedal') - points = request.child_value('score') - combo = request.child_value('combo') + }[request.child_value("sheet_num")] + medal = request.child_value("clearmedal") + points = request.child_value("score") + combo = request.child_value("combo") stats = { - 'cool': request.child_value('cool'), - 'great': request.child_value('great'), - 'good': request.child_value('good'), - 'bad': request.child_value('bad') + "cool": request.child_value("cool"), + "great": request.child_value("great"), + "good": request.child_value("good"), + "bad": request.child_value("bad"), } medal = { self.GAME_PLAY_MEDAL_NO_PLAY: self.PLAY_MEDAL_NO_PLAY, @@ -459,13 +473,15 @@ class PopnMusicLapistoria(PopnMusicBase): self.GAME_PLAY_MEDAL_STAR_FULL_COMBO: self.PLAY_MEDAL_STAR_FULL_COMBO, self.GAME_PLAY_MEDAL_PERFECT: self.PLAY_MEDAL_PERFECT, }[medal] - self.update_score(userid, songid, chart, points, medal, combo=combo, stats=stats) + self.update_score( + userid, songid, chart, points, medal, combo=combo, stats=stats + ) return root def handle_player22_write_course_request(self, request: Node) -> Node: - refid = request.child_value('ref_id') + refid = request.child_value("ref_id") - root = Node.void('player22') + root = Node.void("player22") if refid is None: return root @@ -474,12 +490,14 @@ class PopnMusicLapistoria(PopnMusicBase): return root # Grab info that we want to update - total_score = request.child_value('total_score') or 0 - course_id = request.child_value('course_id') + total_score = request.child_value("total_score") or 0 + course_id = request.child_value("course_id") if course_id is not None: machine = self.data.local.machine.get_machine(self.config.machine.pcbid) - pref = request.child_value('pref') or self.get_machine_region() - profile = self.get_profile(userid) or Profile(self.game, self.version, refid, 0) + pref = request.child_value("pref") or self.get_machine_region() + profile = self.get_profile(userid) or Profile( + self.game, self.version, refid, 0 + ) course = self.data.local.user.get_achievement( self.game, @@ -491,37 +509,43 @@ class PopnMusicLapistoria(PopnMusicBase): stage_scores: Dict[int, int] = {} for child in request.children: - if child.name != 'stage': + if child.name != "stage": continue - stage = child.child_value('stage') - score = child.child_value('score') + stage = child.child_value("stage") + score = child.child_value("score") if isinstance(stage, int) and isinstance(score, int): stage_scores[stage] = score # Update the scores if this was a new high score. - if total_score > course.get_int('total_score'): - course.replace_int('total_score', total_score) - course.replace_int('stage1_score', stage_scores.get(0, 0)) - course.replace_int('stage2_score', stage_scores.get(1, 0)) - course.replace_int('stage3_score', stage_scores.get(2, 0)) - course.replace_int('stage4_score', stage_scores.get(3, 0)) + if total_score > course.get_int("total_score"): + course.replace_int("total_score", total_score) + course.replace_int("stage1_score", stage_scores.get(0, 0)) + course.replace_int("stage2_score", stage_scores.get(1, 0)) + course.replace_int("stage3_score", stage_scores.get(2, 0)) + course.replace_int("stage4_score", stage_scores.get(3, 0)) # Only update ojamas used if this was an updated score. - course.replace_int('clear_norma', request.child_value('clear_norma')) + course.replace_int("clear_norma", request.child_value("clear_norma")) # Only udpate what location and prefecture this was scored in # if we updated our score. - course.replace_int('pref', pref) - course.replace_int('lid', machine.arcade) + course.replace_int("pref", pref) + course.replace_int("lid", machine.arcade) # Update medal and combo values. - course.replace_int('max_combo', max(course.get_int('max_combo'), request.child_value('max_combo'))) - course.replace_int('clear_medal', max(course.get_int('clear_medal'), request.child_value('clear_medal'))) + course.replace_int( + "max_combo", + max(course.get_int("max_combo"), request.child_value("max_combo")), + ) + course.replace_int( + "clear_medal", + max(course.get_int("clear_medal"), request.child_value("clear_medal")), + ) # Add one to the play count for this course. - course.increment_int('play_cnt') + course.increment_int("play_cnt") self.data.local.user.put_achievement( self.game, @@ -533,10 +557,23 @@ class PopnMusicLapistoria(PopnMusicBase): ) # Now, attempt to calculate ranking for this user for this run. - all_courses = self.data.local.user.get_all_achievements(self.game, self.version, achievementid=course_id, achievementtype="course") - global_ranking = sorted(all_courses, key=lambda entry: entry[1].data.get_int('total_score'), reverse=True) - pref_ranking = [c for c in global_ranking if c[1].data.get_int('pref') == pref] - local_ranking = [c for c in global_ranking if c[1].data.get_int('lid') == machine.arcade] + all_courses = self.data.local.user.get_all_achievements( + self.game, + self.version, + achievementid=course_id, + achievementtype="course", + ) + global_ranking = sorted( + all_courses, + key=lambda entry: entry[1].data.get_int("total_score"), + reverse=True, + ) + pref_ranking = [ + c for c in global_ranking if c[1].data.get_int("pref") == pref + ] + local_ranking = [ + c for c in global_ranking if c[1].data.get_int("lid") == machine.arcade + ] global_rank = len(global_ranking) pref_rank = len(pref_ranking) @@ -557,56 +594,58 @@ class PopnMusicLapistoria(PopnMusicBase): # Now, return it all. for rank_type, personal_rank, count in [ - ('all_ranking', global_rank, len(global_ranking)), - ('pref_ranking', pref_rank, len(pref_ranking)), - ('location_ranking', local_rank, len(local_ranking)), + ("all_ranking", global_rank, len(global_ranking)), + ("pref_ranking", pref_rank, len(pref_ranking)), + ("location_ranking", local_rank, len(local_ranking)), ]: ranknode = Node.void(rank_type) root.add_child(ranknode) - ranknode.add_child(Node.string('name', profile.get_str('name', 'なし'))) - ranknode.add_child(Node.s16('chara_num', profile.get_int('chara', -1))) - ranknode.add_child(Node.s32('stage1_score', stage_scores.get(0, 0))) - ranknode.add_child(Node.s32('stage2_score', stage_scores.get(1, 0))) - ranknode.add_child(Node.s32('stage3_score', stage_scores.get(2, 0))) - ranknode.add_child(Node.s32('stage4_score', stage_scores.get(3, 0))) - ranknode.add_child(Node.s32('total_score', total_score)) - ranknode.add_child(Node.s16('player_count', count)) - ranknode.add_child(Node.s16('player_rank', personal_rank)) + ranknode.add_child(Node.string("name", profile.get_str("name", "なし"))) + ranknode.add_child(Node.s16("chara_num", profile.get_int("chara", -1))) + ranknode.add_child(Node.s32("stage1_score", stage_scores.get(0, 0))) + ranknode.add_child(Node.s32("stage2_score", stage_scores.get(1, 0))) + ranknode.add_child(Node.s32("stage3_score", stage_scores.get(2, 0))) + ranknode.add_child(Node.s32("stage4_score", stage_scores.get(3, 0))) + ranknode.add_child(Node.s32("total_score", total_score)) + ranknode.add_child(Node.s16("player_count", count)) + ranknode.add_child(Node.s16("player_rank", personal_rank)) return root def format_profile(self, userid: UserID, profile: Profile) -> Node: - root = Node.void('player22') + root = Node.void("player22") # Result - root.add_child(Node.s8('result', 0)) + root.add_child(Node.s8("result", 0)) # Set up account - account = Node.void('account') + account = Node.void("account") root.add_child(account) - account.add_child(Node.string('name', profile.get_str('name', 'なし'))) - account.add_child(Node.string('g_pm_id', ID.format_extid(profile.extid))) - account.add_child(Node.s8('tutorial', profile.get_int('tutorial', -1))) - account.add_child(Node.s16('read_news', profile.get_int('read_news', 0))) - account.add_child(Node.s8('staff', 0)) - account.add_child(Node.s8('is_conv', 0)) - account.add_child(Node.s16('item_type', 0)) - account.add_child(Node.s16('item_id', 0)) - account.add_child(Node.s16_array('license_data', [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1])) + account.add_child(Node.string("name", profile.get_str("name", "なし"))) + account.add_child(Node.string("g_pm_id", ID.format_extid(profile.extid))) + account.add_child(Node.s8("tutorial", profile.get_int("tutorial", -1))) + account.add_child(Node.s16("read_news", profile.get_int("read_news", 0))) + account.add_child(Node.s8("staff", 0)) + account.add_child(Node.s8("is_conv", 0)) + account.add_child(Node.s16("item_type", 0)) + account.add_child(Node.s16("item_id", 0)) + account.add_child( + Node.s16_array("license_data", [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1]) + ) # Statistics section and scores section statistics = self.get_play_statistics(userid) - account.add_child(Node.s16('total_play_cnt', statistics.total_plays)) - account.add_child(Node.s16('today_play_cnt', statistics.today_plays)) - account.add_child(Node.s16('consecutive_days', statistics.consecutive_days)) - account.add_child(Node.s16('total_days', statistics.total_days)) - account.add_child(Node.s16('interval_day', 0)) + account.add_child(Node.s16("total_play_cnt", statistics.total_plays)) + account.add_child(Node.s16("today_play_cnt", statistics.today_plays)) + account.add_child(Node.s16("consecutive_days", statistics.consecutive_days)) + account.add_child(Node.s16("total_days", statistics.total_days)) + account.add_child(Node.s16("interval_day", 0)) # Number of rivals that are active for this version. links = self.data.local.user.get_links(self.game, self.version, userid) rivalcount = 0 for link in links: - if link.type != 'rival': + if link.type != "rival": continue if not self.has_profile(link.other_userid): @@ -614,18 +653,28 @@ class PopnMusicLapistoria(PopnMusicBase): # This profile is valid. rivalcount += 1 - account.add_child(Node.u8('active_fr_num', rivalcount)) + account.add_child(Node.u8("active_fr_num", rivalcount)) # Add scores section - last_played = [x[0] for x in self.data.local.music.get_last_played(self.game, self.version, userid, 5)] - most_played = [x[0] for x in self.data.local.music.get_most_played(self.game, self.version, userid, 10)] + last_played = [ + x[0] + for x in self.data.local.music.get_last_played( + self.game, self.version, userid, 5 + ) + ] + most_played = [ + x[0] + for x in self.data.local.music.get_most_played( + self.game, self.version, userid, 10 + ) + ] while len(last_played) < 5: last_played.append(-1) while len(most_played) < 10: most_played.append(-1) - account.add_child(Node.s16_array('my_best', most_played)) - account.add_child(Node.s16_array('latest_music', last_played)) + account.add_child(Node.s16_array("my_best", most_played)) + account.add_child(Node.s16_array("latest_music", last_played)) scores = self.data.remote.music.get_scores(self.game, self.version, userid) for score in scores: @@ -639,132 +688,159 @@ class PopnMusicLapistoria(PopnMusicBase): continue points = score.points - medal = score.data.get_int('medal') + medal = score.data.get_int("medal") - music = Node.void('music') + music = Node.void("music") root.add_child(music) - music.add_child(Node.s16('music_num', score.id)) - music.add_child(Node.u8('sheet_num', { - self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY, - self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL, - self.CHART_TYPE_HYPER: self.GAME_CHART_TYPE_HYPER, - self.CHART_TYPE_EX: self.GAME_CHART_TYPE_EX, - }[score.chart])) - music.add_child(Node.s16('cnt', score.plays)) - music.add_child(Node.s32('score', points)) - music.add_child(Node.u8('clear_type', { - self.PLAY_MEDAL_NO_PLAY: self.GAME_PLAY_MEDAL_NO_PLAY, - self.PLAY_MEDAL_CIRCLE_FAILED: self.GAME_PLAY_MEDAL_CIRCLE_FAILED, - self.PLAY_MEDAL_DIAMOND_FAILED: self.GAME_PLAY_MEDAL_DIAMOND_FAILED, - self.PLAY_MEDAL_STAR_FAILED: self.GAME_PLAY_MEDAL_STAR_FAILED, - self.PLAY_MEDAL_EASY_CLEAR: self.GAME_PLAY_MEDAL_EASY_CLEAR, - self.PLAY_MEDAL_CIRCLE_CLEARED: self.GAME_PLAY_MEDAL_CIRCLE_CLEARED, - self.PLAY_MEDAL_DIAMOND_CLEARED: self.GAME_PLAY_MEDAL_DIAMOND_CLEARED, - self.PLAY_MEDAL_STAR_CLEARED: self.GAME_PLAY_MEDAL_STAR_CLEARED, - self.PLAY_MEDAL_CIRCLE_FULL_COMBO: self.GAME_PLAY_MEDAL_CIRCLE_FULL_COMBO, - self.PLAY_MEDAL_DIAMOND_FULL_COMBO: self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO, - self.PLAY_MEDAL_STAR_FULL_COMBO: self.GAME_PLAY_MEDAL_STAR_FULL_COMBO, - self.PLAY_MEDAL_PERFECT: self.GAME_PLAY_MEDAL_PERFECT, - }[medal])) - music.add_child(Node.s32('old_score', 0)) - music.add_child(Node.u8('old_clear_type', 0)) + music.add_child(Node.s16("music_num", score.id)) + music.add_child( + Node.u8( + "sheet_num", + { + self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY, + self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL, + self.CHART_TYPE_HYPER: self.GAME_CHART_TYPE_HYPER, + self.CHART_TYPE_EX: self.GAME_CHART_TYPE_EX, + }[score.chart], + ) + ) + music.add_child(Node.s16("cnt", score.plays)) + music.add_child(Node.s32("score", points)) + music.add_child( + Node.u8( + "clear_type", + { + self.PLAY_MEDAL_NO_PLAY: self.GAME_PLAY_MEDAL_NO_PLAY, + self.PLAY_MEDAL_CIRCLE_FAILED: self.GAME_PLAY_MEDAL_CIRCLE_FAILED, + self.PLAY_MEDAL_DIAMOND_FAILED: self.GAME_PLAY_MEDAL_DIAMOND_FAILED, + self.PLAY_MEDAL_STAR_FAILED: self.GAME_PLAY_MEDAL_STAR_FAILED, + self.PLAY_MEDAL_EASY_CLEAR: self.GAME_PLAY_MEDAL_EASY_CLEAR, + self.PLAY_MEDAL_CIRCLE_CLEARED: self.GAME_PLAY_MEDAL_CIRCLE_CLEARED, + self.PLAY_MEDAL_DIAMOND_CLEARED: self.GAME_PLAY_MEDAL_DIAMOND_CLEARED, + self.PLAY_MEDAL_STAR_CLEARED: self.GAME_PLAY_MEDAL_STAR_CLEARED, + self.PLAY_MEDAL_CIRCLE_FULL_COMBO: self.GAME_PLAY_MEDAL_CIRCLE_FULL_COMBO, + self.PLAY_MEDAL_DIAMOND_FULL_COMBO: self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO, + self.PLAY_MEDAL_STAR_FULL_COMBO: self.GAME_PLAY_MEDAL_STAR_FULL_COMBO, + self.PLAY_MEDAL_PERFECT: self.GAME_PLAY_MEDAL_PERFECT, + }[medal], + ) + ) + music.add_child(Node.s32("old_score", 0)) + music.add_child(Node.u8("old_clear_type", 0)) # Net VS section - netvs = Node.void('netvs') + netvs = Node.void("netvs") root.add_child(netvs) - netvs.add_child(Node.s32('rank_point', 0)) - netvs.add_child(Node.s16_array('record', [0, 0, 0, 0, 0, 0])) - netvs.add_child(Node.u8('rank', 0)) - netvs.add_child(Node.s8('vs_rank_old', 0)) - netvs.add_child(Node.s8_array('ojama_condition', [0] * 74)) - netvs.add_child(Node.s8_array('set_ojama', [0, 0, 0])) - netvs.add_child(Node.s8_array('set_recommend', [0, 0, 0])) - netvs.add_child(Node.u32('netvs_play_cnt', 0)) + netvs.add_child(Node.s32("rank_point", 0)) + netvs.add_child(Node.s16_array("record", [0, 0, 0, 0, 0, 0])) + netvs.add_child(Node.u8("rank", 0)) + netvs.add_child(Node.s8("vs_rank_old", 0)) + netvs.add_child(Node.s8_array("ojama_condition", [0] * 74)) + netvs.add_child(Node.s8_array("set_ojama", [0, 0, 0])) + netvs.add_child(Node.s8_array("set_recommend", [0, 0, 0])) + netvs.add_child(Node.u32("netvs_play_cnt", 0)) for dialog in [0, 1, 2, 3, 4, 5]: - netvs.add_child(Node.string('dialog', f'dialog#{dialog}')) + netvs.add_child(Node.string("dialog", f"dialog#{dialog}")) # Set up config - config = Node.void('config') + config = Node.void("config") root.add_child(config) - config.add_child(Node.u8('mode', profile.get_int('mode', 0))) - config.add_child(Node.s16('chara', profile.get_int('chara', -1))) - config.add_child(Node.s16('music', profile.get_int('music', -1))) - config.add_child(Node.u8('sheet', profile.get_int('sheet', 0))) - config.add_child(Node.s8('category', profile.get_int('category', 1))) - config.add_child(Node.s8('sub_category', profile.get_int('sub_category', -1))) - config.add_child(Node.s8('chara_category', profile.get_int('chara_category', -1))) - config.add_child(Node.s16('story_id', profile.get_int('story_id', -1))) - config.add_child(Node.s16('course_id', profile.get_int('course_id', -1))) - config.add_child(Node.s8('course_folder', profile.get_int('course_folder', -1))) - config.add_child(Node.s8('story_folder', profile.get_int('story_folder', -1))) - config.add_child(Node.s8('ms_banner_disp', profile.get_int('ms_banner_disp'))) - config.add_child(Node.s8('ms_down_info', profile.get_int('ms_down_info'))) - config.add_child(Node.s8('ms_side_info', profile.get_int('ms_side_info'))) - config.add_child(Node.s8('ms_raise_type', profile.get_int('ms_raise_type'))) - config.add_child(Node.s8('ms_rnd_type', profile.get_int('ms_rnd_type'))) + config.add_child(Node.u8("mode", profile.get_int("mode", 0))) + config.add_child(Node.s16("chara", profile.get_int("chara", -1))) + config.add_child(Node.s16("music", profile.get_int("music", -1))) + config.add_child(Node.u8("sheet", profile.get_int("sheet", 0))) + config.add_child(Node.s8("category", profile.get_int("category", 1))) + config.add_child(Node.s8("sub_category", profile.get_int("sub_category", -1))) + config.add_child( + Node.s8("chara_category", profile.get_int("chara_category", -1)) + ) + config.add_child(Node.s16("story_id", profile.get_int("story_id", -1))) + config.add_child(Node.s16("course_id", profile.get_int("course_id", -1))) + config.add_child(Node.s8("course_folder", profile.get_int("course_folder", -1))) + config.add_child(Node.s8("story_folder", profile.get_int("story_folder", -1))) + config.add_child(Node.s8("ms_banner_disp", profile.get_int("ms_banner_disp"))) + config.add_child(Node.s8("ms_down_info", profile.get_int("ms_down_info"))) + config.add_child(Node.s8("ms_side_info", profile.get_int("ms_side_info"))) + config.add_child(Node.s8("ms_raise_type", profile.get_int("ms_raise_type"))) + config.add_child(Node.s8("ms_rnd_type", profile.get_int("ms_rnd_type"))) # Set up option - option_dict = profile.get_dict('option') - option = Node.void('option') + option_dict = profile.get_dict("option") + option = Node.void("option") root.add_child(option) - option.add_child(Node.s16('hispeed', option_dict.get_int('hispeed', 10))) - option.add_child(Node.u8('popkun', option_dict.get_int('popkun', 0))) - option.add_child(Node.bool('hidden', option_dict.get_bool('hidden', False))) - option.add_child(Node.s16('hidden_rate', option_dict.get_int('hidden_rate', -1))) - option.add_child(Node.bool('sudden', option_dict.get_bool('sudden', False))) - option.add_child(Node.s16('sudden_rate', option_dict.get_int('sudden_rate', -1))) - option.add_child(Node.s8('randmir', option_dict.get_int('randmir', 0))) - option.add_child(Node.s8('gauge_type', option_dict.get_int('gauge_type', 0))) - option.add_child(Node.u8('ojama_0', option_dict.get_int('ojama_0', 0))) - option.add_child(Node.u8('ojama_1', option_dict.get_int('ojama_1', 0))) - option.add_child(Node.bool('forever_0', option_dict.get_bool('forever_0', False))) - option.add_child(Node.bool('forever_1', option_dict.get_bool('forever_1', False))) - option.add_child(Node.bool('full_setting', option_dict.get_bool('full_setting', False))) + option.add_child(Node.s16("hispeed", option_dict.get_int("hispeed", 10))) + option.add_child(Node.u8("popkun", option_dict.get_int("popkun", 0))) + option.add_child(Node.bool("hidden", option_dict.get_bool("hidden", False))) + option.add_child( + Node.s16("hidden_rate", option_dict.get_int("hidden_rate", -1)) + ) + option.add_child(Node.bool("sudden", option_dict.get_bool("sudden", False))) + option.add_child( + Node.s16("sudden_rate", option_dict.get_int("sudden_rate", -1)) + ) + option.add_child(Node.s8("randmir", option_dict.get_int("randmir", 0))) + option.add_child(Node.s8("gauge_type", option_dict.get_int("gauge_type", 0))) + option.add_child(Node.u8("ojama_0", option_dict.get_int("ojama_0", 0))) + option.add_child(Node.u8("ojama_1", option_dict.get_int("ojama_1", 0))) + option.add_child( + Node.bool("forever_0", option_dict.get_bool("forever_0", False)) + ) + option.add_child( + Node.bool("forever_1", option_dict.get_bool("forever_1", False)) + ) + option.add_child( + Node.bool("full_setting", option_dict.get_bool("full_setting", False)) + ) # Set up info - info = Node.void('info') + info = Node.void("info") root.add_child(info) - info.add_child(Node.u16('ep', profile.get_int('ep', 0))) - info.add_child(Node.u16('ap', profile.get_int('ap', 0))) + info.add_child(Node.u16("ep", profile.get_int("ep", 0))) + info.add_child(Node.u16("ap", profile.get_int("ap", 0))) # Set up custom_cate - custom_cate = Node.void('custom_cate') + custom_cate = Node.void("custom_cate") root.add_child(custom_cate) - custom_cate.add_child(Node.s8('valid', 0)) - custom_cate.add_child(Node.s8('lv_min', -1)) - custom_cate.add_child(Node.s8('lv_max', -1)) - custom_cate.add_child(Node.s8('medal_min', -1)) - custom_cate.add_child(Node.s8('medal_max', -1)) - custom_cate.add_child(Node.s8('friend_no', -1)) - custom_cate.add_child(Node.s8('score_flg', -1)) + custom_cate.add_child(Node.s8("valid", 0)) + custom_cate.add_child(Node.s8("lv_min", -1)) + custom_cate.add_child(Node.s8("lv_max", -1)) + custom_cate.add_child(Node.s8("medal_min", -1)) + custom_cate.add_child(Node.s8("medal_max", -1)) + custom_cate.add_child(Node.s8("friend_no", -1)) + custom_cate.add_child(Node.s8("score_flg", -1)) # Set up customize - customize_dict = profile.get_dict('customize') - customize = Node.void('customize') + customize_dict = profile.get_dict("customize") + customize = Node.void("customize") root.add_child(customize) - customize.add_child(Node.u16('effect', customize_dict.get_int('effect'))) - customize.add_child(Node.u16('hukidashi', customize_dict.get_int('hukidashi'))) - customize.add_child(Node.u16('font', customize_dict.get_int('font'))) - customize.add_child(Node.u16('comment_1', customize_dict.get_int('comment_1'))) - customize.add_child(Node.u16('comment_2', customize_dict.get_int('comment_2'))) + customize.add_child(Node.u16("effect", customize_dict.get_int("effect"))) + customize.add_child(Node.u16("hukidashi", customize_dict.get_int("hukidashi"))) + customize.add_child(Node.u16("font", customize_dict.get_int("font"))) + customize.add_child(Node.u16("comment_1", customize_dict.get_int("comment_1"))) + customize.add_child(Node.u16("comment_2", customize_dict.get_int("comment_2"))) game_config = self.get_game_config() - if game_config.get_bool('force_unlock_songs'): - songs = {song.id for song in self.data.local.music.get_all_songs(self.game, self.version)} + if game_config.get_bool("force_unlock_songs"): + songs = { + song.id + for song in self.data.local.music.get_all_songs(self.game, self.version) + } for song in songs: - item = Node.void('item') + item = Node.void("item") root.add_child(item) - item.add_child(Node.u8('type', 0)) - item.add_child(Node.u16('id', song)) - item.add_child(Node.u16('param', 15)) - item.add_child(Node.bool('is_new', False)) + item.add_child(Node.u8("type", 0)) + item.add_child(Node.u16("id", song)) + item.add_child(Node.u16("param", 15)) + item.add_child(Node.bool("is_new", False)) # Set up achievements - achievements = self.data.local.user.get_achievements(self.game, self.version, userid) + achievements = self.data.local.user.get_achievements( + self.game, self.version, userid + ) for achievement in achievements: - if achievement.type == 'item': - itemtype = achievement.data.get_int('type') - param = achievement.data.get_int('param') + if achievement.type == "item": + itemtype = achievement.data.get_int("type") + param = achievement.data.get_int("param") # Maximum for each type is as follows: # 0, 1423 - These are song unlocks as far as I can tell, matches Eclale/UsaNeko. @@ -776,136 +852,144 @@ class PopnMusicLapistoria(PopnMusicBase): # 6, 24 # 7, 4158 - if game_config.get_bool('force_unlock_songs') and itemtype == 0: + if game_config.get_bool("force_unlock_songs") and itemtype == 0: # We already sent song unlocks in the force unlock section above. continue - item = Node.void('item') + item = Node.void("item") root.add_child(item) - item.add_child(Node.u8('type', itemtype)) - item.add_child(Node.u16('id', achievement.id)) - item.add_child(Node.u16('param', param)) - item.add_child(Node.bool('is_new', False)) + item.add_child(Node.u8("type", itemtype)) + item.add_child(Node.u16("id", achievement.id)) + item.add_child(Node.u16("param", param)) + item.add_child(Node.bool("is_new", False)) - elif achievement.type == 'achievement': - count = achievement.data.get_int('count') + elif achievement.type == "achievement": + count = achievement.data.get_int("count") - ach_node = Node.void('achievement') + ach_node = Node.void("achievement") root.add_child(ach_node) - ach_node.add_child(Node.u8('type', achievement.id)) - ach_node.add_child(Node.u32('count', count)) + ach_node.add_child(Node.u8("type", achievement.id)) + ach_node.add_child(Node.u32("count", count)) - elif achievement.type == 'chara': - friendship = achievement.data.get_int('friendship') + elif achievement.type == "chara": + friendship = achievement.data.get_int("friendship") - chara = Node.void('chara_param') + chara = Node.void("chara_param") root.add_child(chara) - chara.add_child(Node.u16('chara_id', achievement.id)) - chara.add_child(Node.u16('friendship', friendship)) + chara.add_child(Node.u16("chara_id", achievement.id)) + chara.add_child(Node.u16("friendship", friendship)) - elif achievement.type == 'story': - chapter = achievement.data.get_int('chapter') - gauge = achievement.data.get_int('gauge') - cleared = achievement.data.get_bool('cleared') - clear_chapter = achievement.data.get_int('clear_chapter') + elif achievement.type == "story": + chapter = achievement.data.get_int("chapter") + gauge = achievement.data.get_int("gauge") + cleared = achievement.data.get_bool("cleared") + clear_chapter = achievement.data.get_int("clear_chapter") - story = Node.void('story') + story = Node.void("story") root.add_child(story) - story.add_child(Node.u32('story_id', achievement.id)) - story.add_child(Node.u32('chapter_id', chapter)) - story.add_child(Node.u16('gauge_point', gauge)) - story.add_child(Node.bool('is_cleared', cleared)) - story.add_child(Node.u32('clear_chapter', clear_chapter)) + story.add_child(Node.u32("story_id", achievement.id)) + story.add_child(Node.u32("chapter_id", chapter)) + story.add_child(Node.u16("gauge_point", gauge)) + story.add_child(Node.bool("is_cleared", cleared)) + story.add_child(Node.u32("clear_chapter", clear_chapter)) - elif achievement.type == 'course': - total_score = achievement.data.get_int('total_score') - max_combo = achievement.data.get_int('max_combo') - play_cnt = achievement.data.get_int('play_cnt') - clear_medal = achievement.data.get_int('clear_medal') - clear_norma = achievement.data.get_int('clear_norma') - stage1_score = achievement.data.get_int('stage1_score') - stage2_score = achievement.data.get_int('stage2_score') - stage3_score = achievement.data.get_int('stage3_score') - stage4_score = achievement.data.get_int('stage4_score') + elif achievement.type == "course": + total_score = achievement.data.get_int("total_score") + max_combo = achievement.data.get_int("max_combo") + play_cnt = achievement.data.get_int("play_cnt") + clear_medal = achievement.data.get_int("clear_medal") + clear_norma = achievement.data.get_int("clear_norma") + stage1_score = achievement.data.get_int("stage1_score") + stage2_score = achievement.data.get_int("stage2_score") + stage3_score = achievement.data.get_int("stage3_score") + stage4_score = achievement.data.get_int("stage4_score") - course = Node.void('course') + course = Node.void("course") root.add_child(course) - course.add_child(Node.s16('course_id', achievement.id)) - course.add_child(Node.u8('clear_medal', clear_medal)) - course.add_child(Node.u8('clear_norma', clear_norma)) - course.add_child(Node.s32('stage1_score', stage1_score)) - course.add_child(Node.s32('stage2_score', stage2_score)) - course.add_child(Node.s32('stage3_score', stage3_score)) - course.add_child(Node.s32('stage4_score', stage4_score)) - course.add_child(Node.s32('total_score', total_score)) - course.add_child(Node.s16('max_cmbo', max_combo)) # Yes, it is misspelled. - course.add_child(Node.s16('play_cnt', play_cnt)) - course.add_child(Node.s16('all_rank', 1)) # Unclear what this does. + course.add_child(Node.s16("course_id", achievement.id)) + course.add_child(Node.u8("clear_medal", clear_medal)) + course.add_child(Node.u8("clear_norma", clear_norma)) + course.add_child(Node.s32("stage1_score", stage1_score)) + course.add_child(Node.s32("stage2_score", stage2_score)) + course.add_child(Node.s32("stage3_score", stage3_score)) + course.add_child(Node.s32("stage4_score", stage4_score)) + course.add_child(Node.s32("total_score", total_score)) + course.add_child( + Node.s16("max_cmbo", max_combo) + ) # Yes, it is misspelled. + course.add_child(Node.s16("play_cnt", play_cnt)) + course.add_child(Node.s16("all_rank", 1)) # Unclear what this does. # There are also course_rank nodes, but it doesn't appear they get displayed # to the user anywhere. return root - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() - account = request.child('account') + account = request.child("account") if account is not None: - newprofile.replace_int('tutorial', account.child_value('tutorial')) - newprofile.replace_int('read_news', account.child_value('read_news')) + newprofile.replace_int("tutorial", account.child_value("tutorial")) + newprofile.replace_int("read_news", account.child_value("read_news")) - info = request.child('info') + info = request.child("info") if info is not None: - newprofile.replace_int('ep', info.child_value('ep')) - newprofile.replace_int('ap', info.child_value('ap')) + newprofile.replace_int("ep", info.child_value("ep")) + newprofile.replace_int("ap", info.child_value("ap")) - config = request.child('config') + config = request.child("config") if config is not None: - newprofile.replace_int('mode', config.child_value('mode')) - newprofile.replace_int('chara', config.child_value('chara')) - newprofile.replace_int('music', config.child_value('music')) - newprofile.replace_int('sheet', config.child_value('sheet')) - newprofile.replace_int('category', config.child_value('category')) - newprofile.replace_int('sub_category', config.child_value('sub_category')) - newprofile.replace_int('chara_category', config.child_value('chara_category')) - newprofile.replace_int('story_id', config.child_value('story_id')) - newprofile.replace_int('course_id', config.child_value('course_id')) - newprofile.replace_int('course_folder', config.child_value('course_folder')) - newprofile.replace_int('story_folder', config.child_value('story_folder')) - newprofile.replace_int('ms_banner_disp', config.child_value('ms_banner_disp')) - newprofile.replace_int('ms_down_info', config.child_value('ms_down_info')) - newprofile.replace_int('ms_side_info', config.child_value('ms_side_info')) - newprofile.replace_int('ms_raise_type', config.child_value('ms_raise_type')) - newprofile.replace_int('ms_rnd_type', config.child_value('ms_rnd_type')) + newprofile.replace_int("mode", config.child_value("mode")) + newprofile.replace_int("chara", config.child_value("chara")) + newprofile.replace_int("music", config.child_value("music")) + newprofile.replace_int("sheet", config.child_value("sheet")) + newprofile.replace_int("category", config.child_value("category")) + newprofile.replace_int("sub_category", config.child_value("sub_category")) + newprofile.replace_int( + "chara_category", config.child_value("chara_category") + ) + newprofile.replace_int("story_id", config.child_value("story_id")) + newprofile.replace_int("course_id", config.child_value("course_id")) + newprofile.replace_int("course_folder", config.child_value("course_folder")) + newprofile.replace_int("story_folder", config.child_value("story_folder")) + newprofile.replace_int( + "ms_banner_disp", config.child_value("ms_banner_disp") + ) + newprofile.replace_int("ms_down_info", config.child_value("ms_down_info")) + newprofile.replace_int("ms_side_info", config.child_value("ms_side_info")) + newprofile.replace_int("ms_raise_type", config.child_value("ms_raise_type")) + newprofile.replace_int("ms_rnd_type", config.child_value("ms_rnd_type")) - option_dict = newprofile.get_dict('option') - option = request.child('option') + option_dict = newprofile.get_dict("option") + option = request.child("option") if option is not None: - option_dict.replace_int('hispeed', option.child_value('hispeed')) - option_dict.replace_int('popkun', option.child_value('popkun')) - option_dict.replace_bool('hidden', option.child_value('hidden')) - option_dict.replace_bool('sudden', option.child_value('sudden')) - option_dict.replace_int('hidden_rate', option.child_value('hidden_rate')) - option_dict.replace_int('sudden_rate', option.child_value('sudden_rate')) - option_dict.replace_int('randmir', option.child_value('randmir')) - option_dict.replace_int('gauge_type', option.child_value('gauge_type')) - option_dict.replace_int('ojama_0', option.child_value('ojama_0')) - option_dict.replace_int('ojama_1', option.child_value('ojama_1')) - option_dict.replace_bool('forever_0', option.child_value('forever_0')) - option_dict.replace_bool('forever_1', option.child_value('forever_1')) - option_dict.replace_bool('full_setting', option.child_value('full_setting')) - newprofile.replace_dict('option', option_dict) + option_dict.replace_int("hispeed", option.child_value("hispeed")) + option_dict.replace_int("popkun", option.child_value("popkun")) + option_dict.replace_bool("hidden", option.child_value("hidden")) + option_dict.replace_bool("sudden", option.child_value("sudden")) + option_dict.replace_int("hidden_rate", option.child_value("hidden_rate")) + option_dict.replace_int("sudden_rate", option.child_value("sudden_rate")) + option_dict.replace_int("randmir", option.child_value("randmir")) + option_dict.replace_int("gauge_type", option.child_value("gauge_type")) + option_dict.replace_int("ojama_0", option.child_value("ojama_0")) + option_dict.replace_int("ojama_1", option.child_value("ojama_1")) + option_dict.replace_bool("forever_0", option.child_value("forever_0")) + option_dict.replace_bool("forever_1", option.child_value("forever_1")) + option_dict.replace_bool("full_setting", option.child_value("full_setting")) + newprofile.replace_dict("option", option_dict) - customize_dict = newprofile.get_dict('customize') - customize = request.child('customize') + customize_dict = newprofile.get_dict("customize") + customize = request.child("customize") if customize is not None: - customize_dict.replace_int('effect', customize.child_value('effect')) - customize_dict.replace_int('hukidashi', customize.child_value('hukidashi')) - customize_dict.replace_int('font', customize.child_value('font')) - customize_dict.replace_int('comment_1', customize.child_value('comment_1')) - customize_dict.replace_int('comment_2', customize.child_value('comment_2')) - newprofile.replace_dict('customize', customize_dict) + customize_dict.replace_int("effect", customize.child_value("effect")) + customize_dict.replace_int("hukidashi", customize.child_value("hukidashi")) + customize_dict.replace_int("font", customize.child_value("font")) + customize_dict.replace_int("comment_1", customize.child_value("comment_1")) + customize_dict.replace_int("comment_2", customize.child_value("comment_2")) + newprofile.replace_dict("customize", customize_dict) # Keep track of play statistics self.update_play_statistics(userid) @@ -913,16 +997,16 @@ class PopnMusicLapistoria(PopnMusicBase): # Extract achievements game_config = self.get_game_config() for node in request.children: - if node.name == 'item': - if not node.child_value('is_new'): + if node.name == "item": + if not node.child_value("is_new"): # No need to save this one continue - itemid = node.child_value('id') - itemtype = node.child_value('type') - param = node.child_value('param') + itemid = node.child_value("id") + itemtype = node.child_value("type") + param = node.child_value("param") - if game_config.get_bool('force_unlock_songs') and itemtype == 0: + if game_config.get_bool("force_unlock_songs") and itemtype == 0: # If we enabled force song unlocks, don't save songs to the profile. continue @@ -931,73 +1015,73 @@ class PopnMusicLapistoria(PopnMusicBase): self.version, userid, itemid, - 'item', + "item", { - 'type': itemtype, - 'param': param, + "type": itemtype, + "param": param, }, ) - elif node.name == 'achievement': - achievementid = node.child_value('type') - count = node.child_value('count') + elif node.name == "achievement": + achievementid = node.child_value("type") + count = node.child_value("count") self.data.local.user.put_achievement( self.game, self.version, userid, achievementid, - 'achievement', + "achievement", { - 'count': count, + "count": count, }, ) - elif node.name == 'chara_param': - charaid = node.child_value('chara_id') - friendship = node.child_value('friendship') + elif node.name == "chara_param": + charaid = node.child_value("chara_id") + friendship = node.child_value("friendship") self.data.local.user.put_achievement( self.game, self.version, userid, charaid, - 'chara', + "chara", { - 'friendship': friendship, + "friendship": friendship, }, ) - elif node.name == 'story': - storyid = node.child_value('story_id') - chapter = node.child_value('chapter_id') - gauge = node.child_value('gauge_point') - cleared = node.child_value('is_cleared') - clear_chapter = node.child_value('clear_chapter') + elif node.name == "story": + storyid = node.child_value("story_id") + chapter = node.child_value("chapter_id") + gauge = node.child_value("gauge_point") + cleared = node.child_value("is_cleared") + clear_chapter = node.child_value("clear_chapter") self.data.local.user.put_achievement( self.game, self.version, userid, storyid, - 'story', + "story", { - 'chapter': chapter, - 'gauge': gauge, - 'cleared': cleared, - 'clear_chapter': clear_chapter, + "chapter": chapter, + "gauge": gauge, + "cleared": cleared, + "clear_chapter": clear_chapter, }, ) return newprofile def format_conversion(self, userid: UserID, profile: Profile) -> Node: - root = Node.void('playerdata') + root = Node.void("playerdata") - root.add_child(Node.string('name', profile.get_str('name', 'なし'))) - root.add_child(Node.s16('chara', profile.get_int('chara', -1))) - root.add_child(Node.s32('option', profile.get_int('option', 0))) - root.add_child(Node.s8('result', 1)) + root.add_child(Node.string("name", profile.get_str("name", "なし"))) + root.add_child(Node.s16("chara", profile.get_int("chara", -1))) + root.add_child(Node.s32("option", profile.get_int("option", 0))) + root.add_child(Node.s8("result", 1)) scores = self.data.remote.music.get_scores(self.game, self.version, userid) for score in scores: @@ -1014,34 +1098,44 @@ class PopnMusicLapistoria(PopnMusicBase): continue points = score.points - medal = score.data.get_int('medal') + medal = score.data.get_int("medal") - music = Node.void('music') + music = Node.void("music") root.add_child(music) - music.add_child(Node.s16('music_num', score.id)) - music.add_child(Node.u8('sheet_num', { - self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY, - self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL, - self.CHART_TYPE_HYPER: self.GAME_CHART_TYPE_HYPER, - self.CHART_TYPE_EX: self.GAME_CHART_TYPE_EX, - }[score.chart])) - music.add_child(Node.s16('cnt', score.plays)) - music.add_child(Node.s32('score', 0)) - music.add_child(Node.u8('clear_type', 0)) - music.add_child(Node.s32('old_score', points)) - music.add_child(Node.u8('old_clear_type', { - self.PLAY_MEDAL_NO_PLAY: self.GAME_PLAY_MEDAL_NO_PLAY, - self.PLAY_MEDAL_CIRCLE_FAILED: self.GAME_PLAY_MEDAL_CIRCLE_FAILED, - self.PLAY_MEDAL_DIAMOND_FAILED: self.GAME_PLAY_MEDAL_DIAMOND_FAILED, - self.PLAY_MEDAL_STAR_FAILED: self.GAME_PLAY_MEDAL_STAR_FAILED, - self.PLAY_MEDAL_EASY_CLEAR: self.GAME_PLAY_MEDAL_EASY_CLEAR, - self.PLAY_MEDAL_CIRCLE_CLEARED: self.GAME_PLAY_MEDAL_CIRCLE_CLEARED, - self.PLAY_MEDAL_DIAMOND_CLEARED: self.GAME_PLAY_MEDAL_DIAMOND_CLEARED, - self.PLAY_MEDAL_STAR_CLEARED: self.GAME_PLAY_MEDAL_STAR_CLEARED, - self.PLAY_MEDAL_CIRCLE_FULL_COMBO: self.GAME_PLAY_MEDAL_CIRCLE_FULL_COMBO, - self.PLAY_MEDAL_DIAMOND_FULL_COMBO: self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO, - self.PLAY_MEDAL_STAR_FULL_COMBO: self.GAME_PLAY_MEDAL_STAR_FULL_COMBO, - self.PLAY_MEDAL_PERFECT: self.GAME_PLAY_MEDAL_PERFECT, - }[medal])) + music.add_child(Node.s16("music_num", score.id)) + music.add_child( + Node.u8( + "sheet_num", + { + self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY, + self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL, + self.CHART_TYPE_HYPER: self.GAME_CHART_TYPE_HYPER, + self.CHART_TYPE_EX: self.GAME_CHART_TYPE_EX, + }[score.chart], + ) + ) + music.add_child(Node.s16("cnt", score.plays)) + music.add_child(Node.s32("score", 0)) + music.add_child(Node.u8("clear_type", 0)) + music.add_child(Node.s32("old_score", points)) + music.add_child( + Node.u8( + "old_clear_type", + { + self.PLAY_MEDAL_NO_PLAY: self.GAME_PLAY_MEDAL_NO_PLAY, + self.PLAY_MEDAL_CIRCLE_FAILED: self.GAME_PLAY_MEDAL_CIRCLE_FAILED, + self.PLAY_MEDAL_DIAMOND_FAILED: self.GAME_PLAY_MEDAL_DIAMOND_FAILED, + self.PLAY_MEDAL_STAR_FAILED: self.GAME_PLAY_MEDAL_STAR_FAILED, + self.PLAY_MEDAL_EASY_CLEAR: self.GAME_PLAY_MEDAL_EASY_CLEAR, + self.PLAY_MEDAL_CIRCLE_CLEARED: self.GAME_PLAY_MEDAL_CIRCLE_CLEARED, + self.PLAY_MEDAL_DIAMOND_CLEARED: self.GAME_PLAY_MEDAL_DIAMOND_CLEARED, + self.PLAY_MEDAL_STAR_CLEARED: self.GAME_PLAY_MEDAL_STAR_CLEARED, + self.PLAY_MEDAL_CIRCLE_FULL_COMBO: self.GAME_PLAY_MEDAL_CIRCLE_FULL_COMBO, + self.PLAY_MEDAL_DIAMOND_FULL_COMBO: self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO, + self.PLAY_MEDAL_STAR_FULL_COMBO: self.GAME_PLAY_MEDAL_STAR_FULL_COMBO, + self.PLAY_MEDAL_PERFECT: self.GAME_PLAY_MEDAL_PERFECT, + }[medal], + ) + ) return root diff --git a/bemani/backend/popn/peace.py b/bemani/backend/popn/peace.py index 8b3500e..128b82a 100644 --- a/bemani/backend/popn/peace.py +++ b/bemani/backend/popn/peace.py @@ -36,102 +36,102 @@ class PopnMusicPeace(PopnMusicModernBase): Return all of our front-end modifiably settings. """ return { - 'ints': [ + "ints": [ { - 'name': 'Music Open Phase', - 'tip': 'Default music phase for all players.', - 'category': 'game_config', - 'setting': 'music_phase', - 'values': { + "name": "Music Open Phase", + "tip": "Default music phase for all players.", + "category": "game_config", + "setting": "music_phase", + "values": { # The value goes to 23 now, but it starts where usaneko left off at 11 # Unlocks a total of 53 songs - 12: 'No music unlocks', - 13: 'Phase 1', - 14: 'Phase 2', - 15: 'Phase 3', - 16: 'Phase 4', - 17: 'Phase 5', - 18: 'Phase 6', - 19: 'Phase 7', - 20: 'Phase 8', - 21: 'Phase 9', - 22: 'Phase 10', - 23: 'Phase MAX', - } + 12: "No music unlocks", + 13: "Phase 1", + 14: "Phase 2", + 15: "Phase 3", + 16: "Phase 4", + 17: "Phase 5", + 18: "Phase 6", + 19: "Phase 7", + 20: "Phase 8", + 21: "Phase 9", + 22: "Phase 10", + 23: "Phase MAX", + }, }, { - 'name': 'NAVI-Kun Event Phase', - 'tip': 'NAVI-Kun event phase for all players.', - 'category': 'game_config', - 'setting': 'navikun_phase', - 'values': { + "name": "NAVI-Kun Event Phase", + "tip": "NAVI-Kun event phase for all players.", + "category": "game_config", + "setting": "navikun_phase", + "values": { # The value goes to 30 now, but it starts where usaneko left off at 15 # Unlocks a total of 89 songs - 15: 'Phase 1', - 16: 'Phase 2', - 17: 'Phase 3', - 18: 'Phase 4', - 19: 'Phase 5', - 20: 'Phase 6', - 21: 'Phase 7', - 22: 'Phase 8', - 23: 'Phase 9', - 24: 'Phase 10', - 25: 'Phase 11', - 26: 'Phase 12', - 27: 'Phase 13', - 28: 'Phase 14', - 29: 'Phase 15', - 30: 'Phase MAX', + 15: "Phase 1", + 16: "Phase 2", + 17: "Phase 3", + 18: "Phase 4", + 19: "Phase 5", + 20: "Phase 6", + 21: "Phase 7", + 22: "Phase 8", + 23: "Phase 9", + 24: "Phase 10", + 25: "Phase 11", + 26: "Phase 12", + 27: "Phase 13", + 28: "Phase 14", + 29: "Phase 15", + 30: "Phase MAX", }, }, { # For festive times, it's possible to change the welcome greeting. I'm not sure why you would want to change this, but now you can. - 'name': 'Holiday Greeting', - 'tip': 'Changes the payment selection confirmation sound.', - 'category': 'game_config', - 'setting': 'holiday_greeting', - 'values': { - 0: 'Okay!', - 1: 'Merry Christmas!', - 2: 'Happy New Year!', - } + "name": "Holiday Greeting", + "tip": "Changes the payment selection confirmation sound.", + "category": "game_config", + "setting": "holiday_greeting", + "values": { + 0: "Okay!", + 1: "Merry Christmas!", + 2: "Happy New Year!", + }, }, { # The following values control the pop'n music event archive. Setting the flag to the following values has the # corresponding effect. Each value will include the events above it, for example setting it to 5 gives you the # pop'n 15 event, as well as SP, 12, and 11 events. Setting it to 0 disabled the event and skips the entire screen, # setting it to 20 makes all of the events available for selection. Completing the minigame unlocks the associated content. - 'name': 'Event Archive Phase', - 'tip': 'Event Archive mini-game phase for all players.', - 'category': 'game_config', - 'setting': 'event_archive_phase', - 'values': { - 0: 'Event Archive disabled', - 1: 'pop\'n music 11 - The Latest Space Station', - 2: 'pop\'n music 11 & 12 Iroha - The Southernmost Point of the Universe / Ninja Otasuke Cheat Sheet in Trouble', - 3: 'pop\'n music Sunny Park - I Love Walking in Happiness Park', - 4: 'pop\'n music 12 Iroha - Ninja Code: April 1st Volume', - 5: 'pop\'n music 15 ADVENTURE - Route to Awaken the Soul', - 6: 'pop\'n music 20 fantasia - A Braided Fantasy Song', - 7: 'EXTRA', - 8: 'pop\'n music 15 ADVENTURE - A Route with a Faint Bell Sound', - 9: 'pop\'n music 13 Carnival - Bunny Magician Attraction', - 10: 'pop\'n music 14 FEVER! - That Burning Special Attack, again!', - 11: 'pop\'n music Sunny Park - Festival Nightfall Park', - 12: 'pop\'n music 20 fantasia - A Fantasy Song by the Bladed Warrior', - 13: 'pop\'n music 19 TUNE STREET - A Town Where the Sound of the Brass Band Rings After School', - 14: 'pop\'n music éclale - Fun Rag Hour', - 15: 'pop\'n music 13 Carnival - Ghost Piano Attraction', - 16: 'pop\'n music 14 FEVER! - That Warrior Defending Peace, again!', - 17: 'pop\'n music 18 Sengoku Retsuden - A Territory with a Glamorous Cultural Flavor', - 18: 'pop\'n music éclale - Runaway Guitarist in the Starry Sky', - 19: 'pop\'n music 17 THE MOVIE - A Blockbuster Uncovering a Conspiracy in the Peaceful City', - 20: 'pop\'n music lapistoria - God\'s Forgotten Things', - } + "name": "Event Archive Phase", + "tip": "Event Archive mini-game phase for all players.", + "category": "game_config", + "setting": "event_archive_phase", + "values": { + 0: "Event Archive disabled", + 1: "pop'n music 11 - The Latest Space Station", + 2: "pop'n music 11 & 12 Iroha - The Southernmost Point of the Universe / Ninja Otasuke Cheat Sheet in Trouble", + 3: "pop'n music Sunny Park - I Love Walking in Happiness Park", + 4: "pop'n music 12 Iroha - Ninja Code: April 1st Volume", + 5: "pop'n music 15 ADVENTURE - Route to Awaken the Soul", + 6: "pop'n music 20 fantasia - A Braided Fantasy Song", + 7: "EXTRA", + 8: "pop'n music 15 ADVENTURE - A Route with a Faint Bell Sound", + 9: "pop'n music 13 Carnival - Bunny Magician Attraction", + 10: "pop'n music 14 FEVER! - That Burning Special Attack, again!", + 11: "pop'n music Sunny Park - Festival Nightfall Park", + 12: "pop'n music 20 fantasia - A Fantasy Song by the Bladed Warrior", + 13: "pop'n music 19 TUNE STREET - A Town Where the Sound of the Brass Band Rings After School", + 14: "pop'n music éclale - Fun Rag Hour", + 15: "pop'n music 13 Carnival - Ghost Piano Attraction", + 16: "pop'n music 14 FEVER! - That Warrior Defending Peace, again!", + 17: "pop'n music 18 Sengoku Retsuden - A Territory with a Glamorous Cultural Flavor", + 18: "pop'n music éclale - Runaway Guitarist in the Starry Sky", + 19: "pop'n music 17 THE MOVIE - A Blockbuster Uncovering a Conspiracy in the Peaceful City", + 20: "pop'n music lapistoria - God's Forgotten Things", + }, }, ], - 'bools': [ + "bools": [ # We don't currently support lobbies or anything, so this is commented out until # somebody gets around to implementing it. # { @@ -141,21 +141,21 @@ class PopnMusicPeace(PopnMusicModernBase): # 'setting': 'enable_net_taisen', # }, { - 'name': 'Force Song Unlock', - 'tip': 'Force unlock all songs.', - 'category': 'game_config', - 'setting': 'force_unlock_songs', + "name": "Force Song Unlock", + "tip": "Force unlock all songs.", + "category": "game_config", + "setting": "force_unlock_songs", }, ], } def get_common_config(self) -> Tuple[Dict[int, int], bool]: game_config = self.get_game_config() - music_phase = game_config.get_int('music_phase') - event_archive_phase = game_config.get_int('event_archive_phase') - holiday_greeting = game_config.get_int('holiday_greeting') + music_phase = game_config.get_int("music_phase") + event_archive_phase = game_config.get_int("event_archive_phase") + holiday_greeting = game_config.get_int("holiday_greeting") enable_net_taisen = False # game_config.get_bool('enable_net_taisen') - navikun_phase = game_config.get_int('navikun_phase') + navikun_phase = game_config.get_int("navikun_phase") # Event phases return ( diff --git a/bemani/backend/popn/sunnypark.py b/bemani/backend/popn/sunnypark.py index 389ac25..0221b09 100644 --- a/bemani/backend/popn/sunnypark.py +++ b/bemani/backend/popn/sunnypark.py @@ -52,44 +52,44 @@ class PopnMusicSunnyPark(PopnMusicBase): Return all of our front-end modifiably settings. """ return { - 'ints': [ + "ints": [ { - 'name': 'Music Open Phase', - 'tip': 'Default music phase for all players.', - 'category': 'game_config', - 'setting': 'music_phase', - 'values': { - 0: 'No music unlocks', - 1: 'Phase 1', - 2: 'Phase 2', - 3: 'Phase 3', - 4: 'Phase 4', - 5: 'Phase 5', - 6: 'Phase 6', - 7: 'Phase 7', - 8: 'Phase 8', - 9: 'Phase MAX', - } + "name": "Music Open Phase", + "tip": "Default music phase for all players.", + "category": "game_config", + "setting": "music_phase", + "values": { + 0: "No music unlocks", + 1: "Phase 1", + 2: "Phase 2", + 3: "Phase 3", + 4: "Phase 4", + 5: "Phase 5", + 6: "Phase 6", + 7: "Phase 7", + 8: "Phase 8", + 9: "Phase MAX", + }, }, { - 'name': 'Event Phase', - 'tip': 'Event phase for all players.', - 'category': 'game_config', - 'setting': 'event_phase', - 'values': { - 0: 'No event', - 1: 'Pop\'n Walker Phase 1', - 2: 'Pop\'n Walker Phase 2', - 3: 'Pop\'n Walker Phase 3', + "name": "Event Phase", + "tip": "Event phase for all players.", + "category": "game_config", + "setting": "event_phase", + "values": { + 0: "No event", + 1: "Pop'n Walker Phase 1", + 2: "Pop'n Walker Phase 2", + 3: "Pop'n Walker Phase 3", # Phase 4 turns off Pop'n Walker but does not enable # Wai Wai Pop'n Zoo so I've left it out. # Phase 5 appears to be identical to phase 6 below. - 6: 'Wai Wai Pop\'n Zoo Phase 1: Elephants', + 6: "Wai Wai Pop'n Zoo Phase 1: Elephants", # Phase 7 appears to be identical to phase 8 below. - 8: 'Wai Wai Pop\'n Zoo Phase 2: Dog', - 9: 'Wai Wai Pop\'n Zoo Phase 3: Alpaca', - 10: 'Wai Wai Pop\'n Zoo Phase 4: Cow', - } + 8: "Wai Wai Pop'n Zoo Phase 2: Dog", + 9: "Wai Wai Pop'n Zoo Phase 3: Alpaca", + 10: "Wai Wai Pop'n Zoo Phase 4: Cow", + }, }, ] } @@ -107,59 +107,74 @@ class PopnMusicSunnyPark(PopnMusicBase): self.PLAY_MEDAL_DIAMOND_FULL_COMBO: self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO, self.PLAY_MEDAL_STAR_FULL_COMBO: self.GAME_PLAY_MEDAL_STAR_FULL_COMBO, self.PLAY_MEDAL_PERFECT: self.GAME_PLAY_MEDAL_PERFECT, - }[score.data.get_int('medal')] + }[score.data.get_int("medal")] medal_pos = { self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY_POSITION, self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL_POSITION, self.CHART_TYPE_HYPER: self.GAME_CHART_TYPE_HYPER_POSITION, self.CHART_TYPE_EX: self.GAME_CHART_TYPE_EX_POSITION, }[score.chart] - return (medal << (medal_pos * 4)) + return medal << (medal_pos * 4) def format_profile(self, userid: UserID, profile: Profile) -> Node: - root = Node.void('playerdata') + root = Node.void("playerdata") # Set up the base profile - base = Node.void('base') + base = Node.void("base") root.add_child(base) - base.add_child(Node.string('name', profile.get_str('name', 'なし'))) - base.add_child(Node.string('g_pm_id', ID.format_extid(profile.extid))) - base.add_child(Node.u8('mode', profile.get_int('mode', 0))) - base.add_child(Node.s8('button', profile.get_int('button', 0))) - base.add_child(Node.s8('last_play_flag', profile.get_int('last_play_flag', -1))) - base.add_child(Node.u8('medal_and_friend', profile.get_int('medal_and_friend', 0))) - base.add_child(Node.s8('category', profile.get_int('category', -1))) - base.add_child(Node.s8('sub_category', profile.get_int('sub_category', -1))) - base.add_child(Node.s16('chara', profile.get_int('chara', -1))) - base.add_child(Node.s8('chara_category', profile.get_int('chara_category', -1))) - base.add_child(Node.u8('collabo', 255)) - base.add_child(Node.u8('sheet', profile.get_int('sheet', 0))) - base.add_child(Node.s8('tutorial', profile.get_int('tutorial', 0))) - base.add_child(Node.s8('music_open_pt', profile.get_int('music_open_pt', 0))) - base.add_child(Node.s8('is_conv', -1)) - base.add_child(Node.s32('option', profile.get_int('option', 0))) - base.add_child(Node.s16('music', profile.get_int('music', -1))) - base.add_child(Node.u16('ep', profile.get_int('ep', 0))) - base.add_child(Node.s32_array('sp_color_flg', profile.get_int_array('sp_color_flg', 2))) - base.add_child(Node.s32('read_news', profile.get_int('read_news', 0))) - base.add_child(Node.s16('consecutive_days_coupon', profile.get_int('consecutive_days_coupon', 0))) - base.add_child(Node.s8('staff', 0)) + base.add_child(Node.string("name", profile.get_str("name", "なし"))) + base.add_child(Node.string("g_pm_id", ID.format_extid(profile.extid))) + base.add_child(Node.u8("mode", profile.get_int("mode", 0))) + base.add_child(Node.s8("button", profile.get_int("button", 0))) + base.add_child(Node.s8("last_play_flag", profile.get_int("last_play_flag", -1))) + base.add_child( + Node.u8("medal_and_friend", profile.get_int("medal_and_friend", 0)) + ) + base.add_child(Node.s8("category", profile.get_int("category", -1))) + base.add_child(Node.s8("sub_category", profile.get_int("sub_category", -1))) + base.add_child(Node.s16("chara", profile.get_int("chara", -1))) + base.add_child(Node.s8("chara_category", profile.get_int("chara_category", -1))) + base.add_child(Node.u8("collabo", 255)) + base.add_child(Node.u8("sheet", profile.get_int("sheet", 0))) + base.add_child(Node.s8("tutorial", profile.get_int("tutorial", 0))) + base.add_child(Node.s8("music_open_pt", profile.get_int("music_open_pt", 0))) + base.add_child(Node.s8("is_conv", -1)) + base.add_child(Node.s32("option", profile.get_int("option", 0))) + base.add_child(Node.s16("music", profile.get_int("music", -1))) + base.add_child(Node.u16("ep", profile.get_int("ep", 0))) + base.add_child( + Node.s32_array("sp_color_flg", profile.get_int_array("sp_color_flg", 2)) + ) + base.add_child(Node.s32("read_news", profile.get_int("read_news", 0))) + base.add_child( + Node.s16( + "consecutive_days_coupon", profile.get_int("consecutive_days_coupon", 0) + ) + ) + base.add_child(Node.s8("staff", 0)) # These are probably from an old event, but if they aren't present and defaulted, # then different songs show up in the Zoo event. - base.add_child(Node.u16_array('gitadora_point', profile.get_int_array('gitadora_point', 3, [2000, 2000, 2000]))) - base.add_child(Node.u8('gitadora_select', profile.get_int('gitadora_select', 2))) + base.add_child( + Node.u16_array( + "gitadora_point", + profile.get_int_array("gitadora_point", 3, [2000, 2000, 2000]), + ) + ) + base.add_child( + Node.u8("gitadora_select", profile.get_int("gitadora_select", 2)) + ) # Statistics section and scores section statistics = self.get_play_statistics(userid) - base.add_child(Node.s32('total_play_cnt', statistics.total_plays)) - base.add_child(Node.s16('today_play_cnt', statistics.today_plays)) - base.add_child(Node.s16('consecutive_days', statistics.consecutive_days)) + base.add_child(Node.s32("total_play_cnt", statistics.total_plays)) + base.add_child(Node.s16("today_play_cnt", statistics.today_plays)) + base.add_child(Node.s16("consecutive_days", statistics.consecutive_days)) # Number of rivals that are active for this version. links = self.data.local.user.get_links(self.game, self.version, userid) rivalcount = 0 for link in links: - if link.type != 'rival': + if link.type != "rival": continue if not self.has_profile(link.other_userid): @@ -167,10 +182,20 @@ class PopnMusicSunnyPark(PopnMusicBase): # This profile is valid. rivalcount += 1 - base.add_child(Node.u8('active_fr_num', rivalcount)) + base.add_child(Node.u8("active_fr_num", rivalcount)) - last_played = [x[0] for x in self.data.local.music.get_last_played(self.game, self.version, userid, 3)] - most_played = [x[0] for x in self.data.local.music.get_most_played(self.game, self.version, userid, 20)] + last_played = [ + x[0] + for x in self.data.local.music.get_last_played( + self.game, self.version, userid, 3 + ) + ] + most_played = [ + x[0] + for x in self.data.local.music.get_most_played( + self.game, self.version, userid, 20 + ) + ] while len(last_played) < 3: last_played.append(-1) while len(most_played) < 20: @@ -193,10 +218,12 @@ class PopnMusicSunnyPark(PopnMusicBase): self.CHART_TYPE_EX, ]: continue - if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY: + if score.data.get_int("medal") == self.PLAY_MEDAL_NO_PLAY: continue - clear_medal[score.id] = clear_medal[score.id] | self.__format_medal_for_score(score) + clear_medal[score.id] = clear_medal[ + score.id + ] | self.__format_medal_for_score(score) hiscore_index = (score.id * 4) + { self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY_POSITION, self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL_POSITION, @@ -206,152 +233,234 @@ class PopnMusicSunnyPark(PopnMusicBase): hiscore_byte_pos = int((hiscore_index * 17) / 8) hiscore_bit_pos = int((hiscore_index * 17) % 8) hiscore_value = score.points << hiscore_bit_pos - hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (hiscore_value & 0xFF) - hiscore_array[hiscore_byte_pos + 1] = hiscore_array[hiscore_byte_pos + 1] | ((hiscore_value >> 8) & 0xFF) - hiscore_array[hiscore_byte_pos + 2] = hiscore_array[hiscore_byte_pos + 2] | ((hiscore_value >> 16) & 0xFF) + hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | ( + hiscore_value & 0xFF + ) + hiscore_array[hiscore_byte_pos + 1] = hiscore_array[ + hiscore_byte_pos + 1 + ] | ((hiscore_value >> 8) & 0xFF) + hiscore_array[hiscore_byte_pos + 2] = hiscore_array[ + hiscore_byte_pos + 2 + ] | ((hiscore_value >> 16) & 0xFF) hiscore = bytes(hiscore_array) - base.add_child(Node.s16_array('my_best', most_played)) - base.add_child(Node.s16_array('latest_music', last_played)) - base.add_child(Node.u16_array('clear_medal', clear_medal)) - base.add_child(Node.u8_array('clear_medal_sub', clear_medal_sub)) + base.add_child(Node.s16_array("my_best", most_played)) + base.add_child(Node.s16_array("latest_music", last_played)) + base.add_child(Node.u16_array("clear_medal", clear_medal)) + base.add_child(Node.u8_array("clear_medal_sub", clear_medal_sub)) # Goes outside of base for some reason - root.add_child(Node.binary('hiscore', hiscore)) + root.add_child(Node.binary("hiscore", hiscore)) # Avatar section - avatar_dict = profile.get_dict('avatar') - avatar = Node.void('avatar') + avatar_dict = profile.get_dict("avatar") + avatar = Node.void("avatar") root.add_child(avatar) - avatar.add_child(Node.u8('hair', avatar_dict.get_int('hair', 0))) - avatar.add_child(Node.u8('face', avatar_dict.get_int('face', 0))) - avatar.add_child(Node.u8('body', avatar_dict.get_int('body', 0))) - avatar.add_child(Node.u8('effect', avatar_dict.get_int('effect', 0))) - avatar.add_child(Node.u8('object', avatar_dict.get_int('object', 0))) - avatar.add_child(Node.u8_array('comment', avatar_dict.get_int_array('comment', 2))) - avatar.add_child(Node.s32_array('get_hair', avatar_dict.get_int_array('get_hair', 2))) - avatar.add_child(Node.s32_array('get_face', avatar_dict.get_int_array('get_face', 2))) - avatar.add_child(Node.s32_array('get_body', avatar_dict.get_int_array('get_body', 2))) - avatar.add_child(Node.s32_array('get_effect', avatar_dict.get_int_array('get_effect', 2))) - avatar.add_child(Node.s32_array('get_object', avatar_dict.get_int_array('get_object', 2))) - avatar.add_child(Node.s32_array('get_comment_over', avatar_dict.get_int_array('get_comment_over', 3))) - avatar.add_child(Node.s32_array('get_comment_under', avatar_dict.get_int_array('get_comment_under', 3))) + avatar.add_child(Node.u8("hair", avatar_dict.get_int("hair", 0))) + avatar.add_child(Node.u8("face", avatar_dict.get_int("face", 0))) + avatar.add_child(Node.u8("body", avatar_dict.get_int("body", 0))) + avatar.add_child(Node.u8("effect", avatar_dict.get_int("effect", 0))) + avatar.add_child(Node.u8("object", avatar_dict.get_int("object", 0))) + avatar.add_child( + Node.u8_array("comment", avatar_dict.get_int_array("comment", 2)) + ) + avatar.add_child( + Node.s32_array("get_hair", avatar_dict.get_int_array("get_hair", 2)) + ) + avatar.add_child( + Node.s32_array("get_face", avatar_dict.get_int_array("get_face", 2)) + ) + avatar.add_child( + Node.s32_array("get_body", avatar_dict.get_int_array("get_body", 2)) + ) + avatar.add_child( + Node.s32_array("get_effect", avatar_dict.get_int_array("get_effect", 2)) + ) + avatar.add_child( + Node.s32_array("get_object", avatar_dict.get_int_array("get_object", 2)) + ) + avatar.add_child( + Node.s32_array( + "get_comment_over", avatar_dict.get_int_array("get_comment_over", 3) + ) + ) + avatar.add_child( + Node.s32_array( + "get_comment_under", avatar_dict.get_int_array("get_comment_under", 3) + ) + ) # Avatar add section - avatar_add_dict = profile.get_dict('avatar_add') - avatar_add = Node.void('avatar_add') + avatar_add_dict = profile.get_dict("avatar_add") + avatar_add = Node.void("avatar_add") root.add_child(avatar_add) - avatar_add.add_child(Node.s32_array('get_hair', avatar_add_dict.get_int_array('get_hair', 2))) - avatar_add.add_child(Node.s32_array('get_face', avatar_add_dict.get_int_array('get_face', 2))) - avatar_add.add_child(Node.s32_array('get_body', avatar_add_dict.get_int_array('get_body', 2))) - avatar_add.add_child(Node.s32_array('get_effect', avatar_add_dict.get_int_array('get_effect', 2))) - avatar_add.add_child(Node.s32_array('get_object', avatar_add_dict.get_int_array('get_object', 2))) - avatar_add.add_child(Node.s32_array('get_comment_over', avatar_add_dict.get_int_array('get_comment_over', 2))) - avatar_add.add_child(Node.s32_array('get_comment_under', avatar_add_dict.get_int_array('get_comment_under', 2))) - avatar_add.add_child(Node.s32_array('new_hair', avatar_add_dict.get_int_array('new_hair', 2))) - avatar_add.add_child(Node.s32_array('new_face', avatar_add_dict.get_int_array('new_face', 2))) - avatar_add.add_child(Node.s32_array('new_body', avatar_add_dict.get_int_array('new_body', 2))) - avatar_add.add_child(Node.s32_array('new_effect', avatar_add_dict.get_int_array('new_effect', 2))) - avatar_add.add_child(Node.s32_array('new_object', avatar_add_dict.get_int_array('new_object', 2))) - avatar_add.add_child(Node.s32_array('new_comment_over', avatar_add_dict.get_int_array('new_comment_over', 2))) - avatar_add.add_child(Node.s32_array('new_comment_under', avatar_add_dict.get_int_array('new_comment_under', 2))) + avatar_add.add_child( + Node.s32_array("get_hair", avatar_add_dict.get_int_array("get_hair", 2)) + ) + avatar_add.add_child( + Node.s32_array("get_face", avatar_add_dict.get_int_array("get_face", 2)) + ) + avatar_add.add_child( + Node.s32_array("get_body", avatar_add_dict.get_int_array("get_body", 2)) + ) + avatar_add.add_child( + Node.s32_array("get_effect", avatar_add_dict.get_int_array("get_effect", 2)) + ) + avatar_add.add_child( + Node.s32_array("get_object", avatar_add_dict.get_int_array("get_object", 2)) + ) + avatar_add.add_child( + Node.s32_array( + "get_comment_over", avatar_add_dict.get_int_array("get_comment_over", 2) + ) + ) + avatar_add.add_child( + Node.s32_array( + "get_comment_under", + avatar_add_dict.get_int_array("get_comment_under", 2), + ) + ) + avatar_add.add_child( + Node.s32_array("new_hair", avatar_add_dict.get_int_array("new_hair", 2)) + ) + avatar_add.add_child( + Node.s32_array("new_face", avatar_add_dict.get_int_array("new_face", 2)) + ) + avatar_add.add_child( + Node.s32_array("new_body", avatar_add_dict.get_int_array("new_body", 2)) + ) + avatar_add.add_child( + Node.s32_array("new_effect", avatar_add_dict.get_int_array("new_effect", 2)) + ) + avatar_add.add_child( + Node.s32_array("new_object", avatar_add_dict.get_int_array("new_object", 2)) + ) + avatar_add.add_child( + Node.s32_array( + "new_comment_over", avatar_add_dict.get_int_array("new_comment_over", 2) + ) + ) + avatar_add.add_child( + Node.s32_array( + "new_comment_under", + avatar_add_dict.get_int_array("new_comment_under", 2), + ) + ) # Net VS section - netvs = Node.void('netvs') + netvs = Node.void("netvs") root.add_child(netvs) - netvs.add_child(Node.s32('rank_point', 0)) - netvs.add_child(Node.s16_array('record', [0, 0, 0, 0, 0, 0])) - netvs.add_child(Node.u8('rank', 0)) - netvs.add_child(Node.s8('vs_rank_old', 0)) - netvs.add_child(Node.s8_array('ojama_condition', [0] * 74)) - netvs.add_child(Node.s8_array('set_ojama', [0, 0, 0])) - netvs.add_child(Node.s8_array('set_recommend', [0, 0, 0])) - netvs.add_child(Node.u8('netvs_play_cnt', 0)) + netvs.add_child(Node.s32("rank_point", 0)) + netvs.add_child(Node.s16_array("record", [0, 0, 0, 0, 0, 0])) + netvs.add_child(Node.u8("rank", 0)) + netvs.add_child(Node.s8("vs_rank_old", 0)) + netvs.add_child(Node.s8_array("ojama_condition", [0] * 74)) + netvs.add_child(Node.s8_array("set_ojama", [0, 0, 0])) + netvs.add_child(Node.s8_array("set_recommend", [0, 0, 0])) + netvs.add_child(Node.u8("netvs_play_cnt", 0)) for dialog in [0, 1, 2, 3, 4, 5]: - netvs.add_child(Node.string('dialog', f'dialog#{dialog}')) + netvs.add_child(Node.string("dialog", f"dialog#{dialog}")) - sp_data = Node.void('sp_data') + sp_data = Node.void("sp_data") root.add_child(sp_data) - sp_data.add_child(Node.s32('sp', profile.get_int('sp', 0))) + sp_data.add_child(Node.s32("sp", profile.get_int("sp", 0))) - gakuen = Node.void('gakuen_data') + gakuen = Node.void("gakuen_data") root.add_child(gakuen) - gakuen.add_child(Node.s32('music_list', -1)) + gakuen.add_child(Node.s32("music_list", -1)) - saucer = Node.void('flying_saucer') + saucer = Node.void("flying_saucer") root.add_child(saucer) - saucer.add_child(Node.s32('music_list', -1)) - saucer.add_child(Node.s32('tune_count', -1)) - saucer.add_child(Node.u32('clear_norma', 0)) - saucer.add_child(Node.u32('clear_norma_add', 0)) + saucer.add_child(Node.s32("music_list", -1)) + saucer.add_child(Node.s32("tune_count", -1)) + saucer.add_child(Node.u32("clear_norma", 0)) + saucer.add_child(Node.u32("clear_norma_add", 0)) # Wai Wai Pop'n Zoo event - zoo_dict = profile.get_dict('zoo') - zoo = Node.void('zoo') + zoo_dict = profile.get_dict("zoo") + zoo = Node.void("zoo") root.add_child(zoo) - zoo.add_child(Node.u16_array('point', zoo_dict.get_int_array('point', 5))) - zoo.add_child(Node.s32_array('music_list', zoo_dict.get_int_array('music_list', 2))) - zoo.add_child(Node.s8_array('today_play_flag', zoo_dict.get_int_array('today_play_flag', 4))) + zoo.add_child(Node.u16_array("point", zoo_dict.get_int_array("point", 5))) + zoo.add_child( + Node.s32_array("music_list", zoo_dict.get_int_array("music_list", 2)) + ) + zoo.add_child( + Node.s8_array( + "today_play_flag", zoo_dict.get_int_array("today_play_flag", 4) + ) + ) # Pop'n Walker event - personal_event_dict = profile.get_dict('personal_event') - personal_event = Node.void('personal_event') + personal_event_dict = profile.get_dict("personal_event") + personal_event = Node.void("personal_event") root.add_child(personal_event) - personal_event.add_child(Node.s16_array('pos', personal_event_dict.get_int_array('pos', 2))) - personal_event.add_child(Node.s16('point', personal_event_dict.get_int('point'))) - personal_event.add_child(Node.u32_array('walk_data', personal_event_dict.get_int_array('walk_data', 128))) - personal_event.add_child(Node.u32_array('event_data', personal_event_dict.get_int_array('event_data', 4))) + personal_event.add_child( + Node.s16_array("pos", personal_event_dict.get_int_array("pos", 2)) + ) + personal_event.add_child( + Node.s16("point", personal_event_dict.get_int("point")) + ) + personal_event.add_child( + Node.u32_array( + "walk_data", personal_event_dict.get_int_array("walk_data", 128) + ) + ) + personal_event.add_child( + Node.u32_array( + "event_data", personal_event_dict.get_int_array("event_data", 4) + ) + ) # We don't support triple journey, so this is stubbed out. - triple = Node.void('triple_journey') + triple = Node.void("triple_journey") root.add_child(triple) - triple.add_child(Node.s32('music_list', -1)) - triple.add_child(Node.s32_array('boss_damage', [65534, 65534, 65534, 65534])) - triple.add_child(Node.s32_array('boss_stun', [0, 0, 0, 0])) - triple.add_child(Node.s32('magic_gauge', 0)) - triple.add_child(Node.s32('today_party', 0)) - triple.add_child(Node.bool('union_magic', False)) - triple.add_child(Node.float('base_attack_rate', 1.0)) - triple.add_child(Node.s32('iidx_play_num', 0)) - triple.add_child(Node.s32('reflec_play_num', 0)) - triple.add_child(Node.s32('voltex_play_num', 0)) - triple.add_child(Node.bool('iidx_play_flg', True)) - triple.add_child(Node.bool('reflec_play_flg', True)) - triple.add_child(Node.bool('voltex_play_flg', True)) + triple.add_child(Node.s32("music_list", -1)) + triple.add_child(Node.s32_array("boss_damage", [65534, 65534, 65534, 65534])) + triple.add_child(Node.s32_array("boss_stun", [0, 0, 0, 0])) + triple.add_child(Node.s32("magic_gauge", 0)) + triple.add_child(Node.s32("today_party", 0)) + triple.add_child(Node.bool("union_magic", False)) + triple.add_child(Node.float("base_attack_rate", 1.0)) + triple.add_child(Node.s32("iidx_play_num", 0)) + triple.add_child(Node.s32("reflec_play_num", 0)) + triple.add_child(Node.s32("voltex_play_num", 0)) + triple.add_child(Node.bool("iidx_play_flg", True)) + triple.add_child(Node.bool("reflec_play_flg", True)) + triple.add_child(Node.bool("voltex_play_flg", True)) - ios = Node.void('ios') + ios = Node.void("ios") root.add_child(ios) - ios.add_child(Node.s32('continueRightAnswer', 30)) - ios.add_child(Node.s32('totalRightAnswer', 30)) + ios.add_child(Node.s32("continueRightAnswer", 30)) + ios.add_child(Node.s32("totalRightAnswer", 30)) - kac2013 = Node.void('kac2013') + kac2013 = Node.void("kac2013") root.add_child(kac2013) - kac2013.add_child(Node.s8('music_num', 0)) - kac2013.add_child(Node.s16('music', 0)) - kac2013.add_child(Node.u8('sheet', 0)) + kac2013.add_child(Node.s8("music_num", 0)) + kac2013.add_child(Node.s16("music", 0)) + kac2013.add_child(Node.u8("sheet", 0)) - baseball = Node.void('baseball_data') + baseball = Node.void("baseball_data") root.add_child(baseball) - baseball.add_child(Node.s64('music_list', -1)) + baseball.add_child(Node.s64("music_list", -1)) for id in [3, 5, 7]: - node = Node.void('floor_infection') + node = Node.void("floor_infection") root.add_child(node) - node.add_child(Node.s32('infection_id', id)) - node.add_child(Node.s32('music_list', -1)) + node.add_child(Node.s32("infection_id", id)) + node.add_child(Node.s32("music_list", -1)) return root def format_conversion(self, userid: UserID, profile: Profile) -> Node: - root = Node.void('playerdata') + root = Node.void("playerdata") - root.add_child(Node.string('name', profile.get_str('name', 'なし'))) - root.add_child(Node.s16('chara', profile.get_int('chara', -1))) - root.add_child(Node.s32('option', profile.get_int('option', 0))) - root.add_child(Node.u8('version', 0)) - root.add_child(Node.u8('kind', 0)) - root.add_child(Node.u8('season', 0)) + root.add_child(Node.string("name", profile.get_str("name", "なし"))) + root.add_child(Node.s16("chara", profile.get_int("chara", -1))) + root.add_child(Node.s32("option", profile.get_int("option", 0))) + root.add_child(Node.u8("version", 0)) + root.add_child(Node.u8("kind", 0)) + root.add_child(Node.u8("season", 0)) clear_medal = [0] * self.GAME_MAX_MUSIC_ID @@ -368,107 +477,169 @@ class PopnMusicSunnyPark(PopnMusicBase): self.CHART_TYPE_EX, ]: continue - if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY: + if score.data.get_int("medal") == self.PLAY_MEDAL_NO_PLAY: continue - clear_medal[score.id] = clear_medal[score.id] | self.__format_medal_for_score(score) + clear_medal[score.id] = clear_medal[ + score.id + ] | self.__format_medal_for_score(score) - root.add_child(Node.u16_array('clear_medal', clear_medal)) + root.add_child(Node.u16_array("clear_medal", clear_medal)) return root - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() - newprofile.replace_int('option', request.child_value('option')) - newprofile.replace_int('chara', request.child_value('chara')) - newprofile.replace_int('mode', request.child_value('mode')) - newprofile.replace_int('button', request.child_value('button')) - newprofile.replace_int('music', request.child_value('music')) - newprofile.replace_int('sheet', request.child_value('sheet')) - newprofile.replace_int('last_play_flag', request.child_value('last_play_flag')) - newprofile.replace_int('category', request.child_value('category')) - newprofile.replace_int('sub_category', request.child_value('sub_category')) - newprofile.replace_int('chara_category', request.child_value('chara_category')) - newprofile.replace_int('medal_and_friend', request.child_value('medal_and_friend')) - newprofile.replace_int('ep', request.child_value('ep')) - newprofile.replace_int_array('sp_color_flg', 2, request.child_value('sp_color_flg')) - newprofile.replace_int('read_news', request.child_value('read_news')) - newprofile.replace_int('consecutive_days_coupon', request.child_value('consecutive_days_coupon')) - newprofile.replace_int('tutorial', request.child_value('tutorial')) - newprofile.replace_int('music_open_pt', request.child_value('music_open_pt')) - newprofile.replace_int_array('gitadora_point', 3, request.child_value('gitadora_point')) - newprofile.replace_int('gitadora_select', request.child_value('gitadora_select')) + newprofile.replace_int("option", request.child_value("option")) + newprofile.replace_int("chara", request.child_value("chara")) + newprofile.replace_int("mode", request.child_value("mode")) + newprofile.replace_int("button", request.child_value("button")) + newprofile.replace_int("music", request.child_value("music")) + newprofile.replace_int("sheet", request.child_value("sheet")) + newprofile.replace_int("last_play_flag", request.child_value("last_play_flag")) + newprofile.replace_int("category", request.child_value("category")) + newprofile.replace_int("sub_category", request.child_value("sub_category")) + newprofile.replace_int("chara_category", request.child_value("chara_category")) + newprofile.replace_int( + "medal_and_friend", request.child_value("medal_and_friend") + ) + newprofile.replace_int("ep", request.child_value("ep")) + newprofile.replace_int_array( + "sp_color_flg", 2, request.child_value("sp_color_flg") + ) + newprofile.replace_int("read_news", request.child_value("read_news")) + newprofile.replace_int( + "consecutive_days_coupon", request.child_value("consecutive_days_coupon") + ) + newprofile.replace_int("tutorial", request.child_value("tutorial")) + newprofile.replace_int("music_open_pt", request.child_value("music_open_pt")) + newprofile.replace_int_array( + "gitadora_point", 3, request.child_value("gitadora_point") + ) + newprofile.replace_int( + "gitadora_select", request.child_value("gitadora_select") + ) - sp_node = request.child('sp_data') + sp_node = request.child("sp_data") if sp_node is not None: - newprofile.replace_int('sp', sp_node.child_value('sp')) + newprofile.replace_int("sp", sp_node.child_value("sp")) - zoo_dict = newprofile.get_dict('zoo') - zoo_node = request.child('zoo') + zoo_dict = newprofile.get_dict("zoo") + zoo_node = request.child("zoo") if zoo_node is not None: - zoo_dict.replace_int_array('point', 5, zoo_node.child_value('point')) - zoo_dict.replace_int_array('music_list', 2, zoo_node.child_value('music_list')) - zoo_dict.replace_int_array('today_play_flag', 4, zoo_node.child_value('today_play_flag')) - newprofile.replace_dict('zoo', zoo_dict) + zoo_dict.replace_int_array("point", 5, zoo_node.child_value("point")) + zoo_dict.replace_int_array( + "music_list", 2, zoo_node.child_value("music_list") + ) + zoo_dict.replace_int_array( + "today_play_flag", 4, zoo_node.child_value("today_play_flag") + ) + newprofile.replace_dict("zoo", zoo_dict) - personal_event_dict = newprofile.get_dict('personal_event') - personal_event_node = request.child('personal_event') + personal_event_dict = newprofile.get_dict("personal_event") + personal_event_node = request.child("personal_event") if personal_event_node is not None: - personal_event_dict.replace_int_array('pos', 2, personal_event_node.child_value('pos')) - personal_event_dict.replace_int('point', personal_event_node.child_value('point')) - personal_event_dict.replace_int_array('walk_data', 128, personal_event_node.child_value('walk_data')) - personal_event_dict.replace_int_array('event_data', 4, personal_event_node.child_value('event_data')) - newprofile.replace_dict('personal_event', personal_event_dict) + personal_event_dict.replace_int_array( + "pos", 2, personal_event_node.child_value("pos") + ) + personal_event_dict.replace_int( + "point", personal_event_node.child_value("point") + ) + personal_event_dict.replace_int_array( + "walk_data", 128, personal_event_node.child_value("walk_data") + ) + personal_event_dict.replace_int_array( + "event_data", 4, personal_event_node.child_value("event_data") + ) + newprofile.replace_dict("personal_event", personal_event_dict) - avatar_dict = newprofile.get_dict('avatar') - avatar_dict.replace_int('hair', request.child_value('hair')) - avatar_dict.replace_int('face', request.child_value('face')) - avatar_dict.replace_int('body', request.child_value('body')) - avatar_dict.replace_int('effect', request.child_value('effect')) - avatar_dict.replace_int('object', request.child_value('object')) - avatar_dict.replace_int_array('comment', 2, request.child_value('comment')) - avatar_dict.replace_int_array('get_hair', 2, request.child_value('get_hair')) - avatar_dict.replace_int_array('get_face', 2, request.child_value('get_face')) - avatar_dict.replace_int_array('get_body', 2, request.child_value('get_body')) - avatar_dict.replace_int_array('get_effect', 2, request.child_value('get_effect')) - avatar_dict.replace_int_array('get_object', 2, request.child_value('get_object')) - avatar_dict.replace_int_array('get_comment_over', 3, request.child_value('get_comment_over')) - avatar_dict.replace_int_array('get_comment_under', 3, request.child_value('get_comment_under')) - newprofile.replace_dict('avatar', avatar_dict) + avatar_dict = newprofile.get_dict("avatar") + avatar_dict.replace_int("hair", request.child_value("hair")) + avatar_dict.replace_int("face", request.child_value("face")) + avatar_dict.replace_int("body", request.child_value("body")) + avatar_dict.replace_int("effect", request.child_value("effect")) + avatar_dict.replace_int("object", request.child_value("object")) + avatar_dict.replace_int_array("comment", 2, request.child_value("comment")) + avatar_dict.replace_int_array("get_hair", 2, request.child_value("get_hair")) + avatar_dict.replace_int_array("get_face", 2, request.child_value("get_face")) + avatar_dict.replace_int_array("get_body", 2, request.child_value("get_body")) + avatar_dict.replace_int_array( + "get_effect", 2, request.child_value("get_effect") + ) + avatar_dict.replace_int_array( + "get_object", 2, request.child_value("get_object") + ) + avatar_dict.replace_int_array( + "get_comment_over", 3, request.child_value("get_comment_over") + ) + avatar_dict.replace_int_array( + "get_comment_under", 3, request.child_value("get_comment_under") + ) + newprofile.replace_dict("avatar", avatar_dict) - avatar_add_dict = newprofile.get_dict('avatar_add') - avatar_add_node = request.child('avatar_add') + avatar_add_dict = newprofile.get_dict("avatar_add") + avatar_add_node = request.child("avatar_add") if avatar_add_node is not None: - avatar_add_dict.replace_int_array('get_hair', 2, avatar_add_node.child_value('get_hair')) - avatar_add_dict.replace_int_array('get_face', 2, avatar_add_node.child_value('get_face')) - avatar_add_dict.replace_int_array('get_body', 2, avatar_add_node.child_value('get_body')) - avatar_add_dict.replace_int_array('get_effect', 2, avatar_add_node.child_value('get_effect')) - avatar_add_dict.replace_int_array('get_object', 2, avatar_add_node.child_value('get_object')) - avatar_add_dict.replace_int_array('get_comment_over', 2, avatar_add_node.child_value('get_comment_over')) - avatar_add_dict.replace_int_array('get_comment_under', 2, avatar_add_node.child_value('get_comment_under')) - avatar_add_dict.replace_int_array('new_hair', 2, avatar_add_node.child_value('new_hair')) - avatar_add_dict.replace_int_array('new_face', 2, avatar_add_node.child_value('new_face')) - avatar_add_dict.replace_int_array('new_body', 2, avatar_add_node.child_value('new_body')) - avatar_add_dict.replace_int_array('new_effect', 2, avatar_add_node.child_value('new_effect')) - avatar_add_dict.replace_int_array('new_object', 2, avatar_add_node.child_value('new_object')) - avatar_add_dict.replace_int_array('new_comment_over', 2, avatar_add_node.child_value('new_comment_over')) - avatar_add_dict.replace_int_array('new_comment_under', 2, avatar_add_node.child_value('new_comment_under')) - newprofile.replace_dict('avatar_add', avatar_add_dict) + avatar_add_dict.replace_int_array( + "get_hair", 2, avatar_add_node.child_value("get_hair") + ) + avatar_add_dict.replace_int_array( + "get_face", 2, avatar_add_node.child_value("get_face") + ) + avatar_add_dict.replace_int_array( + "get_body", 2, avatar_add_node.child_value("get_body") + ) + avatar_add_dict.replace_int_array( + "get_effect", 2, avatar_add_node.child_value("get_effect") + ) + avatar_add_dict.replace_int_array( + "get_object", 2, avatar_add_node.child_value("get_object") + ) + avatar_add_dict.replace_int_array( + "get_comment_over", 2, avatar_add_node.child_value("get_comment_over") + ) + avatar_add_dict.replace_int_array( + "get_comment_under", 2, avatar_add_node.child_value("get_comment_under") + ) + avatar_add_dict.replace_int_array( + "new_hair", 2, avatar_add_node.child_value("new_hair") + ) + avatar_add_dict.replace_int_array( + "new_face", 2, avatar_add_node.child_value("new_face") + ) + avatar_add_dict.replace_int_array( + "new_body", 2, avatar_add_node.child_value("new_body") + ) + avatar_add_dict.replace_int_array( + "new_effect", 2, avatar_add_node.child_value("new_effect") + ) + avatar_add_dict.replace_int_array( + "new_object", 2, avatar_add_node.child_value("new_object") + ) + avatar_add_dict.replace_int_array( + "new_comment_over", 2, avatar_add_node.child_value("new_comment_over") + ) + avatar_add_dict.replace_int_array( + "new_comment_under", 2, avatar_add_node.child_value("new_comment_under") + ) + newprofile.replace_dict("avatar_add", avatar_add_dict) # Keep track of play statistics self.update_play_statistics(userid) # Extract scores for node in request.children: - if node.name == 'stage': - songid = node.child_value('no') + if node.name == "stage": + songid = node.child_value("no") chart = { self.GAME_CHART_TYPE_EASY: self.CHART_TYPE_EASY, self.GAME_CHART_TYPE_NORMAL: self.CHART_TYPE_NORMAL, self.GAME_CHART_TYPE_HYPER: self.CHART_TYPE_HYPER, self.GAME_CHART_TYPE_EX: self.CHART_TYPE_EX, - }[node.child_value('sheet')] - medal = (node.child_value('n_data') >> (chart * 4)) & 0x000F + }[node.child_value("sheet")] + medal = (node.child_value("n_data") >> (chart * 4)) & 0x000F medal = { self.GAME_PLAY_MEDAL_CIRCLE_FAILED: self.PLAY_MEDAL_CIRCLE_FAILED, self.GAME_PLAY_MEDAL_DIAMOND_FAILED: self.PLAY_MEDAL_DIAMOND_FAILED, @@ -481,143 +652,151 @@ class PopnMusicSunnyPark(PopnMusicBase): self.GAME_PLAY_MEDAL_STAR_FULL_COMBO: self.PLAY_MEDAL_STAR_FULL_COMBO, self.GAME_PLAY_MEDAL_PERFECT: self.PLAY_MEDAL_PERFECT, }[medal] - points = node.child_value('score') + points = node.child_value("score") self.update_score(userid, songid, chart, points, medal) return newprofile def handle_game_get_request(self, request: Node) -> Node: game_config = self.get_game_config() - event_phase = game_config.get_int('event_phase') - music_phase = game_config.get_int('music_phase') + event_phase = game_config.get_int("event_phase") + music_phase = game_config.get_int("music_phase") - root = Node.void('game') - root.add_child(Node.s32('ir_phase', 0)) - root.add_child(Node.s32('music_open_phase', music_phase)) - root.add_child(Node.s32('collabo_phase', 8)) - root.add_child(Node.s32('personal_event_phase', event_phase)) - root.add_child(Node.s32('shop_event_phase', 6)) - root.add_child(Node.s32('netvs_phase', 0)) - root.add_child(Node.s32('card_phase', 9)) - root.add_child(Node.s32('other_phase', 9)) - root.add_child(Node.s32('local_matching_enable', 1)) - root.add_child(Node.s32('n_matching_sec', 60)) - root.add_child(Node.s32('l_matching_sec', 60)) - root.add_child(Node.s32('is_check_cpu', 0)) - root.add_child(Node.s32('week_no', 0)) - root.add_child(Node.s16_array('sel_ranking', [-1, -1, -1, -1, -1])) - root.add_child(Node.s16_array('up_ranking', [-1, -1, -1, -1, -1])) + root = Node.void("game") + root.add_child(Node.s32("ir_phase", 0)) + root.add_child(Node.s32("music_open_phase", music_phase)) + root.add_child(Node.s32("collabo_phase", 8)) + root.add_child(Node.s32("personal_event_phase", event_phase)) + root.add_child(Node.s32("shop_event_phase", 6)) + root.add_child(Node.s32("netvs_phase", 0)) + root.add_child(Node.s32("card_phase", 9)) + root.add_child(Node.s32("other_phase", 9)) + root.add_child(Node.s32("local_matching_enable", 1)) + root.add_child(Node.s32("n_matching_sec", 60)) + root.add_child(Node.s32("l_matching_sec", 60)) + root.add_child(Node.s32("is_check_cpu", 0)) + root.add_child(Node.s32("week_no", 0)) + root.add_child(Node.s16_array("sel_ranking", [-1, -1, -1, -1, -1])) + root.add_child(Node.s16_array("up_ranking", [-1, -1, -1, -1, -1])) return root def handle_game_active_request(self, request: Node) -> Node: # Update the name of this cab for admin purposes machine = self.get_machine() - machine.name = request.child_value('shop_name') or machine.name - machine.data.replace_int('pref', request.child_value('pref')) + machine.name = request.child_value("shop_name") or machine.name + machine.data.replace_int("pref", request.child_value("pref")) self.update_machine(machine) - return Node.void('game') + return Node.void("game") def handle_game_taxphase_request(self, request: Node) -> Node: - return Node.void('game') + return Node.void("game") def handle_playerdata_expire_request(self, request: Node) -> Node: - return Node.void('playerdata') + return Node.void("playerdata") def handle_playerdata_logout_request(self, request: Node) -> Node: - return Node.void('playerdata') + return Node.void("playerdata") def handle_playerdata_get_request(self, request: Node) -> Node: - modelstring = request.attribute('model') - refid = request.child_value('ref_id') + modelstring = request.attribute("model") + refid = request.child_value("ref_id") root = self.get_profile_by_refid( refid, self.NEW_PROFILE_ONLY if modelstring is None else self.OLD_PROFILE_ONLY, ) if root is None: - root = Node.void('playerdata') - root.set_attribute('status', str(Status.NO_PROFILE)) + root = Node.void("playerdata") + root.set_attribute("status", str(Status.NO_PROFILE)) return root def handle_playerdata_conversion_request(self, request: Node) -> Node: - refid = request.child_value('ref_id') - name = request.child_value('name') - chara = request.child_value('chara') + refid = request.child_value("ref_id") + name = request.child_value("name") + chara = request.child_value("chara") root = self.new_profile_by_refid(refid, name, chara) if root is None: - root = Node.void('playerdata') - root.set_attribute('status', str(Status.NO_PROFILE)) + root = Node.void("playerdata") + root.set_attribute("status", str(Status.NO_PROFILE)) return root def handle_playerdata_new_request(self, request: Node) -> Node: - refid = request.child_value('ref_id') - name = request.child_value('name') + refid = request.child_value("ref_id") + name = request.child_value("name") root = self.new_profile_by_refid(refid, name) if root is None: - root = Node.void('playerdata') - root.set_attribute('status', str(Status.NO_PROFILE)) + root = Node.void("playerdata") + root.set_attribute("status", str(Status.NO_PROFILE)) return root def handle_playerdata_set_request(self, request: Node) -> Node: - refid = request.attribute('ref_id') + refid = request.attribute("ref_id") machine = self.get_machine() - root = Node.void('playerdata') - root.add_child(Node.s8('pref', machine.data.get_int('pref', self.get_machine_region()))) + root = Node.void("playerdata") + root.add_child( + Node.s8("pref", machine.data.get_int("pref", self.get_machine_region())) + ) if refid is None: - root.add_child(Node.string('name', '')) - root.add_child(Node.s8('get_coupon_cnt', -1)) - root.add_child(Node.s16('chara', -1)) - root.add_child(Node.u8('hair', 0)) - root.add_child(Node.u8('face', 0)) - root.add_child(Node.u8('body', 0)) - root.add_child(Node.u8('effect', 0)) - root.add_child(Node.u8('object', 0)) - root.add_child(Node.u8('comment_1', 0)) - root.add_child(Node.u8('comment_2', 0)) + root.add_child(Node.string("name", "")) + root.add_child(Node.s8("get_coupon_cnt", -1)) + root.add_child(Node.s16("chara", -1)) + root.add_child(Node.u8("hair", 0)) + root.add_child(Node.u8("face", 0)) + root.add_child(Node.u8("body", 0)) + root.add_child(Node.u8("effect", 0)) + root.add_child(Node.u8("object", 0)) + root.add_child(Node.u8("comment_1", 0)) + root.add_child(Node.u8("comment_2", 0)) return root userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is None: - root.add_child(Node.string('name', '')) - root.add_child(Node.s8('get_coupon_cnt', -1)) - root.add_child(Node.s16('chara', -1)) - root.add_child(Node.u8('hair', 0)) - root.add_child(Node.u8('face', 0)) - root.add_child(Node.u8('body', 0)) - root.add_child(Node.u8('effect', 0)) - root.add_child(Node.u8('object', 0)) - root.add_child(Node.u8('comment_1', 0)) - root.add_child(Node.u8('comment_2', 0)) + root.add_child(Node.string("name", "")) + root.add_child(Node.s8("get_coupon_cnt", -1)) + root.add_child(Node.s16("chara", -1)) + root.add_child(Node.u8("hair", 0)) + root.add_child(Node.u8("face", 0)) + root.add_child(Node.u8("body", 0)) + root.add_child(Node.u8("effect", 0)) + root.add_child(Node.u8("object", 0)) + root.add_child(Node.u8("comment_1", 0)) + root.add_child(Node.u8("comment_2", 0)) return root - oldprofile = self.get_profile(userid) or Profile(self.game, self.version, refid, 0) + oldprofile = self.get_profile(userid) or Profile( + self.game, self.version, refid, 0 + ) newprofile = self.unformat_profile(userid, request, oldprofile) if newprofile is not None: self.put_profile(userid, newprofile) - avatar_dict = newprofile.get_dict('avatar') + avatar_dict = newprofile.get_dict("avatar") - root.add_child(Node.string('name', newprofile['name'])) - root.add_child(Node.s8('get_coupon_cnt', -1)) - root.add_child(Node.s16('chara', newprofile.get_int('chara', -1))) - root.add_child(Node.u8('hair', avatar_dict.get_int('hair', 0))) - root.add_child(Node.u8('face', avatar_dict.get_int('face', 0))) - root.add_child(Node.u8('body', avatar_dict.get_int('body', 0))) - root.add_child(Node.u8('effect', avatar_dict.get_int('effect', 0))) - root.add_child(Node.u8('object', avatar_dict.get_int('object', 0))) - root.add_child(Node.u8('comment_1', avatar_dict.get_int_array('comment', 2)[0])) - root.add_child(Node.u8('comment_2', avatar_dict.get_int_array('comment', 2)[1])) + root.add_child(Node.string("name", newprofile["name"])) + root.add_child(Node.s8("get_coupon_cnt", -1)) + root.add_child(Node.s16("chara", newprofile.get_int("chara", -1))) + root.add_child(Node.u8("hair", avatar_dict.get_int("hair", 0))) + root.add_child(Node.u8("face", avatar_dict.get_int("face", 0))) + root.add_child(Node.u8("body", avatar_dict.get_int("body", 0))) + root.add_child(Node.u8("effect", avatar_dict.get_int("effect", 0))) + root.add_child(Node.u8("object", avatar_dict.get_int("object", 0))) + root.add_child( + Node.u8("comment_1", avatar_dict.get_int_array("comment", 2)[0]) + ) + root.add_child( + Node.u8("comment_2", avatar_dict.get_int_array("comment", 2)[1]) + ) return root def handle_playerdata_friend_request(self, request: Node) -> Node: - refid = request.attribute('ref_id') - root = Node.void('playerdata') + refid = request.attribute("ref_id") + root = Node.void("playerdata") # Look up our own user ID based on the RefID provided. userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is None: - root.set_attribute('status', str(Status.NO_PROFILE)) + root.set_attribute("status", str(Status.NO_PROFILE)) return root # Grab the links that we care about. @@ -625,7 +804,7 @@ class PopnMusicSunnyPark(PopnMusicBase): profiles: Dict[UserID, Profile] = {} rivals: List[Link] = [] for link in links: - if link.type != 'rival': + if link.type != "rival": continue other_profile = self.get_profile(link.other_userid) @@ -640,26 +819,30 @@ class PopnMusicSunnyPark(PopnMusicBase): scores = self.data.remote.music.get_scores(self.game, self.version, rivalid) # First, output general profile info. - friend = Node.void('friend') + friend = Node.void("friend") root.add_child(friend) # This might be for having non-active or non-confirmed friends, but setting to 0 makes the # ranking numbers disappear and the player icon show a questionmark. - friend.add_child(Node.s8('open', 1)) + friend.add_child(Node.s8("open", 1)) # Set up some sane defaults. - friend.add_child(Node.string('name', rivalprofile.get_str('name', 'なし'))) - friend.add_child(Node.string('g_pm_id', ID.format_extid(rivalprofile.extid))) - friend.add_child(Node.s16('chara', rivalprofile.get_int('chara', -1))) + friend.add_child(Node.string("name", rivalprofile.get_str("name", "なし"))) + friend.add_child( + Node.string("g_pm_id", ID.format_extid(rivalprofile.extid)) + ) + friend.add_child(Node.s16("chara", rivalprofile.get_int("chara", -1))) # Set up player avatar. - avatar_dict = rivalprofile.get_dict('avatar') - friend.add_child(Node.u8('hair', avatar_dict.get_int('hair', 0))) - friend.add_child(Node.u8('face', avatar_dict.get_int('face', 0))) - friend.add_child(Node.u8('body', avatar_dict.get_int('body', 0))) - friend.add_child(Node.u8('effect', avatar_dict.get_int('effect', 0))) - friend.add_child(Node.u8('object', avatar_dict.get_int('object', 0))) - friend.add_child(Node.u8_array('comment', avatar_dict.get_int_array('comment', 2))) + avatar_dict = rivalprofile.get_dict("avatar") + friend.add_child(Node.u8("hair", avatar_dict.get_int("hair", 0))) + friend.add_child(Node.u8("face", avatar_dict.get_int("face", 0))) + friend.add_child(Node.u8("body", avatar_dict.get_int("body", 0))) + friend.add_child(Node.u8("effect", avatar_dict.get_int("effect", 0))) + friend.add_child(Node.u8("object", avatar_dict.get_int("object", 0))) + friend.add_child( + Node.u8_array("comment", avatar_dict.get_int_array("comment", 2)) + ) # Perform hiscore/medal conversion. hiscore_array = [0] * int((((self.GAME_MAX_MUSIC_ID * 4) * 17) + 7) / 8) @@ -676,10 +859,12 @@ class PopnMusicSunnyPark(PopnMusicBase): self.CHART_TYPE_EX, ]: continue - if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY: + if score.data.get_int("medal") == self.PLAY_MEDAL_NO_PLAY: continue - clear_medal[score.id] = clear_medal[score.id] | self.__format_medal_for_score(score) + clear_medal[score.id] = clear_medal[ + score.id + ] | self.__format_medal_for_score(score) hiscore_index = (score.id * 4) + { self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY_POSITION, self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL_POSITION, @@ -689,16 +874,22 @@ class PopnMusicSunnyPark(PopnMusicBase): hiscore_byte_pos = int((hiscore_index * 17) / 8) hiscore_bit_pos = int((hiscore_index * 17) % 8) hiscore_value = score.points << hiscore_bit_pos - hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (hiscore_value & 0xFF) - hiscore_array[hiscore_byte_pos + 1] = hiscore_array[hiscore_byte_pos + 1] | ((hiscore_value >> 8) & 0xFF) - hiscore_array[hiscore_byte_pos + 2] = hiscore_array[hiscore_byte_pos + 2] | ((hiscore_value >> 16) & 0xFF) + hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | ( + hiscore_value & 0xFF + ) + hiscore_array[hiscore_byte_pos + 1] = hiscore_array[ + hiscore_byte_pos + 1 + ] | ((hiscore_value >> 8) & 0xFF) + hiscore_array[hiscore_byte_pos + 2] = hiscore_array[ + hiscore_byte_pos + 2 + ] | ((hiscore_value >> 16) & 0xFF) hiscore = bytes(hiscore_array) - friend.add_child(Node.u16_array('clear_medal', clear_medal)) - friend.add_child(Node.binary('hiscore', hiscore)) + friend.add_child(Node.u16_array("clear_medal", clear_medal)) + friend.add_child(Node.binary("hiscore", hiscore)) return root def handle_lobby_requests(self, request: Node) -> Node: # Stub out the entire lobby service - return Node.void('lobby') + return Node.void("lobby") diff --git a/bemani/backend/popn/tunestreet.py b/bemani/backend/popn/tunestreet.py index 1e257b4..073f3ae 100644 --- a/bemani/backend/popn/tunestreet.py +++ b/bemani/backend/popn/tunestreet.py @@ -66,64 +66,64 @@ class PopnMusicTuneStreet(PopnMusicBase): Return all of our front-end modifiably settings. """ return { - 'ints': [ + "ints": [ { - 'name': 'Game Phase', - 'tip': 'Game unlock phase for all players.', - 'category': 'game_config', - 'setting': 'game_phase', - 'values': { - 0: 'NO PHASE', - 1: 'SECRET DATA RELEASE', - 2: 'MAX: ALL DATA RELEASE', - } + "name": "Game Phase", + "tip": "Game unlock phase for all players.", + "category": "game_config", + "setting": "game_phase", + "values": { + 0: "NO PHASE", + 1: "SECRET DATA RELEASE", + 2: "MAX: ALL DATA RELEASE", + }, }, { - 'name': 'Town Mode Phase', - 'tip': 'Town mode phase for all players.', - 'category': 'game_config', - 'setting': 'town_phase', - 'values': { - 0: 'town mode disabled', - 1: 'town phase 1', - 2: 'town phase 2', - 3: 'Pop\'n Naan Festival', + "name": "Town Mode Phase", + "tip": "Town mode phase for all players.", + "category": "game_config", + "setting": "town_phase", + "values": { + 0: "town mode disabled", + 1: "town phase 1", + 2: "town phase 2", + 3: "Pop'n Naan Festival", # 4 seems to be a continuation of town phase 2. Intentionally leaving it out. - 5: 'town phase 3', - 6: 'town phase 4', - 7: 'Miracle 4 + 1', + 5: "town phase 3", + 6: "town phase 4", + 7: "Miracle 4 + 1", # 8 seems to be a continuation of town phase 4. Intentionally leaving it out. - 9: 'town phase MAX', - 10: 'Find your daughter!', + 9: "town phase MAX", + 10: "Find your daughter!", # 11 is a continuation of phase MAX after find your daughter, with Tanabata # bamboo grass added as well. - 11: 'town phase MAX+1', - 12: 'Peruri-san visits', + 11: "town phase MAX+1", + 12: "Peruri-san visits", # 13 is a continuation of phase MAX+1 after peruri-san visits, with Watermelon # pattern tank added as well. - 13: 'town phase MAX+2', - 14: 'Find Deuil!', + 13: "town phase MAX+2", + 14: "Find Deuil!", # 15 is a continuation of phase MAX+2 after find deuil, with Tsukimi dumplings # added as well. - 15: 'town phase MAX+3', - 16: 'Landmark stamp rally', + 15: "town phase MAX+3", + 16: "Landmark stamp rally", # 17 is a continuation of MAX+3 after landmark stamp rally ends, but offering # no additional stuff. - } + }, }, ], - 'bools': [ + "bools": [ { - 'name': 'Force Song Unlock', - 'tip': 'Force unlock all songs.', - 'category': 'game_config', - 'setting': 'force_unlock_songs', + "name": "Force Song Unlock", + "tip": "Force unlock all songs.", + "category": "game_config", + "setting": "force_unlock_songs", }, { - 'name': 'Force Customization Unlock', - 'tip': 'Force unlock all theme and menu customizations.', - 'category': 'game_config', - 'setting': 'force_unlock_customizations', + "name": "Force Customization Unlock", + "tip": "Force unlock all theme and menu customizations.", + "category": "game_config", + "setting": "force_unlock_customizations", }, ], } @@ -166,11 +166,11 @@ class PopnMusicTuneStreet(PopnMusicBase): self.PLAY_MEDAL_DIAMOND_FULL_COMBO: self.GAME_PLAY_FLAG_FULL_COMBO, self.PLAY_MEDAL_STAR_FULL_COMBO: self.GAME_PLAY_FLAG_FULL_COMBO, self.PLAY_MEDAL_PERFECT: self.GAME_PLAY_FLAG_PERFECT_COMBO, - }[score.data.get_int('medal')] + }[score.data.get_int("medal")] return (flags << shift) | playedflag def format_profile(self, userid: UserID, profile: Profile) -> Node: - root = Node.void('playerdata') + root = Node.void("playerdata") # Format profile binary_profile = [0] * 2198 @@ -178,7 +178,7 @@ class PopnMusicTuneStreet(PopnMusicBase): # Copy name. We intentionally leave location 12 alone as it is # the null termination for the name if it happens to be 12 # characters (6 shift-jis kana). - name_binary = profile.get_str('name', 'なし').encode('shift-jis')[0:12] + name_binary = profile.get_str("name", "なし").encode("shift-jis")[0:12] for name_pos, byte in enumerate(name_binary): binary_profile[name_pos] = byte @@ -199,32 +199,32 @@ class PopnMusicTuneStreet(PopnMusicBase): 13: 5, 14: 5, 15: 5, - }[profile.get_int('play_mode')] + }[profile.get_int("play_mode")] # Copy miscelaneous values - binary_profile[15] = profile.get_int('last_play_flag') & 0xFF - binary_profile[16] = profile.get_int('medal_and_friend') & 0xFF - binary_profile[37] = profile.get_int('read_news') & 0xFF - binary_profile[38] = profile.get_int('skin_tex_note') & 0xFF - binary_profile[39] = profile.get_int('skin_tex_cmn') & 0xFF - binary_profile[40] = profile.get_int('skin_sd_bgm') & 0xFF - binary_profile[41] = profile.get_int('skin_sd_se') & 0xFF - binary_profile[44] = profile.get_int('option') & 0xFF - binary_profile[45] = (profile.get_int('option') >> 8) & 0xFF - binary_profile[46] = (profile.get_int('option') >> 16) & 0xFF - binary_profile[47] = (profile.get_int('option') >> 24) & 0xFF - binary_profile[48] = profile.get_int('jubeat_collabo') & 0xFF - binary_profile[49] = (profile.get_int('jubeat_collabo') >> 8) & 0xFF + binary_profile[15] = profile.get_int("last_play_flag") & 0xFF + binary_profile[16] = profile.get_int("medal_and_friend") & 0xFF + binary_profile[37] = profile.get_int("read_news") & 0xFF + binary_profile[38] = profile.get_int("skin_tex_note") & 0xFF + binary_profile[39] = profile.get_int("skin_tex_cmn") & 0xFF + binary_profile[40] = profile.get_int("skin_sd_bgm") & 0xFF + binary_profile[41] = profile.get_int("skin_sd_se") & 0xFF + binary_profile[44] = profile.get_int("option") & 0xFF + binary_profile[45] = (profile.get_int("option") >> 8) & 0xFF + binary_profile[46] = (profile.get_int("option") >> 16) & 0xFF + binary_profile[47] = (profile.get_int("option") >> 24) & 0xFF + binary_profile[48] = profile.get_int("jubeat_collabo") & 0xFF + binary_profile[49] = (profile.get_int("jubeat_collabo") >> 8) & 0xFF # 52-56 and 56-60 make up two 32 bit colors found in color_3p_flag. - binary_profile[60] = profile.get_int('chara', -1) & 0xFF - binary_profile[61] = (profile.get_int('chara', -1) >> 8) & 0xFF - binary_profile[62] = profile.get_int('music') & 0xFF - binary_profile[63] = (profile.get_int('music') >> 8) & 0xFF - binary_profile[64] = profile.get_int('sheet') & 0xFF - binary_profile[65] = profile.get_int('category') & 0xFF - binary_profile[66] = profile.get_int('norma_point') & 0xFF - binary_profile[67] = (profile.get_int('norma_point') >> 8) & 0xFF + binary_profile[60] = profile.get_int("chara", -1) & 0xFF + binary_profile[61] = (profile.get_int("chara", -1) >> 8) & 0xFF + binary_profile[62] = profile.get_int("music") & 0xFF + binary_profile[63] = (profile.get_int("music") >> 8) & 0xFF + binary_profile[64] = profile.get_int("sheet") & 0xFF + binary_profile[65] = profile.get_int("category") & 0xFF + binary_profile[66] = profile.get_int("norma_point") & 0xFF + binary_profile[67] = (profile.get_int("norma_point") >> 8) & 0xFF # Format Scores hiscore_array = [0] * int((((self.GAME_MAX_MUSIC_ID * 7) * 17) + 7) / 8) @@ -238,14 +238,18 @@ class PopnMusicTuneStreet(PopnMusicBase): self.CHART_TYPE_EASY, ]: continue - if score.data.get_int('medal') == self.PLAY_MEDAL_NO_PLAY: + if score.data.get_int("medal") == self.PLAY_MEDAL_NO_PLAY: continue flags = self.__format_flags_for_score(score) flags_index = score.id * 2 - binary_profile[108 + flags_index] = binary_profile[108 + flags_index] | (flags & 0xFF) - binary_profile[109 + flags_index] = binary_profile[109 + flags_index] | ((flags >> 8) & 0xFF) + binary_profile[108 + flags_index] = binary_profile[108 + flags_index] | ( + flags & 0xFF + ) + binary_profile[109 + flags_index] = binary_profile[109 + flags_index] | ( + (flags >> 8) & 0xFF + ) if score.chart in [ self.CHART_TYPE_ENJOY_5_BUTTON, @@ -267,12 +271,23 @@ class PopnMusicTuneStreet(PopnMusicBase): hiscore_byte_pos = int((hiscore_index * 17) / 8) hiscore_bit_pos = int((hiscore_index * 17) % 8) hiscore_value = score.points << hiscore_bit_pos - hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (hiscore_value & 0xFF) - hiscore_array[hiscore_byte_pos + 1] = hiscore_array[hiscore_byte_pos + 1] | ((hiscore_value >> 8) & 0xFF) - hiscore_array[hiscore_byte_pos + 2] = hiscore_array[hiscore_byte_pos + 2] | ((hiscore_value >> 16) & 0xFF) + hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | ( + hiscore_value & 0xFF + ) + hiscore_array[hiscore_byte_pos + 1] = hiscore_array[ + hiscore_byte_pos + 1 + ] | ((hiscore_value >> 8) & 0xFF) + hiscore_array[hiscore_byte_pos + 2] = hiscore_array[ + hiscore_byte_pos + 2 + ] | ((hiscore_value >> 16) & 0xFF) # Format most played - most_played = [x[0] for x in self.data.local.music.get_most_played(self.game, self.version, userid, 20)] + most_played = [ + x[0] + for x in self.data.local.music.get_most_played( + self.game, self.version, userid, 20 + ) + ] while len(most_played) < 20: most_played.append(-1) profile_pos = 68 @@ -291,22 +306,24 @@ class PopnMusicTuneStreet(PopnMusicBase): # - 10 appears to be purchased BGMs. # - 11 appears to be purchased sound effects. binary_town = [0] * 141 - town = profile.get_dict('town') + town = profile.get_dict("town") # Last play flag, so the selection for 5/9/9+cool sticks. - binary_town[140] = town.get_int('play_type') + binary_town[140] = town.get_int("play_type") # Fill in basic town points, tracked here and returned in basic profile for some reason. - binary_town[0] = town.get_int('points') & 0xFF - binary_town[1] = (town.get_int('points') >> 8) & 0xFF - binary_town[2] = (town.get_int('points') >> 16) & 0xFF - binary_town[3] = (town.get_int('points') >> 24) & 0xFF + binary_town[0] = town.get_int("points") & 0xFF + binary_town[1] = (town.get_int("points") >> 8) & 0xFF + binary_town[2] = (town.get_int("points") >> 16) & 0xFF + binary_town[3] = (town.get_int("points") >> 24) & 0xFF # Fill in purchase flags (this is for stuff like BGMs, SEs, Pop-kun customizations, etc). - bought_flg = town.get_int_array('bought_flg', 3) + bought_flg = town.get_int_array("bought_flg", 3) game_config = self.get_game_config() - force_unlock_songs = game_config.get_bool('force_unlock_songs') - force_unlock_customizations = game_config.get_bool('force_unlock_customizations') + force_unlock_songs = game_config.get_bool("force_unlock_songs") + force_unlock_customizations = game_config.get_bool( + "force_unlock_customizations" + ) if force_unlock_songs: bought_flg[0] = 0xFFFFFFFF @@ -320,7 +337,7 @@ class PopnMusicTuneStreet(PopnMusicBase): binary_town[off + 3] = (bought_flg[flg] >> 24) & 0xFF # Fill in build flags (presumably for what parcels of land have been bought and built on). - build_flg = town.get_int_array('build_flg', 8) + build_flg = town.get_int_array("build_flg", 8) for flg, off in enumerate([16, 20, 24, 28, 32, 36, 40, 44]): binary_town[off + 0] = build_flg[flg] & 0xFF binary_town[off + 1] = (build_flg[flg] >> 8) & 0xFF @@ -328,15 +345,37 @@ class PopnMusicTuneStreet(PopnMusicBase): binary_town[off + 3] = (build_flg[flg] >> 24) & 0xFF # Fill in character flags (presumably for character location, orientation, stats, etc). - chara_flg = town.get_int_array('chara_flg', 19) - for flg, off in enumerate([48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120]): + chara_flg = town.get_int_array("chara_flg", 19) + for flg, off in enumerate( + [ + 48, + 52, + 56, + 60, + 64, + 68, + 72, + 76, + 80, + 84, + 88, + 92, + 96, + 100, + 104, + 108, + 112, + 116, + 120, + ] + ): binary_town[off + 0] = chara_flg[flg] & 0xFF binary_town[off + 1] = (chara_flg[flg] >> 8) & 0xFF binary_town[off + 2] = (chara_flg[flg] >> 16) & 0xFF binary_town[off + 3] = (chara_flg[flg] >> 24) & 0xFF # Fill in miscellaneous event flags. - event_flg = town.get_int_array('event_flg', 4) + event_flg = town.get_int_array("event_flg", 4) for flg, off in enumerate([124, 128, 132, 136]): binary_town[off + 0] = event_flg[flg] & 0xFF binary_town[off + 1] = (event_flg[flg] >> 8) & 0xFF @@ -344,58 +383,72 @@ class PopnMusicTuneStreet(PopnMusicBase): binary_town[off + 3] = (event_flg[flg] >> 24) & 0xFF # Construct final profile - root.add_child(Node.binary('b', bytes(binary_profile))) - root.add_child(Node.binary('hiscore', bytes(hiscore_array))) - root.add_child(Node.binary('town', bytes(binary_town))) + root.add_child(Node.binary("b", bytes(binary_profile))) + root.add_child(Node.binary("hiscore", bytes(hiscore_array))) + root.add_child(Node.binary("town", bytes(binary_town))) return root - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() # Extract the playmode, important for scores later - playmode = int(request.attribute('play_mode')) - newprofile.replace_int('play_mode', playmode) + playmode = int(request.attribute("play_mode")) + newprofile.replace_int("play_mode", playmode) # Extract profile options - newprofile.replace_int('chara', int(request.attribute('chara_num'))) - if 'option' in request.attributes: - newprofile.replace_int('option', int(request.attribute('option'))) - if 'last_play_flag' in request.attributes: - newprofile.replace_int('last_play_flag', int(request.attribute('last_play_flag'))) - if 'medal_and_friend' in request.attributes: - newprofile.replace_int('medal_and_friend', int(request.attribute('medal_and_friend'))) - if 'music_num' in request.attributes: - newprofile.replace_int('music', int(request.attribute('music_num'))) - if 'sheet_num' in request.attributes: - newprofile.replace_int('sheet', int(request.attribute('sheet_num'))) - if 'category_num' in request.attributes: - newprofile.replace_int('category', int(request.attribute('category_num'))) - if 'read_news_no_max' in request.attributes: - newprofile.replace_int('read_news', int(request.attribute('read_news_no_max'))) - if 'jubeat_collabo' in request.attributes: - newprofile.replace_int('jubeat_collabo', int(request.attribute('jubeat_collabo'))) - if 'norma_point' in request.attributes: - newprofile.replace_int('norma_point', int(request.attribute('norma_point'))) - if 'skin_tex_note' in request.attributes: - newprofile.replace_int('skin_tex_note', int(request.attribute('skin_tex_note'))) - if 'skin_tex_cmn' in request.attributes: - newprofile.replace_int('skin_tex_cmn', int(request.attribute('skin_tex_cmn'))) - if 'skin_sd_bgm' in request.attributes: - newprofile.replace_int('skin_sd_bgm', int(request.attribute('skin_sd_bgm'))) - if 'skin_sd_se' in request.attributes: - newprofile.replace_int('skin_sd_se', int(request.attribute('skin_sd_se'))) + newprofile.replace_int("chara", int(request.attribute("chara_num"))) + if "option" in request.attributes: + newprofile.replace_int("option", int(request.attribute("option"))) + if "last_play_flag" in request.attributes: + newprofile.replace_int( + "last_play_flag", int(request.attribute("last_play_flag")) + ) + if "medal_and_friend" in request.attributes: + newprofile.replace_int( + "medal_and_friend", int(request.attribute("medal_and_friend")) + ) + if "music_num" in request.attributes: + newprofile.replace_int("music", int(request.attribute("music_num"))) + if "sheet_num" in request.attributes: + newprofile.replace_int("sheet", int(request.attribute("sheet_num"))) + if "category_num" in request.attributes: + newprofile.replace_int("category", int(request.attribute("category_num"))) + if "read_news_no_max" in request.attributes: + newprofile.replace_int( + "read_news", int(request.attribute("read_news_no_max")) + ) + if "jubeat_collabo" in request.attributes: + newprofile.replace_int( + "jubeat_collabo", int(request.attribute("jubeat_collabo")) + ) + if "norma_point" in request.attributes: + newprofile.replace_int("norma_point", int(request.attribute("norma_point"))) + if "skin_tex_note" in request.attributes: + newprofile.replace_int( + "skin_tex_note", int(request.attribute("skin_tex_note")) + ) + if "skin_tex_cmn" in request.attributes: + newprofile.replace_int( + "skin_tex_cmn", int(request.attribute("skin_tex_cmn")) + ) + if "skin_sd_bgm" in request.attributes: + newprofile.replace_int("skin_sd_bgm", int(request.attribute("skin_sd_bgm"))) + if "skin_sd_se" in request.attributes: + newprofile.replace_int("skin_sd_se", int(request.attribute("skin_sd_se"))) # Keep track of play statistics self.update_play_statistics(userid) # Extract scores for node in request.children: - if node.name == 'music': - songid = int(node.attribute('music_num')) - chart = int(node.attribute('sheet_num')) - points = int(node.attribute('score')) - data = int(node.attribute('data')) + if node.name == "music": + songid = int(node.attribute("music_num")) + chart = int(node.attribute("sheet_num")) + points = int(node.attribute("score")) + data = int(node.attribute("data")) # We never save battle scores if chart in [ @@ -405,7 +458,10 @@ class PopnMusicTuneStreet(PopnMusicBase): continue # Arrange order to be compatible with future mixes - if playmode in {self.GAME_PLAY_MODE_CHO_CHALLENGE, self.GAME_PLAY_MODE_TOWN_CHO_CHALLENGE}: + if playmode in { + self.GAME_PLAY_MODE_CHO_CHALLENGE, + self.GAME_PLAY_MODE_TOWN_CHO_CHALLENGE, + }: if chart in [ self.GAME_CHART_TYPE_5_BUTTON, self.GAME_CHART_TYPE_ENJOY_5_BUTTON, @@ -463,26 +519,32 @@ class PopnMusicTuneStreet(PopnMusicBase): self.update_score(userid, songid, chart, points, medal) # Update town mode data. - town = newprofile.get_dict('town') + town = newprofile.get_dict("town") # Basic stuff that's in the base node for no reason? - if 'tp' in request.attributes: - town.replace_int('points', int(request.attribute('tp'))) + if "tp" in request.attributes: + town.replace_int("points", int(request.attribute("tp"))) # Stuff that is in the town node - townnode = request.child('town') + townnode = request.child("town") if townnode is not None: - if 'play_type' in townnode.attributes: - town.replace_int('play_type', int(townnode.attribute('play_type'))) - if 'base' in townnode.attributes: - town.replace_int_array('base', 4, [int(x) for x in townnode.attribute('base').split(',')]) - if 'bought_flg' in townnode.attributes: - bought_array = [int(x) for x in townnode.attribute('bought_flg').split(',')] + if "play_type" in townnode.attributes: + town.replace_int("play_type", int(townnode.attribute("play_type"))) + if "base" in townnode.attributes: + town.replace_int_array( + "base", 4, [int(x) for x in townnode.attribute("base").split(",")] + ) + if "bought_flg" in townnode.attributes: + bought_array = [ + int(x) for x in townnode.attribute("bought_flg").split(",") + ] if len(bought_array) == 3: game_config = self.get_game_config() - force_unlock_songs = game_config.get_bool('force_unlock_songs') - force_unlock_customizations = game_config.get_bool('force_unlock_customizations') - old_bought_array = town.get_int_array('bought_flg', 3) + force_unlock_songs = game_config.get_bool("force_unlock_songs") + force_unlock_customizations = game_config.get_bool( + "force_unlock_customizations" + ) + old_bought_array = town.get_int_array("bought_flg", 3) if force_unlock_songs: # Don't save force unlocked flags, it'll clobber the profile. @@ -491,66 +553,91 @@ class PopnMusicTuneStreet(PopnMusicBase): # Don't save force unlocked flags, it'll clobber the profile. bought_array[1] = old_bought_array[1] - town.replace_int_array('bought_flg', 3, bought_array) - if 'build_flg' in townnode.attributes: - town.replace_int_array('build_flg', 8, [int(x) for x in townnode.attribute('build_flg').split(',')]) - if 'chara_flg' in townnode.attributes: - town.replace_int_array('chara_flg', 19, [int(x) for x in townnode.attribute('chara_flg').split(',')]) - if 'event_flg' in townnode.attributes: - town.replace_int_array('event_flg', 4, [int(x) for x in townnode.attribute('event_flg').split(',')]) + town.replace_int_array("bought_flg", 3, bought_array) + if "build_flg" in townnode.attributes: + town.replace_int_array( + "build_flg", + 8, + [int(x) for x in townnode.attribute("build_flg").split(",")], + ) + if "chara_flg" in townnode.attributes: + town.replace_int_array( + "chara_flg", + 19, + [int(x) for x in townnode.attribute("chara_flg").split(",")], + ) + if "event_flg" in townnode.attributes: + town.replace_int_array( + "event_flg", + 4, + [int(x) for x in townnode.attribute("event_flg").split(",")], + ) for bid in range(8): - if f'building_{bid}' in townnode.attributes: - town.replace_int_array(f'building_{bid}', 8, [int(x) for x in townnode.attribute(f'building_{bid}').split(',')]) + if f"building_{bid}" in townnode.attributes: + town.replace_int_array( + f"building_{bid}", + 8, + [ + int(x) + for x in townnode.attribute(f"building_{bid}").split(",") + ], + ) - newprofile.replace_dict('town', town) + newprofile.replace_dict("town", town) return newprofile def handle_game_get_request(self, request: Node) -> Node: game_config = self.get_game_config() - game_phase = game_config.get_int('game_phase') - town_phase = game_config.get_int('town_phase') + game_phase = game_config.get_int("game_phase") + town_phase = game_config.get_int("town_phase") - root = Node.void('game') - root.set_attribute('game_phase', str(game_phase)) # Phase unlocks, for song availability. - root.set_attribute('boss_battle_point', '1') - root.set_attribute('boss_diff', '100,100,100,100,100,100,100,100,100,100') - root.set_attribute('card_phase', '3') - root.set_attribute('event_phase', str(town_phase)) # Town mode, for the main event. - root.set_attribute('gfdm_phase', '2') - root.set_attribute('ir_phase', '14') - root.set_attribute('jubeat_phase', '2') - root.set_attribute('local_matching_enable', '1') - root.set_attribute('matching_sec', '120') - root.set_attribute('netvs_phase', '0') # Net taisen mode phase, maximum 18 (no lobby support). + root = Node.void("game") + root.set_attribute( + "game_phase", str(game_phase) + ) # Phase unlocks, for song availability. + root.set_attribute("boss_battle_point", "1") + root.set_attribute("boss_diff", "100,100,100,100,100,100,100,100,100,100") + root.set_attribute("card_phase", "3") + root.set_attribute( + "event_phase", str(town_phase) + ) # Town mode, for the main event. + root.set_attribute("gfdm_phase", "2") + root.set_attribute("ir_phase", "14") + root.set_attribute("jubeat_phase", "2") + root.set_attribute("local_matching_enable", "1") + root.set_attribute("matching_sec", "120") + root.set_attribute( + "netvs_phase", "0" + ) # Net taisen mode phase, maximum 18 (no lobby support). return root def handle_game_active_request(self, request: Node) -> Node: # Update the name of this cab for admin purposes - self.update_machine_name(request.attribute('shop_name')) - return Node.void('game') + self.update_machine_name(request.attribute("shop_name")) + return Node.void("game") def handle_playerdata_expire_request(self, request: Node) -> Node: - return Node.void('playerdata') + return Node.void("playerdata") def handle_playerdata_logout_request(self, request: Node) -> Node: - return Node.void('playerdata') + return Node.void("playerdata") def handle_playerdata_get_request(self, request: Node) -> Node: - modelstring = request.attribute('model') - refid = request.attribute('ref_id') + modelstring = request.attribute("model") + refid = request.attribute("ref_id") root = self.get_profile_by_refid( refid, self.NEW_PROFILE_ONLY if modelstring is None else self.OLD_PROFILE_ONLY, ) if root is None: - root = Node.void('playerdata') - root.set_attribute('status', str(Status.NO_PROFILE)) + root = Node.void("playerdata") + root.set_attribute("status", str(Status.NO_PROFILE)) return root def handle_playerdata_town_request(self, request: Node) -> Node: - refid = request.attribute('ref_id') - root = Node.void('playerdata') + refid = request.attribute("ref_id") + root = Node.void("playerdata") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is None: @@ -560,11 +647,11 @@ class PopnMusicTuneStreet(PopnMusicBase): if profile is None: return root - town = profile.get_dict('town') + town = profile.get_dict("town") - residence = Node.void('residence') + residence = Node.void("residence") root.add_child(residence) - residence.set_attribute('id', str(town.get_int('residence'))) + residence.set_attribute("id", str(town.get_int("residence"))) # It appears there can be up to 9 map nodes, not sure why. I'm only returning the # first one. Perhaps if there's multiple towns, the residence ID lets you choose @@ -572,35 +659,35 @@ class PopnMusicTuneStreet(PopnMusicBase): mapdata = [0] * 180 # Map over progress for base and buildings. Positions 173-176 are for base flags. - base = town.get_int_array('base', 4) + base = town.get_int_array("base", 4) for i in range(4): mapdata[173 + i] = base[i] # Positions 42-105 are for building flags. for bid, start in enumerate([42, 50, 58, 66, 74, 82, 90, 98]): - building = town.get_int_array(f'building_{bid}', 8) + building = town.get_int_array(f"building_{bid}", 8) for i in range(8): mapdata[start + i] = building[i] - mapnode = Node.binary('map', bytes(mapdata)) + mapnode = Node.binary("map", bytes(mapdata)) root.add_child(mapnode) - mapnode.set_attribute('residence', '0') + mapnode.set_attribute("residence", "0") return root def handle_playerdata_new_request(self, request: Node) -> Node: - refid = request.attribute('ref_id') - name = request.attribute('name') + refid = request.attribute("ref_id") + name = request.attribute("name") root = self.new_profile_by_refid(refid, name) if root is None: - root = Node.void('playerdata') - root.set_attribute('status', str(Status.NO_PROFILE)) + root = Node.void("playerdata") + root.set_attribute("status", str(Status.NO_PROFILE)) return root def handle_playerdata_set_request(self, request: Node) -> Node: - refid = request.attribute('ref_id') + refid = request.attribute("ref_id") - root = Node.void('playerdata') + root = Node.void("playerdata") if refid is None: return root @@ -608,7 +695,9 @@ class PopnMusicTuneStreet(PopnMusicBase): if userid is None: return root - oldprofile = self.get_profile(userid) or Profile(self.game, self.version, refid, 0) + oldprofile = self.get_profile(userid) or Profile( + self.game, self.version, refid, 0 + ) newprofile = self.unformat_profile(userid, request, oldprofile) if newprofile is not None: @@ -618,4 +707,4 @@ class PopnMusicTuneStreet(PopnMusicBase): def handle_lobby_requests(self, request: Node) -> Node: # Stub out the entire lobby service - return Node.void('lobby') + return Node.void("lobby") diff --git a/bemani/backend/popn/usaneko.py b/bemani/backend/popn/usaneko.py index 5adcb94..0512956 100644 --- a/bemani/backend/popn/usaneko.py +++ b/bemani/backend/popn/usaneko.py @@ -36,76 +36,76 @@ class PopnMusicUsaNeko(PopnMusicModernBase): Return all of our front-end modifiably settings. """ return { - 'ints': [ + "ints": [ { - 'name': 'Music Open Phase', - 'tip': 'Default music phase for all players.', - 'category': 'game_config', - 'setting': 'music_phase', - 'values': { - 0: 'No music unlocks', - 1: 'Phase 1', - 2: 'Phase 2', - 3: 'Phase 3', - 4: 'Phase 4', - 5: 'Phase 5', - 6: 'Phase 6', - 7: 'Phase 7', - 8: 'Phase 8', - 9: 'Phase 9', - 10: 'Phase 10', - 11: 'Phase MAX', - } + "name": "Music Open Phase", + "tip": "Default music phase for all players.", + "category": "game_config", + "setting": "music_phase", + "values": { + 0: "No music unlocks", + 1: "Phase 1", + 2: "Phase 2", + 3: "Phase 3", + 4: "Phase 4", + 5: "Phase 5", + 6: "Phase 6", + 7: "Phase 7", + 8: "Phase 8", + 9: "Phase 9", + 10: "Phase 10", + 11: "Phase MAX", + }, }, { - 'name': 'NAVI-Kun Event Phase', - 'tip': 'NAVI-Kun event phase for all players.', - 'category': 'game_config', - 'setting': 'navikun_phase', - 'values': { - 0: 'Phase 1', - 1: 'Phase 2', - 2: 'Phase 3', - 3: 'Phase 4', - 4: 'Phase 5', - 5: 'Phase 6', - 6: 'Phase 7', - 7: 'Phase 8', - 8: 'Phase 9', - 9: 'Phase 10', - 10: 'Phase 11', - 11: 'Phase 12', - 12: 'Phase 13', - 13: 'Phase 14', - 14: 'Phase 15', - 15: 'Phase MAX', + "name": "NAVI-Kun Event Phase", + "tip": "NAVI-Kun event phase for all players.", + "category": "game_config", + "setting": "navikun_phase", + "values": { + 0: "Phase 1", + 1: "Phase 2", + 2: "Phase 3", + 3: "Phase 4", + 4: "Phase 5", + 5: "Phase 6", + 6: "Phase 7", + 7: "Phase 8", + 8: "Phase 9", + 9: "Phase 10", + 10: "Phase 11", + 11: "Phase 12", + 12: "Phase 13", + 13: "Phase 14", + 14: "Phase 15", + 15: "Phase MAX", }, }, { # For festive times, it's possible to change the welcome greeting. I'm not sure why you would want to change this, but now you can. - 'name': 'Holiday Greeting', - 'tip': 'Changes the payment selection confirmation sound.', - 'category': 'game_config', - 'setting': 'holiday_greeting', - 'values': { - 0: 'Okay!', - 1: 'Merry Christmas!', - 2: 'Happy New Year!', - } + "name": "Holiday Greeting", + "tip": "Changes the payment selection confirmation sound.", + "category": "game_config", + "setting": "holiday_greeting", + "values": { + 0: "Okay!", + 1: "Merry Christmas!", + 2: "Happy New Year!", + }, }, { - 'name': 'Active Event', - 'tip': 'Active event for all players.', - 'category': 'game_config', - 'setting': 'active_event', - 'values': { - 0: 'No event', - 1: 'NAVI-Kun event', - 2: 'Daily Mission event', + "name": "Active Event", + "tip": "Active event for all players.", + "category": "game_config", + "setting": "active_event", + "values": { + 0: "No event", + 1: "NAVI-Kun event", + 2: "Daily Mission event", }, }, ], - 'bools': [ + "bools": [ # We don't currently support lobbies or anything, so this is commented out until # somebody gets around to implementing it. # { @@ -115,20 +115,20 @@ class PopnMusicUsaNeko(PopnMusicModernBase): # 'setting': 'enable_net_taisen', # }, { - 'name': 'Force Song Unlock', - 'tip': 'Force unlock all songs.', - 'category': 'game_config', - 'setting': 'force_unlock_songs', + "name": "Force Song Unlock", + "tip": "Force unlock all songs.", + "category": "game_config", + "setting": "force_unlock_songs", }, ], } def get_common_config(self) -> Tuple[Dict[int, int], bool]: game_config = self.get_game_config() - music_phase = game_config.get_int('music_phase') - holiday_greeting = game_config.get_int('holiday_greeting') - active_event = game_config.get_int('active_event') - navikun_phase = game_config.get_int('navikun_phase') + music_phase = game_config.get_int("music_phase") + holiday_greeting = game_config.get_int("holiday_greeting") + active_event = game_config.get_int("active_event") + navikun_phase = game_config.get_int("navikun_phase") enable_net_taisen = False # game_config.get_bool('enable_net_taisen') navikun_enabled = active_event == 1 diff --git a/bemani/backend/reflec/base.py b/bemani/backend/reflec/base.py index cda785f..23828e3 100644 --- a/bemani/backend/reflec/base.py +++ b/bemani/backend/reflec/base.py @@ -26,23 +26,31 @@ class ReflecBeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): CLEAR_TYPE_NO_PLAY: Final[int] = DBConstants.REFLEC_BEAT_CLEAR_TYPE_NO_PLAY CLEAR_TYPE_FAILED: Final[int] = DBConstants.REFLEC_BEAT_CLEAR_TYPE_FAILED CLEAR_TYPE_CLEARED: Final[int] = DBConstants.REFLEC_BEAT_CLEAR_TYPE_CLEARED - CLEAR_TYPE_HARD_CLEARED: Final[int] = DBConstants.REFLEC_BEAT_CLEAR_TYPE_HARD_CLEARED - CLEAR_TYPE_S_HARD_CLEARED: Final[int] = DBConstants.REFLEC_BEAT_CLEAR_TYPE_S_HARD_CLEARED + CLEAR_TYPE_HARD_CLEARED: Final[ + int + ] = DBConstants.REFLEC_BEAT_CLEAR_TYPE_HARD_CLEARED + CLEAR_TYPE_S_HARD_CLEARED: Final[ + int + ] = DBConstants.REFLEC_BEAT_CLEAR_TYPE_S_HARD_CLEARED # Combo types, as saved/loaded from the DB COMBO_TYPE_NONE: Final[int] = DBConstants.REFLEC_BEAT_COMBO_TYPE_NONE - COMBO_TYPE_ALMOST_COMBO: Final[int] = DBConstants.REFLEC_BEAT_COMBO_TYPE_ALMOST_COMBO + COMBO_TYPE_ALMOST_COMBO: Final[ + int + ] = DBConstants.REFLEC_BEAT_COMBO_TYPE_ALMOST_COMBO COMBO_TYPE_FULL_COMBO: Final[int] = DBConstants.REFLEC_BEAT_COMBO_TYPE_FULL_COMBO - COMBO_TYPE_FULL_COMBO_ALL_JUST: Final[int] = DBConstants.REFLEC_BEAT_COMBO_TYPE_FULL_COMBO_ALL_JUST + COMBO_TYPE_FULL_COMBO_ALL_JUST: Final[ + int + ] = DBConstants.REFLEC_BEAT_COMBO_TYPE_FULL_COMBO_ALL_JUST # Return the local2 and lobby2 service so that matching will work on newer # Reflec Beat games. extra_services: List[str] = [ - 'local2', - 'lobby2', + "local2", + "lobby2", ] - def previous_version(self) -> Optional['ReflecBeatBase']: + def previous_version(self) -> Optional["ReflecBeatBase"]: """ Returns the previous version of the game, based on this game. Should be overridden. @@ -54,9 +62,11 @@ class ReflecBeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): Base handler for a profile. Given a userid and a profile dictionary, return a Node representing a profile. Should be overridden. """ - return Node.void('pc') + return Node.void("pc") - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: """ Base handler for profile parsing. Given a request and an old profile, return a new profile that's been updated with the contents of the request. @@ -84,7 +94,9 @@ class ReflecBeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): return None return self.format_profile(userid, profile) - def put_profile_by_refid(self, refid: Optional[str], request: Node) -> Optional[Profile]: + def put_profile_by_refid( + self, refid: Optional[str], request: Node + ) -> Optional[Profile]: """ Given a RefID and a request node, unformat the profile and save it. """ @@ -121,10 +133,10 @@ class ReflecBeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): clear_type: int, combo_type: int, miss_count: int, - combo: Optional[int]=None, - stats: Optional[Dict[str, int]]=None, - param: Optional[int]=None, - kflag: Optional[int]=None, + combo: Optional[int] = None, + stats: Optional[Dict[str, int]] = None, + param: Optional[int] = None, + kflag: Optional[int] = None, ) -> None: """ Given various pieces of a score, update the user's high score and score @@ -176,53 +188,66 @@ class ReflecBeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): scoredata = oldscore.data # Update the last played time - scoredata.replace_int('last_played_time', now) + scoredata.replace_int("last_played_time", now) # Replace clear type with highest value and timestamps - if clear_type >= scoredata.get_int('clear_type'): - scoredata.replace_int('clear_type', max(scoredata.get_int('clear_type'), clear_type)) - scoredata.replace_int('best_clear_type_time', now) - history.replace_int('clear_type', clear_type) + if clear_type >= scoredata.get_int("clear_type"): + scoredata.replace_int( + "clear_type", max(scoredata.get_int("clear_type"), clear_type) + ) + scoredata.replace_int("best_clear_type_time", now) + history.replace_int("clear_type", clear_type) # Replace combo type with highest value and timestamps - if combo_type >= scoredata.get_int('combo_type'): - scoredata.replace_int('combo_type', max(scoredata.get_int('combo_type'), combo_type)) - scoredata.replace_int('best_clear_type_time', now) - history.replace_int('combo_type', combo_type) + if combo_type >= scoredata.get_int("combo_type"): + scoredata.replace_int( + "combo_type", max(scoredata.get_int("combo_type"), combo_type) + ) + scoredata.replace_int("best_clear_type_time", now) + history.replace_int("combo_type", combo_type) # Update the combo for this song if combo is not None: - scoredata.replace_int('combo', max(scoredata.get_int('combo'), combo)) - history.replace_int('combo', combo) + scoredata.replace_int("combo", max(scoredata.get_int("combo"), combo)) + history.replace_int("combo", combo) # Update the param for this song if param is not None: - scoredata.replace_int('param', max(scoredata.get_int('param'), param)) - history.replace_int('param', param) + scoredata.replace_int("param", max(scoredata.get_int("param"), param)) + history.replace_int("param", param) # Update the kflag for this song if kflag is not None: - scoredata.replace_int('kflag', max(scoredata.get_int('kflag'), kflag)) - history.replace_int('kflag', kflag) + scoredata.replace_int("kflag", max(scoredata.get_int("kflag"), kflag)) + history.replace_int("kflag", kflag) # Update win/lost/draw stats for this song if stats is not None: - scoredata.replace_dict('stats', stats) - history.replace_dict('stats', stats) + scoredata.replace_dict("stats", stats) + history.replace_dict("stats", stats) # Update the achievement rate with timestamps - if achievement_rate >= scoredata.get_int('achievement_rate'): - scoredata.replace_int('achievement_rate', max(scoredata.get_int('achievement_rate'), achievement_rate)) - scoredata.replace_int('best_achievement_rate_time', now) - history.replace_int('achievement_rate', achievement_rate) + if achievement_rate >= scoredata.get_int("achievement_rate"): + scoredata.replace_int( + "achievement_rate", + max(scoredata.get_int("achievement_rate"), achievement_rate), + ) + scoredata.replace_int("best_achievement_rate_time", now) + history.replace_int("achievement_rate", achievement_rate) # Update the miss count with timestamps, either if it was lowered, or if the old value was blank. # If the new value is -1 (we didn't get a miss count this time), never update the old value. if miss_count >= 0: - if miss_count <= scoredata.get_int('miss_count', 999999) or scoredata.get_int('miss_count') == -1: - scoredata.replace_int('miss_count', min(scoredata.get_int('miss_count', 999999), miss_count)) - scoredata.replace_int('best_miss_count_time', now) - history.replace_int('miss_count', miss_count) + if ( + miss_count <= scoredata.get_int("miss_count", 999999) + or scoredata.get_int("miss_count") == -1 + ): + scoredata.replace_int( + "miss_count", + min(scoredata.get_int("miss_count", 999999), miss_count), + ) + scoredata.replace_int("best_miss_count_time", now) + history.replace_int("miss_count", miss_count) # Look up where this score was earned lid = self.get_machine_id() diff --git a/bemani/backend/reflec/colette.py b/bemani/backend/reflec/colette.py index 44ad4ac..abb6670 100644 --- a/bemani/backend/reflec/colette.py +++ b/bemani/backend/reflec/colette.py @@ -30,15 +30,15 @@ class ReflecBeatColette(ReflecBeatBase): Return all of our front-end modifiably settings. """ return { - 'bools': [ + "bools": [ { - 'name': 'Force Song Unlock', - 'tip': 'Force unlock all songs.', - 'category': 'game_config', - 'setting': 'force_unlock_songs', + "name": "Force Song Unlock", + "tip": "Force unlock all songs.", + "category": "game_config", + "setting": "force_unlock_songs", }, ], - 'ints': [], + "ints": [], } def __db_to_game_clear_type(self, db_clear_type: int, db_combo_type: int) -> int: @@ -60,8 +60,8 @@ class ReflecBeatColette(ReflecBeatBase): self.COMBO_TYPE_FULL_COMBO_ALL_JUST, ]: return self.GAME_CLEAR_TYPE_FULL_COMBO - raise Exception(f'Invalid db_combo_type {db_combo_type}') - raise Exception(f'Invalid db_clear_type {db_clear_type}') + raise Exception(f"Invalid db_combo_type {db_combo_type}") + raise Exception(f"Invalid db_clear_type {db_clear_type}") def __game_to_db_clear_type(self, game_clear_type: int) -> Tuple[int, int]: if game_clear_type == self.GAME_CLEAR_TYPE_NO_PLAY: @@ -75,52 +75,54 @@ class ReflecBeatColette(ReflecBeatBase): if game_clear_type == self.GAME_CLEAR_TYPE_FULL_COMBO: return (self.CLEAR_TYPE_CLEARED, self.COMBO_TYPE_FULL_COMBO) - raise Exception(f'Invalid game_clear_type {game_clear_type}') + raise Exception(f"Invalid game_clear_type {game_clear_type}") def handle_pcb_error_request(self, request: Node) -> Node: - return Node.void('pcb') + return Node.void("pcb") def handle_pcb_uptime_update_request(self, request: Node) -> Node: - return Node.void('pcb') + return Node.void("pcb") def handle_pcb_boot_request(self, request: Node) -> Node: - shop_id = ID.parse_machine_id(request.child_value('lid')) + shop_id = ID.parse_machine_id(request.child_value("lid")) machine = self.get_machine_by_id(shop_id) if machine is not None: machine_name = machine.name - close = machine.data.get_bool('close') - hour = machine.data.get_int('hour') - minute = machine.data.get_int('minute') + close = machine.data.get_bool("close") + hour = machine.data.get_int("hour") + minute = machine.data.get_int("minute") else: - machine_name = '' + machine_name = "" close = False hour = 0 minute = 0 - root = Node.void('pcb') - sinfo = Node.void('sinfo') + root = Node.void("pcb") + sinfo = Node.void("sinfo") root.add_child(sinfo) - sinfo.add_child(Node.string('nm', machine_name)) - sinfo.add_child(Node.bool('cl_enbl', close)) - sinfo.add_child(Node.u8('cl_h', hour)) - sinfo.add_child(Node.u8('cl_m', minute)) + sinfo.add_child(Node.string("nm", machine_name)) + sinfo.add_child(Node.bool("cl_enbl", close)) + sinfo.add_child(Node.u8("cl_h", hour)) + sinfo.add_child(Node.u8("cl_m", minute)) return root def handle_shop_setting_write_request(self, request: Node) -> Node: - return Node.void('shop') + return Node.void("shop") def handle_shop_info_write_request(self, request: Node) -> Node: - self.update_machine_name(request.child_value('sinfo/nm')) - self.update_machine_data({ - 'close': request.child_value('sinfo/cl_enbl'), - 'hour': request.child_value('sinfo/cl_h'), - 'minute': request.child_value('sinfo/cl_m'), - 'pref': request.child_value('sinfo/prf'), - }) - return Node.void('shop') + self.update_machine_name(request.child_value("sinfo/nm")) + self.update_machine_data( + { + "close": request.child_value("sinfo/cl_enbl"), + "hour": request.child_value("sinfo/cl_h"), + "minute": request.child_value("sinfo/cl_m"), + "pref": request.child_value("sinfo/prf"), + } + ) + return Node.void("shop") def __add_event_info(self, root: Node) -> None: - event_ctrl = Node.void('event_ctrl') + event_ctrl = Node.void("event_ctrl") root.add_child(event_ctrl) events: Dict[int, int] = { @@ -129,12 +131,12 @@ class ReflecBeatColette(ReflecBeatBase): } for (eventid, phase) in events.items(): - data = Node.void('data') + data = Node.void("data") event_ctrl.add_child(data) - data.add_child(Node.s32('type', eventid)) - data.add_child(Node.s32('phase', phase)) + data.add_child(Node.s32("type", eventid)) + data.add_child(Node.s32("phase", phase)) - item_lock_ctrl = Node.void('item_lock_ctrl') + item_lock_ctrl = Node.void("item_lock_ctrl") root.add_child(item_lock_ctrl) # Contains zero or more nodes like: # @@ -144,35 +146,37 @@ class ReflecBeatColette(ReflecBeatBase): # def handle_info_common_request(self, request: Node) -> Node: - root = Node.void('info') + root = Node.void("info") self.__add_event_info(root) return root def handle_info_ranking_request(self, request: Node) -> Node: - version = request.child_value('ver') + version = request.child_value("ver") - root = Node.void('info') - root.add_child(Node.s32('ver', version)) - ranking = Node.void('ranking') + root = Node.void("info") + root.add_child(Node.s32("ver", version)) + ranking = Node.void("ranking") root.add_child(ranking) - def add_hitchart(name: str, start: int, end: int, hitchart: List[Tuple[int, int]]) -> None: + def add_hitchart( + name: str, start: int, end: int, hitchart: List[Tuple[int, int]] + ) -> None: base = Node.void(name) ranking.add_child(base) - base.add_child(Node.s32('bt', start)) - base.add_child(Node.s32('et', end)) - new = Node.void('new') + base.add_child(Node.s32("bt", start)) + base.add_child(Node.s32("et", end)) + new = Node.void("new") base.add_child(new) for (mid, plays) in hitchart: - d = Node.void('d') + d = Node.void("d") new.add_child(d) - d.add_child(Node.s16('mid', mid)) - d.add_child(Node.s32('cnt', plays)) + d.add_child(Node.s16("mid", mid)) + d.add_child(Node.s32("cnt", plays)) # Weekly hit chart add_hitchart( - 'weekly', + "weekly", Time.now() - Time.SECONDS_IN_WEEK, Time.now(), self.data.local.music.get_hit_chart(self.game, self.version, 1024, 7), @@ -180,7 +184,7 @@ class ReflecBeatColette(ReflecBeatBase): # Monthly hit chart add_hitchart( - 'monthly', + "monthly", Time.now() - Time.SECONDS_IN_DAY * 30, Time.now(), self.data.local.music.get_hit_chart(self.game, self.version, 1024, 30), @@ -188,7 +192,7 @@ class ReflecBeatColette(ReflecBeatBase): # All time hit chart add_hitchart( - 'total', + "total", Time.now() - Time.SECONDS_IN_DAY * 365, Time.now(), self.data.local.music.get_hit_chart(self.game, self.version, 1024, 365), @@ -197,24 +201,24 @@ class ReflecBeatColette(ReflecBeatBase): return root def handle_info_pzlcmt_read_request(self, request: Node) -> Node: - extid = request.child_value('uid') - teamid = request.child_value('tid') - limit = request.child_value('limit') + extid = request.child_value("uid") + teamid = request.child_value("tid") + limit = request.child_value("limit") userid = self.data.remote.user.from_extid(self.game, self.version, extid) comments = [ - achievement for achievement in - self.data.local.user.get_all_time_based_achievements(self.game, self.version) - if achievement[1].type == 'puzzle_comment' + achievement + for achievement in self.data.local.user.get_all_time_based_achievements( + self.game, self.version + ) + if achievement[1].type == "puzzle_comment" ] comments.sort(key=lambda x: x[1].timestamp, reverse=True) - favorites = [ - comment for comment in comments - if comment[0] == userid - ] + favorites = [comment for comment in comments if comment[0] == userid] teamcomments = [ - comment for comment in comments - if comment[1].data.get_int('teamid') == teamid + comment + for comment in comments + if comment[1].data.get_int("teamid") == teamid ] # Cap all comment blocks to the limit @@ -223,10 +227,10 @@ class ReflecBeatColette(ReflecBeatBase): favorites = favorites[:limit] teamcomments = teamcomments[:limit] - root = Node.void('info') - comment = Node.void('comment') + root = Node.void("info") + comment = Node.void("comment") root.add_child(comment) - comment.add_child(Node.s32('time', Time.now())) + comment.add_child(Node.s32("time", Time.now())) # Mapping of profiles to userIDs uid_mapping = { @@ -239,49 +243,49 @@ class ReflecBeatColette(ReflecBeatBase): self.version, "", 0, - {'name': 'PLAYER'}, + {"name": "PLAYER"}, ) def add_comments(name: str, selected: List[Tuple[UserID, Achievement]]) -> None: for (uid, ach) in selected: cmnt = Node.void(name) root.add_child(cmnt) - cmnt.add_child(Node.s32('uid', uid_mapping[uid].extid)) - cmnt.add_child(Node.string('name', uid_mapping[uid].get_str('name'))) - cmnt.add_child(Node.s16('icon', ach.data.get_int('icon'))) - cmnt.add_child(Node.s8('bln', ach.data.get_int('bln'))) - cmnt.add_child(Node.s32('tid', ach.data.get_int('teamid'))) - cmnt.add_child(Node.string('t_name', ach.data.get_str('teamname'))) - cmnt.add_child(Node.s8('pref', ach.data.get_int('prefecture'))) - cmnt.add_child(Node.s32('time', ach.timestamp)) - cmnt.add_child(Node.string('comment', ach.data.get_str('comment'))) - cmnt.add_child(Node.bool('is_tweet', ach.data.get_bool('tweet'))) + cmnt.add_child(Node.s32("uid", uid_mapping[uid].extid)) + cmnt.add_child(Node.string("name", uid_mapping[uid].get_str("name"))) + cmnt.add_child(Node.s16("icon", ach.data.get_int("icon"))) + cmnt.add_child(Node.s8("bln", ach.data.get_int("bln"))) + cmnt.add_child(Node.s32("tid", ach.data.get_int("teamid"))) + cmnt.add_child(Node.string("t_name", ach.data.get_str("teamname"))) + cmnt.add_child(Node.s8("pref", ach.data.get_int("prefecture"))) + cmnt.add_child(Node.s32("time", ach.timestamp)) + cmnt.add_child(Node.string("comment", ach.data.get_str("comment"))) + cmnt.add_child(Node.bool("is_tweet", ach.data.get_bool("tweet"))) # Add all comments - add_comments('c', comments) + add_comments("c", comments) # Add personal comments (favorites) - add_comments('cf', favorites) + add_comments("cf", favorites) # Add team comments - add_comments('ct', teamcomments) + add_comments("ct", teamcomments) return root def handle_info_pzlcmt_write_request(self, request: Node) -> Node: - extid = request.child_value('uid') + extid = request.child_value("uid") userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is None: # Anonymous comment userid = UserID(0) - icon = request.child_value('icon') - bln = request.child_value('bln') - teamid = request.child_value('tid') - teamname = request.child_value('t_name') - prefecture = request.child_value('pref') - comment = request.child_value('comment') - is_tweet = request.child_value('is_tweet') + icon = request.child_value("icon") + bln = request.child_value("bln") + teamid = request.child_value("tid") + teamname = request.child_value("t_name") + prefecture = request.child_value("pref") + comment = request.child_value("comment") + is_tweet = request.child_value("is_tweet") # Link comment to user's profile self.data.local.user.put_time_based_achievement( @@ -289,73 +293,73 @@ class ReflecBeatColette(ReflecBeatBase): self.version, userid, 0, # We never have an ID for this, since comments are add-only - 'puzzle_comment', + "puzzle_comment", { - 'icon': icon, - 'bln': bln, - 'teamid': teamid, - 'teamname': teamname, - 'prefecture': prefecture, - 'comment': comment, - 'tweet': is_tweet, + "icon": icon, + "bln": bln, + "teamid": teamid, + "teamname": teamname, + "prefecture": prefecture, + "comment": comment, + "tweet": is_tweet, }, ) - return Node.void('info') + return Node.void("info") def handle_jbrbcollabo_save_request(self, request: Node) -> Node: - jbrbcollabo = Node.void('jbrbcollabo') - jbrbcollabo.add_child(Node.u16('marathontype', 0)) - jbrbcollabo.add_child(Node.u32('smith_start', 0)) - jbrbcollabo.add_child(Node.u32('pastel_start', 0)) - jbrbcollabo.add_child(Node.u32('smith_run', 0)) - jbrbcollabo.add_child(Node.u32('pastel_run', 0)) - jbrbcollabo.add_child(Node.u16('smith_ouen', 0)) - jbrbcollabo.add_child(Node.u16('pastel_ouen', 0)) - jbrbcollabo.add_child(Node.u32('smith_water_run', 0)) - jbrbcollabo.add_child(Node.u32('pastel_water_run', 0)) - jbrbcollabo.add_child(Node.bool('getwater', False)) - jbrbcollabo.add_child(Node.bool('smith_goal', False)) - jbrbcollabo.add_child(Node.bool('pastel_goal', False)) - jbrbcollabo.add_child(Node.u16('distancetype', 0)) - jbrbcollabo.add_child(Node.bool('run1_1_j_flg', False)) - jbrbcollabo.add_child(Node.bool('run1_2_j_flg', False)) - jbrbcollabo.add_child(Node.bool('run1_3_j_flg', False)) - jbrbcollabo.add_child(Node.bool('run1_4_flg', False)) - jbrbcollabo.add_child(Node.bool('run1_1_r_flg', False)) - jbrbcollabo.add_child(Node.bool('run1_2_r_flg', False)) - jbrbcollabo.add_child(Node.bool('run1_3_r_flg', False)) - jbrbcollabo.add_child(Node.bool('run1_4_flg', False)) - jbrbcollabo.add_child(Node.bool('run2_1_j_flg', False)) - jbrbcollabo.add_child(Node.bool('run2_2_j_flg', False)) - jbrbcollabo.add_child(Node.bool('run2_3_j_flg', False)) - jbrbcollabo.add_child(Node.bool('run2_4_flg', False)) - jbrbcollabo.add_child(Node.bool('run2_1_r_flg', False)) - jbrbcollabo.add_child(Node.bool('run2_2_r_flg', False)) - jbrbcollabo.add_child(Node.bool('run2_3_r_flg', False)) - jbrbcollabo.add_child(Node.bool('run2_4_flg', False)) - jbrbcollabo.add_child(Node.bool('run3_1_j_flg', False)) - jbrbcollabo.add_child(Node.bool('run3_2_j_flg', False)) - jbrbcollabo.add_child(Node.bool('run3_3_j_flg', False)) - jbrbcollabo.add_child(Node.bool('run3_4_flg', False)) - jbrbcollabo.add_child(Node.bool('run3_1_r_flg', False)) - jbrbcollabo.add_child(Node.bool('run3_2_r_flg', False)) - jbrbcollabo.add_child(Node.bool('run3_3_r_flg', False)) - jbrbcollabo.add_child(Node.bool('run3_4_flg', False)) - jbrbcollabo.add_child(Node.bool('run4_1_j_flg', False)) - jbrbcollabo.add_child(Node.bool('run4_1_r_flg', False)) - jbrbcollabo.add_child(Node.bool('run4_2_flg', False)) - jbrbcollabo.add_child(Node.bool('run4_2_flg', False)) - jbrbcollabo.add_child(Node.bool('start_flg', False)) + jbrbcollabo = Node.void("jbrbcollabo") + jbrbcollabo.add_child(Node.u16("marathontype", 0)) + jbrbcollabo.add_child(Node.u32("smith_start", 0)) + jbrbcollabo.add_child(Node.u32("pastel_start", 0)) + jbrbcollabo.add_child(Node.u32("smith_run", 0)) + jbrbcollabo.add_child(Node.u32("pastel_run", 0)) + jbrbcollabo.add_child(Node.u16("smith_ouen", 0)) + jbrbcollabo.add_child(Node.u16("pastel_ouen", 0)) + jbrbcollabo.add_child(Node.u32("smith_water_run", 0)) + jbrbcollabo.add_child(Node.u32("pastel_water_run", 0)) + jbrbcollabo.add_child(Node.bool("getwater", False)) + jbrbcollabo.add_child(Node.bool("smith_goal", False)) + jbrbcollabo.add_child(Node.bool("pastel_goal", False)) + jbrbcollabo.add_child(Node.u16("distancetype", 0)) + jbrbcollabo.add_child(Node.bool("run1_1_j_flg", False)) + jbrbcollabo.add_child(Node.bool("run1_2_j_flg", False)) + jbrbcollabo.add_child(Node.bool("run1_3_j_flg", False)) + jbrbcollabo.add_child(Node.bool("run1_4_flg", False)) + jbrbcollabo.add_child(Node.bool("run1_1_r_flg", False)) + jbrbcollabo.add_child(Node.bool("run1_2_r_flg", False)) + jbrbcollabo.add_child(Node.bool("run1_3_r_flg", False)) + jbrbcollabo.add_child(Node.bool("run1_4_flg", False)) + jbrbcollabo.add_child(Node.bool("run2_1_j_flg", False)) + jbrbcollabo.add_child(Node.bool("run2_2_j_flg", False)) + jbrbcollabo.add_child(Node.bool("run2_3_j_flg", False)) + jbrbcollabo.add_child(Node.bool("run2_4_flg", False)) + jbrbcollabo.add_child(Node.bool("run2_1_r_flg", False)) + jbrbcollabo.add_child(Node.bool("run2_2_r_flg", False)) + jbrbcollabo.add_child(Node.bool("run2_3_r_flg", False)) + jbrbcollabo.add_child(Node.bool("run2_4_flg", False)) + jbrbcollabo.add_child(Node.bool("run3_1_j_flg", False)) + jbrbcollabo.add_child(Node.bool("run3_2_j_flg", False)) + jbrbcollabo.add_child(Node.bool("run3_3_j_flg", False)) + jbrbcollabo.add_child(Node.bool("run3_4_flg", False)) + jbrbcollabo.add_child(Node.bool("run3_1_r_flg", False)) + jbrbcollabo.add_child(Node.bool("run3_2_r_flg", False)) + jbrbcollabo.add_child(Node.bool("run3_3_r_flg", False)) + jbrbcollabo.add_child(Node.bool("run3_4_flg", False)) + jbrbcollabo.add_child(Node.bool("run4_1_j_flg", False)) + jbrbcollabo.add_child(Node.bool("run4_1_r_flg", False)) + jbrbcollabo.add_child(Node.bool("run4_2_flg", False)) + jbrbcollabo.add_child(Node.bool("run4_2_flg", False)) + jbrbcollabo.add_child(Node.bool("start_flg", False)) return jbrbcollabo def handle_lobby_entry_request(self, request: Node) -> Node: - root = Node.void('lobby') - root.add_child(Node.s32('interval', 120)) - root.add_child(Node.s32('interval_p', 120)) + root = Node.void("lobby") + root.add_child(Node.s32("interval", 120)) + root.add_child(Node.s32("interval_p", 120)) # Create a lobby entry for this user - extid = request.child_value('e/uid') + extid = request.child_value("e/uid") userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: profile = self.get_profile(userid) @@ -364,66 +368,66 @@ class ReflecBeatColette(ReflecBeatBase): self.version, userid, { - 'mid': request.child_value('e/mid'), - 'ng': request.child_value('e/ng'), - 'mopt': request.child_value('e/mopt'), - 'tid': request.child_value('e/tid'), - 'tn': request.child_value('e/tn'), - 'topt': request.child_value('e/topt'), - 'lid': request.child_value('e/lid'), - 'sn': request.child_value('e/sn'), - 'pref': request.child_value('e/pref'), - 'stg': request.child_value('e/stg'), - 'pside': request.child_value('e/pside'), - 'eatime': request.child_value('e/eatime'), - 'ga': request.child_value('e/ga'), - 'gp': request.child_value('e/gp'), - 'la': request.child_value('e/la'), - 'ver': request.child_value('e/ver'), - } + "mid": request.child_value("e/mid"), + "ng": request.child_value("e/ng"), + "mopt": request.child_value("e/mopt"), + "tid": request.child_value("e/tid"), + "tn": request.child_value("e/tn"), + "topt": request.child_value("e/topt"), + "lid": request.child_value("e/lid"), + "sn": request.child_value("e/sn"), + "pref": request.child_value("e/pref"), + "stg": request.child_value("e/stg"), + "pside": request.child_value("e/pside"), + "eatime": request.child_value("e/eatime"), + "ga": request.child_value("e/ga"), + "gp": request.child_value("e/gp"), + "la": request.child_value("e/la"), + "ver": request.child_value("e/ver"), + }, ) lobby = self.data.local.lobby.get_lobby( self.game, self.version, userid, ) - root.add_child(Node.s32('eid', lobby.get_int('id'))) - e = Node.void('e') + root.add_child(Node.s32("eid", lobby.get_int("id"))) + e = Node.void("e") root.add_child(e) - e.add_child(Node.s32('eid', lobby.get_int('id'))) - e.add_child(Node.u16('mid', lobby.get_int('mid'))) - e.add_child(Node.u8('ng', lobby.get_int('ng'))) - e.add_child(Node.s32('uid', profile.extid)) - e.add_child(Node.s32('uattr', profile.get_int('uattr'))) - e.add_child(Node.string('pn', profile.get_str('name'))) - e.add_child(Node.s16('mg', profile.get_int('mg'))) - e.add_child(Node.s32('mopt', lobby.get_int('mopt'))) - e.add_child(Node.s32('tid', lobby.get_int('tid'))) - e.add_child(Node.string('tn', lobby.get_str('tn'))) - e.add_child(Node.s32('topt', lobby.get_int('topt'))) - e.add_child(Node.string('lid', lobby.get_str('lid'))) - e.add_child(Node.string('sn', lobby.get_str('sn'))) - e.add_child(Node.u8('pref', lobby.get_int('pref'))) - e.add_child(Node.s8('stg', lobby.get_int('stg'))) - e.add_child(Node.s8('pside', lobby.get_int('pside'))) - e.add_child(Node.s16('eatime', lobby.get_int('eatime'))) - e.add_child(Node.u8_array('ga', lobby.get_int_array('ga', 4))) - e.add_child(Node.u16('gp', lobby.get_int('gp'))) - e.add_child(Node.u8_array('la', lobby.get_int_array('la', 4))) - e.add_child(Node.u8('ver', lobby.get_int('ver'))) + e.add_child(Node.s32("eid", lobby.get_int("id"))) + e.add_child(Node.u16("mid", lobby.get_int("mid"))) + e.add_child(Node.u8("ng", lobby.get_int("ng"))) + e.add_child(Node.s32("uid", profile.extid)) + e.add_child(Node.s32("uattr", profile.get_int("uattr"))) + e.add_child(Node.string("pn", profile.get_str("name"))) + e.add_child(Node.s16("mg", profile.get_int("mg"))) + e.add_child(Node.s32("mopt", lobby.get_int("mopt"))) + e.add_child(Node.s32("tid", lobby.get_int("tid"))) + e.add_child(Node.string("tn", lobby.get_str("tn"))) + e.add_child(Node.s32("topt", lobby.get_int("topt"))) + e.add_child(Node.string("lid", lobby.get_str("lid"))) + e.add_child(Node.string("sn", lobby.get_str("sn"))) + e.add_child(Node.u8("pref", lobby.get_int("pref"))) + e.add_child(Node.s8("stg", lobby.get_int("stg"))) + e.add_child(Node.s8("pside", lobby.get_int("pside"))) + e.add_child(Node.s16("eatime", lobby.get_int("eatime"))) + e.add_child(Node.u8_array("ga", lobby.get_int_array("ga", 4))) + e.add_child(Node.u16("gp", lobby.get_int("gp"))) + e.add_child(Node.u8_array("la", lobby.get_int_array("la", 4))) + e.add_child(Node.u8("ver", lobby.get_int("ver"))) return root def handle_lobby_read_request(self, request: Node) -> Node: - root = Node.void('lobby') - root.add_child(Node.s32('interval', 120)) - root.add_child(Node.s32('interval_p', 120)) + root = Node.void("lobby") + root.add_child(Node.s32("interval", 120)) + root.add_child(Node.s32("interval_p", 120)) # Look up all lobbies matching the criteria specified - ver = request.child_value('var') - mg = request.child_value('m_grade') # noqa: F841 - extid = request.child_value('uid') - limit = request.child_value('max') + ver = request.child_value("var") + mg = request.child_value("m_grade") # noqa: F841 + extid = request.child_value("uid") + limit = request.child_value("max") userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: lobbies = self.data.local.lobby.get_all_lobbies(self.game, self.version) @@ -434,7 +438,7 @@ class ReflecBeatColette(ReflecBeatBase): if user == userid: # If we have our own lobby, don't return it continue - if ver != lobby.get_int('ver'): + if ver != lobby.get_int("ver"): # Don't return lobby data for different versions continue @@ -443,44 +447,44 @@ class ReflecBeatColette(ReflecBeatBase): # No profile info, don't return this lobby continue - e = Node.void('e') + e = Node.void("e") root.add_child(e) - e.add_child(Node.s32('eid', lobby.get_int('id'))) - e.add_child(Node.u16('mid', lobby.get_int('mid'))) - e.add_child(Node.u8('ng', lobby.get_int('ng'))) - e.add_child(Node.s32('uid', profile.extid)) - e.add_child(Node.s32('uattr', profile.get_int('uattr'))) - e.add_child(Node.string('pn', profile.get_str('name'))) - e.add_child(Node.s16('mg', profile.get_int('mg'))) - e.add_child(Node.s32('mopt', lobby.get_int('mopt'))) - e.add_child(Node.s32('tid', lobby.get_int('tid'))) - e.add_child(Node.string('tn', lobby.get_str('tn'))) - e.add_child(Node.s32('topt', lobby.get_int('topt'))) - e.add_child(Node.string('lid', lobby.get_str('lid'))) - e.add_child(Node.string('sn', lobby.get_str('sn'))) - e.add_child(Node.u8('pref', lobby.get_int('pref'))) - e.add_child(Node.s8('stg', lobby.get_int('stg'))) - e.add_child(Node.s8('pside', lobby.get_int('pside'))) - e.add_child(Node.s16('eatime', lobby.get_int('eatime'))) - e.add_child(Node.u8_array('ga', lobby.get_int_array('ga', 4))) - e.add_child(Node.u16('gp', lobby.get_int('gp'))) - e.add_child(Node.u8_array('la', lobby.get_int_array('la', 4))) - e.add_child(Node.u8('ver', lobby.get_int('ver'))) + e.add_child(Node.s32("eid", lobby.get_int("id"))) + e.add_child(Node.u16("mid", lobby.get_int("mid"))) + e.add_child(Node.u8("ng", lobby.get_int("ng"))) + e.add_child(Node.s32("uid", profile.extid)) + e.add_child(Node.s32("uattr", profile.get_int("uattr"))) + e.add_child(Node.string("pn", profile.get_str("name"))) + e.add_child(Node.s16("mg", profile.get_int("mg"))) + e.add_child(Node.s32("mopt", lobby.get_int("mopt"))) + e.add_child(Node.s32("tid", lobby.get_int("tid"))) + e.add_child(Node.string("tn", lobby.get_str("tn"))) + e.add_child(Node.s32("topt", lobby.get_int("topt"))) + e.add_child(Node.string("lid", lobby.get_str("lid"))) + e.add_child(Node.string("sn", lobby.get_str("sn"))) + e.add_child(Node.u8("pref", lobby.get_int("pref"))) + e.add_child(Node.s8("stg", lobby.get_int("stg"))) + e.add_child(Node.s8("pside", lobby.get_int("pside"))) + e.add_child(Node.s16("eatime", lobby.get_int("eatime"))) + e.add_child(Node.u8_array("ga", lobby.get_int_array("ga", 4))) + e.add_child(Node.u16("gp", lobby.get_int("gp"))) + e.add_child(Node.u8_array("la", lobby.get_int_array("la", 4))) + e.add_child(Node.u8("ver", lobby.get_int("ver"))) limit = limit - 1 return root def handle_lobby_delete_request(self, request: Node) -> Node: - eid = request.child_value('eid') + eid = request.child_value("eid") self.data.local.lobby.destroy_lobby(eid) - return Node.void('lobby') + return Node.void("lobby") def handle_player_start_request(self, request: Node) -> Node: - root = Node.void('player') + root = Node.void("player") # Create a new play session based on info from the request - refid = request.child_value('rid') + refid = request.child_value("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: self.data.local.lobby.put_play_session_info( @@ -488,9 +492,9 @@ class ReflecBeatColette(ReflecBeatBase): self.version, userid, { - 'ga': request.child_value('ga'), - 'gp': request.child_value('gp'), - 'la': request.child_value('la'), + "ga": request.child_value("ga"), + "gp": request.child_value("gp"), + "la": request.child_value("la"), }, ) info = self.data.local.lobby.get_play_session_info( @@ -499,116 +503,116 @@ class ReflecBeatColette(ReflecBeatBase): userid, ) if info is not None: - play_id = info.get_int('id') + play_id = info.get_int("id") else: play_id = 0 else: play_id = 0 # Session stuff, and resend global defaults - root.add_child(Node.s32('plyid', play_id)) - root.add_child(Node.u64('start_time', Time.now() * 1000)) + root.add_child(Node.s32("plyid", play_id)) + root.add_child(Node.u64("start_time", Time.now() * 1000)) self.__add_event_info(root) # Event settings and such - lincle_link_4 = Node.void('lincle_link_4') + lincle_link_4 = Node.void("lincle_link_4") root.add_child(lincle_link_4) - lincle_link_4.add_child(Node.u32('qpro', 0)) - lincle_link_4.add_child(Node.u32('glass', 0)) - lincle_link_4.add_child(Node.u32('treasure', 0)) - lincle_link_4.add_child(Node.bool('for_iidx_0_0', False)) - lincle_link_4.add_child(Node.bool('for_iidx_0_1', False)) - lincle_link_4.add_child(Node.bool('for_iidx_0_2', False)) - lincle_link_4.add_child(Node.bool('for_iidx_0_3', False)) - lincle_link_4.add_child(Node.bool('for_iidx_0_4', False)) - lincle_link_4.add_child(Node.bool('for_iidx_0_5', False)) - lincle_link_4.add_child(Node.bool('for_iidx_0_6', False)) - lincle_link_4.add_child(Node.bool('for_iidx_0', False)) - lincle_link_4.add_child(Node.bool('for_iidx_1', False)) - lincle_link_4.add_child(Node.bool('for_iidx_2', False)) - lincle_link_4.add_child(Node.bool('for_iidx_3', False)) - lincle_link_4.add_child(Node.bool('for_iidx_4', False)) - lincle_link_4.add_child(Node.bool('for_rb_0_0', False)) - lincle_link_4.add_child(Node.bool('for_rb_0_1', False)) - lincle_link_4.add_child(Node.bool('for_rb_0_2', False)) - lincle_link_4.add_child(Node.bool('for_rb_0_3', False)) - lincle_link_4.add_child(Node.bool('for_rb_0_4', False)) - lincle_link_4.add_child(Node.bool('for_rb_0_5', False)) - lincle_link_4.add_child(Node.bool('for_rb_0_6', False)) - lincle_link_4.add_child(Node.bool('for_rb_0', False)) - lincle_link_4.add_child(Node.bool('for_rb_1', False)) - lincle_link_4.add_child(Node.bool('for_rb_2', False)) - lincle_link_4.add_child(Node.bool('for_rb_3', False)) - lincle_link_4.add_child(Node.bool('for_rb_4', False)) - lincle_link_4.add_child(Node.bool('qproflg', False)) - lincle_link_4.add_child(Node.bool('glassflg', False)) - lincle_link_4.add_child(Node.bool('complete', False)) + lincle_link_4.add_child(Node.u32("qpro", 0)) + lincle_link_4.add_child(Node.u32("glass", 0)) + lincle_link_4.add_child(Node.u32("treasure", 0)) + lincle_link_4.add_child(Node.bool("for_iidx_0_0", False)) + lincle_link_4.add_child(Node.bool("for_iidx_0_1", False)) + lincle_link_4.add_child(Node.bool("for_iidx_0_2", False)) + lincle_link_4.add_child(Node.bool("for_iidx_0_3", False)) + lincle_link_4.add_child(Node.bool("for_iidx_0_4", False)) + lincle_link_4.add_child(Node.bool("for_iidx_0_5", False)) + lincle_link_4.add_child(Node.bool("for_iidx_0_6", False)) + lincle_link_4.add_child(Node.bool("for_iidx_0", False)) + lincle_link_4.add_child(Node.bool("for_iidx_1", False)) + lincle_link_4.add_child(Node.bool("for_iidx_2", False)) + lincle_link_4.add_child(Node.bool("for_iidx_3", False)) + lincle_link_4.add_child(Node.bool("for_iidx_4", False)) + lincle_link_4.add_child(Node.bool("for_rb_0_0", False)) + lincle_link_4.add_child(Node.bool("for_rb_0_1", False)) + lincle_link_4.add_child(Node.bool("for_rb_0_2", False)) + lincle_link_4.add_child(Node.bool("for_rb_0_3", False)) + lincle_link_4.add_child(Node.bool("for_rb_0_4", False)) + lincle_link_4.add_child(Node.bool("for_rb_0_5", False)) + lincle_link_4.add_child(Node.bool("for_rb_0_6", False)) + lincle_link_4.add_child(Node.bool("for_rb_0", False)) + lincle_link_4.add_child(Node.bool("for_rb_1", False)) + lincle_link_4.add_child(Node.bool("for_rb_2", False)) + lincle_link_4.add_child(Node.bool("for_rb_3", False)) + lincle_link_4.add_child(Node.bool("for_rb_4", False)) + lincle_link_4.add_child(Node.bool("qproflg", False)) + lincle_link_4.add_child(Node.bool("glassflg", False)) + lincle_link_4.add_child(Node.bool("complete", False)) - jbrbcollabo = Node.void('jbrbcollabo') + jbrbcollabo = Node.void("jbrbcollabo") root.add_child(jbrbcollabo) - jbrbcollabo.add_child(Node.bool('run1_1_j_flg', False)) - jbrbcollabo.add_child(Node.bool('run1_2_j_flg', False)) - jbrbcollabo.add_child(Node.bool('run1_3_j_flg', False)) - jbrbcollabo.add_child(Node.bool('run1_4_flg', False)) - jbrbcollabo.add_child(Node.bool('run1_1_r_flg', False)) - jbrbcollabo.add_child(Node.bool('run1_2_r_flg', False)) - jbrbcollabo.add_child(Node.bool('run1_3_r_flg', False)) - jbrbcollabo.add_child(Node.bool('run1_4_flg', False)) - jbrbcollabo.add_child(Node.bool('run2_1_j_flg', False)) - jbrbcollabo.add_child(Node.bool('run2_2_j_flg', False)) - jbrbcollabo.add_child(Node.bool('run2_3_j_flg', False)) - jbrbcollabo.add_child(Node.bool('run2_4_flg', False)) - jbrbcollabo.add_child(Node.bool('run2_1_r_flg', False)) - jbrbcollabo.add_child(Node.bool('run2_2_r_flg', False)) - jbrbcollabo.add_child(Node.bool('run2_3_r_flg', False)) - jbrbcollabo.add_child(Node.bool('run2_4_flg', False)) - jbrbcollabo.add_child(Node.bool('run3_1_j_flg', False)) - jbrbcollabo.add_child(Node.bool('run3_2_j_flg', False)) - jbrbcollabo.add_child(Node.bool('run3_3_j_flg', False)) - jbrbcollabo.add_child(Node.bool('run3_4_flg', False)) - jbrbcollabo.add_child(Node.bool('run3_1_r_flg', False)) - jbrbcollabo.add_child(Node.bool('run3_2_r_flg', False)) - jbrbcollabo.add_child(Node.bool('run3_3_r_flg', False)) - jbrbcollabo.add_child(Node.bool('run3_4_flg', False)) - jbrbcollabo.add_child(Node.u16('marathontype', 0)) - jbrbcollabo.add_child(Node.u32('smith_start', 0)) - jbrbcollabo.add_child(Node.u32('pastel_start', 0)) - jbrbcollabo.add_child(Node.u16('smith_ouen', 0)) - jbrbcollabo.add_child(Node.u16('pastel_ouen', 0)) - jbrbcollabo.add_child(Node.u16('distancetype', 0)) - jbrbcollabo.add_child(Node.bool('smith_goal', False)) - jbrbcollabo.add_child(Node.bool('pastel_goal', False)) - jbrbcollabo.add_child(Node.bool('run4_1_j_flg', False)) - jbrbcollabo.add_child(Node.bool('run4_1_r_flg', False)) - jbrbcollabo.add_child(Node.bool('run4_2_flg', False)) - jbrbcollabo.add_child(Node.bool('run4_2_flg', False)) - jbrbcollabo.add_child(Node.bool('start_flg', False)) + jbrbcollabo.add_child(Node.bool("run1_1_j_flg", False)) + jbrbcollabo.add_child(Node.bool("run1_2_j_flg", False)) + jbrbcollabo.add_child(Node.bool("run1_3_j_flg", False)) + jbrbcollabo.add_child(Node.bool("run1_4_flg", False)) + jbrbcollabo.add_child(Node.bool("run1_1_r_flg", False)) + jbrbcollabo.add_child(Node.bool("run1_2_r_flg", False)) + jbrbcollabo.add_child(Node.bool("run1_3_r_flg", False)) + jbrbcollabo.add_child(Node.bool("run1_4_flg", False)) + jbrbcollabo.add_child(Node.bool("run2_1_j_flg", False)) + jbrbcollabo.add_child(Node.bool("run2_2_j_flg", False)) + jbrbcollabo.add_child(Node.bool("run2_3_j_flg", False)) + jbrbcollabo.add_child(Node.bool("run2_4_flg", False)) + jbrbcollabo.add_child(Node.bool("run2_1_r_flg", False)) + jbrbcollabo.add_child(Node.bool("run2_2_r_flg", False)) + jbrbcollabo.add_child(Node.bool("run2_3_r_flg", False)) + jbrbcollabo.add_child(Node.bool("run2_4_flg", False)) + jbrbcollabo.add_child(Node.bool("run3_1_j_flg", False)) + jbrbcollabo.add_child(Node.bool("run3_2_j_flg", False)) + jbrbcollabo.add_child(Node.bool("run3_3_j_flg", False)) + jbrbcollabo.add_child(Node.bool("run3_4_flg", False)) + jbrbcollabo.add_child(Node.bool("run3_1_r_flg", False)) + jbrbcollabo.add_child(Node.bool("run3_2_r_flg", False)) + jbrbcollabo.add_child(Node.bool("run3_3_r_flg", False)) + jbrbcollabo.add_child(Node.bool("run3_4_flg", False)) + jbrbcollabo.add_child(Node.u16("marathontype", 0)) + jbrbcollabo.add_child(Node.u32("smith_start", 0)) + jbrbcollabo.add_child(Node.u32("pastel_start", 0)) + jbrbcollabo.add_child(Node.u16("smith_ouen", 0)) + jbrbcollabo.add_child(Node.u16("pastel_ouen", 0)) + jbrbcollabo.add_child(Node.u16("distancetype", 0)) + jbrbcollabo.add_child(Node.bool("smith_goal", False)) + jbrbcollabo.add_child(Node.bool("pastel_goal", False)) + jbrbcollabo.add_child(Node.bool("run4_1_j_flg", False)) + jbrbcollabo.add_child(Node.bool("run4_1_r_flg", False)) + jbrbcollabo.add_child(Node.bool("run4_2_flg", False)) + jbrbcollabo.add_child(Node.bool("run4_2_flg", False)) + jbrbcollabo.add_child(Node.bool("start_flg", False)) - tricolettepark = Node.void('tricolettepark') + tricolettepark = Node.void("tricolettepark") root.add_child(tricolettepark) - tricolettepark.add_child(Node.s32('open_music', -1)) - tricolettepark.add_child(Node.s32('boss0_damage', -1)) - tricolettepark.add_child(Node.s32('boss1_damage', -1)) - tricolettepark.add_child(Node.s32('boss2_damage', -1)) - tricolettepark.add_child(Node.s32('boss3_damage', -1)) - tricolettepark.add_child(Node.s32('boss0_stun', -1)) - tricolettepark.add_child(Node.s32('boss1_stun', -1)) - tricolettepark.add_child(Node.s32('boss2_stun', -1)) - tricolettepark.add_child(Node.s32('boss3_stun', -1)) - tricolettepark.add_child(Node.s32('magic_gauge', -1)) - tricolettepark.add_child(Node.s32('today_party', -1)) - tricolettepark.add_child(Node.bool('union_magic', False)) - tricolettepark.add_child(Node.bool('is_complete', False)) - tricolettepark.add_child(Node.float('base_attack_rate', 1.0)) + tricolettepark.add_child(Node.s32("open_music", -1)) + tricolettepark.add_child(Node.s32("boss0_damage", -1)) + tricolettepark.add_child(Node.s32("boss1_damage", -1)) + tricolettepark.add_child(Node.s32("boss2_damage", -1)) + tricolettepark.add_child(Node.s32("boss3_damage", -1)) + tricolettepark.add_child(Node.s32("boss0_stun", -1)) + tricolettepark.add_child(Node.s32("boss1_stun", -1)) + tricolettepark.add_child(Node.s32("boss2_stun", -1)) + tricolettepark.add_child(Node.s32("boss3_stun", -1)) + tricolettepark.add_child(Node.s32("magic_gauge", -1)) + tricolettepark.add_child(Node.s32("today_party", -1)) + tricolettepark.add_child(Node.bool("union_magic", False)) + tricolettepark.add_child(Node.bool("is_complete", False)) + tricolettepark.add_child(Node.float("base_attack_rate", 1.0)) return root def handle_player_delete_request(self, request: Node) -> Node: - return Node.void('player') + return Node.void("player") def handle_player_end_request(self, request: Node) -> Node: # Destroy play session based on info from the request - refid = request.child_value('rid') + refid = request.child_value("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: # Kill any lingering lobbies by this user @@ -618,409 +622,493 @@ class ReflecBeatColette(ReflecBeatBase): userid, ) if lobby is not None: - self.data.local.lobby.destroy_lobby(lobby.get_int('id')) - self.data.local.lobby.destroy_play_session_info(self.game, self.version, userid) + self.data.local.lobby.destroy_lobby(lobby.get_int("id")) + self.data.local.lobby.destroy_play_session_info( + self.game, self.version, userid + ) - return Node.void('player') + return Node.void("player") def handle_player_succeed_request(self, request: Node) -> Node: - refid = request.child_value('rid') + refid = request.child_value("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: previous_version = self.previous_version() profile = previous_version.get_profile(userid) - achievements = self.data.local.user.get_achievements(previous_version.game, previous_version.version, userid) - scores = self.data.remote.music.get_scores(previous_version.game, previous_version.version, userid) + achievements = self.data.local.user.get_achievements( + previous_version.game, previous_version.version, userid + ) + scores = self.data.remote.music.get_scores( + previous_version.game, previous_version.version, userid + ) else: profile = None - root = Node.void('player') + root = Node.void("player") if profile is None: - root.add_child(Node.string('name', '')) - root.add_child(Node.s16('lv', -1)) - root.add_child(Node.s32('exp', -1)) - root.add_child(Node.s32('grd', -1)) - root.add_child(Node.s32('ap', -1)) + root.add_child(Node.string("name", "")) + root.add_child(Node.s16("lv", -1)) + root.add_child(Node.s32("exp", -1)) + root.add_child(Node.s32("grd", -1)) + root.add_child(Node.s32("ap", -1)) - root.add_child(Node.void('released')) - root.add_child(Node.void('mrecord')) + root.add_child(Node.void("released")) + root.add_child(Node.void("mrecord")) else: - root.add_child(Node.string('name', profile.get_str('name'))) - root.add_child(Node.s16('lv', profile.get_int('lvl'))) - root.add_child(Node.s32('exp', profile.get_int('exp'))) - root.add_child(Node.s32('grd', profile.get_int('mg'))) # This is a guess - root.add_child(Node.s32('ap', profile.get_int('ap'))) + root.add_child(Node.string("name", profile.get_str("name"))) + root.add_child(Node.s16("lv", profile.get_int("lvl"))) + root.add_child(Node.s32("exp", profile.get_int("exp"))) + root.add_child(Node.s32("grd", profile.get_int("mg"))) # This is a guess + root.add_child(Node.s32("ap", profile.get_int("ap"))) - released = Node.void('released') + released = Node.void("released") root.add_child(released) for item in achievements: - if item.type != 'item_0': + if item.type != "item_0": continue - released.add_child(Node.s16('i', item.id)) + released.add_child(Node.s16("i", item.id)) - mrecord = Node.void('mrecord') + mrecord = Node.void("mrecord") root.add_child(mrecord) for score in scores: - mrec = Node.void('mrec') + mrec = Node.void("mrec") mrecord.add_child(mrec) - mrec.add_child(Node.s16('mid', score.id)) - mrec.add_child(Node.s8('ntgrd', score.chart)) - mrec.add_child(Node.s32('pc', score.plays)) - mrec.add_child(Node.s8('ct', self.__db_to_game_clear_type(score.data.get_int('clear_type'), score.data.get_int('combo_type')))) - mrec.add_child(Node.s16('ar', score.data.get_int('achievement_rate'))) - mrec.add_child(Node.s16('scr', score.points)) - mrec.add_child(Node.s16('cmb', score.data.get_int('combo'))) - mrec.add_child(Node.s16('ms', score.data.get_int('miss_count'))) - mrec.add_child(Node.u16('ver', 0)) + mrec.add_child(Node.s16("mid", score.id)) + mrec.add_child(Node.s8("ntgrd", score.chart)) + mrec.add_child(Node.s32("pc", score.plays)) + mrec.add_child( + Node.s8( + "ct", + self.__db_to_game_clear_type( + score.data.get_int("clear_type"), + score.data.get_int("combo_type"), + ), + ) + ) + mrec.add_child(Node.s16("ar", score.data.get_int("achievement_rate"))) + mrec.add_child(Node.s16("scr", score.points)) + mrec.add_child(Node.s16("cmb", score.data.get_int("combo"))) + mrec.add_child(Node.s16("ms", score.data.get_int("miss_count"))) + mrec.add_child(Node.u16("ver", 0)) return root def handle_player_read_request(self, request: Node) -> Node: - refid = request.child_value('rid') + refid = request.child_value("rid") profile = self.get_profile_by_refid(refid) if profile: return profile - return Node.void('player') + return Node.void("player") def handle_player_write_request(self, request: Node) -> Node: - refid = request.child_value('pdata/account/rid') + refid = request.child_value("pdata/account/rid") profile = self.put_profile_by_refid(refid, request) - root = Node.void('player') + root = Node.void("player") if profile is None: - root.add_child(Node.s32('uid', 0)) + root.add_child(Node.s32("uid", 0)) else: - root.add_child(Node.s32('uid', profile.extid)) + root.add_child(Node.s32("uid", profile.extid)) return root def format_profile(self, userid: UserID, profile: Profile) -> Node: statistics = self.get_play_statistics(userid) game_config = self.get_game_config() - achievements = self.data.local.user.get_achievements(self.game, self.version, userid) + achievements = self.data.local.user.get_achievements( + self.game, self.version, userid + ) scores = self.data.remote.music.get_scores(self.game, self.version, userid) links = self.data.local.user.get_links(self.game, self.version, userid) - root = Node.void('player') - pdata = Node.void('pdata') + root = Node.void("player") + pdata = Node.void("pdata") root.add_child(pdata) - account = Node.void('account') + account = Node.void("account") pdata.add_child(account) - account.add_child(Node.s32('usrid', profile.extid)) - account.add_child(Node.s32('tpc', statistics.total_plays)) - account.add_child(Node.s32('dpc', statistics.today_plays)) - account.add_child(Node.s32('crd', 1)) - account.add_child(Node.s32('brd', 1)) - account.add_child(Node.s32('tdc', statistics.total_days)) - account.add_child(Node.s32('intrvld', 0)) - account.add_child(Node.s16('ver', 5)) - account.add_child(Node.u64('pst', 0)) - account.add_child(Node.u64('st', Time.now() * 1000)) + account.add_child(Node.s32("usrid", profile.extid)) + account.add_child(Node.s32("tpc", statistics.total_plays)) + account.add_child(Node.s32("dpc", statistics.today_plays)) + account.add_child(Node.s32("crd", 1)) + account.add_child(Node.s32("brd", 1)) + account.add_child(Node.s32("tdc", statistics.total_days)) + account.add_child(Node.s32("intrvld", 0)) + account.add_child(Node.s16("ver", 5)) + account.add_child(Node.u64("pst", 0)) + account.add_child(Node.u64("st", Time.now() * 1000)) # Base account info - base = Node.void('base') + base = Node.void("base") pdata.add_child(base) - base.add_child(Node.string('name', profile.get_str('name'))) - base.add_child(Node.s32('exp', profile.get_int('exp'))) - base.add_child(Node.s32('lv', profile.get_int('lvl'))) - base.add_child(Node.s32('mg', profile.get_int('mg'))) - base.add_child(Node.s32('ap', profile.get_int('ap'))) - base.add_child(Node.s32('tid', profile.get_int('team_id', -1))) - base.add_child(Node.string('tname', profile.get_str('team_name', ''))) - base.add_child(Node.string('cmnt', '')) - base.add_child(Node.s32('uattr', profile.get_int('uattr'))) - base.add_child(Node.s32_array('hidden_param', profile.get_int_array('hidden_param', 50))) - base.add_child(Node.s32('tbs', -1)) - base.add_child(Node.s32('tbs_r', -1)) + base.add_child(Node.string("name", profile.get_str("name"))) + base.add_child(Node.s32("exp", profile.get_int("exp"))) + base.add_child(Node.s32("lv", profile.get_int("lvl"))) + base.add_child(Node.s32("mg", profile.get_int("mg"))) + base.add_child(Node.s32("ap", profile.get_int("ap"))) + base.add_child(Node.s32("tid", profile.get_int("team_id", -1))) + base.add_child(Node.string("tname", profile.get_str("team_name", ""))) + base.add_child(Node.string("cmnt", "")) + base.add_child(Node.s32("uattr", profile.get_int("uattr"))) + base.add_child( + Node.s32_array("hidden_param", profile.get_int_array("hidden_param", 50)) + ) + base.add_child(Node.s32("tbs", -1)) + base.add_child(Node.s32("tbs_r", -1)) # Rivals - rival = Node.void('rival') + rival = Node.void("rival") pdata.add_child(rival) slotid = 0 for link in links: - if link.type != 'rival': + if link.type != "rival": continue rprofile = self.get_profile(link.other_userid) if rprofile is None: continue - r = Node.void('r') + r = Node.void("r") rival.add_child(r) - r.add_child(Node.s32('slot_id', slotid)) - r.add_child(Node.s32('id', rprofile.extid)) - r.add_child(Node.string('name', rprofile.get_str('name'))) - r.add_child(Node.bool('friend', True)) - r.add_child(Node.bool('locked', False)) - r.add_child(Node.s32('rc', 0)) + r.add_child(Node.s32("slot_id", slotid)) + r.add_child(Node.s32("id", rprofile.extid)) + r.add_child(Node.string("name", rprofile.get_str("name"))) + r.add_child(Node.bool("friend", True)) + r.add_child(Node.bool("locked", False)) + r.add_child(Node.s32("rc", 0)) slotid = slotid + 1 # Player customizations - custom = Node.void('custom') - customdict = profile.get_dict('custom') + custom = Node.void("custom") + customdict = profile.get_dict("custom") pdata.add_child(custom) - custom.add_child(Node.u8('st_shot', customdict.get_int('st_shot'))) - custom.add_child(Node.u8('st_frame', customdict.get_int('st_frame'))) - custom.add_child(Node.u8('st_expl', customdict.get_int('st_expl'))) - custom.add_child(Node.u8('st_bg', customdict.get_int('st_bg'))) - custom.add_child(Node.u8('st_shot_vol', customdict.get_int('st_shot_vol'))) - custom.add_child(Node.u8('st_bg_bri', customdict.get_int('st_bg_bri'))) - custom.add_child(Node.u8('st_obj_size', customdict.get_int('st_obj_size'))) - custom.add_child(Node.u8('st_jr_gauge', customdict.get_int('st_jr_gauge'))) - custom.add_child(Node.u8('st_clr_gauge', customdict.get_int('st_clr_gauge'))) - custom.add_child(Node.u8('st_jdg_disp', customdict.get_int('st_jdg_disp'))) - custom.add_child(Node.u8('st_tm_disp', customdict.get_int('st_tm_disp'))) - custom.add_child(Node.u8('st_rnd', customdict.get_int('st_rnd'))) - custom.add_child(Node.s16_array('schat_0', customdict.get_int_array('schat_0', 9))) - custom.add_child(Node.s16_array('schat_1', customdict.get_int_array('schat_1', 9))) - custom.add_child(Node.s16_array('ichat_0', customdict.get_int_array('ichat_0', 6))) - custom.add_child(Node.s16_array('ichat_1', customdict.get_int_array('ichat_1', 6))) + custom.add_child(Node.u8("st_shot", customdict.get_int("st_shot"))) + custom.add_child(Node.u8("st_frame", customdict.get_int("st_frame"))) + custom.add_child(Node.u8("st_expl", customdict.get_int("st_expl"))) + custom.add_child(Node.u8("st_bg", customdict.get_int("st_bg"))) + custom.add_child(Node.u8("st_shot_vol", customdict.get_int("st_shot_vol"))) + custom.add_child(Node.u8("st_bg_bri", customdict.get_int("st_bg_bri"))) + custom.add_child(Node.u8("st_obj_size", customdict.get_int("st_obj_size"))) + custom.add_child(Node.u8("st_jr_gauge", customdict.get_int("st_jr_gauge"))) + custom.add_child(Node.u8("st_clr_gauge", customdict.get_int("st_clr_gauge"))) + custom.add_child(Node.u8("st_jdg_disp", customdict.get_int("st_jdg_disp"))) + custom.add_child(Node.u8("st_tm_disp", customdict.get_int("st_tm_disp"))) + custom.add_child(Node.u8("st_rnd", customdict.get_int("st_rnd"))) + custom.add_child( + Node.s16_array("schat_0", customdict.get_int_array("schat_0", 9)) + ) + custom.add_child( + Node.s16_array("schat_1", customdict.get_int_array("schat_1", 9)) + ) + custom.add_child( + Node.s16_array("ichat_0", customdict.get_int_array("ichat_0", 6)) + ) + custom.add_child( + Node.s16_array("ichat_1", customdict.get_int_array("ichat_1", 6)) + ) # Player external config - config = Node.void('config') - configdict = profile.get_dict('config') + config = Node.void("config") + configdict = profile.get_dict("config") pdata.add_child(config) - config.add_child(Node.u8('msel_bgm', configdict.get_int('msel_bgm'))) - config.add_child(Node.u8('narrowdown_type', configdict.get_int('narrowdown_type'))) - config.add_child(Node.s16('icon_id', configdict.get_int('icon_id'))) - config.add_child(Node.s16('byword_0', configdict.get_int('byword_0'))) - config.add_child(Node.s16('byword_1', configdict.get_int('byword_1'))) - config.add_child(Node.bool('is_auto_byword_0', configdict.get_bool('is_auto_byword_0'))) - config.add_child(Node.bool('is_auto_byword_1', configdict.get_bool('is_auto_byword_1'))) - config.add_child(Node.u8('mrec_type', configdict.get_int('mrec_type'))) - config.add_child(Node.u8('tab_sel', configdict.get_int('tab_sel'))) - config.add_child(Node.u8('card_disp', configdict.get_int('card_disp'))) - config.add_child(Node.u8('score_tab_disp', configdict.get_int('score_tab_disp'))) - config.add_child(Node.s16('last_music_id', configdict.get_int('last_music_id', -1))) - config.add_child(Node.u8('last_note_grade', configdict.get_int('last_note_grade'))) - config.add_child(Node.u8('sort_type', configdict.get_int('sort_type'))) - config.add_child(Node.u8('rival_panel_type', configdict.get_int('rival_panel_type'))) - config.add_child(Node.u64('random_entry_work', configdict.get_int('random_entry_work'))) - config.add_child(Node.u8('folder_lamp_type', configdict.get_int('folder_lamp_type'))) - config.add_child(Node.bool('is_tweet', configdict.get_bool('is_tweet'))) - config.add_child(Node.bool('is_link_twitter', configdict.get_bool('is_link_twitter'))) + config.add_child(Node.u8("msel_bgm", configdict.get_int("msel_bgm"))) + config.add_child( + Node.u8("narrowdown_type", configdict.get_int("narrowdown_type")) + ) + config.add_child(Node.s16("icon_id", configdict.get_int("icon_id"))) + config.add_child(Node.s16("byword_0", configdict.get_int("byword_0"))) + config.add_child(Node.s16("byword_1", configdict.get_int("byword_1"))) + config.add_child( + Node.bool("is_auto_byword_0", configdict.get_bool("is_auto_byword_0")) + ) + config.add_child( + Node.bool("is_auto_byword_1", configdict.get_bool("is_auto_byword_1")) + ) + config.add_child(Node.u8("mrec_type", configdict.get_int("mrec_type"))) + config.add_child(Node.u8("tab_sel", configdict.get_int("tab_sel"))) + config.add_child(Node.u8("card_disp", configdict.get_int("card_disp"))) + config.add_child( + Node.u8("score_tab_disp", configdict.get_int("score_tab_disp")) + ) + config.add_child( + Node.s16("last_music_id", configdict.get_int("last_music_id", -1)) + ) + config.add_child( + Node.u8("last_note_grade", configdict.get_int("last_note_grade")) + ) + config.add_child(Node.u8("sort_type", configdict.get_int("sort_type"))) + config.add_child( + Node.u8("rival_panel_type", configdict.get_int("rival_panel_type")) + ) + config.add_child( + Node.u64("random_entry_work", configdict.get_int("random_entry_work")) + ) + config.add_child( + Node.u8("folder_lamp_type", configdict.get_int("folder_lamp_type")) + ) + config.add_child(Node.bool("is_tweet", configdict.get_bool("is_tweet"))) + config.add_child( + Node.bool("is_link_twitter", configdict.get_bool("is_link_twitter")) + ) # Stamps - stamp = Node.void('stamp') - stampdict = profile.get_dict('stamp') + stamp = Node.void("stamp") + stampdict = profile.get_dict("stamp") pdata.add_child(stamp) - stamp.add_child(Node.s32_array('stmpcnt', stampdict.get_int_array('stmpcnt', 5))) - stamp.add_child(Node.s32_array('tcktcnt', stampdict.get_int_array('tcktcnt', 5))) - stamp.add_child(Node.s64('area', stampdict.get_int('area'))) - stamp.add_child(Node.s64('prfvst', stampdict.get_int('prfvst'))) - stamp.add_child(Node.s32('reserve', stampdict.get_int('reserve'))) + stamp.add_child( + Node.s32_array("stmpcnt", stampdict.get_int_array("stmpcnt", 5)) + ) + stamp.add_child( + Node.s32_array("tcktcnt", stampdict.get_int_array("tcktcnt", 5)) + ) + stamp.add_child(Node.s64("area", stampdict.get_int("area"))) + stamp.add_child(Node.s64("prfvst", stampdict.get_int("prfvst"))) + stamp.add_child(Node.s32("reserve", stampdict.get_int("reserve"))) # Unlocks - released = Node.void('released') + released = Node.void("released") pdata.add_child(released) for item in achievements: - if item.type[:5] != 'item_': + if item.type[:5] != "item_": continue itemtype = int(item.type[5:]) - if game_config.get_bool('force_unlock_songs') and itemtype == 0: + if game_config.get_bool("force_unlock_songs") and itemtype == 0: # Don't echo unlocks when we're force unlocking, we'll do it later continue - info = Node.void('info') + info = Node.void("info") released.add_child(info) - info.add_child(Node.u8('type', itemtype)) - info.add_child(Node.u16('id', item.id)) - info.add_child(Node.u16('param', item.data.get_int('param'))) + info.add_child(Node.u8("type", itemtype)) + info.add_child(Node.u16("id", item.id)) + info.add_child(Node.u16("param", item.data.get_int("param"))) - if game_config.get_bool('force_unlock_songs'): + if game_config.get_bool("force_unlock_songs"): ids: Dict[int, int] = {} songs = self.data.local.music.get_all_songs(self.game, self.version) for song in songs: if song.id not in ids: ids[song.id] = 0 - if song.data.get_int('difficulty') > 0: + if song.data.get_int("difficulty") > 0: ids[song.id] = ids[song.id] | (1 << song.chart) for songid in ids: if ids[songid] == 0: continue - info = Node.void('info') + info = Node.void("info") released.add_child(info) - info.add_child(Node.u8('type', 0)) - info.add_child(Node.u16('id', songid)) - info.add_child(Node.u16('param', ids[songid])) + info.add_child(Node.u8("type", 0)) + info.add_child(Node.u16("id", songid)) + info.add_child(Node.u16("param", ids[songid])) # Favorite songs - fav_music_slot = Node.void('fav_music_slot') + fav_music_slot = Node.void("fav_music_slot") pdata.add_child(fav_music_slot) for item in achievements: - if item.type != 'music': + if item.type != "music": continue - slot = Node.void('slot') + slot = Node.void("slot") fav_music_slot.add_child(slot) - slot.add_child(Node.u8('slot_id', item.id)) - slot.add_child(Node.s16('music_id', item.data.get_int('music_id'))) + slot.add_child(Node.u8("slot_id", item.id)) + slot.add_child(Node.s16("music_id", item.data.get_int("music_id"))) # Event stuff - order = Node.void('order') + order = Node.void("order") pdata.add_child(order) - order.add_child(Node.s32('exp', profile.get_int('order_exp'))) + order.add_child(Node.s32("exp", profile.get_int("order_exp"))) for item in achievements: - if item.type != 'order': + if item.type != "order": continue - data = Node.void('d') + data = Node.void("d") order.add_child(data) - data.add_child(Node.s16('order', item.id)) - data.add_child(Node.s16('slt', item.data.get_int('slt'))) - data.add_child(Node.s32('ccnt', item.data.get_int('ccnt'))) - data.add_child(Node.s32('fcnt', item.data.get_int('fcnt'))) - data.add_child(Node.s32('fcnt1', item.data.get_int('fcnt1'))) - data.add_child(Node.s32('prm', item.data.get_int('param'))) + data.add_child(Node.s16("order", item.id)) + data.add_child(Node.s16("slt", item.data.get_int("slt"))) + data.add_child(Node.s32("ccnt", item.data.get_int("ccnt"))) + data.add_child(Node.s32("fcnt", item.data.get_int("fcnt"))) + data.add_child(Node.s32("fcnt1", item.data.get_int("fcnt1"))) + data.add_child(Node.s32("prm", item.data.get_int("param"))) - seedpod = Node.void('seedpod') + seedpod = Node.void("seedpod") pdata.add_child(seedpod) for item in achievements: - if item.type != 'seedpod': + if item.type != "seedpod": continue - data = Node.void('data') + data = Node.void("data") seedpod.add_child(data) - data.add_child(Node.s16('id', item.id)) - data.add_child(Node.s16('pod', item.data.get_int('pod'))) + data.add_child(Node.s16("id", item.id)) + data.add_child(Node.s16("pod", item.data.get_int("pod"))) - eqpexp = Node.void('eqpexp') + eqpexp = Node.void("eqpexp") pdata.add_child(eqpexp) for item in achievements: - if item.type[:7] != 'eqpexp_': + if item.type[:7] != "eqpexp_": continue stype = int(item.type[7:]) - data = Node.void('data') + data = Node.void("data") eqpexp.add_child(data) - data.add_child(Node.s16('id', item.id)) - data.add_child(Node.s32('exp', item.data.get_int('exp'))) - data.add_child(Node.s16('stype', stype)) + data.add_child(Node.s16("id", item.id)) + data.add_child(Node.s32("exp", item.data.get_int("exp"))) + data.add_child(Node.s16("stype", stype)) - eventexp = Node.void('evntexp') + eventexp = Node.void("evntexp") pdata.add_child(eventexp) for item in achievements: - if item.type != 'eventexp': + if item.type != "eventexp": continue - data = Node.void('data') + data = Node.void("data") eventexp.add_child(data) - data.add_child(Node.s16('id', item.id)) - data.add_child(Node.s32('exp', item.data.get_int('exp'))) + data.add_child(Node.s16("id", item.id)) + data.add_child(Node.s32("exp", item.data.get_int("exp"))) # Scores - record = Node.void('record') + record = Node.void("record") pdata.add_child(record) - record_old = Node.void('record_old') + record_old = Node.void("record_old") pdata.add_child(record_old) for score in scores: - rec = Node.void('rec') + rec = Node.void("rec") record.add_child(rec) - rec.add_child(Node.s16('mid', score.id)) - rec.add_child(Node.s8('ntgrd', score.chart)) - rec.add_child(Node.s32('pc', score.plays)) - rec.add_child(Node.s8('ct', self.__db_to_game_clear_type(score.data.get_int('clear_type'), score.data.get_int('combo_type')))) - rec.add_child(Node.s16('ar', score.data.get_int('achievement_rate'))) - rec.add_child(Node.s16('scr', score.points)) - rec.add_child(Node.s16('cmb', score.data.get_int('combo'))) - rec.add_child(Node.s16('ms', score.data.get_int('miss_count'))) - rec.add_child(Node.s32('bscrt', score.timestamp)) - rec.add_child(Node.s32('bart', score.data.get_int('best_achievement_rate_time'))) - rec.add_child(Node.s32('bctt', score.data.get_int('best_clear_type_time'))) - rec.add_child(Node.s32('bmst', score.data.get_int('best_miss_count_time'))) - rec.add_child(Node.s32('time', score.data.get_int('last_played_time'))) + rec.add_child(Node.s16("mid", score.id)) + rec.add_child(Node.s8("ntgrd", score.chart)) + rec.add_child(Node.s32("pc", score.plays)) + rec.add_child( + Node.s8( + "ct", + self.__db_to_game_clear_type( + score.data.get_int("clear_type"), + score.data.get_int("combo_type"), + ), + ) + ) + rec.add_child(Node.s16("ar", score.data.get_int("achievement_rate"))) + rec.add_child(Node.s16("scr", score.points)) + rec.add_child(Node.s16("cmb", score.data.get_int("combo"))) + rec.add_child(Node.s16("ms", score.data.get_int("miss_count"))) + rec.add_child(Node.s32("bscrt", score.timestamp)) + rec.add_child( + Node.s32("bart", score.data.get_int("best_achievement_rate_time")) + ) + rec.add_child(Node.s32("bctt", score.data.get_int("best_clear_type_time"))) + rec.add_child(Node.s32("bmst", score.data.get_int("best_miss_count_time"))) + rec.add_child(Node.s32("time", score.data.get_int("last_played_time"))) return root - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: game_config = self.get_game_config() newprofile = oldprofile.clone() - newprofile.replace_int('lid', ID.parse_machine_id(request.child_value('pdata/account/lid'))) - newprofile.replace_str('name', request.child_value('pdata/base/name')) - newprofile.replace_int('exp', request.child_value('pdata/base/exp')) - newprofile.replace_int('lvl', request.child_value('pdata/base/lvl')) - newprofile.replace_int('mg', request.child_value('pdata/base/mg')) - newprofile.replace_int('ap', request.child_value('pdata/base/ap')) - newprofile.replace_int_array('hidden_param', 50, request.child_value('pdata/base/hidden_param')) + newprofile.replace_int( + "lid", ID.parse_machine_id(request.child_value("pdata/account/lid")) + ) + newprofile.replace_str("name", request.child_value("pdata/base/name")) + newprofile.replace_int("exp", request.child_value("pdata/base/exp")) + newprofile.replace_int("lvl", request.child_value("pdata/base/lvl")) + newprofile.replace_int("mg", request.child_value("pdata/base/mg")) + newprofile.replace_int("ap", request.child_value("pdata/base/ap")) + newprofile.replace_int_array( + "hidden_param", 50, request.child_value("pdata/base/hidden_param") + ) - configdict = newprofile.get_dict('config') - config = request.child('pdata/config') + configdict = newprofile.get_dict("config") + config = request.child("pdata/config") if config: - configdict.replace_int('msel_bgm', config.child_value('msel_bgm')) - configdict.replace_int('narrowdown_type', config.child_value('narrowdown_type')) - configdict.replace_int('icon_id', config.child_value('icon_id')) - configdict.replace_int('byword_0', config.child_value('byword_0')) - configdict.replace_int('byword_1', config.child_value('byword_1')) - configdict.replace_bool('is_auto_byword_0', config.child_value('is_auto_byword_0')) - configdict.replace_bool('is_auto_byword_1', config.child_value('is_auto_byword_1')) - configdict.replace_int('mrec_type', config.child_value('mrec_type')) - configdict.replace_int('tab_sel', config.child_value('tab_sel')) - configdict.replace_int('card_disp', config.child_value('card_disp')) - configdict.replace_int('score_tab_disp', config.child_value('score_tab_disp')) - configdict.replace_int('last_music_id', config.child_value('last_music_id')) - configdict.replace_int('last_note_grade', config.child_value('last_note_grade')) - configdict.replace_int('sort_type', config.child_value('sort_type')) - configdict.replace_int('rival_panel_type', config.child_value('rival_panel_type')) - configdict.replace_int('random_entry_work', config.child_value('random_entry_work')) - configdict.replace_int('folder_lamp_type', config.child_value('folder_lamp_type')) - configdict.replace_bool('is_tweet', config.child_value('is_tweet')) - configdict.replace_bool('is_link_twitter', config.child_value('is_link_twitter')) - newprofile.replace_dict('config', configdict) + configdict.replace_int("msel_bgm", config.child_value("msel_bgm")) + configdict.replace_int( + "narrowdown_type", config.child_value("narrowdown_type") + ) + configdict.replace_int("icon_id", config.child_value("icon_id")) + configdict.replace_int("byword_0", config.child_value("byword_0")) + configdict.replace_int("byword_1", config.child_value("byword_1")) + configdict.replace_bool( + "is_auto_byword_0", config.child_value("is_auto_byword_0") + ) + configdict.replace_bool( + "is_auto_byword_1", config.child_value("is_auto_byword_1") + ) + configdict.replace_int("mrec_type", config.child_value("mrec_type")) + configdict.replace_int("tab_sel", config.child_value("tab_sel")) + configdict.replace_int("card_disp", config.child_value("card_disp")) + configdict.replace_int( + "score_tab_disp", config.child_value("score_tab_disp") + ) + configdict.replace_int("last_music_id", config.child_value("last_music_id")) + configdict.replace_int( + "last_note_grade", config.child_value("last_note_grade") + ) + configdict.replace_int("sort_type", config.child_value("sort_type")) + configdict.replace_int( + "rival_panel_type", config.child_value("rival_panel_type") + ) + configdict.replace_int( + "random_entry_work", config.child_value("random_entry_work") + ) + configdict.replace_int( + "folder_lamp_type", config.child_value("folder_lamp_type") + ) + configdict.replace_bool("is_tweet", config.child_value("is_tweet")) + configdict.replace_bool( + "is_link_twitter", config.child_value("is_link_twitter") + ) + newprofile.replace_dict("config", configdict) - customdict = newprofile.get_dict('custom') - custom = request.child('pdata/custom') + customdict = newprofile.get_dict("custom") + custom = request.child("pdata/custom") if custom: - customdict.replace_int('st_shot', custom.child_value('st_shot')) - customdict.replace_int('st_frame', custom.child_value('st_frame')) - customdict.replace_int('st_expl', custom.child_value('st_expl')) - customdict.replace_int('st_bg', custom.child_value('st_bg')) - customdict.replace_int('st_shot_vol', custom.child_value('st_shot_vol')) - customdict.replace_int('st_bg_bri', custom.child_value('st_bg_bri')) - customdict.replace_int('st_obj_size', custom.child_value('st_obj_size')) - customdict.replace_int('st_jr_gauge', custom.child_value('st_jr_gauge')) - customdict.replace_int('st_clr_gauge', custom.child_value('st_clr_gauge')) - customdict.replace_int('st_jdg_disp', custom.child_value('st_jdg_disp')) - customdict.replace_int('st_tm_disp', custom.child_value('st_tm_disp')) - customdict.replace_int('st_rnd', custom.child_value('st_rnd')) - customdict.replace_int_array('schat_0', 9, custom.child_value('schat_0')) - customdict.replace_int_array('schat_1', 9, custom.child_value('schat_1')) - customdict.replace_int_array('ichat_0', 6, custom.child_value('ichat_0')) - customdict.replace_int_array('ichat_1', 6, custom.child_value('ichat_1')) - newprofile.replace_dict('custom', customdict) + customdict.replace_int("st_shot", custom.child_value("st_shot")) + customdict.replace_int("st_frame", custom.child_value("st_frame")) + customdict.replace_int("st_expl", custom.child_value("st_expl")) + customdict.replace_int("st_bg", custom.child_value("st_bg")) + customdict.replace_int("st_shot_vol", custom.child_value("st_shot_vol")) + customdict.replace_int("st_bg_bri", custom.child_value("st_bg_bri")) + customdict.replace_int("st_obj_size", custom.child_value("st_obj_size")) + customdict.replace_int("st_jr_gauge", custom.child_value("st_jr_gauge")) + customdict.replace_int("st_clr_gauge", custom.child_value("st_clr_gauge")) + customdict.replace_int("st_jdg_disp", custom.child_value("st_jdg_disp")) + customdict.replace_int("st_tm_disp", custom.child_value("st_tm_disp")) + customdict.replace_int("st_rnd", custom.child_value("st_rnd")) + customdict.replace_int_array("schat_0", 9, custom.child_value("schat_0")) + customdict.replace_int_array("schat_1", 9, custom.child_value("schat_1")) + customdict.replace_int_array("ichat_0", 6, custom.child_value("ichat_0")) + customdict.replace_int_array("ichat_1", 6, custom.child_value("ichat_1")) + newprofile.replace_dict("custom", customdict) # Stamps - stampdict = newprofile.get_dict('stamp') - stamp = request.child('pdata/stamp') + stampdict = newprofile.get_dict("stamp") + stamp = request.child("pdata/stamp") if stamp: - stampdict.replace_int_array('stmpcnt', 5, stamp.child_value('stmpcnt')) - stampdict.replace_int_array('tcktcnt', 5, stamp.child_value('tcktcnt')) - stampdict.replace_int('area', stamp.child_value('area')) - stampdict.replace_int('prfvst', stamp.child_value('prfvst')) - stampdict.replace_int('reserve', stamp.child_value('reserve')) - newprofile.replace_dict('stamp', stampdict) + stampdict.replace_int_array("stmpcnt", 5, stamp.child_value("stmpcnt")) + stampdict.replace_int_array("tcktcnt", 5, stamp.child_value("tcktcnt")) + stampdict.replace_int("area", stamp.child_value("area")) + stampdict.replace_int("prfvst", stamp.child_value("prfvst")) + stampdict.replace_int("reserve", stamp.child_value("reserve")) + newprofile.replace_dict("stamp", stampdict) # Unlockable orders - newprofile.replace_int('order_exp', request.child_value('pdata/order/exp')) - order = request.child('pdata/order') + newprofile.replace_int("order_exp", request.child_value("pdata/order/exp")) + order = request.child("pdata/order") if order: for child in order.children: - if child.name != 'd': + if child.name != "d": continue - orderid = child.child_value('order') - slt = child.child_value('slt') - ccnt = child.child_value('ccnt') - fcnt = child.child_value('fcnt') - fcnt1 = child.child_value('fcnt1') - param = child.child_value('prm') + orderid = child.child_value("order") + slt = child.child_value("slt") + ccnt = child.child_value("ccnt") + fcnt = child.child_value("fcnt") + fcnt1 = child.child_value("fcnt1") + param = child.child_value("prm") if slt == -1: # The game doesn't return valid data for this selection @@ -1033,27 +1121,27 @@ class ReflecBeatColette(ReflecBeatBase): self.version, userid, orderid, - 'order', + "order", { - 'slt': slt, - 'ccnt': ccnt, - 'fcnt': fcnt, - 'fcnt1': fcnt1, - 'param': param, + "slt": slt, + "ccnt": ccnt, + "fcnt": fcnt, + "fcnt1": fcnt1, + "param": param, }, ) # Music unlocks and other stuff - released = request.child('pdata/released') + released = request.child("pdata/released") if released: for child in released.children: - if child.name != 'info': + if child.name != "info": continue - item_id = child.child_value('id') - item_type = child.child_value('type') - param = child.child_value('param') - if game_config.get_bool('force_unlock_songs') and item_type == 0: + item_id = child.child_value("id") + item_type = child.child_value("type") + param = child.child_value("param") + if game_config.get_bool("force_unlock_songs") and item_type == 0: # Don't save unlocks when we're force unlocking continue @@ -1062,21 +1150,21 @@ class ReflecBeatColette(ReflecBeatBase): self.version, userid, item_id, - f'item_{item_type}', + f"item_{item_type}", { - 'param': param, + "param": param, }, ) # Favorite music - fav_music_slot = request.child('pdata/fav_music_slot') + fav_music_slot = request.child("pdata/fav_music_slot") if fav_music_slot: for child in fav_music_slot.children: - if child.name != 'slot': + if child.name != "slot": continue - slot_id = child.child_value('slot_id') - music_id = child.child_value('music_id') + slot_id = child.child_value("slot_id") + music_id = child.child_value("music_id") if music_id == -1: # Delete this favorite self.data.local.user.destroy_achievement( @@ -1084,7 +1172,7 @@ class ReflecBeatColette(ReflecBeatBase): self.version, userid, slot_id, - 'music', + "music", ) else: # Add/update this favorite @@ -1093,110 +1181,116 @@ class ReflecBeatColette(ReflecBeatBase): self.version, userid, slot_id, - 'music', + "music", { - 'music_id': music_id, + "music_id": music_id, }, ) # Event stuff - seedpod = request.child('pdata/seedpod') + seedpod = request.child("pdata/seedpod") if seedpod: for child in seedpod.children: - if child.name != 'data': + if child.name != "data": continue - seedpod_id = child.child_value('id') - seedpod_pod = child.child_value('pod') + seedpod_id = child.child_value("id") + seedpod_pod = child.child_value("pod") self.data.local.user.put_achievement( self.game, self.version, userid, seedpod_id, - 'seedpod', + "seedpod", { - 'pod': seedpod_pod, + "pod": seedpod_pod, }, ) - eventexp = request.child('pdata/evntexp') + eventexp = request.child("pdata/evntexp") if eventexp: for child in eventexp.children: - if child.name != 'data': + if child.name != "data": continue - eventexp_id = child.child_value('id') - eventexp_exp = child.child_value('exp') + eventexp_id = child.child_value("id") + eventexp_exp = child.child_value("exp") # Experience is additive, so load it first and add the updated amount - data = self.data.local.user.get_achievement( - self.game, - self.version, - userid, - eventexp_id, - 'eventexp', - ) or ValidatedDict() + data = ( + self.data.local.user.get_achievement( + self.game, + self.version, + userid, + eventexp_id, + "eventexp", + ) + or ValidatedDict() + ) self.data.local.user.put_achievement( self.game, self.version, userid, eventexp_id, - 'eventexp', + "eventexp", { - 'exp': data.get_int('exp') + eventexp_exp, + "exp": data.get_int("exp") + eventexp_exp, }, ) - eqpexp = request.child('pdata/eqpexp') + eqpexp = request.child("pdata/eqpexp") if eqpexp: for child in eqpexp.children: - if child.name != 'data': + if child.name != "data": continue - eqpexp_id = child.child_value('id') - eqpexp_exp = child.child_value('exp') - eqpexp_stype = child.child_value('stype') + eqpexp_id = child.child_value("id") + eqpexp_exp = child.child_value("exp") + eqpexp_stype = child.child_value("stype") # Experience is additive, so load it first and add the updated amount - data = self.data.local.user.get_achievement( - self.game, - self.version, - userid, - eqpexp_id, - f'eqpexp_{eqpexp_stype}', - ) or ValidatedDict() + data = ( + self.data.local.user.get_achievement( + self.game, + self.version, + userid, + eqpexp_id, + f"eqpexp_{eqpexp_stype}", + ) + or ValidatedDict() + ) self.data.local.user.put_achievement( self.game, self.version, userid, eqpexp_id, - f'eqpexp_{eqpexp_stype}', + f"eqpexp_{eqpexp_stype}", { - 'exp': data.get_int('exp') + eqpexp_exp, + "exp": data.get_int("exp") + eqpexp_exp, }, ) # Grab any new records set during this play session - songplays = request.child('pdata/stglog') + songplays = request.child("pdata/stglog") if songplays: for child in songplays.children: - if child.name != 'log': + if child.name != "log": continue - songid = child.child_value('mid') - chart = child.child_value('ng') - clear_type = child.child_value('ct') + songid = child.child_value("mid") + chart = child.child_value("ng") + clear_type = child.child_value("ct") if songid == 0 and chart == 0 and clear_type == -1: # Dummy song save during profile create continue - points = child.child_value('sc') - achievement_rate = child.child_value('ar') + points = child.child_value("sc") + achievement_rate = child.child_value("ar") clear_type, combo_type = self.__game_to_db_clear_type(clear_type) - combo = child.child_value('cmb') - miss_count = child.child_value('jt_ms') + combo = child.child_value("cmb") + miss_count = child.child_value("jt_ms") self.update_score( userid, songid, diff --git a/bemani/backend/reflec/factory.py b/bemani/backend/reflec/factory.py index 33de431..cc5292f 100644 --- a/bemani/backend/reflec/factory.py +++ b/bemani/backend/reflec/factory.py @@ -24,12 +24,17 @@ class ReflecBeatFactory(Factory): @classmethod def register_all(cls) -> None: - for gamecode in ['KBR', 'LBR', 'MBR']: + for gamecode in ["KBR", "LBR", "MBR"]: Base.register(gamecode, ReflecBeatFactory) @classmethod - def create(cls, data: Data, config: Config, model: Model, parentmodel: Optional[Model]=None) -> Optional[Base]: - + def create( + cls, + data: Data, + config: Config, + model: Model, + parentmodel: Optional[Model] = None, + ) -> Optional[Base]: def version_from_date(date: int) -> Optional[int]: if date < 2014060400: return VersionConstants.REFLEC_BEAT_COLETTE @@ -43,16 +48,16 @@ class ReflecBeatFactory(Factory): return VersionConstants.REFLEC_BEAT_REFLESIA return None - if model.gamecode == 'KBR': + if model.gamecode == "KBR": return ReflecBeat(data, config, model) - if model.gamecode == 'LBR': + if model.gamecode == "LBR": return ReflecBeatLimelight(data, config, model) - if model.gamecode == 'MBR': + if model.gamecode == "MBR": if model.version is None: if parentmodel is None: return None - if parentmodel.gamecode not in ['KBR', 'LBR', 'MBR']: + if parentmodel.gamecode not in ["KBR", "LBR", "MBR"]: return None parentversion = version_from_date(parentmodel.version) if parentversion == VersionConstants.REFLEC_BEAT_COLETTE: diff --git a/bemani/backend/reflec/groovin.py b/bemani/backend/reflec/groovin.py index cfacafa..486669f 100644 --- a/bemani/backend/reflec/groovin.py +++ b/bemani/backend/reflec/groovin.py @@ -39,15 +39,15 @@ class ReflecBeatGroovin(ReflecBeatBase): Return all of our front-end modifiably settings. """ return { - 'bools': [ + "bools": [ { - 'name': 'Force Song Unlock', - 'tip': 'Force unlock all songs.', - 'category': 'game_config', - 'setting': 'force_unlock_songs', + "name": "Force Song Unlock", + "tip": "Force unlock all songs.", + "category": "game_config", + "setting": "force_unlock_songs", }, ], - 'ints': [], + "ints": [], } def __db_to_game_clear_type(self, db_status: int) -> int: @@ -90,49 +90,51 @@ class ReflecBeatGroovin(ReflecBeatBase): return self.COMBO_TYPE_FULL_COMBO if game_combo == self.GAME_COMBO_TYPE_FULL_COMBO_ALL_JUST: return self.COMBO_TYPE_FULL_COMBO_ALL_JUST - raise Exception(f'Invalid game_combo value {game_combo}') + raise Exception(f"Invalid game_combo value {game_combo}") def handle_pcb_rb4error_request(self, request: Node) -> Node: - return Node.void('pcb') + return Node.void("pcb") def handle_pcb_rb4uptime_update_request(self, request: Node) -> Node: - return Node.void('pcb') + return Node.void("pcb") def handle_pcb_rb4boot_request(self, request: Node) -> Node: - shop_id = ID.parse_machine_id(request.child_value('lid')) + shop_id = ID.parse_machine_id(request.child_value("lid")) machine = self.get_machine_by_id(shop_id) if machine is not None: machine_name = machine.name - close = machine.data.get_bool('close') - hour = machine.data.get_int('hour') - minute = machine.data.get_int('minute') + close = machine.data.get_bool("close") + hour = machine.data.get_int("hour") + minute = machine.data.get_int("minute") else: - machine_name = '' + machine_name = "" close = False hour = 0 minute = 0 - root = Node.void('pcb') - sinfo = Node.void('sinfo') + root = Node.void("pcb") + sinfo = Node.void("sinfo") root.add_child(sinfo) - sinfo.add_child(Node.string('nm', machine_name)) - sinfo.add_child(Node.bool('cl_enbl', close)) - sinfo.add_child(Node.u8('cl_h', hour)) - sinfo.add_child(Node.u8('cl_m', minute)) - sinfo.add_child(Node.bool('shop_flag', True)) + sinfo.add_child(Node.string("nm", machine_name)) + sinfo.add_child(Node.bool("cl_enbl", close)) + sinfo.add_child(Node.u8("cl_h", hour)) + sinfo.add_child(Node.u8("cl_m", minute)) + sinfo.add_child(Node.bool("shop_flag", True)) return root def handle_lobby_rb4entry_request(self, request: Node) -> Node: - root = Node.void('lobby') - root.add_child(Node.s32('interval', 120)) - root.add_child(Node.s32('interval_p', 120)) + root = Node.void("lobby") + root.add_child(Node.s32("interval", 120)) + root.add_child(Node.s32("interval_p", 120)) # Create a lobby entry for this user - extid = request.child_value('e/uid') + extid = request.child_value("e/uid") userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: profile = self.get_profile(userid) - info = self.data.local.lobby.get_play_session_info(self.game, self.version, userid) + info = self.data.local.lobby.get_play_session_info( + self.game, self.version, userid + ) if profile is None or info is None: return root @@ -141,63 +143,63 @@ class ReflecBeatGroovin(ReflecBeatBase): self.version, userid, { - 'mid': request.child_value('e/mid'), - 'ng': request.child_value('e/ng'), - 'mopt': request.child_value('e/mopt'), - 'lid': request.child_value('e/lid'), - 'sn': request.child_value('e/sn'), - 'pref': request.child_value('e/pref'), - 'stg': request.child_value('e/stg'), - 'pside': request.child_value('e/pside'), - 'eatime': request.child_value('e/eatime'), - 'ga': request.child_value('e/ga'), - 'gp': request.child_value('e/gp'), - 'la': request.child_value('e/la'), - 'ver': request.child_value('e/ver'), - 'tension': request.child_value('e/tension'), - } + "mid": request.child_value("e/mid"), + "ng": request.child_value("e/ng"), + "mopt": request.child_value("e/mopt"), + "lid": request.child_value("e/lid"), + "sn": request.child_value("e/sn"), + "pref": request.child_value("e/pref"), + "stg": request.child_value("e/stg"), + "pside": request.child_value("e/pside"), + "eatime": request.child_value("e/eatime"), + "ga": request.child_value("e/ga"), + "gp": request.child_value("e/gp"), + "la": request.child_value("e/la"), + "ver": request.child_value("e/ver"), + "tension": request.child_value("e/tension"), + }, ) lobby = self.data.local.lobby.get_lobby( self.game, self.version, userid, ) - root.add_child(Node.s32('eid', lobby.get_int('id'))) - e = Node.void('e') + root.add_child(Node.s32("eid", lobby.get_int("id"))) + e = Node.void("e") root.add_child(e) - e.add_child(Node.s32('eid', lobby.get_int('id'))) - e.add_child(Node.u16('mid', lobby.get_int('mid'))) - e.add_child(Node.u8('ng', lobby.get_int('ng'))) - e.add_child(Node.s32('uid', profile.extid)) - e.add_child(Node.s32('uattr', profile.get_int('uattr'))) - e.add_child(Node.string('pn', profile.get_str('name'))) - e.add_child(Node.s32('plyid', info.get_int('id'))) - e.add_child(Node.s16('mg', profile.get_int('mg'))) - e.add_child(Node.s32('mopt', lobby.get_int('mopt'))) - e.add_child(Node.string('lid', lobby.get_str('lid'))) - e.add_child(Node.string('sn', lobby.get_str('sn'))) - e.add_child(Node.u8('pref', lobby.get_int('pref'))) - e.add_child(Node.s8('stg', lobby.get_int('stg'))) - e.add_child(Node.s8('pside', lobby.get_int('pside'))) - e.add_child(Node.s16('eatime', lobby.get_int('eatime'))) - e.add_child(Node.u8_array('ga', lobby.get_int_array('ga', 4))) - e.add_child(Node.u16('gp', lobby.get_int('gp'))) - e.add_child(Node.u8_array('la', lobby.get_int_array('la', 4))) - e.add_child(Node.u8('ver', lobby.get_int('ver'))) - e.add_child(Node.s8('tension', lobby.get_int('tension'))) + e.add_child(Node.s32("eid", lobby.get_int("id"))) + e.add_child(Node.u16("mid", lobby.get_int("mid"))) + e.add_child(Node.u8("ng", lobby.get_int("ng"))) + e.add_child(Node.s32("uid", profile.extid)) + e.add_child(Node.s32("uattr", profile.get_int("uattr"))) + e.add_child(Node.string("pn", profile.get_str("name"))) + e.add_child(Node.s32("plyid", info.get_int("id"))) + e.add_child(Node.s16("mg", profile.get_int("mg"))) + e.add_child(Node.s32("mopt", lobby.get_int("mopt"))) + e.add_child(Node.string("lid", lobby.get_str("lid"))) + e.add_child(Node.string("sn", lobby.get_str("sn"))) + e.add_child(Node.u8("pref", lobby.get_int("pref"))) + e.add_child(Node.s8("stg", lobby.get_int("stg"))) + e.add_child(Node.s8("pside", lobby.get_int("pside"))) + e.add_child(Node.s16("eatime", lobby.get_int("eatime"))) + e.add_child(Node.u8_array("ga", lobby.get_int_array("ga", 4))) + e.add_child(Node.u16("gp", lobby.get_int("gp"))) + e.add_child(Node.u8_array("la", lobby.get_int_array("la", 4))) + e.add_child(Node.u8("ver", lobby.get_int("ver"))) + e.add_child(Node.s8("tension", lobby.get_int("tension"))) return root def handle_lobby_rb4read_request(self, request: Node) -> Node: - root = Node.void('lobby') - root.add_child(Node.s32('interval', 120)) - root.add_child(Node.s32('interval_p', 120)) + root = Node.void("lobby") + root.add_child(Node.s32("interval", 120)) + root.add_child(Node.s32("interval_p", 120)) # Look up all lobbies matching the criteria specified - ver = request.child_value('var') - mg = request.child_value('m_grade') # noqa: F841 - extid = request.child_value('uid') - limit = request.child_value('max') + ver = request.child_value("var") + mg = request.child_value("m_grade") # noqa: F841 + extid = request.child_value("uid") + limit = request.child_value("max") userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: lobbies = self.data.local.lobby.get_all_lobbies(self.game, self.version) @@ -208,63 +210,67 @@ class ReflecBeatGroovin(ReflecBeatBase): if user == userid: # If we have our own lobby, don't return it continue - if ver != lobby.get_int('ver'): + if ver != lobby.get_int("ver"): # Don't return lobby data for different versions continue profile = self.get_profile(user) - info = self.data.local.lobby.get_play_session_info(self.game, self.version, userid) + info = self.data.local.lobby.get_play_session_info( + self.game, self.version, userid + ) if profile is None or info is None: # No profile info, don't return this lobby return root - e = Node.void('e') + e = Node.void("e") root.add_child(e) - e.add_child(Node.s32('eid', lobby.get_int('id'))) - e.add_child(Node.u16('mid', lobby.get_int('mid'))) - e.add_child(Node.u8('ng', lobby.get_int('ng'))) - e.add_child(Node.s32('uid', profile.extid)) - e.add_child(Node.s32('uattr', profile.get_int('uattr'))) - e.add_child(Node.string('pn', profile.get_str('name'))) - e.add_child(Node.s32('plyid', info.get_int('id'))) - e.add_child(Node.s16('mg', profile.get_int('mg'))) - e.add_child(Node.s32('mopt', lobby.get_int('mopt'))) - e.add_child(Node.string('lid', lobby.get_str('lid'))) - e.add_child(Node.string('sn', lobby.get_str('sn'))) - e.add_child(Node.u8('pref', lobby.get_int('pref'))) - e.add_child(Node.s8('stg', lobby.get_int('stg'))) - e.add_child(Node.s8('pside', lobby.get_int('pside'))) - e.add_child(Node.s16('eatime', lobby.get_int('eatime'))) - e.add_child(Node.u8_array('ga', lobby.get_int_array('ga', 4))) - e.add_child(Node.u16('gp', lobby.get_int('gp'))) - e.add_child(Node.u8_array('la', lobby.get_int_array('la', 4))) - e.add_child(Node.u8('ver', lobby.get_int('ver'))) - e.add_child(Node.s8('tension', lobby.get_int('tension'))) + e.add_child(Node.s32("eid", lobby.get_int("id"))) + e.add_child(Node.u16("mid", lobby.get_int("mid"))) + e.add_child(Node.u8("ng", lobby.get_int("ng"))) + e.add_child(Node.s32("uid", profile.extid)) + e.add_child(Node.s32("uattr", profile.get_int("uattr"))) + e.add_child(Node.string("pn", profile.get_str("name"))) + e.add_child(Node.s32("plyid", info.get_int("id"))) + e.add_child(Node.s16("mg", profile.get_int("mg"))) + e.add_child(Node.s32("mopt", lobby.get_int("mopt"))) + e.add_child(Node.string("lid", lobby.get_str("lid"))) + e.add_child(Node.string("sn", lobby.get_str("sn"))) + e.add_child(Node.u8("pref", lobby.get_int("pref"))) + e.add_child(Node.s8("stg", lobby.get_int("stg"))) + e.add_child(Node.s8("pside", lobby.get_int("pside"))) + e.add_child(Node.s16("eatime", lobby.get_int("eatime"))) + e.add_child(Node.u8_array("ga", lobby.get_int_array("ga", 4))) + e.add_child(Node.u16("gp", lobby.get_int("gp"))) + e.add_child(Node.u8_array("la", lobby.get_int_array("la", 4))) + e.add_child(Node.u8("ver", lobby.get_int("ver"))) + e.add_child(Node.s8("tension", lobby.get_int("tension"))) limit = limit - 1 return root def handle_lobby_rb4delete_request(self, request: Node) -> Node: - eid = request.child_value('eid') + eid = request.child_value("eid") self.data.local.lobby.destroy_lobby(eid) - return Node.void('lobby') + return Node.void("lobby") def handle_shop_rb4setting_write_request(self, request: Node) -> Node: - return Node.void('shop') + return Node.void("shop") def handle_shop_rb4info_write_request(self, request: Node) -> Node: - self.update_machine_name(request.child_value('sinfo/nm')) - self.update_machine_data({ - 'close': request.child_value('sinfo/cl_enbl'), - 'hour': request.child_value('sinfo/cl_h'), - 'minute': request.child_value('sinfo/cl_m'), - 'pref': request.child_value('sinfo/prf'), - }) - return Node.void('shop') + self.update_machine_name(request.child_value("sinfo/nm")) + self.update_machine_data( + { + "close": request.child_value("sinfo/cl_enbl"), + "hour": request.child_value("sinfo/cl_h"), + "minute": request.child_value("sinfo/cl_m"), + "pref": request.child_value("sinfo/prf"), + } + ) + return Node.void("shop") def __add_event_info(self, root: Node) -> None: - event_ctrl = Node.void('event_ctrl') + event_ctrl = Node.void("event_ctrl") root.add_child(event_ctrl) # Contains zero or more nodes like: # @@ -276,7 +282,7 @@ class ReflecBeatGroovin(ReflecBeatBase): # any # - item_lock_ctrl = Node.void('item_lock_ctrl') + item_lock_ctrl = Node.void("item_lock_ctrl") root.add_child(item_lock_ctrl) # Contains zero or more nodes like: # @@ -286,26 +292,30 @@ class ReflecBeatGroovin(ReflecBeatBase): # def __add_shop_score(self, root: Node) -> None: - shop_score = Node.void('shop_score') + shop_score = Node.void("shop_score") root.add_child(shop_score) - today = Node.void('today') + today = Node.void("today") shop_score.add_child(today) - yesterday = Node.void('yesterday') + yesterday = Node.void("yesterday") shop_score.add_child(yesterday) all_profiles = self.data.local.user.get_all_profiles(self.game, self.version) - all_attempts = self.data.local.music.get_all_attempts(self.game, self.version, timelimit=(Time.beginning_of_today() - Time.SECONDS_IN_DAY)) + all_attempts = self.data.local.music.get_all_attempts( + self.game, + self.version, + timelimit=(Time.beginning_of_today() - Time.SECONDS_IN_DAY), + ) machine = self.data.local.machine.get_machine(self.config.machine.pcbid) if machine.arcade is not None: lids = [ - machine.id for machine in self.data.local.machine.get_all_machines(machine.arcade) + machine.id + for machine in self.data.local.machine.get_all_machines(machine.arcade) ] else: lids = [machine.id] relevant_profiles = [ - profile for profile in all_profiles - if profile[1].get_int('lid', -1) in lids + profile for profile in all_profiles if profile[1].get_int("lid", -1) in lids ] for (rootnode, timeoffset) in [ @@ -314,10 +324,11 @@ class ReflecBeatGroovin(ReflecBeatBase): ]: # Grab all attempts made in the relevant day relevant_attempts = [ - attempt for attempt in all_attempts + attempt + for attempt in all_attempts if ( - attempt[1].timestamp >= (Time.beginning_of_today() - timeoffset) and - attempt[1].timestamp <= (Time.end_of_today() - timeoffset) + attempt[1].timestamp >= (Time.beginning_of_today() - timeoffset) + and attempt[1].timestamp <= (Time.end_of_today() - timeoffset) ) ] @@ -333,7 +344,10 @@ class ReflecBeatGroovin(ReflecBeatBase): scores_by_user[userid][attempt.id][attempt.chart] = attempt else: # If this attempt is better than the stored one, replace it - if scores_by_user[userid][attempt.id][attempt.chart].points < attempt.points: + if ( + scores_by_user[userid][attempt.id][attempt.chart].points + < attempt.points + ): scores_by_user[userid][attempt.id][attempt.chart] = attempt # Calculate points earned by user in the day @@ -342,53 +356,68 @@ class ReflecBeatGroovin(ReflecBeatBase): points_by_user[userid] = 0 for mid in scores_by_user[userid]: for chart in scores_by_user[userid][mid]: - points_by_user[userid] = points_by_user[userid] + scores_by_user[userid][mid][chart].points + points_by_user[userid] = ( + points_by_user[userid] + + scores_by_user[userid][mid][chart].points + ) # Output that day's earned points for (userid, profile) in relevant_profiles: - data = Node.void('data') + data = Node.void("data") rootnode.add_child(data) - data.add_child(Node.s16('day_id', int((Time.now() - timeoffset) / Time.SECONDS_IN_DAY))) - data.add_child(Node.s32('user_id', profile.extid)) - data.add_child(Node.s16('icon_id', profile.get_dict('config').get_int('icon_id'))) - data.add_child(Node.s16('point', min(points_by_user.get(userid, 0), 32767))) - data.add_child(Node.s32('update_time', Time.now())) - data.add_child(Node.string('name', profile.get_str('name'))) + data.add_child( + Node.s16( + "day_id", int((Time.now() - timeoffset) / Time.SECONDS_IN_DAY) + ) + ) + data.add_child(Node.s32("user_id", profile.extid)) + data.add_child( + Node.s16("icon_id", profile.get_dict("config").get_int("icon_id")) + ) + data.add_child( + Node.s16("point", min(points_by_user.get(userid, 0), 32767)) + ) + data.add_child(Node.s32("update_time", Time.now())) + data.add_child(Node.string("name", profile.get_str("name"))) - rootnode.add_child(Node.s32('timestamp', Time.beginning_of_today() - timeoffset)) + rootnode.add_child( + Node.s32("timestamp", Time.beginning_of_today() - timeoffset) + ) def handle_info_rb4common_request(self, request: Node) -> Node: - root = Node.void('info') + root = Node.void("info") self.__add_event_info(root) self.__add_shop_score(root) return root def handle_info_rb4ranking_request(self, request: Node) -> Node: - version = request.child_value('ver') + version = request.child_value("ver") - root = Node.void('info') - root.add_child(Node.s32('ver', version)) - ranking = Node.void('ranking') + root = Node.void("info") + root.add_child(Node.s32("ver", version)) + ranking = Node.void("ranking") root.add_child(ranking) - def add_hitchart(name: str, start: int, end: int, hitchart: List[Tuple[int, int]]) -> None: + def add_hitchart( + name: str, start: int, end: int, hitchart: List[Tuple[int, int]] + ) -> None: base = Node.void(name) ranking.add_child(base) - base.add_child(Node.s32('bt', start)) - base.add_child(Node.s32('et', end)) - new = Node.void('new') + base.add_child(Node.s32("bt", start)) + base.add_child(Node.s32("et", end)) + new = Node.void("new") base.add_child(new) for (mid, plays) in hitchart: - d = Node.void('d') + d = Node.void("d") new.add_child(d) - d.add_child(Node.s16('mid', mid)) - d.add_child(Node.s32('cnt', plays)) + d.add_child(Node.s16("mid", mid)) + d.add_child(Node.s32("cnt", plays)) # Weekly hit chart add_hitchart( - 'weekly', + "weekly", Time.now() - Time.SECONDS_IN_WEEK, Time.now(), self.data.local.music.get_hit_chart(self.game, self.version, 1024, 7), @@ -396,7 +425,7 @@ class ReflecBeatGroovin(ReflecBeatBase): # Monthly hit chart add_hitchart( - 'monthly', + "monthly", Time.now() - Time.SECONDS_IN_DAY * 30, Time.now(), self.data.local.music.get_hit_chart(self.game, self.version, 1024, 30), @@ -404,7 +433,7 @@ class ReflecBeatGroovin(ReflecBeatBase): # All time hit chart add_hitchart( - 'total', + "total", Time.now() - Time.SECONDS_IN_DAY * 365, Time.now(), self.data.local.music.get_hit_chart(self.game, self.version, 1024, 365), @@ -413,13 +442,13 @@ class ReflecBeatGroovin(ReflecBeatBase): return root def handle_info_rb4shop_score_ranking_request(self, request: Node) -> Node: - start_music_id = request.child_value('min') - end_music_id = request.child_value('max') + start_music_id = request.child_value("min") + end_music_id = request.child_value("max") - root = Node.void('info') - shop_score = Node.void('shop_score') + root = Node.void("info") + shop_score = Node.void("shop_score") root.add_child(shop_score) - shop_score.add_child(Node.s32('time', Time.now())) + shop_score.add_child(Node.s32("time", Time.now())) profiles: Dict[UserID, Profile] = {} for songid in range(start_music_id, end_music_id + 1): @@ -447,39 +476,48 @@ class ReflecBeatGroovin(ReflecBeatBase): profiles[userid] = self.get_any_profile(userid) profile = profiles[userid] - data = Node.void('data') + data = Node.void("data") shop_score.add_child(data) - data.add_child(Node.s32('rank', i + 1)) - data.add_child(Node.s16('music_id', songid)) - data.add_child(Node.s8('note_grade', score.chart)) - data.add_child(Node.s8('clear_type', self.__db_to_game_clear_type(score.data.get_int('clear_type')))) - data.add_child(Node.s32('user_id', profile.extid)) - data.add_child(Node.s16('icon_id', profile.get_dict('config').get_int('icon_id'))) - data.add_child(Node.s32('score', score.points)) - data.add_child(Node.s32('time', score.timestamp)) - data.add_child(Node.string('name', profile.get_str('name'))) + data.add_child(Node.s32("rank", i + 1)) + data.add_child(Node.s16("music_id", songid)) + data.add_child(Node.s8("note_grade", score.chart)) + data.add_child( + Node.s8( + "clear_type", + self.__db_to_game_clear_type( + score.data.get_int("clear_type") + ), + ) + ) + data.add_child(Node.s32("user_id", profile.extid)) + data.add_child( + Node.s16( + "icon_id", profile.get_dict("config").get_int("icon_id") + ) + ) + data.add_child(Node.s32("score", score.points)) + data.add_child(Node.s32("time", score.timestamp)) + data.add_child(Node.string("name", profile.get_str("name"))) return root def handle_info_rb4pzlcmt_read_request(self, request: Node) -> Node: - extid = request.child_value('uid') - locid = ID.parse_machine_id(request.child_value('lid')) - limit = request.child_value('limit') + extid = request.child_value("uid") + locid = ID.parse_machine_id(request.child_value("lid")) + limit = request.child_value("limit") userid = self.data.remote.user.from_extid(self.game, self.version, extid) comments = [ - achievement for achievement in - self.data.local.user.get_all_time_based_achievements(self.game, self.version) - if achievement[1].type == 'puzzle_comment' + achievement + for achievement in self.data.local.user.get_all_time_based_achievements( + self.game, self.version + ) + if achievement[1].type == "puzzle_comment" ] comments.sort(key=lambda x: x[1].timestamp, reverse=True) - favorites = [ - comment for comment in comments - if comment[0] == userid - ] + favorites = [comment for comment in comments if comment[0] == userid] locationcomments = [ - comment for comment in comments - if comment[1].data.get_int('locid') == locid + comment for comment in comments if comment[1].data.get_int("locid") == locid ] # Cap all comment blocks to the limit @@ -488,10 +526,10 @@ class ReflecBeatGroovin(ReflecBeatBase): favorites = favorites[:limit] locationcomments = locationcomments[:limit] - root = Node.void('info') - comment = Node.void('comment') + root = Node.void("info") + comment = Node.void("comment") root.add_child(comment) - comment.add_child(Node.s32('time', Time.now())) + comment.add_child(Node.s32("time", Time.now())) # Mapping of profiles to userIDs uid_mapping = { @@ -504,47 +542,49 @@ class ReflecBeatGroovin(ReflecBeatBase): self.version, "", 0, - {'name': 'PLAYER'}, + {"name": "PLAYER"}, ) def add_comments(name: str, selected: List[Tuple[UserID, Achievement]]) -> None: for (uid, ach) in selected: cmnt = Node.void(name) root.add_child(cmnt) - cmnt.add_child(Node.s32('uid', uid_mapping[uid].extid)) - cmnt.add_child(Node.string('name', uid_mapping[uid].get_str('name'))) - cmnt.add_child(Node.s16('icon', ach.data.get_int('icon'))) - cmnt.add_child(Node.s8('bln', ach.data.get_int('bln'))) - cmnt.add_child(Node.string('lid', ID.format_machine_id(ach.data.get_int('locid')))) - cmnt.add_child(Node.s8('pref', ach.data.get_int('prefecture'))) - cmnt.add_child(Node.s32('time', ach.timestamp)) - cmnt.add_child(Node.string('comment', ach.data.get_str('comment'))) - cmnt.add_child(Node.bool('is_tweet', ach.data.get_bool('tweet'))) + cmnt.add_child(Node.s32("uid", uid_mapping[uid].extid)) + cmnt.add_child(Node.string("name", uid_mapping[uid].get_str("name"))) + cmnt.add_child(Node.s16("icon", ach.data.get_int("icon"))) + cmnt.add_child(Node.s8("bln", ach.data.get_int("bln"))) + cmnt.add_child( + Node.string("lid", ID.format_machine_id(ach.data.get_int("locid"))) + ) + cmnt.add_child(Node.s8("pref", ach.data.get_int("prefecture"))) + cmnt.add_child(Node.s32("time", ach.timestamp)) + cmnt.add_child(Node.string("comment", ach.data.get_str("comment"))) + cmnt.add_child(Node.bool("is_tweet", ach.data.get_bool("tweet"))) # Add all comments - add_comments('c', comments) + add_comments("c", comments) # Add personal comments (favorites) - add_comments('cf', favorites) + add_comments("cf", favorites) # Add location comments - add_comments('cs', locationcomments) + add_comments("cs", locationcomments) return root def handle_info_rb4pzlcmt_write_request(self, request: Node) -> Node: - extid = request.child_value('uid') + extid = request.child_value("uid") userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is None: # Anonymous comment userid = UserID(0) - icon = request.child_value('icon') - bln = request.child_value('bln') - locid = ID.parse_machine_id(request.child_value('lid')) - prefecture = request.child_value('pref') - comment = request.child_value('comment') - is_tweet = request.child_value('is_tweet') + icon = request.child_value("icon") + bln = request.child_value("bln") + locid = ID.parse_machine_id(request.child_value("lid")) + prefecture = request.child_value("pref") + comment = request.child_value("comment") + is_tweet = request.child_value("is_tweet") # Link comment to user's profile self.data.local.user.put_time_based_achievement( @@ -552,24 +592,24 @@ class ReflecBeatGroovin(ReflecBeatBase): self.version, userid, 0, # We never have an ID for this, since comments are add-only - 'puzzle_comment', + "puzzle_comment", { - 'icon': icon, - 'bln': bln, - 'locid': locid, - 'prefecture': prefecture, - 'comment': comment, - 'tweet': is_tweet, + "icon": icon, + "bln": bln, + "locid": locid, + "prefecture": prefecture, + "comment": comment, + "tweet": is_tweet, }, ) - return Node.void('info') + return Node.void("info") def handle_player_rb4start_request(self, request: Node) -> Node: - root = Node.void('player') + root = Node.void("player") # Create a new play session based on info from the request - refid = request.child_value('rid') + refid = request.child_value("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: self.data.local.lobby.put_play_session_info( @@ -577,10 +617,10 @@ class ReflecBeatGroovin(ReflecBeatBase): self.version, userid, { - 'ga': request.child_value('ga'), - 'gp': request.child_value('gp'), - 'la': request.child_value('la'), - 'pnid': request.child_value('pnid'), + "ga": request.child_value("ga"), + "gp": request.child_value("gp"), + "la": request.child_value("la"), + "pnid": request.child_value("pnid"), }, ) info = self.data.local.lobby.get_play_session_info( @@ -589,22 +629,22 @@ class ReflecBeatGroovin(ReflecBeatBase): userid, ) if info is not None: - play_id = info.get_int('id') + play_id = info.get_int("id") else: play_id = 0 else: play_id = 0 # Session stuff, and resend global defaults - root.add_child(Node.s32('plyid', play_id)) - root.add_child(Node.u64('start_time', Time.now() * 1000)) + root.add_child(Node.s32("plyid", play_id)) + root.add_child(Node.u64("start_time", Time.now() * 1000)) self.__add_event_info(root) return root def handle_player_rb4end_request(self, request: Node) -> Node: # Destroy play session based on info from the request - refid = request.child_value('rid') + refid = request.child_value("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: # Kill any lingering lobbies by this user @@ -614,112 +654,129 @@ class ReflecBeatGroovin(ReflecBeatBase): userid, ) if lobby is not None: - self.data.local.lobby.destroy_lobby(lobby.get_int('id')) - self.data.local.lobby.destroy_play_session_info(self.game, self.version, userid) + self.data.local.lobby.destroy_lobby(lobby.get_int("id")) + self.data.local.lobby.destroy_play_session_info( + self.game, self.version, userid + ) - return Node.void('player') + return Node.void("player") def handle_player_rb4readepisode_request(self, request: Node) -> Node: - extid = request.child_value('user_id') + extid = request.child_value("user_id") userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: - achievements = self.data.local.user.get_achievements(self.game, self.version, userid) + achievements = self.data.local.user.get_achievements( + self.game, self.version, userid + ) else: achievements = [] - root = Node.void('player') - pdata = Node.void('pdata') + root = Node.void("player") + pdata = Node.void("pdata") root.add_child(pdata) - episode = Node.void('episode') + episode = Node.void("episode") pdata.add_child(episode) for achievement in achievements: - if achievement.type != 'episode': + if achievement.type != "episode": continue - info = Node.void('info') + info = Node.void("info") episode.add_child(info) - info.add_child(Node.s32('user_id', extid)) - info.add_child(Node.u8('type', achievement.id)) - info.add_child(Node.u16('value0', achievement.data.get_int('value0'))) - info.add_child(Node.u16('value1', achievement.data.get_int('value1'))) - info.add_child(Node.string('text', achievement.data.get_str('text'))) - info.add_child(Node.s32('time', achievement.data.get_int('time'))) + info.add_child(Node.s32("user_id", extid)) + info.add_child(Node.u8("type", achievement.id)) + info.add_child(Node.u16("value0", achievement.data.get_int("value0"))) + info.add_child(Node.u16("value1", achievement.data.get_int("value1"))) + info.add_child(Node.string("text", achievement.data.get_str("text"))) + info.add_child(Node.s32("time", achievement.data.get_int("time"))) return root def handle_player_rb4readscore_request(self, request: Node) -> Node: - refid = request.child_value('rid') + refid = request.child_value("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is None: scores: List[Score] = [] else: scores = self.data.remote.music.get_scores(self.game, self.version, userid) - root = Node.void('player') - pdata = Node.void('pdata') + root = Node.void("player") + pdata = Node.void("pdata") root.add_child(pdata) - record = Node.void('record') + record = Node.void("record") pdata.add_child(record) - record_old = Node.void('record_old') + record_old = Node.void("record_old") pdata.add_child(record_old) for score in scores: - rec = Node.void('rec') + rec = Node.void("rec") record.add_child(rec) - rec.add_child(Node.s16('mid', score.id)) - rec.add_child(Node.s8('ntgrd', score.chart)) - rec.add_child(Node.s32('pc', score.plays)) - rec.add_child(Node.s8('ct', self.__db_to_game_clear_type(score.data.get_int('clear_type')))) - rec.add_child(Node.s16('ar', score.data.get_int('achievement_rate'))) - rec.add_child(Node.s16('scr', score.points)) - rec.add_child(Node.s16('ms', score.data.get_int('miss_count'))) - rec.add_child(Node.s16( - 'param', - self.__db_to_game_combo_type(score.data.get_int('combo_type')) + score.data.get_int('param'), - )) - rec.add_child(Node.s32('bscrt', score.timestamp)) - rec.add_child(Node.s32('bart', score.data.get_int('best_achievement_rate_time'))) - rec.add_child(Node.s32('bctt', score.data.get_int('best_clear_type_time'))) - rec.add_child(Node.s32('bmst', score.data.get_int('best_miss_count_time'))) - rec.add_child(Node.s32('time', score.data.get_int('last_played_time'))) + rec.add_child(Node.s16("mid", score.id)) + rec.add_child(Node.s8("ntgrd", score.chart)) + rec.add_child(Node.s32("pc", score.plays)) + rec.add_child( + Node.s8( + "ct", self.__db_to_game_clear_type(score.data.get_int("clear_type")) + ) + ) + rec.add_child(Node.s16("ar", score.data.get_int("achievement_rate"))) + rec.add_child(Node.s16("scr", score.points)) + rec.add_child(Node.s16("ms", score.data.get_int("miss_count"))) + rec.add_child( + Node.s16( + "param", + self.__db_to_game_combo_type(score.data.get_int("combo_type")) + + score.data.get_int("param"), + ) + ) + rec.add_child(Node.s32("bscrt", score.timestamp)) + rec.add_child( + Node.s32("bart", score.data.get_int("best_achievement_rate_time")) + ) + rec.add_child(Node.s32("bctt", score.data.get_int("best_clear_type_time"))) + rec.add_child(Node.s32("bmst", score.data.get_int("best_miss_count_time"))) + rec.add_child(Node.s32("time", score.data.get_int("last_played_time"))) return root def handle_player_rb4selectscore_request(self, request: Node) -> Node: - extid = request.child_value('uid') - songid = request.child_value('music_id') - chart = request.child_value('note_grade') + extid = request.child_value("uid") + songid = request.child_value("music_id") + chart = request.child_value("note_grade") userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is None: score = None profile = None else: - score = self.data.remote.music.get_score(self.game, self.version, userid, songid, chart) + score = self.data.remote.music.get_score( + self.game, self.version, userid, songid, chart + ) profile = self.get_any_profile(userid) - root = Node.void('player') + root = Node.void("player") if score is not None and profile is not None: - player_select_score = Node.void('player_select_score') + player_select_score = Node.void("player_select_score") root.add_child(player_select_score) - player_select_score.add_child(Node.s32('user_id', extid)) - player_select_score.add_child(Node.string('name', profile.get_str('name'))) - player_select_score.add_child(Node.s32('m_score', score.points)) - player_select_score.add_child(Node.s32('m_scoreTime', score.timestamp)) - player_select_score.add_child(Node.s16('m_iconID', profile.get_dict('config').get_int('icon_id'))) + player_select_score.add_child(Node.s32("user_id", extid)) + player_select_score.add_child(Node.string("name", profile.get_str("name"))) + player_select_score.add_child(Node.s32("m_score", score.points)) + player_select_score.add_child(Node.s32("m_scoreTime", score.timestamp)) + player_select_score.add_child( + Node.s16("m_iconID", profile.get_dict("config").get_int("icon_id")) + ) return root def handle_player_rbsvLinkageSave_request(self, request: Node) -> Node: # I think this is ReflecBeat/SoundVoltex linkage save, and I # am somewhat convinced that PK/BN is for packets/blocks, but # whatever. - root = Node.void('player') - root.add_child(Node.s32('before_pk_value', -1)) - root.add_child(Node.s32('after_pk_value', -1)) - root.add_child(Node.s32('before_bn_value', -1)) - root.add_child(Node.s32('after_bn_value', -1)) + root = Node.void("player") + root.add_child(Node.s32("before_pk_value", -1)) + root.add_child(Node.s32("after_pk_value", -1)) + root.add_child(Node.s32("before_bn_value", -1)) + root.add_child(Node.s32("after_bn_value", -1)) return root def handle_player_rb4total_bestallrank_read_request(self, request: Node) -> Node: @@ -736,7 +793,7 @@ class ReflecBeatGroovin(ReflecBeatBase): # second and third values appear unused in-game. I think this is supposed # to give a player the idea of what ranking they are on the server for # various scores. - current_scores = request.child_value('score') + current_scores = request.child_value("score") # First, grab all scores on the network for this version, and all songs # available so we know which songs are new to this version of the game. @@ -744,7 +801,11 @@ class ReflecBeatGroovin(ReflecBeatBase): all_songs = self.data.local.music.get_all_songs(self.game, self.version) # Figure out what song IDs are new - new_songs = {song.id for song in all_songs if song.data.get_int('folder', 0) == self.version} + new_songs = { + song.id + for song in all_songs + if song.data.get_int("folder", 0) == self.version + } # Now grab all participating users that had scores all_users = {userid for (userid, score) in all_scores} @@ -753,8 +814,11 @@ class ReflecBeatGroovin(ReflecBeatBase): # scores where the user at least cleared the song. scores_by_user = { userid: [ - score for (uid, score) in all_scores - if uid == userid and score.data.get_int('clear_type') >= self.CLEAR_TYPE_CLEARED] + score + for (uid, score) in all_scores + if uid == userid + and score.data.get_int("clear_type") >= self.CLEAR_TYPE_CLEARED + ] for userid in all_users } @@ -768,28 +832,52 @@ class ReflecBeatGroovin(ReflecBeatBase): ) basic_scores = sorted( [ - sum([score.points for score in scores if score.chart == self.CHART_TYPE_BASIC]) + sum( + [ + score.points + for score in scores + if score.chart == self.CHART_TYPE_BASIC + ] + ) for userid, scores in scores_by_user.items() ], reverse=True, ) medium_scores = sorted( [ - sum([score.points for score in scores if score.chart == self.CHART_TYPE_MEDIUM]) + sum( + [ + score.points + for score in scores + if score.chart == self.CHART_TYPE_MEDIUM + ] + ) for userid, scores in scores_by_user.items() ], reverse=True, ) hard_scores = sorted( [ - sum([score.points for score in scores if score.chart == self.CHART_TYPE_HARD]) + sum( + [ + score.points + for score in scores + if score.chart == self.CHART_TYPE_HARD + ] + ) for userid, scores in scores_by_user.items() ], reverse=True, ) special_scores = sorted( [ - sum([score.points for score in scores if score.chart == self.CHART_TYPE_SPECIAL]) + sum( + [ + score.points + for score in scores + if score.chart == self.CHART_TYPE_SPECIAL + ] + ) for userid, scores in scores_by_user.items() ], reverse=True, @@ -829,524 +917,610 @@ class ReflecBeatGroovin(ReflecBeatBase): break user_place[i] = user_place[i] + 1 - root = Node.void('player') - scorenode = Node.void('score') + root = Node.void("player") + scorenode = Node.void("score") root.add_child(scorenode) - scorenode.add_child(Node.s32_array('rank', user_place)) - scorenode.add_child(Node.s32_array('score', [0] * 6)) - scorenode.add_child(Node.s32_array('allrank', [len(total_scores)] * 6)) + scorenode.add_child(Node.s32_array("rank", user_place)) + scorenode.add_child(Node.s32_array("score", [0] * 6)) + scorenode.add_child(Node.s32_array("allrank", [len(total_scores)] * 6)) return root def handle_player_rb4delete_request(self, request: Node) -> Node: - return Node.void('player') + return Node.void("player") def handle_player_rb4read_request(self, request: Node) -> Node: - refid = request.child_value('rid') + refid = request.child_value("rid") profile = self.get_profile_by_refid(refid) if profile: return profile - return Node.void('player') + return Node.void("player") def handle_player_rb4succeed_request(self, request: Node) -> Node: - refid = request.child_value('rid') + refid = request.child_value("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: previous_version = self.previous_version() profile = previous_version.get_profile(userid) - achievements = self.data.local.user.get_achievements(previous_version.game, previous_version.version, userid) - scores = self.data.remote.music.get_scores(previous_version.game, previous_version.version, userid) + achievements = self.data.local.user.get_achievements( + previous_version.game, previous_version.version, userid + ) + scores = self.data.remote.music.get_scores( + previous_version.game, previous_version.version, userid + ) else: profile = None - root = Node.void('player') + root = Node.void("player") if profile is None: # Return empty succeed to say this is new - root.add_child(Node.string('name', '')) - root.add_child(Node.s16('lv', -1)) - root.add_child(Node.s32('exp', -1)) - root.add_child(Node.s32('grd', -1)) - root.add_child(Node.s32('ap', -1)) - root.add_child(Node.s32('money', -1)) - root.add_child(Node.void('released')) - root.add_child(Node.void('mrecord')) + root.add_child(Node.string("name", "")) + root.add_child(Node.s16("lv", -1)) + root.add_child(Node.s32("exp", -1)) + root.add_child(Node.s32("grd", -1)) + root.add_child(Node.s32("ap", -1)) + root.add_child(Node.s32("money", -1)) + root.add_child(Node.void("released")) + root.add_child(Node.void("mrecord")) else: # Return previous profile formatted to say this is data succession - root.add_child(Node.string('name', profile.get_str('name'))) - root.add_child(Node.s16('lv', profile.get_int('lvl'))) - root.add_child(Node.s32('exp', profile.get_int('exp'))) - root.add_child(Node.s32('grd', profile.get_int('mg'))) # This is a guess - root.add_child(Node.s32('ap', profile.get_int('ap'))) - root.add_child(Node.s32('money', 0)) + root.add_child(Node.string("name", profile.get_str("name"))) + root.add_child(Node.s16("lv", profile.get_int("lvl"))) + root.add_child(Node.s32("exp", profile.get_int("exp"))) + root.add_child(Node.s32("grd", profile.get_int("mg"))) # This is a guess + root.add_child(Node.s32("ap", profile.get_int("ap"))) + root.add_child(Node.s32("money", 0)) - released = Node.void('released') + released = Node.void("released") root.add_child(released) for item in achievements: - if item.type != 'item_0': + if item.type != "item_0": continue - released.add_child(Node.s16('i', item.id)) + released.add_child(Node.s16("i", item.id)) - mrecord = Node.void('mrecord') + mrecord = Node.void("mrecord") root.add_child(mrecord) for score in scores: - mrec = Node.void('mrec') + mrec = Node.void("mrec") mrecord.add_child(mrec) - mrec.add_child(Node.s16('mid', score.id)) - mrec.add_child(Node.s8('ntgrd', score.chart)) - mrec.add_child(Node.s32('pc', score.plays)) - mrec.add_child(Node.s8('ct', self.__db_to_game_clear_type(score.data.get_int('clear_type')))) - mrec.add_child(Node.s16('ar', score.data.get_int('achievement_rate'))) - mrec.add_child(Node.s16('scr', score.points)) - mrec.add_child(Node.s16('ms', score.data.get_int('miss_count'))) - mrec.add_child(Node.u16('ver', 0)) - mrec.add_child(Node.s32('bst', score.timestamp)) - mrec.add_child(Node.s32('bat', score.data.get_int('best_achievement_rate_time'))) - mrec.add_child(Node.s32('bct', score.data.get_int('best_clear_type_time'))) - mrec.add_child(Node.s32('bmt', score.data.get_int('best_miss_count_time'))) + mrec.add_child(Node.s16("mid", score.id)) + mrec.add_child(Node.s8("ntgrd", score.chart)) + mrec.add_child(Node.s32("pc", score.plays)) + mrec.add_child( + Node.s8( + "ct", + self.__db_to_game_clear_type(score.data.get_int("clear_type")), + ) + ) + mrec.add_child(Node.s16("ar", score.data.get_int("achievement_rate"))) + mrec.add_child(Node.s16("scr", score.points)) + mrec.add_child(Node.s16("ms", score.data.get_int("miss_count"))) + mrec.add_child(Node.u16("ver", 0)) + mrec.add_child(Node.s32("bst", score.timestamp)) + mrec.add_child( + Node.s32("bat", score.data.get_int("best_achievement_rate_time")) + ) + mrec.add_child( + Node.s32("bct", score.data.get_int("best_clear_type_time")) + ) + mrec.add_child( + Node.s32("bmt", score.data.get_int("best_miss_count_time")) + ) return root def handle_player_rb4write_request(self, request: Node) -> Node: - refid = request.child_value('pdata/account/rid') + refid = request.child_value("pdata/account/rid") profile = self.put_profile_by_refid(refid, request) - root = Node.void('player') + root = Node.void("player") if profile is None: - root.add_child(Node.s32('uid', 0)) + root.add_child(Node.s32("uid", 0)) else: - root.add_child(Node.s32('uid', profile.extid)) + root.add_child(Node.s32("uid", profile.extid)) return root def format_profile(self, userid: UserID, profile: Profile) -> Node: statistics = self.get_play_statistics(userid) game_config = self.get_game_config() - achievements = self.data.local.user.get_achievements(self.game, self.version, userid) + achievements = self.data.local.user.get_achievements( + self.game, self.version, userid + ) links = self.data.local.user.get_links(self.game, self.version, userid) - root = Node.void('player') - pdata = Node.void('pdata') + root = Node.void("player") + pdata = Node.void("pdata") root.add_child(pdata) # Account info - account = Node.void('account') + account = Node.void("account") pdata.add_child(account) - account.add_child(Node.s32('usrid', profile.extid)) - account.add_child(Node.s32('tpc', statistics.total_plays)) - account.add_child(Node.s32('dpc', statistics.today_plays)) - account.add_child(Node.s32('crd', 1)) - account.add_child(Node.s32('brd', 1)) - account.add_child(Node.s32('tdc', statistics.total_days)) - account.add_child(Node.s32('intrvld', 0)) - account.add_child(Node.s16('ver', 1)) - account.add_child(Node.u64('pst', 0)) - account.add_child(Node.u64('st', Time.now() * 1000)) - account.add_child(Node.u8('debutVer', 2)) + account.add_child(Node.s32("usrid", profile.extid)) + account.add_child(Node.s32("tpc", statistics.total_plays)) + account.add_child(Node.s32("dpc", statistics.today_plays)) + account.add_child(Node.s32("crd", 1)) + account.add_child(Node.s32("brd", 1)) + account.add_child(Node.s32("tdc", statistics.total_days)) + account.add_child(Node.s32("intrvld", 0)) + account.add_child(Node.s16("ver", 1)) + account.add_child(Node.u64("pst", 0)) + account.add_child(Node.u64("st", Time.now() * 1000)) + account.add_child(Node.u8("debutVer", 2)) # Base profile info - base = Node.void('base') + base = Node.void("base") pdata.add_child(base) - base.add_child(Node.string('name', profile.get_str('name'))) - base.add_child(Node.s32('exp', profile.get_int('exp'))) - base.add_child(Node.s32('lv', profile.get_int('lvl'))) - base.add_child(Node.s32('mg', profile.get_int('mg'))) - base.add_child(Node.s32('ap', profile.get_int('ap'))) - base.add_child(Node.string('cmnt', '')) - base.add_child(Node.s32('uattr', profile.get_int('uattr'))) - base.add_child(Node.s32('money', profile.get_int('money'))) - base.add_child(Node.s32('tbs', -1)) - base.add_child(Node.s32('tbs_r', -1)) - base.add_child(Node.s32('tbgs', -1)) - base.add_child(Node.s32('tbgs_r', -1)) - base.add_child(Node.s32('tbms', -1)) - base.add_child(Node.s32('tbms_r', -1)) - base.add_child(Node.s32('qe_win', -1)) - base.add_child(Node.s32('qe_legend', -1)) - base.add_child(Node.s32('qe2_win', -1)) - base.add_child(Node.s32('qe2_legend', -1)) - base.add_child(Node.s32('qe3_win', -1)) - base.add_child(Node.s32('qe3_legend', -1)) - base.add_child(Node.s16_array('mlog', [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1])) - base.add_child(Node.s32('class', profile.get_int('class'))) - base.add_child(Node.s32('class_ar', profile.get_int('class_ar'))) - base.add_child(Node.s32('getrfl', -1)) - base.add_child(Node.s32('upper_pt', profile.get_int('upper_pt'))) + base.add_child(Node.string("name", profile.get_str("name"))) + base.add_child(Node.s32("exp", profile.get_int("exp"))) + base.add_child(Node.s32("lv", profile.get_int("lvl"))) + base.add_child(Node.s32("mg", profile.get_int("mg"))) + base.add_child(Node.s32("ap", profile.get_int("ap"))) + base.add_child(Node.string("cmnt", "")) + base.add_child(Node.s32("uattr", profile.get_int("uattr"))) + base.add_child(Node.s32("money", profile.get_int("money"))) + base.add_child(Node.s32("tbs", -1)) + base.add_child(Node.s32("tbs_r", -1)) + base.add_child(Node.s32("tbgs", -1)) + base.add_child(Node.s32("tbgs_r", -1)) + base.add_child(Node.s32("tbms", -1)) + base.add_child(Node.s32("tbms_r", -1)) + base.add_child(Node.s32("qe_win", -1)) + base.add_child(Node.s32("qe_legend", -1)) + base.add_child(Node.s32("qe2_win", -1)) + base.add_child(Node.s32("qe2_legend", -1)) + base.add_child(Node.s32("qe3_win", -1)) + base.add_child(Node.s32("qe3_legend", -1)) + base.add_child(Node.s16_array("mlog", [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1])) + base.add_child(Node.s32("class", profile.get_int("class"))) + base.add_child(Node.s32("class_ar", profile.get_int("class_ar"))) + base.add_child(Node.s32("getrfl", -1)) + base.add_child(Node.s32("upper_pt", profile.get_int("upper_pt"))) # Rivals - rival = Node.void('rival') + rival = Node.void("rival") pdata.add_child(rival) slotid = 0 for link in links: - if link.type != 'rival': + if link.type != "rival": continue rprofile = self.get_profile(link.other_userid) if rprofile is None: continue - lobbyinfo = self.data.local.lobby.get_play_session_info(self.game, self.version, link.other_userid) + lobbyinfo = self.data.local.lobby.get_play_session_info( + self.game, self.version, link.other_userid + ) if lobbyinfo is None: lobbyinfo = ValidatedDict() - r = Node.void('r') + r = Node.void("r") rival.add_child(r) - r.add_child(Node.s32('slot_id', slotid)) - r.add_child(Node.s32('id', rprofile.extid)) - r.add_child(Node.string('name', rprofile.get_str('name'))) - r.add_child(Node.s32('icon', profile.get_dict('config').get_int('icon_id'))) - r.add_child(Node.s32('m_level', profile.get_int('mg'))) - r.add_child(Node.s32('class', profile.get_int('class'))) - r.add_child(Node.s32('class_ar', profile.get_int('class_ar'))) - r.add_child(Node.bool('friend', True)) - r.add_child(Node.bool('target', False)) - r.add_child(Node.u32('time', lobbyinfo.get_int('time'))) - r.add_child(Node.u8_array('ga', lobbyinfo.get_int_array('ga', 4))) - r.add_child(Node.u16('gp', lobbyinfo.get_int('gp'))) - r.add_child(Node.u8_array('ipn', lobbyinfo.get_int_array('la', 4))) - r.add_child(Node.u8_array('pnid', lobbyinfo.get_int_array('pnid', 16))) + r.add_child(Node.s32("slot_id", slotid)) + r.add_child(Node.s32("id", rprofile.extid)) + r.add_child(Node.string("name", rprofile.get_str("name"))) + r.add_child(Node.s32("icon", profile.get_dict("config").get_int("icon_id"))) + r.add_child(Node.s32("m_level", profile.get_int("mg"))) + r.add_child(Node.s32("class", profile.get_int("class"))) + r.add_child(Node.s32("class_ar", profile.get_int("class_ar"))) + r.add_child(Node.bool("friend", True)) + r.add_child(Node.bool("target", False)) + r.add_child(Node.u32("time", lobbyinfo.get_int("time"))) + r.add_child(Node.u8_array("ga", lobbyinfo.get_int_array("ga", 4))) + r.add_child(Node.u16("gp", lobbyinfo.get_int("gp"))) + r.add_child(Node.u8_array("ipn", lobbyinfo.get_int_array("la", 4))) + r.add_child(Node.u8_array("pnid", lobbyinfo.get_int_array("pnid", 16))) slotid = slotid + 1 # Stamps - stamp = Node.void('stamp') - stampdict = profile.get_dict('stamp') + stamp = Node.void("stamp") + stampdict = profile.get_dict("stamp") pdata.add_child(stamp) - stamp.add_child(Node.s32_array('stmpcnt', stampdict.get_int_array('stmpcnt', 10))) - stamp.add_child(Node.s64('area', stampdict.get_int('area'))) - stamp.add_child(Node.s64('prfvst', stampdict.get_int('prfvst'))) + stamp.add_child( + Node.s32_array("stmpcnt", stampdict.get_int_array("stmpcnt", 10)) + ) + stamp.add_child(Node.s64("area", stampdict.get_int("area"))) + stamp.add_child(Node.s64("prfvst", stampdict.get_int("prfvst"))) # Configuration - configdict = profile.get_dict('config') - config = Node.void('config') + configdict = profile.get_dict("config") + config = Node.void("config") pdata.add_child(config) - config.add_child(Node.u8('msel_bgm', configdict.get_int('msel_bgm'))) - config.add_child(Node.u8('narrowdown_type', configdict.get_int('narrowdown_type'))) - config.add_child(Node.s16('icon_id', configdict.get_int('icon_id'))) - config.add_child(Node.s16('byword_0', configdict.get_int('byword_0'))) - config.add_child(Node.s16('byword_1', configdict.get_int('byword_1'))) - config.add_child(Node.bool('is_auto_byword_0', configdict.get_bool('is_auto_byword_0'))) - config.add_child(Node.bool('is_auto_byword_1', configdict.get_bool('is_auto_byword_1'))) - config.add_child(Node.u8('mrec_type', configdict.get_int('mrec_type'))) - config.add_child(Node.u8('tab_sel', configdict.get_int('tab_sel'))) - config.add_child(Node.u8('card_disp', configdict.get_int('card_disp'))) - config.add_child(Node.u8('score_tab_disp', configdict.get_int('score_tab_disp'))) - config.add_child(Node.s16('last_music_id', configdict.get_int('last_music_id', -1))) - config.add_child(Node.u8('last_note_grade', configdict.get_int('last_note_grade'))) - config.add_child(Node.u8('sort_type', configdict.get_int('sort_type'))) - config.add_child(Node.u8('rival_panel_type', configdict.get_int('rival_panel_type'))) - config.add_child(Node.u64('random_entry_work', configdict.get_int('random_entry_work'))) - config.add_child(Node.u64('custom_folder_work', configdict.get_int('custom_folder_work'))) - config.add_child(Node.u8('folder_type', configdict.get_int('folder_type'))) - config.add_child(Node.u8('folder_lamp_type', configdict.get_int('folder_lamp_type'))) - config.add_child(Node.bool('is_tweet', configdict.get_bool('is_tweet'))) - config.add_child(Node.bool('is_link_twitter', configdict.get_bool('is_link_twitter'))) + config.add_child(Node.u8("msel_bgm", configdict.get_int("msel_bgm"))) + config.add_child( + Node.u8("narrowdown_type", configdict.get_int("narrowdown_type")) + ) + config.add_child(Node.s16("icon_id", configdict.get_int("icon_id"))) + config.add_child(Node.s16("byword_0", configdict.get_int("byword_0"))) + config.add_child(Node.s16("byword_1", configdict.get_int("byword_1"))) + config.add_child( + Node.bool("is_auto_byword_0", configdict.get_bool("is_auto_byword_0")) + ) + config.add_child( + Node.bool("is_auto_byword_1", configdict.get_bool("is_auto_byword_1")) + ) + config.add_child(Node.u8("mrec_type", configdict.get_int("mrec_type"))) + config.add_child(Node.u8("tab_sel", configdict.get_int("tab_sel"))) + config.add_child(Node.u8("card_disp", configdict.get_int("card_disp"))) + config.add_child( + Node.u8("score_tab_disp", configdict.get_int("score_tab_disp")) + ) + config.add_child( + Node.s16("last_music_id", configdict.get_int("last_music_id", -1)) + ) + config.add_child( + Node.u8("last_note_grade", configdict.get_int("last_note_grade")) + ) + config.add_child(Node.u8("sort_type", configdict.get_int("sort_type"))) + config.add_child( + Node.u8("rival_panel_type", configdict.get_int("rival_panel_type")) + ) + config.add_child( + Node.u64("random_entry_work", configdict.get_int("random_entry_work")) + ) + config.add_child( + Node.u64("custom_folder_work", configdict.get_int("custom_folder_work")) + ) + config.add_child(Node.u8("folder_type", configdict.get_int("folder_type"))) + config.add_child( + Node.u8("folder_lamp_type", configdict.get_int("folder_lamp_type")) + ) + config.add_child(Node.bool("is_tweet", configdict.get_bool("is_tweet"))) + config.add_child( + Node.bool("is_link_twitter", configdict.get_bool("is_link_twitter")) + ) # Customizations - customdict = profile.get_dict('custom') - custom = Node.void('custom') + customdict = profile.get_dict("custom") + custom = Node.void("custom") pdata.add_child(custom) - custom.add_child(Node.u8('st_shot', customdict.get_int('st_shot'))) - custom.add_child(Node.u8('st_frame', customdict.get_int('st_frame'))) - custom.add_child(Node.u8('st_expl', customdict.get_int('st_expl'))) - custom.add_child(Node.u8('st_bg', customdict.get_int('st_bg'))) - custom.add_child(Node.u8('st_shot_vol', customdict.get_int('st_shot_vol'))) - custom.add_child(Node.u8('st_bg_bri', customdict.get_int('st_bg_bri'))) - custom.add_child(Node.u8('st_obj_size', customdict.get_int('st_obj_size'))) - custom.add_child(Node.u8('st_jr_gauge', customdict.get_int('st_jr_gauge'))) - custom.add_child(Node.u8('st_clr_gauge', customdict.get_int('st_clr_gauge'))) - custom.add_child(Node.u8('st_jdg_disp', customdict.get_int('st_jdg_disp'))) - custom.add_child(Node.u8('st_tm_disp', customdict.get_int('st_tm_disp'))) - custom.add_child(Node.u8('st_rnd', customdict.get_int('st_rnd'))) - custom.add_child(Node.u8('st_hazard', customdict.get_int('st_hazard'))) - custom.add_child(Node.u8('st_clr_cond', customdict.get_int('st_clr_cond'))) - custom.add_child(Node.s16_array('schat_0', customdict.get_int_array('schat_0', 10))) - custom.add_child(Node.s16_array('schat_1', customdict.get_int_array('schat_1', 10))) - custom.add_child(Node.u8('cheer_voice', customdict.get_int('cheer_voice'))) - custom.add_child(Node.u8('same_time_note_disp', customdict.get_int('same_time_note_disp'))) + custom.add_child(Node.u8("st_shot", customdict.get_int("st_shot"))) + custom.add_child(Node.u8("st_frame", customdict.get_int("st_frame"))) + custom.add_child(Node.u8("st_expl", customdict.get_int("st_expl"))) + custom.add_child(Node.u8("st_bg", customdict.get_int("st_bg"))) + custom.add_child(Node.u8("st_shot_vol", customdict.get_int("st_shot_vol"))) + custom.add_child(Node.u8("st_bg_bri", customdict.get_int("st_bg_bri"))) + custom.add_child(Node.u8("st_obj_size", customdict.get_int("st_obj_size"))) + custom.add_child(Node.u8("st_jr_gauge", customdict.get_int("st_jr_gauge"))) + custom.add_child(Node.u8("st_clr_gauge", customdict.get_int("st_clr_gauge"))) + custom.add_child(Node.u8("st_jdg_disp", customdict.get_int("st_jdg_disp"))) + custom.add_child(Node.u8("st_tm_disp", customdict.get_int("st_tm_disp"))) + custom.add_child(Node.u8("st_rnd", customdict.get_int("st_rnd"))) + custom.add_child(Node.u8("st_hazard", customdict.get_int("st_hazard"))) + custom.add_child(Node.u8("st_clr_cond", customdict.get_int("st_clr_cond"))) + custom.add_child( + Node.s16_array("schat_0", customdict.get_int_array("schat_0", 10)) + ) + custom.add_child( + Node.s16_array("schat_1", customdict.get_int_array("schat_1", 10)) + ) + custom.add_child(Node.u8("cheer_voice", customdict.get_int("cheer_voice"))) + custom.add_child( + Node.u8("same_time_note_disp", customdict.get_int("same_time_note_disp")) + ) # Unlocks - released = Node.void('released') + released = Node.void("released") pdata.add_child(released) for item in achievements: - if item.type[:5] != 'item_': + if item.type[:5] != "item_": continue itemtype = int(item.type[5:]) - if game_config.get_bool('force_unlock_songs') and itemtype == 0: + if game_config.get_bool("force_unlock_songs") and itemtype == 0: # Don't echo unlocks when we're force unlocking, we'll do it later continue - info = Node.void('info') + info = Node.void("info") released.add_child(info) - info.add_child(Node.u8('type', itemtype)) - info.add_child(Node.u16('id', item.id)) - info.add_child(Node.u16('param', item.data.get_int('param'))) + info.add_child(Node.u8("type", itemtype)) + info.add_child(Node.u16("id", item.id)) + info.add_child(Node.u16("param", item.data.get_int("param"))) - if game_config.get_bool('force_unlock_songs'): + if game_config.get_bool("force_unlock_songs"): ids: Dict[int, int] = {} songs = self.data.local.music.get_all_songs(self.game, self.version) for song in songs: if song.id not in ids: ids[song.id] = 0 - if song.data.get_int('difficulty') > 0: + if song.data.get_int("difficulty") > 0: ids[song.id] = ids[song.id] | (1 << song.chart) for songid in ids: if ids[songid] == 0: continue - info = Node.void('info') + info = Node.void("info") released.add_child(info) - info.add_child(Node.u8('type', 0)) - info.add_child(Node.u16('id', songid)) - info.add_child(Node.u16('param', ids[songid])) + info.add_child(Node.u8("type", 0)) + info.add_child(Node.u16("id", songid)) + info.add_child(Node.u16("param", ids[songid])) # Announcements - announce = Node.void('announce') + announce = Node.void("announce") pdata.add_child(announce) for announcement in achievements: - if announcement.type[:13] != 'announcement_': + if announcement.type[:13] != "announcement_": continue announcementtype = int(announcement.type[13:]) - info = Node.void('info') + info = Node.void("info") announce.add_child(info) - info.add_child(Node.u8('type', announcementtype)) - info.add_child(Node.u16('id', announcement.id)) - info.add_child(Node.u16('param', announcement.data.get_int('param'))) - info.add_child(Node.bool('bneedannounce', announcement.data.get_bool('need'))) + info.add_child(Node.u8("type", announcementtype)) + info.add_child(Node.u16("id", announcement.id)) + info.add_child(Node.u16("param", announcement.data.get_int("param"))) + info.add_child( + Node.bool("bneedannounce", announcement.data.get_bool("need")) + ) # Dojo ranking return - dojo = Node.void('dojo') + dojo = Node.void("dojo") pdata.add_child(dojo) for entry in achievements: - if entry.type != 'dojo': + if entry.type != "dojo": continue - rec = Node.void('rec') + rec = Node.void("rec") dojo.add_child(rec) - rec.add_child(Node.s32('class', entry.id)) - rec.add_child(Node.s32('clear_type', entry.data.get_int('clear_type'))) - rec.add_child(Node.s32('total_ar', entry.data.get_int('ar'))) - rec.add_child(Node.s32('total_score', entry.data.get_int('score'))) - rec.add_child(Node.s32('play_count', entry.data.get_int('plays'))) - rec.add_child(Node.s32('last_play_time', entry.data.get_int('play_timestamp'))) - rec.add_child(Node.s32('record_update_time', entry.data.get_int('record_timestamp'))) - rec.add_child(Node.s32('rank', 0)) + rec.add_child(Node.s32("class", entry.id)) + rec.add_child(Node.s32("clear_type", entry.data.get_int("clear_type"))) + rec.add_child(Node.s32("total_ar", entry.data.get_int("ar"))) + rec.add_child(Node.s32("total_score", entry.data.get_int("score"))) + rec.add_child(Node.s32("play_count", entry.data.get_int("plays"))) + rec.add_child( + Node.s32("last_play_time", entry.data.get_int("play_timestamp")) + ) + rec.add_child( + Node.s32("record_update_time", entry.data.get_int("record_timestamp")) + ) + rec.add_child(Node.s32("rank", 0)) # Player Parameters - player_param = Node.void('player_param') + player_param = Node.void("player_param") pdata.add_child(player_param) for param in achievements: - if param.type[:13] != 'player_param_': + if param.type[:13] != "player_param_": continue itemtype = int(param.type[13:]) - itemnode = Node.void('item') + itemnode = Node.void("item") player_param.add_child(itemnode) - itemnode.add_child(Node.s32('type', itemtype)) - itemnode.add_child(Node.s32('bank', param.id)) - itemnode.add_child(Node.s32_array('data', param.data.get_int_array('data', 256))) + itemnode.add_child(Node.s32("type", itemtype)) + itemnode.add_child(Node.s32("bank", param.id)) + itemnode.add_child( + Node.s32_array("data", param.data.get_int_array("data", 256)) + ) # Shop score for players self.__add_shop_score(pdata) # Quest data - questdict = profile.get_dict('quest') - quest = Node.void('quest') + questdict = profile.get_dict("quest") + quest = Node.void("quest") pdata.add_child(quest) - quest.add_child(Node.s16('eye_color', questdict.get_int('eye_color'))) - quest.add_child(Node.s16('body_color', questdict.get_int('body_color'))) - quest.add_child(Node.s16('item', questdict.get_int('item'))) - quest.add_child(Node.string('comment', '')) + quest.add_child(Node.s16("eye_color", questdict.get_int("eye_color"))) + quest.add_child(Node.s16("body_color", questdict.get_int("body_color"))) + quest.add_child(Node.s16("item", questdict.get_int("item"))) + quest.add_child(Node.string("comment", "")) # Derby settings - derby = Node.void('derby') + derby = Node.void("derby") pdata.add_child(derby) - derby.add_child(Node.bool('is_open', False)) + derby.add_child(Node.bool("is_open", False)) # Codebreaking stuff - codebreaking = Node.void('codebreaking') + codebreaking = Node.void("codebreaking") pdata.add_child(codebreaking) - codebreaking.add_child(Node.s32('cb_id', -1)) - codebreaking.add_child(Node.s32('cb_sub_id', -1)) - codebreaking.add_child(Node.s32('music_id', -1)) - codebreaking.add_child(Node.string('question', '')) + codebreaking.add_child(Node.s32("cb_id", -1)) + codebreaking.add_child(Node.s32("cb_sub_id", -1)) + codebreaking.add_child(Node.s32("music_id", -1)) + codebreaking.add_child(Node.string("question", "")) # Unknown IIDX link crap - iidx_linkage = Node.void('iidx_linkage') + iidx_linkage = Node.void("iidx_linkage") pdata.add_child(iidx_linkage) - iidx_linkage.add_child(Node.s32('linkage_id', -1)) - iidx_linkage.add_child(Node.s32('phase', -1)) - iidx_linkage.add_child(Node.s64('long_bit_0', -1)) - iidx_linkage.add_child(Node.s64('long_bit_1', -1)) - iidx_linkage.add_child(Node.s64('long_bit_2', -1)) - iidx_linkage.add_child(Node.s64('long_bit_3', -1)) - iidx_linkage.add_child(Node.s64('long_bit_4', -1)) - iidx_linkage.add_child(Node.s64('long_bit_5', -1)) - iidx_linkage.add_child(Node.s32('add_0', -1)) - iidx_linkage.add_child(Node.s32('add_1', -1)) - iidx_linkage.add_child(Node.s32('add_2', -1)) - iidx_linkage.add_child(Node.s32('add_3', -1)) + iidx_linkage.add_child(Node.s32("linkage_id", -1)) + iidx_linkage.add_child(Node.s32("phase", -1)) + iidx_linkage.add_child(Node.s64("long_bit_0", -1)) + iidx_linkage.add_child(Node.s64("long_bit_1", -1)) + iidx_linkage.add_child(Node.s64("long_bit_2", -1)) + iidx_linkage.add_child(Node.s64("long_bit_3", -1)) + iidx_linkage.add_child(Node.s64("long_bit_4", -1)) + iidx_linkage.add_child(Node.s64("long_bit_5", -1)) + iidx_linkage.add_child(Node.s32("add_0", -1)) + iidx_linkage.add_child(Node.s32("add_1", -1)) + iidx_linkage.add_child(Node.s32("add_2", -1)) + iidx_linkage.add_child(Node.s32("add_3", -1)) # Unknown event crap - pue = Node.void('pue') + pue = Node.void("pue") pdata.add_child(pue) - pue.add_child(Node.s32('event_id', -1)) - pue.add_child(Node.s32('point', -1)) - pue.add_child(Node.s32('value0', -1)) - pue.add_child(Node.s32('value1', -1)) - pue.add_child(Node.s32('value2', -1)) - pue.add_child(Node.s32('value3', -1)) - pue.add_child(Node.s32('value4', -1)) - pue.add_child(Node.s32('start_time', -1)) - pue.add_child(Node.s32('end_time', -1)) + pue.add_child(Node.s32("event_id", -1)) + pue.add_child(Node.s32("point", -1)) + pue.add_child(Node.s32("value0", -1)) + pue.add_child(Node.s32("value1", -1)) + pue.add_child(Node.s32("value2", -1)) + pue.add_child(Node.s32("value3", -1)) + pue.add_child(Node.s32("value4", -1)) + pue.add_child(Node.s32("start_time", -1)) + pue.add_child(Node.s32("end_time", -1)) return root - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: game_config = self.get_game_config() newprofile = oldprofile.clone() # Save base player profile info - newprofile.replace_int('lid', ID.parse_machine_id(request.child_value('pdata/account/lid'))) - newprofile.replace_str('name', request.child_value('pdata/base/name')) - newprofile.replace_int('exp', request.child_value('pdata/base/exp')) - newprofile.replace_int('lvl', request.child_value('pdata/base/lvl')) - newprofile.replace_int('mg', request.child_value('pdata/base/mg')) - newprofile.replace_int('ap', request.child_value('pdata/base/ap')) - newprofile.replace_int('money', request.child_value('pdata/base/money')) - newprofile.replace_int('class', request.child_value('pdata/base/class')) - newprofile.replace_int('class_ar', request.child_value('pdata/base/class_ar')) - newprofile.replace_int('upper_pt', request.child_value('pdata/base/upper_pt')) + newprofile.replace_int( + "lid", ID.parse_machine_id(request.child_value("pdata/account/lid")) + ) + newprofile.replace_str("name", request.child_value("pdata/base/name")) + newprofile.replace_int("exp", request.child_value("pdata/base/exp")) + newprofile.replace_int("lvl", request.child_value("pdata/base/lvl")) + newprofile.replace_int("mg", request.child_value("pdata/base/mg")) + newprofile.replace_int("ap", request.child_value("pdata/base/ap")) + newprofile.replace_int("money", request.child_value("pdata/base/money")) + newprofile.replace_int("class", request.child_value("pdata/base/class")) + newprofile.replace_int("class_ar", request.child_value("pdata/base/class_ar")) + newprofile.replace_int("upper_pt", request.child_value("pdata/base/upper_pt")) # Save stamps - stampdict = newprofile.get_dict('stamp') - stamp = request.child('pdata/stamp') + stampdict = newprofile.get_dict("stamp") + stamp = request.child("pdata/stamp") if stamp: - stampdict.replace_int_array('stmpcnt', 10, stamp.child_value('stmpcnt')) - stampdict.replace_int('area', stamp.child_value('area')) - stampdict.replace_int('prfvst', stamp.child_value('prfvst')) - newprofile.replace_dict('stamp', stampdict) + stampdict.replace_int_array("stmpcnt", 10, stamp.child_value("stmpcnt")) + stampdict.replace_int("area", stamp.child_value("area")) + stampdict.replace_int("prfvst", stamp.child_value("prfvst")) + newprofile.replace_dict("stamp", stampdict) # Save quest stuff - questdict = newprofile.get_dict('quest') - quest = request.child('pdata/quest') + questdict = newprofile.get_dict("quest") + quest = request.child("pdata/quest") if quest: - questdict.replace_int('eye_color', quest.child_value('eye_color')) - questdict.replace_int('body_color', quest.child_value('body_color')) - questdict.replace_int('item', quest.child_value('item')) - newprofile.replace_dict('quest', questdict) + questdict.replace_int("eye_color", quest.child_value("eye_color")) + questdict.replace_int("body_color", quest.child_value("body_color")) + questdict.replace_int("item", quest.child_value("item")) + newprofile.replace_dict("quest", questdict) # Save player dojo - dojo = request.child('pdata/dojo') + dojo = request.child("pdata/dojo") if dojo: - dojoid = dojo.child_value('class') - clear_type = dojo.child_value('clear_type') - ar = dojo.child_value('t_ar') - score = dojo.child_value('t_score') + dojoid = dojo.child_value("class") + clear_type = dojo.child_value("clear_type") + ar = dojo.child_value("t_ar") + score = dojo.child_value("t_score") # Figure out timestamp stuff - data = self.data.local.user.get_achievement( - self.game, - self.version, - userid, - dojoid, - 'dojo', - ) or ValidatedDict() + data = ( + self.data.local.user.get_achievement( + self.game, + self.version, + userid, + dojoid, + "dojo", + ) + or ValidatedDict() + ) - if ar >= data.get_int('ar'): + if ar >= data.get_int("ar"): # We set a new achievement rate, keep the new values record_time = Time.now() else: # We didn't, keep the old values for achievement rate, but # override score and clear_type only if they were better. - record_time = data.get_int('record_timestamp') - ar = data.get_int('ar') - score = max(score, data.get_int('score')) - clear_type = max(clear_type, data.get_int('clear_type')) + record_time = data.get_int("record_timestamp") + ar = data.get_int("ar") + score = max(score, data.get_int("score")) + clear_type = max(clear_type, data.get_int("clear_type")) play_time = Time.now() - plays = data.get_int('plays') + 1 + plays = data.get_int("plays") + 1 self.data.local.user.put_achievement( self.game, self.version, userid, dojoid, - 'dojo', + "dojo", { - 'clear_type': clear_type, - 'ar': ar, - 'score': score, - 'plays': plays, - 'play_timestamp': play_time, - 'record_timestamp': record_time, + "clear_type": clear_type, + "ar": ar, + "score": score, + "plays": plays, + "play_timestamp": play_time, + "record_timestamp": record_time, }, ) # Save player config - configdict = newprofile.get_dict('config') - config = request.child('pdata/config') + configdict = newprofile.get_dict("config") + config = request.child("pdata/config") if config: - configdict.replace_int('msel_bgm', config.child_value('msel_bgm')) - configdict.replace_int('narrowdown_type', config.child_value('narrowdown_type')) - configdict.replace_int('icon_id', config.child_value('icon_id')) - configdict.replace_int('byword_0', config.child_value('byword_0')) - configdict.replace_int('byword_1', config.child_value('byword_1')) - configdict.replace_bool('is_auto_byword_0', config.child_value('is_auto_byword_0')) - configdict.replace_bool('is_auto_byword_1', config.child_value('is_auto_byword_1')) - configdict.replace_int('mrec_type', config.child_value('mrec_type')) - configdict.replace_int('tab_sel', config.child_value('tab_sel')) - configdict.replace_int('card_disp', config.child_value('card_disp')) - configdict.replace_int('score_tab_disp', config.child_value('score_tab_disp')) - configdict.replace_int('last_music_id', config.child_value('last_music_id')) - configdict.replace_int('last_note_grade', config.child_value('last_note_grade')) - configdict.replace_int('sort_type', config.child_value('sort_type')) - configdict.replace_int('rival_panel_type', config.child_value('rival_panel_type')) - configdict.replace_int('random_entry_work', config.child_value('random_entry_work')) - configdict.replace_int('custom_folder_work', config.child_value('custom_folder_work')) - configdict.replace_int('folder_type', config.child_value('folder_type')) - configdict.replace_int('folder_lamp_type', config.child_value('folder_lamp_type')) - configdict.replace_bool('is_tweet', config.child_value('is_tweet')) - configdict.replace_bool('is_link_twitter', config.child_value('is_link_twitter')) - newprofile.replace_dict('config', configdict) + configdict.replace_int("msel_bgm", config.child_value("msel_bgm")) + configdict.replace_int( + "narrowdown_type", config.child_value("narrowdown_type") + ) + configdict.replace_int("icon_id", config.child_value("icon_id")) + configdict.replace_int("byword_0", config.child_value("byword_0")) + configdict.replace_int("byword_1", config.child_value("byword_1")) + configdict.replace_bool( + "is_auto_byword_0", config.child_value("is_auto_byword_0") + ) + configdict.replace_bool( + "is_auto_byword_1", config.child_value("is_auto_byword_1") + ) + configdict.replace_int("mrec_type", config.child_value("mrec_type")) + configdict.replace_int("tab_sel", config.child_value("tab_sel")) + configdict.replace_int("card_disp", config.child_value("card_disp")) + configdict.replace_int( + "score_tab_disp", config.child_value("score_tab_disp") + ) + configdict.replace_int("last_music_id", config.child_value("last_music_id")) + configdict.replace_int( + "last_note_grade", config.child_value("last_note_grade") + ) + configdict.replace_int("sort_type", config.child_value("sort_type")) + configdict.replace_int( + "rival_panel_type", config.child_value("rival_panel_type") + ) + configdict.replace_int( + "random_entry_work", config.child_value("random_entry_work") + ) + configdict.replace_int( + "custom_folder_work", config.child_value("custom_folder_work") + ) + configdict.replace_int("folder_type", config.child_value("folder_type")) + configdict.replace_int( + "folder_lamp_type", config.child_value("folder_lamp_type") + ) + configdict.replace_bool("is_tweet", config.child_value("is_tweet")) + configdict.replace_bool( + "is_link_twitter", config.child_value("is_link_twitter") + ) + newprofile.replace_dict("config", configdict) # Save player custom settings - customdict = newprofile.get_dict('custom') - custom = request.child('pdata/custom') + customdict = newprofile.get_dict("custom") + custom = request.child("pdata/custom") if custom: - customdict.replace_int('st_shot', custom.child_value('st_shot')) - customdict.replace_int('st_frame', custom.child_value('st_frame')) - customdict.replace_int('st_expl', custom.child_value('st_expl')) - customdict.replace_int('st_bg', custom.child_value('st_bg')) - customdict.replace_int('st_shot_vol', custom.child_value('st_shot_vol')) - customdict.replace_int('st_bg_bri', custom.child_value('st_bg_bri')) - customdict.replace_int('st_obj_size', custom.child_value('st_obj_size')) - customdict.replace_int('st_jr_gauge', custom.child_value('st_jr_gauge')) - customdict.replace_int('st_clr_gauge', custom.child_value('st_clr_gauge')) - customdict.replace_int('st_jdg_disp', custom.child_value('st_jdg_disp')) - customdict.replace_int('st_tm_disp', custom.child_value('st_tm_disp')) - customdict.replace_int('st_rnd', custom.child_value('st_rnd')) - customdict.replace_int('st_hazard', custom.child_value('st_hazard')) - customdict.replace_int('st_clr_cond', custom.child_value('st_clr_cond')) - customdict.replace_int_array('schat_0', 10, custom.child_value('schat_0')) - customdict.replace_int_array('schat_1', 10, custom.child_value('schat_1')) - customdict.replace_int('cheer_voice', custom.child_value('cheer_voice')) - customdict.replace_int('same_time_note_disp', custom.child_value('same_time_note_disp')) - newprofile.replace_dict('custom', customdict) + customdict.replace_int("st_shot", custom.child_value("st_shot")) + customdict.replace_int("st_frame", custom.child_value("st_frame")) + customdict.replace_int("st_expl", custom.child_value("st_expl")) + customdict.replace_int("st_bg", custom.child_value("st_bg")) + customdict.replace_int("st_shot_vol", custom.child_value("st_shot_vol")) + customdict.replace_int("st_bg_bri", custom.child_value("st_bg_bri")) + customdict.replace_int("st_obj_size", custom.child_value("st_obj_size")) + customdict.replace_int("st_jr_gauge", custom.child_value("st_jr_gauge")) + customdict.replace_int("st_clr_gauge", custom.child_value("st_clr_gauge")) + customdict.replace_int("st_jdg_disp", custom.child_value("st_jdg_disp")) + customdict.replace_int("st_tm_disp", custom.child_value("st_tm_disp")) + customdict.replace_int("st_rnd", custom.child_value("st_rnd")) + customdict.replace_int("st_hazard", custom.child_value("st_hazard")) + customdict.replace_int("st_clr_cond", custom.child_value("st_clr_cond")) + customdict.replace_int_array("schat_0", 10, custom.child_value("schat_0")) + customdict.replace_int_array("schat_1", 10, custom.child_value("schat_1")) + customdict.replace_int("cheer_voice", custom.child_value("cheer_voice")) + customdict.replace_int( + "same_time_note_disp", custom.child_value("same_time_note_disp") + ) + newprofile.replace_dict("custom", customdict) # Save player parameter info - params = request.child('pdata/player_param') + params = request.child("pdata/player_param") if params: for child in params.children: - if child.name != 'item': + if child.name != "item": continue - item_type = child.child_value('type') - bank = child.child_value('bank') - paramdata = child.child_value('data') or [] + item_type = child.child_value("type") + bank = child.child_value("bank") + paramdata = child.child_value("data") or [] while len(paramdata) < 256: paramdata.append(0) self.data.local.user.put_achievement( @@ -1354,54 +1528,56 @@ class ReflecBeatGroovin(ReflecBeatBase): self.version, userid, bank, - f'player_param_{item_type}', + f"player_param_{item_type}", { - 'data': paramdata, + "data": paramdata, }, ) # Save player episode info - episode = request.child('pdata/episode') + episode = request.child("pdata/episode") if episode: for child in episode.children: - if child.name != 'info': + if child.name != "info": continue # I assume this is copypasta, but I want to be sure - extid = child.child_value('user_id') + extid = child.child_value("user_id") if extid != newprofile.extid: - raise Exception(f'Unexpected user ID, got {extid} expecting {newprofile.extid}') + raise Exception( + f"Unexpected user ID, got {extid} expecting {newprofile.extid}" + ) - episode_type = child.child_value('type') - episode_value0 = child.child_value('value0') - episode_value1 = child.child_value('value1') - episode_text = child.child_value('text') - episode_time = child.child_value('time') + episode_type = child.child_value("type") + episode_value0 = child.child_value("value0") + episode_value1 = child.child_value("value1") + episode_text = child.child_value("text") + episode_time = child.child_value("time") self.data.local.user.put_achievement( self.game, self.version, userid, episode_type, - 'episode', + "episode", { - 'value0': episode_value0, - 'value1': episode_value1, - 'text': episode_text, - 'time': episode_time, + "value0": episode_value0, + "value1": episode_value1, + "text": episode_text, + "time": episode_time, }, ) # Save released info - released = request.child('pdata/released') + released = request.child("pdata/released") if released: for child in released.children: - if child.name != 'info': + if child.name != "info": continue - item_id = child.child_value('id') - item_type = child.child_value('type') - param = child.child_value('param') - if game_config.get_bool('force_unlock_songs') and item_type == 0: + item_id = child.child_value("id") + item_type = child.child_value("type") + param = child.child_value("param") + if game_config.get_bool("force_unlock_songs") and item_type == 0: # Don't save unlocks when we're force unlocking continue @@ -1410,44 +1586,46 @@ class ReflecBeatGroovin(ReflecBeatBase): self.version, userid, item_id, - f'item_{item_type}', + f"item_{item_type}", { - 'param': param, + "param": param, }, ) # Save announce info - announce = request.child('pdata/announce') + announce = request.child("pdata/announce") if announce: for child in announce.children: - if child.name != 'info': + if child.name != "info": continue - announce_id = child.child_value('id') - announce_type = child.child_value('type') - param = child.child_value('param') - need = child.child_value('bneedannounce') + announce_id = child.child_value("id") + announce_type = child.child_value("type") + param = child.child_value("param") + need = child.child_value("bneedannounce") self.data.local.user.put_achievement( self.game, self.version, userid, announce_id, - f'announcement_{announce_type}', + f"announcement_{announce_type}", { - 'param': param, - 'need': need, + "param": param, + "need": need, }, ) # Grab any new rivals added during this play session - rivalnode = request.child('pdata/rival') + rivalnode = request.child("pdata/rival") if rivalnode: for child in rivalnode.children: - if child.name != 'r': + if child.name != "r": continue - extid = child.child_value('id') - other_userid = self.data.remote.user.from_extid(self.game, self.version, extid) + extid = child.child_value("id") + other_userid = self.data.remote.user.from_extid( + self.game, self.version, extid + ) if other_userid is None: continue @@ -1455,29 +1633,29 @@ class ReflecBeatGroovin(ReflecBeatBase): self.game, self.version, userid, - 'rival', + "rival", other_userid, {}, ) # Grab any new records set during this play session - songplays = request.child('pdata/stglog') + songplays = request.child("pdata/stglog") if songplays: for child in songplays.children: - if child.name != 'log': + if child.name != "log": continue - songid = child.child_value('mid') - chart = child.child_value('ng') - clear_type = child.child_value('ct') + songid = child.child_value("mid") + chart = child.child_value("ng") + clear_type = child.child_value("ct") if songid == 0 and chart == 0 and clear_type == -1: # Dummy song save during profile create continue - points = child.child_value('sc') - achievement_rate = child.child_value('ar') - param = child.child_value('param') - miss_count = child.child_value('jt_ms') + points = child.child_value("sc") + achievement_rate = child.child_value("ar") + param = child.child_value("param") + miss_count = child.child_value("jt_ms") # Param is some random bits along with the combo type combo_type = param & 0x3 diff --git a/bemani/backend/reflec/limelight.py b/bemani/backend/reflec/limelight.py index 4c7799b..5ebefe6 100644 --- a/bemani/backend/reflec/limelight.py +++ b/bemani/backend/reflec/limelight.py @@ -32,15 +32,15 @@ class ReflecBeatLimelight(ReflecBeatBase): Return all of our front-end modifiably settings. """ return { - 'bools': [ + "bools": [ { - 'name': 'Force Song Unlock', - 'tip': 'Force unlock all songs.', - 'category': 'game_config', - 'setting': 'force_unlock_songs', + "name": "Force Song Unlock", + "tip": "Force unlock all songs.", + "category": "game_config", + "setting": "force_unlock_songs", }, ], - 'ints': [], + "ints": [], } def __db_to_game_clear_type(self, db_clear_type: int, db_combo_type: int) -> int: @@ -64,8 +64,8 @@ class ReflecBeatLimelight(ReflecBeatBase): ]: return self.GAME_CLEAR_TYPE_FULL_COMBO - raise Exception(f'Invalid db_combo_type {db_combo_type}') - raise Exception(f'Invalid db_clear_type {db_clear_type}') + raise Exception(f"Invalid db_combo_type {db_combo_type}") + raise Exception(f"Invalid db_clear_type {db_clear_type}") def __game_to_db_clear_type(self, game_clear_type: int) -> Tuple[int, int]: if game_clear_type == self.GAME_CLEAR_TYPE_NO_PLAY: @@ -77,70 +77,72 @@ class ReflecBeatLimelight(ReflecBeatBase): if game_clear_type == self.GAME_CLEAR_TYPE_FULL_COMBO: return (self.CLEAR_TYPE_CLEARED, self.COMBO_TYPE_FULL_COMBO) - raise Exception(f'Invalid game_clear_type {game_clear_type}') + raise Exception(f"Invalid game_clear_type {game_clear_type}") def handle_log_exception_request(self, request: Node) -> Node: - return Node.void('log') + return Node.void("log") def handle_log_pcb_status_request(self, request: Node) -> Node: - return Node.void('log') + return Node.void("log") def handle_log_opsetting_request(self, request: Node) -> Node: - return Node.void('log') + return Node.void("log") def handle_log_play_request(self, request: Node) -> Node: - return Node.void('log') + return Node.void("log") def handle_pcbinfo_get_request(self, request: Node) -> Node: - shop_id = ID.parse_machine_id(request.child_value('lid')) + shop_id = ID.parse_machine_id(request.child_value("lid")) machine = self.get_machine_by_id(shop_id) if machine is not None: machine_name = machine.name - close = machine.data.get_bool('close') - hour = machine.data.get_int('hour') - minute = machine.data.get_int('minute') - pref = machine.data.get_int('pref', self.get_machine_region()) + close = machine.data.get_bool("close") + hour = machine.data.get_int("hour") + minute = machine.data.get_int("minute") + pref = machine.data.get_int("pref", self.get_machine_region()) else: - machine_name = '' + machine_name = "" close = False hour = 0 minute = 0 pref = self.get_machine_region() - root = Node.void('pcbinfo') - info = Node.void('info') + root = Node.void("pcbinfo") + info = Node.void("info") root.add_child(info) - info.add_child(Node.string('name', machine_name)) - info.add_child(Node.s16('pref', pref)) - info.add_child(Node.bool('close', close)) - info.add_child(Node.u8('hour', hour)) - info.add_child(Node.u8('min', minute)) + info.add_child(Node.string("name", machine_name)) + info.add_child(Node.s16("pref", pref)) + info.add_child(Node.bool("close", close)) + info.add_child(Node.u8("hour", hour)) + info.add_child(Node.u8("min", minute)) return root def handle_pcbinfo_set_request(self, request: Node) -> Node: - self.update_machine_name(request.child_value('info/name')) - self.update_machine_data({ - 'close': request.child_value('info/close'), - 'hour': request.child_value('info/hour'), - 'minute': request.child_value('info/min'), - 'pref': request.child_value('info/pref'), - }) - return Node.void('pcbinfo') + self.update_machine_name(request.child_value("info/name")) + self.update_machine_data( + { + "close": request.child_value("info/close"), + "hour": request.child_value("info/hour"), + "minute": request.child_value("info/min"), + "pref": request.child_value("info/pref"), + } + ) + return Node.void("pcbinfo") def __add_event_info(self, request: Node) -> None: events: Dict[int, int] = {} for (_eventid, _phase) in events.items(): - data = Node.void('data') + data = Node.void("data") request.add_child(data) - data.add_child(Node.s32('type', -1)) - data.add_child(Node.s32('value', -1)) + data.add_child(Node.s32("type", -1)) + data.add_child(Node.s32("value", -1)) def handle_sysinfo_get_request(self, request: Node) -> Node: - root = Node.void('sysinfo') - trd = Node.void('trd') + root = Node.void("sysinfo") + trd = Node.void("trd") root.add_child(trd) # Add event info @@ -149,38 +151,42 @@ class ReflecBeatLimelight(ReflecBeatBase): return root def handle_ranking_read_request(self, request: Node) -> Node: - root = Node.void('ranking') + root = Node.void("ranking") - licenses = Node.void('lic_10') + licenses = Node.void("lic_10") root.add_child(licenses) - originals = Node.void('org_10') + originals = Node.void("org_10") root.add_child(originals) - licenses.add_child(Node.time('time', Time.now())) - originals.add_child(Node.time('time', Time.now())) + licenses.add_child(Node.time("time", Time.now())) + originals.add_child(Node.time("time", Time.now())) hitchart = self.data.local.music.get_hit_chart(self.game, self.version, 10) rank = 1 for (mid, _plays) in hitchart: - record = Node.void('record') + record = Node.void("record") originals.add_child(record) - record.add_child(Node.s16('id', mid)) - record.add_child(Node.s16('rank', rank)) + record.add_child(Node.s16("id", mid)) + record.add_child(Node.s16("rank", rank)) rank = rank + 1 return root def handle_event_r_get_all_request(self, request: Node) -> Node: - limit = request.child_value('limit') + limit = request.child_value("limit") comments = [ - achievement for achievement in - self.data.local.user.get_all_time_based_achievements(self.game, self.version) - if achievement[1].type == 'puzzle_comment' + achievement + for achievement in self.data.local.user.get_all_time_based_achievements( + self.game, self.version + ) + if achievement[1].type == "puzzle_comment" ] comments.sort(key=lambda x: x[1].timestamp, reverse=True) - statuses = self.data.local.lobby.get_all_play_session_infos(self.game, self.version) - statuses.sort(key=lambda x: x[1]['time'], reverse=True) + statuses = self.data.local.lobby.get_all_play_session_infos( + self.game, self.version + ) + statuses.sort(key=lambda x: x[1]["time"], reverse=True) # Cap all comment blocks to the limit if limit >= 0: @@ -189,24 +195,24 @@ class ReflecBeatLimelight(ReflecBeatBase): # Mapping of profiles to userIDs uid_mapping = { - uid: prof for (uid, prof) in self.get_any_profiles( - [c[0] for c in comments] + - [s[0] for s in statuses] + uid: prof + for (uid, prof) in self.get_any_profiles( + [c[0] for c in comments] + [s[0] for s in statuses] ) } # Mapping of location ID to machine name lid_mapping: Dict[int, str] = {} - root = Node.void('event_r') - root.add_child(Node.s32('time', Time.now())) - statusnode = Node.void('status') + root = Node.void("event_r") + root.add_child(Node.s32("time", Time.now())) + statusnode = Node.void("status") root.add_child(statusnode) - commentnode = Node.void('comment') + commentnode = Node.void("comment") root.add_child(commentnode) for (uid, comment) in comments: - lid = ID.parse_machine_id(comment.data.get_str('lid')) + lid = ID.parse_machine_id(comment.data.get_str("lid")) # Look up external data for the request if lid not in lid_mapping: @@ -214,25 +220,25 @@ class ReflecBeatLimelight(ReflecBeatBase): if machine is not None: lid_mapping[lid] = machine.name else: - lid_mapping[lid] = '' + lid_mapping[lid] = "" - c = Node.void('c') + c = Node.void("c") commentnode.add_child(c) - c.add_child(Node.s32('uid', uid_mapping[uid].extid)) - c.add_child(Node.string('p_name', uid_mapping[uid].get_str('name'))) - c.add_child(Node.s32('exp', uid_mapping[uid].get_int('exp'))) - c.add_child(Node.s32('customize', comment.data.get_int('customize'))) - c.add_child(Node.s32('tid', comment.data.get_int('teamid'))) - c.add_child(Node.string('t_name', comment.data.get_str('teamname'))) - c.add_child(Node.string('lid', comment.data.get_str('lid'))) - c.add_child(Node.string('s_name', lid_mapping[lid])) - c.add_child(Node.s8('pref', comment.data.get_int('prefecture'))) - c.add_child(Node.s32('time', comment.timestamp)) - c.add_child(Node.string('comment', comment.data.get_str('comment'))) - c.add_child(Node.bool('is_tweet', comment.data.get_bool('tweet'))) + c.add_child(Node.s32("uid", uid_mapping[uid].extid)) + c.add_child(Node.string("p_name", uid_mapping[uid].get_str("name"))) + c.add_child(Node.s32("exp", uid_mapping[uid].get_int("exp"))) + c.add_child(Node.s32("customize", comment.data.get_int("customize"))) + c.add_child(Node.s32("tid", comment.data.get_int("teamid"))) + c.add_child(Node.string("t_name", comment.data.get_str("teamname"))) + c.add_child(Node.string("lid", comment.data.get_str("lid"))) + c.add_child(Node.string("s_name", lid_mapping[lid])) + c.add_child(Node.s8("pref", comment.data.get_int("prefecture"))) + c.add_child(Node.s32("time", comment.timestamp)) + c.add_child(Node.string("comment", comment.data.get_str("comment"))) + c.add_child(Node.bool("is_tweet", comment.data.get_bool("tweet"))) for (uid, status) in statuses: - lid = ID.parse_machine_id(status.get_str('lid')) + lid = ID.parse_machine_id(status.get_str("lid")) # Look up external data for the request if lid not in lid_mapping: @@ -240,41 +246,43 @@ class ReflecBeatLimelight(ReflecBeatBase): if machine is not None: lid_mapping[lid] = machine.name else: - lid_mapping[lid] = '' + lid_mapping[lid] = "" - s = Node.void('s') + s = Node.void("s") statusnode.add_child(s) - s.add_child(Node.s32('uid', uid_mapping[uid].extid)) - s.add_child(Node.string('p_name', uid_mapping[uid].get_str('name'))) - s.add_child(Node.s32('exp', uid_mapping[uid].get_int('exp'))) - s.add_child(Node.s32('customize', status.get_int('customize'))) - s.add_child(Node.s32('tid', uid_mapping[uid].get_int('team_id', -1))) - s.add_child(Node.string('t_name', uid_mapping[uid].get_str('team_name', ''))) - s.add_child(Node.string('lid', status.get_str('lid'))) - s.add_child(Node.string('s_name', lid_mapping[lid])) - s.add_child(Node.s8('pref', status.get_int('prefecture'))) - s.add_child(Node.s32('time', status.get_int('time'))) - s.add_child(Node.s8('status', status.get_int('status'))) - s.add_child(Node.s8('stage', status.get_int('stage'))) - s.add_child(Node.s32('mid', status.get_int('mid'))) - s.add_child(Node.s8('ng', status.get_int('ng'))) + s.add_child(Node.s32("uid", uid_mapping[uid].extid)) + s.add_child(Node.string("p_name", uid_mapping[uid].get_str("name"))) + s.add_child(Node.s32("exp", uid_mapping[uid].get_int("exp"))) + s.add_child(Node.s32("customize", status.get_int("customize"))) + s.add_child(Node.s32("tid", uid_mapping[uid].get_int("team_id", -1))) + s.add_child( + Node.string("t_name", uid_mapping[uid].get_str("team_name", "")) + ) + s.add_child(Node.string("lid", status.get_str("lid"))) + s.add_child(Node.string("s_name", lid_mapping[lid])) + s.add_child(Node.s8("pref", status.get_int("prefecture"))) + s.add_child(Node.s32("time", status.get_int("time"))) + s.add_child(Node.s8("status", status.get_int("status"))) + s.add_child(Node.s8("stage", status.get_int("stage"))) + s.add_child(Node.s32("mid", status.get_int("mid"))) + s.add_child(Node.s8("ng", status.get_int("ng"))) return root def handle_event_w_add_comment_request(self, request: Node) -> Node: - extid = request.child_value('uid') + extid = request.child_value("uid") userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is None: # Anonymous comment userid = UserID(0) - customize = request.child_value('customize') - lid = request.child_value('lid') - teamid = request.child_value('tid') - teamname = request.child_value('t_name') - prefecture = request.child_value('pref') - comment = request.child_value('comment') - is_tweet = request.child_value('is_tweet') + customize = request.child_value("customize") + lid = request.child_value("lid") + teamid = request.child_value("tid") + teamname = request.child_value("t_name") + prefecture = request.child_value("pref") + comment = request.child_value("comment") + is_tweet = request.child_value("is_tweet") # Link comment to user's profile self.data.local.user.put_time_based_achievement( @@ -282,56 +290,56 @@ class ReflecBeatLimelight(ReflecBeatBase): self.version, userid, 0, # We never have an ID for this, since comments are add-only - 'puzzle_comment', + "puzzle_comment", { - 'customize': customize, - 'lid': lid, - 'teamid': teamid, - 'teamname': teamname, - 'prefecture': prefecture, - 'comment': comment, - 'tweet': is_tweet, + "customize": customize, + "lid": lid, + "teamid": teamid, + "teamname": teamname, + "prefecture": prefecture, + "comment": comment, + "tweet": is_tweet, }, ) - return Node.void('event_w') + return Node.void("event_w") def handle_event_w_update_status_request(self, request: Node) -> Node: # Update user status so puzzle comments can show it - extid = request.child_value('uid') + extid = request.child_value("uid") userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: - customize = request.child_value('customize') - status = request.child_value('status') - stage = request.child_value('stage') - mid = request.child_value('mid') - ng = request.child_value('ng') - lid = request.child_value('lid') - prefecture = request.child_value('pref') + customize = request.child_value("customize") + status = request.child_value("status") + stage = request.child_value("stage") + mid = request.child_value("mid") + ng = request.child_value("ng") + lid = request.child_value("lid") + prefecture = request.child_value("pref") self.data.local.lobby.put_play_session_info( self.game, self.version, userid, { - 'customize': customize, - 'status': status, - 'stage': stage, - 'mid': mid, - 'ng': ng, - 'lid': lid, - 'prefecture': prefecture, + "customize": customize, + "status": status, + "stage": stage, + "mid": mid, + "ng": ng, + "lid": lid, + "prefecture": prefecture, }, ) - return Node.void('event_w') + return Node.void("event_w") def handle_lobby_entry_request(self, request: Node) -> Node: - root = Node.void('lobby') - root.add_child(Node.s32('interval', 120)) - root.add_child(Node.s32('interval_p', 120)) + root = Node.void("lobby") + root.add_child(Node.s32("interval", 120)) + root.add_child(Node.s32("interval_p", 120)) # Create a lobby entry for this user - extid = request.child_value('e/uid') + extid = request.child_value("e/uid") userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: profile = self.get_profile(userid) @@ -340,63 +348,63 @@ class ReflecBeatLimelight(ReflecBeatBase): self.version, userid, { - 'mid': request.child_value('e/mid'), - 'ng': request.child_value('e/ng'), - 'mopt': request.child_value('e/mopt'), - 'tid': request.child_value('e/tid'), - 'tn': request.child_value('e/tn'), - 'topt': request.child_value('e/topt'), - 'lid': request.child_value('e/lid'), - 'sn': request.child_value('e/sn'), - 'pref': request.child_value('e/pref'), - 'stg': request.child_value('e/stg'), - 'pside': request.child_value('e/pside'), - 'eatime': request.child_value('e/eatime'), - 'ga': request.child_value('e/ga'), - 'gp': request.child_value('e/gp'), - 'la': request.child_value('e/la'), - } + "mid": request.child_value("e/mid"), + "ng": request.child_value("e/ng"), + "mopt": request.child_value("e/mopt"), + "tid": request.child_value("e/tid"), + "tn": request.child_value("e/tn"), + "topt": request.child_value("e/topt"), + "lid": request.child_value("e/lid"), + "sn": request.child_value("e/sn"), + "pref": request.child_value("e/pref"), + "stg": request.child_value("e/stg"), + "pside": request.child_value("e/pside"), + "eatime": request.child_value("e/eatime"), + "ga": request.child_value("e/ga"), + "gp": request.child_value("e/gp"), + "la": request.child_value("e/la"), + }, ) lobby = self.data.local.lobby.get_lobby( self.game, self.version, userid, ) - root.add_child(Node.s32('eid', lobby.get_int('id'))) - e = Node.void('e') + root.add_child(Node.s32("eid", lobby.get_int("id"))) + e = Node.void("e") root.add_child(e) - e.add_child(Node.s32('eid', lobby.get_int('id'))) - e.add_child(Node.u16('mid', lobby.get_int('mid'))) - e.add_child(Node.u8('ng', lobby.get_int('ng'))) - e.add_child(Node.s32('uid', profile.extid)) - e.add_child(Node.string('pn', profile.get_str('name'))) - e.add_child(Node.s32('uattr', profile.get_int('uattr'))) - e.add_child(Node.s32('mopt', lobby.get_int('mopt'))) - e.add_child(Node.s16('mg', profile.get_int('mg'))) - e.add_child(Node.s32('tid', lobby.get_int('tid'))) - e.add_child(Node.string('tn', lobby.get_str('tn'))) - e.add_child(Node.s32('topt', lobby.get_int('topt'))) - e.add_child(Node.string('lid', lobby.get_str('lid'))) - e.add_child(Node.string('sn', lobby.get_str('sn'))) - e.add_child(Node.u8('pref', lobby.get_int('pref'))) - e.add_child(Node.s8('stg', lobby.get_int('stg'))) - e.add_child(Node.s8('pside', lobby.get_int('pside'))) - e.add_child(Node.s16('eatime', lobby.get_int('eatime'))) - e.add_child(Node.u8_array('ga', lobby.get_int_array('ga', 4))) - e.add_child(Node.u16('gp', lobby.get_int('gp'))) - e.add_child(Node.u8_array('la', lobby.get_int_array('la', 4))) + e.add_child(Node.s32("eid", lobby.get_int("id"))) + e.add_child(Node.u16("mid", lobby.get_int("mid"))) + e.add_child(Node.u8("ng", lobby.get_int("ng"))) + e.add_child(Node.s32("uid", profile.extid)) + e.add_child(Node.string("pn", profile.get_str("name"))) + e.add_child(Node.s32("uattr", profile.get_int("uattr"))) + e.add_child(Node.s32("mopt", lobby.get_int("mopt"))) + e.add_child(Node.s16("mg", profile.get_int("mg"))) + e.add_child(Node.s32("tid", lobby.get_int("tid"))) + e.add_child(Node.string("tn", lobby.get_str("tn"))) + e.add_child(Node.s32("topt", lobby.get_int("topt"))) + e.add_child(Node.string("lid", lobby.get_str("lid"))) + e.add_child(Node.string("sn", lobby.get_str("sn"))) + e.add_child(Node.u8("pref", lobby.get_int("pref"))) + e.add_child(Node.s8("stg", lobby.get_int("stg"))) + e.add_child(Node.s8("pside", lobby.get_int("pside"))) + e.add_child(Node.s16("eatime", lobby.get_int("eatime"))) + e.add_child(Node.u8_array("ga", lobby.get_int_array("ga", 4))) + e.add_child(Node.u16("gp", lobby.get_int("gp"))) + e.add_child(Node.u8_array("la", lobby.get_int_array("la", 4))) return root def handle_lobby_read_request(self, request: Node) -> Node: - root = Node.void('lobby') - root.add_child(Node.s32('interval', 120)) - root.add_child(Node.s32('interval_p', 120)) + root = Node.void("lobby") + root.add_child(Node.s32("interval", 120)) + root.add_child(Node.s32("interval_p", 120)) # Look up all lobbies matching the criteria specified - mg = request.child_value('m_grade') # noqa: F841 - extid = request.child_value('uid') - limit = request.child_value('max') + mg = request.child_value("m_grade") # noqa: F841 + extid = request.child_value("uid") + limit = request.child_value("max") userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: lobbies = self.data.local.lobby.get_all_lobbies(self.game, self.version) @@ -413,92 +421,89 @@ class ReflecBeatLimelight(ReflecBeatBase): # No profile info, don't return this lobby continue - e = Node.void('e') + e = Node.void("e") root.add_child(e) - e.add_child(Node.s32('eid', lobby.get_int('id'))) - e.add_child(Node.u16('mid', lobby.get_int('mid'))) - e.add_child(Node.u8('ng', lobby.get_int('ng'))) - e.add_child(Node.s32('uid', profile.extid)) - e.add_child(Node.string('pn', profile.get_str('name'))) - e.add_child(Node.s32('uattr', profile.get_int('uattr'))) - e.add_child(Node.s32('mopt', lobby.get_int('mopt'))) - e.add_child(Node.s16('mg', profile.get_int('mg'))) - e.add_child(Node.s32('tid', lobby.get_int('tid'))) - e.add_child(Node.string('tn', lobby.get_str('tn'))) - e.add_child(Node.s32('topt', lobby.get_int('topt'))) - e.add_child(Node.string('lid', lobby.get_str('lid'))) - e.add_child(Node.string('sn', lobby.get_str('sn'))) - e.add_child(Node.u8('pref', lobby.get_int('pref'))) - e.add_child(Node.s8('stg', lobby.get_int('stg'))) - e.add_child(Node.s8('pside', lobby.get_int('pside'))) - e.add_child(Node.s16('eatime', lobby.get_int('eatime'))) - e.add_child(Node.u8_array('ga', lobby.get_int_array('ga', 4))) - e.add_child(Node.u16('gp', lobby.get_int('gp'))) - e.add_child(Node.u8_array('la', lobby.get_int_array('la', 4))) + e.add_child(Node.s32("eid", lobby.get_int("id"))) + e.add_child(Node.u16("mid", lobby.get_int("mid"))) + e.add_child(Node.u8("ng", lobby.get_int("ng"))) + e.add_child(Node.s32("uid", profile.extid)) + e.add_child(Node.string("pn", profile.get_str("name"))) + e.add_child(Node.s32("uattr", profile.get_int("uattr"))) + e.add_child(Node.s32("mopt", lobby.get_int("mopt"))) + e.add_child(Node.s16("mg", profile.get_int("mg"))) + e.add_child(Node.s32("tid", lobby.get_int("tid"))) + e.add_child(Node.string("tn", lobby.get_str("tn"))) + e.add_child(Node.s32("topt", lobby.get_int("topt"))) + e.add_child(Node.string("lid", lobby.get_str("lid"))) + e.add_child(Node.string("sn", lobby.get_str("sn"))) + e.add_child(Node.u8("pref", lobby.get_int("pref"))) + e.add_child(Node.s8("stg", lobby.get_int("stg"))) + e.add_child(Node.s8("pside", lobby.get_int("pside"))) + e.add_child(Node.s16("eatime", lobby.get_int("eatime"))) + e.add_child(Node.u8_array("ga", lobby.get_int_array("ga", 4))) + e.add_child(Node.u16("gp", lobby.get_int("gp"))) + e.add_child(Node.u8_array("la", lobby.get_int_array("la", 4))) limit = limit - 1 return root def handle_lobby_delete_request(self, request: Node) -> Node: - eid = request.child_value('eid') + eid = request.child_value("eid") self.data.local.lobby.destroy_lobby(eid) - return Node.void('lobby') + return Node.void("lobby") def handle_player_start_request(self, request: Node) -> Node: # Add a dummy entry into the lobby setup so we can clean up on end play - refid = request.child_value('rid') + refid = request.child_value("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: self.data.local.lobby.put_play_session_info( - self.game, - self.version, - userid, - {} + self.game, self.version, userid, {} ) - root = Node.void('player') - root.add_child(Node.bool('is_suc', True)) + root = Node.void("player") + root.add_child(Node.bool("is_suc", True)) - unlock_music = Node.void('unlock_music') + unlock_music = Node.void("unlock_music") root.add_child(unlock_music) - unlock_item = Node.void('unlock_item') + unlock_item = Node.void("unlock_item") root.add_child(unlock_item) - item_lock_ctrl = Node.void('item_lock_ctrl') + item_lock_ctrl = Node.void("item_lock_ctrl") root.add_child(item_lock_ctrl) - lincle_link_4 = Node.void('lincle_link_4') + lincle_link_4 = Node.void("lincle_link_4") root.add_child(lincle_link_4) - lincle_link_4.add_child(Node.u32('qpro', 0)) - lincle_link_4.add_child(Node.u32('glass', 0)) - lincle_link_4.add_child(Node.u32('treasure', 0)) - lincle_link_4.add_child(Node.bool('for_iidx_0_0', False)) - lincle_link_4.add_child(Node.bool('for_iidx_0_1', False)) - lincle_link_4.add_child(Node.bool('for_iidx_0_2', False)) - lincle_link_4.add_child(Node.bool('for_iidx_0_3', False)) - lincle_link_4.add_child(Node.bool('for_iidx_0_4', False)) - lincle_link_4.add_child(Node.bool('for_iidx_0_5', False)) - lincle_link_4.add_child(Node.bool('for_iidx_0_6', False)) - lincle_link_4.add_child(Node.bool('for_iidx_0', False)) - lincle_link_4.add_child(Node.bool('for_iidx_1', False)) - lincle_link_4.add_child(Node.bool('for_iidx_2', False)) - lincle_link_4.add_child(Node.bool('for_iidx_3', False)) - lincle_link_4.add_child(Node.bool('for_iidx_4', False)) - lincle_link_4.add_child(Node.bool('for_rb_0_0', False)) - lincle_link_4.add_child(Node.bool('for_rb_0_1', False)) - lincle_link_4.add_child(Node.bool('for_rb_0_2', False)) - lincle_link_4.add_child(Node.bool('for_rb_0_3', False)) - lincle_link_4.add_child(Node.bool('for_rb_0_4', False)) - lincle_link_4.add_child(Node.bool('for_rb_0_5', False)) - lincle_link_4.add_child(Node.bool('for_rb_0_6', False)) - lincle_link_4.add_child(Node.bool('for_rb_0', False)) - lincle_link_4.add_child(Node.bool('for_rb_1', False)) - lincle_link_4.add_child(Node.bool('for_rb_2', False)) - lincle_link_4.add_child(Node.bool('for_rb_3', False)) - lincle_link_4.add_child(Node.bool('for_rb_4', False)) - lincle_link_4.add_child(Node.bool('qproflg', False)) - lincle_link_4.add_child(Node.bool('glassflg', False)) - lincle_link_4.add_child(Node.bool('complete', False)) + lincle_link_4.add_child(Node.u32("qpro", 0)) + lincle_link_4.add_child(Node.u32("glass", 0)) + lincle_link_4.add_child(Node.u32("treasure", 0)) + lincle_link_4.add_child(Node.bool("for_iidx_0_0", False)) + lincle_link_4.add_child(Node.bool("for_iidx_0_1", False)) + lincle_link_4.add_child(Node.bool("for_iidx_0_2", False)) + lincle_link_4.add_child(Node.bool("for_iidx_0_3", False)) + lincle_link_4.add_child(Node.bool("for_iidx_0_4", False)) + lincle_link_4.add_child(Node.bool("for_iidx_0_5", False)) + lincle_link_4.add_child(Node.bool("for_iidx_0_6", False)) + lincle_link_4.add_child(Node.bool("for_iidx_0", False)) + lincle_link_4.add_child(Node.bool("for_iidx_1", False)) + lincle_link_4.add_child(Node.bool("for_iidx_2", False)) + lincle_link_4.add_child(Node.bool("for_iidx_3", False)) + lincle_link_4.add_child(Node.bool("for_iidx_4", False)) + lincle_link_4.add_child(Node.bool("for_rb_0_0", False)) + lincle_link_4.add_child(Node.bool("for_rb_0_1", False)) + lincle_link_4.add_child(Node.bool("for_rb_0_2", False)) + lincle_link_4.add_child(Node.bool("for_rb_0_3", False)) + lincle_link_4.add_child(Node.bool("for_rb_0_4", False)) + lincle_link_4.add_child(Node.bool("for_rb_0_5", False)) + lincle_link_4.add_child(Node.bool("for_rb_0_6", False)) + lincle_link_4.add_child(Node.bool("for_rb_0", False)) + lincle_link_4.add_child(Node.bool("for_rb_1", False)) + lincle_link_4.add_child(Node.bool("for_rb_2", False)) + lincle_link_4.add_child(Node.bool("for_rb_3", False)) + lincle_link_4.add_child(Node.bool("for_rb_4", False)) + lincle_link_4.add_child(Node.bool("qproflg", False)) + lincle_link_4.add_child(Node.bool("glassflg", False)) + lincle_link_4.add_child(Node.bool("complete", False)) # Add event info self.__add_event_info(root) @@ -506,11 +511,11 @@ class ReflecBeatLimelight(ReflecBeatBase): return root def handle_player_delete_request(self, request: Node) -> Node: - return Node.void('player') + return Node.void("player") def handle_player_end_request(self, request: Node) -> Node: # Destroy play session based on info from the request - refid = request.child_value('rid') + refid = request.child_value("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: # Kill any lingering lobbies by this user @@ -520,337 +525,447 @@ class ReflecBeatLimelight(ReflecBeatBase): userid, ) if lobby is not None: - self.data.local.lobby.destroy_lobby(lobby.get_int('id')) - self.data.local.lobby.destroy_play_session_info(self.game, self.version, userid) + self.data.local.lobby.destroy_lobby(lobby.get_int("id")) + self.data.local.lobby.destroy_play_session_info( + self.game, self.version, userid + ) - return Node.void('player') + return Node.void("player") def handle_player_read_request(self, request: Node) -> Node: - refid = request.child_value('rid') + refid = request.child_value("rid") profile = self.get_profile_by_refid(refid) if profile: return profile - return Node.void('player') + return Node.void("player") def handle_player_write_request(self, request: Node) -> Node: - refid = request.child_value('rid') + refid = request.child_value("rid") profile = self.put_profile_by_refid(refid, request) - root = Node.void('player') + root = Node.void("player") if profile is None: - root.add_child(Node.s32('uid', 0)) + root.add_child(Node.s32("uid", 0)) else: - root.add_child(Node.s32('uid', profile.extid)) - root.add_child(Node.s32('time', Time.now())) + root.add_child(Node.s32("uid", profile.extid)) + root.add_child(Node.s32("time", Time.now())) return root def handle_player_succeed_request(self, request: Node) -> Node: - refid = request.child_value('rid') + refid = request.child_value("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: previous_version = self.previous_version() profile = previous_version.get_profile(userid) - achievements = self.data.local.user.get_achievements(previous_version.game, previous_version.version, userid) - scores = self.data.remote.music.get_scores(previous_version.game, previous_version.version, userid) + achievements = self.data.local.user.get_achievements( + previous_version.game, previous_version.version, userid + ) + scores = self.data.remote.music.get_scores( + previous_version.game, previous_version.version, userid + ) else: profile = None - root = Node.void('player') + root = Node.void("player") if profile is None: - root.add_child(Node.string('name', '')) - root.add_child(Node.s32('lv', -1)) - root.add_child(Node.s32('exp', -1)) - root.add_child(Node.s32('grade', -1)) - root.add_child(Node.s32('ap', -1)) + root.add_child(Node.string("name", "")) + root.add_child(Node.s32("lv", -1)) + root.add_child(Node.s32("exp", -1)) + root.add_child(Node.s32("grade", -1)) + root.add_child(Node.s32("ap", -1)) - root.add_child(Node.void('released')) - root.add_child(Node.void('mrecord')) + root.add_child(Node.void("released")) + root.add_child(Node.void("mrecord")) else: - root.add_child(Node.string('name', profile.get_str('name'))) - root.add_child(Node.s32('lv', profile.get_int('lvl'))) - root.add_child(Node.s32('exp', profile.get_int('exp'))) - root.add_child(Node.s32('grade', profile.get_int('mg'))) # This is a guess - root.add_child(Node.s32('ap', profile.get_int('ap'))) + root.add_child(Node.string("name", profile.get_str("name"))) + root.add_child(Node.s32("lv", profile.get_int("lvl"))) + root.add_child(Node.s32("exp", profile.get_int("exp"))) + root.add_child(Node.s32("grade", profile.get_int("mg"))) # This is a guess + root.add_child(Node.s32("ap", profile.get_int("ap"))) - released = Node.void('released') + released = Node.void("released") root.add_child(released) for item in achievements: - if item.type != 'item_0': + if item.type != "item_0": continue - released.add_child(Node.s16('i', item.id)) + released.add_child(Node.s16("i", item.id)) - mrecord = Node.void('mrecord') + mrecord = Node.void("mrecord") root.add_child(mrecord) for score in scores: - mrec = Node.void('mrec') + mrec = Node.void("mrec") mrecord.add_child(mrec) - mrec.add_child(Node.s32('mid', score.id)) - mrec.add_child(Node.s32('ctype', score.chart)) - mrec.add_child(Node.s32('win', score.data.get_dict('stats').get_int('win'))) - mrec.add_child(Node.s32('lose', score.data.get_dict('stats').get_int('win'))) - mrec.add_child(Node.s32('draw', score.data.get_dict('stats').get_int('win'))) - mrec.add_child(Node.s32('score', score.points)) - mrec.add_child(Node.s32('combo', score.data.get_int('combo'))) - mrec.add_child(Node.s32('miss', score.data.get_int('miss_count'))) - mrec.add_child(Node.s32('grade', self.__db_to_game_clear_type(score.data.get_int('clear_type'), score.data.get_int('combo_type')))) - mrec.add_child(Node.s32('ap', score.data.get_int('achievement_rate'))) + mrec.add_child(Node.s32("mid", score.id)) + mrec.add_child(Node.s32("ctype", score.chart)) + mrec.add_child( + Node.s32("win", score.data.get_dict("stats").get_int("win")) + ) + mrec.add_child( + Node.s32("lose", score.data.get_dict("stats").get_int("win")) + ) + mrec.add_child( + Node.s32("draw", score.data.get_dict("stats").get_int("win")) + ) + mrec.add_child(Node.s32("score", score.points)) + mrec.add_child(Node.s32("combo", score.data.get_int("combo"))) + mrec.add_child(Node.s32("miss", score.data.get_int("miss_count"))) + mrec.add_child( + Node.s32( + "grade", + self.__db_to_game_clear_type( + score.data.get_int("clear_type"), + score.data.get_int("combo_type"), + ), + ) + ) + mrec.add_child(Node.s32("ap", score.data.get_int("achievement_rate"))) return root def format_profile(self, userid: UserID, profile: Profile) -> Node: statistics = self.get_play_statistics(userid) game_config = self.get_game_config() - achievements = self.data.local.user.get_achievements(self.game, self.version, userid) + achievements = self.data.local.user.get_achievements( + self.game, self.version, userid + ) scores = self.data.remote.music.get_scores(self.game, self.version, userid) links = self.data.local.user.get_links(self.game, self.version, userid) - root = Node.void('player') - pdata = Node.void('pdata') + root = Node.void("player") + pdata = Node.void("pdata") root.add_child(pdata) - base = Node.void('base') + base = Node.void("base") pdata.add_child(base) - base.add_child(Node.s32('uid', profile.extid)) - base.add_child(Node.string('name', profile.get_str('name'))) - base.add_child(Node.s16('icon_id', profile.get_int('icon'))) - base.add_child(Node.s16('lv', profile.get_int('lvl'))) - base.add_child(Node.s32('exp', profile.get_int('exp'))) - base.add_child(Node.s16('mg', profile.get_int('mg'))) - base.add_child(Node.s16('ap', profile.get_int('ap'))) - base.add_child(Node.s32('pc', profile.get_int('pc'))) - base.add_child(Node.s32('uattr', profile.get_int('uattr'))) + base.add_child(Node.s32("uid", profile.extid)) + base.add_child(Node.string("name", profile.get_str("name"))) + base.add_child(Node.s16("icon_id", profile.get_int("icon"))) + base.add_child(Node.s16("lv", profile.get_int("lvl"))) + base.add_child(Node.s32("exp", profile.get_int("exp"))) + base.add_child(Node.s16("mg", profile.get_int("mg"))) + base.add_child(Node.s16("ap", profile.get_int("ap"))) + base.add_child(Node.s32("pc", profile.get_int("pc"))) + base.add_child(Node.s32("uattr", profile.get_int("uattr"))) - con = Node.void('con') + con = Node.void("con") pdata.add_child(con) - con.add_child(Node.s32('day', statistics.today_plays)) - con.add_child(Node.s32('cnt', statistics.total_plays)) - con.add_child(Node.s32('total_cnt', statistics.total_plays)) - con.add_child(Node.s32('last', statistics.last_play_timestamp)) - con.add_child(Node.s32('now', Time.now())) + con.add_child(Node.s32("day", statistics.today_plays)) + con.add_child(Node.s32("cnt", statistics.total_plays)) + con.add_child(Node.s32("total_cnt", statistics.total_plays)) + con.add_child(Node.s32("last", statistics.last_play_timestamp)) + con.add_child(Node.s32("now", Time.now())) - team = Node.void('team') + team = Node.void("team") pdata.add_child(team) - team.add_child(Node.s32('id', profile.get_int('team_id', -1))) - team.add_child(Node.string('name', profile.get_str('team_name', ''))) + team.add_child(Node.s32("id", profile.get_int("team_id", -1))) + team.add_child(Node.string("name", profile.get_str("team_name", ""))) - custom = Node.void('custom') - customdict = profile.get_dict('custom') + custom = Node.void("custom") + customdict = profile.get_dict("custom") pdata.add_child(custom) - custom.add_child(Node.u8('s_gls', customdict.get_int('s_gls'))) - custom.add_child(Node.u8('bgm_m', customdict.get_int('bgm_m'))) - custom.add_child(Node.u8('st_f', customdict.get_int('st_f'))) - custom.add_child(Node.u8('st_bg', customdict.get_int('st_bg'))) - custom.add_child(Node.u8('st_bg_b', customdict.get_int('st_bg_b'))) - custom.add_child(Node.u8('eff_e', customdict.get_int('eff_e'))) - custom.add_child(Node.u8('se_s', customdict.get_int('se_s'))) - custom.add_child(Node.u8('se_s_v', customdict.get_int('se_s_v'))) - custom.add_child(Node.s16('last_music_id', customdict.get_int('last_music_id'))) - custom.add_child(Node.u8('last_note_grade', customdict.get_int('last_note_grade'))) - custom.add_child(Node.u8('sort_type', customdict.get_int('sort_type'))) - custom.add_child(Node.u8('narrowdown_type', customdict.get_int('narrowdown_type'))) - custom.add_child(Node.bool('is_begginer', customdict.get_bool('is_begginer'))) # Yes, this is spelled right - custom.add_child(Node.bool('is_tut', customdict.get_bool('is_tut'))) - custom.add_child(Node.s16_array('symbol_chat_0', customdict.get_int_array('symbol_chat_0', 6))) - custom.add_child(Node.s16_array('symbol_chat_1', customdict.get_int_array('symbol_chat_1', 6))) - custom.add_child(Node.u8('gauge_style', customdict.get_int('gauge_style'))) - custom.add_child(Node.u8('obj_shade', customdict.get_int('obj_shade'))) - custom.add_child(Node.u8('obj_size', customdict.get_int('obj_size'))) - custom.add_child(Node.s16_array('byword', customdict.get_int_array('byword', 2))) - custom.add_child(Node.bool_array('is_auto_byword', customdict.get_bool_array('is_auto_byword', 2))) - custom.add_child(Node.bool('is_tweet', customdict.get_bool('is_tweet'))) - custom.add_child(Node.bool('is_link_twitter', customdict.get_bool('is_link_twitter'))) - custom.add_child(Node.s16('mrec_type', customdict.get_int('mrec_type'))) - custom.add_child(Node.s16('card_disp_type', customdict.get_int('card_disp_type'))) - custom.add_child(Node.s16('tab_sel', customdict.get_int('tab_sel'))) - custom.add_child(Node.s32_array('hidden_param', customdict.get_int_array('hidden_param', 20))) + custom.add_child(Node.u8("s_gls", customdict.get_int("s_gls"))) + custom.add_child(Node.u8("bgm_m", customdict.get_int("bgm_m"))) + custom.add_child(Node.u8("st_f", customdict.get_int("st_f"))) + custom.add_child(Node.u8("st_bg", customdict.get_int("st_bg"))) + custom.add_child(Node.u8("st_bg_b", customdict.get_int("st_bg_b"))) + custom.add_child(Node.u8("eff_e", customdict.get_int("eff_e"))) + custom.add_child(Node.u8("se_s", customdict.get_int("se_s"))) + custom.add_child(Node.u8("se_s_v", customdict.get_int("se_s_v"))) + custom.add_child(Node.s16("last_music_id", customdict.get_int("last_music_id"))) + custom.add_child( + Node.u8("last_note_grade", customdict.get_int("last_note_grade")) + ) + custom.add_child(Node.u8("sort_type", customdict.get_int("sort_type"))) + custom.add_child( + Node.u8("narrowdown_type", customdict.get_int("narrowdown_type")) + ) + custom.add_child( + Node.bool("is_begginer", customdict.get_bool("is_begginer")) + ) # Yes, this is spelled right + custom.add_child(Node.bool("is_tut", customdict.get_bool("is_tut"))) + custom.add_child( + Node.s16_array( + "symbol_chat_0", customdict.get_int_array("symbol_chat_0", 6) + ) + ) + custom.add_child( + Node.s16_array( + "symbol_chat_1", customdict.get_int_array("symbol_chat_1", 6) + ) + ) + custom.add_child(Node.u8("gauge_style", customdict.get_int("gauge_style"))) + custom.add_child(Node.u8("obj_shade", customdict.get_int("obj_shade"))) + custom.add_child(Node.u8("obj_size", customdict.get_int("obj_size"))) + custom.add_child( + Node.s16_array("byword", customdict.get_int_array("byword", 2)) + ) + custom.add_child( + Node.bool_array( + "is_auto_byword", customdict.get_bool_array("is_auto_byword", 2) + ) + ) + custom.add_child(Node.bool("is_tweet", customdict.get_bool("is_tweet"))) + custom.add_child( + Node.bool("is_link_twitter", customdict.get_bool("is_link_twitter")) + ) + custom.add_child(Node.s16("mrec_type", customdict.get_int("mrec_type"))) + custom.add_child( + Node.s16("card_disp_type", customdict.get_int("card_disp_type")) + ) + custom.add_child(Node.s16("tab_sel", customdict.get_int("tab_sel"))) + custom.add_child( + Node.s32_array("hidden_param", customdict.get_int_array("hidden_param", 20)) + ) - released = Node.void('released') + released = Node.void("released") pdata.add_child(released) for item in achievements: - if item.type[:5] != 'item_': + if item.type[:5] != "item_": continue itemtype = int(item.type[5:]) - if game_config.get_bool('force_unlock_songs') and itemtype == 0: + if game_config.get_bool("force_unlock_songs") and itemtype == 0: # Don't echo unlocks when we're force unlocking, we'll do it later continue - info = Node.void('info') + info = Node.void("info") released.add_child(info) - info.add_child(Node.u8('type', itemtype)) - info.add_child(Node.u16('id', item.id)) - info.add_child(Node.u16('param', item.data.get_int('param'))) + info.add_child(Node.u8("type", itemtype)) + info.add_child(Node.u16("id", item.id)) + info.add_child(Node.u16("param", item.data.get_int("param"))) - if game_config.get_bool('force_unlock_songs'): + if game_config.get_bool("force_unlock_songs"): ids: Dict[int, int] = {} songs = self.data.local.music.get_all_songs(self.game, self.version) for song in songs: if song.id not in ids: ids[song.id] = 0 - if song.data.get_int('difficulty') > 0: + if song.data.get_int("difficulty") > 0: ids[song.id] = ids[song.id] | (1 << song.chart) for songid in ids: if ids[songid] == 0: continue - info = Node.void('info') + info = Node.void("info") released.add_child(info) - info.add_child(Node.u8('type', 0)) - info.add_child(Node.u16('id', songid)) - info.add_child(Node.u16('param', ids[songid])) + info.add_child(Node.u8("type", 0)) + info.add_child(Node.u16("id", songid)) + info.add_child(Node.u16("param", ids[songid])) # Scores - record = Node.void('record') + record = Node.void("record") pdata.add_child(record) for score in scores: - rec = Node.void('rec') + rec = Node.void("rec") record.add_child(rec) - rec.add_child(Node.u16('mid', score.id)) - rec.add_child(Node.u8('ng', score.chart)) - rec.add_child(Node.s32('point', score.data.get_dict('stats').get_int('earned_points'))) - rec.add_child(Node.s32('played_time', score.timestamp)) + rec.add_child(Node.u16("mid", score.id)) + rec.add_child(Node.u8("ng", score.chart)) + rec.add_child( + Node.s32("point", score.data.get_dict("stats").get_int("earned_points")) + ) + rec.add_child(Node.s32("played_time", score.timestamp)) - mrec_0 = Node.void('mrec_0') + mrec_0 = Node.void("mrec_0") rec.add_child(mrec_0) - mrec_0.add_child(Node.s32('win', score.data.get_dict('stats').get_int('win'))) - mrec_0.add_child(Node.s32('lose', score.data.get_dict('stats').get_int('lose'))) - mrec_0.add_child(Node.s32('draw', score.data.get_dict('stats').get_int('draw'))) - mrec_0.add_child(Node.u8('ct', self.__db_to_game_clear_type(score.data.get_int('clear_type'), score.data.get_int('combo_type')))) - mrec_0.add_child(Node.s16('ar', int(score.data.get_int('achievement_rate') / 10))) - mrec_0.add_child(Node.s32('bs', score.points)) - mrec_0.add_child(Node.s16('mc', score.data.get_int('combo'))) - mrec_0.add_child(Node.s16('bmc', score.data.get_int('miss_count'))) + mrec_0.add_child( + Node.s32("win", score.data.get_dict("stats").get_int("win")) + ) + mrec_0.add_child( + Node.s32("lose", score.data.get_dict("stats").get_int("lose")) + ) + mrec_0.add_child( + Node.s32("draw", score.data.get_dict("stats").get_int("draw")) + ) + mrec_0.add_child( + Node.u8( + "ct", + self.__db_to_game_clear_type( + score.data.get_int("clear_type"), + score.data.get_int("combo_type"), + ), + ) + ) + mrec_0.add_child( + Node.s16("ar", int(score.data.get_int("achievement_rate") / 10)) + ) + mrec_0.add_child(Node.s32("bs", score.points)) + mrec_0.add_child(Node.s16("mc", score.data.get_int("combo"))) + mrec_0.add_child(Node.s16("bmc", score.data.get_int("miss_count"))) - mrec_1 = Node.void('mrec_1') + mrec_1 = Node.void("mrec_1") rec.add_child(mrec_1) - mrec_1.add_child(Node.s32('win', 0)) - mrec_1.add_child(Node.s32('lose', 0)) - mrec_1.add_child(Node.s32('draw', 0)) - mrec_1.add_child(Node.u8('ct', 0)) - mrec_1.add_child(Node.s16('ar', 0)) - mrec_1.add_child(Node.s32('bs', 0)) - mrec_1.add_child(Node.s16('mc', 0)) - mrec_1.add_child(Node.s16('bmc', -1)) + mrec_1.add_child(Node.s32("win", 0)) + mrec_1.add_child(Node.s32("lose", 0)) + mrec_1.add_child(Node.s32("draw", 0)) + mrec_1.add_child(Node.u8("ct", 0)) + mrec_1.add_child(Node.s16("ar", 0)) + mrec_1.add_child(Node.s32("bs", 0)) + mrec_1.add_child(Node.s16("mc", 0)) + mrec_1.add_child(Node.s16("bmc", -1)) # Comment (seems unused?) - pdata.add_child(Node.string('cmnt', '')) + pdata.add_child(Node.string("cmnt", "")) # Rivals - rival = Node.void('rival') + rival = Node.void("rival") pdata.add_child(rival) slotid = 0 for link in links: - if link.type != 'rival': + if link.type != "rival": continue rprofile = self.get_profile(link.other_userid) if rprofile is None: continue - r = Node.void('r') + r = Node.void("r") rival.add_child(r) - r.add_child(Node.u8('slot_id', slotid)) - r.add_child(Node.string('name', rprofile.get_str('name'))) - r.add_child(Node.s32('id', rprofile.extid)) - r.add_child(Node.bool('friend', True)) - r.add_child(Node.bool('locked', False)) - r.add_child(Node.s32('rc', 0)) + r.add_child(Node.u8("slot_id", slotid)) + r.add_child(Node.string("name", rprofile.get_str("name"))) + r.add_child(Node.s32("id", rprofile.extid)) + r.add_child(Node.bool("friend", True)) + r.add_child(Node.bool("locked", False)) + r.add_child(Node.s32("rc", 0)) slotid = slotid + 1 # Glass points - glass = Node.void('glass') + glass = Node.void("glass") pdata.add_child(glass) for item in achievements: - if item.type != 'glass': + if item.type != "glass": continue - g = Node.void('g') + g = Node.void("g") glass.add_child(g) - g.add_child(Node.s32('id', item.id)) - g.add_child(Node.s32('exp', item.data.get_int('exp'))) + g.add_child(Node.s32("id", item.id)) + g.add_child(Node.s32("exp", item.data.get_int("exp"))) # Favorite music - fav_music_slot = Node.void('fav_music_slot') + fav_music_slot = Node.void("fav_music_slot") pdata.add_child(fav_music_slot) for item in achievements: - if item.type != 'music': + if item.type != "music": continue - slot = Node.void('slot') + slot = Node.void("slot") fav_music_slot.add_child(slot) - slot.add_child(Node.u8('slot_id', item.id)) - slot.add_child(Node.s16('music_id', item.data.get_int('music_id'))) + slot.add_child(Node.u8("slot_id", item.id)) + slot.add_child(Node.s16("music_id", item.data.get_int("music_id"))) - narrow_down = Node.void('narrow_down') + narrow_down = Node.void("narrow_down") pdata.add_child(narrow_down) - narrow_down.add_child(Node.s32_array('adv_param', [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1])) + narrow_down.add_child( + Node.s32_array( + "adv_param", + [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], + ) + ) return root - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: game_config = self.get_game_config() newprofile = oldprofile.clone() - newprofile.replace_int('lid', ID.parse_machine_id(request.child_value('lid'))) - newprofile.replace_str('name', request.child_value('pdata/base/name')) - newprofile.replace_int('icon', request.child_value('pdata/base/icon_id')) - newprofile.replace_int('lvl', request.child_value('pdata/base/lv')) - newprofile.replace_int('exp', request.child_value('pdata/base/exp')) - newprofile.replace_int('mg', request.child_value('pdata/base/mg')) - newprofile.replace_int('ap', request.child_value('pdata/base/ap')) - newprofile.replace_int('pc', request.child_value('pdata/base/pc')) - newprofile.replace_int('uattr', request.child_value('pdata/base/uattr')) + newprofile.replace_int("lid", ID.parse_machine_id(request.child_value("lid"))) + newprofile.replace_str("name", request.child_value("pdata/base/name")) + newprofile.replace_int("icon", request.child_value("pdata/base/icon_id")) + newprofile.replace_int("lvl", request.child_value("pdata/base/lv")) + newprofile.replace_int("exp", request.child_value("pdata/base/exp")) + newprofile.replace_int("mg", request.child_value("pdata/base/mg")) + newprofile.replace_int("ap", request.child_value("pdata/base/ap")) + newprofile.replace_int("pc", request.child_value("pdata/base/pc")) + newprofile.replace_int("uattr", request.child_value("pdata/base/uattr")) - customdict = newprofile.get_dict('custom') - custom = request.child('pdata/custom') + customdict = newprofile.get_dict("custom") + custom = request.child("pdata/custom") if custom: - customdict.replace_int('s_gls', custom.child_value('s_gls')) - customdict.replace_int('bgm_m', custom.child_value('bgm_m')) - customdict.replace_int('st_f', custom.child_value('st_f')) - customdict.replace_int('st_bg', custom.child_value('st_bg')) - customdict.replace_int('st_bg_b', custom.child_value('st_bg_b')) - customdict.replace_int('eff_e', custom.child_value('eff_e')) - customdict.replace_int('se_s', custom.child_value('se_s')) - customdict.replace_int('se_s_v', custom.child_value('se_s_v')) - customdict.replace_int('last_music_id', custom.child_value('last_music_id')) - customdict.replace_int('last_note_grade', custom.child_value('last_note_grade')) - customdict.replace_int('sort_type', custom.child_value('sort_type')) - customdict.replace_int('narrowdown_type', custom.child_value('narrowdown_type')) - customdict.replace_bool('is_begginer', custom.child_value('is_begginer')) # Yes, this is spelled right - customdict.replace_bool('is_tut', custom.child_value('is_tut')) - customdict.replace_int_array('symbol_chat_0', 6, custom.child_value('symbol_chat_0')) - customdict.replace_int_array('symbol_chat_1', 6, custom.child_value('symbol_chat_1')) - customdict.replace_int('gauge_style', custom.child_value('gauge_style')) - customdict.replace_int('obj_shade', custom.child_value('obj_shade')) - customdict.replace_int('obj_size', custom.child_value('obj_size')) - customdict.replace_int_array('byword', 2, custom.child_value('byword')) - customdict.replace_bool_array('is_auto_byword', 2, custom.child_value('is_auto_byword')) - customdict.replace_bool('is_tweet', custom.child_value('is_tweet')) - customdict.replace_bool('is_link_twitter', custom.child_value('is_link_twitter')) - customdict.replace_int('mrec_type', custom.child_value('mrec_type')) - customdict.replace_int('card_disp_type', custom.child_value('card_disp_type')) - customdict.replace_int('tab_sel', custom.child_value('tab_sel')) - customdict.replace_int_array('hidden_param', 20, custom.child_value('hidden_param')) - newprofile.replace_dict('custom', customdict) + customdict.replace_int("s_gls", custom.child_value("s_gls")) + customdict.replace_int("bgm_m", custom.child_value("bgm_m")) + customdict.replace_int("st_f", custom.child_value("st_f")) + customdict.replace_int("st_bg", custom.child_value("st_bg")) + customdict.replace_int("st_bg_b", custom.child_value("st_bg_b")) + customdict.replace_int("eff_e", custom.child_value("eff_e")) + customdict.replace_int("se_s", custom.child_value("se_s")) + customdict.replace_int("se_s_v", custom.child_value("se_s_v")) + customdict.replace_int("last_music_id", custom.child_value("last_music_id")) + customdict.replace_int( + "last_note_grade", custom.child_value("last_note_grade") + ) + customdict.replace_int("sort_type", custom.child_value("sort_type")) + customdict.replace_int( + "narrowdown_type", custom.child_value("narrowdown_type") + ) + customdict.replace_bool( + "is_begginer", custom.child_value("is_begginer") + ) # Yes, this is spelled right + customdict.replace_bool("is_tut", custom.child_value("is_tut")) + customdict.replace_int_array( + "symbol_chat_0", 6, custom.child_value("symbol_chat_0") + ) + customdict.replace_int_array( + "symbol_chat_1", 6, custom.child_value("symbol_chat_1") + ) + customdict.replace_int("gauge_style", custom.child_value("gauge_style")) + customdict.replace_int("obj_shade", custom.child_value("obj_shade")) + customdict.replace_int("obj_size", custom.child_value("obj_size")) + customdict.replace_int_array("byword", 2, custom.child_value("byword")) + customdict.replace_bool_array( + "is_auto_byword", 2, custom.child_value("is_auto_byword") + ) + customdict.replace_bool("is_tweet", custom.child_value("is_tweet")) + customdict.replace_bool( + "is_link_twitter", custom.child_value("is_link_twitter") + ) + customdict.replace_int("mrec_type", custom.child_value("mrec_type")) + customdict.replace_int( + "card_disp_type", custom.child_value("card_disp_type") + ) + customdict.replace_int("tab_sel", custom.child_value("tab_sel")) + customdict.replace_int_array( + "hidden_param", 20, custom.child_value("hidden_param") + ) + newprofile.replace_dict("custom", customdict) # Music unlocks and other stuff - released = request.child('pdata/released') + released = request.child("pdata/released") if released: for child in released.children: - if child.name != 'info': + if child.name != "info": continue - item_id = child.child_value('id') - item_type = child.child_value('type') - param = child.child_value('param') - if game_config.get_bool('force_unlock_songs') and item_type == 0: + item_id = child.child_value("id") + item_type = child.child_value("type") + param = child.child_value("param") + if game_config.get_bool("force_unlock_songs") and item_type == 0: # Don't save unlocks when we're force unlocking continue @@ -859,9 +974,9 @@ class ReflecBeatLimelight(ReflecBeatBase): self.version, userid, item_id, - f'item_{item_type}', + f"item_{item_type}", { - 'param': param, + "param": param, }, ) @@ -872,45 +987,45 @@ class ReflecBeatLimelight(ReflecBeatBase): # the previous try. So, we must also look at the battle log for the actual play scores, # and combine the data if we can. savedrecords: Dict[int, Dict[int, Dict[str, int]]] = {} - songplays = request.child('pdata/record') + songplays = request.child("pdata/record") if songplays: for child in songplays.children: - if child.name != 'rec': + if child.name != "rec": continue - songid = child.child_value('mid') - chart = child.child_value('ng') + songid = child.child_value("mid") + chart = child.child_value("ng") # These don't get sent with the battle logs, so we try to construct # the values here. if songid not in savedrecords: savedrecords[songid] = {} savedrecords[songid][chart] = { - 'achievement_rate': child.child_value('mrec_0/ar') * 10, - 'points': child.child_value('mrec_0/bs'), - 'combo': child.child_value('mrec_0/mc'), - 'miss_count': child.child_value('mrec_0/bmc'), - 'win': child.child_value('mrec_0/win'), - 'lose': child.child_value('mrec_0/lose'), - 'draw': child.child_value('mrec_0/draw'), - 'earned_points': child.child_value('point'), + "achievement_rate": child.child_value("mrec_0/ar") * 10, + "points": child.child_value("mrec_0/bs"), + "combo": child.child_value("mrec_0/mc"), + "miss_count": child.child_value("mrec_0/bmc"), + "win": child.child_value("mrec_0/win"), + "lose": child.child_value("mrec_0/lose"), + "draw": child.child_value("mrec_0/draw"), + "earned_points": child.child_value("point"), } # Now, see the actual battles that were played. If we can, unify the data with a record. # We only do that when the record achievement rate and score matches the battle achievement # rate and score, so we know for a fact that that record was generated by this battle. - battlelogs = request.child('pdata/blog') + battlelogs = request.child("pdata/blog") if battlelogs: for child in battlelogs.children: - if child.name != 'log': + if child.name != "log": continue - songid = child.child_value('mid') - chart = child.child_value('ng') + songid = child.child_value("mid") + chart = child.child_value("ng") - clear_type = child.child_value('myself/ct') - achievement_rate = child.child_value('myself/ar') * 10 - points = child.child_value('myself/s') + clear_type = child.child_value("myself/ct") + achievement_rate = child.child_value("myself/ar") * 10 + points = child.child_value("myself/s") clear_type, combo_type = self.__game_to_db_clear_type(clear_type) @@ -923,18 +1038,18 @@ class ReflecBeatLimelight(ReflecBeatBase): data = savedrecords[songid][chart] if ( - data['achievement_rate'] == achievement_rate and - data['points'] == points + data["achievement_rate"] == achievement_rate + and data["points"] == points ): # This is the same record! Use the stats from it to update our # internal representation. - combo = data['combo'] - miss_count = data['miss_count'] + combo = data["combo"] + miss_count = data["miss_count"] stats = { - 'win': data['win'], - 'lose': data['lose'], - 'draw': data['draw'], - 'earned_points': data['earned_points'], + "win": data["win"], + "lose": data["lose"], + "draw": data["draw"], + "earned_points": data["earned_points"], } self.update_score( @@ -951,34 +1066,34 @@ class ReflecBeatLimelight(ReflecBeatBase): ) # Keep track of glass points so unlocks work - glass = request.child('pdata/glass') + glass = request.child("pdata/glass") if glass: for child in glass.children: - if child.name != 'g': + if child.name != "g": continue - gid = child.child_value('id') - exp = child.child_value('exp') + gid = child.child_value("id") + exp = child.child_value("exp") self.data.local.user.put_achievement( self.game, self.version, userid, gid, - 'glass', + "glass", { - 'exp': exp, + "exp": exp, }, ) # Keep track of favorite music selections - fav_music_slot = request.child('pdata/fav_music_slot') + fav_music_slot = request.child("pdata/fav_music_slot") if fav_music_slot: for child in fav_music_slot.children: - if child.name != 'slot': + if child.name != "slot": continue - slot_id = child.child_value('slot_id') - music_id = child.child_value('music_id') + slot_id = child.child_value("slot_id") + music_id = child.child_value("music_id") if music_id == -1: # Delete this favorite self.data.local.user.destroy_achievement( @@ -986,7 +1101,7 @@ class ReflecBeatLimelight(ReflecBeatBase): self.version, userid, slot_id, - 'music', + "music", ) else: # Add/update this favorite @@ -995,9 +1110,9 @@ class ReflecBeatLimelight(ReflecBeatBase): self.version, userid, slot_id, - 'music', + "music", { - 'music_id': music_id, + "music_id": music_id, }, ) diff --git a/bemani/backend/reflec/reflecbeat.py b/bemani/backend/reflec/reflecbeat.py index b30c821..6338fe3 100644 --- a/bemani/backend/reflec/reflecbeat.py +++ b/bemani/backend/reflec/reflecbeat.py @@ -27,15 +27,15 @@ class ReflecBeat(ReflecBeatBase): Return all of our front-end modifiably settings. """ return { - 'bools': [ + "bools": [ { - 'name': 'Force Song Unlock', - 'tip': 'Force unlock all songs.', - 'category': 'game_config', - 'setting': 'force_unlock_songs', + "name": "Force Song Unlock", + "tip": "Force unlock all songs.", + "category": "game_config", + "setting": "force_unlock_songs", }, ], - 'ints': [], + "ints": [], } def __db_to_game_clear_type(self, db_clear_type: int, db_combo_type: int) -> int: @@ -59,10 +59,12 @@ class ReflecBeat(ReflecBeatBase): ]: return self.GAME_CLEAR_TYPE_FULL_COMBO - raise Exception(f'Invalid db_combo_type {db_combo_type}') - raise Exception(f'Invalid db_clear_type {db_clear_type}') + raise Exception(f"Invalid db_combo_type {db_combo_type}") + raise Exception(f"Invalid db_clear_type {db_clear_type}") - def __game_to_db_clear_type(self, game_clear_type: int, game_achievement_rate: int) -> Tuple[int, int]: + def __game_to_db_clear_type( + self, game_clear_type: int, game_achievement_rate: int + ) -> Tuple[int, int]: if game_clear_type == self.GAME_CLEAR_TYPE_NO_PLAY: return (self.CLEAR_TYPE_NO_PLAY, self.COMBO_TYPE_NONE) if game_clear_type == self.GAME_CLEAR_TYPE_PLAYED: @@ -73,67 +75,69 @@ class ReflecBeat(ReflecBeatBase): if game_clear_type == self.GAME_CLEAR_TYPE_FULL_COMBO: return (self.CLEAR_TYPE_CLEARED, self.COMBO_TYPE_FULL_COMBO) - raise Exception(f'Invalid game_clear_type {game_clear_type}') + raise Exception(f"Invalid game_clear_type {game_clear_type}") def handle_log_pcb_status_request(self, request: Node) -> Node: - return Node.void('log') + return Node.void("log") def handle_log_opsetting_request(self, request: Node) -> Node: - return Node.void('log') + return Node.void("log") def handle_log_play_request(self, request: Node) -> Node: - return Node.void('log') + return Node.void("log") def handle_pcbinfo_get_request(self, request: Node) -> Node: - shop_id = ID.parse_machine_id(request.child_value('lid')) + shop_id = ID.parse_machine_id(request.child_value("lid")) machine = self.get_machine_by_id(shop_id) if machine is not None: machine_name = machine.name - close = machine.data.get_bool('close') - hour = machine.data.get_int('hour') - minute = machine.data.get_int('minute') - pref = machine.data.get_int('pref', self.get_machine_region()) + close = machine.data.get_bool("close") + hour = machine.data.get_int("hour") + minute = machine.data.get_int("minute") + pref = machine.data.get_int("pref", self.get_machine_region()) else: - machine_name = '' + machine_name = "" close = False hour = 0 minute = 0 pref = self.get_machine_region() - root = Node.void('pcbinfo') - info = Node.void('info') + root = Node.void("pcbinfo") + info = Node.void("info") root.add_child(info) - info.add_child(Node.string('name', machine_name)) - info.add_child(Node.s16('pref', pref)) - info.add_child(Node.bool('close', close)) - info.add_child(Node.u8('hour', hour)) - info.add_child(Node.u8('min', minute)) + info.add_child(Node.string("name", machine_name)) + info.add_child(Node.s16("pref", pref)) + info.add_child(Node.bool("close", close)) + info.add_child(Node.u8("hour", hour)) + info.add_child(Node.u8("min", minute)) return root def handle_pcbinfo_set_request(self, request: Node) -> Node: - self.update_machine_name(request.child_value('info/name')) - self.update_machine_data({ - 'close': request.child_value('info/close'), - 'hour': request.child_value('info/hour'), - 'minute': request.child_value('info/min'), - 'pref': request.child_value('info/pref'), - }) - return Node.void('pcbinfo') + self.update_machine_name(request.child_value("info/name")) + self.update_machine_data( + { + "close": request.child_value("info/close"), + "hour": request.child_value("info/hour"), + "minute": request.child_value("info/min"), + "pref": request.child_value("info/pref"), + } + ) + return Node.void("pcbinfo") def __add_event_info(self, request: Node) -> None: events: Dict[int, int] = {} for (_eventid, _phase) in events.items(): - data = Node.void('data') + data = Node.void("data") request.add_child(data) - data.add_child(Node.s32('type', -1)) - data.add_child(Node.s32('value', -1)) + data.add_child(Node.s32("type", -1)) + data.add_child(Node.s32("value", -1)) def handle_sysinfo_get_request(self, request: Node) -> Node: - root = Node.void('sysinfo') - trd = Node.void('trd') + root = Node.void("sysinfo") + trd = Node.void("trd") root.add_child(trd) # Add event info @@ -142,16 +146,16 @@ class ReflecBeat(ReflecBeatBase): return root def handle_sysinfo_fan_request(self, request: Node) -> Node: - sysinfo = Node.void('sysinfo') - sysinfo.add_child(Node.u8('pref', self.get_machine_region())) - sysinfo.add_child(Node.string('lid', request.child_value('lid'))) + sysinfo = Node.void("sysinfo") + sysinfo.add_child(Node.u8("pref", self.get_machine_region())) + sysinfo.add_child(Node.string("lid", request.child_value("lid"))) return sysinfo def handle_lobby_entry_request(self, request: Node) -> Node: - root = Node.void('lobby') + root = Node.void("lobby") # Create a lobby entry for this user - extid = request.child_value('e/uid') + extid = request.child_value("e/uid") userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: profile = self.get_profile(userid) @@ -160,49 +164,49 @@ class ReflecBeat(ReflecBeatBase): self.version, userid, { - 'mid': request.child_value('e/mid'), - 'ng': request.child_value('e/ng'), - 'lid': request.child_value('e/lid'), - 'sn': request.child_value('e/sn'), - 'pref': request.child_value('e/pref'), - 'ga': request.child_value('e/ga'), - 'gp': request.child_value('e/gp'), - 'la': request.child_value('e/la'), - } + "mid": request.child_value("e/mid"), + "ng": request.child_value("e/ng"), + "lid": request.child_value("e/lid"), + "sn": request.child_value("e/sn"), + "pref": request.child_value("e/pref"), + "ga": request.child_value("e/ga"), + "gp": request.child_value("e/gp"), + "la": request.child_value("e/la"), + }, ) lobby = self.data.local.lobby.get_lobby( self.game, self.version, userid, ) - root.add_child(Node.s32('eid', lobby.get_int('id'))) - e = Node.void('e') + root.add_child(Node.s32("eid", lobby.get_int("id"))) + e = Node.void("e") root.add_child(e) - e.add_child(Node.s32('eid', lobby.get_int('id'))) - e.add_child(Node.u16('mid', lobby.get_int('mid'))) - e.add_child(Node.u8('ng', lobby.get_int('ng'))) - e.add_child(Node.s32('uid', profile.extid)) - e.add_child(Node.string('pn', profile.get_str('name'))) - e.add_child(Node.s32('exp', profile.get_int('exp'))) - e.add_child(Node.u8('mg', profile.get_int('mg'))) - e.add_child(Node.s32('tid', lobby.get_int('tid'))) - e.add_child(Node.string('tn', lobby.get_str('tn'))) - e.add_child(Node.string('lid', lobby.get_str('lid'))) - e.add_child(Node.string('sn', lobby.get_str('sn'))) - e.add_child(Node.u8('pref', lobby.get_int('pref'))) - e.add_child(Node.u8_array('ga', lobby.get_int_array('ga', 4))) - e.add_child(Node.u16('gp', lobby.get_int('gp'))) - e.add_child(Node.u8_array('la', lobby.get_int_array('la', 4))) + e.add_child(Node.s32("eid", lobby.get_int("id"))) + e.add_child(Node.u16("mid", lobby.get_int("mid"))) + e.add_child(Node.u8("ng", lobby.get_int("ng"))) + e.add_child(Node.s32("uid", profile.extid)) + e.add_child(Node.string("pn", profile.get_str("name"))) + e.add_child(Node.s32("exp", profile.get_int("exp"))) + e.add_child(Node.u8("mg", profile.get_int("mg"))) + e.add_child(Node.s32("tid", lobby.get_int("tid"))) + e.add_child(Node.string("tn", lobby.get_str("tn"))) + e.add_child(Node.string("lid", lobby.get_str("lid"))) + e.add_child(Node.string("sn", lobby.get_str("sn"))) + e.add_child(Node.u8("pref", lobby.get_int("pref"))) + e.add_child(Node.u8_array("ga", lobby.get_int_array("ga", 4))) + e.add_child(Node.u16("gp", lobby.get_int("gp"))) + e.add_child(Node.u8_array("la", lobby.get_int_array("la", 4))) return root def handle_lobby_read_request(self, request: Node) -> Node: - root = Node.void('lobby') + root = Node.void("lobby") # Look up all lobbies matching the criteria specified - mg = request.child_value('m_grade') # noqa: F841 - extid = request.child_value('uid') - limit = request.child_value('max') + mg = request.child_value("m_grade") # noqa: F841 + extid = request.child_value("uid") + limit = request.child_value("max") userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: lobbies = self.data.local.lobby.get_all_lobbies(self.game, self.version) @@ -219,47 +223,44 @@ class ReflecBeat(ReflecBeatBase): # No profile info, don't return this lobby continue - e = Node.void('e') + e = Node.void("e") root.add_child(e) - e.add_child(Node.s32('eid', lobby.get_int('id'))) - e.add_child(Node.u16('mid', lobby.get_int('mid'))) - e.add_child(Node.u8('ng', lobby.get_int('ng'))) - e.add_child(Node.s32('uid', profile.extid)) - e.add_child(Node.string('pn', profile.get_str('name'))) - e.add_child(Node.s32('exp', profile.get_int('exp'))) - e.add_child(Node.u8('mg', profile.get_int('mg'))) - e.add_child(Node.s32('tid', lobby.get_int('tid'))) - e.add_child(Node.string('tn', lobby.get_str('tn'))) - e.add_child(Node.string('lid', lobby.get_str('lid'))) - e.add_child(Node.string('sn', lobby.get_str('sn'))) - e.add_child(Node.u8('pref', lobby.get_int('pref'))) - e.add_child(Node.u8_array('ga', lobby.get_int_array('ga', 4))) - e.add_child(Node.u16('gp', lobby.get_int('gp'))) - e.add_child(Node.u8_array('la', lobby.get_int_array('la', 4))) + e.add_child(Node.s32("eid", lobby.get_int("id"))) + e.add_child(Node.u16("mid", lobby.get_int("mid"))) + e.add_child(Node.u8("ng", lobby.get_int("ng"))) + e.add_child(Node.s32("uid", profile.extid)) + e.add_child(Node.string("pn", profile.get_str("name"))) + e.add_child(Node.s32("exp", profile.get_int("exp"))) + e.add_child(Node.u8("mg", profile.get_int("mg"))) + e.add_child(Node.s32("tid", lobby.get_int("tid"))) + e.add_child(Node.string("tn", lobby.get_str("tn"))) + e.add_child(Node.string("lid", lobby.get_str("lid"))) + e.add_child(Node.string("sn", lobby.get_str("sn"))) + e.add_child(Node.u8("pref", lobby.get_int("pref"))) + e.add_child(Node.u8_array("ga", lobby.get_int_array("ga", 4))) + e.add_child(Node.u16("gp", lobby.get_int("gp"))) + e.add_child(Node.u8_array("la", lobby.get_int_array("la", 4))) limit = limit - 1 return root def handle_lobby_delete_request(self, request: Node) -> Node: - eid = request.child_value('eid') + eid = request.child_value("eid") self.data.local.lobby.destroy_lobby(eid) - return Node.void('lobby') + return Node.void("lobby") def handle_player_start_request(self, request: Node) -> Node: # Add a dummy entry into the lobby setup so we can clean up on end play - refid = request.child_value('rid') + refid = request.child_value("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: self.data.local.lobby.put_play_session_info( - self.game, - self.version, - userid, - {} + self.game, self.version, userid, {} ) - root = Node.void('player') - root.add_child(Node.bool('is_suc', True)) + root = Node.void("player") + root.add_child(Node.bool("is_suc", True)) # Add event info self.__add_event_info(root) @@ -267,11 +268,11 @@ class ReflecBeat(ReflecBeatBase): return root def handle_player_delete_request(self, request: Node) -> Node: - return Node.void('player') + return Node.void("player") def handle_player_end_request(self, request: Node) -> Node: # Destroy play session based on info from the request - refid = request.child_value('rid') + refid = request.child_value("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: # Kill any lingering lobbies by this user @@ -281,159 +282,182 @@ class ReflecBeat(ReflecBeatBase): userid, ) if lobby is not None: - self.data.local.lobby.destroy_lobby(lobby.get_int('id')) - self.data.local.lobby.destroy_play_session_info(self.game, self.version, userid) + self.data.local.lobby.destroy_lobby(lobby.get_int("id")) + self.data.local.lobby.destroy_play_session_info( + self.game, self.version, userid + ) - return Node.void('player') + return Node.void("player") def handle_player_read_request(self, request: Node) -> Node: - refid = request.child_value('rid') + refid = request.child_value("rid") profile = self.get_profile_by_refid(refid) if profile: return profile - return Node.void('player') + return Node.void("player") def handle_player_write_request(self, request: Node) -> Node: - refid = request.child_value('rid') + refid = request.child_value("rid") profile = self.put_profile_by_refid(refid, request) - root = Node.void('player') + root = Node.void("player") if profile is None: - root.add_child(Node.s32('uid', 0)) + root.add_child(Node.s32("uid", 0)) else: - root.add_child(Node.s32('uid', profile.extid)) - root.add_child(Node.s32('time', Time.now())) + root.add_child(Node.s32("uid", profile.extid)) + root.add_child(Node.s32("time", Time.now())) return root def format_profile(self, userid: UserID, profile: Profile) -> Node: statistics = self.get_play_statistics(userid) game_config = self.get_game_config() - achievements = self.data.local.user.get_achievements(self.game, self.version, userid) + achievements = self.data.local.user.get_achievements( + self.game, self.version, userid + ) scores = self.data.remote.music.get_scores(self.game, self.version, userid) - root = Node.void('player') - pdata = Node.void('pdata') + root = Node.void("player") + pdata = Node.void("pdata") root.add_child(pdata) - base = Node.void('base') + base = Node.void("base") pdata.add_child(base) - base.add_child(Node.s32('uid', profile.extid)) - base.add_child(Node.string('name', profile.get_str('name'))) - base.add_child(Node.s16('lv', profile.get_int('lvl'))) - base.add_child(Node.s32('exp', profile.get_int('exp'))) - base.add_child(Node.s16('mg', profile.get_int('mg'))) - base.add_child(Node.s16('ap', profile.get_int('ap'))) - base.add_child(Node.s32('flag', profile.get_int('flag'))) + base.add_child(Node.s32("uid", profile.extid)) + base.add_child(Node.string("name", profile.get_str("name"))) + base.add_child(Node.s16("lv", profile.get_int("lvl"))) + base.add_child(Node.s32("exp", profile.get_int("exp"))) + base.add_child(Node.s16("mg", profile.get_int("mg"))) + base.add_child(Node.s16("ap", profile.get_int("ap"))) + base.add_child(Node.s32("flag", profile.get_int("flag"))) - con = Node.void('con') + con = Node.void("con") pdata.add_child(con) - con.add_child(Node.s32('day', statistics.today_plays)) - con.add_child(Node.s32('cnt', statistics.total_plays)) - con.add_child(Node.s32('last', statistics.last_play_timestamp)) - con.add_child(Node.s32('now', Time.now())) + con.add_child(Node.s32("day", statistics.today_plays)) + con.add_child(Node.s32("cnt", statistics.total_plays)) + con.add_child(Node.s32("last", statistics.last_play_timestamp)) + con.add_child(Node.s32("now", Time.now())) - team = Node.void('team') + team = Node.void("team") pdata.add_child(team) - team.add_child(Node.s32('id', -1)) - team.add_child(Node.string('name', '')) + team.add_child(Node.s32("id", -1)) + team.add_child(Node.string("name", "")) - custom = Node.void('custom') - customdict = profile.get_dict('custom') + custom = Node.void("custom") + customdict = profile.get_dict("custom") pdata.add_child(custom) - custom.add_child(Node.u8('bgm_m', customdict.get_int('bgm_m'))) - custom.add_child(Node.u8('st_f', customdict.get_int('st_f'))) - custom.add_child(Node.u8('st_bg', customdict.get_int('st_bg'))) - custom.add_child(Node.u8('st_bg_b', customdict.get_int('st_bg_b'))) - custom.add_child(Node.u8('eff_e', customdict.get_int('eff_e'))) - custom.add_child(Node.u8('se_s', customdict.get_int('se_s'))) - custom.add_child(Node.u8('se_s_v', customdict.get_int('se_s_v'))) + custom.add_child(Node.u8("bgm_m", customdict.get_int("bgm_m"))) + custom.add_child(Node.u8("st_f", customdict.get_int("st_f"))) + custom.add_child(Node.u8("st_bg", customdict.get_int("st_bg"))) + custom.add_child(Node.u8("st_bg_b", customdict.get_int("st_bg_b"))) + custom.add_child(Node.u8("eff_e", customdict.get_int("eff_e"))) + custom.add_child(Node.u8("se_s", customdict.get_int("se_s"))) + custom.add_child(Node.u8("se_s_v", customdict.get_int("se_s_v"))) - released = Node.void('released') + released = Node.void("released") pdata.add_child(released) for item in achievements: - if item.type[:5] != 'item_': + if item.type[:5] != "item_": continue itemtype = int(item.type[5:]) - if game_config.get_bool('force_unlock_songs') and itemtype == 0: + if game_config.get_bool("force_unlock_songs") and itemtype == 0: # Don't echo unlocks when we're force unlocking, we'll do it later continue - info = Node.void('info') + info = Node.void("info") released.add_child(info) - info.add_child(Node.u8('type', itemtype)) - info.add_child(Node.u16('id', item.id)) + info.add_child(Node.u8("type", itemtype)) + info.add_child(Node.u16("id", item.id)) - if game_config.get_bool('force_unlock_songs'): - songs = {song.id for song in self.data.local.music.get_all_songs(self.game, self.version)} + if game_config.get_bool("force_unlock_songs"): + songs = { + song.id + for song in self.data.local.music.get_all_songs(self.game, self.version) + } for songid in songs: - info = Node.void('info') + info = Node.void("info") released.add_child(info) - info.add_child(Node.u8('type', 0)) - info.add_child(Node.u16('id', songid)) + info.add_child(Node.u8("type", 0)) + info.add_child(Node.u16("id", songid)) # Scores - record = Node.void('record') + record = Node.void("record") pdata.add_child(record) for score in scores: - rec = Node.void('rec') + rec = Node.void("rec") record.add_child(rec) - rec.add_child(Node.u16('mid', score.id)) - rec.add_child(Node.u8('ng', score.chart)) - rec.add_child(Node.s32('win', score.data.get_dict('stats').get_int('win'))) - rec.add_child(Node.s32('lose', score.data.get_dict('stats').get_int('lose'))) - rec.add_child(Node.s32('draw', score.data.get_dict('stats').get_int('draw'))) - rec.add_child(Node.u8('ct', self.__db_to_game_clear_type(score.data.get_int('clear_type'), score.data.get_int('combo_type')))) - rec.add_child(Node.s16('ar', int(score.data.get_int('achievement_rate') / 10))) - rec.add_child(Node.s16('bs', score.points)) - rec.add_child(Node.s16('mc', score.data.get_int('combo'))) - rec.add_child(Node.s16('bmc', score.data.get_int('miss_count'))) + rec.add_child(Node.u16("mid", score.id)) + rec.add_child(Node.u8("ng", score.chart)) + rec.add_child(Node.s32("win", score.data.get_dict("stats").get_int("win"))) + rec.add_child( + Node.s32("lose", score.data.get_dict("stats").get_int("lose")) + ) + rec.add_child( + Node.s32("draw", score.data.get_dict("stats").get_int("draw")) + ) + rec.add_child( + Node.u8( + "ct", + self.__db_to_game_clear_type( + score.data.get_int("clear_type"), + score.data.get_int("combo_type"), + ), + ) + ) + rec.add_child( + Node.s16("ar", int(score.data.get_int("achievement_rate") / 10)) + ) + rec.add_child(Node.s16("bs", score.points)) + rec.add_child(Node.s16("mc", score.data.get_int("combo"))) + rec.add_child(Node.s16("bmc", score.data.get_int("miss_count"))) # In original ReflecBeat, the entire battle log was returned for each battle. # We don't support storing all of that info, so don't return anything here. - blog = Node.void('blog') + blog = Node.void("blog") pdata.add_child(blog) # Comment (seems unused?) - pdata.add_child(Node.string('cmnt', '')) + pdata.add_child(Node.string("cmnt", "")) return root - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: game_config = self.get_game_config() newprofile = oldprofile.clone() - newprofile.replace_int('lid', ID.parse_machine_id(request.child_value('lid'))) - newprofile.replace_str('name', request.child_value('pdata/base/name')) - newprofile.replace_int('lvl', request.child_value('pdata/base/lv')) - newprofile.replace_int('exp', request.child_value('pdata/base/exp')) - newprofile.replace_int('mg', request.child_value('pdata/base/mg')) - newprofile.replace_int('ap', request.child_value('pdata/base/ap')) - newprofile.replace_int('flag', request.child_value('pdata/base/flag')) + newprofile.replace_int("lid", ID.parse_machine_id(request.child_value("lid"))) + newprofile.replace_str("name", request.child_value("pdata/base/name")) + newprofile.replace_int("lvl", request.child_value("pdata/base/lv")) + newprofile.replace_int("exp", request.child_value("pdata/base/exp")) + newprofile.replace_int("mg", request.child_value("pdata/base/mg")) + newprofile.replace_int("ap", request.child_value("pdata/base/ap")) + newprofile.replace_int("flag", request.child_value("pdata/base/flag")) - customdict = newprofile.get_dict('custom') - custom = request.child('pdata/custom') + customdict = newprofile.get_dict("custom") + custom = request.child("pdata/custom") if custom: - customdict.replace_int('bgm_m', custom.child_value('bgm_m')) - customdict.replace_int('st_f', custom.child_value('st_f')) - customdict.replace_int('st_bg', custom.child_value('st_bg')) - customdict.replace_int('st_bg_b', custom.child_value('st_bg_b')) - customdict.replace_int('eff_e', custom.child_value('eff_e')) - customdict.replace_int('se_s', custom.child_value('se_s')) - customdict.replace_int('se_s_v', custom.child_value('se_s_v')) - newprofile.replace_dict('custom', customdict) + customdict.replace_int("bgm_m", custom.child_value("bgm_m")) + customdict.replace_int("st_f", custom.child_value("st_f")) + customdict.replace_int("st_bg", custom.child_value("st_bg")) + customdict.replace_int("st_bg_b", custom.child_value("st_bg_b")) + customdict.replace_int("eff_e", custom.child_value("eff_e")) + customdict.replace_int("se_s", custom.child_value("se_s")) + customdict.replace_int("se_s_v", custom.child_value("se_s_v")) + newprofile.replace_dict("custom", customdict) # Music unlocks and other stuff - released = request.child('pdata/released') + released = request.child("pdata/released") if released: for child in released.children: - if child.name != 'info': + if child.name != "info": continue - item_id = child.child_value('id') - item_type = child.child_value('type') - if game_config.get_bool('force_unlock_songs') and item_type == 0: + item_id = child.child_value("id") + item_type = child.child_value("type") + if game_config.get_bool("force_unlock_songs") and item_type == 0: # Don't save unlocks when we're force unlocking continue @@ -442,7 +466,7 @@ class ReflecBeat(ReflecBeatBase): self.version, userid, item_id, - f'item_{item_type}', + f"item_{item_type}", {}, ) @@ -453,46 +477,48 @@ class ReflecBeat(ReflecBeatBase): # the previous try. So, we must also look at the battle log for the actual play scores, # and combine the data if we can. savedrecords: Dict[int, Dict[int, Dict[str, int]]] = {} - songplays = request.child('pdata/record') + songplays = request.child("pdata/record") if songplays: for child in songplays.children: - if child.name != 'rec': + if child.name != "rec": continue - songid = child.child_value('mid') - chart = child.child_value('ng') + songid = child.child_value("mid") + chart = child.child_value("ng") # These don't get sent with the battle logs, so we try to construct # the values here. if songid not in savedrecords: savedrecords[songid] = {} savedrecords[songid][chart] = { - 'achievement_rate': child.child_value('ar') * 10, - 'points': child.child_value('bs'), - 'combo': child.child_value('mc'), - 'miss_count': child.child_value('bmc'), - 'win': child.child_value('win'), - 'lose': child.child_value('lose'), - 'draw': child.child_value('draw'), + "achievement_rate": child.child_value("ar") * 10, + "points": child.child_value("bs"), + "combo": child.child_value("mc"), + "miss_count": child.child_value("bmc"), + "win": child.child_value("win"), + "lose": child.child_value("lose"), + "draw": child.child_value("draw"), } # Now, see the actual battles that were played. If we can, unify the data with a record. # We only do that when the record achievement rate and score matches the battle achievement # rate and score, so we know for a fact that that record was generated by this battle. - battlelogs = request.child('pdata/blog') + battlelogs = request.child("pdata/blog") if battlelogs: for child in battlelogs.children: - if child.name != 'log': + if child.name != "log": continue - songid = child.child_value('mid') - chart = child.child_value('ng') + songid = child.child_value("mid") + chart = child.child_value("ng") - clear_type = child.child_value('myself/ct') - achievement_rate = child.child_value('myself/ar') * 10 - points = child.child_value('myself/s') + clear_type = child.child_value("myself/ct") + achievement_rate = child.child_value("myself/ar") * 10 + points = child.child_value("myself/s") - clear_type, combo_type = self.__game_to_db_clear_type(clear_type, achievement_rate) + clear_type, combo_type = self.__game_to_db_clear_type( + clear_type, achievement_rate + ) combo = None miss_count = -1 @@ -503,17 +529,17 @@ class ReflecBeat(ReflecBeatBase): data = savedrecords[songid][chart] if ( - data['achievement_rate'] == achievement_rate and - data['points'] == points + data["achievement_rate"] == achievement_rate + and data["points"] == points ): # This is the same record! Use the stats from it to update our # internal representation. - combo = data['combo'] - miss_count = data['miss_count'] + combo = data["combo"] + miss_count = data["miss_count"] stats = { - 'win': data['win'], - 'lose': data['lose'], - 'draw': data['draw'], + "win": data["win"], + "lose": data["lose"], + "draw": data["draw"], } self.update_score( diff --git a/bemani/backend/reflec/volzza.py b/bemani/backend/reflec/volzza.py index d377277..a79de8e 100644 --- a/bemani/backend/reflec/volzza.py +++ b/bemani/backend/reflec/volzza.py @@ -23,19 +23,19 @@ class ReflecBeatVolzza(ReflecBeatVolzzaBase): Return all of our front-end modifiably settings. """ return { - 'bools': [ + "bools": [ { - 'name': 'Force Song Unlock', - 'tip': 'Force unlock all songs.', - 'category': 'game_config', - 'setting': 'force_unlock_songs', + "name": "Force Song Unlock", + "tip": "Force unlock all songs.", + "category": "game_config", + "setting": "force_unlock_songs", }, ], - 'ints': [], + "ints": [], } def _add_event_info(self, root: Node) -> None: - event_ctrl = Node.void('event_ctrl') + event_ctrl = Node.void("event_ctrl") root.add_child(event_ctrl) # Contains zero or more nodes like: # @@ -47,7 +47,7 @@ class ReflecBeatVolzza(ReflecBeatVolzzaBase): # any # - item_lock_ctrl = Node.void('item_lock_ctrl') + item_lock_ctrl = Node.void("item_lock_ctrl") root.add_child(item_lock_ctrl) # Contains zero or more nodes like: # @@ -57,92 +57,111 @@ class ReflecBeatVolzza(ReflecBeatVolzzaBase): # def handle_player_rb5_player_read_score_request(self, request: Node) -> Node: - refid = request.child_value('rid') + refid = request.child_value("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is None: scores: List[Score] = [] else: scores = self.data.remote.music.get_scores(self.game, self.version, userid) - root = Node.void('player') - pdata = Node.void('pdata') + root = Node.void("player") + pdata = Node.void("pdata") root.add_child(pdata) - record = Node.void('record') + record = Node.void("record") pdata.add_child(record) for score in scores: - rec = Node.void('rec') + rec = Node.void("rec") record.add_child(rec) - rec.add_child(Node.s16('mid', score.id)) - rec.add_child(Node.s8('ntgrd', score.chart)) - rec.add_child(Node.s32('pc', score.plays)) - rec.add_child(Node.s8('ct', self._db_to_game_clear_type(score.data.get_int('clear_type')))) - rec.add_child(Node.s16('ar', score.data.get_int('achievement_rate'))) - rec.add_child(Node.s16('scr', score.points)) - rec.add_child(Node.s16('ms', score.data.get_int('miss_count'))) - rec.add_child(Node.s16( - 'param', - self._db_to_game_combo_type(score.data.get_int('combo_type')) + score.data.get_int('param'), - )) - rec.add_child(Node.s32('bscrt', score.timestamp)) - rec.add_child(Node.s32('bart', score.data.get_int('best_achievement_rate_time'))) - rec.add_child(Node.s32('bctt', score.data.get_int('best_clear_type_time'))) - rec.add_child(Node.s32('bmst', score.data.get_int('best_miss_count_time'))) - rec.add_child(Node.s32('time', score.data.get_int('last_played_time'))) - rec.add_child(Node.s32('k_flag', score.data.get_int('kflag'))) + rec.add_child(Node.s16("mid", score.id)) + rec.add_child(Node.s8("ntgrd", score.chart)) + rec.add_child(Node.s32("pc", score.plays)) + rec.add_child( + Node.s8( + "ct", self._db_to_game_clear_type(score.data.get_int("clear_type")) + ) + ) + rec.add_child(Node.s16("ar", score.data.get_int("achievement_rate"))) + rec.add_child(Node.s16("scr", score.points)) + rec.add_child(Node.s16("ms", score.data.get_int("miss_count"))) + rec.add_child( + Node.s16( + "param", + self._db_to_game_combo_type(score.data.get_int("combo_type")) + + score.data.get_int("param"), + ) + ) + rec.add_child(Node.s32("bscrt", score.timestamp)) + rec.add_child( + Node.s32("bart", score.data.get_int("best_achievement_rate_time")) + ) + rec.add_child(Node.s32("bctt", score.data.get_int("best_clear_type_time"))) + rec.add_child(Node.s32("bmst", score.data.get_int("best_miss_count_time"))) + rec.add_child(Node.s32("time", score.data.get_int("last_played_time"))) + rec.add_child(Node.s32("k_flag", score.data.get_int("kflag"))) return root def handle_player_rb5_player_read_rival_score_request(self, request: Node) -> Node: - extid = request.child_value('uid') - songid = request.child_value('music_id') - chart = request.child_value('note_grade') + extid = request.child_value("uid") + songid = request.child_value("music_id") + chart = request.child_value("note_grade") userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is None: score = None profile = None else: - score = self.data.remote.music.get_score(self.game, self.version, userid, songid, chart) + score = self.data.remote.music.get_score( + self.game, self.version, userid, songid, chart + ) profile = self.get_any_profile(userid) - root = Node.void('player') + root = Node.void("player") if score is not None and profile is not None: - player_select_score = Node.void('player_select_score') + player_select_score = Node.void("player_select_score") root.add_child(player_select_score) - player_select_score.add_child(Node.s32('user_id', extid)) - player_select_score.add_child(Node.string('name', profile.get_str('name'))) - player_select_score.add_child(Node.s32('m_score', score.points)) - player_select_score.add_child(Node.s32('m_scoreTime', score.timestamp)) - player_select_score.add_child(Node.s16('m_iconID', profile.get_dict('config').get_int('icon_id'))) + player_select_score.add_child(Node.s32("user_id", extid)) + player_select_score.add_child(Node.string("name", profile.get_str("name"))) + player_select_score.add_child(Node.s32("m_score", score.points)) + player_select_score.add_child(Node.s32("m_scoreTime", score.timestamp)) + player_select_score.add_child( + Node.s16("m_iconID", profile.get_dict("config").get_int("icon_id")) + ) return root - def handle_player_rb5_player_read_rival_ranking_data_request(self, request: Node) -> Node: - extid = request.child_value('uid') + def handle_player_rb5_player_read_rival_ranking_data_request( + self, request: Node + ) -> Node: + extid = request.child_value("uid") userid = self.data.remote.user.from_extid(self.game, self.version, extid) - root = Node.void('player') - rival_data = Node.void('rival_data') + root = Node.void("player") + rival_data = Node.void("rival_data") root.add_child(rival_data) if userid is not None: links = self.data.local.user.get_links(self.game, self.version, userid) for link in links: - if link.type != 'rival': + if link.type != "rival": continue rprofile = self.get_profile(link.other_userid) if rprofile is None: continue - rl = Node.void('rl') + rl = Node.void("rl") rival_data.add_child(rl) - rl.add_child(Node.s32('uid', rprofile.extid)) - rl.add_child(Node.string('nm', rprofile.get_str('name'))) - rl.add_child(Node.s16('ic', rprofile.get_dict('config').get_int('icon_id'))) + rl.add_child(Node.s32("uid", rprofile.extid)) + rl.add_child(Node.string("nm", rprofile.get_str("name"))) + rl.add_child( + Node.s16("ic", rprofile.get_dict("config").get_int("icon_id")) + ) - scores = self.data.remote.music.get_scores(self.game, self.version, link.other_userid) + scores = self.data.remote.music.get_scores( + self.game, self.version, link.other_userid + ) scores_by_musicid: Dict[int, List[Score]] = {} for score in scores: if score.id not in scores_by_musicid: @@ -155,17 +174,16 @@ class ReflecBeatVolzza(ReflecBeatVolzzaBase): for score in scores ] timestamps = [ - score.timestamp if score is not None else 0 - for score in scores + score.timestamp if score is not None else 0 for score in scores ] - sl = Node.void('sl') + sl = Node.void("sl") rl.add_child(sl) - sl.add_child(Node.s16('mid', mid)) + sl.add_child(Node.s16("mid", mid)) # Score, but shifted left 32 bits for no reason - sl.add_child(Node.u64_array('m', points)) + sl.add_child(Node.u64_array("m", points)) # Timestamp of the clear - sl.add_child(Node.u64_array('t', timestamps)) + sl.add_child(Node.u64_array("t", timestamps)) return root @@ -174,8 +192,8 @@ class ReflecBeatVolzza(ReflecBeatVolzzaBase): # [total score, basic chart score, medium chart score, hard chart score, # special chart score]. It also returns the previous rank, but this is # not used in-game as far as I can tell. - current_scores = request.child_value('sc') - current_minigame_score = request.child_value('mg_sc') + current_scores = request.child_value("sc") + current_minigame_score = request.child_value("mg_sc") # First, grab all scores on the network for this version. all_scores = self.data.remote.music.get_all_scores(self.game, self.version) @@ -187,15 +205,20 @@ class ReflecBeatVolzza(ReflecBeatVolzzaBase): # scores where the user at least cleared the song. scores_by_user = { userid: [ - score for (uid, score) in all_scores - if uid == userid and score.data.get_int('clear_type') >= self.CLEAR_TYPE_CLEARED] + score + for (uid, score) in all_scores + if uid == userid + and score.data.get_int("clear_type") >= self.CLEAR_TYPE_CLEARED + ] for userid in all_users } # Now grab all user profiles for this game all_profiles = { - profile[0]: profile[1] for profile in - self.data.remote.user.get_all_profiles(self.game, self.version) + profile[0]: profile[1] + for profile in self.data.remote.user.get_all_profiles( + self.game, self.version + ) } # Now, sum up the scores into the five categories that the game expects. @@ -208,34 +231,60 @@ class ReflecBeatVolzza(ReflecBeatVolzzaBase): ) basic_scores = sorted( [ - sum([score.points for score in scores if score.chart == self.CHART_TYPE_BASIC]) + sum( + [ + score.points + for score in scores + if score.chart == self.CHART_TYPE_BASIC + ] + ) for userid, scores in scores_by_user.items() ], reverse=True, ) medium_scores = sorted( [ - sum([score.points for score in scores if score.chart == self.CHART_TYPE_MEDIUM]) + sum( + [ + score.points + for score in scores + if score.chart == self.CHART_TYPE_MEDIUM + ] + ) for userid, scores in scores_by_user.items() ], reverse=True, ) hard_scores = sorted( [ - sum([score.points for score in scores if score.chart == self.CHART_TYPE_HARD]) + sum( + [ + score.points + for score in scores + if score.chart == self.CHART_TYPE_HARD + ] + ) for userid, scores in scores_by_user.items() ], ) special_scores = sorted( [ - sum([score.points for score in scores if score.chart == self.CHART_TYPE_SPECIAL]) + sum( + [ + score.points + for score in scores + if score.chart == self.CHART_TYPE_SPECIAL + ] + ) for userid, scores in scores_by_user.items() ], reverse=True, ) minigame_scores = sorted( [ - all_profiles.get(userid, Profile(self.game, self.version, "", 0)).get_int('mgsc') + all_profiles.get( + userid, Profile(self.game, self.version, "", 0) + ).get_int("mgsc") for userid in all_users ], reverse=True, @@ -273,40 +322,42 @@ class ReflecBeatVolzza(ReflecBeatVolzzaBase): minigame_rank = user_place[-1] user_place = user_place[:-1] - root = Node.void('player') + root = Node.void("player") # Populate current ranking. - tbs = Node.void('tbs') + tbs = Node.void("tbs") root.add_child(tbs) - tbs.add_child(Node.s32_array('new_rank', user_place)) - tbs.add_child(Node.s32_array('old_rank', [-1, -1, -1, -1, -1])) + tbs.add_child(Node.s32_array("new_rank", user_place)) + tbs.add_child(Node.s32_array("old_rank", [-1, -1, -1, -1, -1])) # Populate current minigame ranking (LOL). - mng = Node.void('mng') + mng = Node.void("mng") root.add_child(mng) - mng.add_child(Node.s32('new_rank', minigame_rank)) - mng.add_child(Node.s32('old_rank', -1)) + mng.add_child(Node.s32("new_rank", minigame_rank)) + mng.add_child(Node.s32("old_rank", -1)) return root def handle_player_rb5_player_write_request(self, request: Node) -> Node: - refid = request.child_value('pdata/account/rid') + refid = request.child_value("pdata/account/rid") profile = self.put_profile_by_refid(refid, request) - root = Node.void('player') + root = Node.void("player") if profile is None: - root.add_child(Node.s32('uid', 0)) + root.add_child(Node.s32("uid", 0)) else: - root.add_child(Node.s32('uid', profile.extid)) + root.add_child(Node.s32("uid", profile.extid)) return root def format_profile(self, userid: UserID, profile: Profile) -> Node: statistics = self.get_play_statistics(userid) game_config = self.get_game_config() - achievements = self.data.local.user.get_achievements(self.game, self.version, userid) + achievements = self.data.local.user.get_achievements( + self.game, self.version, userid + ) links = self.data.local.user.get_links(self.game, self.version, userid) - root = Node.void('player') - pdata = Node.void('pdata') + root = Node.void("player") + pdata = Node.void("pdata") root.add_child(pdata) # Previous account info @@ -317,306 +368,410 @@ class ReflecBeatVolzza(ReflecBeatVolzzaBase): succeeded = False # Account info - account = Node.void('account') + account = Node.void("account") pdata.add_child(account) - account.add_child(Node.s32('usrid', profile.extid)) - account.add_child(Node.s32('tpc', statistics.total_plays)) - account.add_child(Node.s32('dpc', statistics.today_plays)) - account.add_child(Node.s32('crd', 1)) - account.add_child(Node.s32('brd', 1)) - account.add_child(Node.s32('tdc', statistics.total_days)) - account.add_child(Node.s32('intrvld', 0)) - account.add_child(Node.s16('ver', 0)) - account.add_child(Node.u64('pst', 0)) - account.add_child(Node.u64('st', Time.now() * 1000)) - account.add_child(Node.bool('succeed', succeeded)) - account.add_child(Node.s32('opc', 0)) - account.add_child(Node.s32('lpc', 0)) - account.add_child(Node.s32('cpc', 0)) + account.add_child(Node.s32("usrid", profile.extid)) + account.add_child(Node.s32("tpc", statistics.total_plays)) + account.add_child(Node.s32("dpc", statistics.today_plays)) + account.add_child(Node.s32("crd", 1)) + account.add_child(Node.s32("brd", 1)) + account.add_child(Node.s32("tdc", statistics.total_days)) + account.add_child(Node.s32("intrvld", 0)) + account.add_child(Node.s16("ver", 0)) + account.add_child(Node.u64("pst", 0)) + account.add_child(Node.u64("st", Time.now() * 1000)) + account.add_child(Node.bool("succeed", succeeded)) + account.add_child(Node.s32("opc", 0)) + account.add_child(Node.s32("lpc", 0)) + account.add_child(Node.s32("cpc", 0)) # Base profile info - base = Node.void('base') + base = Node.void("base") pdata.add_child(base) - base.add_child(Node.string('name', profile.get_str('name'))) - base.add_child(Node.s32('mg', profile.get_int('mg'))) - base.add_child(Node.s32('ap', profile.get_int('ap'))) - base.add_child(Node.string('cmnt', '')) - base.add_child(Node.s32('uattr', profile.get_int('uattr'))) - base.add_child(Node.s32('money', profile.get_int('money'))) - base.add_child(Node.s32('tbs', -1)) - base.add_child(Node.s32_array('tbgs', [-1, -1, -1, -1])) - base.add_child(Node.s16_array('mlog', [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1])) - base.add_child(Node.s32('class', profile.get_int('class'))) - base.add_child(Node.s32('class_ar', profile.get_int('class_ar'))) + base.add_child(Node.string("name", profile.get_str("name"))) + base.add_child(Node.s32("mg", profile.get_int("mg"))) + base.add_child(Node.s32("ap", profile.get_int("ap"))) + base.add_child(Node.string("cmnt", "")) + base.add_child(Node.s32("uattr", profile.get_int("uattr"))) + base.add_child(Node.s32("money", profile.get_int("money"))) + base.add_child(Node.s32("tbs", -1)) + base.add_child(Node.s32_array("tbgs", [-1, -1, -1, -1])) + base.add_child( + Node.s16_array( + "mlog", + [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], + ) + ) + base.add_child(Node.s32("class", profile.get_int("class"))) + base.add_child(Node.s32("class_ar", profile.get_int("class_ar"))) # Rivals - rival = Node.void('rival') + rival = Node.void("rival") pdata.add_child(rival) slotid = 0 for link in links: - if link.type != 'rival': + if link.type != "rival": continue rprofile = self.get_profile(link.other_userid) if rprofile is None: continue - lobbyinfo = self.data.local.lobby.get_play_session_info(self.game, self.version, link.other_userid) + lobbyinfo = self.data.local.lobby.get_play_session_info( + self.game, self.version, link.other_userid + ) if lobbyinfo is None: lobbyinfo = ValidatedDict() - r = Node.void('r') + r = Node.void("r") rival.add_child(r) - r.add_child(Node.s32('slot_id', slotid)) - r.add_child(Node.s32('id', rprofile.extid)) - r.add_child(Node.string('name', rprofile.get_str('name'))) - r.add_child(Node.s32('icon', rprofile.get_dict('config').get_int('icon_id'))) - r.add_child(Node.s32('class', rprofile.get_int('class'))) - r.add_child(Node.s32('class_ar', rprofile.get_int('class_ar'))) - r.add_child(Node.bool('friend', True)) - r.add_child(Node.bool('target', False)) - r.add_child(Node.u32('time', lobbyinfo.get_int('time'))) - r.add_child(Node.u8_array('ga', lobbyinfo.get_int_array('ga', 4))) - r.add_child(Node.u16('gp', lobbyinfo.get_int('gp'))) - r.add_child(Node.u8_array('ipn', lobbyinfo.get_int_array('la', 4))) - r.add_child(Node.u8_array('pnid', lobbyinfo.get_int_array('pnid', 16))) + r.add_child(Node.s32("slot_id", slotid)) + r.add_child(Node.s32("id", rprofile.extid)) + r.add_child(Node.string("name", rprofile.get_str("name"))) + r.add_child( + Node.s32("icon", rprofile.get_dict("config").get_int("icon_id")) + ) + r.add_child(Node.s32("class", rprofile.get_int("class"))) + r.add_child(Node.s32("class_ar", rprofile.get_int("class_ar"))) + r.add_child(Node.bool("friend", True)) + r.add_child(Node.bool("target", False)) + r.add_child(Node.u32("time", lobbyinfo.get_int("time"))) + r.add_child(Node.u8_array("ga", lobbyinfo.get_int_array("ga", 4))) + r.add_child(Node.u16("gp", lobbyinfo.get_int("gp"))) + r.add_child(Node.u8_array("ipn", lobbyinfo.get_int_array("la", 4))) + r.add_child(Node.u8_array("pnid", lobbyinfo.get_int_array("pnid", 16))) slotid = slotid + 1 # Configuration - configdict = profile.get_dict('config') - config = Node.void('config') + configdict = profile.get_dict("config") + config = Node.void("config") pdata.add_child(config) - config.add_child(Node.u8('msel_bgm', configdict.get_int('msel_bgm'))) - config.add_child(Node.u8('narrowdown_type', configdict.get_int('narrowdown_type'))) - config.add_child(Node.s16('icon_id', configdict.get_int('icon_id'))) - config.add_child(Node.s16('byword_0', configdict.get_int('byword_0'))) - config.add_child(Node.s16('byword_1', configdict.get_int('byword_1'))) - config.add_child(Node.bool('is_auto_byword_0', configdict.get_bool('is_auto_byword_0'))) - config.add_child(Node.bool('is_auto_byword_1', configdict.get_bool('is_auto_byword_1'))) - config.add_child(Node.u8('mrec_type', configdict.get_int('mrec_type'))) - config.add_child(Node.u8('tab_sel', configdict.get_int('tab_sel'))) - config.add_child(Node.u8('card_disp', configdict.get_int('card_disp'))) - config.add_child(Node.u8('score_tab_disp', configdict.get_int('score_tab_disp'))) - config.add_child(Node.s16('last_music_id', configdict.get_int('last_music_id', -1))) - config.add_child(Node.u8('last_note_grade', configdict.get_int('last_note_grade'))) - config.add_child(Node.u8('sort_type', configdict.get_int('sort_type'))) - config.add_child(Node.u8('rival_panel_type', configdict.get_int('rival_panel_type'))) - config.add_child(Node.u64('random_entry_work', configdict.get_int('random_entry_work'))) - config.add_child(Node.u64('custom_folder_work', configdict.get_int('custom_folder_work'))) - config.add_child(Node.u8('folder_type', configdict.get_int('folder_type'))) - config.add_child(Node.u8('folder_lamp_type', configdict.get_int('folder_lamp_type'))) - config.add_child(Node.bool('is_tweet', configdict.get_bool('is_tweet'))) - config.add_child(Node.bool('is_link_twitter', configdict.get_bool('is_link_twitter'))) + config.add_child(Node.u8("msel_bgm", configdict.get_int("msel_bgm"))) + config.add_child( + Node.u8("narrowdown_type", configdict.get_int("narrowdown_type")) + ) + config.add_child(Node.s16("icon_id", configdict.get_int("icon_id"))) + config.add_child(Node.s16("byword_0", configdict.get_int("byword_0"))) + config.add_child(Node.s16("byword_1", configdict.get_int("byword_1"))) + config.add_child( + Node.bool("is_auto_byword_0", configdict.get_bool("is_auto_byword_0")) + ) + config.add_child( + Node.bool("is_auto_byword_1", configdict.get_bool("is_auto_byword_1")) + ) + config.add_child(Node.u8("mrec_type", configdict.get_int("mrec_type"))) + config.add_child(Node.u8("tab_sel", configdict.get_int("tab_sel"))) + config.add_child(Node.u8("card_disp", configdict.get_int("card_disp"))) + config.add_child( + Node.u8("score_tab_disp", configdict.get_int("score_tab_disp")) + ) + config.add_child( + Node.s16("last_music_id", configdict.get_int("last_music_id", -1)) + ) + config.add_child( + Node.u8("last_note_grade", configdict.get_int("last_note_grade")) + ) + config.add_child(Node.u8("sort_type", configdict.get_int("sort_type"))) + config.add_child( + Node.u8("rival_panel_type", configdict.get_int("rival_panel_type")) + ) + config.add_child( + Node.u64("random_entry_work", configdict.get_int("random_entry_work")) + ) + config.add_child( + Node.u64("custom_folder_work", configdict.get_int("custom_folder_work")) + ) + config.add_child(Node.u8("folder_type", configdict.get_int("folder_type"))) + config.add_child( + Node.u8("folder_lamp_type", configdict.get_int("folder_lamp_type")) + ) + config.add_child(Node.bool("is_tweet", configdict.get_bool("is_tweet"))) + config.add_child( + Node.bool("is_link_twitter", configdict.get_bool("is_link_twitter")) + ) # Customizations - customdict = profile.get_dict('custom') - custom = Node.void('custom') + customdict = profile.get_dict("custom") + custom = Node.void("custom") pdata.add_child(custom) - custom.add_child(Node.u8('st_shot', customdict.get_int('st_shot'))) - custom.add_child(Node.u8('st_frame', customdict.get_int('st_frame'))) - custom.add_child(Node.u8('st_expl', customdict.get_int('st_expl'))) - custom.add_child(Node.u8('st_bg', customdict.get_int('st_bg'))) - custom.add_child(Node.u8('st_shot_vol', customdict.get_int('st_shot_vol'))) - custom.add_child(Node.u8('st_bg_bri', customdict.get_int('st_bg_bri'))) - custom.add_child(Node.u8('st_obj_size', customdict.get_int('st_obj_size'))) - custom.add_child(Node.u8('st_jr_gauge', customdict.get_int('st_jr_gauge'))) - custom.add_child(Node.u8('st_clr_gauge', customdict.get_int('st_clr_gauge'))) - custom.add_child(Node.u8('st_jdg_disp', customdict.get_int('st_jdg_disp'))) - custom.add_child(Node.u8('st_rnd', customdict.get_int('st_rnd'))) - custom.add_child(Node.u8('st_hazard', customdict.get_int('st_hazard'))) - custom.add_child(Node.u8('st_clr_cond', customdict.get_int('st_clr_cond'))) - custom.add_child(Node.u8('same_time_note_disp', customdict.get_int('same_time_note_disp'))) - custom.add_child(Node.u8('st_gr_gauge_type', customdict.get_int('st_gr_gauge_type'))) - custom.add_child(Node.s16('voice_message_set', customdict.get_int('voice_message_set', -1))) - custom.add_child(Node.u8('voice_message_volume', customdict.get_int('voice_message_volume'))) + custom.add_child(Node.u8("st_shot", customdict.get_int("st_shot"))) + custom.add_child(Node.u8("st_frame", customdict.get_int("st_frame"))) + custom.add_child(Node.u8("st_expl", customdict.get_int("st_expl"))) + custom.add_child(Node.u8("st_bg", customdict.get_int("st_bg"))) + custom.add_child(Node.u8("st_shot_vol", customdict.get_int("st_shot_vol"))) + custom.add_child(Node.u8("st_bg_bri", customdict.get_int("st_bg_bri"))) + custom.add_child(Node.u8("st_obj_size", customdict.get_int("st_obj_size"))) + custom.add_child(Node.u8("st_jr_gauge", customdict.get_int("st_jr_gauge"))) + custom.add_child(Node.u8("st_clr_gauge", customdict.get_int("st_clr_gauge"))) + custom.add_child(Node.u8("st_jdg_disp", customdict.get_int("st_jdg_disp"))) + custom.add_child(Node.u8("st_rnd", customdict.get_int("st_rnd"))) + custom.add_child(Node.u8("st_hazard", customdict.get_int("st_hazard"))) + custom.add_child(Node.u8("st_clr_cond", customdict.get_int("st_clr_cond"))) + custom.add_child( + Node.u8("same_time_note_disp", customdict.get_int("same_time_note_disp")) + ) + custom.add_child( + Node.u8("st_gr_gauge_type", customdict.get_int("st_gr_gauge_type")) + ) + custom.add_child( + Node.s16("voice_message_set", customdict.get_int("voice_message_set", -1)) + ) + custom.add_child( + Node.u8("voice_message_volume", customdict.get_int("voice_message_volume")) + ) # Unlocks - released = Node.void('released') + released = Node.void("released") pdata.add_child(released) for item in achievements: - if item.type[:5] != 'item_': + if item.type[:5] != "item_": continue itemtype = int(item.type[5:]) - if game_config.get_bool('force_unlock_songs') and itemtype == 0: + if game_config.get_bool("force_unlock_songs") and itemtype == 0: # Don't echo unlocks when we're force unlocking, we'll do it later continue - info = Node.void('info') + info = Node.void("info") released.add_child(info) - info.add_child(Node.u8('type', itemtype)) - info.add_child(Node.u16('id', item.id)) - info.add_child(Node.u16('param', item.data.get_int('param'))) + info.add_child(Node.u8("type", itemtype)) + info.add_child(Node.u16("id", item.id)) + info.add_child(Node.u16("param", item.data.get_int("param"))) - if game_config.get_bool('force_unlock_songs'): + if game_config.get_bool("force_unlock_songs"): ids: Dict[int, int] = {} songs = self.data.local.music.get_all_songs(self.game, self.version) for song in songs: if song.id not in ids: ids[song.id] = 0 - if song.data.get_int('difficulty') > 0: + if song.data.get_int("difficulty") > 0: ids[song.id] = ids[song.id] | (1 << song.chart) for songid in ids: if ids[songid] == 0: continue - info = Node.void('info') + info = Node.void("info") released.add_child(info) - info.add_child(Node.u8('type', 0)) - info.add_child(Node.u16('id', songid)) - info.add_child(Node.u16('param', ids[songid])) + info.add_child(Node.u8("type", 0)) + info.add_child(Node.u16("id", songid)) + info.add_child(Node.u16("param", ids[songid])) # Announcements - announce = Node.void('announce') + announce = Node.void("announce") pdata.add_child(announce) for announcement in achievements: - if announcement.type[:13] != 'announcement_': + if announcement.type[:13] != "announcement_": continue announcementtype = int(announcement.type[13:]) - info = Node.void('info') + info = Node.void("info") announce.add_child(info) - info.add_child(Node.u8('type', announcementtype)) - info.add_child(Node.u16('id', announcement.id)) - info.add_child(Node.u16('param', announcement.data.get_int('param'))) - info.add_child(Node.bool('bneedannounce', announcement.data.get_bool('need'))) + info.add_child(Node.u8("type", announcementtype)) + info.add_child(Node.u16("id", announcement.id)) + info.add_child(Node.u16("param", announcement.data.get_int("param"))) + info.add_child( + Node.bool("bneedannounce", announcement.data.get_bool("need")) + ) # Dojo ranking return - dojo = Node.void('dojo') + dojo = Node.void("dojo") pdata.add_child(dojo) for entry in achievements: - if entry.type != 'dojo': + if entry.type != "dojo": continue - rec = Node.void('rec') + rec = Node.void("rec") dojo.add_child(rec) - rec.add_child(Node.s32('class', entry.id)) - rec.add_child(Node.s32('clear_type', entry.data.get_int('clear_type'))) - rec.add_child(Node.s32('total_ar', entry.data.get_int('ar'))) - rec.add_child(Node.s32('total_score', entry.data.get_int('score'))) - rec.add_child(Node.s32('play_count', entry.data.get_int('plays'))) - rec.add_child(Node.s32('last_play_time', entry.data.get_int('play_timestamp'))) - rec.add_child(Node.s32('record_update_time', entry.data.get_int('record_timestamp'))) - rec.add_child(Node.s32('rank', 0)) + rec.add_child(Node.s32("class", entry.id)) + rec.add_child(Node.s32("clear_type", entry.data.get_int("clear_type"))) + rec.add_child(Node.s32("total_ar", entry.data.get_int("ar"))) + rec.add_child(Node.s32("total_score", entry.data.get_int("score"))) + rec.add_child(Node.s32("play_count", entry.data.get_int("plays"))) + rec.add_child( + Node.s32("last_play_time", entry.data.get_int("play_timestamp")) + ) + rec.add_child( + Node.s32("record_update_time", entry.data.get_int("record_timestamp")) + ) + rec.add_child(Node.s32("rank", 0)) # Player Parameters - player_param = Node.void('player_param') + player_param = Node.void("player_param") pdata.add_child(player_param) for param in achievements: - if param.type[:13] != 'player_param_': + if param.type[:13] != "player_param_": continue itemtype = int(param.type[13:]) - itemnode = Node.void('item') + itemnode = Node.void("item") player_param.add_child(itemnode) - itemnode.add_child(Node.s32('type', itemtype)) - itemnode.add_child(Node.s32('bank', param.id)) - itemnode.add_child(Node.s32_array('data', param.data.get_int_array('data', 256))) + itemnode.add_child(Node.s32("type", itemtype)) + itemnode.add_child(Node.s32("bank", param.id)) + itemnode.add_child( + Node.s32_array("data", param.data.get_int_array("data", 256)) + ) # Shop score for players self._add_shop_score(pdata) # My List data - mylist = Node.void('mylist') + mylist = Node.void("mylist") pdata.add_child(mylist) - listdata = Node.void('list') + listdata = Node.void("list") mylist.add_child(listdata) - listdata.add_child(Node.s16('idx', 0)) - listdata.add_child(Node.s16_array('mlst', profile.get_int_array('favorites', 30, [-1] * 30))) + listdata.add_child(Node.s16("idx", 0)) + listdata.add_child( + Node.s16_array("mlst", profile.get_int_array("favorites", 30, [-1] * 30)) + ) # Minigame settings - minigame = Node.void('minigame') + minigame = Node.void("minigame") pdata.add_child(minigame) - minigame.add_child(Node.s8('mgid', profile.get_int('mgid'))) - minigame.add_child(Node.s32('sc', profile.get_int('mgsc'))) + minigame.add_child(Node.s8("mgid", profile.get_int("mgid"))) + minigame.add_child(Node.s32("sc", profile.get_int("mgsc"))) # Derby settings - derby = Node.void('derby') + derby = Node.void("derby") pdata.add_child(derby) - derby.add_child(Node.bool('is_open', False)) + derby.add_child(Node.bool("is_open", False)) return root - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: game_config = self.get_game_config() newprofile = oldprofile.clone() # Save base player profile info - newprofile.replace_int('lid', ID.parse_machine_id(request.child_value('pdata/account/lid'))) - newprofile.replace_str('name', request.child_value('pdata/base/name')) - newprofile.replace_int('mg', request.child_value('pdata/base/mg')) - newprofile.replace_int('ap', request.child_value('pdata/base/ap')) - newprofile.replace_int('uattr', request.child_value('pdata/base/uattr')) - newprofile.replace_int('money', request.child_value('pdata/base/money')) - newprofile.replace_int('class', request.child_value('pdata/base/class')) - newprofile.replace_int('class_ar', request.child_value('pdata/base/class_ar')) - newprofile.replace_int('mgid', request.child_value('pdata/minigame/mgid')) - newprofile.replace_int('mgsc', request.child_value('pdata/minigame/sc')) - newprofile.replace_int_array('favorites', 30, request.child_value('pdata/mylist/list/mlst')) + newprofile.replace_int( + "lid", ID.parse_machine_id(request.child_value("pdata/account/lid")) + ) + newprofile.replace_str("name", request.child_value("pdata/base/name")) + newprofile.replace_int("mg", request.child_value("pdata/base/mg")) + newprofile.replace_int("ap", request.child_value("pdata/base/ap")) + newprofile.replace_int("uattr", request.child_value("pdata/base/uattr")) + newprofile.replace_int("money", request.child_value("pdata/base/money")) + newprofile.replace_int("class", request.child_value("pdata/base/class")) + newprofile.replace_int("class_ar", request.child_value("pdata/base/class_ar")) + newprofile.replace_int("mgid", request.child_value("pdata/minigame/mgid")) + newprofile.replace_int("mgsc", request.child_value("pdata/minigame/sc")) + newprofile.replace_int_array( + "favorites", 30, request.child_value("pdata/mylist/list/mlst") + ) # Save player config - configdict = newprofile.get_dict('config') - config = request.child('pdata/config') + configdict = newprofile.get_dict("config") + config = request.child("pdata/config") if config: - configdict.replace_int('msel_bgm', config.child_value('msel_bgm')) - configdict.replace_int('narrowdown_type', config.child_value('narrowdown_type')) - configdict.replace_int('icon_id', config.child_value('icon_id')) - configdict.replace_int('byword_0', config.child_value('byword_0')) - configdict.replace_int('byword_1', config.child_value('byword_1')) - configdict.replace_bool('is_auto_byword_0', config.child_value('is_auto_byword_0')) - configdict.replace_bool('is_auto_byword_1', config.child_value('is_auto_byword_1')) - configdict.replace_int('mrec_type', config.child_value('mrec_type')) - configdict.replace_int('tab_sel', config.child_value('tab_sel')) - configdict.replace_int('card_disp', config.child_value('card_disp')) - configdict.replace_int('score_tab_disp', config.child_value('score_tab_disp')) - configdict.replace_int('last_music_id', config.child_value('last_music_id')) - configdict.replace_int('last_note_grade', config.child_value('last_note_grade')) - configdict.replace_int('sort_type', config.child_value('sort_type')) - configdict.replace_int('rival_panel_type', config.child_value('rival_panel_type')) - configdict.replace_int('random_entry_work', config.child_value('random_entry_work')) - configdict.replace_int('custom_folder_work', config.child_value('custom_folder_work')) - configdict.replace_int('folder_type', config.child_value('folder_type')) - configdict.replace_int('folder_lamp_type', config.child_value('folder_lamp_type')) - configdict.replace_bool('is_tweet', config.child_value('is_tweet')) - configdict.replace_bool('is_link_twitter', config.child_value('is_link_twitter')) - newprofile.replace_dict('config', configdict) + configdict.replace_int("msel_bgm", config.child_value("msel_bgm")) + configdict.replace_int( + "narrowdown_type", config.child_value("narrowdown_type") + ) + configdict.replace_int("icon_id", config.child_value("icon_id")) + configdict.replace_int("byword_0", config.child_value("byword_0")) + configdict.replace_int("byword_1", config.child_value("byword_1")) + configdict.replace_bool( + "is_auto_byword_0", config.child_value("is_auto_byword_0") + ) + configdict.replace_bool( + "is_auto_byword_1", config.child_value("is_auto_byword_1") + ) + configdict.replace_int("mrec_type", config.child_value("mrec_type")) + configdict.replace_int("tab_sel", config.child_value("tab_sel")) + configdict.replace_int("card_disp", config.child_value("card_disp")) + configdict.replace_int( + "score_tab_disp", config.child_value("score_tab_disp") + ) + configdict.replace_int("last_music_id", config.child_value("last_music_id")) + configdict.replace_int( + "last_note_grade", config.child_value("last_note_grade") + ) + configdict.replace_int("sort_type", config.child_value("sort_type")) + configdict.replace_int( + "rival_panel_type", config.child_value("rival_panel_type") + ) + configdict.replace_int( + "random_entry_work", config.child_value("random_entry_work") + ) + configdict.replace_int( + "custom_folder_work", config.child_value("custom_folder_work") + ) + configdict.replace_int("folder_type", config.child_value("folder_type")) + configdict.replace_int( + "folder_lamp_type", config.child_value("folder_lamp_type") + ) + configdict.replace_bool("is_tweet", config.child_value("is_tweet")) + configdict.replace_bool( + "is_link_twitter", config.child_value("is_link_twitter") + ) + newprofile.replace_dict("config", configdict) # Save player custom settings - customdict = newprofile.get_dict('custom') - custom = request.child('pdata/custom') + customdict = newprofile.get_dict("custom") + custom = request.child("pdata/custom") if custom: - customdict.replace_int('st_shot', custom.child_value('st_shot')) - customdict.replace_int('st_frame', custom.child_value('st_frame')) - customdict.replace_int('st_expl', custom.child_value('st_expl')) - customdict.replace_int('st_bg', custom.child_value('st_bg')) - customdict.replace_int('st_shot_vol', custom.child_value('st_shot_vol')) - customdict.replace_int('st_bg_bri', custom.child_value('st_bg_bri')) - customdict.replace_int('st_obj_size', custom.child_value('st_obj_size')) - customdict.replace_int('st_jr_gauge', custom.child_value('st_jr_gauge')) - customdict.replace_int('st_clr_gauge', custom.child_value('st_clr_gauge')) - customdict.replace_int('st_rnd', custom.child_value('st_rnd')) - customdict.replace_int('st_hazard', custom.child_value('st_hazard')) - customdict.replace_int('st_clr_cond', custom.child_value('st_clr_cond')) - customdict.replace_int('same_time_note_disp', custom.child_value('same_time_note_disp')) - customdict.replace_int('st_gr_gauge_type', custom.child_value('st_gr_gauge_type')) - customdict.replace_int('voice_message_set', custom.child_value('voice_message_set')) - customdict.replace_int('voice_message_volume', custom.child_value('voice_message_volume')) - newprofile.replace_dict('custom', customdict) + customdict.replace_int("st_shot", custom.child_value("st_shot")) + customdict.replace_int("st_frame", custom.child_value("st_frame")) + customdict.replace_int("st_expl", custom.child_value("st_expl")) + customdict.replace_int("st_bg", custom.child_value("st_bg")) + customdict.replace_int("st_shot_vol", custom.child_value("st_shot_vol")) + customdict.replace_int("st_bg_bri", custom.child_value("st_bg_bri")) + customdict.replace_int("st_obj_size", custom.child_value("st_obj_size")) + customdict.replace_int("st_jr_gauge", custom.child_value("st_jr_gauge")) + customdict.replace_int("st_clr_gauge", custom.child_value("st_clr_gauge")) + customdict.replace_int("st_rnd", custom.child_value("st_rnd")) + customdict.replace_int("st_hazard", custom.child_value("st_hazard")) + customdict.replace_int("st_clr_cond", custom.child_value("st_clr_cond")) + customdict.replace_int( + "same_time_note_disp", custom.child_value("same_time_note_disp") + ) + customdict.replace_int( + "st_gr_gauge_type", custom.child_value("st_gr_gauge_type") + ) + customdict.replace_int( + "voice_message_set", custom.child_value("voice_message_set") + ) + customdict.replace_int( + "voice_message_volume", custom.child_value("voice_message_volume") + ) + newprofile.replace_dict("custom", customdict) # Save player parameter info - params = request.child('pdata/player_param') + params = request.child("pdata/player_param") if params: for child in params.children: - if child.name != 'item': + if child.name != "item": continue - item_type = child.child_value('type') - bank = child.child_value('bank') - data = child.child_value('data') + item_type = child.child_value("type") + bank = child.child_value("bank") + data = child.child_value("data") while len(data) < 256: data.append(0) self.data.local.user.put_achievement( @@ -624,54 +779,56 @@ class ReflecBeatVolzza(ReflecBeatVolzzaBase): self.version, userid, bank, - f'player_param_{item_type}', + f"player_param_{item_type}", { - 'data': data, + "data": data, }, ) # Save player episode info - episode = request.child('pdata/episode') + episode = request.child("pdata/episode") if episode: for child in episode.children: - if child.name != 'info': + if child.name != "info": continue # I assume this is copypasta, but I want to be sure - extid = child.child_value('user_id') + extid = child.child_value("user_id") if extid != newprofile.extid: - raise Exception(f'Unexpected user ID, got {extid} expecting {newprofile.extid}') + raise Exception( + f"Unexpected user ID, got {extid} expecting {newprofile.extid}" + ) - episode_type = child.child_value('type') - episode_value0 = child.child_value('value0') - episode_value1 = child.child_value('value1') - episode_text = child.child_value('text') - episode_time = child.child_value('time') + episode_type = child.child_value("type") + episode_value0 = child.child_value("value0") + episode_value1 = child.child_value("value1") + episode_text = child.child_value("text") + episode_time = child.child_value("time") self.data.local.user.put_achievement( self.game, self.version, userid, episode_type, - 'episode', + "episode", { - 'value0': episode_value0, - 'value1': episode_value1, - 'text': episode_text, - 'time': episode_time, + "value0": episode_value0, + "value1": episode_value1, + "text": episode_text, + "time": episode_time, }, ) # Save released info - released = request.child('pdata/released') + released = request.child("pdata/released") if released: for child in released.children: - if child.name != 'info': + if child.name != "info": continue - item_id = child.child_value('id') - item_type = child.child_value('type') - param = child.child_value('param') - if game_config.get_bool('force_unlock_songs') and item_type == 0: + item_id = child.child_value("id") + item_type = child.child_value("type") + param = child.child_value("param") + if game_config.get_bool("force_unlock_songs") and item_type == 0: # Don't save unlocks when we're force unlocking continue @@ -680,91 +837,96 @@ class ReflecBeatVolzza(ReflecBeatVolzzaBase): self.version, userid, item_id, - f'item_{item_type}', + f"item_{item_type}", { - 'param': param, + "param": param, }, ) # Save announce info - announce = request.child('pdata/announce') + announce = request.child("pdata/announce") if announce: for child in announce.children: - if child.name != 'info': + if child.name != "info": continue - announce_id = child.child_value('id') - announce_type = child.child_value('type') - param = child.child_value('param') - need = child.child_value('bneedannounce') + announce_id = child.child_value("id") + announce_type = child.child_value("type") + param = child.child_value("param") + need = child.child_value("bneedannounce") self.data.local.user.put_achievement( self.game, self.version, userid, announce_id, - f'announcement_{announce_type}', + f"announcement_{announce_type}", { - 'param': param, - 'need': need, + "param": param, + "need": need, }, ) # Save player dojo - dojo = request.child('pdata/dojo') + dojo = request.child("pdata/dojo") if dojo: - dojoid = dojo.child_value('class') - clear_type = dojo.child_value('clear_type') - ar = dojo.child_value('t_ar') - score = dojo.child_value('t_score') + dojoid = dojo.child_value("class") + clear_type = dojo.child_value("clear_type") + ar = dojo.child_value("t_ar") + score = dojo.child_value("t_score") # Figure out timestamp stuff - data = self.data.local.user.get_achievement( - self.game, - self.version, - userid, - dojoid, - 'dojo', - ) or ValidatedDict() + data = ( + self.data.local.user.get_achievement( + self.game, + self.version, + userid, + dojoid, + "dojo", + ) + or ValidatedDict() + ) - if ar >= data.get_int('ar'): + if ar >= data.get_int("ar"): # We set a new achievement rate, keep the new values record_time = Time.now() else: # We didn't, keep the old values for achievement rate, but # override score and clear_type only if they were better. - record_time = data.get_int('record_timestamp') - ar = data.get_int('ar') - score = max(score, data.get_int('score')) - clear_type = max(clear_type, data.get_int('clear_type')) + record_time = data.get_int("record_timestamp") + ar = data.get_int("ar") + score = max(score, data.get_int("score")) + clear_type = max(clear_type, data.get_int("clear_type")) play_time = Time.now() - plays = data.get_int('plays') + 1 + plays = data.get_int("plays") + 1 self.data.local.user.put_achievement( self.game, self.version, userid, dojoid, - 'dojo', + "dojo", { - 'clear_type': clear_type, - 'ar': ar, - 'score': score, - 'plays': plays, - 'play_timestamp': play_time, - 'record_timestamp': record_time, + "clear_type": clear_type, + "ar": ar, + "score": score, + "plays": plays, + "play_timestamp": play_time, + "record_timestamp": record_time, }, ) # Grab any new rivals added during this play session - rivalnode = request.child('pdata/rival') + rivalnode = request.child("pdata/rival") if rivalnode: for child in rivalnode.children: - if child.name != 'r': + if child.name != "r": continue - extid = child.child_value('id') - other_userid = self.data.remote.user.from_extid(self.game, self.version, extid) + extid = child.child_value("id") + other_userid = self.data.remote.user.from_extid( + self.game, self.version, extid + ) if other_userid is None: continue @@ -772,30 +934,30 @@ class ReflecBeatVolzza(ReflecBeatVolzzaBase): self.game, self.version, userid, - 'rival', + "rival", other_userid, {}, ) # Grab any new records set during this play session - songplays = request.child('pdata/stglog') + songplays = request.child("pdata/stglog") if songplays: for child in songplays.children: - if child.name != 'log': + if child.name != "log": continue - songid = child.child_value('mid') - chart = child.child_value('ng') - clear_type = child.child_value('ct') + songid = child.child_value("mid") + chart = child.child_value("ng") + clear_type = child.child_value("ct") if songid == 0 and chart == 0 and clear_type == -1: # Dummy song save during profile create continue - points = child.child_value('sc') - achievement_rate = child.child_value('ar') - param = child.child_value('param') - miss_count = child.child_value('jt_ms') - k_flag = child.child_value('k_flag') + points = child.child_value("sc") + achievement_rate = child.child_value("ar") + param = child.child_value("param") + miss_count = child.child_value("jt_ms") + k_flag = child.child_value("k_flag") # Param is some random bits along with the combo type combo_type = param & 0x3 diff --git a/bemani/backend/reflec/volzza2.py b/bemani/backend/reflec/volzza2.py index fe20f9b..2f37130 100644 --- a/bemani/backend/reflec/volzza2.py +++ b/bemani/backend/reflec/volzza2.py @@ -23,19 +23,19 @@ class ReflecBeatVolzza2(ReflecBeatVolzzaBase): Return all of our front-end modifiably settings. """ return { - 'bools': [ + "bools": [ { - 'name': 'Force Song Unlock', - 'tip': 'Force unlock all songs.', - 'category': 'game_config', - 'setting': 'force_unlock_songs', + "name": "Force Song Unlock", + "tip": "Force unlock all songs.", + "category": "game_config", + "setting": "force_unlock_songs", }, ], - 'ints': [], + "ints": [], } def _add_event_info(self, root: Node) -> None: - event_ctrl = Node.void('event_ctrl') + event_ctrl = Node.void("event_ctrl") root.add_child(event_ctrl) # Contains zero or more nodes like: # @@ -47,7 +47,7 @@ class ReflecBeatVolzza2(ReflecBeatVolzzaBase): # any # - item_lock_ctrl = Node.void('item_lock_ctrl') + item_lock_ctrl = Node.void("item_lock_ctrl") root.add_child(item_lock_ctrl) # Contains zero or more nodes like: # @@ -56,112 +56,136 @@ class ReflecBeatVolzza2(ReflecBeatVolzzaBase): # 0-3 # - mycourse_ctrl = Node.void('mycourse_ctrl') + mycourse_ctrl = Node.void("mycourse_ctrl") root.add_child(mycourse_ctrl) - songs = {song.id for song in self.data.local.music.get_all_songs(self.game, self.version)} + songs = { + song.id + for song in self.data.local.music.get_all_songs(self.game, self.version) + } for song in songs: - data = Node.void('data') + data = Node.void("data") mycourse_ctrl.add_child(data) - data.add_child(Node.s16('mycourse_id', 1)) - data.add_child(Node.s32('type', 0)) - data.add_child(Node.s32('music_id', song)) + data.add_child(Node.s16("mycourse_id", 1)) + data.add_child(Node.s32("type", 0)) + data.add_child(Node.s32("music_id", song)) def handle_player_rb5_player_read_score_old_5_request(self, request: Node) -> Node: - root = Node.void('player') - pdata = Node.void('pdata') + root = Node.void("player") + pdata = Node.void("pdata") root.add_child(pdata) - record = Node.void('record_old') + record = Node.void("record_old") pdata.add_child(record) return root def handle_player_rb5_player_read_score_5_request(self, request: Node) -> Node: - refid = request.child_value('rid') + refid = request.child_value("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is None: scores: List[Score] = [] else: scores = self.data.remote.music.get_scores(self.game, self.version, userid) - root = Node.void('player') - pdata = Node.void('pdata') + root = Node.void("player") + pdata = Node.void("pdata") root.add_child(pdata) - record = Node.void('record') + record = Node.void("record") pdata.add_child(record) for score in scores: - rec = Node.void('rec') + rec = Node.void("rec") record.add_child(rec) - rec.add_child(Node.s16('mid', score.id)) - rec.add_child(Node.s8('ntgrd', score.chart)) - rec.add_child(Node.s32('pc', score.plays)) - rec.add_child(Node.s8('ct', self._db_to_game_clear_type(score.data.get_int('clear_type')))) - rec.add_child(Node.s16('ar', score.data.get_int('achievement_rate'))) - rec.add_child(Node.s16('scr', score.points)) - rec.add_child(Node.s16('ms', score.data.get_int('miss_count'))) - rec.add_child(Node.s16( - 'param', - self._db_to_game_combo_type(score.data.get_int('combo_type')) + score.data.get_int('param'), - )) - rec.add_child(Node.s32('bscrt', score.timestamp)) - rec.add_child(Node.s32('bart', score.data.get_int('best_achievement_rate_time'))) - rec.add_child(Node.s32('bctt', score.data.get_int('best_clear_type_time'))) - rec.add_child(Node.s32('bmst', score.data.get_int('best_miss_count_time'))) - rec.add_child(Node.s32('time', score.data.get_int('last_played_time'))) - rec.add_child(Node.s32('k_flag', score.data.get_int('kflag'))) + rec.add_child(Node.s16("mid", score.id)) + rec.add_child(Node.s8("ntgrd", score.chart)) + rec.add_child(Node.s32("pc", score.plays)) + rec.add_child( + Node.s8( + "ct", self._db_to_game_clear_type(score.data.get_int("clear_type")) + ) + ) + rec.add_child(Node.s16("ar", score.data.get_int("achievement_rate"))) + rec.add_child(Node.s16("scr", score.points)) + rec.add_child(Node.s16("ms", score.data.get_int("miss_count"))) + rec.add_child( + Node.s16( + "param", + self._db_to_game_combo_type(score.data.get_int("combo_type")) + + score.data.get_int("param"), + ) + ) + rec.add_child(Node.s32("bscrt", score.timestamp)) + rec.add_child( + Node.s32("bart", score.data.get_int("best_achievement_rate_time")) + ) + rec.add_child(Node.s32("bctt", score.data.get_int("best_clear_type_time"))) + rec.add_child(Node.s32("bmst", score.data.get_int("best_miss_count_time"))) + rec.add_child(Node.s32("time", score.data.get_int("last_played_time"))) + rec.add_child(Node.s32("k_flag", score.data.get_int("kflag"))) return root - def handle_player_rb5_player_read_rival_score_5_request(self, request: Node) -> Node: - extid = request.child_value('uid') - songid = request.child_value('music_id') - chart = request.child_value('note_grade') + def handle_player_rb5_player_read_rival_score_5_request( + self, request: Node + ) -> Node: + extid = request.child_value("uid") + songid = request.child_value("music_id") + chart = request.child_value("note_grade") userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is None: score = None profile = None else: - score = self.data.remote.music.get_score(self.game, self.version, userid, songid, chart) + score = self.data.remote.music.get_score( + self.game, self.version, userid, songid, chart + ) profile = self.get_any_profile(userid) - root = Node.void('player') + root = Node.void("player") if score is not None and profile is not None: - player_select_score = Node.void('player_select_score') + player_select_score = Node.void("player_select_score") root.add_child(player_select_score) - player_select_score.add_child(Node.s32('user_id', extid)) - player_select_score.add_child(Node.string('name', profile.get_str('name'))) - player_select_score.add_child(Node.s32('m_score', score.points)) - player_select_score.add_child(Node.s32('m_scoreTime', score.timestamp)) - player_select_score.add_child(Node.s16('m_iconID', profile.get_dict('config').get_int('icon_id'))) + player_select_score.add_child(Node.s32("user_id", extid)) + player_select_score.add_child(Node.string("name", profile.get_str("name"))) + player_select_score.add_child(Node.s32("m_score", score.points)) + player_select_score.add_child(Node.s32("m_scoreTime", score.timestamp)) + player_select_score.add_child( + Node.s16("m_iconID", profile.get_dict("config").get_int("icon_id")) + ) return root - def handle_player_rb5_player_read_rival_ranking_data_5_request(self, request: Node) -> Node: - extid = request.child_value('uid') + def handle_player_rb5_player_read_rival_ranking_data_5_request( + self, request: Node + ) -> Node: + extid = request.child_value("uid") userid = self.data.remote.user.from_extid(self.game, self.version, extid) - root = Node.void('player') - rival_data = Node.void('rival_data') + root = Node.void("player") + rival_data = Node.void("rival_data") root.add_child(rival_data) if userid is not None: links = self.data.local.user.get_links(self.game, self.version, userid) for link in links: - if link.type != 'rival': + if link.type != "rival": continue rprofile = self.get_profile(link.other_userid) if rprofile is None: continue - rl = Node.void('rl') + rl = Node.void("rl") rival_data.add_child(rl) - rl.add_child(Node.s32('uid', rprofile.extid)) - rl.add_child(Node.string('nm', rprofile.get_str('name'))) - rl.add_child(Node.s16('ic', rprofile.get_dict('config').get_int('icon_id'))) + rl.add_child(Node.s32("uid", rprofile.extid)) + rl.add_child(Node.string("nm", rprofile.get_str("name"))) + rl.add_child( + Node.s16("ic", rprofile.get_dict("config").get_int("icon_id")) + ) - scores = self.data.remote.music.get_scores(self.game, self.version, link.other_userid) + scores = self.data.remote.music.get_scores( + self.game, self.version, link.other_userid + ) scores_by_musicid: Dict[int, List[Score]] = {} for score in scores: if score.id not in scores_by_musicid: @@ -174,17 +198,16 @@ class ReflecBeatVolzza2(ReflecBeatVolzzaBase): for score in scores ] timestamps = [ - score.timestamp if score is not None else 0 - for score in scores + score.timestamp if score is not None else 0 for score in scores ] - sl = Node.void('sl') + sl = Node.void("sl") rl.add_child(sl) - sl.add_child(Node.s16('mid', mid)) + sl.add_child(Node.s16("mid", mid)) # Score, but shifted left 32 bits for no reason - sl.add_child(Node.u64_array('m', points)) + sl.add_child(Node.u64_array("m", points)) # Timestamp of the clear - sl.add_child(Node.u64_array('t', timestamps)) + sl.add_child(Node.u64_array("t", timestamps)) return root @@ -193,8 +216,8 @@ class ReflecBeatVolzza2(ReflecBeatVolzzaBase): # [total score, basic chart score, medium chart score, hard chart score, # special chart score]. It also returns the previous rank, but this is # not used in-game as far as I can tell. - current_scores = request.child_value('sc') - current_minigame_score = request.child_value('mg_sc') + current_scores = request.child_value("sc") + current_minigame_score = request.child_value("mg_sc") # First, grab all scores on the network for this version. all_scores = self.data.remote.music.get_all_scores(self.game, self.version) @@ -206,15 +229,20 @@ class ReflecBeatVolzza2(ReflecBeatVolzzaBase): # scores where the user at least cleared the song. scores_by_user = { userid: [ - score for (uid, score) in all_scores - if uid == userid and score.data.get_int('clear_type') >= self.CLEAR_TYPE_CLEARED] + score + for (uid, score) in all_scores + if uid == userid + and score.data.get_int("clear_type") >= self.CLEAR_TYPE_CLEARED + ] for userid in all_users } # Now grab all user profiles for this game all_profiles = { - profile[0]: profile[1] for profile in - self.data.remote.user.get_all_profiles(self.game, self.version) + profile[0]: profile[1] + for profile in self.data.remote.user.get_all_profiles( + self.game, self.version + ) } # Now, sum up the scores into the five categories that the game expects. @@ -227,34 +255,60 @@ class ReflecBeatVolzza2(ReflecBeatVolzzaBase): ) basic_scores = sorted( [ - sum([score.points for score in scores if score.chart == self.CHART_TYPE_BASIC]) + sum( + [ + score.points + for score in scores + if score.chart == self.CHART_TYPE_BASIC + ] + ) for userid, scores in scores_by_user.items() ], reverse=True, ) medium_scores = sorted( [ - sum([score.points for score in scores if score.chart == self.CHART_TYPE_MEDIUM]) + sum( + [ + score.points + for score in scores + if score.chart == self.CHART_TYPE_MEDIUM + ] + ) for userid, scores in scores_by_user.items() ], reverse=True, ) hard_scores = sorted( [ - sum([score.points for score in scores if score.chart == self.CHART_TYPE_HARD]) + sum( + [ + score.points + for score in scores + if score.chart == self.CHART_TYPE_HARD + ] + ) for userid, scores in scores_by_user.items() ], ) special_scores = sorted( [ - sum([score.points for score in scores if score.chart == self.CHART_TYPE_SPECIAL]) + sum( + [ + score.points + for score in scores + if score.chart == self.CHART_TYPE_SPECIAL + ] + ) for userid, scores in scores_by_user.items() ], reverse=True, ) minigame_scores = sorted( [ - all_profiles.get(userid, Profile(self.game, self.version, "", 0)).get_int('mgsc') + all_profiles.get( + userid, Profile(self.game, self.version, "", 0) + ).get_int("mgsc") for userid in all_users ], reverse=True, @@ -292,41 +346,43 @@ class ReflecBeatVolzza2(ReflecBeatVolzzaBase): minigame_rank = user_place[-1] user_place = user_place[:-1] - root = Node.void('player') + root = Node.void("player") # Populate current ranking. - tbs = Node.void('tbs') + tbs = Node.void("tbs") root.add_child(tbs) - tbs.add_child(Node.s32_array('new_rank', user_place)) - tbs.add_child(Node.s32_array('old_rank', [-1, -1, -1, -1, -1])) + tbs.add_child(Node.s32_array("new_rank", user_place)) + tbs.add_child(Node.s32_array("old_rank", [-1, -1, -1, -1, -1])) # Populate current minigame ranking (LOL). - mng = Node.void('mng') + mng = Node.void("mng") root.add_child(mng) - mng.add_child(Node.s32('new_rank', minigame_rank)) - mng.add_child(Node.s32('old_rank', -1)) + mng.add_child(Node.s32("new_rank", minigame_rank)) + mng.add_child(Node.s32("old_rank", -1)) return root def handle_player_rb5_player_write_5_request(self, request: Node) -> Node: - refid = request.child_value('pdata/account/rid') + refid = request.child_value("pdata/account/rid") profile = self.put_profile_by_refid(refid, request) - root = Node.void('player') + root = Node.void("player") if profile is None: - root.add_child(Node.s32('uid', 0)) + root.add_child(Node.s32("uid", 0)) else: - root.add_child(Node.s32('uid', profile.extid)) + root.add_child(Node.s32("uid", profile.extid)) return root def format_profile(self, userid: UserID, profile: Profile) -> Node: statistics = self.get_play_statistics(userid) game_config = self.get_game_config() - achievements = self.data.local.user.get_achievements(self.game, self.version, userid) + achievements = self.data.local.user.get_achievements( + self.game, self.version, userid + ) links = self.data.local.user.get_links(self.game, self.version, userid) rprofiles: Dict[UserID, Profile] = {} - root = Node.void('player') - pdata = Node.void('pdata') + root = Node.void("player") + pdata = Node.void("pdata") root.add_child(pdata) # Previous account info @@ -337,47 +393,73 @@ class ReflecBeatVolzza2(ReflecBeatVolzzaBase): succeeded = False # Account info - account = Node.void('account') + account = Node.void("account") pdata.add_child(account) - account.add_child(Node.s32('usrid', profile.extid)) - account.add_child(Node.s32('tpc', statistics.total_plays)) - account.add_child(Node.s32('dpc', statistics.today_plays)) - account.add_child(Node.s32('crd', 1)) - account.add_child(Node.s32('brd', 1)) - account.add_child(Node.s32('tdc', statistics.total_days)) - account.add_child(Node.s32('intrvld', 0)) - account.add_child(Node.s16('ver', 0)) - account.add_child(Node.bool('succeed', succeeded)) - account.add_child(Node.u64('pst', 0)) - account.add_child(Node.u64('st', Time.now() * 1000)) - account.add_child(Node.s32('opc', 0)) - account.add_child(Node.s32('lpc', 0)) - account.add_child(Node.s32('cpc', 0)) - account.add_child(Node.s32('mpc', 0)) + account.add_child(Node.s32("usrid", profile.extid)) + account.add_child(Node.s32("tpc", statistics.total_plays)) + account.add_child(Node.s32("dpc", statistics.today_plays)) + account.add_child(Node.s32("crd", 1)) + account.add_child(Node.s32("brd", 1)) + account.add_child(Node.s32("tdc", statistics.total_days)) + account.add_child(Node.s32("intrvld", 0)) + account.add_child(Node.s16("ver", 0)) + account.add_child(Node.bool("succeed", succeeded)) + account.add_child(Node.u64("pst", 0)) + account.add_child(Node.u64("st", Time.now() * 1000)) + account.add_child(Node.s32("opc", 0)) + account.add_child(Node.s32("lpc", 0)) + account.add_child(Node.s32("cpc", 0)) + account.add_child(Node.s32("mpc", 0)) # Base profile info - base = Node.void('base') + base = Node.void("base") pdata.add_child(base) - base.add_child(Node.string('name', profile.get_str('name'))) - base.add_child(Node.s32('mg', profile.get_int('mg'))) - base.add_child(Node.s32('ap', profile.get_int('ap'))) - base.add_child(Node.string('cmnt', '')) - base.add_child(Node.s32('uattr', profile.get_int('uattr'))) - base.add_child(Node.s32('money', profile.get_int('money'))) - base.add_child(Node.s32('tbs_5', -1)) - base.add_child(Node.s32_array('tbgs_5', [-1, -1, -1, -1])) - base.add_child(Node.s16_array('mlog', [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1])) - base.add_child(Node.s32('class', profile.get_int('class'))) - base.add_child(Node.s32('class_ar', profile.get_int('class_ar'))) - base.add_child(Node.s32('skill_point', profile.get_int('skill_point'))) - base.add_child(Node.bool('meteor_flg', False)) + base.add_child(Node.string("name", profile.get_str("name"))) + base.add_child(Node.s32("mg", profile.get_int("mg"))) + base.add_child(Node.s32("ap", profile.get_int("ap"))) + base.add_child(Node.string("cmnt", "")) + base.add_child(Node.s32("uattr", profile.get_int("uattr"))) + base.add_child(Node.s32("money", profile.get_int("money"))) + base.add_child(Node.s32("tbs_5", -1)) + base.add_child(Node.s32_array("tbgs_5", [-1, -1, -1, -1])) + base.add_child( + Node.s16_array( + "mlog", + [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], + ) + ) + base.add_child(Node.s32("class", profile.get_int("class"))) + base.add_child(Node.s32("class_ar", profile.get_int("class_ar"))) + base.add_child(Node.s32("skill_point", profile.get_int("skill_point"))) + base.add_child(Node.bool("meteor_flg", False)) # Rivals - rival = Node.void('rival') + rival = Node.void("rival") pdata.add_child(rival) slotid = 0 for link in links: - if link.type != 'rival': + if link.type != "rival": continue if link.other_userid not in rprofiles: @@ -387,240 +469,308 @@ class ReflecBeatVolzza2(ReflecBeatVolzzaBase): rprofiles[link.other_userid] = rprofile else: rprofile = rprofiles[link.other_userid] - lobbyinfo = self.data.local.lobby.get_play_session_info(self.game, self.version, link.other_userid) + lobbyinfo = self.data.local.lobby.get_play_session_info( + self.game, self.version, link.other_userid + ) if lobbyinfo is None: lobbyinfo = ValidatedDict() - r = Node.void('r') + r = Node.void("r") rival.add_child(r) - r.add_child(Node.s32('slot_id', slotid)) - r.add_child(Node.s32('id', rprofile.extid)) - r.add_child(Node.string('name', rprofile.get_str('name'))) - r.add_child(Node.s32('icon', rprofile.get_dict('config').get_int('icon_id'))) - r.add_child(Node.s32('class', rprofile.get_int('class'))) - r.add_child(Node.s32('class_ar', rprofile.get_int('class_ar'))) - r.add_child(Node.bool('friend', True)) - r.add_child(Node.bool('target', False)) - r.add_child(Node.u32('time', lobbyinfo.get_int('time'))) - r.add_child(Node.u8_array('ga', lobbyinfo.get_int_array('ga', 4))) - r.add_child(Node.u16('gp', lobbyinfo.get_int('gp'))) - r.add_child(Node.u8_array('ipn', lobbyinfo.get_int_array('la', 4))) - r.add_child(Node.u8_array('pnid', lobbyinfo.get_int_array('pnid', 16))) + r.add_child(Node.s32("slot_id", slotid)) + r.add_child(Node.s32("id", rprofile.extid)) + r.add_child(Node.string("name", rprofile.get_str("name"))) + r.add_child( + Node.s32("icon", rprofile.get_dict("config").get_int("icon_id")) + ) + r.add_child(Node.s32("class", rprofile.get_int("class"))) + r.add_child(Node.s32("class_ar", rprofile.get_int("class_ar"))) + r.add_child(Node.bool("friend", True)) + r.add_child(Node.bool("target", False)) + r.add_child(Node.u32("time", lobbyinfo.get_int("time"))) + r.add_child(Node.u8_array("ga", lobbyinfo.get_int_array("ga", 4))) + r.add_child(Node.u16("gp", lobbyinfo.get_int("gp"))) + r.add_child(Node.u8_array("ipn", lobbyinfo.get_int_array("la", 4))) + r.add_child(Node.u8_array("pnid", lobbyinfo.get_int_array("pnid", 16))) slotid = slotid + 1 # Configuration - configdict = profile.get_dict('config') - config = Node.void('config') + configdict = profile.get_dict("config") + config = Node.void("config") pdata.add_child(config) - config.add_child(Node.u8('msel_bgm', configdict.get_int('msel_bgm'))) - config.add_child(Node.u8('narrowdown_type', configdict.get_int('narrowdown_type'))) - config.add_child(Node.s16('icon_id', configdict.get_int('icon_id'))) - config.add_child(Node.s16('byword_0', configdict.get_int('byword_0'))) - config.add_child(Node.s16('byword_1', configdict.get_int('byword_1'))) - config.add_child(Node.bool('is_auto_byword_0', configdict.get_bool('is_auto_byword_0'))) - config.add_child(Node.bool('is_auto_byword_1', configdict.get_bool('is_auto_byword_1'))) - config.add_child(Node.u8('mrec_type', configdict.get_int('mrec_type'))) - config.add_child(Node.u8('tab_sel', configdict.get_int('tab_sel'))) - config.add_child(Node.u8('card_disp', configdict.get_int('card_disp'))) - config.add_child(Node.u8('score_tab_disp', configdict.get_int('score_tab_disp'))) - config.add_child(Node.s16('last_music_id', configdict.get_int('last_music_id', -1))) - config.add_child(Node.u8('last_note_grade', configdict.get_int('last_note_grade'))) - config.add_child(Node.u8('sort_type', configdict.get_int('sort_type'))) - config.add_child(Node.u8('rival_panel_type', configdict.get_int('rival_panel_type'))) - config.add_child(Node.u64('random_entry_work', configdict.get_int('random_entry_work'))) - config.add_child(Node.u64('custom_folder_work', configdict.get_int('custom_folder_work'))) - config.add_child(Node.u8('folder_type', configdict.get_int('folder_type'))) - config.add_child(Node.u8('folder_lamp_type', configdict.get_int('folder_lamp_type'))) - config.add_child(Node.bool('is_tweet', configdict.get_bool('is_tweet'))) - config.add_child(Node.bool('is_link_twitter', configdict.get_bool('is_link_twitter'))) + config.add_child(Node.u8("msel_bgm", configdict.get_int("msel_bgm"))) + config.add_child( + Node.u8("narrowdown_type", configdict.get_int("narrowdown_type")) + ) + config.add_child(Node.s16("icon_id", configdict.get_int("icon_id"))) + config.add_child(Node.s16("byword_0", configdict.get_int("byword_0"))) + config.add_child(Node.s16("byword_1", configdict.get_int("byword_1"))) + config.add_child( + Node.bool("is_auto_byword_0", configdict.get_bool("is_auto_byword_0")) + ) + config.add_child( + Node.bool("is_auto_byword_1", configdict.get_bool("is_auto_byword_1")) + ) + config.add_child(Node.u8("mrec_type", configdict.get_int("mrec_type"))) + config.add_child(Node.u8("tab_sel", configdict.get_int("tab_sel"))) + config.add_child(Node.u8("card_disp", configdict.get_int("card_disp"))) + config.add_child( + Node.u8("score_tab_disp", configdict.get_int("score_tab_disp")) + ) + config.add_child( + Node.s16("last_music_id", configdict.get_int("last_music_id", -1)) + ) + config.add_child( + Node.u8("last_note_grade", configdict.get_int("last_note_grade")) + ) + config.add_child(Node.u8("sort_type", configdict.get_int("sort_type"))) + config.add_child( + Node.u8("rival_panel_type", configdict.get_int("rival_panel_type")) + ) + config.add_child( + Node.u64("random_entry_work", configdict.get_int("random_entry_work")) + ) + config.add_child( + Node.u64("custom_folder_work", configdict.get_int("custom_folder_work")) + ) + config.add_child(Node.u8("folder_type", configdict.get_int("folder_type"))) + config.add_child( + Node.u8("folder_lamp_type", configdict.get_int("folder_lamp_type")) + ) + config.add_child(Node.bool("is_tweet", configdict.get_bool("is_tweet"))) + config.add_child( + Node.bool("is_link_twitter", configdict.get_bool("is_link_twitter")) + ) # Customizations - customdict = profile.get_dict('custom') - custom = Node.void('custom') + customdict = profile.get_dict("custom") + custom = Node.void("custom") pdata.add_child(custom) - custom.add_child(Node.u8('st_shot', customdict.get_int('st_shot'))) - custom.add_child(Node.u8('st_frame', customdict.get_int('st_frame'))) - custom.add_child(Node.u8('st_expl', customdict.get_int('st_expl'))) - custom.add_child(Node.u8('st_bg', customdict.get_int('st_bg'))) - custom.add_child(Node.u8('st_shot_vol', customdict.get_int('st_shot_vol'))) - custom.add_child(Node.u8('st_bg_bri', customdict.get_int('st_bg_bri'))) - custom.add_child(Node.u8('st_obj_size', customdict.get_int('st_obj_size'))) - custom.add_child(Node.u8('st_jr_gauge', customdict.get_int('st_jr_gauge'))) - custom.add_child(Node.u8('st_clr_gauge', customdict.get_int('st_clr_gauge'))) - custom.add_child(Node.u8('st_rnd', customdict.get_int('st_rnd'))) - custom.add_child(Node.u8('st_gr_gauge_type', customdict.get_int('st_gr_gauge_type'))) - custom.add_child(Node.s16('voice_message_set', customdict.get_int('voice_message_set', -1))) - custom.add_child(Node.u8('same_time_note_disp', customdict.get_int('same_time_note_disp'))) - custom.add_child(Node.u8('st_score_disp_type', customdict.get_int('st_score_disp_type'))) - custom.add_child(Node.u8('st_bonus_type', customdict.get_int('st_bonus_type'))) - custom.add_child(Node.u8('st_rivalnote_type', customdict.get_int('st_rivalnote_type'))) - custom.add_child(Node.u8('st_topassist_type', customdict.get_int('st_topassist_type'))) - custom.add_child(Node.u8('high_speed', customdict.get_int('high_speed'))) - custom.add_child(Node.u8('st_hazard', customdict.get_int('st_hazard'))) - custom.add_child(Node.u8('st_clr_cond', customdict.get_int('st_clr_cond'))) - custom.add_child(Node.u8('voice_message_volume', customdict.get_int('voice_message_volume'))) + custom.add_child(Node.u8("st_shot", customdict.get_int("st_shot"))) + custom.add_child(Node.u8("st_frame", customdict.get_int("st_frame"))) + custom.add_child(Node.u8("st_expl", customdict.get_int("st_expl"))) + custom.add_child(Node.u8("st_bg", customdict.get_int("st_bg"))) + custom.add_child(Node.u8("st_shot_vol", customdict.get_int("st_shot_vol"))) + custom.add_child(Node.u8("st_bg_bri", customdict.get_int("st_bg_bri"))) + custom.add_child(Node.u8("st_obj_size", customdict.get_int("st_obj_size"))) + custom.add_child(Node.u8("st_jr_gauge", customdict.get_int("st_jr_gauge"))) + custom.add_child(Node.u8("st_clr_gauge", customdict.get_int("st_clr_gauge"))) + custom.add_child(Node.u8("st_rnd", customdict.get_int("st_rnd"))) + custom.add_child( + Node.u8("st_gr_gauge_type", customdict.get_int("st_gr_gauge_type")) + ) + custom.add_child( + Node.s16("voice_message_set", customdict.get_int("voice_message_set", -1)) + ) + custom.add_child( + Node.u8("same_time_note_disp", customdict.get_int("same_time_note_disp")) + ) + custom.add_child( + Node.u8("st_score_disp_type", customdict.get_int("st_score_disp_type")) + ) + custom.add_child(Node.u8("st_bonus_type", customdict.get_int("st_bonus_type"))) + custom.add_child( + Node.u8("st_rivalnote_type", customdict.get_int("st_rivalnote_type")) + ) + custom.add_child( + Node.u8("st_topassist_type", customdict.get_int("st_topassist_type")) + ) + custom.add_child(Node.u8("high_speed", customdict.get_int("high_speed"))) + custom.add_child(Node.u8("st_hazard", customdict.get_int("st_hazard"))) + custom.add_child(Node.u8("st_clr_cond", customdict.get_int("st_clr_cond"))) + custom.add_child( + Node.u8("voice_message_volume", customdict.get_int("voice_message_volume")) + ) # Unlocks - released = Node.void('released') + released = Node.void("released") pdata.add_child(released) for item in achievements: - if item.type[:5] != 'item_': + if item.type[:5] != "item_": continue itemtype = int(item.type[5:]) - if game_config.get_bool('force_unlock_songs') and itemtype == 0: + if game_config.get_bool("force_unlock_songs") and itemtype == 0: # Don't echo unlocks when we're force unlocking, we'll do it later continue - info = Node.void('info') + info = Node.void("info") released.add_child(info) - info.add_child(Node.u8('type', itemtype)) - info.add_child(Node.u16('id', item.id)) - info.add_child(Node.u16('param', item.data.get_int('param'))) - info.add_child(Node.s32('insert_time', item.data.get_int('time'))) + info.add_child(Node.u8("type", itemtype)) + info.add_child(Node.u16("id", item.id)) + info.add_child(Node.u16("param", item.data.get_int("param"))) + info.add_child(Node.s32("insert_time", item.data.get_int("time"))) - if game_config.get_bool('force_unlock_songs'): + if game_config.get_bool("force_unlock_songs"): ids: Dict[int, int] = {} songs = self.data.local.music.get_all_songs(self.game, self.version) for song in songs: if song.id not in ids: ids[song.id] = 0 - if song.data.get_int('difficulty') > 0: + if song.data.get_int("difficulty") > 0: ids[song.id] = ids[song.id] | (1 << song.chart) for songid in ids: if ids[songid] == 0: continue - info = Node.void('info') + info = Node.void("info") released.add_child(info) - info.add_child(Node.u8('type', 0)) - info.add_child(Node.u16('id', songid)) - info.add_child(Node.u16('param', ids[songid])) - info.add_child(Node.s32('insert_time', Time.now())) + info.add_child(Node.u8("type", 0)) + info.add_child(Node.u16("id", songid)) + info.add_child(Node.u16("param", ids[songid])) + info.add_child(Node.s32("insert_time", Time.now())) # Announcements - announce = Node.void('announce') + announce = Node.void("announce") pdata.add_child(announce) for announcement in achievements: - if announcement.type[:13] != 'announcement_': + if announcement.type[:13] != "announcement_": continue announcementtype = int(announcement.type[13:]) - info = Node.void('info') + info = Node.void("info") announce.add_child(info) - info.add_child(Node.u8('type', announcementtype)) - info.add_child(Node.u16('id', announcement.id)) - info.add_child(Node.u16('param', announcement.data.get_int('param'))) - info.add_child(Node.bool('bneedannounce', announcement.data.get_bool('need'))) + info.add_child(Node.u8("type", announcementtype)) + info.add_child(Node.u16("id", announcement.id)) + info.add_child(Node.u16("param", announcement.data.get_int("param"))) + info.add_child( + Node.bool("bneedannounce", announcement.data.get_bool("need")) + ) # Dojo ranking return - dojo = Node.void('dojo') + dojo = Node.void("dojo") pdata.add_child(dojo) for entry in achievements: - if entry.type != 'dojo': + if entry.type != "dojo": continue - rec = Node.void('rec') + rec = Node.void("rec") dojo.add_child(rec) - rec.add_child(Node.s32('class', entry.id)) - rec.add_child(Node.s32('clear_type', entry.data.get_int('clear_type'))) - rec.add_child(Node.s32('total_ar', entry.data.get_int('ar'))) - rec.add_child(Node.s32('total_score', entry.data.get_int('score'))) - rec.add_child(Node.s32('play_count', entry.data.get_int('plays'))) - rec.add_child(Node.s32('last_play_time', entry.data.get_int('play_timestamp'))) - rec.add_child(Node.s32('record_update_time', entry.data.get_int('record_timestamp'))) - rec.add_child(Node.s32('rank', 0)) + rec.add_child(Node.s32("class", entry.id)) + rec.add_child(Node.s32("clear_type", entry.data.get_int("clear_type"))) + rec.add_child(Node.s32("total_ar", entry.data.get_int("ar"))) + rec.add_child(Node.s32("total_score", entry.data.get_int("score"))) + rec.add_child(Node.s32("play_count", entry.data.get_int("plays"))) + rec.add_child( + Node.s32("last_play_time", entry.data.get_int("play_timestamp")) + ) + rec.add_child( + Node.s32("record_update_time", entry.data.get_int("record_timestamp")) + ) + rec.add_child(Node.s32("rank", 0)) # Player Parameters - player_param = Node.void('player_param') + player_param = Node.void("player_param") pdata.add_child(player_param) for param in achievements: - if param.type[:13] != 'player_param_': + if param.type[:13] != "player_param_": continue itemtype = int(param.type[13:]) - itemnode = Node.void('item') + itemnode = Node.void("item") player_param.add_child(itemnode) - itemnode.add_child(Node.s32('type', itemtype)) - itemnode.add_child(Node.s32('bank', param.id)) - itemnode.add_child(Node.s32_array('data', param.data.get_int_array('data', 256))) + itemnode.add_child(Node.s32("type", itemtype)) + itemnode.add_child(Node.s32("bank", param.id)) + itemnode.add_child( + Node.s32_array("data", param.data.get_int_array("data", 256)) + ) # Shop score for players self._add_shop_score(pdata) # My List data - mylist = Node.void('mylist') + mylist = Node.void("mylist") pdata.add_child(mylist) - listdata = Node.void('list') + listdata = Node.void("list") mylist.add_child(listdata) - listdata.add_child(Node.s16('idx', 0)) - listdata.add_child(Node.s16_array('mlst', profile.get_int_array('favorites', 30, [-1] * 30))) + listdata.add_child(Node.s16("idx", 0)) + listdata.add_child( + Node.s16_array("mlst", profile.get_int_array("favorites", 30, [-1] * 30)) + ) # Minigame settings - minigame = Node.void('minigame') + minigame = Node.void("minigame") pdata.add_child(minigame) - minigame.add_child(Node.s8('mgid', profile.get_int('mgid'))) - minigame.add_child(Node.s32('sc', profile.get_int('mgsc'))) + minigame.add_child(Node.s8("mgid", profile.get_int("mgid"))) + minigame.add_child(Node.s32("sc", profile.get_int("mgsc"))) # Derby settings - derby = Node.void('derby') + derby = Node.void("derby") pdata.add_child(derby) - derby.add_child(Node.bool('is_open', False)) + derby.add_child(Node.bool("is_open", False)) # Music rank points - music_rank_point = Node.void('music_rank_point') + music_rank_point = Node.void("music_rank_point") pdata.add_child(music_rank_point) # yurukome list stuff - yurukome_list = Node.void('yurukome_list') + yurukome_list = Node.void("yurukome_list") pdata.add_child(yurukome_list) for entry in achievements: - if entry.type != 'yurukome': + if entry.type != "yurukome": continue - yurukome = Node.void('yurukome') + yurukome = Node.void("yurukome") yurukome_list.add_child(yurukome) - yurukome.add_child(Node.s32('yurukome_id', entry.id)) + yurukome.add_child(Node.s32("yurukome_id", entry.id)) # My course mode - mycourse = Node.void('mycourse') - mycoursedict = profile.get_dict('mycourse') + mycourse = Node.void("mycourse") + mycoursedict = profile.get_dict("mycourse") pdata.add_child(mycourse) - mycourse.add_child(Node.s16('mycourse_id', 1)) - mycourse.add_child(Node.s32('music_id_1', mycoursedict.get_int('music_id_1', -1))) - mycourse.add_child(Node.s16('note_grade_1', mycoursedict.get_int('note_grade_1', -1))) - mycourse.add_child(Node.s32('score_1', mycoursedict.get_int('score_1', -1))) - mycourse.add_child(Node.s32('music_id_2', mycoursedict.get_int('music_id_2', -1))) - mycourse.add_child(Node.s16('note_grade_2', mycoursedict.get_int('note_grade_2', -1))) - mycourse.add_child(Node.s32('score_2', mycoursedict.get_int('score_2', -1))) - mycourse.add_child(Node.s32('music_id_3', mycoursedict.get_int('music_id_3', -1))) - mycourse.add_child(Node.s16('note_grade_3', mycoursedict.get_int('note_grade_3', -1))) - mycourse.add_child(Node.s32('score_3', mycoursedict.get_int('score_3', -1))) - mycourse.add_child(Node.s32('music_id_4', mycoursedict.get_int('music_id_4', -1))) - mycourse.add_child(Node.s16('note_grade_4', mycoursedict.get_int('note_grade_4', -1))) - mycourse.add_child(Node.s32('score_4', mycoursedict.get_int('score_4', -1))) - mycourse.add_child(Node.s32('insert_time', mycoursedict.get_int('insert_time', -1))) - mycourse.add_child(Node.s32('def_music_id_1', -1)) - mycourse.add_child(Node.s16('def_note_grade_1', -1)) - mycourse.add_child(Node.s32('def_music_id_2', -1)) - mycourse.add_child(Node.s16('def_note_grade_2', -1)) - mycourse.add_child(Node.s32('def_music_id_3', -1)) - mycourse.add_child(Node.s16('def_note_grade_3', -1)) - mycourse.add_child(Node.s32('def_music_id_4', -1)) - mycourse.add_child(Node.s16('def_note_grade_4', -1)) + mycourse.add_child(Node.s16("mycourse_id", 1)) + mycourse.add_child( + Node.s32("music_id_1", mycoursedict.get_int("music_id_1", -1)) + ) + mycourse.add_child( + Node.s16("note_grade_1", mycoursedict.get_int("note_grade_1", -1)) + ) + mycourse.add_child(Node.s32("score_1", mycoursedict.get_int("score_1", -1))) + mycourse.add_child( + Node.s32("music_id_2", mycoursedict.get_int("music_id_2", -1)) + ) + mycourse.add_child( + Node.s16("note_grade_2", mycoursedict.get_int("note_grade_2", -1)) + ) + mycourse.add_child(Node.s32("score_2", mycoursedict.get_int("score_2", -1))) + mycourse.add_child( + Node.s32("music_id_3", mycoursedict.get_int("music_id_3", -1)) + ) + mycourse.add_child( + Node.s16("note_grade_3", mycoursedict.get_int("note_grade_3", -1)) + ) + mycourse.add_child(Node.s32("score_3", mycoursedict.get_int("score_3", -1))) + mycourse.add_child( + Node.s32("music_id_4", mycoursedict.get_int("music_id_4", -1)) + ) + mycourse.add_child( + Node.s16("note_grade_4", mycoursedict.get_int("note_grade_4", -1)) + ) + mycourse.add_child(Node.s32("score_4", mycoursedict.get_int("score_4", -1))) + mycourse.add_child( + Node.s32("insert_time", mycoursedict.get_int("insert_time", -1)) + ) + mycourse.add_child(Node.s32("def_music_id_1", -1)) + mycourse.add_child(Node.s16("def_note_grade_1", -1)) + mycourse.add_child(Node.s32("def_music_id_2", -1)) + mycourse.add_child(Node.s16("def_note_grade_2", -1)) + mycourse.add_child(Node.s32("def_music_id_3", -1)) + mycourse.add_child(Node.s16("def_note_grade_3", -1)) + mycourse.add_child(Node.s32("def_music_id_4", -1)) + mycourse.add_child(Node.s16("def_note_grade_4", -1)) # Friend course scores - mycourse_f = Node.void('mycourse_f') + mycourse_f = Node.void("mycourse_f") pdata.add_child(mycourse_f) for link in links: - if link.type != 'rival': + if link.type != "rival": continue if link.other_userid not in rprofiles: @@ -630,109 +780,169 @@ class ReflecBeatVolzza2(ReflecBeatVolzzaBase): rprofiles[link.other_userid] = rprofile else: rprofile = rprofiles[link.other_userid] - mycoursedict = rprofile.get_dict('mycourse') + mycoursedict = rprofile.get_dict("mycourse") - rec = Node.void('rec') + rec = Node.void("rec") mycourse_f.add_child(rec) - rec.add_child(Node.s32('rival_id', rprofile.extid)) - rec.add_child(Node.s16('mycourse_id', 1)) - rec.add_child(Node.s32('music_id_1', mycoursedict.get_int('music_id_1', -1))) - rec.add_child(Node.s16('note_grade_1', mycoursedict.get_int('note_grade_1', -1))) - rec.add_child(Node.s32('score_1', mycoursedict.get_int('score_1', -1))) - rec.add_child(Node.s32('music_id_2', mycoursedict.get_int('music_id_2', -1))) - rec.add_child(Node.s16('note_grade_2', mycoursedict.get_int('note_grade_2', -1))) - rec.add_child(Node.s32('score_2', mycoursedict.get_int('score_2', -1))) - rec.add_child(Node.s32('music_id_3', mycoursedict.get_int('music_id_3', -1))) - rec.add_child(Node.s16('note_grade_3', mycoursedict.get_int('note_grade_3', -1))) - rec.add_child(Node.s32('score_3', mycoursedict.get_int('score_3', -1))) - rec.add_child(Node.s32('music_id_4', mycoursedict.get_int('music_id_4', -1))) - rec.add_child(Node.s16('note_grade_4', mycoursedict.get_int('note_grade_4', -1))) - rec.add_child(Node.s32('score_4', mycoursedict.get_int('score_4', -1))) - rec.add_child(Node.s32('insert_time', mycoursedict.get_int('insert_time', -1))) + rec.add_child(Node.s32("rival_id", rprofile.extid)) + rec.add_child(Node.s16("mycourse_id", 1)) + rec.add_child( + Node.s32("music_id_1", mycoursedict.get_int("music_id_1", -1)) + ) + rec.add_child( + Node.s16("note_grade_1", mycoursedict.get_int("note_grade_1", -1)) + ) + rec.add_child(Node.s32("score_1", mycoursedict.get_int("score_1", -1))) + rec.add_child( + Node.s32("music_id_2", mycoursedict.get_int("music_id_2", -1)) + ) + rec.add_child( + Node.s16("note_grade_2", mycoursedict.get_int("note_grade_2", -1)) + ) + rec.add_child(Node.s32("score_2", mycoursedict.get_int("score_2", -1))) + rec.add_child( + Node.s32("music_id_3", mycoursedict.get_int("music_id_3", -1)) + ) + rec.add_child( + Node.s16("note_grade_3", mycoursedict.get_int("note_grade_3", -1)) + ) + rec.add_child(Node.s32("score_3", mycoursedict.get_int("score_3", -1))) + rec.add_child( + Node.s32("music_id_4", mycoursedict.get_int("music_id_4", -1)) + ) + rec.add_child( + Node.s16("note_grade_4", mycoursedict.get_int("note_grade_4", -1)) + ) + rec.add_child(Node.s32("score_4", mycoursedict.get_int("score_4", -1))) + rec.add_child( + Node.s32("insert_time", mycoursedict.get_int("insert_time", -1)) + ) return root - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: game_config = self.get_game_config() newprofile = oldprofile.clone() # Save base player profile info - newprofile.replace_int('lid', ID.parse_machine_id(request.child_value('pdata/account/lid'))) - newprofile.replace_str('name', request.child_value('pdata/base/name')) - newprofile.replace_int('mg', request.child_value('pdata/base/mg')) - newprofile.replace_int('ap', request.child_value('pdata/base/ap')) - newprofile.replace_int('uattr', request.child_value('pdata/base/uattr')) - newprofile.replace_int('money', request.child_value('pdata/base/money')) - newprofile.replace_int('class', request.child_value('pdata/base/class')) - newprofile.replace_int('class_ar', request.child_value('pdata/base/class_ar')) - newprofile.replace_int('skill_point', request.child_value('pdata/base/skill_point')) - newprofile.replace_int('mgid', request.child_value('pdata/minigame/mgid')) - newprofile.replace_int('mgsc', request.child_value('pdata/minigame/sc')) - newprofile.replace_int_array('favorites', 30, request.child_value('pdata/mylist/list/mlst')) + newprofile.replace_int( + "lid", ID.parse_machine_id(request.child_value("pdata/account/lid")) + ) + newprofile.replace_str("name", request.child_value("pdata/base/name")) + newprofile.replace_int("mg", request.child_value("pdata/base/mg")) + newprofile.replace_int("ap", request.child_value("pdata/base/ap")) + newprofile.replace_int("uattr", request.child_value("pdata/base/uattr")) + newprofile.replace_int("money", request.child_value("pdata/base/money")) + newprofile.replace_int("class", request.child_value("pdata/base/class")) + newprofile.replace_int("class_ar", request.child_value("pdata/base/class_ar")) + newprofile.replace_int( + "skill_point", request.child_value("pdata/base/skill_point") + ) + newprofile.replace_int("mgid", request.child_value("pdata/minigame/mgid")) + newprofile.replace_int("mgsc", request.child_value("pdata/minigame/sc")) + newprofile.replace_int_array( + "favorites", 30, request.child_value("pdata/mylist/list/mlst") + ) # Save player config - configdict = newprofile.get_dict('config') - config = request.child('pdata/config') + configdict = newprofile.get_dict("config") + config = request.child("pdata/config") if config: - configdict.replace_int('msel_bgm', config.child_value('msel_bgm')) - configdict.replace_int('narrowdown_type', config.child_value('narrowdown_type')) - configdict.replace_int('icon_id', config.child_value('icon_id')) - configdict.replace_int('byword_0', config.child_value('byword_0')) - configdict.replace_int('byword_1', config.child_value('byword_1')) - configdict.replace_bool('is_auto_byword_0', config.child_value('is_auto_byword_0')) - configdict.replace_bool('is_auto_byword_1', config.child_value('is_auto_byword_1')) - configdict.replace_int('mrec_type', config.child_value('mrec_type')) - configdict.replace_int('tab_sel', config.child_value('tab_sel')) - configdict.replace_int('card_disp', config.child_value('card_disp')) - configdict.replace_int('score_tab_disp', config.child_value('score_tab_disp')) - configdict.replace_int('last_music_id', config.child_value('last_music_id')) - configdict.replace_int('last_note_grade', config.child_value('last_note_grade')) - configdict.replace_int('sort_type', config.child_value('sort_type')) - configdict.replace_int('rival_panel_type', config.child_value('rival_panel_type')) - configdict.replace_int('random_entry_work', config.child_value('random_entry_work')) - configdict.replace_int('custom_folder_work', config.child_value('custom_folder_work')) - configdict.replace_int('folder_type', config.child_value('folder_type')) - configdict.replace_int('folder_lamp_type', config.child_value('folder_lamp_type')) - configdict.replace_bool('is_tweet', config.child_value('is_tweet')) - configdict.replace_bool('is_link_twitter', config.child_value('is_link_twitter')) - newprofile.replace_dict('config', configdict) + configdict.replace_int("msel_bgm", config.child_value("msel_bgm")) + configdict.replace_int( + "narrowdown_type", config.child_value("narrowdown_type") + ) + configdict.replace_int("icon_id", config.child_value("icon_id")) + configdict.replace_int("byword_0", config.child_value("byword_0")) + configdict.replace_int("byword_1", config.child_value("byword_1")) + configdict.replace_bool( + "is_auto_byword_0", config.child_value("is_auto_byword_0") + ) + configdict.replace_bool( + "is_auto_byword_1", config.child_value("is_auto_byword_1") + ) + configdict.replace_int("mrec_type", config.child_value("mrec_type")) + configdict.replace_int("tab_sel", config.child_value("tab_sel")) + configdict.replace_int("card_disp", config.child_value("card_disp")) + configdict.replace_int( + "score_tab_disp", config.child_value("score_tab_disp") + ) + configdict.replace_int("last_music_id", config.child_value("last_music_id")) + configdict.replace_int( + "last_note_grade", config.child_value("last_note_grade") + ) + configdict.replace_int("sort_type", config.child_value("sort_type")) + configdict.replace_int( + "rival_panel_type", config.child_value("rival_panel_type") + ) + configdict.replace_int( + "random_entry_work", config.child_value("random_entry_work") + ) + configdict.replace_int( + "custom_folder_work", config.child_value("custom_folder_work") + ) + configdict.replace_int("folder_type", config.child_value("folder_type")) + configdict.replace_int( + "folder_lamp_type", config.child_value("folder_lamp_type") + ) + configdict.replace_bool("is_tweet", config.child_value("is_tweet")) + configdict.replace_bool( + "is_link_twitter", config.child_value("is_link_twitter") + ) + newprofile.replace_dict("config", configdict) # Save player custom settings - customdict = newprofile.get_dict('custom') - custom = request.child('pdata/custom') + customdict = newprofile.get_dict("custom") + custom = request.child("pdata/custom") if custom: - customdict.replace_int('st_shot', custom.child_value('st_shot')) - customdict.replace_int('st_frame', custom.child_value('st_frame')) - customdict.replace_int('st_expl', custom.child_value('st_expl')) - customdict.replace_int('st_bg', custom.child_value('st_bg')) - customdict.replace_int('st_shot_vol', custom.child_value('st_shot_vol')) - customdict.replace_int('st_bg_bri', custom.child_value('st_bg_bri')) - customdict.replace_int('st_obj_size', custom.child_value('st_obj_size')) - customdict.replace_int('st_jr_gauge', custom.child_value('st_jr_gauge')) - customdict.replace_int('st_clr_gauge', custom.child_value('st_clr_gauge')) - customdict.replace_int('st_gr_gauge_type', custom.child_value('st_gr_gauge_type')) - customdict.replace_int('voice_message_set', custom.child_value('voice_message_set')) - customdict.replace_int('same_time_note_disp', custom.child_value('same_time_note_disp')) - customdict.replace_int('st_score_disp_type', custom.child_value('st_score_disp_type')) - customdict.replace_int('st_bonus_type', custom.child_value('st_bonus_type')) - customdict.replace_int('st_rivalnote_type', custom.child_value('st_rivalnote_type')) - customdict.replace_int('st_topassist_type', custom.child_value('st_topassist_type')) - customdict.replace_int('high_speed', custom.child_value('high_speed')) - customdict.replace_int('st_hazard', custom.child_value('st_hazard')) - customdict.replace_int('st_clr_cond', custom.child_value('st_clr_cond')) - customdict.replace_int('voice_message_volume', custom.child_value('voice_message_volume')) - newprofile.replace_dict('custom', customdict) + customdict.replace_int("st_shot", custom.child_value("st_shot")) + customdict.replace_int("st_frame", custom.child_value("st_frame")) + customdict.replace_int("st_expl", custom.child_value("st_expl")) + customdict.replace_int("st_bg", custom.child_value("st_bg")) + customdict.replace_int("st_shot_vol", custom.child_value("st_shot_vol")) + customdict.replace_int("st_bg_bri", custom.child_value("st_bg_bri")) + customdict.replace_int("st_obj_size", custom.child_value("st_obj_size")) + customdict.replace_int("st_jr_gauge", custom.child_value("st_jr_gauge")) + customdict.replace_int("st_clr_gauge", custom.child_value("st_clr_gauge")) + customdict.replace_int( + "st_gr_gauge_type", custom.child_value("st_gr_gauge_type") + ) + customdict.replace_int( + "voice_message_set", custom.child_value("voice_message_set") + ) + customdict.replace_int( + "same_time_note_disp", custom.child_value("same_time_note_disp") + ) + customdict.replace_int( + "st_score_disp_type", custom.child_value("st_score_disp_type") + ) + customdict.replace_int("st_bonus_type", custom.child_value("st_bonus_type")) + customdict.replace_int( + "st_rivalnote_type", custom.child_value("st_rivalnote_type") + ) + customdict.replace_int( + "st_topassist_type", custom.child_value("st_topassist_type") + ) + customdict.replace_int("high_speed", custom.child_value("high_speed")) + customdict.replace_int("st_hazard", custom.child_value("st_hazard")) + customdict.replace_int("st_clr_cond", custom.child_value("st_clr_cond")) + customdict.replace_int( + "voice_message_volume", custom.child_value("voice_message_volume") + ) + newprofile.replace_dict("custom", customdict) # Save player parameter info - params = request.child('pdata/player_param') + params = request.child("pdata/player_param") if params: for child in params.children: - if child.name != 'item': + if child.name != "item": continue - item_type = child.child_value('type') - bank = child.child_value('bank') - data = child.child_value('data') + item_type = child.child_value("type") + bank = child.child_value("bank") + data = child.child_value("data") while len(data) < 256: data.append(0) self.data.local.user.put_achievement( @@ -740,55 +950,57 @@ class ReflecBeatVolzza2(ReflecBeatVolzzaBase): self.version, userid, bank, - f'player_param_{item_type}', + f"player_param_{item_type}", { - 'data': data, + "data": data, }, ) # Save player episode info - episode = request.child('pdata/episode') + episode = request.child("pdata/episode") if episode: for child in episode.children: - if child.name != 'info': + if child.name != "info": continue # I assume this is copypasta, but I want to be sure - extid = child.child_value('user_id') + extid = child.child_value("user_id") if extid != newprofile.extid: - raise Exception(f'Unexpected user ID, got {extid} expecting {newprofile.extid}') + raise Exception( + f"Unexpected user ID, got {extid} expecting {newprofile.extid}" + ) - episode_type = child.child_value('type') - episode_value0 = child.child_value('value0') - episode_value1 = child.child_value('value1') - episode_text = child.child_value('text') - episode_time = child.child_value('time') + episode_type = child.child_value("type") + episode_value0 = child.child_value("value0") + episode_value1 = child.child_value("value1") + episode_text = child.child_value("text") + episode_time = child.child_value("time") self.data.local.user.put_achievement( self.game, self.version, userid, episode_type, - 'episode', + "episode", { - 'value0': episode_value0, - 'value1': episode_value1, - 'text': episode_text, - 'time': episode_time, + "value0": episode_value0, + "value1": episode_value1, + "text": episode_text, + "time": episode_time, }, ) # Save released info - released = request.child('pdata/released') + released = request.child("pdata/released") if released: for child in released.children: - if child.name != 'info': + if child.name != "info": continue - item_id = child.child_value('id') - item_type = child.child_value('type') - param = child.child_value('param') - time = child.child_value('insert_time') or Time.now() - if game_config.get_bool('force_unlock_songs') and item_type == 0: + item_id = child.child_value("id") + item_type = child.child_value("type") + param = child.child_value("param") + time = child.child_value("insert_time") or Time.now() + if game_config.get_bool("force_unlock_songs") and item_type == 0: # Don't save unlocks when we're force unlocking continue @@ -797,55 +1009,55 @@ class ReflecBeatVolzza2(ReflecBeatVolzzaBase): self.version, userid, item_id, - f'item_{item_type}', + f"item_{item_type}", { - 'param': param, - 'time': time, + "param": param, + "time": time, }, ) # Save announce info - announce = request.child('pdata/announce') + announce = request.child("pdata/announce") if announce: for child in announce.children: - if child.name != 'info': + if child.name != "info": continue - announce_id = child.child_value('id') - announce_type = child.child_value('type') - param = child.child_value('param') - need = child.child_value('bneedannounce') + announce_id = child.child_value("id") + announce_type = child.child_value("type") + param = child.child_value("param") + need = child.child_value("bneedannounce") self.data.local.user.put_achievement( self.game, self.version, userid, announce_id, - f'announcement_{announce_type}', + f"announcement_{announce_type}", { - 'param': param, - 'need': need, + "param": param, + "need": need, }, ) # Grab any new records set during this play session - songplays = request.child('pdata/stglog') + songplays = request.child("pdata/stglog") if songplays: for child in songplays.children: - if child.name != 'log': + if child.name != "log": continue - songid = child.child_value('mid') - chart = child.child_value('ng') - clear_type = child.child_value('ct') + songid = child.child_value("mid") + chart = child.child_value("ng") + clear_type = child.child_value("ct") if songid == 0 and chart == 0 and clear_type == -1: # Dummy song save during profile create continue - points = child.child_value('sc') - achievement_rate = child.child_value('ar') - param = child.child_value('param') - miss_count = child.child_value('jt_ms') - k_flag = child.child_value('k_flag') + points = child.child_value("sc") + achievement_rate = child.child_value("ar") + param = child.child_value("param") + miss_count = child.child_value("jt_ms") + k_flag = child.child_value("k_flag") # Param is some random bits along with the combo type combo_type = param & 0x3 @@ -867,14 +1079,16 @@ class ReflecBeatVolzza2(ReflecBeatVolzzaBase): ) # Grab any new rivals added during this play session - rivalnode = request.child('pdata/rival') + rivalnode = request.child("pdata/rival") if rivalnode: for child in rivalnode.children: - if child.name != 'r': + if child.name != "r": continue - extid = child.child_value('id') - other_userid = self.data.remote.user.from_extid(self.game, self.version, extid) + extid = child.child_value("id") + other_userid = self.data.remote.user.from_extid( + self.game, self.version, extid + ) if other_userid is None: continue @@ -882,111 +1096,130 @@ class ReflecBeatVolzza2(ReflecBeatVolzzaBase): self.game, self.version, userid, - 'rival', + "rival", other_userid, {}, ) # Save player dojo - dojo = request.child('pdata/dojo') + dojo = request.child("pdata/dojo") if dojo: - dojoid = dojo.child_value('class') - clear_type = dojo.child_value('clear_type') - ar = dojo.child_value('t_ar') - score = dojo.child_value('t_score') + dojoid = dojo.child_value("class") + clear_type = dojo.child_value("clear_type") + ar = dojo.child_value("t_ar") + score = dojo.child_value("t_score") # Figure out timestamp stuff - data = self.data.local.user.get_achievement( - self.game, - self.version, - userid, - dojoid, - 'dojo', - ) or ValidatedDict() + data = ( + self.data.local.user.get_achievement( + self.game, + self.version, + userid, + dojoid, + "dojo", + ) + or ValidatedDict() + ) - if ar >= data.get_int('ar'): + if ar >= data.get_int("ar"): # We set a new achievement rate, keep the new values record_time = Time.now() else: # We didn't, keep the old values for achievement rate, but # override score and clear_type only if they were better. - record_time = data.get_int('record_timestamp') - ar = data.get_int('ar') - score = max(score, data.get_int('score')) - clear_type = max(clear_type, data.get_int('clear_type')) + record_time = data.get_int("record_timestamp") + ar = data.get_int("ar") + score = max(score, data.get_int("score")) + clear_type = max(clear_type, data.get_int("clear_type")) play_time = Time.now() - plays = data.get_int('plays') + 1 + plays = data.get_int("plays") + 1 self.data.local.user.put_achievement( self.game, self.version, userid, dojoid, - 'dojo', + "dojo", { - 'clear_type': clear_type, - 'ar': ar, - 'score': score, - 'plays': plays, - 'play_timestamp': play_time, - 'record_timestamp': record_time, + "clear_type": clear_type, + "ar": ar, + "score": score, + "plays": plays, + "play_timestamp": play_time, + "record_timestamp": record_time, }, ) # Save yurukome stuff - yurukome_list = request.child('pdata/yurukome_list') + yurukome_list = request.child("pdata/yurukome_list") if yurukome_list: for child in yurukome_list.children: - if child.name != 'yurukome': + if child.name != "yurukome": continue - yurukome_id = child.child_value('yurukome_id') + yurukome_id = child.child_value("yurukome_id") self.data.local.user.put_achievement( self.game, self.version, userid, yurukome_id, - 'yurukome', + "yurukome", {}, ) # Save mycourse stuff - mycoursedict = newprofile.get_dict('mycourse') - mycourse = request.child('pdata/mycourse') + mycoursedict = newprofile.get_dict("mycourse") + mycourse = request.child("pdata/mycourse") if mycourse: # Only replace course if it was a new record score-wise. - score_1 = mycourse.child_value('score_1') - score_2 = mycourse.child_value('score_2') - score_3 = mycourse.child_value('score_3') - score_4 = mycourse.child_value('score_4') + score_1 = mycourse.child_value("score_1") + score_2 = mycourse.child_value("score_2") + score_3 = mycourse.child_value("score_3") + score_4 = mycourse.child_value("score_4") total = 0 for score in [score_1, score_2, score_3, score_4]: if score is not None and score >= 0: total = total + score oldtotal = ( - mycoursedict.get_int('score_1', 0) + - mycoursedict.get_int('score_2', 0) + - mycoursedict.get_int('score_3', 0) + - mycoursedict.get_int('score_4', 0) + mycoursedict.get_int("score_1", 0) + + mycoursedict.get_int("score_2", 0) + + mycoursedict.get_int("score_3", 0) + + mycoursedict.get_int("score_4", 0) ) if total >= oldtotal: - mycoursedict.replace_int('music_id_1', mycourse.child_value('music_id_1')) - mycoursedict.replace_int('note_grade_1', mycourse.child_value('note_grade_1')) - mycoursedict.replace_int('score_1', score_1) - mycoursedict.replace_int('music_id_2', mycourse.child_value('music_id_2')) - mycoursedict.replace_int('note_grade_2', mycourse.child_value('note_grade_2')) - mycoursedict.replace_int('score_2', score_2) - mycoursedict.replace_int('music_id_3', mycourse.child_value('music_id_3')) - mycoursedict.replace_int('note_grade_3', mycourse.child_value('note_grade_3')) - mycoursedict.replace_int('score_3', score_3) - mycoursedict.replace_int('music_id_4', mycourse.child_value('music_id_4')) - mycoursedict.replace_int('note_grade_4', mycourse.child_value('note_grade_4')) - mycoursedict.replace_int('score_4', score_4) - mycoursedict.replace_int('insert_time', Time.now()) - newprofile.replace_dict('mycourse', mycoursedict) + mycoursedict.replace_int( + "music_id_1", mycourse.child_value("music_id_1") + ) + mycoursedict.replace_int( + "note_grade_1", mycourse.child_value("note_grade_1") + ) + mycoursedict.replace_int("score_1", score_1) + mycoursedict.replace_int( + "music_id_2", mycourse.child_value("music_id_2") + ) + mycoursedict.replace_int( + "note_grade_2", mycourse.child_value("note_grade_2") + ) + mycoursedict.replace_int("score_2", score_2) + mycoursedict.replace_int( + "music_id_3", mycourse.child_value("music_id_3") + ) + mycoursedict.replace_int( + "note_grade_3", mycourse.child_value("note_grade_3") + ) + mycoursedict.replace_int("score_3", score_3) + mycoursedict.replace_int( + "music_id_4", mycourse.child_value("music_id_4") + ) + mycoursedict.replace_int( + "note_grade_4", mycourse.child_value("note_grade_4") + ) + mycoursedict.replace_int("score_4", score_4) + mycoursedict.replace_int("insert_time", Time.now()) + newprofile.replace_dict("mycourse", mycoursedict) # Keep track of play statistics self.update_play_statistics(userid) diff --git a/bemani/backend/reflec/volzzabase.py b/bemani/backend/reflec/volzzabase.py index fc8f98c..dd09e1d 100644 --- a/bemani/backend/reflec/volzzabase.py +++ b/bemani/backend/reflec/volzzabase.py @@ -66,33 +66,37 @@ class ReflecBeatVolzzaBase(ReflecBeatBase): return self.COMBO_TYPE_FULL_COMBO if game_combo == self.GAME_COMBO_TYPE_FULL_COMBO_ALL_JUST: return self.COMBO_TYPE_FULL_COMBO_ALL_JUST - raise Exception(f'Invalid game_combo value {game_combo}') + raise Exception(f"Invalid game_combo value {game_combo}") def _add_event_info(self, root: Node) -> None: # Overridden in subclasses pass def _add_shop_score(self, root: Node) -> None: - shop_score = Node.void('shop_score') + shop_score = Node.void("shop_score") root.add_child(shop_score) - today = Node.void('today') + today = Node.void("today") shop_score.add_child(today) - yesterday = Node.void('yesterday') + yesterday = Node.void("yesterday") shop_score.add_child(yesterday) all_profiles = self.data.local.user.get_all_profiles(self.game, self.version) - all_attempts = self.data.local.music.get_all_attempts(self.game, self.version, timelimit=(Time.beginning_of_today() - Time.SECONDS_IN_DAY)) + all_attempts = self.data.local.music.get_all_attempts( + self.game, + self.version, + timelimit=(Time.beginning_of_today() - Time.SECONDS_IN_DAY), + ) machine = self.data.local.machine.get_machine(self.config.machine.pcbid) if machine.arcade is not None: lids = [ - machine.id for machine in self.data.local.machine.get_all_machines(machine.arcade) + machine.id + for machine in self.data.local.machine.get_all_machines(machine.arcade) ] else: lids = [machine.id] relevant_profiles = [ - profile for profile in all_profiles - if profile[1].get_int('lid', -1) in lids + profile for profile in all_profiles if profile[1].get_int("lid", -1) in lids ] for (rootnode, timeoffset) in [ @@ -101,10 +105,11 @@ class ReflecBeatVolzzaBase(ReflecBeatBase): ]: # Grab all attempts made in the relevant day relevant_attempts = [ - attempt for attempt in all_attempts + attempt + for attempt in all_attempts if ( - attempt[1].timestamp >= (Time.beginning_of_today() - timeoffset) and - attempt[1].timestamp <= (Time.end_of_today() - timeoffset) + attempt[1].timestamp >= (Time.beginning_of_today() - timeoffset) + and attempt[1].timestamp <= (Time.end_of_today() - timeoffset) ) ] @@ -120,7 +125,10 @@ class ReflecBeatVolzzaBase(ReflecBeatBase): scores_by_user[userid][attempt.id][attempt.chart] = attempt else: # If this attempt is better than the stored one, replace it - if scores_by_user[userid][attempt.id][attempt.chart].points < attempt.points: + if ( + scores_by_user[userid][attempt.id][attempt.chart].points + < attempt.points + ): scores_by_user[userid][attempt.id][attempt.chart] = attempt # Calculate points earned by user in the day @@ -129,52 +137,65 @@ class ReflecBeatVolzzaBase(ReflecBeatBase): points_by_user[userid] = 0 for mid in scores_by_user[userid]: for chart in scores_by_user[userid][mid]: - points_by_user[userid] = points_by_user[userid] + scores_by_user[userid][mid][chart].points + points_by_user[userid] = ( + points_by_user[userid] + + scores_by_user[userid][mid][chart].points + ) # Output that day's earned points for (userid, profile) in relevant_profiles: - data = Node.void('data') + data = Node.void("data") rootnode.add_child(data) - data.add_child(Node.s16('day_id', int((Time.now() - timeoffset) / Time.SECONDS_IN_DAY))) - data.add_child(Node.s32('user_id', profile.extid)) - data.add_child(Node.s16('icon_id', profile.get_dict('config').get_int('icon_id'))) - data.add_child(Node.s16('point', min(points_by_user.get(userid, 0), 32767))) - data.add_child(Node.s32('update_time', Time.now())) - data.add_child(Node.string('name', profile.get_str('name'))) + data.add_child( + Node.s16( + "day_id", int((Time.now() - timeoffset) / Time.SECONDS_IN_DAY) + ) + ) + data.add_child(Node.s32("user_id", profile.extid)) + data.add_child( + Node.s16("icon_id", profile.get_dict("config").get_int("icon_id")) + ) + data.add_child( + Node.s16("point", min(points_by_user.get(userid, 0), 32767)) + ) + data.add_child(Node.s32("update_time", Time.now())) + data.add_child(Node.string("name", profile.get_str("name"))) - rootnode.add_child(Node.s32('time', Time.beginning_of_today() - timeoffset)) + rootnode.add_child(Node.s32("time", Time.beginning_of_today() - timeoffset)) def handle_info_rb5_info_read_request(self, request: Node) -> Node: - root = Node.void('info') + root = Node.void("info") self._add_event_info(root) return root def handle_info_rb5_info_read_hit_chart_request(self, request: Node) -> Node: - version = request.child_value('ver') + version = request.child_value("ver") - root = Node.void('info') - root.add_child(Node.s32('ver', version)) - ranking = Node.void('ranking') + root = Node.void("info") + root.add_child(Node.s32("ver", version)) + ranking = Node.void("ranking") root.add_child(ranking) - def add_hitchart(name: str, start: int, end: int, hitchart: List[Tuple[int, int]]) -> None: + def add_hitchart( + name: str, start: int, end: int, hitchart: List[Tuple[int, int]] + ) -> None: base = Node.void(name) ranking.add_child(base) - base.add_child(Node.s32('bt', start)) - base.add_child(Node.s32('et', end)) - new = Node.void('new') + base.add_child(Node.s32("bt", start)) + base.add_child(Node.s32("et", end)) + new = Node.void("new") base.add_child(new) for (mid, plays) in hitchart: - d = Node.void('d') + d = Node.void("d") new.add_child(d) - d.add_child(Node.s16('mid', mid)) - d.add_child(Node.s32('cnt', plays)) + d.add_child(Node.s16("mid", mid)) + d.add_child(Node.s32("cnt", plays)) # Weekly hit chart add_hitchart( - 'weekly', + "weekly", Time.now() - Time.SECONDS_IN_WEEK, Time.now(), self.data.local.music.get_hit_chart(self.game, self.version, 1024, 7), @@ -182,7 +203,7 @@ class ReflecBeatVolzzaBase(ReflecBeatBase): # Monthly hit chart add_hitchart( - 'monthly', + "monthly", Time.now() - Time.SECONDS_IN_DAY * 30, Time.now(), self.data.local.music.get_hit_chart(self.game, self.version, 1024, 30), @@ -190,7 +211,7 @@ class ReflecBeatVolzzaBase(ReflecBeatBase): # All time hit chart add_hitchart( - 'total', + "total", Time.now() - Time.SECONDS_IN_DAY * 365, Time.now(), self.data.local.music.get_hit_chart(self.game, self.version, 1024, 365), @@ -199,13 +220,13 @@ class ReflecBeatVolzzaBase(ReflecBeatBase): return root def handle_info_rb5_info_read_shop_ranking_request(self, request: Node) -> Node: - start_music_id = request.child_value('min') - end_music_id = request.child_value('max') + start_music_id = request.child_value("min") + end_music_id = request.child_value("max") - root = Node.void('info') - shop_score = Node.void('shop_score') + root = Node.void("info") + shop_score = Node.void("shop_score") root.add_child(shop_score) - shop_score.add_child(Node.s32('time', Time.now())) + shop_score.add_child(Node.s32("time", Time.now())) profiles: Dict[UserID, Profile] = {} for songid in range(start_music_id, end_music_id + 1): @@ -233,31 +254,44 @@ class ReflecBeatVolzzaBase(ReflecBeatBase): profiles[userid] = self.get_any_profile(userid) profile = profiles[userid] - data = Node.void('data') + data = Node.void("data") shop_score.add_child(data) - data.add_child(Node.s32('rank', i + 1)) - data.add_child(Node.s16('music_id', songid)) - data.add_child(Node.s8('note_grade', score.chart)) - data.add_child(Node.s8('clear_type', self._db_to_game_clear_type(score.data.get_int('clear_type')))) - data.add_child(Node.s32('user_id', profile.extid)) - data.add_child(Node.s16('icon_id', profile.get_dict('config').get_int('icon_id'))) - data.add_child(Node.s32('score', score.points)) - data.add_child(Node.s32('time', score.timestamp)) - data.add_child(Node.string('name', profile.get_str('name'))) + data.add_child(Node.s32("rank", i + 1)) + data.add_child(Node.s16("music_id", songid)) + data.add_child(Node.s8("note_grade", score.chart)) + data.add_child( + Node.s8( + "clear_type", + self._db_to_game_clear_type( + score.data.get_int("clear_type") + ), + ) + ) + data.add_child(Node.s32("user_id", profile.extid)) + data.add_child( + Node.s16( + "icon_id", profile.get_dict("config").get_int("icon_id") + ) + ) + data.add_child(Node.s32("score", score.points)) + data.add_child(Node.s32("time", score.timestamp)) + data.add_child(Node.string("name", profile.get_str("name"))) return root def handle_lobby_rb5_lobby_entry_request(self, request: Node) -> Node: - root = Node.void('lobby') - root.add_child(Node.s32('interval', 120)) - root.add_child(Node.s32('interval_p', 120)) + root = Node.void("lobby") + root.add_child(Node.s32("interval", 120)) + root.add_child(Node.s32("interval_p", 120)) # Create a lobby entry for this user - extid = request.child_value('e/uid') + extid = request.child_value("e/uid") userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: profile = self.get_profile(userid) - info = self.data.local.lobby.get_play_session_info(self.game, self.version, userid) + info = self.data.local.lobby.get_play_session_info( + self.game, self.version, userid + ) if profile is None or info is None: return root @@ -266,61 +300,61 @@ class ReflecBeatVolzzaBase(ReflecBeatBase): self.version, userid, { - 'mid': request.child_value('e/mid'), - 'ng': request.child_value('e/ng'), - 'mopt': request.child_value('e/mopt'), - 'lid': request.child_value('e/lid'), - 'sn': request.child_value('e/sn'), - 'pref': request.child_value('e/pref'), - 'stg': request.child_value('e/stg'), - 'pside': request.child_value('e/pside'), - 'eatime': request.child_value('e/eatime'), - 'ga': request.child_value('e/ga'), - 'gp': request.child_value('e/gp'), - 'la': request.child_value('e/la'), - 'ver': request.child_value('e/ver'), - } + "mid": request.child_value("e/mid"), + "ng": request.child_value("e/ng"), + "mopt": request.child_value("e/mopt"), + "lid": request.child_value("e/lid"), + "sn": request.child_value("e/sn"), + "pref": request.child_value("e/pref"), + "stg": request.child_value("e/stg"), + "pside": request.child_value("e/pside"), + "eatime": request.child_value("e/eatime"), + "ga": request.child_value("e/ga"), + "gp": request.child_value("e/gp"), + "la": request.child_value("e/la"), + "ver": request.child_value("e/ver"), + }, ) lobby = self.data.local.lobby.get_lobby( self.game, self.version, userid, ) - root.add_child(Node.s32('eid', lobby.get_int('id'))) - e = Node.void('e') + root.add_child(Node.s32("eid", lobby.get_int("id"))) + e = Node.void("e") root.add_child(e) - e.add_child(Node.s32('eid', lobby.get_int('id'))) - e.add_child(Node.u16('mid', lobby.get_int('mid'))) - e.add_child(Node.u8('ng', lobby.get_int('ng'))) - e.add_child(Node.s32('uid', profile.extid)) - e.add_child(Node.s32('uattr', profile.get_int('uattr'))) - e.add_child(Node.string('pn', profile.get_str('name'))) - e.add_child(Node.s32('plyid', info.get_int('id'))) - e.add_child(Node.s16('mg', profile.get_int('mg'))) - e.add_child(Node.s32('mopt', lobby.get_int('mopt'))) - e.add_child(Node.string('lid', lobby.get_str('lid'))) - e.add_child(Node.string('sn', lobby.get_str('sn'))) - e.add_child(Node.u8('pref', lobby.get_int('pref'))) - e.add_child(Node.s8('stg', lobby.get_int('stg'))) - e.add_child(Node.s8('pside', lobby.get_int('pside'))) - e.add_child(Node.s16('eatime', lobby.get_int('eatime'))) - e.add_child(Node.u8_array('ga', lobby.get_int_array('ga', 4))) - e.add_child(Node.u16('gp', lobby.get_int('gp'))) - e.add_child(Node.u8_array('la', lobby.get_int_array('la', 4))) - e.add_child(Node.u8('ver', lobby.get_int('ver'))) + e.add_child(Node.s32("eid", lobby.get_int("id"))) + e.add_child(Node.u16("mid", lobby.get_int("mid"))) + e.add_child(Node.u8("ng", lobby.get_int("ng"))) + e.add_child(Node.s32("uid", profile.extid)) + e.add_child(Node.s32("uattr", profile.get_int("uattr"))) + e.add_child(Node.string("pn", profile.get_str("name"))) + e.add_child(Node.s32("plyid", info.get_int("id"))) + e.add_child(Node.s16("mg", profile.get_int("mg"))) + e.add_child(Node.s32("mopt", lobby.get_int("mopt"))) + e.add_child(Node.string("lid", lobby.get_str("lid"))) + e.add_child(Node.string("sn", lobby.get_str("sn"))) + e.add_child(Node.u8("pref", lobby.get_int("pref"))) + e.add_child(Node.s8("stg", lobby.get_int("stg"))) + e.add_child(Node.s8("pside", lobby.get_int("pside"))) + e.add_child(Node.s16("eatime", lobby.get_int("eatime"))) + e.add_child(Node.u8_array("ga", lobby.get_int_array("ga", 4))) + e.add_child(Node.u16("gp", lobby.get_int("gp"))) + e.add_child(Node.u8_array("la", lobby.get_int_array("la", 4))) + e.add_child(Node.u8("ver", lobby.get_int("ver"))) return root def handle_lobby_rb5_lobby_read_request(self, request: Node) -> Node: - root = Node.void('lobby') - root.add_child(Node.s32('interval', 120)) - root.add_child(Node.s32('interval_p', 120)) + root = Node.void("lobby") + root.add_child(Node.s32("interval", 120)) + root.add_child(Node.s32("interval_p", 120)) # Look up all lobbies matching the criteria specified - ver = request.child_value('var') - mg = request.child_value('m_grade') # noqa: F841 - extid = request.child_value('uid') - limit = request.child_value('max') + ver = request.child_value("var") + mg = request.child_value("m_grade") # noqa: F841 + extid = request.child_value("uid") + limit = request.child_value("max") userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: lobbies = self.data.local.lobby.get_all_lobbies(self.game, self.version) @@ -331,95 +365,99 @@ class ReflecBeatVolzzaBase(ReflecBeatBase): if user == userid: # If we have our own lobby, don't return it continue - if ver != lobby.get_int('ver'): + if ver != lobby.get_int("ver"): # Don't return lobby data for different versions continue profile = self.get_profile(user) - info = self.data.local.lobby.get_play_session_info(self.game, self.version, userid) + info = self.data.local.lobby.get_play_session_info( + self.game, self.version, userid + ) if profile is None or info is None: # No profile info, don't return this lobby return root - e = Node.void('e') + e = Node.void("e") root.add_child(e) - e.add_child(Node.s32('eid', lobby.get_int('id'))) - e.add_child(Node.u16('mid', lobby.get_int('mid'))) - e.add_child(Node.u8('ng', lobby.get_int('ng'))) - e.add_child(Node.s32('uid', profile.extid)) - e.add_child(Node.s32('uattr', profile.get_int('uattr'))) - e.add_child(Node.string('pn', profile.get_str('name'))) - e.add_child(Node.s32('plyid', info.get_int('id'))) - e.add_child(Node.s16('mg', profile.get_int('mg'))) - e.add_child(Node.s32('mopt', lobby.get_int('mopt'))) - e.add_child(Node.string('lid', lobby.get_str('lid'))) - e.add_child(Node.string('sn', lobby.get_str('sn'))) - e.add_child(Node.u8('pref', lobby.get_int('pref'))) - e.add_child(Node.s8('stg', lobby.get_int('stg'))) - e.add_child(Node.s8('pside', lobby.get_int('pside'))) - e.add_child(Node.s16('eatime', lobby.get_int('eatime'))) - e.add_child(Node.u8_array('ga', lobby.get_int_array('ga', 4))) - e.add_child(Node.u16('gp', lobby.get_int('gp'))) - e.add_child(Node.u8_array('la', lobby.get_int_array('la', 4))) - e.add_child(Node.u8('ver', lobby.get_int('ver'))) + e.add_child(Node.s32("eid", lobby.get_int("id"))) + e.add_child(Node.u16("mid", lobby.get_int("mid"))) + e.add_child(Node.u8("ng", lobby.get_int("ng"))) + e.add_child(Node.s32("uid", profile.extid)) + e.add_child(Node.s32("uattr", profile.get_int("uattr"))) + e.add_child(Node.string("pn", profile.get_str("name"))) + e.add_child(Node.s32("plyid", info.get_int("id"))) + e.add_child(Node.s16("mg", profile.get_int("mg"))) + e.add_child(Node.s32("mopt", lobby.get_int("mopt"))) + e.add_child(Node.string("lid", lobby.get_str("lid"))) + e.add_child(Node.string("sn", lobby.get_str("sn"))) + e.add_child(Node.u8("pref", lobby.get_int("pref"))) + e.add_child(Node.s8("stg", lobby.get_int("stg"))) + e.add_child(Node.s8("pside", lobby.get_int("pside"))) + e.add_child(Node.s16("eatime", lobby.get_int("eatime"))) + e.add_child(Node.u8_array("ga", lobby.get_int_array("ga", 4))) + e.add_child(Node.u16("gp", lobby.get_int("gp"))) + e.add_child(Node.u8_array("la", lobby.get_int_array("la", 4))) + e.add_child(Node.u8("ver", lobby.get_int("ver"))) limit = limit - 1 return root def handle_lobby_rb5_lobby_delete_entry_request(self, request: Node) -> Node: - eid = request.child_value('eid') + eid = request.child_value("eid") self.data.local.lobby.destroy_lobby(eid) - return Node.void('lobby') + return Node.void("lobby") def handle_pcb_rb5_pcb_boot_request(self, request: Node) -> Node: - shop_id = ID.parse_machine_id(request.child_value('lid')) + shop_id = ID.parse_machine_id(request.child_value("lid")) machine = self.get_machine_by_id(shop_id) if machine is not None: machine_name = machine.name - close = machine.data.get_bool('close') - hour = machine.data.get_int('hour') - minute = machine.data.get_int('minute') + close = machine.data.get_bool("close") + hour = machine.data.get_int("hour") + minute = machine.data.get_int("minute") else: - machine_name = '' + machine_name = "" close = False hour = 0 minute = 0 - root = Node.void('pcb') - sinfo = Node.void('sinfo') + root = Node.void("pcb") + sinfo = Node.void("sinfo") root.add_child(sinfo) - sinfo.add_child(Node.string('nm', machine_name)) - sinfo.add_child(Node.bool('cl_enbl', close)) - sinfo.add_child(Node.u8('cl_h', hour)) - sinfo.add_child(Node.u8('cl_m', minute)) - sinfo.add_child(Node.bool('shop_flag', True)) + sinfo.add_child(Node.string("nm", machine_name)) + sinfo.add_child(Node.bool("cl_enbl", close)) + sinfo.add_child(Node.u8("cl_h", hour)) + sinfo.add_child(Node.u8("cl_m", minute)) + sinfo.add_child(Node.bool("shop_flag", True)) return root def handle_pcb_rb5_pcb_error_request(self, request: Node) -> Node: - return Node.void('pcb') + return Node.void("pcb") def handle_pcb_rb5_pcb_update_request(self, request: Node) -> Node: - return Node.void('pcb') + return Node.void("pcb") def handle_shop_rb5_shop_write_setting_request(self, request: Node) -> Node: - return Node.void('shop') + return Node.void("shop") def handle_shop_rb5_shop_write_info_request(self, request: Node) -> Node: - self.update_machine_name(request.child_value('sinfo/nm')) - self.update_machine_data({ - 'close': request.child_value('sinfo/cl_enbl'), - 'hour': request.child_value('sinfo/cl_h'), - 'minute': request.child_value('sinfo/cl_m'), - 'pref': request.child_value('sinfo/prf'), - }) - return Node.void('shop') + self.update_machine_name(request.child_value("sinfo/nm")) + self.update_machine_data( + { + "close": request.child_value("sinfo/cl_enbl"), + "hour": request.child_value("sinfo/cl_h"), + "minute": request.child_value("sinfo/cl_m"), + "pref": request.child_value("sinfo/prf"), + } + ) + return Node.void("shop") def handle_player_rb5_player_start_request(self, request: Node) -> Node: - root = Node.void('player') + root = Node.void("player") # Create a new play session based on info from the request - refid = request.child_value('rid') + refid = request.child_value("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: self.data.local.lobby.put_play_session_info( @@ -427,10 +465,10 @@ class ReflecBeatVolzzaBase(ReflecBeatBase): self.version, userid, { - 'ga': request.child_value('ga'), - 'gp': request.child_value('gp'), - 'la': request.child_value('la'), - 'pnid': request.child_value('pnid'), + "ga": request.child_value("ga"), + "gp": request.child_value("gp"), + "la": request.child_value("la"), + "pnid": request.child_value("pnid"), }, ) info = self.data.local.lobby.get_play_session_info( @@ -439,22 +477,22 @@ class ReflecBeatVolzzaBase(ReflecBeatBase): userid, ) if info is not None: - play_id = info.get_int('id') + play_id = info.get_int("id") else: play_id = 0 else: play_id = 0 # Session stuff, and resend global defaults - root.add_child(Node.s32('plyid', play_id)) - root.add_child(Node.u64('start_time', Time.now() * 1000)) + root.add_child(Node.s32("plyid", play_id)) + root.add_child(Node.u64("start_time", Time.now() * 1000)) self._add_event_info(root) return root def handle_player_rb5_player_end_request(self, request: Node) -> Node: # Destroy play session based on info from the request - refid = request.child_value('rid') + refid = request.child_value("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: # Kill any lingering lobbies by this user @@ -464,16 +502,18 @@ class ReflecBeatVolzzaBase(ReflecBeatBase): userid, ) if lobby is not None: - self.data.local.lobby.destroy_lobby(lobby.get_int('id')) - self.data.local.lobby.destroy_play_session_info(self.game, self.version, userid) + self.data.local.lobby.destroy_lobby(lobby.get_int("id")) + self.data.local.lobby.destroy_play_session_info( + self.game, self.version, userid + ) - return Node.void('player') + return Node.void("player") def handle_player_rb5_player_delete_request(self, request: Node) -> Node: - return Node.void('player') + return Node.void("player") def handle_player_rb5_player_succeed_request(self, request: Node) -> Node: - refid = request.child_value('rid') + refid = request.child_value("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: previous_version = self.previous_version() @@ -481,25 +521,25 @@ class ReflecBeatVolzzaBase(ReflecBeatBase): else: profile = None - root = Node.void('player') + root = Node.void("player") if profile is None: # Return empty succeed to say this is new - root.add_child(Node.string('name', '')) - root.add_child(Node.s32('grd', -1)) - root.add_child(Node.s32('ap', -1)) - root.add_child(Node.s32('uattr', 0)) + root.add_child(Node.string("name", "")) + root.add_child(Node.s32("grd", -1)) + root.add_child(Node.s32("ap", -1)) + root.add_child(Node.s32("uattr", 0)) else: # Return previous profile formatted to say this is data succession - root.add_child(Node.string('name', profile.get_str('name'))) - root.add_child(Node.s32('grd', profile.get_int('mg'))) # This is a guess - root.add_child(Node.s32('ap', profile.get_int('ap'))) - root.add_child(Node.s32('uattr', profile.get_int('uattr'))) + root.add_child(Node.string("name", profile.get_str("name"))) + root.add_child(Node.s32("grd", profile.get_int("mg"))) # This is a guess + root.add_child(Node.s32("ap", profile.get_int("ap"))) + root.add_child(Node.s32("uattr", profile.get_int("uattr"))) return root def handle_player_rb5_player_read_request(self, request: Node) -> Node: - refid = request.child_value('rid') + refid = request.child_value("rid") profile = self.get_profile_by_refid(refid) if profile: return profile - return Node.void('player') + return Node.void("player") diff --git a/bemani/backend/sdvx/base.py b/bemani/backend/sdvx/base.py index eda58d4..2d921ad 100644 --- a/bemani/backend/sdvx/base.py +++ b/bemani/backend/sdvx/base.py @@ -21,7 +21,9 @@ class SoundVoltexBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): CLEAR_TYPE_CLEAR: Final[int] = DBConstants.SDVX_CLEAR_TYPE_CLEAR CLEAR_TYPE_HARD_CLEAR: Final[int] = DBConstants.SDVX_CLEAR_TYPE_HARD_CLEAR CLEAR_TYPE_ULTIMATE_CHAIN: Final[int] = DBConstants.SDVX_CLEAR_TYPE_ULTIMATE_CHAIN - CLEAR_TYPE_PERFECT_ULTIMATE_CHAIN: Final[int] = DBConstants.SDVX_CLEAR_TYPE_PERFECT_ULTIMATE_CHAIN + CLEAR_TYPE_PERFECT_ULTIMATE_CHAIN: Final[ + int + ] = DBConstants.SDVX_CLEAR_TYPE_PERFECT_ULTIMATE_CHAIN GRADE_NO_PLAY: Final[int] = DBConstants.SDVX_GRADE_NO_PLAY GRADE_D: Final[int] = DBConstants.SDVX_GRADE_D @@ -41,7 +43,7 @@ class SoundVoltexBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): CHART_TYPE_INFINITE: Final[int] = 3 CHART_TYPE_MAXIMUM: Final[int] = 4 - def previous_version(self) -> Optional['SoundVoltexBase']: + def previous_version(self) -> Optional["SoundVoltexBase"]: """ Returns the previous version of the game, based on this game. Should be overridden. @@ -66,7 +68,9 @@ class SoundVoltexBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): # Now, return it return self.format_profile(userid, profile) - def new_profile_by_refid(self, refid: Optional[str], name: Optional[str], locid: Optional[int]) -> Node: + def new_profile_by_refid( + self, refid: Optional[str], name: Optional[str], locid: Optional[int] + ) -> Node: """ Given a RefID and an optional name, create a profile and then return a formatted profile node. Similar rationale to get_profile_by_refid. @@ -75,7 +79,7 @@ class SoundVoltexBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): return None if name is None: - name = 'NONAME' + name = "NONAME" # First, create and save the default profile userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -85,8 +89,8 @@ class SoundVoltexBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): refid, 0, { - 'name': name, - 'loc': locid, + "name": name, + "loc": locid, }, ) self.put_profile(userid, profile) @@ -97,9 +101,11 @@ class SoundVoltexBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): Base handler for a profile. Given a userid and a profile dictionary, return a Node representing a profile. Should be overridden. """ - return Node.void('game') + return Node.void("game") - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: """ Base handler for profile parsing. Given a request and an old profile, return a new profile that's been updated with the contents of the request. @@ -121,16 +127,18 @@ class SoundVoltexBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): }, } """ - all_attempts, remote_attempts = Parallel.execute([ - lambda: self.data.local.music.get_all_attempts( - game=self.game, - version=self.version, - ), - lambda: self.data.remote.music.get_clear_rates( - game=self.game, - version=self.version, - ) - ]) + all_attempts, remote_attempts = Parallel.execute( + [ + lambda: self.data.local.music.get_all_attempts( + game=self.game, + version=self.version, + ), + lambda: self.data.remote.music.get_clear_rates( + game=self.game, + version=self.version, + ), + ] + ) attempts: Dict[int, Dict[int, Dict[str, int]]] = {} for (_, attempt) in all_attempts: # Terrible temporary structure is terrible. @@ -138,26 +146,33 @@ class SoundVoltexBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): attempts[attempt.id] = {} if attempt.chart not in attempts[attempt.id]: attempts[attempt.id][attempt.chart] = { - 'total': 0, - 'clears': 0, - 'average': 0, + "total": 0, + "clears": 0, + "average": 0, } # We saw an attempt, keep the total attempts in sync. - attempts[attempt.id][attempt.chart]['average'] = int( + attempts[attempt.id][attempt.chart]["average"] = int( ( - (attempts[attempt.id][attempt.chart]['average'] * attempts[attempt.id][attempt.chart]['total']) + - attempt.points - ) / (attempts[attempt.id][attempt.chart]['total'] + 1) + ( + attempts[attempt.id][attempt.chart]["average"] + * attempts[attempt.id][attempt.chart]["total"] + ) + + attempt.points + ) + / (attempts[attempt.id][attempt.chart]["total"] + 1) ) - attempts[attempt.id][attempt.chart]['total'] += 1 + attempts[attempt.id][attempt.chart]["total"] += 1 - if attempt.data.get_int('clear_type', self.CLEAR_TYPE_NO_PLAY) in [self.CLEAR_TYPE_NO_PLAY, self.CLEAR_TYPE_FAILED]: + if attempt.data.get_int("clear_type", self.CLEAR_TYPE_NO_PLAY) in [ + self.CLEAR_TYPE_NO_PLAY, + self.CLEAR_TYPE_FAILED, + ]: # This attempt was a failure, so don't count it against clears of full combos continue # It was at least a clear - attempts[attempt.id][attempt.chart]['clears'] += 1 + attempts[attempt.id][attempt.chart]["clears"] += 1 # Merge in remote attempts for songid in remote_attempts: @@ -167,13 +182,17 @@ class SoundVoltexBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): for songchart in remote_attempts[songid]: if songchart not in attempts[songid]: attempts[songid][songchart] = { - 'total': 0, - 'clears': 0, - 'average': 0, + "total": 0, + "clears": 0, + "average": 0, } - attempts[songid][songchart]['total'] += remote_attempts[songid][songchart]['plays'] - attempts[songid][songchart]['clears'] += remote_attempts[songid][songchart]['clears'] + attempts[songid][songchart]["total"] += remote_attempts[songid][ + songchart + ]["plays"] + attempts[songid][songchart]["clears"] += remote_attempts[songid][ + songchart + ]["clears"] return attempts @@ -186,7 +205,7 @@ class SoundVoltexBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): clear_type: int, grade: int, combo: int, - stats: Optional[Dict[str, int]]=None, + stats: Optional[Dict[str, int]] = None, ) -> None: """ Given various pieces of a score, update the user's high score and score @@ -248,21 +267,23 @@ class SoundVoltexBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): scoredata = oldscore.data # Replace clear type and grade - scoredata.replace_int('clear_type', max(scoredata.get_int('clear_type'), clear_type)) - history.replace_int('clear_type', clear_type) - scoredata.replace_int('grade', max(scoredata.get_int('grade'), grade)) - history.replace_int('grade', grade) + scoredata.replace_int( + "clear_type", max(scoredata.get_int("clear_type"), clear_type) + ) + history.replace_int("clear_type", clear_type) + scoredata.replace_int("grade", max(scoredata.get_int("grade"), grade)) + history.replace_int("grade", grade) # If we have a combo, replace it - scoredata.replace_int('combo', max(scoredata.get_int('combo'), combo)) - history.replace_int('combo', combo) + scoredata.replace_int("combo", max(scoredata.get_int("combo"), combo)) + history.replace_int("combo", combo) # If we have play stats, replace it if stats is not None: if raised: # We have stats, and there's a new high score, update the stats - scoredata.replace_dict('stats', stats) - history.replace_dict('stats', stats) + scoredata.replace_dict("stats", stats) + history.replace_dict("stats", stats) # Look up where this score was earned lid = self.get_machine_id() diff --git a/bemani/backend/sdvx/booth.py b/bemani/backend/sdvx/booth.py index 039abdd..f953c44 100644 --- a/bemani/backend/sdvx/booth.py +++ b/bemani/backend/sdvx/booth.py @@ -14,7 +14,7 @@ class SoundVoltexBooth( SoundVoltexBase, ): - name: str = 'SOUND VOLTEX BOOTH' + name: str = "SOUND VOLTEX BOOTH" version: int = VersionConstants.SDVX_BOOTH GAME_LIMITED_LOCKED: Final[int] = 1 @@ -42,24 +42,24 @@ class SoundVoltexBooth( Return all of our front-end modifiably settings. """ return { - 'bools': [ + "bools": [ { - 'name': 'Disable Online Matching', - 'tip': 'Disable online matching between games.', - 'category': 'game_config', - 'setting': 'disable_matching', + "name": "Disable Online Matching", + "tip": "Disable online matching between games.", + "category": "game_config", + "setting": "disable_matching", }, { - 'name': 'Force Song Unlock', - 'tip': 'Force unlock all songs.', - 'category': 'game_config', - 'setting': 'force_unlock_songs', + "name": "Force Song Unlock", + "tip": "Force unlock all songs.", + "category": "game_config", + "setting": "force_unlock_songs", }, { - 'name': 'Force Appeal Card Unlock', - 'tip': 'Force unlock all appeal cards.', - 'category': 'game_config', - 'setting': 'force_unlock_cards', + "name": "Force Appeal Card Unlock", + "tip": "Force unlock all appeal cards.", + "category": "game_config", + "setting": "force_unlock_cards", }, ], } @@ -112,108 +112,108 @@ class SoundVoltexBooth( }[grade] def handle_game_exception_request(self, request: Node) -> Node: - return Node.void('game') + return Node.void("game") def handle_game_entry_s_request(self, request: Node) -> Node: - game = Node.void('game') + game = Node.void("game") # This should be created on the fly for a lobby that we're in. - game.add_child(Node.u32('entry_id', 1)) + game.add_child(Node.u32("entry_id", 1)) return game def handle_game_lounge_request(self, request: Node) -> Node: - game = Node.void('game') + game = Node.void("game") # Refresh interval in seconds. - game.add_child(Node.u32('interval', 10)) + game.add_child(Node.u32("interval", 10)) return game def handle_game_entry_e_request(self, request: Node) -> Node: # Lobby destroy method, eid attribute (u32) should be used # to destroy any open lobbies. - return Node.void('game') + return Node.void("game") def handle_game_frozen_request(self, request: Node) -> Node: - game = Node.void('game') - game.set_attribute('result', '0') + game = Node.void("game") + game.set_attribute("result", "0") return game def handle_game_shop_request(self, request: Node) -> Node: - self.update_machine_name(request.child_value('shopname')) + self.update_machine_name(request.child_value("shopname")) # Respond with number of milliseconds until next request - game = Node.void('game') - game.add_child(Node.u32('nxt_time', 1000 * 5 * 60)) + game = Node.void("game") + game.add_child(Node.u32("nxt_time", 1000 * 5 * 60)) return game def handle_game_common_request(self, request: Node) -> Node: - game = Node.void('game') - limited = Node.void('limited') + game = Node.void("game") + limited = Node.void("limited") game.add_child(limited) game_config = self.get_game_config() - if game_config.get_bool('force_unlock_songs'): + if game_config.get_bool("force_unlock_songs"): ids = set() songs = self.data.local.music.get_all_songs(self.game, self.version) for song in songs: - if song.data.get_int('limited') == self.GAME_LIMITED_LOCKED: + if song.data.get_int("limited") == self.GAME_LIMITED_LOCKED: ids.add(song.id) for songid in ids: - music = Node.void('music') + music = Node.void("music") limited.add_child(music) - music.set_attribute('id', str(songid)) - music.set_attribute('flag', str(self.GAME_LIMITED_UNLOCKED)) + music.set_attribute("id", str(songid)) + music.set_attribute("flag", str(self.GAME_LIMITED_UNLOCKED)) - event = Node.void('event') + event = Node.void("event") game.add_child(event) def enable_event(eid: int) -> None: - evt = Node.void('info') + evt = Node.void("info") event.add_child(evt) - evt.set_attribute('id', str(eid)) + evt.set_attribute("id", str(eid)) - if not game_config.get_bool('disable_matching'): + if not game_config.get_bool("disable_matching"): enable_event(3) # Matching enabled enable_event(9) # Rank Soukuu enable_event(13) # Year-end bonus - catalog = Node.void('catalog') + catalog = Node.void("catalog") game.add_child(catalog) songunlocks = self.data.local.game.get_items(self.game, self.version) for unlock in songunlocks: - if unlock.type != 'song_unlock': + if unlock.type != "song_unlock": continue - info = Node.void('info') + info = Node.void("info") catalog.add_child(info) - info.set_attribute('id', str(unlock.id)) - info.set_attribute('currency', str(self.GAME_CURRENCY_BLOCKS)) - info.set_attribute('price', str(unlock.data.get_int('blocks'))) + info.set_attribute("id", str(unlock.id)) + info.set_attribute("currency", str(self.GAME_CURRENCY_BLOCKS)) + info.set_attribute("price", str(unlock.data.get_int("blocks"))) - kacinfo = Node.void('kacinfo') + kacinfo = Node.void("kacinfo") game.add_child(kacinfo) - kacinfo.add_child(Node.u32('note00', 0)) - kacinfo.add_child(Node.u32('note01', 0)) - kacinfo.add_child(Node.u32('note02', 0)) - kacinfo.add_child(Node.u32('note10', 0)) - kacinfo.add_child(Node.u32('note11', 0)) - kacinfo.add_child(Node.u32('note12', 0)) - kacinfo.add_child(Node.u32('rabbeat0', 0)) - kacinfo.add_child(Node.u32('rabbeat1', 0)) + kacinfo.add_child(Node.u32("note00", 0)) + kacinfo.add_child(Node.u32("note01", 0)) + kacinfo.add_child(Node.u32("note02", 0)) + kacinfo.add_child(Node.u32("note10", 0)) + kacinfo.add_child(Node.u32("note11", 0)) + kacinfo.add_child(Node.u32("note12", 0)) + kacinfo.add_child(Node.u32("rabbeat0", 0)) + kacinfo.add_child(Node.u32("rabbeat1", 0)) return game def handle_game_hiscore_request(self, request: Node) -> Node: - game = Node.void('game') + game = Node.void("game") # Ranking system I think? for i in range(1, 21): - ranking = Node.void('ranking') + ranking = Node.void("ranking") game.add_child(ranking) - ranking.set_attribute('id', str(i)) + ranking.set_attribute("id", str(i)) - hiscore = Node.void('hiscore') + hiscore = Node.void("hiscore") game.add_child(hiscore) - hiscore.set_attribute('type', '1') + hiscore.set_attribute("type", "1") records = self.data.remote.music.get_all_records(self.game, self.version) @@ -228,44 +228,47 @@ class SoundVoltexBooth( records_by_id[score.id][score.chart] = record missing_users.append(userid) - users = {userid: profile for (userid, profile) in self.get_any_profiles(missing_users)} + users = { + userid: profile + for (userid, profile) in self.get_any_profiles(missing_users) + } # Output records for songid in records_by_id: - music = Node.void('music') + music = Node.void("music") hiscore.add_child(music) - music.set_attribute('id', str(songid)) + music.set_attribute("id", str(songid)) for chart in records_by_id[songid]: - note = Node.void('note') + note = Node.void("note") music.add_child(note) - note.set_attribute('type', str(chart)) + note.set_attribute("type", str(chart)) userid, score = records_by_id[songid][chart] - note.set_attribute('score', str(score.points)) - note.set_attribute('name', users[userid].get_str('name')) + note.set_attribute("score", str(score.points)) + note.set_attribute("name", users[userid].get_str("name")) return game def handle_game_new_request(self, request: Node) -> Node: - refid = request.attribute('refid') - name = request.attribute('name') - loc = ID.parse_machine_id(request.attribute('locid')) + refid = request.attribute("refid") + name = request.attribute("name") + loc = ID.parse_machine_id(request.attribute("locid")) self.new_profile_by_refid(refid, name, loc) - root = Node.void('game') + root = Node.void("game") return root def handle_game_load_request(self, request: Node) -> Node: - refid = request.attribute('dataid') + refid = request.attribute("dataid") root = self.get_profile_by_refid(refid) if root is None: - root = Node.void('game') - root.set_attribute('none', '1') + root = Node.void("game") + root.set_attribute("none", "1") return root def handle_game_save_request(self, request: Node) -> Node: - refid = request.attribute('refid') + refid = request.attribute("refid") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -281,10 +284,10 @@ class SoundVoltexBooth( if userid is not None and newprofile is not None: self.put_profile(userid, newprofile) - return Node.void('game') + return Node.void("game") def handle_game_load_m_request(self, request: Node) -> Node: - refid = request.attribute('dataid') + refid = request.attribute("dataid") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -305,27 +308,33 @@ class SoundVoltexBooth( scores_by_id[score.id][score.chart] = score # Output to the game - game = Node.void('game') + game = Node.void("game") for songid in scores_by_id: - music = Node.void('music') + music = Node.void("music") game.add_child(music) - music.set_attribute('music_id', str(songid)) + music.set_attribute("music_id", str(songid)) for chart in scores_by_id[songid]: - typenode = Node.void('type') + typenode = Node.void("type") music.add_child(typenode) - typenode.set_attribute('type_id', str(chart)) + typenode.set_attribute("type_id", str(chart)) score = scores_by_id[songid][chart] - typenode.set_attribute('score', str(score.points)) - typenode.set_attribute('cnt', str(score.plays)) - typenode.set_attribute('clear_type', str(self.__db_to_game_clear_type(score.data.get_int('clear_type')))) - typenode.set_attribute('score_grade', str(self.__db_to_game_grade(score.data.get_int('grade')))) + typenode.set_attribute("score", str(score.points)) + typenode.set_attribute("cnt", str(score.plays)) + typenode.set_attribute( + "clear_type", + str(self.__db_to_game_clear_type(score.data.get_int("clear_type"))), + ) + typenode.set_attribute( + "score_grade", + str(self.__db_to_game_grade(score.data.get_int("grade"))), + ) return game def handle_game_save_m_request(self, request: Node) -> Node: - refid = request.attribute('dataid') + refid = request.attribute("dataid") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -333,14 +342,14 @@ class SoundVoltexBooth( userid = None if userid is None: - return Node.void('game') + return Node.void("game") - musicid = int(request.attribute('music_id')) - chart = int(request.attribute('music_type')) - score = int(request.attribute('score')) - combo = int(request.attribute('max_chain')) - grade = self.__game_to_db_grade(int(request.attribute('score_grade'))) - clear_type = self.__game_to_db_clear_type(int(request.attribute('clear_type'))) + musicid = int(request.attribute("music_id")) + chart = int(request.attribute("music_type")) + score = int(request.attribute("score")) + combo = int(request.attribute("max_chain")) + grade = self.__game_to_db_grade(int(request.attribute("score_grade"))) + clear_type = self.__game_to_db_clear_type(int(request.attribute("clear_type"))) # Save the score self.update_score( @@ -354,10 +363,10 @@ class SoundVoltexBooth( ) # No response necessary - return Node.void('game') + return Node.void("game") def handle_game_buy_request(self, request: Node) -> Node: - refid = request.attribute('refid') + refid = request.attribute("refid") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -371,21 +380,26 @@ class SoundVoltexBooth( if userid is not None and profile is not None: # Look up packets and blocks - packet = profile.get_int('packet') - block = profile.get_int('block') + packet = profile.get_int("packet") + block = profile.get_int("block") # Add on any additional we earned this round - packet = packet + (request.child_value('earned_gamecoin_packet') or 0) - block = block + (request.child_value('earned_gamecoin_block') or 0) + packet = packet + (request.child_value("earned_gamecoin_packet") or 0) + block = block + (request.child_value("earned_gamecoin_block") or 0) # Look up the item to get the actual price and currency used - item = self.data.local.game.get_item(self.game, self.version, request.child_value('catalog_id'), 'song_unlock') + item = self.data.local.game.get_item( + self.game, + self.version, + request.child_value("catalog_id"), + "song_unlock", + ) if item is not None: - currency_type = request.child_value('currency_type') + currency_type = request.child_value("currency_type") if currency_type == self.GAME_CURRENCY_PACKETS: - if 'packets' in item: + if "packets" in item: # This is a valid purchase - newpacket = packet - item.get_int('packets') + newpacket = packet - item.get_int("packets") if newpacket < 0: result = 1 else: @@ -395,9 +409,9 @@ class SoundVoltexBooth( # Bad transaction result = 1 elif currency_type == self.GAME_CURRENCY_BLOCKS: - if 'blocks' in item: + if "blocks" in item: # This is a valid purchase - newblock = block - item.get_int('blocks') + newblock = block - item.get_int("blocks") if newblock < 0: result = 1 else: @@ -412,8 +426,8 @@ class SoundVoltexBooth( if result == 0: # Transaction is valid, update the profile with new packets and blocks - profile.replace_int('packet', packet) - profile.replace_int('block', block) + profile.replace_int("packet", packet) + profile.replace_int("block", block) self.put_profile(userid, profile) else: # Bad catalog ID @@ -424,97 +438,117 @@ class SoundVoltexBooth( block = 0 result = 1 - game = Node.void('game') - game.add_child(Node.u32('gamecoin_packet', packet)) - game.add_child(Node.u32('gamecoin_block', block)) - game.add_child(Node.s8('result', result)) + game = Node.void("game") + game.add_child(Node.u32("gamecoin_packet", packet)) + game.add_child(Node.u32("gamecoin_block", block)) + game.add_child(Node.s8("result", result)) return game def format_profile(self, userid: UserID, profile: Profile) -> Node: - game = Node.void('game') + game = Node.void("game") # Generic profile stuff - game.add_child(Node.string('name', profile.get_str('name'))) - game.add_child(Node.string('code', ID.format_extid(profile.extid))) - game.add_child(Node.u32('gamecoin_packet', profile.get_int('packet'))) - game.add_child(Node.u32('gamecoin_block', profile.get_int('block'))) - game.add_child(Node.u32('exp_point', profile.get_int('exp'))) - game.add_child(Node.u32('m_user_cnt', profile.get_int('m_user_cnt'))) + game.add_child(Node.string("name", profile.get_str("name"))) + game.add_child(Node.string("code", ID.format_extid(profile.extid))) + game.add_child(Node.u32("gamecoin_packet", profile.get_int("packet"))) + game.add_child(Node.u32("gamecoin_block", profile.get_int("block"))) + game.add_child(Node.u32("exp_point", profile.get_int("exp"))) + game.add_child(Node.u32("m_user_cnt", profile.get_int("m_user_cnt"))) game_config = self.get_game_config() - if game_config.get_bool('force_unlock_cards'): - game.add_child(Node.bool_array('have_item', [True] * 512)) + if game_config.get_bool("force_unlock_cards"): + game.add_child(Node.bool_array("have_item", [True] * 512)) else: - game.add_child(Node.bool_array('have_item', [x > 0 for x in profile.get_int_array('have_item', 512)])) - if game_config.get_bool('force_unlock_songs'): - game.add_child(Node.bool_array('have_note', [True] * 512)) + game.add_child( + Node.bool_array( + "have_item", + [x > 0 for x in profile.get_int_array("have_item", 512)], + ) + ) + if game_config.get_bool("force_unlock_songs"): + game.add_child(Node.bool_array("have_note", [True] * 512)) else: - game.add_child(Node.bool_array('have_note', [x > 0 for x in profile.get_int_array('have_note', 512)])) + game.add_child( + Node.bool_array( + "have_note", + [x > 0 for x in profile.get_int_array("have_note", 512)], + ) + ) # Last played stuff - lastdict = profile.get_dict('last') - last = Node.void('last') + lastdict = profile.get_dict("last") + last = Node.void("last") game.add_child(last) - last.set_attribute('music_id', str(lastdict.get_int('music_id'))) - last.set_attribute('music_type', str(lastdict.get_int('music_type'))) - last.set_attribute('sort_type', str(lastdict.get_int('sort_type'))) - last.set_attribute('headphone', str(lastdict.get_int('headphone'))) - last.set_attribute('hispeed', str(lastdict.get_int('hispeed'))) - last.set_attribute('appeal_id', str(lastdict.get_int('appeal_id'))) - last.set_attribute('frame0', str(lastdict.get_int('frame0'))) - last.set_attribute('frame1', str(lastdict.get_int('frame1'))) - last.set_attribute('frame2', str(lastdict.get_int('frame2'))) - last.set_attribute('frame3', str(lastdict.get_int('frame3'))) - last.set_attribute('frame4', str(lastdict.get_int('frame4'))) + last.set_attribute("music_id", str(lastdict.get_int("music_id"))) + last.set_attribute("music_type", str(lastdict.get_int("music_type"))) + last.set_attribute("sort_type", str(lastdict.get_int("sort_type"))) + last.set_attribute("headphone", str(lastdict.get_int("headphone"))) + last.set_attribute("hispeed", str(lastdict.get_int("hispeed"))) + last.set_attribute("appeal_id", str(lastdict.get_int("appeal_id"))) + last.set_attribute("frame0", str(lastdict.get_int("frame0"))) + last.set_attribute("frame1", str(lastdict.get_int("frame1"))) + last.set_attribute("frame2", str(lastdict.get_int("frame2"))) + last.set_attribute("frame3", str(lastdict.get_int("frame3"))) + last.set_attribute("frame4", str(lastdict.get_int("frame4"))) return game - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() # Update experience and in-game currencies - earned_gamecoin_packet = request.child_value('earned_gamecoin_packet') + earned_gamecoin_packet = request.child_value("earned_gamecoin_packet") if earned_gamecoin_packet is not None: - newprofile.replace_int('packet', newprofile.get_int('packet') + earned_gamecoin_packet) - earned_gamecoin_block = request.child_value('earned_gamecoin_block') + newprofile.replace_int( + "packet", newprofile.get_int("packet") + earned_gamecoin_packet + ) + earned_gamecoin_block = request.child_value("earned_gamecoin_block") if earned_gamecoin_block is not None: - newprofile.replace_int('block', newprofile.get_int('block') + earned_gamecoin_block) - gain_exp = request.child_value('gain_exp') + newprofile.replace_int( + "block", newprofile.get_int("block") + earned_gamecoin_block + ) + gain_exp = request.child_value("gain_exp") if gain_exp is not None: - newprofile.replace_int('exp', newprofile.get_int('exp') + gain_exp) + newprofile.replace_int("exp", newprofile.get_int("exp") + gain_exp) # Miscelaneous stuff - newprofile.replace_int('m_user_cnt', request.child_value('m_user_cnt')) + newprofile.replace_int("m_user_cnt", request.child_value("m_user_cnt")) # Update user's unlock status if we aren't force unlocked game_config = self.get_game_config() - if not game_config.get_bool('force_unlock_cards'): - have_item = request.child_value('have_item') + if not game_config.get_bool("force_unlock_cards"): + have_item = request.child_value("have_item") if have_item is not None: - newprofile.replace_int_array('have_item', 512, [1 if x else 0 for x in have_item]) - if not game_config.get_bool('force_unlock_songs'): - have_note = request.child_value('have_note') + newprofile.replace_int_array( + "have_item", 512, [1 if x else 0 for x in have_item] + ) + if not game_config.get_bool("force_unlock_songs"): + have_note = request.child_value("have_note") if have_note is not None: - newprofile.replace_int_array('have_note', 512, [1 if x else 0 for x in have_note]) + newprofile.replace_int_array( + "have_note", 512, [1 if x else 0 for x in have_note] + ) # Grab last information. - lastdict = newprofile.get_dict('last') - lastdict.replace_int('headphone', request.child_value('headphone')) - lastdict.replace_int('hispeed', request.child_value('hispeed')) - lastdict.replace_int('appeal_id', request.child_value('appeal_id')) - lastdict.replace_int('frame0', request.child_value('frame0')) - lastdict.replace_int('frame1', request.child_value('frame1')) - lastdict.replace_int('frame2', request.child_value('frame2')) - lastdict.replace_int('frame3', request.child_value('frame3')) - lastdict.replace_int('frame4', request.child_value('frame4')) - last = request.child('last') + lastdict = newprofile.get_dict("last") + lastdict.replace_int("headphone", request.child_value("headphone")) + lastdict.replace_int("hispeed", request.child_value("hispeed")) + lastdict.replace_int("appeal_id", request.child_value("appeal_id")) + lastdict.replace_int("frame0", request.child_value("frame0")) + lastdict.replace_int("frame1", request.child_value("frame1")) + lastdict.replace_int("frame2", request.child_value("frame2")) + lastdict.replace_int("frame3", request.child_value("frame3")) + lastdict.replace_int("frame4", request.child_value("frame4")) + last = request.child("last") if last is not None: - lastdict.replace_int('music_id', intish(last.attribute('music_id'))) - lastdict.replace_int('music_type', intish(last.attribute('music_type'))) - lastdict.replace_int('sort_type', intish(last.attribute('sort_type'))) + lastdict.replace_int("music_id", intish(last.attribute("music_id"))) + lastdict.replace_int("music_type", intish(last.attribute("music_type"))) + lastdict.replace_int("sort_type", intish(last.attribute("sort_type"))) # Save back last information gleaned from results - newprofile.replace_dict('last', lastdict) + newprofile.replace_dict("last", lastdict) # Keep track of play statistics self.update_play_statistics(userid) diff --git a/bemani/backend/sdvx/factory.py b/bemani/backend/sdvx/factory.py index 3f2f6ec..cafbe01 100644 --- a/bemani/backend/sdvx/factory.py +++ b/bemani/backend/sdvx/factory.py @@ -22,12 +22,17 @@ class SoundVoltexFactory(Factory): @classmethod def register_all(cls) -> None: - for gamecode in ['KFC']: + for gamecode in ["KFC"]: Base.register(gamecode, SoundVoltexFactory) @classmethod - def create(cls, data: Data, config: Config, model: Model, parentmodel: Optional[Model]=None) -> Optional[Base]: - + def create( + cls, + data: Data, + config: Config, + model: Model, + parentmodel: Optional[Model] = None, + ) -> Optional[Base]: def version_from_date(date: int) -> Optional[int]: if date < 2013060500: return VersionConstants.SDVX_BOOTH @@ -39,14 +44,14 @@ class SoundVoltexFactory(Factory): return VersionConstants.SDVX_HEAVENLY_HAVEN return None - if model.gamecode == 'KFC': + if model.gamecode == "KFC": if model.version is None: if parentmodel is None: return None # We have no way to tell apart newer versions. However, we can make # an educated guess if we happen to be summoned for old profile lookup. - if parentmodel.gamecode != 'KFC': + if parentmodel.gamecode != "KFC": return None parentversion = version_from_date(parentmodel.version) diff --git a/bemani/backend/sdvx/gravitywars.py b/bemani/backend/sdvx/gravitywars.py index 6dcb20e..4a29a99 100644 --- a/bemani/backend/sdvx/gravitywars.py +++ b/bemani/backend/sdvx/gravitywars.py @@ -14,7 +14,7 @@ class SoundVoltexGravityWars( SoundVoltexBase, ): - name: str = 'SOUND VOLTEX III GRAVITY WARS' + name: str = "SOUND VOLTEX III GRAVITY WARS" version: int = VersionConstants.SDVX_GRAVITY_WARS GAME_LIMITED_LOCKED: Final[int] = 1 @@ -50,30 +50,30 @@ class SoundVoltexGravityWars( Return all of our front-end modifiably settings. """ return { - 'bools': [ + "bools": [ { - 'name': 'Disable Online Matching', - 'tip': 'Disable online matching between games.', - 'category': 'game_config', - 'setting': 'disable_matching', + "name": "Disable Online Matching", + "tip": "Disable online matching between games.", + "category": "game_config", + "setting": "disable_matching", }, { - 'name': 'Force Song Unlock', - 'tip': 'Force unlock all songs.', - 'category': 'game_config', - 'setting': 'force_unlock_songs', + "name": "Force Song Unlock", + "tip": "Force unlock all songs.", + "category": "game_config", + "setting": "force_unlock_songs", }, { - 'name': 'Force Appeal Card Unlock', - 'tip': 'Force unlock all appeal cards.', - 'category': 'game_config', - 'setting': 'force_unlock_cards', + "name": "Force Appeal Card Unlock", + "tip": "Force unlock all appeal cards.", + "category": "game_config", + "setting": "force_unlock_cards", }, { - 'name': 'Force Crew Card Unlock', - 'tip': 'Force unlock all crew and subcrew cards.', - 'category': 'game_config', - 'setting': 'force_unlock_crew', + "name": "Force Crew Card Unlock", + "tip": "Force unlock all crew and subcrew cards.", + "category": "game_config", + "setting": "force_unlock_crew", }, ], } @@ -140,51 +140,54 @@ class SoundVoltexGravityWars( def __get_skill_analyzer_skill_levels(self) -> Dict[int, str]: return { - 0: 'Skill LEVEL 01 岳翔', - 1: 'Skill LEVEL 02 流星', - 2: 'Skill LEVEL 03 月衝', - 3: 'Skill LEVEL 04 瞬光', - 4: 'Skill LEVEL 05 天極', - 5: 'Skill LEVEL 06 烈風', - 6: 'Skill LEVEL 07 雷電', - 7: 'Skill LEVEL 08 麗華', - 8: 'Skill LEVEL 09 魔騎士', - 9: 'Skill LEVEL 10 剛力羅', - 10: 'Skill LEVEL 11 或帝滅斗', - 11: 'Skill LEVEL ∞(12) 暴龍天', + 0: "Skill LEVEL 01 岳翔", + 1: "Skill LEVEL 02 流星", + 2: "Skill LEVEL 03 月衝", + 3: "Skill LEVEL 04 瞬光", + 4: "Skill LEVEL 05 天極", + 5: "Skill LEVEL 06 烈風", + 6: "Skill LEVEL 07 雷電", + 7: "Skill LEVEL 08 麗華", + 8: "Skill LEVEL 09 魔騎士", + 9: "Skill LEVEL 10 剛力羅", + 10: "Skill LEVEL 11 或帝滅斗", + 11: "Skill LEVEL ∞(12) 暴龍天", } def handle_game_3_common_request(self, request: Node) -> Node: - game = Node.void('game_3') - limited = Node.void('music_limited') + game = Node.void("game_3") + limited = Node.void("music_limited") game.add_child(limited) # Song unlock config game_config = self.get_game_config() - if game_config.get_bool('force_unlock_songs'): + if game_config.get_bool("force_unlock_songs"): ids = set() songs = self.data.local.music.get_all_songs(self.game, self.version) for song in songs: - if song.data.get_int('limited') in (self.GAME_LIMITED_LOCKED, self.GAME_LIMITED_UNLOCKABLE): + if song.data.get_int("limited") in ( + self.GAME_LIMITED_LOCKED, + self.GAME_LIMITED_UNLOCKABLE, + ): ids.add((song.id, song.chart)) for (songid, chart) in ids: - info = Node.void('info') + info = Node.void("info") limited.add_child(info) - info.add_child(Node.s32('music_id', songid)) - info.add_child(Node.u8('music_type', chart)) - info.add_child(Node.u8('limited', self.GAME_LIMITED_UNLOCKED)) + info.add_child(Node.s32("music_id", songid)) + info.add_child(Node.u8("music_type", chart)) + info.add_child(Node.u8("limited", self.GAME_LIMITED_UNLOCKED)) # Event config - event = Node.void('event') + event = Node.void("event") game.add_child(event) def enable_event(eid: int) -> None: - evt = Node.void('info') + evt = Node.void("info") event.add_child(evt) - evt.add_child(Node.u32('event_id', eid)) + evt.add_child(Node.u32("event_id", eid)) - if not game_config.get_bool('disable_matching'): + if not game_config.get_bool("disable_matching"): enable_event(1) # Matching enabled enable_event(2) # Floor Infection enable_event(3) # Policy Break @@ -194,7 +197,7 @@ class SoundVoltexGravityWars( enable_event(eventid) # Skill Analyzer config - skill_course = Node.void('skill_course') + skill_course = Node.void("skill_course") game.add_child(skill_course) seasons = self._get_skill_analyzer_seasons() @@ -202,74 +205,91 @@ class SoundVoltexGravityWars( courses = self._get_skill_analyzer_courses() max_level: Dict[int, int] = {} for course in courses: - max_level[course['level']] = max(course['season_id'], max_level.get(course['level'], -1)) + max_level[course["level"]] = max( + course["season_id"], max_level.get(course["level"], -1) + ) for course in courses: - info = Node.void('info') + info = Node.void("info") skill_course.add_child(info) - info.add_child(Node.s16('course_id', course.get('id', course['level']))) - info.add_child(Node.s16('level', course['level'])) - info.add_child(Node.s32('season_id', course['season_id'])) - info.add_child(Node.string('season_name', seasons[course['season_id']])) - info.add_child(Node.bool('season_new_flg', max_level[course['level']] == course['season_id'])) - info.add_child(Node.string('course_name', course.get('skill_name', skillnames.get(course['level'], '')))) - info.add_child(Node.s16('course_type', 0)) - info.add_child(Node.s16('skill_name_id', course.get('skill_name_id', course['level']))) - info.add_child(Node.bool('matching_assist', course['level'] >= 0 and course['level'] <= 6)) - info.add_child(Node.s16('gauge_type', self.GAME_GAUGE_TYPE_SKILL)) - info.add_child(Node.s16('paseli_type', 0)) + info.add_child(Node.s16("course_id", course.get("id", course["level"]))) + info.add_child(Node.s16("level", course["level"])) + info.add_child(Node.s32("season_id", course["season_id"])) + info.add_child(Node.string("season_name", seasons[course["season_id"]])) + info.add_child( + Node.bool( + "season_new_flg", max_level[course["level"]] == course["season_id"] + ) + ) + info.add_child( + Node.string( + "course_name", + course.get("skill_name", skillnames.get(course["level"], "")), + ) + ) + info.add_child(Node.s16("course_type", 0)) + info.add_child( + Node.s16("skill_name_id", course.get("skill_name_id", course["level"])) + ) + info.add_child( + Node.bool( + "matching_assist", course["level"] >= 0 and course["level"] <= 6 + ) + ) + info.add_child(Node.s16("gauge_type", self.GAME_GAUGE_TYPE_SKILL)) + info.add_child(Node.s16("paseli_type", 0)) - for trackno, trackdata in enumerate(course['tracks']): - track = Node.void('track') + for trackno, trackdata in enumerate(course["tracks"]): + track = Node.void("track") info.add_child(track) - track.add_child(Node.s16('track_no', trackno)) - track.add_child(Node.s32('music_id', trackdata['id'])) - track.add_child(Node.s8('music_type', trackdata['type'])) + track.add_child(Node.s16("track_no", trackno)) + track.add_child(Node.s32("music_id", trackdata["id"])) + track.add_child(Node.s8("music_type", trackdata["type"])) return game def handle_game_3_exception_request(self, request: Node) -> Node: - return Node.void('game_3') + return Node.void("game_3") def handle_game_3_shop_request(self, request: Node) -> Node: - self.update_machine_name(request.child_value('shopname')) + self.update_machine_name(request.child_value("shopname")) # Respond with number of milliseconds until next request - game = Node.void('game_3') - game.add_child(Node.u32('nxt_time', 1000 * 5 * 60)) + game = Node.void("game_3") + game.add_child(Node.u32("nxt_time", 1000 * 5 * 60)) return game def handle_game_3_lounge_request(self, request: Node) -> Node: - game = Node.void('game_3') + game = Node.void("game_3") # Refresh interval in seconds. - game.add_child(Node.u32('interval', 10)) + game.add_child(Node.u32("interval", 10)) return game def handle_game_3_entry_s_request(self, request: Node) -> Node: - game = Node.void('game_3') + game = Node.void("game_3") # This should be created on the fly for a lobby that we're in. - game.add_child(Node.u32('entry_id', 1)) + game.add_child(Node.u32("entry_id", 1)) return game def handle_game_3_entry_e_request(self, request: Node) -> Node: # Lobby destroy method, eid node (u32) should be used # to destroy any open lobbies. - return Node.void('game_3') + return Node.void("game_3") def handle_game_3_frozen_request(self, request: Node) -> Node: - game = Node.void('game_3') - game.add_child(Node.u8('result', 0)) + game = Node.void("game_3") + game.add_child(Node.u8("result", 0)) return game def handle_game_3_save_e_request(self, request: Node) -> Node: # This has to do with Policy Break against ReflecBeat and # floor infection, but we don't implement multi-game support so meh. - return Node.void('game_3') + return Node.void("game_3") def handle_game_3_play_e_request(self, request: Node) -> Node: - return Node.void('game_3') + return Node.void("game_3") def handle_game_3_buy_request(self, request: Node) -> Node: - refid = request.child_value('refid') + refid = request.child_value("refid") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -283,15 +303,15 @@ class SoundVoltexGravityWars( if userid is not None and profile is not None: # Look up packets and blocks - packet = profile.get_int('packet') - block = profile.get_int('block') + packet = profile.get_int("packet") + block = profile.get_int("block") # Add on any additional we earned this round - packet = packet + (request.child_value('earned_gamecoin_packet') or 0) - block = block + (request.child_value('earned_gamecoin_block') or 0) + packet = packet + (request.child_value("earned_gamecoin_packet") or 0) + block = block + (request.child_value("earned_gamecoin_block") or 0) - currency_type = request.child_value('currency_type') - price = request.child_value('item/price') + currency_type = request.child_value("currency_type") + price = request.child_value("item/price") if isinstance(price, list): # Sometimes we end up buying more than one item at once price = sum(price) @@ -318,14 +338,14 @@ class SoundVoltexGravityWars( if result == 0: # Transaction is valid, update the profile with new packets and blocks - profile.replace_int('packet', packet) - profile.replace_int('block', block) + profile.replace_int("packet", packet) + profile.replace_int("block", block) self.put_profile(userid, profile) # If this was a song unlock, we should mark it as unlocked - item_type = request.child_value('item/item_type') - item_id = request.child_value('item/item_id') - param = request.child_value('item/param') + item_type = request.child_value("item/item_type") + item_id = request.child_value("item/item_id") + param = request.child_value("item/param") if not isinstance(item_type, list): # Sometimes we buy multiple things at once. Make it easier by always assuming this. @@ -339,9 +359,9 @@ class SoundVoltexGravityWars( self.version, userid, item_id[i], - f'item_{item_type[i]}', + f"item_{item_type[i]}", { - 'param': param[i], + "param": param[i], }, ) @@ -351,23 +371,23 @@ class SoundVoltexGravityWars( block = 0 result = 1 - game = Node.void('game_3') - game.add_child(Node.u32('gamecoin_packet', packet)) - game.add_child(Node.u32('gamecoin_block', block)) - game.add_child(Node.s8('result', result)) + game = Node.void("game_3") + game.add_child(Node.u32("gamecoin_packet", packet)) + game.add_child(Node.u32("gamecoin_block", block)) + game.add_child(Node.s8("result", result)) return game def handle_game_3_new_request(self, request: Node) -> Node: - refid = request.child_value('refid') - name = request.child_value('name') - loc = ID.parse_machine_id(request.child_value('locid')) + refid = request.child_value("refid") + name = request.child_value("name") + loc = ID.parse_machine_id(request.child_value("locid")) self.new_profile_by_refid(refid, name, loc) - root = Node.void('game_3') + root = Node.void("game_3") return root def handle_game_3_load_request(self, request: Node) -> Node: - refid = request.child_value('refid') + refid = request.child_value("refid") root = self.get_profile_by_refid(refid) if root is not None: return root @@ -386,17 +406,17 @@ class SoundVoltexGravityWars( profile = None if profile is not None: - root = Node.void('game_3') - root.add_child(Node.u8('result', 2)) - root.add_child(Node.string('name', profile.get_str('name'))) + root = Node.void("game_3") + root.add_child(Node.u8("result", 2)) + root.add_child(Node.string("name", profile.get_str("name"))) return root else: - root = Node.void('game_3') - root.add_child(Node.u8('result', 1)) + root = Node.void("game_3") + root.add_child(Node.u8("result", 1)) return root def handle_game_3_save_request(self, request: Node) -> Node: - refid = request.child_value('refid') + refid = request.child_value("refid") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -412,10 +432,10 @@ class SoundVoltexGravityWars( if userid is not None and newprofile is not None: self.put_profile(userid, newprofile) - return Node.void('game_3') + return Node.void("game_3") def handle_game_3_load_m_request(self, request: Node) -> Node: - refid = request.child_value('dataid') + refid = request.child_value("dataid") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -428,28 +448,37 @@ class SoundVoltexGravityWars( scores = [] # Output to the game - game = Node.void('game_3') - new = Node.void('new') + game = Node.void("game_3") + new = Node.void("new") game.add_child(new) for score in scores: - music = Node.void('music') + music = Node.void("music") new.add_child(music) - music.add_child(Node.u32('music_id', score.id)) - music.add_child(Node.u32('music_type', score.chart)) - music.add_child(Node.u32('score', score.points)) - music.add_child(Node.u32('cnt', score.plays)) - music.add_child(Node.u32('clear_type', self.__db_to_game_clear_type(score.data.get_int('clear_type')))) - music.add_child(Node.u32('score_grade', self.__db_to_game_grade(score.data.get_int('grade')))) - stats = score.data.get_dict('stats') - music.add_child(Node.u32('btn_rate', stats.get_int('btn_rate'))) - music.add_child(Node.u32('long_rate', stats.get_int('long_rate'))) - music.add_child(Node.u32('vol_rate', stats.get_int('vol_rate'))) + music.add_child(Node.u32("music_id", score.id)) + music.add_child(Node.u32("music_type", score.chart)) + music.add_child(Node.u32("score", score.points)) + music.add_child(Node.u32("cnt", score.plays)) + music.add_child( + Node.u32( + "clear_type", + self.__db_to_game_clear_type(score.data.get_int("clear_type")), + ) + ) + music.add_child( + Node.u32( + "score_grade", self.__db_to_game_grade(score.data.get_int("grade")) + ) + ) + stats = score.data.get_dict("stats") + music.add_child(Node.u32("btn_rate", stats.get_int("btn_rate"))) + music.add_child(Node.u32("long_rate", stats.get_int("long_rate"))) + music.add_child(Node.u32("vol_rate", stats.get_int("vol_rate"))) return game def handle_game_3_save_m_request(self, request: Node) -> Node: - refid = request.child_value('refid') + refid = request.child_value("refid") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -457,19 +486,19 @@ class SoundVoltexGravityWars( userid = None # Doesn't matter if userid is None here, that's an anonymous score - musicid = request.child_value('music_id') - chart = request.child_value('music_type') - points = request.child_value('score') - combo = request.child_value('max_chain') - clear_type = self.__game_to_db_clear_type(request.child_value('clear_type')) - grade = self.__game_to_db_grade(request.child_value('score_grade')) + musicid = request.child_value("music_id") + chart = request.child_value("music_type") + points = request.child_value("score") + combo = request.child_value("max_chain") + clear_type = self.__game_to_db_clear_type(request.child_value("clear_type")) + grade = self.__game_to_db_grade(request.child_value("score_grade")) stats = { - 'btn_rate': request.child_value('btn_rate'), - 'long_rate': request.child_value('long_rate'), - 'vol_rate': request.child_value('vol_rate'), - 'critical': request.child_value('critical'), - 'near': request.child_value('near'), - 'error': request.child_value('error'), + "btn_rate": request.child_value("btn_rate"), + "long_rate": request.child_value("long_rate"), + "vol_rate": request.child_value("vol_rate"), + "critical": request.child_value("critical"), + "near": request.child_value("near"), + "error": request.child_value("error"), } # Save the score @@ -485,10 +514,10 @@ class SoundVoltexGravityWars( ) # Return a blank response - return Node.void('game_3') + return Node.void("game_3") def handle_game_3_save_c_request(self, request: Node) -> Node: - refid = request.child_value('dataid') + refid = request.child_value("dataid") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -496,27 +525,29 @@ class SoundVoltexGravityWars( userid = None if userid is not None: - course_id = request.child_value('crsid') - clear_type = request.child_value('ct') - achievement_rate = request.child_value('ar') - season_id = request.child_value('ssnid') + course_id = request.child_value("crsid") + clear_type = request.child_value("ct") + achievement_rate = request.child_value("ar") + season_id = request.child_value("ssnid") # Do not update the course achievement when old achievement rate is greater. - old = self.data.local.user.get_achievement(self.game, self.version, userid, (season_id * 100) + course_id, 'course') - if old is not None and old.get_int('achievement_rate') > achievement_rate: - return Node.void('game_3') + old = self.data.local.user.get_achievement( + self.game, self.version, userid, (season_id * 100) + course_id, "course" + ) + if old is not None and old.get_int("achievement_rate") > achievement_rate: + return Node.void("game_3") self.data.local.user.put_achievement( self.game, self.version, userid, (season_id * 100) + course_id, - 'course', + "course", { - 'clear_type': clear_type, - 'achievement_rate': achievement_rate, + "clear_type": clear_type, + "achievement_rate": achievement_rate, }, ) # Return a blank response - return Node.void('game_3') + return Node.void("game_3") diff --git a/bemani/backend/sdvx/gravitywars_s1.py b/bemani/backend/sdvx/gravitywars_s1.py index 6ef9f06..0c5dc42 100644 --- a/bemani/backend/sdvx/gravitywars_s1.py +++ b/bemani/backend/sdvx/gravitywars_s1.py @@ -10,3034 +10,3033 @@ from bemani.protocol import Node class SoundVoltexGravityWarsSeason1( SoundVoltexGravityWars, ): - def _get_skill_analyzer_seasons(self) -> Dict[int, str]: return { - 1: 'SKILL ANALYZER 第1回 Aコース', - 2: 'SKILL ANALYZER 第1回 Bコース', - 3: 'SKILL ANALYZER 第1回 Cコース', - 4: 'The 4th KAC コース', - 5: 'SKILL ANALYZER 第2回 Aコース', - 6: 'SKILL ANALYZER 第2回 Bコース', - 7: 'SKILL ANALYZER 第2回 Cコース', - 8: 'SKILL ANALYZER 第3回 Aコース', - 9: 'SKILL ANALYZER 第3回 Bコース', - 10: 'SKILL ANALYZER 第3回 Cコース', - 11: 'SKILL ANALYZER 第4回', - 12: 'SKILL ANALYZER 第5回 Aコース', - 13: 'SKILL ANALYZER 第5回 Bコース', - 14: 'SKILL ANALYZER 第5回 Cコース', - 15: 'SKILL ANALYZER 第6回 Aコース', - 16: 'SKILL ANALYZER 第6回 Bコース', + 1: "SKILL ANALYZER 第1回 Aコース", + 2: "SKILL ANALYZER 第1回 Bコース", + 3: "SKILL ANALYZER 第1回 Cコース", + 4: "The 4th KAC コース", + 5: "SKILL ANALYZER 第2回 Aコース", + 6: "SKILL ANALYZER 第2回 Bコース", + 7: "SKILL ANALYZER 第2回 Cコース", + 8: "SKILL ANALYZER 第3回 Aコース", + 9: "SKILL ANALYZER 第3回 Bコース", + 10: "SKILL ANALYZER 第3回 Cコース", + 11: "SKILL ANALYZER 第4回", + 12: "SKILL ANALYZER 第5回 Aコース", + 13: "SKILL ANALYZER 第5回 Bコース", + 14: "SKILL ANALYZER 第5回 Cコース", + 15: "SKILL ANALYZER 第6回 Aコース", + 16: "SKILL ANALYZER 第6回 Bコース", } def _get_skill_analyzer_courses(self) -> List[Dict[str, Any]]: return [ { - 'level': 0, - 'season_id': 1, - 'tracks': [ + "level": 0, + "season_id": 1, + "tracks": [ { - 'id': 109, - 'type': self.CHART_TYPE_NOVICE, + "id": 109, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 283, - 'type': self.CHART_TYPE_ADVANCED, + "id": 283, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 279, - 'type': self.CHART_TYPE_ADVANCED, + "id": 279, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 1, - 'tracks': [ + "level": 1, + "season_id": 1, + "tracks": [ { - 'id': 76, - 'type': self.CHART_TYPE_ADVANCED, + "id": 76, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 196, - 'type': self.CHART_TYPE_ADVANCED, + "id": 196, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 8, - 'type': self.CHART_TYPE_ADVANCED, + "id": 8, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 1, - 'tracks': [ + "level": 2, + "season_id": 1, + "tracks": [ { - 'id': 90, - 'type': self.CHART_TYPE_ADVANCED, + "id": 90, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 228, - 'type': self.CHART_TYPE_ADVANCED, + "id": 228, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 80, - 'type': self.CHART_TYPE_ADVANCED, + "id": 80, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 1, - 'tracks': [ + "level": 3, + "season_id": 1, + "tracks": [ { - 'id': 125, - 'type': self.CHART_TYPE_ADVANCED, + "id": 125, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 201, - 'type': self.CHART_TYPE_EXHAUST, + "id": 201, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 237, - 'type': self.CHART_TYPE_EXHAUST, + "id": 237, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 1, - 'tracks': [ + "level": 4, + "season_id": 1, + "tracks": [ { - 'id': 393, - 'type': self.CHART_TYPE_ADVANCED, + "id": 393, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 352, - 'type': self.CHART_TYPE_EXHAUST, + "id": 352, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 66, - 'type': self.CHART_TYPE_EXHAUST, + "id": 66, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 1, - 'tracks': [ + "level": 5, + "season_id": 1, + "tracks": [ { - 'id': 383, - 'type': self.CHART_TYPE_EXHAUST, + "id": 383, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 511, - 'type': self.CHART_TYPE_EXHAUST, + "id": 511, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 331, - 'type': self.CHART_TYPE_EXHAUST, + "id": 331, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 1, - 'tracks': [ + "level": 6, + "season_id": 1, + "tracks": [ { - 'id': 422, - 'type': self.CHART_TYPE_EXHAUST, + "id": 422, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 445, - 'type': self.CHART_TYPE_EXHAUST, + "id": 445, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 71, - 'type': self.CHART_TYPE_EXHAUST, + "id": 71, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 1, - 'tracks': [ + "level": 7, + "season_id": 1, + "tracks": [ { - 'id': 454, - 'type': self.CHART_TYPE_EXHAUST, + "id": 454, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 158, - 'type': self.CHART_TYPE_EXHAUST, + "id": 158, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 173, - 'type': self.CHART_TYPE_EXHAUST, + "id": 173, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 1, - 'tracks': [ + "level": 8, + "season_id": 1, + "tracks": [ { - 'id': 322, - 'type': self.CHART_TYPE_EXHAUST, + "id": 322, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 63, - 'type': self.CHART_TYPE_EXHAUST, + "id": 63, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 124, - 'type': self.CHART_TYPE_EXHAUST, + "id": 124, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 1, - 'tracks': [ + "level": 9, + "season_id": 1, + "tracks": [ { - 'id': 348, - 'type': self.CHART_TYPE_EXHAUST, + "id": 348, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 73, - 'type': self.CHART_TYPE_EXHAUST, + "id": 73, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 259, - 'type': self.CHART_TYPE_EXHAUST, + "id": 259, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 2, - 'tracks': [ + "level": 0, + "season_id": 2, + "tracks": [ { - 'id': 374, - 'type': self.CHART_TYPE_NOVICE, + "id": 374, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 84, - 'type': self.CHART_TYPE_ADVANCED, + "id": 84, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 303, - 'type': self.CHART_TYPE_ADVANCED, + "id": 303, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 2, - 'tracks': [ + "level": 1, + "season_id": 2, + "tracks": [ { - 'id': 22, - 'type': self.CHART_TYPE_ADVANCED, + "id": 22, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 274, - 'type': self.CHART_TYPE_ADVANCED, + "id": 274, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 183, - 'type': self.CHART_TYPE_ADVANCED, + "id": 183, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 2, - 'tracks': [ + "level": 2, + "season_id": 2, + "tracks": [ { - 'id': 56, - 'type': self.CHART_TYPE_ADVANCED, + "id": 56, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 244, - 'type': self.CHART_TYPE_ADVANCED, + "id": 244, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 4, - 'type': self.CHART_TYPE_EXHAUST, + "id": 4, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 3, - 'season_id': 2, - 'tracks': [ + "level": 3, + "season_id": 2, + "tracks": [ { - 'id': 414, - 'type': self.CHART_TYPE_ADVANCED, + "id": 414, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 209, - 'type': self.CHART_TYPE_EXHAUST, + "id": 209, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 334, - 'type': self.CHART_TYPE_EXHAUST, + "id": 334, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 2, - 'tracks': [ + "level": 4, + "season_id": 2, + "tracks": [ { - 'id': 123, - 'type': self.CHART_TYPE_ADVANCED, + "id": 123, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 403, - 'type': self.CHART_TYPE_EXHAUST, + "id": 403, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 23, - 'type': self.CHART_TYPE_INFINITE, + "id": 23, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'level': 5, - 'season_id': 2, - 'tracks': [ + "level": 5, + "season_id": 2, + "tracks": [ { - 'id': 391, - 'type': self.CHART_TYPE_EXHAUST, + "id": 391, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 239, - 'type': self.CHART_TYPE_EXHAUST, + "id": 239, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 426, - 'type': self.CHART_TYPE_EXHAUST, + "id": 426, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 2, - 'tracks': [ + "level": 6, + "season_id": 2, + "tracks": [ { - 'id': 389, - 'type': self.CHART_TYPE_EXHAUST, + "id": 389, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 89, - 'type': self.CHART_TYPE_EXHAUST, + "id": 89, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 246, - 'type': self.CHART_TYPE_EXHAUST, + "id": 246, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 2, - 'tracks': [ + "level": 7, + "season_id": 2, + "tracks": [ { - 'id': 419, - 'type': self.CHART_TYPE_EXHAUST, + "id": 419, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 299, - 'type': self.CHART_TYPE_EXHAUST, + "id": 299, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 341, - 'type': self.CHART_TYPE_EXHAUST, + "id": 341, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 2, - 'tracks': [ + "level": 8, + "season_id": 2, + "tracks": [ { - 'id': 394, - 'type': self.CHART_TYPE_EXHAUST, + "id": 394, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 466, - 'type': self.CHART_TYPE_EXHAUST, + "id": 466, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 47, - 'type': self.CHART_TYPE_EXHAUST, + "id": 47, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 2, - 'tracks': [ + "level": 9, + "season_id": 2, + "tracks": [ { - 'id': 500, - 'type': self.CHART_TYPE_EXHAUST, + "id": 500, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 247, - 'type': self.CHART_TYPE_EXHAUST, + "id": 247, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 229, - 'type': self.CHART_TYPE_EXHAUST, + "id": 229, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 3, - 'tracks': [ + "level": 0, + "season_id": 3, + "tracks": [ { - 'id': 36, - 'type': self.CHART_TYPE_NOVICE, + "id": 36, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 189, - 'type': self.CHART_TYPE_ADVANCED, + "id": 189, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 171, - 'type': self.CHART_TYPE_ADVANCED, + "id": 171, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 3, - 'tracks': [ + "level": 1, + "season_id": 3, + "tracks": [ { - 'id': 182, - 'type': self.CHART_TYPE_ADVANCED, + "id": 182, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 3, - 'type': self.CHART_TYPE_ADVANCED, + "id": 3, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 105, - 'type': self.CHART_TYPE_ADVANCED, + "id": 105, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 3, - 'tracks': [ + "level": 2, + "season_id": 3, + "tracks": [ { - 'id': 14, - 'type': self.CHART_TYPE_ADVANCED, + "id": 14, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 120, - 'type': self.CHART_TYPE_ADVANCED, + "id": 120, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 86, - 'type': self.CHART_TYPE_ADVANCED, + "id": 86, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 3, - 'tracks': [ + "level": 3, + "season_id": 3, + "tracks": [ { - 'id': 390, - 'type': self.CHART_TYPE_ADVANCED, + "id": 390, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 243, - 'type': self.CHART_TYPE_EXHAUST, + "id": 243, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 186, - 'type': self.CHART_TYPE_EXHAUST, + "id": 186, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 3, - 'tracks': [ + "level": 4, + "season_id": 3, + "tracks": [ { - 'id': 36, - 'type': self.CHART_TYPE_ADVANCED, + "id": 36, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 423, - 'type': self.CHART_TYPE_EXHAUST, + "id": 423, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 59, - 'type': self.CHART_TYPE_EXHAUST, + "id": 59, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 3, - 'tracks': [ + "level": 5, + "season_id": 3, + "tracks": [ { - 'id': 452, - 'type': self.CHART_TYPE_EXHAUST, + "id": 452, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 262, - 'type': self.CHART_TYPE_EXHAUST, + "id": 262, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 480, - 'type': self.CHART_TYPE_INFINITE, + "id": 480, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'level': 6, - 'season_id': 3, - 'tracks': [ + "level": 6, + "season_id": 3, + "tracks": [ { - 'id': 411, - 'type': self.CHART_TYPE_EXHAUST, + "id": 411, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 70, - 'type': self.CHART_TYPE_EXHAUST, + "id": 70, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 211, - 'type': self.CHART_TYPE_EXHAUST, + "id": 211, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 3, - 'tracks': [ + "level": 7, + "season_id": 3, + "tracks": [ { - 'id': 30, - 'type': self.CHART_TYPE_EXHAUST, + "id": 30, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 72, - 'type': self.CHART_TYPE_INFINITE, + "id": 72, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 293, - 'type': self.CHART_TYPE_EXHAUST, + "id": 293, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 3, - 'tracks': [ + "level": 8, + "season_id": 3, + "tracks": [ { - 'id': 87, - 'type': self.CHART_TYPE_INFINITE, + "id": 87, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 117, - 'type': self.CHART_TYPE_EXHAUST, + "id": 117, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 269, - 'type': self.CHART_TYPE_EXHAUST, + "id": 269, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 3, - 'tracks': [ + "level": 9, + "season_id": 3, + "tracks": [ { - 'id': 498, - 'type': self.CHART_TYPE_EXHAUST, + "id": 498, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 437, - 'type': self.CHART_TYPE_EXHAUST, + "id": 437, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 126, - 'type': self.CHART_TYPE_EXHAUST, + "id": 126, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 0, - 'level': -1, - 'skill_name': 'エンジョイ♪ごりらコースA', - 'skill_name_id': 12, - 'season_id': 4, - 'tracks': [ + "id": 0, + "level": -1, + "skill_name": "エンジョイ♪ごりらコースA", + "skill_name_id": 12, + "season_id": 4, + "tracks": [ { - 'id': 466, - 'type': self.CHART_TYPE_ADVANCED, + "id": 466, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 273, - 'type': self.CHART_TYPE_ADVANCED, + "id": 273, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 470, - 'type': self.CHART_TYPE_ADVANCED, + "id": 470, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'id': 1, - 'level': -1, - 'skill_name': 'エンジョイ♪ごりらコースB', - 'skill_name_id': 12, - 'season_id': 4, - 'tracks': [ + "id": 1, + "level": -1, + "skill_name": "エンジョイ♪ごりらコースB", + "skill_name_id": 12, + "season_id": 4, + "tracks": [ { - 'id': 194, - 'type': self.CHART_TYPE_ADVANCED, + "id": 194, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 343, - 'type': self.CHART_TYPE_ADVANCED, + "id": 343, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 501, - 'type': self.CHART_TYPE_ADVANCED, + "id": 501, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'id': 2, - 'level': -1, - 'skill_name': 'エンジョイ♪ごりらコースC', - 'skill_name_id': 12, - 'season_id': 4, - 'tracks': [ + "id": 2, + "level": -1, + "skill_name": "エンジョイ♪ごりらコースC", + "skill_name_id": 12, + "season_id": 4, + "tracks": [ { - 'id': 356, - 'type': self.CHART_TYPE_ADVANCED, + "id": 356, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 7, - 'type': self.CHART_TYPE_EXHAUST, + "id": 7, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 472, - 'type': self.CHART_TYPE_ADVANCED, + "id": 472, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'id': 3, - 'level': -1, - 'skill_name': 'エンジョイ♪ごりらコースD', - 'skill_name_id': 12, - 'season_id': 4, - 'tracks': [ + "id": 3, + "level": -1, + "skill_name": "エンジョイ♪ごりらコースD", + "skill_name_id": 12, + "season_id": 4, + "tracks": [ { - 'id': 299, - 'type': self.CHART_TYPE_ADVANCED, + "id": 299, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 333, - 'type': self.CHART_TYPE_ADVANCED, + "id": 333, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 583, - 'type': self.CHART_TYPE_ADVANCED, + "id": 583, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'id': 4, - 'level': -1, - 'skill_name': 'チャレンジ★ごりらコースA', - 'skill_name_id': 12, - 'season_id': 4, - 'tracks': [ + "id": 4, + "level": -1, + "skill_name": "チャレンジ★ごりらコースA", + "skill_name_id": 12, + "season_id": 4, + "tracks": [ { - 'id': 466, - 'type': self.CHART_TYPE_EXHAUST, + "id": 466, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 273, - 'type': self.CHART_TYPE_EXHAUST, + "id": 273, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 470, - 'type': self.CHART_TYPE_EXHAUST, + "id": 470, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 5, - 'level': -1, - 'skill_name': 'チャレンジ★ごりらコースB', - 'skill_name_id': 12, - 'season_id': 4, - 'tracks': [ + "id": 5, + "level": -1, + "skill_name": "チャレンジ★ごりらコースB", + "skill_name_id": 12, + "season_id": 4, + "tracks": [ { - 'id': 194, - 'type': self.CHART_TYPE_EXHAUST, + "id": 194, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 343, - 'type': self.CHART_TYPE_EXHAUST, + "id": 343, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 501, - 'type': self.CHART_TYPE_EXHAUST, + "id": 501, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 6, - 'level': -1, - 'skill_name': 'チャレンジ★ごりらコースC', - 'skill_name_id': 12, - 'season_id': 4, - 'tracks': [ + "id": 6, + "level": -1, + "skill_name": "チャレンジ★ごりらコースC", + "skill_name_id": 12, + "season_id": 4, + "tracks": [ { - 'id': 356, - 'type': self.CHART_TYPE_EXHAUST, + "id": 356, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 7, - 'type': self.CHART_TYPE_INFINITE, + "id": 7, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 472, - 'type': self.CHART_TYPE_EXHAUST, + "id": 472, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 7, - 'level': -1, - 'skill_name': 'チャレンジ★ごりらコースD', - 'skill_name_id': 12, - 'season_id': 4, - 'tracks': [ + "id": 7, + "level": -1, + "skill_name": "チャレンジ★ごりらコースD", + "skill_name_id": 12, + "season_id": 4, + "tracks": [ { - 'id': 299, - 'type': self.CHART_TYPE_EXHAUST, + "id": 299, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 333, - 'type': self.CHART_TYPE_EXHAUST, + "id": 333, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 583, - 'type': self.CHART_TYPE_EXHAUST, + "id": 583, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 5, - 'tracks': [ + "level": 0, + "season_id": 5, + "tracks": [ { - 'id': 47, - 'type': self.CHART_TYPE_NOVICE, + "id": 47, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 334, - 'type': self.CHART_TYPE_ADVANCED, + "id": 334, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 10, - 'type': self.CHART_TYPE_ADVANCED, + "id": 10, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 5, - 'tracks': [ + "level": 1, + "season_id": 5, + "tracks": [ { - 'id': 11, - 'type': self.CHART_TYPE_ADVANCED, + "id": 11, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 224, - 'type': self.CHART_TYPE_ADVANCED, + "id": 224, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 132, - 'type': self.CHART_TYPE_ADVANCED, + "id": 132, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 5, - 'tracks': [ + "level": 2, + "season_id": 5, + "tracks": [ { - 'id': 137, - 'type': self.CHART_TYPE_ADVANCED, + "id": 137, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 336, - 'type': self.CHART_TYPE_ADVANCED, + "id": 336, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 380, - 'type': self.CHART_TYPE_ADVANCED, + "id": 380, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 5, - 'tracks': [ + "level": 3, + "season_id": 5, + "tracks": [ { - 'id': 109, - 'type': self.CHART_TYPE_ADVANCED, + "id": 109, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 308, - 'type': self.CHART_TYPE_EXHAUST, + "id": 308, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 113, - 'type': self.CHART_TYPE_EXHAUST, + "id": 113, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 5, - 'tracks': [ + "level": 4, + "season_id": 5, + "tracks": [ { - 'id': 101, - 'type': self.CHART_TYPE_ADVANCED, + "id": 101, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 200, - 'type': self.CHART_TYPE_EXHAUST, + "id": 200, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 478, - 'type': self.CHART_TYPE_EXHAUST, + "id": 478, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 5, - 'tracks': [ + "level": 5, + "season_id": 5, + "tracks": [ { - 'id': 487, - 'type': self.CHART_TYPE_EXHAUST, + "id": 487, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 254, - 'type': self.CHART_TYPE_EXHAUST, + "id": 254, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 410, - 'type': self.CHART_TYPE_EXHAUST, + "id": 410, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 5, - 'tracks': [ + "level": 6, + "season_id": 5, + "tracks": [ { - 'id': 196, - 'type': self.CHART_TYPE_EXHAUST, + "id": 196, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 170, - 'type': self.CHART_TYPE_EXHAUST, + "id": 170, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 218, - 'type': self.CHART_TYPE_EXHAUST, + "id": 218, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 5, - 'tracks': [ + "level": 7, + "season_id": 5, + "tracks": [ { - 'id': 489, - 'type': self.CHART_TYPE_EXHAUST, + "id": 489, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 519, - 'type': self.CHART_TYPE_EXHAUST, + "id": 519, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 373, - 'type': self.CHART_TYPE_EXHAUST, + "id": 373, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 5, - 'tracks': [ + "level": 8, + "season_id": 5, + "tracks": [ { - 'id': 456, - 'type': self.CHART_TYPE_EXHAUST, + "id": 456, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 263, - 'type': self.CHART_TYPE_EXHAUST, + "id": 263, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 390, - 'type': self.CHART_TYPE_EXHAUST, + "id": 390, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 5, - 'tracks': [ + "level": 9, + "season_id": 5, + "tracks": [ { - 'id': 19, - 'type': self.CHART_TYPE_INFINITE, + "id": 19, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 116, - 'type': self.CHART_TYPE_EXHAUST, + "id": 116, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 508, - 'type': self.CHART_TYPE_EXHAUST, + "id": 508, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 6, - 'tracks': [ + "level": 0, + "season_id": 6, + "tracks": [ { - 'id': 123, - 'type': self.CHART_TYPE_NOVICE, + "id": 123, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 231, - 'type': self.CHART_TYPE_ADVANCED, + "id": 231, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 185, - 'type': self.CHART_TYPE_ADVANCED, + "id": 185, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 6, - 'tracks': [ + "level": 1, + "season_id": 6, + "tracks": [ { - 'id': 65, - 'type': self.CHART_TYPE_ADVANCED, + "id": 65, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 386, - 'type': self.CHART_TYPE_ADVANCED, + "id": 386, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 92, - 'type': self.CHART_TYPE_ADVANCED, + "id": 92, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 6, - 'tracks': [ + "level": 2, + "season_id": 6, + "tracks": [ { - 'id': 379, - 'type': self.CHART_TYPE_ADVANCED, + "id": 379, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 225, - 'type': self.CHART_TYPE_ADVANCED, + "id": 225, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 427, - 'type': self.CHART_TYPE_ADVANCED, + "id": 427, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 6, - 'tracks': [ + "level": 3, + "season_id": 6, + "tracks": [ { - 'id': 122, - 'type': self.CHART_TYPE_ADVANCED, + "id": 122, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 249, - 'type': self.CHART_TYPE_EXHAUST, + "id": 249, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 185, - 'type': self.CHART_TYPE_EXHAUST, + "id": 185, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 6, - 'tracks': [ + "level": 4, + "season_id": 6, + "tracks": [ { - 'id': 413, - 'type': self.CHART_TYPE_ADVANCED, + "id": 413, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 157, - 'type': self.CHART_TYPE_EXHAUST, + "id": 157, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 402, - 'type': self.CHART_TYPE_EXHAUST, + "id": 402, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 6, - 'tracks': [ + "level": 5, + "season_id": 6, + "tracks": [ { - 'id': 412, - 'type': self.CHART_TYPE_EXHAUST, + "id": 412, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 323, - 'type': self.CHART_TYPE_EXHAUST, + "id": 323, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 256, - 'type': self.CHART_TYPE_EXHAUST, + "id": 256, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 6, - 'tracks': [ + "level": 6, + "season_id": 6, + "tracks": [ { - 'id': 400, - 'type': self.CHART_TYPE_EXHAUST, + "id": 400, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 368, - 'type': self.CHART_TYPE_EXHAUST, + "id": 368, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 241, - 'type': self.CHART_TYPE_EXHAUST, + "id": 241, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 6, - 'tracks': [ + "level": 7, + "season_id": 6, + "tracks": [ { - 'id': 453, - 'type': self.CHART_TYPE_EXHAUST, + "id": 453, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 442, - 'type': self.CHART_TYPE_EXHAUST, + "id": 442, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 216, - 'type': self.CHART_TYPE_EXHAUST, + "id": 216, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 6, - 'tracks': [ + "level": 8, + "season_id": 6, + "tracks": [ { - 'id': 370, - 'type': self.CHART_TYPE_EXHAUST, + "id": 370, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 244, - 'type': self.CHART_TYPE_EXHAUST, + "id": 244, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 252, - 'type': self.CHART_TYPE_EXHAUST, + "id": 252, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 6, - 'tracks': [ + "level": 9, + "season_id": 6, + "tracks": [ { - 'id': 359, - 'type': self.CHART_TYPE_EXHAUST, + "id": 359, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 214, - 'type': self.CHART_TYPE_EXHAUST, + "id": 214, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 506, - 'type': self.CHART_TYPE_EXHAUST, + "id": 506, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 7, - 'tracks': [ + "level": 0, + "season_id": 7, + "tracks": [ { - 'id': 124, - 'type': self.CHART_TYPE_NOVICE, + "id": 124, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 446, - 'type': self.CHART_TYPE_ADVANCED, + "id": 446, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 34, - 'type': self.CHART_TYPE_ADVANCED, + "id": 34, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 7, - 'tracks': [ + "level": 1, + "season_id": 7, + "tracks": [ { - 'id': 113, - 'type': self.CHART_TYPE_ADVANCED, + "id": 113, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 309, - 'type': self.CHART_TYPE_ADVANCED, + "id": 309, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 42, - 'type': self.CHART_TYPE_ADVANCED, + "id": 42, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 7, - 'tracks': [ + "level": 2, + "season_id": 7, + "tracks": [ { - 'id': 353, - 'type': self.CHART_TYPE_ADVANCED, + "id": 353, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 246, - 'type': self.CHART_TYPE_ADVANCED, + "id": 246, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 130, - 'type': self.CHART_TYPE_ADVANCED, + "id": 130, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 7, - 'tracks': [ + "level": 3, + "season_id": 7, + "tracks": [ { - 'id': 63, - 'type': self.CHART_TYPE_ADVANCED, + "id": 63, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 219, - 'type': self.CHART_TYPE_EXHAUST, + "id": 219, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 153, - 'type': self.CHART_TYPE_EXHAUST, + "id": 153, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 7, - 'tracks': [ + "level": 4, + "season_id": 7, + "tracks": [ { - 'id': 418, - 'type': self.CHART_TYPE_ADVANCED, + "id": 418, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 369, - 'type': self.CHART_TYPE_EXHAUST, + "id": 369, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 385, - 'type': self.CHART_TYPE_EXHAUST, + "id": 385, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 7, - 'tracks': [ + "level": 5, + "season_id": 7, + "tracks": [ { - 'id': 226, - 'type': self.CHART_TYPE_EXHAUST, + "id": 226, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 301, - 'type': self.CHART_TYPE_EXHAUST, + "id": 301, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 159, - 'type': self.CHART_TYPE_EXHAUST, + "id": 159, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 7, - 'tracks': [ + "level": 6, + "season_id": 7, + "tracks": [ { - 'id': 311, - 'type': self.CHART_TYPE_EXHAUST, + "id": 311, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 255, - 'type': self.CHART_TYPE_EXHAUST, + "id": 255, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 213, - 'type': self.CHART_TYPE_EXHAUST, + "id": 213, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 7, - 'tracks': [ + "level": 7, + "season_id": 7, + "tracks": [ { - 'id': 357, - 'type': self.CHART_TYPE_EXHAUST, + "id": 357, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 268, - 'type': self.CHART_TYPE_EXHAUST, + "id": 268, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 304, - 'type': self.CHART_TYPE_EXHAUST, + "id": 304, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 7, - 'tracks': [ + "level": 8, + "season_id": 7, + "tracks": [ { - 'id': 295, - 'type': self.CHART_TYPE_EXHAUST, + "id": 295, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 36, - 'type': self.CHART_TYPE_EXHAUST, + "id": 36, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 302, - 'type': self.CHART_TYPE_EXHAUST, + "id": 302, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 7, - 'tracks': [ + "level": 9, + "season_id": 7, + "tracks": [ { - 'id': 7, - 'type': self.CHART_TYPE_INFINITE, + "id": 7, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 208, - 'type': self.CHART_TYPE_EXHAUST, + "id": 208, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 376, - 'type': self.CHART_TYPE_EXHAUST, + "id": 376, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 8, - 'tracks': [ + "level": 0, + "season_id": 8, + "tracks": [ { - 'id': 101, - 'type': self.CHART_TYPE_NOVICE, + "id": 101, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 219, - 'type': self.CHART_TYPE_ADVANCED, + "id": 219, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 159, - 'type': self.CHART_TYPE_ADVANCED, + "id": 159, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 8, - 'tracks': [ + "level": 1, + "season_id": 8, + "tracks": [ { - 'id': 87, - 'type': self.CHART_TYPE_ADVANCED, + "id": 87, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 337, - 'type': self.CHART_TYPE_ADVANCED, + "id": 337, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 403, - 'type': self.CHART_TYPE_ADVANCED, + "id": 403, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 8, - 'tracks': [ + "level": 2, + "season_id": 8, + "tracks": [ { - 'id': 30, - 'type': self.CHART_TYPE_ADVANCED, + "id": 30, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 596, - 'type': self.CHART_TYPE_ADVANCED, + "id": 596, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 39, - 'type': self.CHART_TYPE_ADVANCED, + "id": 39, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 8, - 'tracks': [ + "level": 3, + "season_id": 8, + "tracks": [ { - 'id': 430, - 'type': self.CHART_TYPE_ADVANCED, + "id": 430, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 561, - 'type': self.CHART_TYPE_EXHAUST, + "id": 561, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 328, - 'type': self.CHART_TYPE_EXHAUST, + "id": 328, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 8, - 'tracks': [ + "level": 4, + "season_id": 8, + "tracks": [ { - 'id': 444, - 'type': self.CHART_TYPE_ADVANCED, + "id": 444, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 618, - 'type': self.CHART_TYPE_EXHAUST, + "id": 618, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 100, - 'type': self.CHART_TYPE_EXHAUST, + "id": 100, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 8, - 'tracks': [ + "level": 5, + "season_id": 8, + "tracks": [ { - 'id': 447, - 'type': self.CHART_TYPE_EXHAUST, + "id": 447, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 545, - 'type': self.CHART_TYPE_EXHAUST, + "id": 545, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 94, - 'type': self.CHART_TYPE_EXHAUST, + "id": 94, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 8, - 'tracks': [ + "level": 6, + "season_id": 8, + "tracks": [ { - 'id': 291, - 'type': self.CHART_TYPE_EXHAUST, + "id": 291, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 2, - 'type': self.CHART_TYPE_EXHAUST, + "id": 2, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 475, - 'type': self.CHART_TYPE_EXHAUST, + "id": 475, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 8, - 'tracks': [ + "level": 7, + "season_id": 8, + "tracks": [ { - 'id': 627, - 'type': self.CHART_TYPE_EXHAUST, + "id": 627, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 624, - 'type': self.CHART_TYPE_EXHAUST, + "id": 624, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 427, - 'type': self.CHART_TYPE_EXHAUST, + "id": 427, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 8, - 'tracks': [ + "level": 8, + "season_id": 8, + "tracks": [ { - 'id': 464, - 'type': self.CHART_TYPE_EXHAUST, + "id": 464, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 122, - 'type': self.CHART_TYPE_EXHAUST, + "id": 122, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 591, - 'type': self.CHART_TYPE_EXHAUST, + "id": 591, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 8, - 'tracks': [ + "level": 9, + "season_id": 8, + "tracks": [ { - 'id': 381, - 'type': self.CHART_TYPE_EXHAUST, + "id": 381, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 463, - 'type': self.CHART_TYPE_EXHAUST, + "id": 463, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 507, - 'type': self.CHART_TYPE_EXHAUST, + "id": 507, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 9, - 'tracks': [ + "level": 0, + "season_id": 9, + "tracks": [ { - 'id': 468, - 'type': self.CHART_TYPE_NOVICE, + "id": 468, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 243, - 'type': self.CHART_TYPE_ADVANCED, + "id": 243, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 388, - 'type': self.CHART_TYPE_ADVANCED, + "id": 388, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 9, - 'tracks': [ + "level": 1, + "season_id": 9, + "tracks": [ { - 'id': 167, - 'type': self.CHART_TYPE_ADVANCED, + "id": 167, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 486, - 'type': self.CHART_TYPE_ADVANCED, + "id": 486, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 75, - 'type': self.CHART_TYPE_ADVANCED, + "id": 75, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 9, - 'tracks': [ + "level": 2, + "season_id": 9, + "tracks": [ { - 'id': 96, - 'type': self.CHART_TYPE_ADVANCED, + "id": 96, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 557, - 'type': self.CHART_TYPE_ADVANCED, + "id": 557, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 55, - 'type': self.CHART_TYPE_ADVANCED, + "id": 55, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 9, - 'tracks': [ + "level": 3, + "season_id": 9, + "tracks": [ { - 'id': 116, - 'type': self.CHART_TYPE_ADVANCED, + "id": 116, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 520, - 'type': self.CHART_TYPE_EXHAUST, + "id": 520, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 314, - 'type': self.CHART_TYPE_EXHAUST, + "id": 314, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 9, - 'tracks': [ + "level": 4, + "season_id": 9, + "tracks": [ { - 'id': 507, - 'type': self.CHART_TYPE_ADVANCED, + "id": 507, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 567, - 'type': self.CHART_TYPE_EXHAUST, + "id": 567, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 205, - 'type': self.CHART_TYPE_EXHAUST, + "id": 205, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 9, - 'tracks': [ + "level": 5, + "season_id": 9, + "tracks": [ { - 'id': 86, - 'type': self.CHART_TYPE_EXHAUST, + "id": 86, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 488, - 'type': self.CHART_TYPE_EXHAUST, + "id": 488, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 80, - 'type': self.CHART_TYPE_EXHAUST, + "id": 80, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 9, - 'tracks': [ + "level": 6, + "season_id": 9, + "tracks": [ { - 'id': 184, - 'type': self.CHART_TYPE_EXHAUST, + "id": 184, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 130, - 'type': self.CHART_TYPE_EXHAUST, + "id": 130, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 524, - 'type': self.CHART_TYPE_EXHAUST, + "id": 524, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 9, - 'tracks': [ + "level": 7, + "season_id": 9, + "tracks": [ { - 'id': 521, - 'type': self.CHART_TYPE_EXHAUST, + "id": 521, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 576, - 'type': self.CHART_TYPE_EXHAUST, + "id": 576, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 503, - 'type': self.CHART_TYPE_EXHAUST, + "id": 503, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 9, - 'tracks': [ + "level": 8, + "season_id": 9, + "tracks": [ { - 'id': 473, - 'type': self.CHART_TYPE_EXHAUST, + "id": 473, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 125, - 'type': self.CHART_TYPE_EXHAUST, + "id": 125, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 538, - 'type': self.CHART_TYPE_EXHAUST, + "id": 538, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 9, - 'tracks': [ + "level": 9, + "season_id": 9, + "tracks": [ { - 'id': 407, - 'type': self.CHART_TYPE_EXHAUST, + "id": 407, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 472, - 'type': self.CHART_TYPE_EXHAUST, + "id": 472, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 363, - 'type': self.CHART_TYPE_EXHAUST, + "id": 363, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 10, - 'tracks': [ + "level": 0, + "season_id": 10, + "tracks": [ { - 'id': 122, - 'type': self.CHART_TYPE_NOVICE, + "id": 122, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 209, - 'type': self.CHART_TYPE_ADVANCED, + "id": 209, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 24, - 'type': self.CHART_TYPE_ADVANCED, + "id": 24, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 10, - 'tracks': [ + "level": 1, + "season_id": 10, + "tracks": [ { - 'id': 405, - 'type': self.CHART_TYPE_ADVANCED, + "id": 405, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 554, - 'type': self.CHART_TYPE_ADVANCED, + "id": 554, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 77, - 'type': self.CHART_TYPE_ADVANCED, + "id": 77, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 10, - 'tracks': [ + "level": 2, + "season_id": 10, + "tracks": [ { - 'id': 426, - 'type': self.CHART_TYPE_ADVANCED, + "id": 426, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 262, - 'type': self.CHART_TYPE_ADVANCED, + "id": 262, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 194, - 'type': self.CHART_TYPE_ADVANCED, + "id": 194, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 10, - 'tracks': [ + "level": 3, + "season_id": 10, + "tracks": [ { - 'id': 343, - 'type': self.CHART_TYPE_ADVANCED, + "id": 343, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 564, - 'type': self.CHART_TYPE_EXHAUST, + "id": 564, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 248, - 'type': self.CHART_TYPE_EXHAUST, + "id": 248, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 10, - 'tracks': [ + "level": 4, + "season_id": 10, + "tracks": [ { - 'id': 126, - 'type': self.CHART_TYPE_ADVANCED, + "id": 126, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 471, - 'type': self.CHART_TYPE_EXHAUST, + "id": 471, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 276, - 'type': self.CHART_TYPE_EXHAUST, + "id": 276, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 10, - 'tracks': [ + "level": 5, + "season_id": 10, + "tracks": [ { - 'id': 476, - 'type': self.CHART_TYPE_EXHAUST, + "id": 476, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 120, - 'type': self.CHART_TYPE_EXHAUST, + "id": 120, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 57, - 'type': self.CHART_TYPE_EXHAUST, + "id": 57, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 10, - 'tracks': [ + "level": 6, + "season_id": 10, + "tracks": [ { - 'id': 146, - 'type': self.CHART_TYPE_EXHAUST, + "id": 146, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 622, - 'type': self.CHART_TYPE_EXHAUST, + "id": 622, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 152, - 'type': self.CHART_TYPE_EXHAUST, + "id": 152, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 10, - 'tracks': [ + "level": 7, + "season_id": 10, + "tracks": [ { - 'id': 562, - 'type': self.CHART_TYPE_EXHAUST, + "id": 562, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 531, - 'type': self.CHART_TYPE_EXHAUST, + "id": 531, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 449, - 'type': self.CHART_TYPE_EXHAUST, + "id": 449, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 10, - 'tracks': [ + "level": 8, + "season_id": 10, + "tracks": [ { - 'id': 404, - 'type': self.CHART_TYPE_EXHAUST, + "id": 404, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 123, - 'type': self.CHART_TYPE_EXHAUST, + "id": 123, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 607, - 'type': self.CHART_TYPE_EXHAUST, + "id": 607, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 10, - 'tracks': [ + "level": 9, + "season_id": 10, + "tracks": [ { - 'id': 469, - 'type': self.CHART_TYPE_EXHAUST, + "id": 469, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 496, - 'type': self.CHART_TYPE_EXHAUST, + "id": 496, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 289, - 'type': self.CHART_TYPE_EXHAUST, + "id": 289, + "type": self.CHART_TYPE_EXHAUST, }, ], }, # Manually specify IDs here since this has more than one level 11. { - 'id': 0, - 'level': 0, - 'season_id': 11, - 'tracks': [ + "id": 0, + "level": 0, + "season_id": 11, + "tracks": [ { - 'id': 190, - 'type': self.CHART_TYPE_ADVANCED, + "id": 190, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 568, - 'type': self.CHART_TYPE_ADVANCED, + "id": 568, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 191, - 'type': self.CHART_TYPE_ADVANCED, + "id": 191, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'id': 1, - 'level': 1, - 'season_id': 11, - 'tracks': [ + "id": 1, + "level": 1, + "season_id": 11, + "tracks": [ { - 'id': 278, - 'type': self.CHART_TYPE_ADVANCED, + "id": 278, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 41, - 'type': self.CHART_TYPE_EXHAUST, + "id": 41, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 18, - 'type': self.CHART_TYPE_ADVANCED, + "id": 18, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'id': 2, - 'level': 2, - 'season_id': 11, - 'tracks': [ + "id": 2, + "level": 2, + "season_id": 11, + "tracks": [ { - 'id': 15, - 'type': self.CHART_TYPE_EXHAUST, + "id": 15, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 483, - 'type': self.CHART_TYPE_ADVANCED, + "id": 483, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 467, - 'type': self.CHART_TYPE_ADVANCED, + "id": 467, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'id': 3, - 'level': 3, - 'season_id': 11, - 'tracks': [ + "id": 3, + "level": 3, + "season_id": 11, + "tracks": [ { - 'id': 585, - 'type': self.CHART_TYPE_ADVANCED, + "id": 585, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 486, - 'type': self.CHART_TYPE_EXHAUST, + "id": 486, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 48, - 'type': self.CHART_TYPE_EXHAUST, + "id": 48, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 4, - 'level': 4, - 'season_id': 11, - 'tracks': [ + "id": 4, + "level": 4, + "season_id": 11, + "tracks": [ { - 'id': 103, - 'type': self.CHART_TYPE_EXHAUST, + "id": 103, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 335, - 'type': self.CHART_TYPE_EXHAUST, + "id": 335, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 224, - 'type': self.CHART_TYPE_EXHAUST, + "id": 224, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 5, - 'level': 5, - 'season_id': 11, - 'tracks': [ + "id": 5, + "level": 5, + "season_id": 11, + "tracks": [ { - 'id': 275, - 'type': self.CHART_TYPE_EXHAUST, + "id": 275, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 438, - 'type': self.CHART_TYPE_EXHAUST, + "id": 438, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 67, - 'type': self.CHART_TYPE_EXHAUST, + "id": 67, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 6, - 'level': 6, - 'season_id': 11, - 'tracks': [ + "id": 6, + "level": 6, + "season_id": 11, + "tracks": [ { - 'id': 202, - 'type': self.CHART_TYPE_EXHAUST, + "id": 202, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 264, - 'type': self.CHART_TYPE_EXHAUST, + "id": 264, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 526, - 'type': self.CHART_TYPE_EXHAUST, + "id": 526, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 7, - 'level': 7, - 'season_id': 11, - 'tracks': [ + "id": 7, + "level": 7, + "season_id": 11, + "tracks": [ { - 'id': 131, - 'type': self.CHART_TYPE_EXHAUST, + "id": 131, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 155, - 'type': self.CHART_TYPE_EXHAUST, + "id": 155, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 394, - 'type': self.CHART_TYPE_EXHAUST, + "id": 394, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 8, - 'level': 8, - 'season_id': 11, - 'tracks': [ + "id": 8, + "level": 8, + "season_id": 11, + "tracks": [ { - 'id': 396, - 'type': self.CHART_TYPE_EXHAUST, + "id": 396, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 346, - 'type': self.CHART_TYPE_EXHAUST, + "id": 346, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 510, - 'type': self.CHART_TYPE_EXHAUST, + "id": 510, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 9, - 'level': 9, - 'season_id': 11, - 'tracks': [ + "id": 9, + "level": 9, + "season_id": 11, + "tracks": [ { - 'id': 326, - 'type': self.CHART_TYPE_EXHAUST, + "id": 326, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 470, - 'type': self.CHART_TYPE_EXHAUST, + "id": 470, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 362, - 'type': self.CHART_TYPE_EXHAUST, + "id": 362, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 10, - 'level': 10, - 'season_id': 11, - 'tracks': [ + "id": 10, + "level": 10, + "season_id": 11, + "tracks": [ { - 'id': 339, - 'type': self.CHART_TYPE_EXHAUST, + "id": 339, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 418, - 'type': self.CHART_TYPE_EXHAUST, + "id": 418, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 525, - 'type': self.CHART_TYPE_EXHAUST, + "id": 525, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 11, - 'level': 10, - 'season_id': 11, - 'tracks': [ + "id": 11, + "level": 10, + "season_id": 11, + "tracks": [ { - 'id': 36, - 'type': self.CHART_TYPE_INFINITE, + "id": 36, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 47, - 'type': self.CHART_TYPE_INFINITE, + "id": 47, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 73, - 'type': self.CHART_TYPE_INFINITE, + "id": 73, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'id': 12, - 'level': 11, - 'season_id': 11, - 'tracks': [ + "id": 12, + "level": 11, + "season_id": 11, + "tracks": [ { - 'id': 126, - 'type': self.CHART_TYPE_INFINITE, + "id": 126, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 367, - 'type': self.CHART_TYPE_EXHAUST, + "id": 367, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 636, - 'type': self.CHART_TYPE_INFINITE, + "id": 636, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'level': 0, - 'season_id': 12, - 'tracks': [ + "level": 0, + "season_id": 12, + "tracks": [ { - 'id': 507, - 'type': self.CHART_TYPE_NOVICE, + "id": 507, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 671, - 'type': self.CHART_TYPE_ADVANCED, + "id": 671, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 176, - 'type': self.CHART_TYPE_ADVANCED, + "id": 176, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 12, - 'tracks': [ + "level": 1, + "season_id": 12, + "tracks": [ { - 'id': 27, - 'type': self.CHART_TYPE_ADVANCED, + "id": 27, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 520, - 'type': self.CHART_TYPE_ADVANCED, + "id": 520, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 103, - 'type': self.CHART_TYPE_ADVANCED, + "id": 103, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 12, - 'tracks': [ + "level": 2, + "season_id": 12, + "tracks": [ { - 'id': 478, - 'type': self.CHART_TYPE_ADVANCED, + "id": 478, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 264, - 'type': self.CHART_TYPE_ADVANCED, + "id": 264, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 322, - 'type': self.CHART_TYPE_ADVANCED, + "id": 322, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 12, - 'tracks': [ + "level": 3, + "season_id": 12, + "tracks": [ { - 'id': 107, - 'type': self.CHART_TYPE_ADVANCED, + "id": 107, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 520, - 'type': self.CHART_TYPE_EXHAUST, + "id": 520, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 163, - 'type': self.CHART_TYPE_EXHAUST, + "id": 163, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 12, - 'tracks': [ + "level": 4, + "season_id": 12, + "tracks": [ { - 'id': 408, - 'type': self.CHART_TYPE_ADVANCED, + "id": 408, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 34, - 'type': self.CHART_TYPE_INFINITE, + "id": 34, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 678, - 'type': self.CHART_TYPE_EXHAUST, + "id": 678, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 12, - 'tracks': [ + "level": 5, + "season_id": 12, + "tracks": [ { - 'id': 481, - 'type': self.CHART_TYPE_EXHAUST, + "id": 481, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 436, - 'type': self.CHART_TYPE_EXHAUST, + "id": 436, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 104, - 'type': self.CHART_TYPE_EXHAUST, + "id": 104, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 12, - 'tracks': [ + "level": 6, + "season_id": 12, + "tracks": [ { - 'id': 55, - 'type': self.CHART_TYPE_EXHAUST, + "id": 55, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 415, - 'type': self.CHART_TYPE_EXHAUST, + "id": 415, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 512, - 'type': self.CHART_TYPE_EXHAUST, + "id": 512, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 12, - 'tracks': [ + "level": 7, + "season_id": 12, + "tracks": [ { - 'id': 483, - 'type': self.CHART_TYPE_EXHAUST, + "id": 483, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 509, - 'type': self.CHART_TYPE_EXHAUST, + "id": 509, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 557, - 'type': self.CHART_TYPE_EXHAUST, + "id": 557, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 12, - 'tracks': [ + "level": 8, + "season_id": 12, + "tracks": [ { - 'id': 497, - 'type': self.CHART_TYPE_EXHAUST, + "id": 497, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 58, - 'type': self.CHART_TYPE_INFINITE, + "id": 58, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 166, - 'type': self.CHART_TYPE_EXHAUST, + "id": 166, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 12, - 'tracks': [ + "level": 9, + "season_id": 12, + "tracks": [ { - 'id': 581, - 'type': self.CHART_TYPE_EXHAUST, + "id": 581, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 439, - 'type': self.CHART_TYPE_EXHAUST, + "id": 439, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 443, - 'type': self.CHART_TYPE_EXHAUST, + "id": 443, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 13, - 'tracks': [ + "level": 0, + "season_id": 13, + "tracks": [ { - 'id': 250, - 'type': self.CHART_TYPE_NOVICE, + "id": 250, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 245, - 'type': self.CHART_TYPE_ADVANCED, + "id": 245, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 186, - 'type': self.CHART_TYPE_ADVANCED, + "id": 186, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 13, - 'tracks': [ + "level": 1, + "season_id": 13, + "tracks": [ { - 'id': 13, - 'type': self.CHART_TYPE_ADVANCED, + "id": 13, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 618, - 'type': self.CHART_TYPE_ADVANCED, + "id": 618, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 31, - 'type': self.CHART_TYPE_ADVANCED, + "id": 31, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 13, - 'tracks': [ + "level": 2, + "season_id": 13, + "tracks": [ { - 'id': 436, - 'type': self.CHART_TYPE_ADVANCED, + "id": 436, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 144, - 'type': self.CHART_TYPE_EXHAUST, + "id": 144, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 79, - 'type': self.CHART_TYPE_ADVANCED, + "id": 79, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 13, - 'tracks': [ + "level": 3, + "season_id": 13, + "tracks": [ { - 'id': 489, - 'type': self.CHART_TYPE_ADVANCED, + "id": 489, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 245, - 'type': self.CHART_TYPE_EXHAUST, + "id": 245, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 222, - 'type': self.CHART_TYPE_EXHAUST, + "id": 222, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 13, - 'tracks': [ + "level": 4, + "season_id": 13, + "tracks": [ { - 'id': 556, - 'type': self.CHART_TYPE_ADVANCED, + "id": 556, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 233, - 'type': self.CHART_TYPE_EXHAUST, + "id": 233, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 565, - 'type': self.CHART_TYPE_EXHAUST, + "id": 565, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 13, - 'tracks': [ + "level": 5, + "season_id": 13, + "tracks": [ { - 'id': 354, - 'type': self.CHART_TYPE_EXHAUST, + "id": 354, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 281, - 'type': self.CHART_TYPE_EXHAUST, + "id": 281, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 2, - 'type': self.CHART_TYPE_EXHAUST, + "id": 2, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 13, - 'tracks': [ + "level": 6, + "season_id": 13, + "tracks": [ { - 'id': 14, - 'type': self.CHART_TYPE_EXHAUST, + "id": 14, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 267, - 'type': self.CHART_TYPE_EXHAUST, + "id": 267, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 490, - 'type': self.CHART_TYPE_EXHAUST, + "id": 490, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 13, - 'tracks': [ + "level": 7, + "season_id": 13, + "tracks": [ { - 'id': 467, - 'type': self.CHART_TYPE_EXHAUST, + "id": 467, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 585, - 'type': self.CHART_TYPE_EXHAUST, + "id": 585, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 560, - 'type': self.CHART_TYPE_EXHAUST, + "id": 560, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 13, - 'tracks': [ + "level": 8, + "season_id": 13, + "tracks": [ { - 'id': 599, - 'type': self.CHART_TYPE_EXHAUST, + "id": 599, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 101, - 'type': self.CHART_TYPE_EXHAUST, + "id": 101, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 109, - 'type': self.CHART_TYPE_EXHAUST, + "id": 109, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 13, - 'tracks': [ + "level": 9, + "season_id": 13, + "tracks": [ { - 'id': 630, - 'type': self.CHART_TYPE_EXHAUST, + "id": 630, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 408, - 'type': self.CHART_TYPE_EXHAUST, + "id": 408, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 393, - 'type': self.CHART_TYPE_EXHAUST, + "id": 393, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 14, - 'tracks': [ + "level": 0, + "season_id": 14, + "tracks": [ { - 'id': 63, - 'type': self.CHART_TYPE_NOVICE, + "id": 63, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 328, - 'type': self.CHART_TYPE_ADVANCED, + "id": 328, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 266, - 'type': self.CHART_TYPE_ADVANCED, + "id": 266, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 14, - 'tracks': [ + "level": 1, + "season_id": 14, + "tracks": [ { - 'id': 23, - 'type': self.CHART_TYPE_ADVANCED, + "id": 23, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 453, - 'type': self.CHART_TYPE_ADVANCED, + "id": 453, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 153, - 'type': self.CHART_TYPE_ADVANCED, + "id": 153, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 14, - 'tracks': [ + "level": 2, + "season_id": 14, + "tracks": [ { - 'id': 458, - 'type': self.CHART_TYPE_ADVANCED, + "id": 458, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 514, - 'type': self.CHART_TYPE_ADVANCED, + "id": 514, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 71, - 'type': self.CHART_TYPE_ADVANCED, + "id": 71, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 14, - 'tracks': [ + "level": 3, + "season_id": 14, + "tracks": [ { - 'id': 392, - 'type': self.CHART_TYPE_ADVANCED, + "id": 392, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 388, - 'type': self.CHART_TYPE_EXHAUST, + "id": 388, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 569, - 'type': self.CHART_TYPE_EXHAUST, + "id": 569, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 14, - 'tracks': [ + "level": 4, + "season_id": 14, + "tracks": [ { - 'id': 508, - 'type': self.CHART_TYPE_ADVANCED, + "id": 508, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 405, - 'type': self.CHART_TYPE_EXHAUST, + "id": 405, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 266, - 'type': self.CHART_TYPE_EXHAUST, + "id": 266, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 14, - 'tracks': [ + "level": 5, + "season_id": 14, + "tracks": [ { - 'id': 50, - 'type': self.CHART_TYPE_EXHAUST, + "id": 50, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 172, - 'type': self.CHART_TYPE_EXHAUST, + "id": 172, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 33, - 'type': self.CHART_TYPE_EXHAUST, + "id": 33, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 14, - 'tracks': [ + "level": 6, + "season_id": 14, + "tracks": [ { - 'id': 210, - 'type': self.CHART_TYPE_EXHAUST, + "id": 210, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 232, - 'type': self.CHART_TYPE_EXHAUST, + "id": 232, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 485, - 'type': self.CHART_TYPE_EXHAUST, + "id": 485, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 14, - 'tracks': [ + "level": 7, + "season_id": 14, + "tracks": [ { - 'id': 457, - 'type': self.CHART_TYPE_EXHAUST, + "id": 457, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 514, - 'type': self.CHART_TYPE_EXHAUST, + "id": 514, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 556, - 'type': self.CHART_TYPE_EXHAUST, + "id": 556, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 14, - 'tracks': [ + "level": 8, + "season_id": 14, + "tracks": [ { - 'id': 534, - 'type': self.CHART_TYPE_EXHAUST, + "id": 534, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 273, - 'type': self.CHART_TYPE_EXHAUST, + "id": 273, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 220, - 'type': self.CHART_TYPE_EXHAUST, + "id": 220, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 14, - 'tracks': [ + "level": 9, + "season_id": 14, + "tracks": [ { - 'id': 420, - 'type': self.CHART_TYPE_EXHAUST, + "id": 420, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 444, - 'type': self.CHART_TYPE_EXHAUST, + "id": 444, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 151, - 'type': self.CHART_TYPE_EXHAUST, + "id": 151, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 15, - 'tracks': [ + "level": 0, + "season_id": 15, + "tracks": [ { - 'id': 117, - 'type': self.CHART_TYPE_NOVICE, + "id": 117, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 564, - 'type': self.CHART_TYPE_ADVANCED, + "id": 564, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 318, - 'type': self.CHART_TYPE_ADVANCED, + "id": 318, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 15, - 'tracks': [ + "level": 1, + "season_id": 15, + "tracks": [ { - 'id': 93, - 'type': self.CHART_TYPE_ADVANCED, + "id": 93, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 308, - 'type': self.CHART_TYPE_ADVANCED, + "id": 308, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 49, - 'type': self.CHART_TYPE_ADVANCED, + "id": 49, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 15, - 'tracks': [ + "level": 2, + "season_id": 15, + "tracks": [ { - 'id': 317, - 'type': self.CHART_TYPE_ADVANCED, + "id": 317, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 335, - 'type': self.CHART_TYPE_ADVANCED, + "id": 335, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 239, - 'type': self.CHART_TYPE_ADVANCED, + "id": 239, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 15, - 'tracks': [ + "level": 3, + "season_id": 15, + "tracks": [ { - 'id': 439, - 'type': self.CHART_TYPE_ADVANCED, + "id": 439, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 44, - 'type': self.CHART_TYPE_EXHAUST, + "id": 44, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 243, - 'type': self.CHART_TYPE_EXHAUST, + "id": 243, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 15, - 'tracks': [ + "level": 4, + "season_id": 15, + "tracks": [ { - 'id': 158, - 'type': self.CHART_TYPE_ADVANCED, + "id": 158, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 175, - 'type': self.CHART_TYPE_EXHAUST, + "id": 175, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 150, - 'type': self.CHART_TYPE_EXHAUST, + "id": 150, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 15, - 'tracks': [ + "level": 5, + "season_id": 15, + "tracks": [ { - 'id': 162, - 'type': self.CHART_TYPE_EXHAUST, + "id": 162, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 79, - 'type': self.CHART_TYPE_EXHAUST, + "id": 79, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 386, - 'type': self.CHART_TYPE_EXHAUST, + "id": 386, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 15, - 'tracks': [ + "level": 6, + "season_id": 15, + "tracks": [ { - 'id': 99, - 'type': self.CHART_TYPE_EXHAUST, + "id": 99, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 22, - 'type': self.CHART_TYPE_INFINITE, + "id": 22, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 164, - 'type': self.CHART_TYPE_EXHAUST, + "id": 164, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 15, - 'tracks': [ + "level": 7, + "season_id": 15, + "tracks": [ { - 'id': 406, - 'type': self.CHART_TYPE_EXHAUST, + "id": 406, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 344, - 'type': self.CHART_TYPE_EXHAUST, + "id": 344, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 6, - 'type': self.CHART_TYPE_INFINITE, + "id": 6, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'level': 8, - 'season_id': 15, - 'tracks': [ + "level": 8, + "season_id": 15, + "tracks": [ { - 'id': 660, - 'type': self.CHART_TYPE_EXHAUST, + "id": 660, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 378, - 'type': self.CHART_TYPE_EXHAUST, + "id": 378, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 465, - 'type': self.CHART_TYPE_EXHAUST, + "id": 465, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 15, - 'tracks': [ + "level": 9, + "season_id": 15, + "tracks": [ { - 'id': 413, - 'type': self.CHART_TYPE_EXHAUST, + "id": 413, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 221, - 'type': self.CHART_TYPE_EXHAUST, + "id": 221, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 342, - 'type': self.CHART_TYPE_EXHAUST, + "id": 342, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 10, - 'season_id': 15, - 'tracks': [ + "level": 10, + "season_id": 15, + "tracks": [ { - 'id': 125, - 'type': self.CHART_TYPE_INFINITE, + "id": 125, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 123, - 'type': self.CHART_TYPE_INFINITE, + "id": 123, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 124, - 'type': self.CHART_TYPE_INFINITE, + "id": 124, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'level': 11, - 'season_id': 15, - 'tracks': [ + "level": 11, + "season_id": 15, + "tracks": [ { - 'id': 366, - 'type': self.CHART_TYPE_EXHAUST, + "id": 366, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 695, - 'type': self.CHART_TYPE_INFINITE, + "id": 695, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 692, - 'type': self.CHART_TYPE_INFINITE, + "id": 692, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'level': 0, - 'season_id': 16, - 'tracks': [ + "level": 0, + "season_id": 16, + "tracks": [ { - 'id': 343, - 'type': self.CHART_TYPE_NOVICE, + "id": 343, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 144, - 'type': self.CHART_TYPE_ADVANCED, + "id": 144, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 569, - 'type': self.CHART_TYPE_ADVANCED, + "id": 569, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 16, - 'tracks': [ + "level": 1, + "season_id": 16, + "tracks": [ { - 'id': 515, - 'type': self.CHART_TYPE_ADVANCED, + "id": 515, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 254, - 'type': self.CHART_TYPE_ADVANCED, + "id": 254, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 354, - 'type': self.CHART_TYPE_ADVANCED, + "id": 354, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 16, - 'tracks': [ + "level": 2, + "season_id": 16, + "tracks": [ { - 'id': 441, - 'type': self.CHART_TYPE_ADVANCED, + "id": 441, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 524, - 'type': self.CHART_TYPE_ADVANCED, + "id": 524, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 187, - 'type': self.CHART_TYPE_ADVANCED, + "id": 187, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 16, - 'tracks': [ + "level": 3, + "season_id": 16, + "tracks": [ { - 'id': 117, - 'type': self.CHART_TYPE_ADVANCED, + "id": 117, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 446, - 'type': self.CHART_TYPE_EXHAUST, + "id": 446, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 435, - 'type': self.CHART_TYPE_EXHAUST, + "id": 435, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 16, - 'tracks': [ + "level": 4, + "season_id": 16, + "tracks": [ { - 'id': 180, - 'type': self.CHART_TYPE_ADVANCED, + "id": 180, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 260, - 'type': self.CHART_TYPE_EXHAUST, + "id": 260, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 451, - 'type': self.CHART_TYPE_EXHAUST, + "id": 451, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 16, - 'tracks': [ + "level": 5, + "season_id": 16, + "tracks": [ { - 'id': 58, - 'type': self.CHART_TYPE_EXHAUST, + "id": 58, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 195, - 'type': self.CHART_TYPE_EXHAUST, + "id": 195, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 236, - 'type': self.CHART_TYPE_EXHAUST, + "id": 236, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 16, - 'tracks': [ + "level": 6, + "season_id": 16, + "tracks": [ { - 'id': 440, - 'type': self.CHART_TYPE_EXHAUST, + "id": 440, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 112, - 'type': self.CHART_TYPE_EXHAUST, + "id": 112, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 401, - 'type': self.CHART_TYPE_EXHAUST, + "id": 401, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 16, - 'tracks': [ + "level": 7, + "season_id": 16, + "tracks": [ { - 'id': 325, - 'type': self.CHART_TYPE_EXHAUST, + "id": 325, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 387, - 'type': self.CHART_TYPE_EXHAUST, + "id": 387, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 42, - 'type': self.CHART_TYPE_INFINITE, + "id": 42, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'level': 8, - 'season_id': 16, - 'tracks': [ + "level": 8, + "season_id": 16, + "tracks": [ { - 'id': 676, - 'type': self.CHART_TYPE_EXHAUST, + "id": 676, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 494, - 'type': self.CHART_TYPE_INFINITE, + "id": 494, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 234, - 'type': self.CHART_TYPE_EXHAUST, + "id": 234, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 16, - 'tracks': [ + "level": 9, + "season_id": 16, + "tracks": [ { - 'id': 155, - 'type': self.CHART_TYPE_INFINITE, + "id": 155, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 623, - 'type': self.CHART_TYPE_EXHAUST, + "id": 623, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 329, - 'type': self.CHART_TYPE_EXHAUST, + "id": 329, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 10, - 'season_id': 16, - 'tracks': [ + "level": 10, + "season_id": 16, + "tracks": [ { - 'id': 450, - 'type': self.CHART_TYPE_EXHAUST, + "id": 450, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 634, - 'type': self.CHART_TYPE_EXHAUST, + "id": 634, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 360, - 'type': self.CHART_TYPE_EXHAUST, + "id": 360, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 11, - 'season_id': 16, - 'tracks': [ + "level": 11, + "season_id": 16, + "tracks": [ { - 'id': 116, - 'type': self.CHART_TYPE_INFINITE, + "id": 116, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 693, - 'type': self.CHART_TYPE_INFINITE, + "id": 693, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 694, - 'type': self.CHART_TYPE_INFINITE, + "id": 694, + "type": self.CHART_TYPE_INFINITE, }, ], }, @@ -3045,260 +3044,302 @@ class SoundVoltexGravityWarsSeason1( def handle_game_3_hiscore_request(self, request: Node) -> Node: # Grab location for local scores - locid = ID.parse_machine_id(request.child_value('locid')) + locid = ID.parse_machine_id(request.child_value("locid")) # Start the response packet - game = Node.void('game_3') + game = Node.void("game_3") # First, grab hit chart playcounts = self.data.local.music.get_hit_chart(self.game, self.version, 1024) - hitchart = Node.void('hitchart') + hitchart = Node.void("hitchart") game.add_child(hitchart) for (songid, count) in playcounts: - info = Node.void('info') + info = Node.void("info") hitchart.add_child(info) - info.add_child(Node.u32('id', songid)) - info.add_child(Node.u32('cnt', count)) + info.add_child(Node.u32("id", songid)) + info.add_child(Node.u32("cnt", count)) # Now, grab user records records = self.data.remote.music.get_all_records(self.game, self.version) missing_users = [userid for (userid, _) in records] - users = {userid: profile for (userid, profile) in self.get_any_profiles(missing_users)} + users = { + userid: profile + for (userid, profile) in self.get_any_profiles(missing_users) + } - hiscore_allover = Node.void('hiscore_allover') + hiscore_allover = Node.void("hiscore_allover") game.add_child(hiscore_allover) # Output records for (userid, score) in records: - info = Node.void('info') + info = Node.void("info") if userid not in users: - raise Exception('Logic error, missing profile for user!') + raise Exception("Logic error, missing profile for user!") profile = users[userid] - info.add_child(Node.u32('id', score.id)) - info.add_child(Node.u32('type', score.chart)) - info.add_child(Node.string('name', profile.get_str('name'))) - info.add_child(Node.string('code', ID.format_extid(profile.extid))) - info.add_child(Node.u32('score', score.points)) + info.add_child(Node.u32("id", score.id)) + info.add_child(Node.u32("type", score.chart)) + info.add_child(Node.string("name", profile.get_str("name"))) + info.add_child(Node.string("code", ID.format_extid(profile.extid))) + info.add_child(Node.u32("score", score.points)) # Add to global scores hiscore_allover.add_child(info) # Now, grab local records area_users = [ - uid for (uid, prof) in self.data.local.user.get_all_profiles(self.game, self.version) - if prof.get_int('loc', -1) == locid + uid + for (uid, prof) in self.data.local.user.get_all_profiles( + self.game, self.version + ) + if prof.get_int("loc", -1) == locid ] - records = self.data.local.music.get_all_records(self.game, self.version, userlist=area_users) + records = self.data.local.music.get_all_records( + self.game, self.version, userlist=area_users + ) missing_users = [userid for (userid, _) in records if userid not in users] for (userid, profile) in self.get_any_profiles(missing_users): users[userid] = profile - hiscore_location = Node.void('hiscore_location') + hiscore_location = Node.void("hiscore_location") game.add_child(hiscore_location) # Output records for (userid, score) in records: - info = Node.void('info') + info = Node.void("info") if userid not in users: - raise Exception('Logic error, missing profile for user!') + raise Exception("Logic error, missing profile for user!") profile = users[userid] - info.add_child(Node.u32('id', score.id)) - info.add_child(Node.u32('type', score.chart)) - info.add_child(Node.string('name', profile.get_str('name'))) - info.add_child(Node.string('code', ID.format_extid(profile.extid))) - info.add_child(Node.u32('score', score.points)) + info.add_child(Node.u32("id", score.id)) + info.add_child(Node.u32("type", score.chart)) + info.add_child(Node.string("name", profile.get_str("name"))) + info.add_child(Node.string("code", ID.format_extid(profile.extid))) + info.add_child(Node.u32("score", score.points)) # Add to global scores hiscore_location.add_child(info) # Now, grab clear rates - clear_rate = Node.void('clear_rate') + clear_rate = Node.void("clear_rate") game.add_child(clear_rate) clears = self.get_clear_rates() for songid in clears: for chart in clears[songid]: - if clears[songid][chart]['total'] > 0: - rate = float(clears[songid][chart]['clears']) / float(clears[songid][chart]['total']) - dnode = Node.void('d') + if clears[songid][chart]["total"] > 0: + rate = float(clears[songid][chart]["clears"]) / float( + clears[songid][chart]["total"] + ) + dnode = Node.void("d") clear_rate.add_child(dnode) - dnode.add_child(Node.u32('id', songid)) - dnode.add_child(Node.u32('type', chart)) - dnode.add_child(Node.s16('cr', int(rate * 10000))) + dnode.add_child(Node.u32("id", songid)) + dnode.add_child(Node.u32("type", chart)) + dnode.add_child(Node.s16("cr", int(rate * 10000))) return game def format_profile(self, userid: UserID, profile: Profile) -> Node: - game = Node.void('game_3') + game = Node.void("game_3") # Generic profile stuff - game.add_child(Node.string('name', profile.get_str('name'))) - game.add_child(Node.string('code', ID.format_extid(profile.extid))) - game.add_child(Node.u32('gamecoin_packet', profile.get_int('packet'))) - game.add_child(Node.u32('gamecoin_block', profile.get_int('block'))) - game.add_child(Node.s16('skill_name_id', profile.get_int('skill_name_id', -1))) - game.add_child(Node.s32_array('hidden_param', profile.get_int_array('hidden_param', 20))) - game.add_child(Node.u32('blaster_energy', profile.get_int('blaster_energy'))) - game.add_child(Node.u32('blaster_count', profile.get_int('blaster_count'))) + game.add_child(Node.string("name", profile.get_str("name"))) + game.add_child(Node.string("code", ID.format_extid(profile.extid))) + game.add_child(Node.u32("gamecoin_packet", profile.get_int("packet"))) + game.add_child(Node.u32("gamecoin_block", profile.get_int("block"))) + game.add_child(Node.s16("skill_name_id", profile.get_int("skill_name_id", -1))) + game.add_child( + Node.s32_array("hidden_param", profile.get_int_array("hidden_param", 20)) + ) + game.add_child(Node.u32("blaster_energy", profile.get_int("blaster_energy"))) + game.add_child(Node.u32("blaster_count", profile.get_int("blaster_count"))) # Play statistics statistics = self.get_play_statistics(userid) - game.add_child(Node.u32('play_count', statistics.total_plays)) - game.add_child(Node.u32('daily_count', statistics.today_plays)) - game.add_child(Node.u32('play_chain', statistics.consecutive_days)) + game.add_child(Node.u32("play_count", statistics.total_plays)) + game.add_child(Node.u32("daily_count", statistics.today_plays)) + game.add_child(Node.u32("play_chain", statistics.consecutive_days)) # Last played stuff - if 'last' in profile: - lastdict = profile.get_dict('last') - last = Node.void('last') + if "last" in profile: + lastdict = profile.get_dict("last") + last = Node.void("last") game.add_child(last) - last.add_child(Node.s32('music_id', lastdict.get_int('music_id', -1))) - last.add_child(Node.u8('music_type', lastdict.get_int('music_type'))) - last.add_child(Node.u8('sort_type', lastdict.get_int('sort_type'))) - last.add_child(Node.u8('narrow_down', lastdict.get_int('narrow_down'))) - last.add_child(Node.u8('headphone', lastdict.get_int('headphone'))) - last.add_child(Node.u16('appeal_id', lastdict.get_int('appeal_id', 1001))) - last.add_child(Node.u16('comment_id', lastdict.get_int('comment_id'))) - last.add_child(Node.u8('gauge_option', lastdict.get_int('gauge_option'))) + last.add_child(Node.s32("music_id", lastdict.get_int("music_id", -1))) + last.add_child(Node.u8("music_type", lastdict.get_int("music_type"))) + last.add_child(Node.u8("sort_type", lastdict.get_int("sort_type"))) + last.add_child(Node.u8("narrow_down", lastdict.get_int("narrow_down"))) + last.add_child(Node.u8("headphone", lastdict.get_int("headphone"))) + last.add_child(Node.u16("appeal_id", lastdict.get_int("appeal_id", 1001))) + last.add_child(Node.u16("comment_id", lastdict.get_int("comment_id"))) + last.add_child(Node.u8("gauge_option", lastdict.get_int("gauge_option"))) # Item unlocks - itemnode = Node.void('item') + itemnode = Node.void("item") game.add_child(itemnode) game_config = self.get_game_config() - achievements = self.data.local.user.get_achievements(self.game, self.version, userid) + achievements = self.data.local.user.get_achievements( + self.game, self.version, userid + ) for item in achievements: - if item.type[:5] != 'item_': + if item.type[:5] != "item_": continue itemtype = int(item.type[5:]) - if game_config.get_bool('force_unlock_songs') and itemtype == self.GAME_CATALOG_TYPE_SONG: + if ( + game_config.get_bool("force_unlock_songs") + and itemtype == self.GAME_CATALOG_TYPE_SONG + ): # Don't echo unlocked songs, we will add all of them later continue - if game_config.get_bool('force_unlock_cards') and itemtype == self.GAME_CATALOG_TYPE_APPEAL_CARD: + if ( + game_config.get_bool("force_unlock_cards") + and itemtype == self.GAME_CATALOG_TYPE_APPEAL_CARD + ): # Don't echo unlocked appeal cards, we will add all of them later continue - info = Node.void('info') + info = Node.void("info") itemnode.add_child(info) - info.add_child(Node.u8('type', itemtype)) - info.add_child(Node.u32('id', item.id)) - info.add_child(Node.u32('param', item.data.get_int('param'))) + info.add_child(Node.u8("type", itemtype)) + info.add_child(Node.u32("id", item.id)) + info.add_child(Node.u32("param", item.data.get_int("param"))) - if game_config.get_bool('force_unlock_songs'): + if game_config.get_bool("force_unlock_songs"): ids: Dict[int, int] = {} songs = self.data.local.music.get_all_songs(self.game, self.version) for song in songs: if song.id not in ids: ids[song.id] = 0 - if song.data.get_int('difficulty') > 0: + if song.data.get_int("difficulty") > 0: ids[song.id] = ids[song.id] | (1 << song.chart) for itemid in ids: if ids[itemid] == 0: continue - info = Node.void('info') + info = Node.void("info") itemnode.add_child(info) - info.add_child(Node.u8('type', self.GAME_CATALOG_TYPE_SONG)) - info.add_child(Node.u32('id', itemid)) - info.add_child(Node.u32('param', ids[itemid])) + info.add_child(Node.u8("type", self.GAME_CATALOG_TYPE_SONG)) + info.add_child(Node.u32("id", itemid)) + info.add_child(Node.u32("param", ids[itemid])) - if game_config.get_bool('force_unlock_cards'): + if game_config.get_bool("force_unlock_cards"): catalog = self.data.local.game.get_items(self.game, self.version) for unlock in catalog: - if unlock.type != 'appealcard': + if unlock.type != "appealcard": continue - info = Node.void('info') + info = Node.void("info") itemnode.add_child(info) - info.add_child(Node.u8('type', self.GAME_CATALOG_TYPE_APPEAL_CARD)) - info.add_child(Node.u32('id', unlock.id)) - info.add_child(Node.u32('param', 1)) + info.add_child(Node.u8("type", self.GAME_CATALOG_TYPE_APPEAL_CARD)) + info.add_child(Node.u32("id", unlock.id)) + info.add_child(Node.u32("param", 1)) # Skill courses - skill = Node.void('skill') + skill = Node.void("skill") game.add_child(skill) - course_all = Node.void('course_all') + course_all = Node.void("course_all") skill.add_child(course_all) for course in achievements: - if course.type != 'course': + if course.type != "course": continue course_id = course.id % 100 season_id = int(course.id / 100) - info = Node.void('d') + info = Node.void("d") course_all.add_child(info) - info.add_child(Node.s16('crsid', course_id)) - info.add_child(Node.s16('ct', course.data.get_int('clear_type'))) - info.add_child(Node.s16('ar', course.data.get_int('achievement_rate'))) - info.add_child(Node.s32('ssnid', season_id)) + info.add_child(Node.s16("crsid", course_id)) + info.add_child(Node.s16("ct", course.data.get_int("clear_type"))) + info.add_child(Node.s16("ar", course.data.get_int("achievement_rate"))) + info.add_child(Node.s32("ssnid", season_id)) # Story mode unlocks - storynode = Node.void('story') + storynode = Node.void("story") game.add_child(storynode) for story in achievements: - if story.type != 'story': + if story.type != "story": continue - info = Node.void('info') + info = Node.void("info") storynode.add_child(info) - info.add_child(Node.s32('story_id', story.id)) - info.add_child(Node.s32('progress_id', story.data.get_int('progress_id'))) - info.add_child(Node.s32('progress_param', story.data.get_int('progress_param'))) - info.add_child(Node.s32('clear_cnt', story.data.get_int('clear_cnt'))) - info.add_child(Node.u32('route_flg', story.data.get_int('route_flg'))) + info.add_child(Node.s32("story_id", story.id)) + info.add_child(Node.s32("progress_id", story.data.get_int("progress_id"))) + info.add_child( + Node.s32("progress_param", story.data.get_int("progress_param")) + ) + info.add_child(Node.s32("clear_cnt", story.data.get_int("clear_cnt"))) + info.add_child(Node.u32("route_flg", story.data.get_int("route_flg"))) return game - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() # Update blaster energy and in-game currencies - earned_gamecoin_packet = request.child_value('earned_gamecoin_packet') + earned_gamecoin_packet = request.child_value("earned_gamecoin_packet") if earned_gamecoin_packet is not None: - newprofile.replace_int('packet', newprofile.get_int('packet') + earned_gamecoin_packet) - earned_gamecoin_block = request.child_value('earned_gamecoin_block') + newprofile.replace_int( + "packet", newprofile.get_int("packet") + earned_gamecoin_packet + ) + earned_gamecoin_block = request.child_value("earned_gamecoin_block") if earned_gamecoin_block is not None: - newprofile.replace_int('block', newprofile.get_int('block') + earned_gamecoin_block) - earned_blaster_energy = request.child_value('earned_blaster_energy') + newprofile.replace_int( + "block", newprofile.get_int("block") + earned_gamecoin_block + ) + earned_blaster_energy = request.child_value("earned_blaster_energy") if earned_blaster_energy is not None: - newprofile.replace_int('blaster_energy', newprofile.get_int('blaster_energy') + earned_blaster_energy) + newprofile.replace_int( + "blaster_energy", + newprofile.get_int("blaster_energy") + earned_blaster_energy, + ) # Miscelaneous stuff - newprofile.replace_int('blaster_count', request.child_value('blaster_count')) - newprofile.replace_int('skill_name_id', request.child_value('skill_name_id')) - newprofile.replace_int_array('hidden_param', 20, request.child_value('hidden_param')) + newprofile.replace_int("blaster_count", request.child_value("blaster_count")) + newprofile.replace_int("skill_name_id", request.child_value("skill_name_id")) + newprofile.replace_int_array( + "hidden_param", 20, request.child_value("hidden_param") + ) # Update user's unlock status if we aren't force unlocked game_config = self.get_game_config() - if request.child('item') is not None: - for child in request.child('item').children: - if child.name != 'info': + if request.child("item") is not None: + for child in request.child("item").children: + if child.name != "info": continue - item_id = child.child_value('id') - item_type = child.child_value('type') - param = child.child_value('param') + item_id = child.child_value("id") + item_type = child.child_value("type") + param = child.child_value("param") - if game_config.get_bool('force_unlock_cards') and item_type == self.GAME_CATALOG_TYPE_APPEAL_CARD: + if ( + game_config.get_bool("force_unlock_cards") + and item_type == self.GAME_CATALOG_TYPE_APPEAL_CARD + ): # Don't save back appeal cards because they were force unlocked continue - if game_config.get_bool('force_unlock_songs') and item_type == self.GAME_CATALOG_TYPE_SONG: + if ( + game_config.get_bool("force_unlock_songs") + and item_type == self.GAME_CATALOG_TYPE_SONG + ): # Don't save back songs, because they were force unlocked continue - if game_config.get_bool('force_unlock_crew') and item_type == self.GAME_CATALOG_TYPE_CREW: + if ( + game_config.get_bool("force_unlock_crew") + and item_type == self.GAME_CATALOG_TYPE_CREW + ): # Don't save back crew, because they were force unlocked continue @@ -3307,50 +3348,50 @@ class SoundVoltexGravityWarsSeason1( self.version, userid, item_id, - f'item_{item_type}', + f"item_{item_type}", { - 'param': param, + "param": param, }, ) # Update story progress - if request.child('story') is not None: - for child in request.child('story').children: - if child.name != 'info': + if request.child("story") is not None: + for child in request.child("story").children: + if child.name != "info": continue - story_id = child.child_value('story_id') - progress_id = child.child_value('progress_id') - progress_param = child.child_value('progress_param') - clear_cnt = child.child_value('clear_cnt') - route_flg = child.child_value('route_flg') + story_id = child.child_value("story_id") + progress_id = child.child_value("progress_id") + progress_param = child.child_value("progress_param") + clear_cnt = child.child_value("clear_cnt") + route_flg = child.child_value("route_flg") self.data.local.user.put_achievement( self.game, self.version, userid, story_id, - 'story', + "story", { - 'progress_id': progress_id, - 'progress_param': progress_param, - 'clear_cnt': clear_cnt, - 'route_flg': route_flg, + "progress_id": progress_id, + "progress_param": progress_param, + "clear_cnt": clear_cnt, + "route_flg": route_flg, }, ) # Grab last information. - lastdict = newprofile.get_dict('last') - lastdict.replace_int('headphone', request.child_value('headphone')) - lastdict.replace_int('appeal_id', request.child_value('appeal_id')) - lastdict.replace_int('comment_id', request.child_value('comment_id')) - lastdict.replace_int('music_id', request.child_value('music_id')) - lastdict.replace_int('music_type', request.child_value('music_type')) - lastdict.replace_int('sort_type', request.child_value('sort_type')) - lastdict.replace_int('narrow_down', request.child_value('narrow_down')) - lastdict.replace_int('gauge_option', request.child_value('gauge_option')) + lastdict = newprofile.get_dict("last") + lastdict.replace_int("headphone", request.child_value("headphone")) + lastdict.replace_int("appeal_id", request.child_value("appeal_id")) + lastdict.replace_int("comment_id", request.child_value("comment_id")) + lastdict.replace_int("music_id", request.child_value("music_id")) + lastdict.replace_int("music_type", request.child_value("music_type")) + lastdict.replace_int("sort_type", request.child_value("sort_type")) + lastdict.replace_int("narrow_down", request.child_value("narrow_down")) + lastdict.replace_int("gauge_option", request.child_value("gauge_option")) # Save back last information gleaned from results - newprofile.replace_dict('last', lastdict) + newprofile.replace_dict("last", lastdict) # Keep track of play statistics self.update_play_statistics(userid) diff --git a/bemani/backend/sdvx/gravitywars_s2.py b/bemani/backend/sdvx/gravitywars_s2.py index fc88c14..240b7a1 100644 --- a/bemani/backend/sdvx/gravitywars_s2.py +++ b/bemani/backend/sdvx/gravitywars_s2.py @@ -10,2913 +10,2912 @@ from bemani.protocol import Node class SoundVoltexGravityWarsSeason2( SoundVoltexGravityWars, ): - def _get_skill_analyzer_seasons(self) -> Dict[int, str]: return { - 1: 'SKILL ANALYZER 第1回 Aコース', - 2: 'SKILL ANALYZER 第1回 Bコース', - 3: 'SKILL ANALYZER 第1回 Cコース', - 4: 'The 4th KAC コース', - 5: 'SKILL ANALYZER 第2回 Aコース', - 6: 'SKILL ANALYZER 第2回 Bコース', - 7: 'SKILL ANALYZER 第2回 Cコース', - 8: 'SKILL ANALYZER 第3回 Aコース', - 9: 'SKILL ANALYZER 第3回 Bコース', - 10: 'SKILL ANALYZER 第3回 Cコース', - 11: 'SKILL ANALYZER 第4回', - 12: 'SKILL ANALYZER 第5回 Aコース', - 13: 'SKILL ANALYZER 第5回 Bコース', - 14: 'SKILL ANALYZER 第5回 Cコース', - 15: 'SKILL ANALYZER 第6回 Aコース', - 16: 'SKILL ANALYZER 第6回 Bコース', - 17: '天下一コース', - 18: 'The 5th KAC コース', - 19: 'VOLTEXES コース', - 20: 'SKILL ANALYZER 第7回', - 21: 'SKILL ANALYZER 第8回', + 1: "SKILL ANALYZER 第1回 Aコース", + 2: "SKILL ANALYZER 第1回 Bコース", + 3: "SKILL ANALYZER 第1回 Cコース", + 4: "The 4th KAC コース", + 5: "SKILL ANALYZER 第2回 Aコース", + 6: "SKILL ANALYZER 第2回 Bコース", + 7: "SKILL ANALYZER 第2回 Cコース", + 8: "SKILL ANALYZER 第3回 Aコース", + 9: "SKILL ANALYZER 第3回 Bコース", + 10: "SKILL ANALYZER 第3回 Cコース", + 11: "SKILL ANALYZER 第4回", + 12: "SKILL ANALYZER 第5回 Aコース", + 13: "SKILL ANALYZER 第5回 Bコース", + 14: "SKILL ANALYZER 第5回 Cコース", + 15: "SKILL ANALYZER 第6回 Aコース", + 16: "SKILL ANALYZER 第6回 Bコース", + 17: "天下一コース", + 18: "The 5th KAC コース", + 19: "VOLTEXES コース", + 20: "SKILL ANALYZER 第7回", + 21: "SKILL ANALYZER 第8回", } def _get_skill_analyzer_courses(self) -> List[Dict[str, Any]]: return [ { - 'level': 0, - 'season_id': 1, - 'tracks': [ + "level": 0, + "season_id": 1, + "tracks": [ { - 'id': 109, - 'type': self.CHART_TYPE_NOVICE, + "id": 109, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 283, - 'type': self.CHART_TYPE_ADVANCED, + "id": 283, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 279, - 'type': self.CHART_TYPE_ADVANCED, + "id": 279, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 1, - 'tracks': [ + "level": 1, + "season_id": 1, + "tracks": [ { - 'id': 76, - 'type': self.CHART_TYPE_ADVANCED, + "id": 76, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 196, - 'type': self.CHART_TYPE_ADVANCED, + "id": 196, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 8, - 'type': self.CHART_TYPE_ADVANCED, + "id": 8, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 1, - 'tracks': [ + "level": 2, + "season_id": 1, + "tracks": [ { - 'id': 90, - 'type': self.CHART_TYPE_ADVANCED, + "id": 90, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 228, - 'type': self.CHART_TYPE_ADVANCED, + "id": 228, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 80, - 'type': self.CHART_TYPE_ADVANCED, + "id": 80, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 1, - 'tracks': [ + "level": 3, + "season_id": 1, + "tracks": [ { - 'id': 125, - 'type': self.CHART_TYPE_ADVANCED, + "id": 125, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 201, - 'type': self.CHART_TYPE_EXHAUST, + "id": 201, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 237, - 'type': self.CHART_TYPE_EXHAUST, + "id": 237, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 1, - 'tracks': [ + "level": 4, + "season_id": 1, + "tracks": [ { - 'id': 393, - 'type': self.CHART_TYPE_ADVANCED, + "id": 393, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 352, - 'type': self.CHART_TYPE_EXHAUST, + "id": 352, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 66, - 'type': self.CHART_TYPE_EXHAUST, + "id": 66, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 1, - 'tracks': [ + "level": 5, + "season_id": 1, + "tracks": [ { - 'id': 383, - 'type': self.CHART_TYPE_EXHAUST, + "id": 383, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 511, - 'type': self.CHART_TYPE_EXHAUST, + "id": 511, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 331, - 'type': self.CHART_TYPE_EXHAUST, + "id": 331, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 1, - 'tracks': [ + "level": 6, + "season_id": 1, + "tracks": [ { - 'id': 422, - 'type': self.CHART_TYPE_EXHAUST, + "id": 422, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 445, - 'type': self.CHART_TYPE_EXHAUST, + "id": 445, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 71, - 'type': self.CHART_TYPE_EXHAUST, + "id": 71, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 1, - 'tracks': [ + "level": 7, + "season_id": 1, + "tracks": [ { - 'id': 454, - 'type': self.CHART_TYPE_EXHAUST, + "id": 454, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 158, - 'type': self.CHART_TYPE_EXHAUST, + "id": 158, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 173, - 'type': self.CHART_TYPE_EXHAUST, + "id": 173, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 1, - 'tracks': [ + "level": 8, + "season_id": 1, + "tracks": [ { - 'id': 322, - 'type': self.CHART_TYPE_EXHAUST, + "id": 322, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 63, - 'type': self.CHART_TYPE_EXHAUST, + "id": 63, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 124, - 'type': self.CHART_TYPE_EXHAUST, + "id": 124, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 1, - 'tracks': [ + "level": 9, + "season_id": 1, + "tracks": [ { - 'id': 348, - 'type': self.CHART_TYPE_EXHAUST, + "id": 348, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 73, - 'type': self.CHART_TYPE_EXHAUST, + "id": 73, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 259, - 'type': self.CHART_TYPE_EXHAUST, + "id": 259, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 2, - 'tracks': [ + "level": 0, + "season_id": 2, + "tracks": [ { - 'id': 374, - 'type': self.CHART_TYPE_NOVICE, + "id": 374, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 84, - 'type': self.CHART_TYPE_ADVANCED, + "id": 84, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 303, - 'type': self.CHART_TYPE_ADVANCED, + "id": 303, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 2, - 'tracks': [ + "level": 1, + "season_id": 2, + "tracks": [ { - 'id': 22, - 'type': self.CHART_TYPE_ADVANCED, + "id": 22, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 274, - 'type': self.CHART_TYPE_ADVANCED, + "id": 274, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 183, - 'type': self.CHART_TYPE_ADVANCED, + "id": 183, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 2, - 'tracks': [ + "level": 2, + "season_id": 2, + "tracks": [ { - 'id': 56, - 'type': self.CHART_TYPE_ADVANCED, + "id": 56, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 244, - 'type': self.CHART_TYPE_ADVANCED, + "id": 244, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 4, - 'type': self.CHART_TYPE_EXHAUST, + "id": 4, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 3, - 'season_id': 2, - 'tracks': [ + "level": 3, + "season_id": 2, + "tracks": [ { - 'id': 414, - 'type': self.CHART_TYPE_ADVANCED, + "id": 414, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 209, - 'type': self.CHART_TYPE_EXHAUST, + "id": 209, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 334, - 'type': self.CHART_TYPE_EXHAUST, + "id": 334, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 2, - 'tracks': [ + "level": 4, + "season_id": 2, + "tracks": [ { - 'id': 123, - 'type': self.CHART_TYPE_ADVANCED, + "id": 123, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 403, - 'type': self.CHART_TYPE_EXHAUST, + "id": 403, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 23, - 'type': self.CHART_TYPE_INFINITE, + "id": 23, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'level': 5, - 'season_id': 2, - 'tracks': [ + "level": 5, + "season_id": 2, + "tracks": [ { - 'id': 391, - 'type': self.CHART_TYPE_EXHAUST, + "id": 391, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 239, - 'type': self.CHART_TYPE_EXHAUST, + "id": 239, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 426, - 'type': self.CHART_TYPE_EXHAUST, + "id": 426, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 2, - 'tracks': [ + "level": 6, + "season_id": 2, + "tracks": [ { - 'id': 389, - 'type': self.CHART_TYPE_EXHAUST, + "id": 389, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 89, - 'type': self.CHART_TYPE_EXHAUST, + "id": 89, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 246, - 'type': self.CHART_TYPE_EXHAUST, + "id": 246, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 2, - 'tracks': [ + "level": 7, + "season_id": 2, + "tracks": [ { - 'id': 419, - 'type': self.CHART_TYPE_EXHAUST, + "id": 419, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 299, - 'type': self.CHART_TYPE_EXHAUST, + "id": 299, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 341, - 'type': self.CHART_TYPE_EXHAUST, + "id": 341, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 2, - 'tracks': [ + "level": 8, + "season_id": 2, + "tracks": [ { - 'id': 394, - 'type': self.CHART_TYPE_EXHAUST, + "id": 394, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 466, - 'type': self.CHART_TYPE_EXHAUST, + "id": 466, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 47, - 'type': self.CHART_TYPE_EXHAUST, + "id": 47, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 2, - 'tracks': [ + "level": 9, + "season_id": 2, + "tracks": [ { - 'id': 500, - 'type': self.CHART_TYPE_EXHAUST, + "id": 500, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 247, - 'type': self.CHART_TYPE_EXHAUST, + "id": 247, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 229, - 'type': self.CHART_TYPE_EXHAUST, + "id": 229, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 3, - 'tracks': [ + "level": 0, + "season_id": 3, + "tracks": [ { - 'id': 36, - 'type': self.CHART_TYPE_NOVICE, + "id": 36, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 189, - 'type': self.CHART_TYPE_ADVANCED, + "id": 189, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 171, - 'type': self.CHART_TYPE_ADVANCED, + "id": 171, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 3, - 'tracks': [ + "level": 1, + "season_id": 3, + "tracks": [ { - 'id': 182, - 'type': self.CHART_TYPE_ADVANCED, + "id": 182, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 3, - 'type': self.CHART_TYPE_ADVANCED, + "id": 3, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 105, - 'type': self.CHART_TYPE_ADVANCED, + "id": 105, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 3, - 'tracks': [ + "level": 2, + "season_id": 3, + "tracks": [ { - 'id': 14, - 'type': self.CHART_TYPE_ADVANCED, + "id": 14, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 120, - 'type': self.CHART_TYPE_ADVANCED, + "id": 120, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 86, - 'type': self.CHART_TYPE_ADVANCED, + "id": 86, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 3, - 'tracks': [ + "level": 3, + "season_id": 3, + "tracks": [ { - 'id': 390, - 'type': self.CHART_TYPE_ADVANCED, + "id": 390, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 243, - 'type': self.CHART_TYPE_EXHAUST, + "id": 243, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 186, - 'type': self.CHART_TYPE_EXHAUST, + "id": 186, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 3, - 'tracks': [ + "level": 4, + "season_id": 3, + "tracks": [ { - 'id': 36, - 'type': self.CHART_TYPE_ADVANCED, + "id": 36, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 423, - 'type': self.CHART_TYPE_EXHAUST, + "id": 423, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 59, - 'type': self.CHART_TYPE_EXHAUST, + "id": 59, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 3, - 'tracks': [ + "level": 5, + "season_id": 3, + "tracks": [ { - 'id': 452, - 'type': self.CHART_TYPE_EXHAUST, + "id": 452, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 262, - 'type': self.CHART_TYPE_EXHAUST, + "id": 262, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 480, - 'type': self.CHART_TYPE_INFINITE, + "id": 480, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'level': 6, - 'season_id': 3, - 'tracks': [ + "level": 6, + "season_id": 3, + "tracks": [ { - 'id': 411, - 'type': self.CHART_TYPE_EXHAUST, + "id": 411, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 70, - 'type': self.CHART_TYPE_EXHAUST, + "id": 70, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 211, - 'type': self.CHART_TYPE_EXHAUST, + "id": 211, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 3, - 'tracks': [ + "level": 7, + "season_id": 3, + "tracks": [ { - 'id': 30, - 'type': self.CHART_TYPE_EXHAUST, + "id": 30, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 72, - 'type': self.CHART_TYPE_INFINITE, + "id": 72, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 293, - 'type': self.CHART_TYPE_EXHAUST, + "id": 293, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 3, - 'tracks': [ + "level": 8, + "season_id": 3, + "tracks": [ { - 'id': 87, - 'type': self.CHART_TYPE_INFINITE, + "id": 87, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 117, - 'type': self.CHART_TYPE_EXHAUST, + "id": 117, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 269, - 'type': self.CHART_TYPE_EXHAUST, + "id": 269, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 3, - 'tracks': [ + "level": 9, + "season_id": 3, + "tracks": [ { - 'id': 498, - 'type': self.CHART_TYPE_EXHAUST, + "id": 498, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 437, - 'type': self.CHART_TYPE_EXHAUST, + "id": 437, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 126, - 'type': self.CHART_TYPE_EXHAUST, + "id": 126, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 0, - 'level': -1, - 'skill_name': 'エンジョイ♪ごりらコースA', - 'skill_name_id': 12, - 'season_id': 4, - 'tracks': [ + "id": 0, + "level": -1, + "skill_name": "エンジョイ♪ごりらコースA", + "skill_name_id": 12, + "season_id": 4, + "tracks": [ { - 'id': 466, - 'type': self.CHART_TYPE_ADVANCED, + "id": 466, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 273, - 'type': self.CHART_TYPE_ADVANCED, + "id": 273, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 470, - 'type': self.CHART_TYPE_ADVANCED, + "id": 470, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'id': 1, - 'level': -1, - 'skill_name': 'エンジョイ♪ごりらコースB', - 'skill_name_id': 12, - 'season_id': 4, - 'tracks': [ + "id": 1, + "level": -1, + "skill_name": "エンジョイ♪ごりらコースB", + "skill_name_id": 12, + "season_id": 4, + "tracks": [ { - 'id': 194, - 'type': self.CHART_TYPE_ADVANCED, + "id": 194, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 343, - 'type': self.CHART_TYPE_ADVANCED, + "id": 343, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 501, - 'type': self.CHART_TYPE_ADVANCED, + "id": 501, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'id': 2, - 'level': -1, - 'skill_name': 'エンジョイ♪ごりらコースC', - 'skill_name_id': 12, - 'season_id': 4, - 'tracks': [ + "id": 2, + "level": -1, + "skill_name": "エンジョイ♪ごりらコースC", + "skill_name_id": 12, + "season_id": 4, + "tracks": [ { - 'id': 356, - 'type': self.CHART_TYPE_ADVANCED, + "id": 356, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 7, - 'type': self.CHART_TYPE_EXHAUST, + "id": 7, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 472, - 'type': self.CHART_TYPE_ADVANCED, + "id": 472, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'id': 3, - 'level': -1, - 'skill_name': 'エンジョイ♪ごりらコースD', - 'skill_name_id': 12, - 'season_id': 4, - 'tracks': [ + "id": 3, + "level": -1, + "skill_name": "エンジョイ♪ごりらコースD", + "skill_name_id": 12, + "season_id": 4, + "tracks": [ { - 'id': 299, - 'type': self.CHART_TYPE_ADVANCED, + "id": 299, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 333, - 'type': self.CHART_TYPE_ADVANCED, + "id": 333, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 583, - 'type': self.CHART_TYPE_ADVANCED, + "id": 583, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'id': 4, - 'level': -1, - 'skill_name': 'チャレンジ★ごりらコースA', - 'skill_name_id': 12, - 'season_id': 4, - 'tracks': [ + "id": 4, + "level": -1, + "skill_name": "チャレンジ★ごりらコースA", + "skill_name_id": 12, + "season_id": 4, + "tracks": [ { - 'id': 466, - 'type': self.CHART_TYPE_EXHAUST, + "id": 466, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 273, - 'type': self.CHART_TYPE_EXHAUST, + "id": 273, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 470, - 'type': self.CHART_TYPE_EXHAUST, + "id": 470, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 5, - 'level': -1, - 'skill_name': 'チャレンジ★ごりらコースB', - 'skill_name_id': 12, - 'season_id': 4, - 'tracks': [ + "id": 5, + "level": -1, + "skill_name": "チャレンジ★ごりらコースB", + "skill_name_id": 12, + "season_id": 4, + "tracks": [ { - 'id': 194, - 'type': self.CHART_TYPE_EXHAUST, + "id": 194, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 343, - 'type': self.CHART_TYPE_EXHAUST, + "id": 343, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 501, - 'type': self.CHART_TYPE_EXHAUST, + "id": 501, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 6, - 'level': -1, - 'skill_name': 'チャレンジ★ごりらコースC', - 'skill_name_id': 12, - 'season_id': 4, - 'tracks': [ + "id": 6, + "level": -1, + "skill_name": "チャレンジ★ごりらコースC", + "skill_name_id": 12, + "season_id": 4, + "tracks": [ { - 'id': 356, - 'type': self.CHART_TYPE_EXHAUST, + "id": 356, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 7, - 'type': self.CHART_TYPE_INFINITE, + "id": 7, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 472, - 'type': self.CHART_TYPE_EXHAUST, + "id": 472, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 7, - 'level': -1, - 'skill_name': 'チャレンジ★ごりらコースD', - 'skill_name_id': 12, - 'season_id': 4, - 'tracks': [ + "id": 7, + "level": -1, + "skill_name": "チャレンジ★ごりらコースD", + "skill_name_id": 12, + "season_id": 4, + "tracks": [ { - 'id': 299, - 'type': self.CHART_TYPE_EXHAUST, + "id": 299, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 333, - 'type': self.CHART_TYPE_EXHAUST, + "id": 333, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 583, - 'type': self.CHART_TYPE_EXHAUST, + "id": 583, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 5, - 'tracks': [ + "level": 0, + "season_id": 5, + "tracks": [ { - 'id': 47, - 'type': self.CHART_TYPE_NOVICE, + "id": 47, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 334, - 'type': self.CHART_TYPE_ADVANCED, + "id": 334, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 10, - 'type': self.CHART_TYPE_ADVANCED, + "id": 10, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 5, - 'tracks': [ + "level": 1, + "season_id": 5, + "tracks": [ { - 'id': 11, - 'type': self.CHART_TYPE_ADVANCED, + "id": 11, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 224, - 'type': self.CHART_TYPE_ADVANCED, + "id": 224, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 132, - 'type': self.CHART_TYPE_ADVANCED, + "id": 132, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 5, - 'tracks': [ + "level": 2, + "season_id": 5, + "tracks": [ { - 'id': 137, - 'type': self.CHART_TYPE_ADVANCED, + "id": 137, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 336, - 'type': self.CHART_TYPE_ADVANCED, + "id": 336, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 380, - 'type': self.CHART_TYPE_ADVANCED, + "id": 380, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 5, - 'tracks': [ + "level": 3, + "season_id": 5, + "tracks": [ { - 'id': 109, - 'type': self.CHART_TYPE_ADVANCED, + "id": 109, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 308, - 'type': self.CHART_TYPE_EXHAUST, + "id": 308, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 113, - 'type': self.CHART_TYPE_EXHAUST, + "id": 113, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 5, - 'tracks': [ + "level": 4, + "season_id": 5, + "tracks": [ { - 'id': 101, - 'type': self.CHART_TYPE_ADVANCED, + "id": 101, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 200, - 'type': self.CHART_TYPE_EXHAUST, + "id": 200, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 478, - 'type': self.CHART_TYPE_EXHAUST, + "id": 478, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 5, - 'tracks': [ + "level": 5, + "season_id": 5, + "tracks": [ { - 'id': 487, - 'type': self.CHART_TYPE_EXHAUST, + "id": 487, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 254, - 'type': self.CHART_TYPE_EXHAUST, + "id": 254, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 410, - 'type': self.CHART_TYPE_EXHAUST, + "id": 410, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 5, - 'tracks': [ + "level": 6, + "season_id": 5, + "tracks": [ { - 'id': 196, - 'type': self.CHART_TYPE_EXHAUST, + "id": 196, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 170, - 'type': self.CHART_TYPE_EXHAUST, + "id": 170, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 218, - 'type': self.CHART_TYPE_EXHAUST, + "id": 218, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 5, - 'tracks': [ + "level": 7, + "season_id": 5, + "tracks": [ { - 'id': 489, - 'type': self.CHART_TYPE_EXHAUST, + "id": 489, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 519, - 'type': self.CHART_TYPE_EXHAUST, + "id": 519, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 373, - 'type': self.CHART_TYPE_EXHAUST, + "id": 373, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 5, - 'tracks': [ + "level": 8, + "season_id": 5, + "tracks": [ { - 'id': 456, - 'type': self.CHART_TYPE_EXHAUST, + "id": 456, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 263, - 'type': self.CHART_TYPE_EXHAUST, + "id": 263, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 390, - 'type': self.CHART_TYPE_EXHAUST, + "id": 390, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 5, - 'tracks': [ + "level": 9, + "season_id": 5, + "tracks": [ { - 'id': 19, - 'type': self.CHART_TYPE_INFINITE, + "id": 19, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 116, - 'type': self.CHART_TYPE_EXHAUST, + "id": 116, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 508, - 'type': self.CHART_TYPE_EXHAUST, + "id": 508, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 6, - 'tracks': [ + "level": 0, + "season_id": 6, + "tracks": [ { - 'id': 123, - 'type': self.CHART_TYPE_NOVICE, + "id": 123, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 231, - 'type': self.CHART_TYPE_ADVANCED, + "id": 231, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 185, - 'type': self.CHART_TYPE_ADVANCED, + "id": 185, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 6, - 'tracks': [ + "level": 1, + "season_id": 6, + "tracks": [ { - 'id': 65, - 'type': self.CHART_TYPE_ADVANCED, + "id": 65, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 386, - 'type': self.CHART_TYPE_ADVANCED, + "id": 386, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 92, - 'type': self.CHART_TYPE_ADVANCED, + "id": 92, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 6, - 'tracks': [ + "level": 2, + "season_id": 6, + "tracks": [ { - 'id': 379, - 'type': self.CHART_TYPE_ADVANCED, + "id": 379, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 225, - 'type': self.CHART_TYPE_ADVANCED, + "id": 225, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 427, - 'type': self.CHART_TYPE_ADVANCED, + "id": 427, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 6, - 'tracks': [ + "level": 3, + "season_id": 6, + "tracks": [ { - 'id': 122, - 'type': self.CHART_TYPE_ADVANCED, + "id": 122, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 249, - 'type': self.CHART_TYPE_EXHAUST, + "id": 249, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 185, - 'type': self.CHART_TYPE_EXHAUST, + "id": 185, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 6, - 'tracks': [ + "level": 4, + "season_id": 6, + "tracks": [ { - 'id': 413, - 'type': self.CHART_TYPE_ADVANCED, + "id": 413, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 157, - 'type': self.CHART_TYPE_EXHAUST, + "id": 157, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 402, - 'type': self.CHART_TYPE_EXHAUST, + "id": 402, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 6, - 'tracks': [ + "level": 5, + "season_id": 6, + "tracks": [ { - 'id': 412, - 'type': self.CHART_TYPE_EXHAUST, + "id": 412, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 323, - 'type': self.CHART_TYPE_EXHAUST, + "id": 323, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 256, - 'type': self.CHART_TYPE_EXHAUST, + "id": 256, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 6, - 'tracks': [ + "level": 6, + "season_id": 6, + "tracks": [ { - 'id': 400, - 'type': self.CHART_TYPE_EXHAUST, + "id": 400, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 368, - 'type': self.CHART_TYPE_EXHAUST, + "id": 368, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 241, - 'type': self.CHART_TYPE_EXHAUST, + "id": 241, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 6, - 'tracks': [ + "level": 7, + "season_id": 6, + "tracks": [ { - 'id': 453, - 'type': self.CHART_TYPE_EXHAUST, + "id": 453, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 442, - 'type': self.CHART_TYPE_EXHAUST, + "id": 442, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 216, - 'type': self.CHART_TYPE_EXHAUST, + "id": 216, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 6, - 'tracks': [ + "level": 8, + "season_id": 6, + "tracks": [ { - 'id': 370, - 'type': self.CHART_TYPE_EXHAUST, + "id": 370, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 244, - 'type': self.CHART_TYPE_EXHAUST, + "id": 244, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 252, - 'type': self.CHART_TYPE_EXHAUST, + "id": 252, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 6, - 'tracks': [ + "level": 9, + "season_id": 6, + "tracks": [ { - 'id': 359, - 'type': self.CHART_TYPE_EXHAUST, + "id": 359, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 214, - 'type': self.CHART_TYPE_EXHAUST, + "id": 214, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 506, - 'type': self.CHART_TYPE_EXHAUST, + "id": 506, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 7, - 'tracks': [ + "level": 0, + "season_id": 7, + "tracks": [ { - 'id': 124, - 'type': self.CHART_TYPE_NOVICE, + "id": 124, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 446, - 'type': self.CHART_TYPE_ADVANCED, + "id": 446, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 34, - 'type': self.CHART_TYPE_ADVANCED, + "id": 34, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 7, - 'tracks': [ + "level": 1, + "season_id": 7, + "tracks": [ { - 'id': 113, - 'type': self.CHART_TYPE_ADVANCED, + "id": 113, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 309, - 'type': self.CHART_TYPE_ADVANCED, + "id": 309, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 42, - 'type': self.CHART_TYPE_ADVANCED, + "id": 42, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 7, - 'tracks': [ + "level": 2, + "season_id": 7, + "tracks": [ { - 'id': 353, - 'type': self.CHART_TYPE_ADVANCED, + "id": 353, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 246, - 'type': self.CHART_TYPE_ADVANCED, + "id": 246, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 130, - 'type': self.CHART_TYPE_ADVANCED, + "id": 130, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 7, - 'tracks': [ + "level": 3, + "season_id": 7, + "tracks": [ { - 'id': 63, - 'type': self.CHART_TYPE_ADVANCED, + "id": 63, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 219, - 'type': self.CHART_TYPE_EXHAUST, + "id": 219, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 153, - 'type': self.CHART_TYPE_EXHAUST, + "id": 153, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 7, - 'tracks': [ + "level": 4, + "season_id": 7, + "tracks": [ { - 'id': 418, - 'type': self.CHART_TYPE_ADVANCED, + "id": 418, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 369, - 'type': self.CHART_TYPE_EXHAUST, + "id": 369, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 385, - 'type': self.CHART_TYPE_EXHAUST, + "id": 385, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 7, - 'tracks': [ + "level": 5, + "season_id": 7, + "tracks": [ { - 'id': 226, - 'type': self.CHART_TYPE_EXHAUST, + "id": 226, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 301, - 'type': self.CHART_TYPE_EXHAUST, + "id": 301, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 159, - 'type': self.CHART_TYPE_EXHAUST, + "id": 159, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 7, - 'tracks': [ + "level": 6, + "season_id": 7, + "tracks": [ { - 'id': 311, - 'type': self.CHART_TYPE_EXHAUST, + "id": 311, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 255, - 'type': self.CHART_TYPE_EXHAUST, + "id": 255, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 213, - 'type': self.CHART_TYPE_EXHAUST, + "id": 213, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 7, - 'tracks': [ + "level": 7, + "season_id": 7, + "tracks": [ { - 'id': 357, - 'type': self.CHART_TYPE_EXHAUST, + "id": 357, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 268, - 'type': self.CHART_TYPE_EXHAUST, + "id": 268, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 304, - 'type': self.CHART_TYPE_EXHAUST, + "id": 304, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 7, - 'tracks': [ + "level": 8, + "season_id": 7, + "tracks": [ { - 'id': 295, - 'type': self.CHART_TYPE_EXHAUST, + "id": 295, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 36, - 'type': self.CHART_TYPE_EXHAUST, + "id": 36, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 302, - 'type': self.CHART_TYPE_EXHAUST, + "id": 302, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 7, - 'tracks': [ + "level": 9, + "season_id": 7, + "tracks": [ { - 'id': 7, - 'type': self.CHART_TYPE_INFINITE, + "id": 7, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 208, - 'type': self.CHART_TYPE_EXHAUST, + "id": 208, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 376, - 'type': self.CHART_TYPE_EXHAUST, + "id": 376, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 8, - 'tracks': [ + "level": 0, + "season_id": 8, + "tracks": [ { - 'id': 101, - 'type': self.CHART_TYPE_NOVICE, + "id": 101, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 219, - 'type': self.CHART_TYPE_ADVANCED, + "id": 219, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 159, - 'type': self.CHART_TYPE_ADVANCED, + "id": 159, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 8, - 'tracks': [ + "level": 1, + "season_id": 8, + "tracks": [ { - 'id': 87, - 'type': self.CHART_TYPE_ADVANCED, + "id": 87, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 337, - 'type': self.CHART_TYPE_ADVANCED, + "id": 337, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 403, - 'type': self.CHART_TYPE_ADVANCED, + "id": 403, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 8, - 'tracks': [ + "level": 2, + "season_id": 8, + "tracks": [ { - 'id': 30, - 'type': self.CHART_TYPE_ADVANCED, + "id": 30, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 596, - 'type': self.CHART_TYPE_ADVANCED, + "id": 596, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 39, - 'type': self.CHART_TYPE_ADVANCED, + "id": 39, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 8, - 'tracks': [ + "level": 3, + "season_id": 8, + "tracks": [ { - 'id': 430, - 'type': self.CHART_TYPE_ADVANCED, + "id": 430, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 561, - 'type': self.CHART_TYPE_EXHAUST, + "id": 561, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 328, - 'type': self.CHART_TYPE_EXHAUST, + "id": 328, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 8, - 'tracks': [ + "level": 4, + "season_id": 8, + "tracks": [ { - 'id': 444, - 'type': self.CHART_TYPE_ADVANCED, + "id": 444, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 618, - 'type': self.CHART_TYPE_EXHAUST, + "id": 618, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 100, - 'type': self.CHART_TYPE_EXHAUST, + "id": 100, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 8, - 'tracks': [ + "level": 5, + "season_id": 8, + "tracks": [ { - 'id': 447, - 'type': self.CHART_TYPE_EXHAUST, + "id": 447, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 545, - 'type': self.CHART_TYPE_EXHAUST, + "id": 545, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 94, - 'type': self.CHART_TYPE_EXHAUST, + "id": 94, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 8, - 'tracks': [ + "level": 6, + "season_id": 8, + "tracks": [ { - 'id': 291, - 'type': self.CHART_TYPE_EXHAUST, + "id": 291, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 2, - 'type': self.CHART_TYPE_EXHAUST, + "id": 2, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 475, - 'type': self.CHART_TYPE_EXHAUST, + "id": 475, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 8, - 'tracks': [ + "level": 7, + "season_id": 8, + "tracks": [ { - 'id': 627, - 'type': self.CHART_TYPE_EXHAUST, + "id": 627, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 624, - 'type': self.CHART_TYPE_EXHAUST, + "id": 624, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 427, - 'type': self.CHART_TYPE_EXHAUST, + "id": 427, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 8, - 'tracks': [ + "level": 8, + "season_id": 8, + "tracks": [ { - 'id': 464, - 'type': self.CHART_TYPE_EXHAUST, + "id": 464, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 122, - 'type': self.CHART_TYPE_EXHAUST, + "id": 122, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 591, - 'type': self.CHART_TYPE_EXHAUST, + "id": 591, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 8, - 'tracks': [ + "level": 9, + "season_id": 8, + "tracks": [ { - 'id': 381, - 'type': self.CHART_TYPE_EXHAUST, + "id": 381, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 463, - 'type': self.CHART_TYPE_EXHAUST, + "id": 463, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 507, - 'type': self.CHART_TYPE_EXHAUST, + "id": 507, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 9, - 'tracks': [ + "level": 0, + "season_id": 9, + "tracks": [ { - 'id': 468, - 'type': self.CHART_TYPE_NOVICE, + "id": 468, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 243, - 'type': self.CHART_TYPE_ADVANCED, + "id": 243, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 388, - 'type': self.CHART_TYPE_ADVANCED, + "id": 388, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 9, - 'tracks': [ + "level": 1, + "season_id": 9, + "tracks": [ { - 'id': 167, - 'type': self.CHART_TYPE_ADVANCED, + "id": 167, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 486, - 'type': self.CHART_TYPE_ADVANCED, + "id": 486, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 75, - 'type': self.CHART_TYPE_ADVANCED, + "id": 75, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 9, - 'tracks': [ + "level": 2, + "season_id": 9, + "tracks": [ { - 'id': 96, - 'type': self.CHART_TYPE_ADVANCED, + "id": 96, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 557, - 'type': self.CHART_TYPE_ADVANCED, + "id": 557, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 55, - 'type': self.CHART_TYPE_ADVANCED, + "id": 55, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 9, - 'tracks': [ + "level": 3, + "season_id": 9, + "tracks": [ { - 'id': 116, - 'type': self.CHART_TYPE_ADVANCED, + "id": 116, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 520, - 'type': self.CHART_TYPE_EXHAUST, + "id": 520, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 314, - 'type': self.CHART_TYPE_EXHAUST, + "id": 314, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 9, - 'tracks': [ + "level": 4, + "season_id": 9, + "tracks": [ { - 'id': 507, - 'type': self.CHART_TYPE_ADVANCED, + "id": 507, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 567, - 'type': self.CHART_TYPE_EXHAUST, + "id": 567, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 205, - 'type': self.CHART_TYPE_EXHAUST, + "id": 205, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 9, - 'tracks': [ + "level": 5, + "season_id": 9, + "tracks": [ { - 'id': 86, - 'type': self.CHART_TYPE_EXHAUST, + "id": 86, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 488, - 'type': self.CHART_TYPE_EXHAUST, + "id": 488, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 80, - 'type': self.CHART_TYPE_EXHAUST, + "id": 80, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 9, - 'tracks': [ + "level": 6, + "season_id": 9, + "tracks": [ { - 'id': 184, - 'type': self.CHART_TYPE_EXHAUST, + "id": 184, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 130, - 'type': self.CHART_TYPE_EXHAUST, + "id": 130, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 524, - 'type': self.CHART_TYPE_EXHAUST, + "id": 524, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 9, - 'tracks': [ + "level": 7, + "season_id": 9, + "tracks": [ { - 'id': 521, - 'type': self.CHART_TYPE_EXHAUST, + "id": 521, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 576, - 'type': self.CHART_TYPE_EXHAUST, + "id": 576, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 503, - 'type': self.CHART_TYPE_EXHAUST, + "id": 503, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 9, - 'tracks': [ + "level": 8, + "season_id": 9, + "tracks": [ { - 'id': 473, - 'type': self.CHART_TYPE_EXHAUST, + "id": 473, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 125, - 'type': self.CHART_TYPE_EXHAUST, + "id": 125, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 538, - 'type': self.CHART_TYPE_EXHAUST, + "id": 538, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 9, - 'tracks': [ + "level": 9, + "season_id": 9, + "tracks": [ { - 'id': 407, - 'type': self.CHART_TYPE_EXHAUST, + "id": 407, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 472, - 'type': self.CHART_TYPE_EXHAUST, + "id": 472, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 363, - 'type': self.CHART_TYPE_EXHAUST, + "id": 363, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 10, - 'tracks': [ + "level": 0, + "season_id": 10, + "tracks": [ { - 'id': 122, - 'type': self.CHART_TYPE_NOVICE, + "id": 122, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 209, - 'type': self.CHART_TYPE_ADVANCED, + "id": 209, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 24, - 'type': self.CHART_TYPE_ADVANCED, + "id": 24, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 10, - 'tracks': [ + "level": 1, + "season_id": 10, + "tracks": [ { - 'id': 405, - 'type': self.CHART_TYPE_ADVANCED, + "id": 405, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 554, - 'type': self.CHART_TYPE_ADVANCED, + "id": 554, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 77, - 'type': self.CHART_TYPE_ADVANCED, + "id": 77, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 10, - 'tracks': [ + "level": 2, + "season_id": 10, + "tracks": [ { - 'id': 426, - 'type': self.CHART_TYPE_ADVANCED, + "id": 426, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 262, - 'type': self.CHART_TYPE_ADVANCED, + "id": 262, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 194, - 'type': self.CHART_TYPE_ADVANCED, + "id": 194, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 10, - 'tracks': [ + "level": 3, + "season_id": 10, + "tracks": [ { - 'id': 343, - 'type': self.CHART_TYPE_ADVANCED, + "id": 343, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 564, - 'type': self.CHART_TYPE_EXHAUST, + "id": 564, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 248, - 'type': self.CHART_TYPE_EXHAUST, + "id": 248, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 10, - 'tracks': [ + "level": 4, + "season_id": 10, + "tracks": [ { - 'id': 126, - 'type': self.CHART_TYPE_ADVANCED, + "id": 126, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 471, - 'type': self.CHART_TYPE_EXHAUST, + "id": 471, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 276, - 'type': self.CHART_TYPE_EXHAUST, + "id": 276, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 10, - 'tracks': [ + "level": 5, + "season_id": 10, + "tracks": [ { - 'id': 476, - 'type': self.CHART_TYPE_EXHAUST, + "id": 476, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 120, - 'type': self.CHART_TYPE_EXHAUST, + "id": 120, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 57, - 'type': self.CHART_TYPE_EXHAUST, + "id": 57, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 10, - 'tracks': [ + "level": 6, + "season_id": 10, + "tracks": [ { - 'id': 146, - 'type': self.CHART_TYPE_EXHAUST, + "id": 146, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 622, - 'type': self.CHART_TYPE_EXHAUST, + "id": 622, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 152, - 'type': self.CHART_TYPE_EXHAUST, + "id": 152, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 10, - 'tracks': [ + "level": 7, + "season_id": 10, + "tracks": [ { - 'id': 562, - 'type': self.CHART_TYPE_EXHAUST, + "id": 562, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 531, - 'type': self.CHART_TYPE_EXHAUST, + "id": 531, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 449, - 'type': self.CHART_TYPE_EXHAUST, + "id": 449, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 10, - 'tracks': [ + "level": 8, + "season_id": 10, + "tracks": [ { - 'id': 404, - 'type': self.CHART_TYPE_EXHAUST, + "id": 404, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 123, - 'type': self.CHART_TYPE_EXHAUST, + "id": 123, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 607, - 'type': self.CHART_TYPE_EXHAUST, + "id": 607, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 10, - 'tracks': [ + "level": 9, + "season_id": 10, + "tracks": [ { - 'id': 469, - 'type': self.CHART_TYPE_EXHAUST, + "id": 469, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 496, - 'type': self.CHART_TYPE_EXHAUST, + "id": 496, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 289, - 'type': self.CHART_TYPE_EXHAUST, + "id": 289, + "type": self.CHART_TYPE_EXHAUST, }, ], }, # Manually specify IDs here since this has more than one level 11. { - 'id': 0, - 'level': 0, - 'season_id': 11, - 'tracks': [ + "id": 0, + "level": 0, + "season_id": 11, + "tracks": [ { - 'id': 190, - 'type': self.CHART_TYPE_ADVANCED, + "id": 190, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 568, - 'type': self.CHART_TYPE_ADVANCED, + "id": 568, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 191, - 'type': self.CHART_TYPE_ADVANCED, + "id": 191, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'id': 1, - 'level': 1, - 'season_id': 11, - 'tracks': [ + "id": 1, + "level": 1, + "season_id": 11, + "tracks": [ { - 'id': 278, - 'type': self.CHART_TYPE_ADVANCED, + "id": 278, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 41, - 'type': self.CHART_TYPE_EXHAUST, + "id": 41, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 18, - 'type': self.CHART_TYPE_ADVANCED, + "id": 18, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'id': 2, - 'level': 2, - 'season_id': 11, - 'tracks': [ + "id": 2, + "level": 2, + "season_id": 11, + "tracks": [ { - 'id': 15, - 'type': self.CHART_TYPE_EXHAUST, + "id": 15, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 483, - 'type': self.CHART_TYPE_ADVANCED, + "id": 483, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 467, - 'type': self.CHART_TYPE_ADVANCED, + "id": 467, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'id': 3, - 'level': 3, - 'season_id': 11, - 'tracks': [ + "id": 3, + "level": 3, + "season_id": 11, + "tracks": [ { - 'id': 585, - 'type': self.CHART_TYPE_ADVANCED, + "id": 585, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 486, - 'type': self.CHART_TYPE_EXHAUST, + "id": 486, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 48, - 'type': self.CHART_TYPE_EXHAUST, + "id": 48, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 4, - 'level': 4, - 'season_id': 11, - 'tracks': [ + "id": 4, + "level": 4, + "season_id": 11, + "tracks": [ { - 'id': 103, - 'type': self.CHART_TYPE_EXHAUST, + "id": 103, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 335, - 'type': self.CHART_TYPE_EXHAUST, + "id": 335, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 224, - 'type': self.CHART_TYPE_EXHAUST, + "id": 224, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 5, - 'level': 5, - 'season_id': 11, - 'tracks': [ + "id": 5, + "level": 5, + "season_id": 11, + "tracks": [ { - 'id': 275, - 'type': self.CHART_TYPE_EXHAUST, + "id": 275, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 438, - 'type': self.CHART_TYPE_EXHAUST, + "id": 438, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 67, - 'type': self.CHART_TYPE_EXHAUST, + "id": 67, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 6, - 'level': 6, - 'season_id': 11, - 'tracks': [ + "id": 6, + "level": 6, + "season_id": 11, + "tracks": [ { - 'id': 202, - 'type': self.CHART_TYPE_EXHAUST, + "id": 202, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 264, - 'type': self.CHART_TYPE_EXHAUST, + "id": 264, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 526, - 'type': self.CHART_TYPE_EXHAUST, + "id": 526, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 7, - 'level': 7, - 'season_id': 11, - 'tracks': [ + "id": 7, + "level": 7, + "season_id": 11, + "tracks": [ { - 'id': 131, - 'type': self.CHART_TYPE_EXHAUST, + "id": 131, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 155, - 'type': self.CHART_TYPE_EXHAUST, + "id": 155, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 394, - 'type': self.CHART_TYPE_EXHAUST, + "id": 394, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 8, - 'level': 8, - 'season_id': 11, - 'tracks': [ + "id": 8, + "level": 8, + "season_id": 11, + "tracks": [ { - 'id': 396, - 'type': self.CHART_TYPE_EXHAUST, + "id": 396, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 346, - 'type': self.CHART_TYPE_EXHAUST, + "id": 346, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 510, - 'type': self.CHART_TYPE_EXHAUST, + "id": 510, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 9, - 'level': 9, - 'season_id': 11, - 'tracks': [ + "id": 9, + "level": 9, + "season_id": 11, + "tracks": [ { - 'id': 326, - 'type': self.CHART_TYPE_EXHAUST, + "id": 326, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 470, - 'type': self.CHART_TYPE_EXHAUST, + "id": 470, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 362, - 'type': self.CHART_TYPE_EXHAUST, + "id": 362, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 10, - 'level': 10, - 'season_id': 11, - 'tracks': [ + "id": 10, + "level": 10, + "season_id": 11, + "tracks": [ { - 'id': 339, - 'type': self.CHART_TYPE_EXHAUST, + "id": 339, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 418, - 'type': self.CHART_TYPE_EXHAUST, + "id": 418, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 525, - 'type': self.CHART_TYPE_EXHAUST, + "id": 525, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 11, - 'level': 10, - 'season_id': 11, - 'tracks': [ + "id": 11, + "level": 10, + "season_id": 11, + "tracks": [ { - 'id': 36, - 'type': self.CHART_TYPE_INFINITE, + "id": 36, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 47, - 'type': self.CHART_TYPE_INFINITE, + "id": 47, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 73, - 'type': self.CHART_TYPE_INFINITE, + "id": 73, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'id': 12, - 'level': 11, - 'season_id': 11, - 'tracks': [ + "id": 12, + "level": 11, + "season_id": 11, + "tracks": [ { - 'id': 126, - 'type': self.CHART_TYPE_INFINITE, + "id": 126, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 367, - 'type': self.CHART_TYPE_EXHAUST, + "id": 367, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 636, - 'type': self.CHART_TYPE_INFINITE, + "id": 636, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'level': 0, - 'season_id': 12, - 'tracks': [ + "level": 0, + "season_id": 12, + "tracks": [ { - 'id': 507, - 'type': self.CHART_TYPE_NOVICE, + "id": 507, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 671, - 'type': self.CHART_TYPE_ADVANCED, + "id": 671, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 176, - 'type': self.CHART_TYPE_ADVANCED, + "id": 176, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 12, - 'tracks': [ + "level": 1, + "season_id": 12, + "tracks": [ { - 'id': 27, - 'type': self.CHART_TYPE_ADVANCED, + "id": 27, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 520, - 'type': self.CHART_TYPE_ADVANCED, + "id": 520, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 103, - 'type': self.CHART_TYPE_ADVANCED, + "id": 103, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 12, - 'tracks': [ + "level": 2, + "season_id": 12, + "tracks": [ { - 'id': 478, - 'type': self.CHART_TYPE_ADVANCED, + "id": 478, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 264, - 'type': self.CHART_TYPE_ADVANCED, + "id": 264, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 322, - 'type': self.CHART_TYPE_ADVANCED, + "id": 322, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 12, - 'tracks': [ + "level": 3, + "season_id": 12, + "tracks": [ { - 'id': 107, - 'type': self.CHART_TYPE_ADVANCED, + "id": 107, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 520, - 'type': self.CHART_TYPE_EXHAUST, + "id": 520, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 163, - 'type': self.CHART_TYPE_EXHAUST, + "id": 163, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 12, - 'tracks': [ + "level": 4, + "season_id": 12, + "tracks": [ { - 'id': 408, - 'type': self.CHART_TYPE_ADVANCED, + "id": 408, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 34, - 'type': self.CHART_TYPE_INFINITE, + "id": 34, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 678, - 'type': self.CHART_TYPE_EXHAUST, + "id": 678, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 12, - 'tracks': [ + "level": 5, + "season_id": 12, + "tracks": [ { - 'id': 481, - 'type': self.CHART_TYPE_EXHAUST, + "id": 481, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 436, - 'type': self.CHART_TYPE_EXHAUST, + "id": 436, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 104, - 'type': self.CHART_TYPE_EXHAUST, + "id": 104, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 12, - 'tracks': [ + "level": 6, + "season_id": 12, + "tracks": [ { - 'id': 55, - 'type': self.CHART_TYPE_EXHAUST, + "id": 55, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 415, - 'type': self.CHART_TYPE_EXHAUST, + "id": 415, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 512, - 'type': self.CHART_TYPE_EXHAUST, + "id": 512, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 12, - 'tracks': [ + "level": 7, + "season_id": 12, + "tracks": [ { - 'id': 483, - 'type': self.CHART_TYPE_EXHAUST, + "id": 483, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 509, - 'type': self.CHART_TYPE_EXHAUST, + "id": 509, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 557, - 'type': self.CHART_TYPE_EXHAUST, + "id": 557, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 12, - 'tracks': [ + "level": 8, + "season_id": 12, + "tracks": [ { - 'id': 497, - 'type': self.CHART_TYPE_EXHAUST, + "id": 497, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 58, - 'type': self.CHART_TYPE_INFINITE, + "id": 58, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 166, - 'type': self.CHART_TYPE_EXHAUST, + "id": 166, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 12, - 'tracks': [ + "level": 9, + "season_id": 12, + "tracks": [ { - 'id': 581, - 'type': self.CHART_TYPE_EXHAUST, + "id": 581, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 439, - 'type': self.CHART_TYPE_EXHAUST, + "id": 439, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 443, - 'type': self.CHART_TYPE_EXHAUST, + "id": 443, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 13, - 'tracks': [ + "level": 0, + "season_id": 13, + "tracks": [ { - 'id': 250, - 'type': self.CHART_TYPE_NOVICE, + "id": 250, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 245, - 'type': self.CHART_TYPE_ADVANCED, + "id": 245, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 186, - 'type': self.CHART_TYPE_ADVANCED, + "id": 186, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 13, - 'tracks': [ + "level": 1, + "season_id": 13, + "tracks": [ { - 'id': 13, - 'type': self.CHART_TYPE_ADVANCED, + "id": 13, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 618, - 'type': self.CHART_TYPE_ADVANCED, + "id": 618, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 31, - 'type': self.CHART_TYPE_ADVANCED, + "id": 31, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 13, - 'tracks': [ + "level": 2, + "season_id": 13, + "tracks": [ { - 'id': 436, - 'type': self.CHART_TYPE_ADVANCED, + "id": 436, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 144, - 'type': self.CHART_TYPE_EXHAUST, + "id": 144, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 79, - 'type': self.CHART_TYPE_ADVANCED, + "id": 79, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 13, - 'tracks': [ + "level": 3, + "season_id": 13, + "tracks": [ { - 'id': 489, - 'type': self.CHART_TYPE_ADVANCED, + "id": 489, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 245, - 'type': self.CHART_TYPE_EXHAUST, + "id": 245, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 222, - 'type': self.CHART_TYPE_EXHAUST, + "id": 222, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 13, - 'tracks': [ + "level": 4, + "season_id": 13, + "tracks": [ { - 'id': 556, - 'type': self.CHART_TYPE_ADVANCED, + "id": 556, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 233, - 'type': self.CHART_TYPE_EXHAUST, + "id": 233, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 565, - 'type': self.CHART_TYPE_EXHAUST, + "id": 565, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 13, - 'tracks': [ + "level": 5, + "season_id": 13, + "tracks": [ { - 'id': 354, - 'type': self.CHART_TYPE_EXHAUST, + "id": 354, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 281, - 'type': self.CHART_TYPE_EXHAUST, + "id": 281, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 2, - 'type': self.CHART_TYPE_EXHAUST, + "id": 2, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 13, - 'tracks': [ + "level": 6, + "season_id": 13, + "tracks": [ { - 'id': 14, - 'type': self.CHART_TYPE_EXHAUST, + "id": 14, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 267, - 'type': self.CHART_TYPE_EXHAUST, + "id": 267, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 490, - 'type': self.CHART_TYPE_EXHAUST, + "id": 490, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 13, - 'tracks': [ + "level": 7, + "season_id": 13, + "tracks": [ { - 'id': 467, - 'type': self.CHART_TYPE_EXHAUST, + "id": 467, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 585, - 'type': self.CHART_TYPE_EXHAUST, + "id": 585, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 560, - 'type': self.CHART_TYPE_EXHAUST, + "id": 560, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 13, - 'tracks': [ + "level": 8, + "season_id": 13, + "tracks": [ { - 'id': 599, - 'type': self.CHART_TYPE_EXHAUST, + "id": 599, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 101, - 'type': self.CHART_TYPE_EXHAUST, + "id": 101, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 109, - 'type': self.CHART_TYPE_EXHAUST, + "id": 109, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 13, - 'tracks': [ + "level": 9, + "season_id": 13, + "tracks": [ { - 'id': 630, - 'type': self.CHART_TYPE_EXHAUST, + "id": 630, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 408, - 'type': self.CHART_TYPE_EXHAUST, + "id": 408, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 393, - 'type': self.CHART_TYPE_EXHAUST, + "id": 393, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 14, - 'tracks': [ + "level": 0, + "season_id": 14, + "tracks": [ { - 'id': 63, - 'type': self.CHART_TYPE_NOVICE, + "id": 63, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 328, - 'type': self.CHART_TYPE_ADVANCED, + "id": 328, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 266, - 'type': self.CHART_TYPE_ADVANCED, + "id": 266, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 14, - 'tracks': [ + "level": 1, + "season_id": 14, + "tracks": [ { - 'id': 23, - 'type': self.CHART_TYPE_ADVANCED, + "id": 23, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 453, - 'type': self.CHART_TYPE_ADVANCED, + "id": 453, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 153, - 'type': self.CHART_TYPE_ADVANCED, + "id": 153, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 14, - 'tracks': [ + "level": 2, + "season_id": 14, + "tracks": [ { - 'id': 458, - 'type': self.CHART_TYPE_ADVANCED, + "id": 458, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 514, - 'type': self.CHART_TYPE_ADVANCED, + "id": 514, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 71, - 'type': self.CHART_TYPE_ADVANCED, + "id": 71, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 14, - 'tracks': [ + "level": 3, + "season_id": 14, + "tracks": [ { - 'id': 392, - 'type': self.CHART_TYPE_ADVANCED, + "id": 392, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 388, - 'type': self.CHART_TYPE_EXHAUST, + "id": 388, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 569, - 'type': self.CHART_TYPE_EXHAUST, + "id": 569, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 14, - 'tracks': [ + "level": 4, + "season_id": 14, + "tracks": [ { - 'id': 508, - 'type': self.CHART_TYPE_ADVANCED, + "id": 508, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 405, - 'type': self.CHART_TYPE_EXHAUST, + "id": 405, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 266, - 'type': self.CHART_TYPE_EXHAUST, + "id": 266, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 14, - 'tracks': [ + "level": 5, + "season_id": 14, + "tracks": [ { - 'id': 50, - 'type': self.CHART_TYPE_EXHAUST, + "id": 50, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 172, - 'type': self.CHART_TYPE_EXHAUST, + "id": 172, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 33, - 'type': self.CHART_TYPE_EXHAUST, + "id": 33, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 14, - 'tracks': [ + "level": 6, + "season_id": 14, + "tracks": [ { - 'id': 210, - 'type': self.CHART_TYPE_EXHAUST, + "id": 210, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 232, - 'type': self.CHART_TYPE_EXHAUST, + "id": 232, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 485, - 'type': self.CHART_TYPE_EXHAUST, + "id": 485, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 14, - 'tracks': [ + "level": 7, + "season_id": 14, + "tracks": [ { - 'id': 457, - 'type': self.CHART_TYPE_EXHAUST, + "id": 457, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 514, - 'type': self.CHART_TYPE_EXHAUST, + "id": 514, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 556, - 'type': self.CHART_TYPE_EXHAUST, + "id": 556, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 14, - 'tracks': [ + "level": 8, + "season_id": 14, + "tracks": [ { - 'id': 534, - 'type': self.CHART_TYPE_EXHAUST, + "id": 534, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 273, - 'type': self.CHART_TYPE_EXHAUST, + "id": 273, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 220, - 'type': self.CHART_TYPE_EXHAUST, + "id": 220, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 14, - 'tracks': [ + "level": 9, + "season_id": 14, + "tracks": [ { - 'id': 420, - 'type': self.CHART_TYPE_EXHAUST, + "id": 420, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 444, - 'type': self.CHART_TYPE_EXHAUST, + "id": 444, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 151, - 'type': self.CHART_TYPE_EXHAUST, + "id": 151, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 15, - 'tracks': [ + "level": 0, + "season_id": 15, + "tracks": [ { - 'id': 117, - 'type': self.CHART_TYPE_NOVICE, + "id": 117, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 564, - 'type': self.CHART_TYPE_ADVANCED, + "id": 564, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 318, - 'type': self.CHART_TYPE_ADVANCED, + "id": 318, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 15, - 'tracks': [ + "level": 1, + "season_id": 15, + "tracks": [ { - 'id': 93, - 'type': self.CHART_TYPE_ADVANCED, + "id": 93, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 308, - 'type': self.CHART_TYPE_ADVANCED, + "id": 308, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 49, - 'type': self.CHART_TYPE_ADVANCED, + "id": 49, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 15, - 'tracks': [ + "level": 2, + "season_id": 15, + "tracks": [ { - 'id': 317, - 'type': self.CHART_TYPE_ADVANCED, + "id": 317, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 335, - 'type': self.CHART_TYPE_ADVANCED, + "id": 335, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 239, - 'type': self.CHART_TYPE_ADVANCED, + "id": 239, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 15, - 'tracks': [ + "level": 3, + "season_id": 15, + "tracks": [ { - 'id': 439, - 'type': self.CHART_TYPE_ADVANCED, + "id": 439, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 44, - 'type': self.CHART_TYPE_EXHAUST, + "id": 44, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 243, - 'type': self.CHART_TYPE_EXHAUST, + "id": 243, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 15, - 'tracks': [ + "level": 4, + "season_id": 15, + "tracks": [ { - 'id': 158, - 'type': self.CHART_TYPE_ADVANCED, + "id": 158, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 175, - 'type': self.CHART_TYPE_EXHAUST, + "id": 175, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 150, - 'type': self.CHART_TYPE_EXHAUST, + "id": 150, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 15, - 'tracks': [ + "level": 5, + "season_id": 15, + "tracks": [ { - 'id': 162, - 'type': self.CHART_TYPE_EXHAUST, + "id": 162, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 79, - 'type': self.CHART_TYPE_EXHAUST, + "id": 79, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 386, - 'type': self.CHART_TYPE_EXHAUST, + "id": 386, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 15, - 'tracks': [ + "level": 6, + "season_id": 15, + "tracks": [ { - 'id': 99, - 'type': self.CHART_TYPE_EXHAUST, + "id": 99, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 22, - 'type': self.CHART_TYPE_INFINITE, + "id": 22, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 164, - 'type': self.CHART_TYPE_EXHAUST, + "id": 164, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 15, - 'tracks': [ + "level": 7, + "season_id": 15, + "tracks": [ { - 'id': 406, - 'type': self.CHART_TYPE_EXHAUST, + "id": 406, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 344, - 'type': self.CHART_TYPE_EXHAUST, + "id": 344, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 6, - 'type': self.CHART_TYPE_INFINITE, + "id": 6, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'level': 8, - 'season_id': 15, - 'tracks': [ + "level": 8, + "season_id": 15, + "tracks": [ { - 'id': 660, - 'type': self.CHART_TYPE_EXHAUST, + "id": 660, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 378, - 'type': self.CHART_TYPE_EXHAUST, + "id": 378, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 465, - 'type': self.CHART_TYPE_EXHAUST, + "id": 465, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 15, - 'tracks': [ + "level": 9, + "season_id": 15, + "tracks": [ { - 'id': 413, - 'type': self.CHART_TYPE_EXHAUST, + "id": 413, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 221, - 'type': self.CHART_TYPE_EXHAUST, + "id": 221, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 342, - 'type': self.CHART_TYPE_EXHAUST, + "id": 342, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 10, - 'season_id': 15, - 'tracks': [ + "level": 10, + "season_id": 15, + "tracks": [ { - 'id': 125, - 'type': self.CHART_TYPE_INFINITE, + "id": 125, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 123, - 'type': self.CHART_TYPE_INFINITE, + "id": 123, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 124, - 'type': self.CHART_TYPE_INFINITE, + "id": 124, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'level': 11, - 'season_id': 15, - 'tracks': [ + "level": 11, + "season_id": 15, + "tracks": [ { - 'id': 366, - 'type': self.CHART_TYPE_EXHAUST, + "id": 366, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 695, - 'type': self.CHART_TYPE_INFINITE, + "id": 695, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 692, - 'type': self.CHART_TYPE_INFINITE, + "id": 692, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'level': 0, - 'season_id': 16, - 'tracks': [ + "level": 0, + "season_id": 16, + "tracks": [ { - 'id': 343, - 'type': self.CHART_TYPE_NOVICE, + "id": 343, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 144, - 'type': self.CHART_TYPE_ADVANCED, + "id": 144, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 569, - 'type': self.CHART_TYPE_ADVANCED, + "id": 569, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 16, - 'tracks': [ + "level": 1, + "season_id": 16, + "tracks": [ { - 'id': 515, - 'type': self.CHART_TYPE_ADVANCED, + "id": 515, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 254, - 'type': self.CHART_TYPE_ADVANCED, + "id": 254, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 354, - 'type': self.CHART_TYPE_ADVANCED, + "id": 354, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 16, - 'tracks': [ + "level": 2, + "season_id": 16, + "tracks": [ { - 'id': 441, - 'type': self.CHART_TYPE_ADVANCED, + "id": 441, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 524, - 'type': self.CHART_TYPE_ADVANCED, + "id": 524, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 187, - 'type': self.CHART_TYPE_ADVANCED, + "id": 187, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 16, - 'tracks': [ + "level": 3, + "season_id": 16, + "tracks": [ { - 'id': 117, - 'type': self.CHART_TYPE_ADVANCED, + "id": 117, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 446, - 'type': self.CHART_TYPE_EXHAUST, + "id": 446, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 435, - 'type': self.CHART_TYPE_EXHAUST, + "id": 435, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 16, - 'tracks': [ + "level": 4, + "season_id": 16, + "tracks": [ { - 'id': 180, - 'type': self.CHART_TYPE_ADVANCED, + "id": 180, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 260, - 'type': self.CHART_TYPE_EXHAUST, + "id": 260, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 451, - 'type': self.CHART_TYPE_EXHAUST, + "id": 451, + "type": self.CHART_TYPE_EXHAUST, }, ], }, @@ -2924,900 +2923,900 @@ class SoundVoltexGravityWarsSeason2( # as a song that it included was removed and thus the course was # as well. { - 'level': 6, - 'season_id': 16, - 'tracks': [ + "level": 6, + "season_id": 16, + "tracks": [ { - 'id': 440, - 'type': self.CHART_TYPE_EXHAUST, + "id": 440, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 112, - 'type': self.CHART_TYPE_EXHAUST, + "id": 112, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 401, - 'type': self.CHART_TYPE_EXHAUST, + "id": 401, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 16, - 'tracks': [ + "level": 7, + "season_id": 16, + "tracks": [ { - 'id': 325, - 'type': self.CHART_TYPE_EXHAUST, + "id": 325, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 387, - 'type': self.CHART_TYPE_EXHAUST, + "id": 387, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 42, - 'type': self.CHART_TYPE_INFINITE, + "id": 42, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'level': 8, - 'season_id': 16, - 'tracks': [ + "level": 8, + "season_id": 16, + "tracks": [ { - 'id': 676, - 'type': self.CHART_TYPE_EXHAUST, + "id": 676, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 494, - 'type': self.CHART_TYPE_INFINITE, + "id": 494, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 234, - 'type': self.CHART_TYPE_EXHAUST, + "id": 234, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 16, - 'tracks': [ + "level": 9, + "season_id": 16, + "tracks": [ { - 'id': 155, - 'type': self.CHART_TYPE_INFINITE, + "id": 155, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 623, - 'type': self.CHART_TYPE_EXHAUST, + "id": 623, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 329, - 'type': self.CHART_TYPE_EXHAUST, + "id": 329, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 10, - 'season_id': 16, - 'tracks': [ + "level": 10, + "season_id": 16, + "tracks": [ { - 'id': 450, - 'type': self.CHART_TYPE_EXHAUST, + "id": 450, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 634, - 'type': self.CHART_TYPE_EXHAUST, + "id": 634, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 360, - 'type': self.CHART_TYPE_EXHAUST, + "id": 360, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 11, - 'season_id': 16, - 'tracks': [ + "level": 11, + "season_id": 16, + "tracks": [ { - 'id': 116, - 'type': self.CHART_TYPE_INFINITE, + "id": 116, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 693, - 'type': self.CHART_TYPE_INFINITE, + "id": 693, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 694, - 'type': self.CHART_TYPE_INFINITE, + "id": 694, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'id': 0, - 'level': -1, - 'skill_name': '天下一 (梅)コース', - 'skill_name_id': 13, - 'season_id': 17, - 'tracks': [ + "id": 0, + "level": -1, + "skill_name": "天下一 (梅)コース", + "skill_name_id": 13, + "season_id": 17, + "tracks": [ { - 'id': 625, - 'type': self.CHART_TYPE_NOVICE, + "id": 625, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 697, - 'type': self.CHART_TYPE_NOVICE, + "id": 697, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 708, - 'type': self.CHART_TYPE_NOVICE, + "id": 708, + "type": self.CHART_TYPE_NOVICE, }, ], }, { - 'id': 1, - 'level': -1, - 'skill_name': '天下一 (竹)コース', - 'skill_name_id': 13, - 'season_id': 17, - 'tracks': [ + "id": 1, + "level": -1, + "skill_name": "天下一 (竹)コース", + "skill_name_id": 13, + "season_id": 17, + "tracks": [ { - 'id': 625, - 'type': self.CHART_TYPE_ADVANCED, + "id": 625, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 697, - 'type': self.CHART_TYPE_ADVANCED, + "id": 697, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 708, - 'type': self.CHART_TYPE_ADVANCED, + "id": 708, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'id': 2, - 'level': -1, - 'skill_name': '天下一 (松)コース', - 'skill_name_id': 13, - 'season_id': 17, - 'tracks': [ + "id": 2, + "level": -1, + "skill_name": "天下一 (松)コース", + "skill_name_id": 13, + "season_id": 17, + "tracks": [ { - 'id': 625, - 'type': self.CHART_TYPE_INFINITE, + "id": 625, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 697, - 'type': self.CHART_TYPE_EXHAUST, + "id": 697, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 708, - 'type': self.CHART_TYPE_EXHAUST, + "id": 708, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 0, - 'level': -1, - 'skill_name': '青龍の戯れ', - 'skill_name_id': 14, - 'season_id': 18, - 'tracks': [ + "id": 0, + "level": -1, + "skill_name": "青龍の戯れ", + "skill_name_id": 14, + "season_id": 18, + "tracks": [ { - 'id': 439, - 'type': self.CHART_TYPE_ADVANCED, + "id": 439, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 675, - 'type': self.CHART_TYPE_ADVANCED, + "id": 675, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 692, - 'type': self.CHART_TYPE_ADVANCED, + "id": 692, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'id': 1, - 'level': -1, - 'skill_name': '朱雀の戯れ', - 'skill_name_id': 16, - 'season_id': 18, - 'tracks': [ + "id": 1, + "level": -1, + "skill_name": "朱雀の戯れ", + "skill_name_id": 16, + "season_id": 18, + "tracks": [ { - 'id': 587, - 'type': self.CHART_TYPE_ADVANCED, + "id": 587, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 543, - 'type': self.CHART_TYPE_ADVANCED, + "id": 543, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 693, - 'type': self.CHART_TYPE_ADVANCED, + "id": 693, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'id': 2, - 'level': -1, - 'skill_name': '玄武の戯れ', - 'skill_name_id': 17, - 'season_id': 18, - 'tracks': [ + "id": 2, + "level": -1, + "skill_name": "玄武の戯れ", + "skill_name_id": 17, + "season_id": 18, + "tracks": [ { - 'id': 696, - 'type': self.CHART_TYPE_ADVANCED, + "id": 696, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 697, - 'type': self.CHART_TYPE_ADVANCED, + "id": 697, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 695, - 'type': self.CHART_TYPE_ADVANCED, + "id": 695, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'id': 3, - 'level': -1, - 'skill_name': '白虎の戯れ', - 'skill_name_id': 15, - 'season_id': 18, - 'tracks': [ + "id": 3, + "level": -1, + "skill_name": "白虎の戯れ", + "skill_name_id": 15, + "season_id": 18, + "tracks": [ { - 'id': 606, - 'type': self.CHART_TYPE_ADVANCED, + "id": 606, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 593, - 'type': self.CHART_TYPE_ADVANCED, + "id": 593, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 694, - 'type': self.CHART_TYPE_ADVANCED, + "id": 694, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'id': 4, - 'level': -1, - 'skill_name': '青龍の戯れ', - 'skill_name_id': 14, - 'season_id': 18, - 'tracks': [ + "id": 4, + "level": -1, + "skill_name": "青龍の戯れ", + "skill_name_id": 14, + "season_id": 18, + "tracks": [ { - 'id': 439, - 'type': self.CHART_TYPE_EXHAUST, + "id": 439, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 675, - 'type': self.CHART_TYPE_EXHAUST, + "id": 675, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 692, - 'type': self.CHART_TYPE_EXHAUST, + "id": 692, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 5, - 'level': -1, - 'skill_name': '朱雀の戯れ', - 'skill_name_id': 16, - 'season_id': 18, - 'tracks': [ + "id": 5, + "level": -1, + "skill_name": "朱雀の戯れ", + "skill_name_id": 16, + "season_id": 18, + "tracks": [ { - 'id': 587, - 'type': self.CHART_TYPE_EXHAUST, + "id": 587, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 543, - 'type': self.CHART_TYPE_EXHAUST, + "id": 543, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 693, - 'type': self.CHART_TYPE_EXHAUST, + "id": 693, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 6, - 'level': -1, - 'skill_name': '玄武の戯れ', - 'skill_name_id': 17, - 'season_id': 18, - 'tracks': [ + "id": 6, + "level": -1, + "skill_name": "玄武の戯れ", + "skill_name_id": 17, + "season_id": 18, + "tracks": [ { - 'id': 696, - 'type': self.CHART_TYPE_EXHAUST, + "id": 696, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 697, - 'type': self.CHART_TYPE_EXHAUST, + "id": 697, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 695, - 'type': self.CHART_TYPE_EXHAUST, + "id": 695, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 7, - 'level': -1, - 'skill_name': '白虎の戯れ', - 'skill_name_id': 15, - 'season_id': 18, - 'tracks': [ + "id": 7, + "level": -1, + "skill_name": "白虎の戯れ", + "skill_name_id": 15, + "season_id": 18, + "tracks": [ { - 'id': 606, - 'type': self.CHART_TYPE_EXHAUST, + "id": 606, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 593, - 'type': self.CHART_TYPE_EXHAUST, + "id": 593, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 694, - 'type': self.CHART_TYPE_EXHAUST, + "id": 694, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 0, - 'level': -1, - 'skill_name': 'RANK 名も無き草', - 'skill_name_id': 18, - 'season_id': 19, - 'tracks': [ + "id": 0, + "level": -1, + "skill_name": "RANK 名も無き草", + "skill_name_id": 18, + "season_id": 19, + "tracks": [ { - 'id': 783, - 'type': self.CHART_TYPE_NOVICE, + "id": 783, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 784, - 'type': self.CHART_TYPE_NOVICE, + "id": 784, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 785, - 'type': self.CHART_TYPE_NOVICE, + "id": 785, + "type": self.CHART_TYPE_NOVICE, }, ], }, { - 'id': 1, - 'level': -1, - 'skill_name': 'RANK 雪月花', - 'skill_name_id': 18, - 'season_id': 19, - 'tracks': [ + "id": 1, + "level": -1, + "skill_name": "RANK 雪月花", + "skill_name_id": 18, + "season_id": 19, + "tracks": [ { - 'id': 783, - 'type': self.CHART_TYPE_ADVANCED, + "id": 783, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 784, - 'type': self.CHART_TYPE_ADVANCED, + "id": 784, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 785, - 'type': self.CHART_TYPE_ADVANCED, + "id": 785, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'id': 2, - 'level': -1, - 'skill_name': 'RANK 金剛雲', - 'skill_name_id': 18, - 'season_id': 19, - 'tracks': [ + "id": 2, + "level": -1, + "skill_name": "RANK 金剛雲", + "skill_name_id": 18, + "season_id": 19, + "tracks": [ { - 'id': 783, - 'type': self.CHART_TYPE_EXHAUST, + "id": 783, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 784, - 'type': self.CHART_TYPE_EXHAUST, + "id": 784, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 785, - 'type': self.CHART_TYPE_EXHAUST, + "id": 785, + "type": self.CHART_TYPE_EXHAUST, }, ], }, # Manually specify IDs here since this has more than one level 11. { - 'id': 0, - 'level': 0, - 'season_id': 20, - 'tracks': [ + "id": 0, + "level": 0, + "season_id": 20, + "tracks": [ { - 'id': 657, - 'type': self.CHART_TYPE_NOVICE, + "id": 657, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 285, - 'type': self.CHART_TYPE_ADVANCED, + "id": 285, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 491, - 'type': self.CHART_TYPE_ADVANCED, + "id": 491, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'id': 1, - 'level': 1, - 'season_id': 20, - 'tracks': [ + "id": 1, + "level": 1, + "season_id": 20, + "tracks": [ { - 'id': 446, - 'type': self.CHART_TYPE_ADVANCED, + "id": 446, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 588, - 'type': self.CHART_TYPE_ADVANCED, + "id": 588, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 21, - 'type': self.CHART_TYPE_ADVANCED, + "id": 21, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'id': 2, - 'level': 2, - 'season_id': 20, - 'tracks': [ + "id": 2, + "level": 2, + "season_id": 20, + "tracks": [ { - 'id': 560, - 'type': self.CHART_TYPE_ADVANCED, + "id": 560, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 602, - 'type': self.CHART_TYPE_ADVANCED, + "id": 602, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 88, - 'type': self.CHART_TYPE_ADVANCED, + "id": 88, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'id': 3, - 'level': 3, - 'season_id': 20, - 'tracks': [ + "id": 3, + "level": 3, + "season_id": 20, + "tracks": [ { - 'id': 470, - 'type': self.CHART_TYPE_ADVANCED, + "id": 470, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 515, - 'type': self.CHART_TYPE_EXHAUST, + "id": 515, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 65, - 'type': self.CHART_TYPE_EXHAUST, + "id": 65, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 4, - 'level': 4, - 'season_id': 20, - 'tracks': [ + "id": 4, + "level": 4, + "season_id": 20, + "tracks": [ { - 'id': 499, - 'type': self.CHART_TYPE_ADVANCED, + "id": 499, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 358, - 'type': self.CHART_TYPE_EXHAUST, + "id": 358, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 72, - 'type': self.CHART_TYPE_EXHAUST, + "id": 72, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 5, - 'level': 5, - 'season_id': 20, - 'tracks': [ + "id": 5, + "level": 5, + "season_id": 20, + "tracks": [ { - 'id': 573, - 'type': self.CHART_TYPE_EXHAUST, + "id": 573, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 559, - 'type': self.CHART_TYPE_EXHAUST, + "id": 559, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 602, - 'type': self.CHART_TYPE_EXHAUST, + "id": 602, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 6, - 'level': 6, - 'season_id': 20, - 'tracks': [ + "id": 6, + "level": 6, + "season_id": 20, + "tracks": [ { - 'id': 255, - 'type': self.CHART_TYPE_EXHAUST, + "id": 255, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 164, - 'type': self.CHART_TYPE_EXHAUST, + "id": 164, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 783, - 'type': self.CHART_TYPE_EXHAUST, + "id": 783, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 7, - 'level': 7, - 'season_id': 20, - 'tracks': [ + "id": 7, + "level": 7, + "season_id": 20, + "tracks": [ { - 'id': 425, - 'type': self.CHART_TYPE_EXHAUST, + "id": 425, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 54, - 'type': self.CHART_TYPE_INFINITE, + "id": 54, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 771, - 'type': self.CHART_TYPE_EXHAUST, + "id": 771, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 8, - 'level': 8, - 'season_id': 20, - 'tracks': [ + "id": 8, + "level": 8, + "season_id": 20, + "tracks": [ { - 'id': 589, - 'type': self.CHART_TYPE_EXHAUST, + "id": 589, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 592, - 'type': self.CHART_TYPE_EXHAUST, + "id": 592, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 776, - 'type': self.CHART_TYPE_EXHAUST, + "id": 776, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 9, - 'level': 9, - 'season_id': 20, - 'tracks': [ + "id": 9, + "level": 9, + "season_id": 20, + "tracks": [ { - 'id': 779, - 'type': self.CHART_TYPE_EXHAUST, + "id": 779, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 611, - 'type': self.CHART_TYPE_EXHAUST, + "id": 611, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 670, - 'type': self.CHART_TYPE_EXHAUST, + "id": 670, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 10, - 'level': 10, - 'season_id': 20, - 'tracks': [ + "id": 10, + "level": 10, + "season_id": 20, + "tracks": [ { - 'id': 522, - 'type': self.CHART_TYPE_EXHAUST, + "id": 522, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 543, - 'type': self.CHART_TYPE_EXHAUST, + "id": 543, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 610, - 'type': self.CHART_TYPE_EXHAUST, + "id": 610, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 11, - 'level': 10, - 'season_id': 20, - 'tracks': [ + "id": 11, + "level": 10, + "season_id": 20, + "tracks": [ { - 'id': 122, - 'type': self.CHART_TYPE_INFINITE, + "id": 122, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 180, - 'type': self.CHART_TYPE_INFINITE, + "id": 180, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 214, - 'type': self.CHART_TYPE_INFINITE, + "id": 214, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'id': 12, - 'level': 11, - 'season_id': 20, - 'tracks': [ + "id": 12, + "level": 11, + "season_id": 20, + "tracks": [ { - 'id': 661, - 'type': self.CHART_TYPE_INFINITE, + "id": 661, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 258, - 'type': self.CHART_TYPE_INFINITE, + "id": 258, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 791, - 'type': self.CHART_TYPE_INFINITE, + "id": 791, + "type": self.CHART_TYPE_INFINITE, }, ], }, # Manually specify IDs here since this has more than one level 11. { - 'id': 0, - 'level': 0, - 'season_id': 21, - 'tracks': [ + "id": 0, + "level": 0, + "season_id": 21, + "tracks": [ { - 'id': 697, - 'type': self.CHART_TYPE_NOVICE, + "id": 697, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 314, - 'type': self.CHART_TYPE_ADVANCED, + "id": 314, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 768, - 'type': self.CHART_TYPE_ADVANCED, + "id": 768, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'id': 1, - 'level': 1, - 'season_id': 21, - 'tracks': [ + "id": 1, + "level": 1, + "season_id": 21, + "tracks": [ { - 'id': 16, - 'type': self.CHART_TYPE_ADVANCED, + "id": 16, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 528, - 'type': self.CHART_TYPE_ADVANCED, + "id": 528, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 118, - 'type': self.CHART_TYPE_ADVANCED, + "id": 118, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'id': 2, - 'level': 2, - 'season_id': 21, - 'tracks': [ + "id": 2, + "level": 2, + "season_id": 21, + "tracks": [ { - 'id': 330, - 'type': self.CHART_TYPE_ADVANCED, + "id": 330, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 644, - 'type': self.CHART_TYPE_ADVANCED, + "id": 644, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 74, - 'type': self.CHART_TYPE_ADVANCED, + "id": 74, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'id': 3, - 'level': 3, - 'season_id': 21, - 'tracks': [ + "id": 3, + "level": 3, + "season_id": 21, + "tracks": [ { - 'id': 494, - 'type': self.CHART_TYPE_ADVANCED, + "id": 494, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 294, - 'type': self.CHART_TYPE_EXHAUST, + "id": 294, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 61, - 'type': self.CHART_TYPE_EXHAUST, + "id": 61, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 4, - 'level': 4, - 'season_id': 21, - 'tracks': [ + "id": 4, + "level": 4, + "season_id": 21, + "tracks": [ { - 'id': 498, - 'type': self.CHART_TYPE_ADVANCED, + "id": 498, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 177, - 'type': self.CHART_TYPE_EXHAUST, + "id": 177, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 212, - 'type': self.CHART_TYPE_EXHAUST, + "id": 212, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 5, - 'level': 5, - 'season_id': 21, - 'tracks': [ + "id": 5, + "level": 5, + "season_id": 21, + "tracks": [ { - 'id': 319, - 'type': self.CHART_TYPE_EXHAUST, + "id": 319, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 53, - 'type': self.CHART_TYPE_EXHAUST, + "id": 53, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 603, - 'type': self.CHART_TYPE_EXHAUST, + "id": 603, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 6, - 'level': 6, - 'season_id': 21, - 'tracks': [ + "id": 6, + "level": 6, + "season_id": 21, + "tracks": [ { - 'id': 688, - 'type': self.CHART_TYPE_EXHAUST, + "id": 688, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 261, - 'type': self.CHART_TYPE_EXHAUST, + "id": 261, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 784, - 'type': self.CHART_TYPE_EXHAUST, + "id": 784, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 7, - 'level': 7, - 'season_id': 21, - 'tracks': [ + "id": 7, + "level": 7, + "season_id": 21, + "tracks": [ { - 'id': 777, - 'type': self.CHART_TYPE_EXHAUST, + "id": 777, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 387, - 'type': self.CHART_TYPE_EXHAUST, + "id": 387, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 659, - 'type': self.CHART_TYPE_EXHAUST, + "id": 659, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 8, - 'level': 8, - 'season_id': 21, - 'tracks': [ + "id": 8, + "level": 8, + "season_id": 21, + "tracks": [ { - 'id': 518, - 'type': self.CHART_TYPE_EXHAUST, + "id": 518, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 714, - 'type': self.CHART_TYPE_EXHAUST, + "id": 714, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 681, - 'type': self.CHART_TYPE_EXHAUST, + "id": 681, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 9, - 'level': 9, - 'season_id': 21, - 'tracks': [ + "id": 9, + "level": 9, + "season_id": 21, + "tracks": [ { - 'id': 529, - 'type': self.CHART_TYPE_EXHAUST, + "id": 529, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 682, - 'type': self.CHART_TYPE_EXHAUST, + "id": 682, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 597, - 'type': self.CHART_TYPE_EXHAUST, + "id": 597, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 10, - 'level': 10, - 'season_id': 21, - 'tracks': [ + "id": 10, + "level": 10, + "season_id": 21, + "tracks": [ { - 'id': 600, - 'type': self.CHART_TYPE_EXHAUST, + "id": 600, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 758, - 'type': self.CHART_TYPE_EXHAUST, + "id": 758, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 816, - 'type': self.CHART_TYPE_EXHAUST, + "id": 816, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'id': 11, - 'level': 10, - 'season_id': 21, - 'tracks': [ + "id": 11, + "level": 10, + "season_id": 21, + "tracks": [ { - 'id': 829, - 'type': self.CHART_TYPE_INFINITE, + "id": 829, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 830, - 'type': self.CHART_TYPE_INFINITE, + "id": 830, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 831, - 'type': self.CHART_TYPE_INFINITE, + "id": 831, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'id': 12, - 'level': 11, - 'season_id': 21, - 'tracks': [ + "id": 12, + "level": 11, + "season_id": 21, + "tracks": [ { - 'id': 914, - 'type': self.CHART_TYPE_INFINITE, + "id": 914, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 913, - 'type': self.CHART_TYPE_INFINITE, + "id": 913, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 915, - 'type': self.CHART_TYPE_INFINITE, + "id": 915, + "type": self.CHART_TYPE_INFINITE, }, ], }, @@ -3833,94 +3832,101 @@ class SoundVoltexGravityWarsSeason2( def handle_game_3_hiscore_request(self, request: Node) -> Node: # Grab location for local scores - locid = ID.parse_machine_id(request.child_value('locid')) + locid = ID.parse_machine_id(request.child_value("locid")) # Start the response packet - game = Node.void('game_3') + game = Node.void("game_3") # First, grab hit chart playcounts = self.data.local.music.get_hit_chart(self.game, self.version, 1024) - hitchart = Node.void('hit') + hitchart = Node.void("hit") game.add_child(hitchart) for (songid, count) in playcounts: - info = Node.void('d') + info = Node.void("d") hitchart.add_child(info) - info.add_child(Node.u32('id', songid)) - info.add_child(Node.u32('cnt', count)) + info.add_child(Node.u32("id", songid)) + info.add_child(Node.u32("cnt", count)) # Now, grab global and local scores as well as clear rates global_records = self.data.remote.music.get_all_records(self.game, self.version) users = { - uid: prof for (uid, prof) in self.data.local.user.get_all_profiles(self.game, self.version) + uid: prof + for (uid, prof) in self.data.local.user.get_all_profiles( + self.game, self.version + ) } - area_users = [ - uid for uid in users - if users[uid].get_int('loc', -1) == locid - ] - area_records = self.data.local.music.get_all_records(self.game, self.version, userlist=area_users) + area_users = [uid for uid in users if users[uid].get_int("loc", -1) == locid] + area_records = self.data.local.music.get_all_records( + self.game, self.version, userlist=area_users + ) clears = self.get_clear_rates() records: Dict[int, Dict[int, Dict[str, Tuple[UserID, Score]]]] = {} - missing_users = ( - [userid for (userid, _) in global_records if userid not in users] + - [userid for (userid, _) in area_records if userid not in users] - ) + missing_users = [ + userid for (userid, _) in global_records if userid not in users + ] + [userid for (userid, _) in area_records if userid not in users] for (userid, profile) in self.get_any_profiles(missing_users): users[userid] = profile for (userid, score) in global_records: if userid not in users: - raise Exception('Logic error, missing profile for user!') + raise Exception("Logic error, missing profile for user!") if score.id not in records: records[score.id] = {} if score.chart not in records[score.id]: records[score.id][score.chart] = {} - records[score.id][score.chart]['global'] = (userid, score) + records[score.id][score.chart]["global"] = (userid, score) for (userid, score) in area_records: if userid not in users: - raise Exception('Logic error, missing profile for user!') + raise Exception("Logic error, missing profile for user!") if score.id not in records: records[score.id] = {} if score.chart not in records[score.id]: records[score.id][score.chart] = {} - records[score.id][score.chart]['area'] = (userid, score) + records[score.id][score.chart]["area"] = (userid, score) # Output it to the game - highscores = Node.void('sc') + highscores = Node.void("sc") game.add_child(highscores) for musicid in records: for chart in records[musicid]: - (globaluserid, globalscore) = records[musicid][chart]['global'] + (globaluserid, globalscore) = records[musicid][chart]["global"] global_profile = users[globaluserid] - if clears[musicid][chart]['total'] > 0: - clear_rate = float(clears[musicid][chart]['clears']) / float(clears[musicid][chart]['total']) + if clears[musicid][chart]["total"] > 0: + clear_rate = float(clears[musicid][chart]["clears"]) / float( + clears[musicid][chart]["total"] + ) else: clear_rate = 0.0 - info = Node.void('d') + info = Node.void("d") highscores.add_child(info) - info.add_child(Node.u32('id', musicid)) - info.add_child(Node.u32('ty', chart)) - info.add_child(Node.string('a_sq', ID.format_extid(global_profile.extid))) - info.add_child(Node.string('a_nm', global_profile.get_str('name'))) - info.add_child(Node.u32('a_sc', globalscore.points)) - info.add_child(Node.s32('cr', int(clear_rate * 10000))) + info.add_child(Node.u32("id", musicid)) + info.add_child(Node.u32("ty", chart)) + info.add_child( + Node.string("a_sq", ID.format_extid(global_profile.extid)) + ) + info.add_child(Node.string("a_nm", global_profile.get_str("name"))) + info.add_child(Node.u32("a_sc", globalscore.points)) + info.add_child(Node.s32("cr", int(clear_rate * 10000))) - if 'area' in records[musicid][chart]: - (localuserid, localscore) = records[musicid][chart]['area'] + if "area" in records[musicid][chart]: + (localuserid, localscore) = records[musicid][chart]["area"] local_profile = users[localuserid] - info.add_child(Node.string('l_sq', ID.format_extid(local_profile.extid))) - info.add_child(Node.string('l_nm', local_profile.get_str('name'))) - info.add_child(Node.u32('l_sc', localscore.points)) + info.add_child( + Node.string("l_sq", ID.format_extid(local_profile.extid)) + ) + info.add_child(Node.string("l_nm", local_profile.get_str("name"))) + info.add_child(Node.u32("l_sc", localscore.points)) return game def handle_game_3_load_r_request(self, request: Node) -> Node: - refid = request.child_value('dataid') - game = Node.void('game_3') + refid = request.child_value("dataid") + game = Node.void("game_3") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -3930,237 +3936,285 @@ class SoundVoltexGravityWarsSeason2( if userid is not None: links = self.data.local.user.get_links(self.game, self.version, userid) for index, link in enumerate(links): - if link.type != 'rival': + if link.type != "rival": continue other_profile = self.get_profile(link.other_userid) if other_profile is None: continue # Base information about rival - rival = Node.void('rival') + rival = Node.void("rival") game.add_child(rival) - rival.add_child(Node.s16('no', index)) - rival.add_child(Node.string('seq', ID.format_extid(other_profile.extid))) - rival.add_child(Node.string('name', other_profile.get_str('name'))) + rival.add_child(Node.s16("no", index)) + rival.add_child( + Node.string("seq", ID.format_extid(other_profile.extid)) + ) + rival.add_child(Node.string("name", other_profile.get_str("name"))) # Return scores for this user on random charts - scores = self.data.remote.music.get_scores(self.game, self.version, link.other_userid) + scores = self.data.remote.music.get_scores( + self.game, self.version, link.other_userid + ) for score in scores: - music = Node.void('music') + music = Node.void("music") rival.add_child(music) - music.set_attribute('id', str(score.id)) - music.set_attribute('type', str(score.chart)) - music.set_attribute('sc', str(score.points)) + music.set_attribute("id", str(score.id)) + music.set_attribute("type", str(score.chart)) + music.set_attribute("sc", str(score.points)) return game def format_profile(self, userid: UserID, profile: Profile) -> Node: - game = Node.void('game_3') + game = Node.void("game_3") # Generic profile stuff - game.add_child(Node.string('name', profile.get_str('name'))) - game.add_child(Node.string('code', ID.format_extid(profile.extid))) - game.add_child(Node.u32('gamecoin_packet', profile.get_int('packet'))) - game.add_child(Node.u32('gamecoin_block', profile.get_int('block'))) - game.add_child(Node.s16('skill_name_id', profile.get_int('chosen_skill_id', profile.get_int('skill_name_id', -1)))) - game.add_child(Node.s32_array('hidden_param', profile.get_int_array('hidden_param', 20))) - game.add_child(Node.u32('blaster_energy', profile.get_int('blaster_energy'))) - game.add_child(Node.u32('blaster_count', profile.get_int('blaster_count'))) + game.add_child(Node.string("name", profile.get_str("name"))) + game.add_child(Node.string("code", ID.format_extid(profile.extid))) + game.add_child(Node.u32("gamecoin_packet", profile.get_int("packet"))) + game.add_child(Node.u32("gamecoin_block", profile.get_int("block"))) + game.add_child( + Node.s16( + "skill_name_id", + profile.get_int( + "chosen_skill_id", profile.get_int("skill_name_id", -1) + ), + ) + ) + game.add_child( + Node.s32_array("hidden_param", profile.get_int_array("hidden_param", 20)) + ) + game.add_child(Node.u32("blaster_energy", profile.get_int("blaster_energy"))) + game.add_child(Node.u32("blaster_count", profile.get_int("blaster_count"))) # Play statistics statistics = self.get_play_statistics(userid) - game.add_child(Node.u32('play_count', statistics.total_plays)) - game.add_child(Node.u32('daily_count', statistics.today_plays)) - game.add_child(Node.u32('play_chain', statistics.consecutive_days)) + game.add_child(Node.u32("play_count", statistics.total_plays)) + game.add_child(Node.u32("daily_count", statistics.today_plays)) + game.add_child(Node.u32("play_chain", statistics.consecutive_days)) # Last played stuff - if 'last' in profile: - lastdict = profile.get_dict('last') - last = Node.void('last') + if "last" in profile: + lastdict = profile.get_dict("last") + last = Node.void("last") game.add_child(last) - last.add_child(Node.s32('music_id', lastdict.get_int('music_id', -1))) - last.add_child(Node.u8('music_type', lastdict.get_int('music_type'))) - last.add_child(Node.u8('sort_type', lastdict.get_int('sort_type'))) - last.add_child(Node.u8('narrow_down', lastdict.get_int('narrow_down'))) - last.add_child(Node.u8('headphone', lastdict.get_int('headphone'))) - last.add_child(Node.u16('appeal_id', lastdict.get_int('appeal_id', 1001))) - last.add_child(Node.u16('comment_id', lastdict.get_int('comment_id'))) - last.add_child(Node.u8('gauge_option', lastdict.get_int('gauge_option'))) + last.add_child(Node.s32("music_id", lastdict.get_int("music_id", -1))) + last.add_child(Node.u8("music_type", lastdict.get_int("music_type"))) + last.add_child(Node.u8("sort_type", lastdict.get_int("sort_type"))) + last.add_child(Node.u8("narrow_down", lastdict.get_int("narrow_down"))) + last.add_child(Node.u8("headphone", lastdict.get_int("headphone"))) + last.add_child(Node.u16("appeal_id", lastdict.get_int("appeal_id", 1001))) + last.add_child(Node.u16("comment_id", lastdict.get_int("comment_id"))) + last.add_child(Node.u8("gauge_option", lastdict.get_int("gauge_option"))) # Item unlocks - itemnode = Node.void('item') + itemnode = Node.void("item") game.add_child(itemnode) game_config = self.get_game_config() - achievements = self.data.local.user.get_achievements(self.game, self.version, userid) + achievements = self.data.local.user.get_achievements( + self.game, self.version, userid + ) for item in achievements: - if item.type[:5] != 'item_': + if item.type[:5] != "item_": continue itemtype = int(item.type[5:]) - if game_config.get_bool('force_unlock_songs') and itemtype == self.GAME_CATALOG_TYPE_SONG: + if ( + game_config.get_bool("force_unlock_songs") + and itemtype == self.GAME_CATALOG_TYPE_SONG + ): # Don't echo unlocked songs, we will add all of them later continue - if game_config.get_bool('force_unlock_cards') and itemtype == self.GAME_CATALOG_TYPE_APPEAL_CARD: + if ( + game_config.get_bool("force_unlock_cards") + and itemtype == self.GAME_CATALOG_TYPE_APPEAL_CARD + ): # Don't echo unlocked appeal cards, we will add all of them later continue - if game_config.get_bool('force_unlock_crew') and itemtype == self.GAME_CATALOG_TYPE_CREW: + if ( + game_config.get_bool("force_unlock_crew") + and itemtype == self.GAME_CATALOG_TYPE_CREW + ): # Don't echo unlocked crew, we will add all of them later continue - info = Node.void('info') + info = Node.void("info") itemnode.add_child(info) - info.add_child(Node.u8('type', itemtype)) - info.add_child(Node.u32('id', item.id)) - info.add_child(Node.u32('param', item.data.get_int('param'))) + info.add_child(Node.u8("type", itemtype)) + info.add_child(Node.u32("id", item.id)) + info.add_child(Node.u32("param", item.data.get_int("param"))) - if game_config.get_bool('force_unlock_songs'): + if game_config.get_bool("force_unlock_songs"): ids: Dict[int, int] = {} songs = self.data.local.music.get_all_songs(self.game, self.version) for song in songs: if song.id not in ids: ids[song.id] = 0 - if song.data.get_int('difficulty') > 0: + if song.data.get_int("difficulty") > 0: ids[song.id] = ids[song.id] | (1 << song.chart) for itemid in ids: if ids[itemid] == 0: continue - info = Node.void('info') + info = Node.void("info") itemnode.add_child(info) - info.add_child(Node.u8('type', self.GAME_CATALOG_TYPE_SONG)) - info.add_child(Node.u32('id', itemid)) - info.add_child(Node.u32('param', ids[itemid])) + info.add_child(Node.u8("type", self.GAME_CATALOG_TYPE_SONG)) + info.add_child(Node.u32("id", itemid)) + info.add_child(Node.u32("param", ids[itemid])) - if game_config.get_bool('force_unlock_cards'): + if game_config.get_bool("force_unlock_cards"): catalog = self.data.local.game.get_items(self.game, self.version) for unlock in catalog: - if unlock.type != 'appealcard': + if unlock.type != "appealcard": continue - info = Node.void('info') + info = Node.void("info") itemnode.add_child(info) - info.add_child(Node.u8('type', self.GAME_CATALOG_TYPE_APPEAL_CARD)) - info.add_child(Node.u32('id', unlock.id)) - info.add_child(Node.u32('param', 1)) + info.add_child(Node.u8("type", self.GAME_CATALOG_TYPE_APPEAL_CARD)) + info.add_child(Node.u32("id", unlock.id)) + info.add_child(Node.u32("param", 1)) - if game_config.get_bool('force_unlock_crew'): + if game_config.get_bool("force_unlock_crew"): for crewid in range(1, 781): - info = Node.void('info') + info = Node.void("info") itemnode.add_child(info) - info.add_child(Node.u8('type', self.GAME_CATALOG_TYPE_CREW)) - info.add_child(Node.u32('id', crewid)) - info.add_child(Node.u32('param', 1)) + info.add_child(Node.u8("type", self.GAME_CATALOG_TYPE_CREW)) + info.add_child(Node.u32("id", crewid)) + info.add_child(Node.u32("param", 1)) # Skill courses - skill = Node.void('skill') + skill = Node.void("skill") game.add_child(skill) - course_all = Node.void('course_all') + course_all = Node.void("course_all") skill.add_child(course_all) skill_level = -1 for course in achievements: - if course.type != 'course': + if course.type != "course": continue course_id = course.id % 100 season_id = int(course.id / 100) - if course.data.get_int('clear_type') >= 2: + if course.data.get_int("clear_type") >= 2: # The user cleared this, lets take the highest level clear for this courselist = [ - c for c in - self._get_skill_analyzer_courses() if - c.get('id', c['level']) == course_id and - c['season_id'] == season_id + c + for c in self._get_skill_analyzer_courses() + if c.get("id", c["level"]) == course_id + and c["season_id"] == season_id ] if len(courselist) > 0: - skill_level = max(skill_level, courselist[0]['level']) + skill_level = max(skill_level, courselist[0]["level"]) - info = Node.void('d') + info = Node.void("d") course_all.add_child(info) - info.add_child(Node.s16('crsid', course_id)) - info.add_child(Node.s16('ct', course.data.get_int('clear_type'))) - info.add_child(Node.s16('ar', course.data.get_int('achievement_rate'))) - info.add_child(Node.s32('ssnid', season_id)) + info.add_child(Node.s16("crsid", course_id)) + info.add_child(Node.s16("ct", course.data.get_int("clear_type"))) + info.add_child(Node.s16("ar", course.data.get_int("achievement_rate"))) + info.add_child(Node.s32("ssnid", season_id)) # Calculated skill level - game.add_child(Node.s16('skill_level', skill_level)) + game.add_child(Node.s16("skill_level", skill_level)) # Story mode unlocks - storynode = Node.void('story') + storynode = Node.void("story") game.add_child(storynode) for story in achievements: - if story.type != 'story': + if story.type != "story": continue - info = Node.void('info') + info = Node.void("info") storynode.add_child(info) - info.add_child(Node.s32('story_id', story.id)) - info.add_child(Node.s32('progress_id', story.data.get_int('progress_id'))) - info.add_child(Node.s32('progress_param', story.data.get_int('progress_param'))) - info.add_child(Node.s32('clear_cnt', story.data.get_int('clear_cnt'))) - info.add_child(Node.u32('route_flg', story.data.get_int('route_flg'))) + info.add_child(Node.s32("story_id", story.id)) + info.add_child(Node.s32("progress_id", story.data.get_int("progress_id"))) + info.add_child( + Node.s32("progress_param", story.data.get_int("progress_param")) + ) + info.add_child(Node.s32("clear_cnt", story.data.get_int("clear_cnt"))) + info.add_child(Node.u32("route_flg", story.data.get_int("route_flg"))) # Game parameters - paramnode = Node.void('param') + paramnode = Node.void("param") game.add_child(paramnode) for param in achievements: - if param.type[:6] != 'param_': + if param.type[:6] != "param_": continue paramtype = int(param.type[6:]) - info = Node.void('info') + info = Node.void("info") paramnode.add_child(info) - info.add_child(Node.s32('id', param.id)) - info.add_child(Node.s32('type', paramtype)) - info.add_child(Node.s32_array('param', param.data['param'])) # This looks to be variable, so no validation on length + info.add_child(Node.s32("id", param.id)) + info.add_child(Node.s32("type", paramtype)) + info.add_child( + Node.s32_array("param", param.data["param"]) + ) # This looks to be variable, so no validation on length return game - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() # Update blaster energy and in-game currencies - earned_gamecoin_packet = request.child_value('earned_gamecoin_packet') + earned_gamecoin_packet = request.child_value("earned_gamecoin_packet") if earned_gamecoin_packet is not None: - newprofile.replace_int('packet', newprofile.get_int('packet') + earned_gamecoin_packet) - earned_gamecoin_block = request.child_value('earned_gamecoin_block') + newprofile.replace_int( + "packet", newprofile.get_int("packet") + earned_gamecoin_packet + ) + earned_gamecoin_block = request.child_value("earned_gamecoin_block") if earned_gamecoin_block is not None: - newprofile.replace_int('block', newprofile.get_int('block') + earned_gamecoin_block) - earned_blaster_energy = request.child_value('earned_blaster_energy') + newprofile.replace_int( + "block", newprofile.get_int("block") + earned_gamecoin_block + ) + earned_blaster_energy = request.child_value("earned_blaster_energy") if earned_blaster_energy is not None: - newprofile.replace_int('blaster_energy', newprofile.get_int('blaster_energy') + earned_blaster_energy) + newprofile.replace_int( + "blaster_energy", + newprofile.get_int("blaster_energy") + earned_blaster_energy, + ) # Miscelaneous stuff - newprofile.replace_int('blaster_count', request.child_value('blaster_count')) - newprofile.replace_int('chosen_skill_id', request.child_value('skill_name_id')) - newprofile.replace_int_array('hidden_param', 20, request.child_value('hidden_param')) + newprofile.replace_int("blaster_count", request.child_value("blaster_count")) + newprofile.replace_int("chosen_skill_id", request.child_value("skill_name_id")) + newprofile.replace_int_array( + "hidden_param", 20, request.child_value("hidden_param") + ) # Update user's unlock status if we aren't force unlocked game_config = self.get_game_config() - if request.child('item') is not None: - for child in request.child('item').children: - if child.name != 'info': + if request.child("item") is not None: + for child in request.child("item").children: + if child.name != "info": continue - item_id = child.child_value('id') - item_type = child.child_value('type') - param = child.child_value('param') + item_id = child.child_value("id") + item_type = child.child_value("type") + param = child.child_value("param") - if game_config.get_bool('force_unlock_cards') and item_type == self.GAME_CATALOG_TYPE_APPEAL_CARD: + if ( + game_config.get_bool("force_unlock_cards") + and item_type == self.GAME_CATALOG_TYPE_APPEAL_CARD + ): # Don't save back appeal cards because they were force unlocked continue - if game_config.get_bool('force_unlock_songs') and item_type == self.GAME_CATALOG_TYPE_SONG: + if ( + game_config.get_bool("force_unlock_songs") + and item_type == self.GAME_CATALOG_TYPE_SONG + ): # Don't save back songs, because they were force unlocked continue - if game_config.get_bool('force_unlock_crew') and item_type == self.GAME_CATALOG_TYPE_CREW: + if ( + game_config.get_bool("force_unlock_crew") + and item_type == self.GAME_CATALOG_TYPE_CREW + ): # Don't save back crew, because they were force unlocked continue @@ -4169,70 +4223,70 @@ class SoundVoltexGravityWarsSeason2( self.version, userid, item_id, - f'item_{item_type}', + f"item_{item_type}", { - 'param': param, + "param": param, }, ) # Update story progress - if request.child('story') is not None: - for child in request.child('story').children: - if child.name != 'info': + if request.child("story") is not None: + for child in request.child("story").children: + if child.name != "info": continue - story_id = child.child_value('story_id') - progress_id = child.child_value('progress_id') - progress_param = child.child_value('progress_param') - clear_cnt = child.child_value('clear_cnt') - route_flg = child.child_value('route_flg') + story_id = child.child_value("story_id") + progress_id = child.child_value("progress_id") + progress_param = child.child_value("progress_param") + clear_cnt = child.child_value("clear_cnt") + route_flg = child.child_value("route_flg") self.data.local.user.put_achievement( self.game, self.version, userid, story_id, - 'story', + "story", { - 'progress_id': progress_id, - 'progress_param': progress_param, - 'clear_cnt': clear_cnt, - 'route_flg': route_flg, + "progress_id": progress_id, + "progress_param": progress_param, + "clear_cnt": clear_cnt, + "route_flg": route_flg, }, ) # Update params - if request.child('param') is not None: - for child in request.child('param').children: - if child.name != 'info': + if request.child("param") is not None: + for child in request.child("param").children: + if child.name != "info": continue - param_id = child.child_value('id') - param_type = child.child_value('type') - param_param = child.child_value('param') + param_id = child.child_value("id") + param_type = child.child_value("type") + param_param = child.child_value("param") self.data.local.user.put_achievement( self.game, self.version, userid, param_id, - f'param_{param_type}', + f"param_{param_type}", { - 'param': param_param, + "param": param_param, }, ) # Grab last information. - lastdict = newprofile.get_dict('last') - lastdict.replace_int('headphone', request.child_value('headphone')) - lastdict.replace_int('appeal_id', request.child_value('appeal_id')) - lastdict.replace_int('comment_id', request.child_value('comment_id')) - lastdict.replace_int('music_id', request.child_value('music_id')) - lastdict.replace_int('music_type', request.child_value('music_type')) - lastdict.replace_int('sort_type', request.child_value('sort_type')) - lastdict.replace_int('narrow_down', request.child_value('narrow_down')) - lastdict.replace_int('gauge_option', request.child_value('gauge_option')) + lastdict = newprofile.get_dict("last") + lastdict.replace_int("headphone", request.child_value("headphone")) + lastdict.replace_int("appeal_id", request.child_value("appeal_id")) + lastdict.replace_int("comment_id", request.child_value("comment_id")) + lastdict.replace_int("music_id", request.child_value("music_id")) + lastdict.replace_int("music_type", request.child_value("music_type")) + lastdict.replace_int("sort_type", request.child_value("sort_type")) + lastdict.replace_int("narrow_down", request.child_value("narrow_down")) + lastdict.replace_int("gauge_option", request.child_value("gauge_option")) # Save back last information gleaned from results - newprofile.replace_dict('last', lastdict) + newprofile.replace_dict("last", lastdict) # Keep track of play statistics self.update_play_statistics(userid) diff --git a/bemani/backend/sdvx/heavenlyhaven.py b/bemani/backend/sdvx/heavenlyhaven.py index 687bd53..50c98d6 100644 --- a/bemani/backend/sdvx/heavenlyhaven.py +++ b/bemani/backend/sdvx/heavenlyhaven.py @@ -15,7 +15,7 @@ class SoundVoltexHeavenlyHaven( SoundVoltexBase, ): - name: str = 'SOUND VOLTEX IV HEAVENLY HAVEN' + name: str = "SOUND VOLTEX IV HEAVENLY HAVEN" version: int = VersionConstants.SDVX_HEAVENLY_HAVEN GAME_LIMITED_LOCKED: Final[int] = 1 @@ -69,12 +69,14 @@ class SoundVoltexHeavenlyHaven( GAME_SKILL_NAME_ID_BMK2017: Final[int] = 19 GAME_SKILL_NAME_ID_KAC_7TH_TIGER: Final[int] = 20 GAME_SKILL_NAME_ID_KAC_7TH_WOLF: Final[int] = 21 - GAME_SKILL_NAME_ID_RIKKA: Final[int] = 22 # For the course that ran from 1/18/2018-2/18/2018 + GAME_SKILL_NAME_ID_RIKKA: Final[ + int + ] = 22 # For the course that ran from 1/18/2018-2/18/2018 GAME_SKILL_NAME_ID_KAC_8TH: Final[int] = 23 # Return the local2 service so that SDVX 4 and above will send certain packets. extra_services: List[str] = [ - 'local2', + "local2", ] @classmethod @@ -83,36 +85,36 @@ class SoundVoltexHeavenlyHaven( Return all of our front-end modifiably settings. """ return { - 'bools': [ + "bools": [ { - 'name': 'Disable Online Matching', - 'tip': 'Disable online matching between games.', - 'category': 'game_config', - 'setting': 'disable_matching', + "name": "Disable Online Matching", + "tip": "Disable online matching between games.", + "category": "game_config", + "setting": "disable_matching", }, { - 'name': 'Force Song Unlock', - 'tip': 'Force unlock all songs.', - 'category': 'game_config', - 'setting': 'force_unlock_songs', + "name": "Force Song Unlock", + "tip": "Force unlock all songs.", + "category": "game_config", + "setting": "force_unlock_songs", }, { - 'name': 'Force Appeal Card Unlock', - 'tip': 'Force unlock all appeal cards.', - 'category': 'game_config', - 'setting': 'force_unlock_cards', + "name": "Force Appeal Card Unlock", + "tip": "Force unlock all appeal cards.", + "category": "game_config", + "setting": "force_unlock_cards", }, { - 'name': 'Force Crew Card Unlock', - 'tip': 'Force unlock all crew and subcrew cards.', - 'category': 'game_config', - 'setting': 'force_unlock_crew', + "name": "Force Crew Card Unlock", + "tip": "Force unlock all crew and subcrew cards.", + "category": "game_config", + "setting": "force_unlock_crew", }, { - 'name': '50th Anniversary Celebration', - 'tip': 'Display the 50th anniversary screen in attract mode', - 'category': 'game_config', - 'setting': '50th_anniversary', + "name": "50th Anniversary Celebration", + "tip": "Display the 50th anniversary screen in attract mode", + "category": "game_config", + "setting": "50th_anniversary", }, ], } @@ -171,24 +173,24 @@ class SoundVoltexHeavenlyHaven( }[grade] def handle_game_sv4_exception_request(self, request: Node) -> Node: - return Node.void('game') + return Node.void("game") def handle_game_sv4_lounge_request(self, request: Node) -> Node: - game = Node.void('game') + game = Node.void("game") # Refresh interval in seconds. - game.add_child(Node.u32('interval', 10)) + game.add_child(Node.u32("interval", 10)) return game def handle_game_sv4_entry_s_request(self, request: Node) -> Node: - game = Node.void('game') + game = Node.void("game") # This should be created on the fly for a lobby that we're in. - game.add_child(Node.u32('entry_id', 1)) + game.add_child(Node.u32("entry_id", 1)) return game def handle_game_sv4_entry_e_request(self, request: Node) -> Node: # Lobby destroy method, eid node (u32) should be used # to destroy any open lobbies. - return Node.void('game') + return Node.void("game") def __get_skill_analyzer_seasons(self) -> Dict[int, str]: return { @@ -255,3040 +257,3043 @@ class SoundVoltexHeavenlyHaven( return [ # Skill LV.01 { - 'season_id': 0, - 'skill_level': 1, - 'tracks': [ + "season_id": 0, + "skill_level": 1, + "tracks": [ { - 'id': 653, - 'type': self.CHART_TYPE_NOVICE, + "id": 653, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 846, - 'type': self.CHART_TYPE_ADVANCED, + "id": 846, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 23, - 'type': self.CHART_TYPE_ADVANCED, + "id": 23, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 1, - 'skill_level': 1, - 'tracks': [ + "season_id": 1, + "skill_level": 1, + "tracks": [ { - 'id': 60, - 'type': self.CHART_TYPE_NOVICE, + "id": 60, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 770, - 'type': self.CHART_TYPE_ADVANCED, + "id": 770, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 16, - 'type': self.CHART_TYPE_ADVANCED, + "id": 16, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 2, - 'skill_level': 1, - 'tracks': [ + "season_id": 2, + "skill_level": 1, + "tracks": [ { - 'id': 17, - 'type': self.CHART_TYPE_ADVANCED, + "id": 17, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 922, - 'type': self.CHART_TYPE_NOVICE, + "id": 922, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 76, - 'type': self.CHART_TYPE_ADVANCED, + "id": 76, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 3, - 'skill_level': 1, - 'tracks': [ + "season_id": 3, + "skill_level": 1, + "tracks": [ { - 'id': 201, - 'type': self.CHART_TYPE_ADVANCED, + "id": 201, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 182, - 'type': self.CHART_TYPE_ADVANCED, + "id": 182, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 766, - 'type': self.CHART_TYPE_ADVANCED, + "id": 766, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 4, - 'skill_level': 1, - 'tracks': [ + "season_id": 4, + "skill_level": 1, + "tracks": [ { - 'id': 106, - 'type': self.CHART_TYPE_ADVANCED, + "id": 106, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 568, - 'type': self.CHART_TYPE_ADVANCED, + "id": 568, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 768, - 'type': self.CHART_TYPE_ADVANCED, + "id": 768, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 5, - 'skill_level': 1, - 'tracks': [ + "season_id": 5, + "skill_level": 1, + "tracks": [ { - 'id': 795, - 'type': self.CHART_TYPE_NOVICE, + "id": 795, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 110, - 'type': self.CHART_TYPE_ADVANCED, + "id": 110, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 51, - 'type': self.CHART_TYPE_EXHAUST, + "id": 51, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 6, - 'skill_level': 1, - 'tracks': [ + "season_id": 6, + "skill_level": 1, + "tracks": [ { - 'id': 258, - 'type': self.CHART_TYPE_NOVICE, + "id": 258, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 913, - 'type': self.CHART_TYPE_NOVICE, + "id": 913, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 189, - 'type': self.CHART_TYPE_ADVANCED, + "id": 189, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 7, - 'skill_level': 1, - 'tracks': [ + "season_id": 7, + "skill_level": 1, + "tracks": [ { - 'id': 1025, - 'type': self.CHART_TYPE_NOVICE, + "id": 1025, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 914, - 'type': self.CHART_TYPE_NOVICE, + "id": 914, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 186, - 'type': self.CHART_TYPE_ADVANCED, + "id": 186, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 8, - 'skill_level': 1, - 'tracks': [ + "season_id": 8, + "skill_level": 1, + "tracks": [ { - 'id': 600, - 'type': self.CHART_TYPE_NOVICE, + "id": 600, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 915, - 'type': self.CHART_TYPE_NOVICE, + "id": 915, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 671, - 'type': self.CHART_TYPE_ADVANCED, + "id": 671, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 9, - 'skill_level': 1, - 'tracks': [ + "season_id": 9, + "skill_level": 1, + "tracks": [ { - 'id': 1035, - 'type': self.CHART_TYPE_NOVICE, + "id": 1035, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 1014, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1014, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 1033, - 'type': self.CHART_TYPE_NOVICE, + "id": 1033, + "type": self.CHART_TYPE_NOVICE, }, ], }, { - 'season_id': 10, - 'skill_level': 1, - 'tracks': [ + "season_id": 10, + "skill_level": 1, + "tracks": [ { - 'id': 1044, - 'type': self.CHART_TYPE_NOVICE, + "id": 1044, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 1176, - 'type': self.CHART_TYPE_NOVICE, + "id": 1176, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 1083, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1083, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 11, - 'skill_level': 1, - 'tracks': [ + "season_id": 11, + "skill_level": 1, + "tracks": [ { - 'id': 1049, - 'type': self.CHART_TYPE_NOVICE, + "id": 1049, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 367, - 'type': self.CHART_TYPE_NOVICE, + "id": 367, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 1005, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1005, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 12, - 'skill_level': 1, - 'tracks': [ + "season_id": 12, + "skill_level": 1, + "tracks": [ { - 'id': 1190, - 'type': self.CHART_TYPE_NOVICE, + "id": 1190, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 636, - 'type': self.CHART_TYPE_NOVICE, + "id": 636, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 1054, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1054, + "type": self.CHART_TYPE_ADVANCED, }, ], }, # Skill LV.02 { - 'season_id': 0, - 'skill_level': 2, - 'tracks': [ + "season_id": 0, + "skill_level": 2, + "tracks": [ { - 'id': 6, - 'type': self.CHART_TYPE_ADVANCED, + "id": 6, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 222, - 'type': self.CHART_TYPE_ADVANCED, + "id": 222, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 48, - 'type': self.CHART_TYPE_ADVANCED, + "id": 48, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 1, - 'skill_level': 2, - 'tracks': [ + "season_id": 1, + "skill_level": 2, + "tracks": [ { - 'id': 566, - 'type': self.CHART_TYPE_ADVANCED, + "id": 566, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 748, - 'type': self.CHART_TYPE_ADVANCED, + "id": 748, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 19, - 'type': self.CHART_TYPE_ADVANCED, + "id": 19, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 2, - 'skill_level': 2, - 'tracks': [ + "season_id": 2, + "skill_level": 2, + "tracks": [ { - 'id': 22, - 'type': self.CHART_TYPE_ADVANCED, + "id": 22, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 40, - 'type': self.CHART_TYPE_ADVANCED, + "id": 40, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 275, - 'type': self.CHART_TYPE_ADVANCED, + "id": 275, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 3, - 'skill_level': 2, - 'tracks': [ + "season_id": 3, + "skill_level": 2, + "tracks": [ { - 'id': 171, - 'type': self.CHART_TYPE_ADVANCED, + "id": 171, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 950, - 'type': self.CHART_TYPE_ADVANCED, + "id": 950, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 513, - 'type': self.CHART_TYPE_ADVANCED, + "id": 513, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 4, - 'skill_level': 2, - 'tracks': [ + "season_id": 4, + "skill_level": 2, + "tracks": [ { - 'id': 185, - 'type': self.CHART_TYPE_ADVANCED, + "id": 185, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 700, - 'type': self.CHART_TYPE_ADVANCED, + "id": 700, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 923, - 'type': self.CHART_TYPE_ADVANCED, + "id": 923, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 5, - 'skill_level': 2, - 'tracks': [ + "season_id": 5, + "skill_level": 2, + "tracks": [ { - 'id': 219, - 'type': self.CHART_TYPE_ADVANCED, + "id": 219, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 528, - 'type': self.CHART_TYPE_ADVANCED, + "id": 528, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 996, - 'type': self.CHART_TYPE_ADVANCED, + "id": 996, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 6, - 'skill_level': 2, - 'tracks': [ + "season_id": 6, + "skill_level": 2, + "tracks": [ { - 'id': 87, - 'type': self.CHART_TYPE_ADVANCED, + "id": 87, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 486, - 'type': self.CHART_TYPE_ADVANCED, + "id": 486, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 66, - 'type': self.CHART_TYPE_ADVANCED, + "id": 66, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 7, - 'skill_level': 2, - 'tracks': [ + "season_id": 7, + "skill_level": 2, + "tracks": [ { - 'id': 93, - 'type': self.CHART_TYPE_ADVANCED, + "id": 93, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 664, - 'type': self.CHART_TYPE_ADVANCED, + "id": 664, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 3, - 'type': self.CHART_TYPE_ADVANCED, + "id": 3, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 8, - 'skill_level': 2, - 'tracks': [ + "season_id": 8, + "skill_level": 2, + "tracks": [ { - 'id': 191, - 'type': self.CHART_TYPE_ADVANCED, + "id": 191, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 771, - 'type': self.CHART_TYPE_ADVANCED, + "id": 771, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 8, - 'type': self.CHART_TYPE_ADVANCED, + "id": 8, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 9, - 'skill_level': 2, - 'tracks': [ + "season_id": 9, + "skill_level": 2, + "tracks": [ { - 'id': 405, - 'type': self.CHART_TYPE_ADVANCED, + "id": 405, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 451, - 'type': self.CHART_TYPE_ADVANCED, + "id": 451, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 173, - 'type': self.CHART_TYPE_ADVANCED, + "id": 173, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 10, - 'skill_level': 2, - 'tracks': [ + "season_id": 10, + "skill_level": 2, + "tracks": [ { - 'id': 1074, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1074, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 1095, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1095, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 930, - 'type': self.CHART_TYPE_ADVANCED, + "id": 930, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 11, - 'skill_level': 2, - 'tracks': [ + "season_id": 11, + "skill_level": 2, + "tracks": [ { - 'id': 1057, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1057, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 1081, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1081, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 868, - 'type': self.CHART_TYPE_ADVANCED, + "id": 868, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 12, - 'skill_level': 2, - 'tracks': [ + "season_id": 12, + "skill_level": 2, + "tracks": [ { - 'id': 1076, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1076, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 1002, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1002, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 916, - 'type': self.CHART_TYPE_ADVANCED, + "id": 916, + "type": self.CHART_TYPE_ADVANCED, }, ], }, # Skill LV.03 { - 'season_id': 0, - 'skill_level': 3, - 'tracks': [ + "season_id": 0, + "skill_level": 3, + "tracks": [ { - 'id': 775, - 'type': self.CHART_TYPE_ADVANCED, + "id": 775, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 684, - 'type': self.CHART_TYPE_ADVANCED, + "id": 684, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 778, - 'type': self.CHART_TYPE_ADVANCED, + "id": 778, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 1, - 'skill_level': 3, - 'tracks': [ + "season_id": 1, + "skill_level": 3, + "tracks": [ { - 'id': 523, - 'type': self.CHART_TYPE_ADVANCED, + "id": 523, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 921, - 'type': self.CHART_TYPE_ADVANCED, + "id": 921, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 218, - 'type': self.CHART_TYPE_ADVANCED, + "id": 218, + "type": self.CHART_TYPE_ADVANCED, }, ], }, # Skill course for Season ID 2 was removed due to removed songs. { - 'season_id': 3, - 'skill_level': 3, - 'tracks': [ + "season_id": 3, + "skill_level": 3, + "tracks": [ { - 'id': 90, - 'type': self.CHART_TYPE_ADVANCED, + "id": 90, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 557, - 'type': self.CHART_TYPE_ADVANCED, + "id": 557, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 843, - 'type': self.CHART_TYPE_ADVANCED, + "id": 843, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 4, - 'skill_level': 3, - 'tracks': [ + "season_id": 4, + "skill_level": 3, + "tracks": [ { - 'id': 317, - 'type': self.CHART_TYPE_ADVANCED, + "id": 317, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 882, - 'type': self.CHART_TYPE_ADVANCED, + "id": 882, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 531, - 'type': self.CHART_TYPE_ADVANCED, + "id": 531, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 5, - 'skill_level': 3, - 'tracks': [ + "season_id": 5, + "skill_level": 3, + "tracks": [ { - 'id': 161, - 'type': self.CHART_TYPE_ADVANCED, + "id": 161, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 291, - 'type': self.CHART_TYPE_ADVANCED, + "id": 291, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 970, - 'type': self.CHART_TYPE_ADVANCED, + "id": 970, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 6, - 'skill_level': 3, - 'tracks': [ + "season_id": 6, + "skill_level": 3, + "tracks": [ { - 'id': 674, - 'type': self.CHART_TYPE_ADVANCED, + "id": 674, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 216, - 'type': self.CHART_TYPE_ADVANCED, + "id": 216, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 434, - 'type': self.CHART_TYPE_ADVANCED, + "id": 434, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 7, - 'skill_level': 3, - 'tracks': [ + "season_id": 7, + "skill_level": 3, + "tracks": [ { - 'id': 590, - 'type': self.CHART_TYPE_ADVANCED, + "id": 590, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 898, - 'type': self.CHART_TYPE_ADVANCED, + "id": 898, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 152, - 'type': self.CHART_TYPE_ADVANCED, + "id": 152, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 8, - 'skill_level': 3, - 'tracks': [ + "season_id": 8, + "skill_level": 3, + "tracks": [ { - 'id': 353, - 'type': self.CHART_TYPE_ADVANCED, + "id": 353, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 896, - 'type': self.CHART_TYPE_ADVANCED, + "id": 896, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 39, - 'type': self.CHART_TYPE_ADVANCED, + "id": 39, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 9, - 'skill_level': 3, - 'tracks': [ + "season_id": 9, + "skill_level": 3, + "tracks": [ { - 'id': 1008, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1008, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 608, - 'type': self.CHART_TYPE_ADVANCED, + "id": 608, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 815, - 'type': self.CHART_TYPE_ADVANCED, + "id": 815, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 10, - 'skill_level': 3, - 'tracks': [ + "season_id": 10, + "skill_level": 3, + "tracks": [ { - 'id': 1086, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1086, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 1122, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1122, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 1026, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1026, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 11, - 'skill_level': 3, - 'tracks': [ + "season_id": 11, + "skill_level": 3, + "tracks": [ { - 'id': 1001, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1001, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 1092, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1092, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 1113, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1113, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 12, - 'skill_level': 3, - 'tracks': [ + "season_id": 12, + "skill_level": 3, + "tracks": [ { - 'id': 1004, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1004, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 1111, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1111, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 1090, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1090, + "type": self.CHART_TYPE_ADVANCED, }, ], }, # Skill LV.04 { - 'season_id': 0, - 'skill_level': 4, - 'tracks': [ + "season_id": 0, + "skill_level": 4, + "tracks": [ { - 'id': 757, - 'type': self.CHART_TYPE_ADVANCED, + "id": 757, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 480, - 'type': self.CHART_TYPE_EXHAUST, + "id": 480, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 758, - 'type': self.CHART_TYPE_ADVANCED, + "id": 758, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 1, - 'skill_level': 4, - 'tracks': [ + "season_id": 1, + "skill_level": 4, + "tracks": [ { - 'id': 467, - 'type': self.CHART_TYPE_ADVANCED, + "id": 467, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 456, - 'type': self.CHART_TYPE_ADVANCED, + "id": 456, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 107, - 'type': self.CHART_TYPE_ADVANCED, + "id": 107, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 2, - 'skill_level': 4, - 'tracks': [ + "season_id": 2, + "skill_level": 4, + "tracks": [ { - 'id': 67, - 'type': self.CHART_TYPE_ADVANCED, + "id": 67, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 544, - 'type': self.CHART_TYPE_ADVANCED, + "id": 544, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 9, - 'type': self.CHART_TYPE_EXHAUST, + "id": 9, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 3, - 'skill_level': 4, - 'tracks': [ + "season_id": 3, + "skill_level": 4, + "tracks": [ { - 'id': 449, - 'type': self.CHART_TYPE_ADVANCED, + "id": 449, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 506, - 'type': self.CHART_TYPE_ADVANCED, + "id": 506, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 962, - 'type': self.CHART_TYPE_ADVANCED, + "id": 962, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 4, - 'skill_level': 4, - 'tracks': [ + "season_id": 4, + "skill_level": 4, + "tracks": [ { - 'id': 136, - 'type': self.CHART_TYPE_ADVANCED, + "id": 136, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 534, - 'type': self.CHART_TYPE_ADVANCED, + "id": 534, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 640, - 'type': self.CHART_TYPE_ADVANCED, + "id": 640, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 5, - 'skill_level': 4, - 'tracks': [ + "season_id": 5, + "skill_level": 4, + "tracks": [ { - 'id': 630, - 'type': self.CHART_TYPE_ADVANCED, + "id": 630, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 647, - 'type': self.CHART_TYPE_ADVANCED, + "id": 647, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 785, - 'type': self.CHART_TYPE_ADVANCED, + "id": 785, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 6, - 'skill_level': 4, - 'tracks': [ + "season_id": 6, + "skill_level": 4, + "tracks": [ { - 'id': 781, - 'type': self.CHART_TYPE_EXHAUST, + "id": 781, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 623, - 'type': self.CHART_TYPE_ADVANCED, + "id": 623, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 540, - 'type': self.CHART_TYPE_ADVANCED, + "id": 540, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 7, - 'skill_level': 4, - 'tracks': [ + "season_id": 7, + "skill_level": 4, + "tracks": [ { - 'id': 104, - 'type': self.CHART_TYPE_ADVANCED, + "id": 104, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 521, - 'type': self.CHART_TYPE_ADVANCED, + "id": 521, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 342, - 'type': self.CHART_TYPE_ADVANCED, + "id": 342, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 8, - 'skill_level': 4, - 'tracks': [ + "season_id": 8, + "skill_level": 4, + "tracks": [ { - 'id': 485, - 'type': self.CHART_TYPE_ADVANCED, + "id": 485, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 359, - 'type': self.CHART_TYPE_ADVANCED, + "id": 359, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 834, - 'type': self.CHART_TYPE_ADVANCED, + "id": 834, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 9, - 'skill_level': 4, - 'tracks': [ + "season_id": 9, + "skill_level": 4, + "tracks": [ { - 'id': 966, - 'type': self.CHART_TYPE_ADVANCED, + "id": 966, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 983, - 'type': self.CHART_TYPE_ADVANCED, + "id": 983, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 967, - 'type': self.CHART_TYPE_ADVANCED, + "id": 967, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 10, - 'skill_level': 4, - 'tracks': [ + "season_id": 10, + "skill_level": 4, + "tracks": [ { - 'id': 1070, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1070, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 1073, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1073, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 1022, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1022, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 11, - 'skill_level': 4, - 'tracks': [ + "season_id": 11, + "skill_level": 4, + "tracks": [ { - 'id': 1075, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1075, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 1123, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1123, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 1029, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1029, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 12, - 'skill_level': 4, - 'tracks': [ + "season_id": 12, + "skill_level": 4, + "tracks": [ { - 'id': 1094, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1094, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 1128, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1128, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 1027, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1027, + "type": self.CHART_TYPE_ADVANCED, }, ], }, # Skill LV.05 { - 'season_id': 0, - 'skill_level': 5, - 'tracks': [ + "season_id": 0, + "skill_level": 5, + "tracks": [ { - 'id': 871, - 'type': self.CHART_TYPE_ADVANCED, + "id": 871, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 327, - 'type': self.CHART_TYPE_EXHAUST, + "id": 327, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 66, - 'type': self.CHART_TYPE_EXHAUST, + "id": 66, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 1, - 'skill_level': 5, - 'tracks': [ + "season_id": 1, + "skill_level": 5, + "tracks": [ { - 'id': 435, - 'type': self.CHART_TYPE_EXHAUST, + "id": 435, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 750, - 'type': self.CHART_TYPE_EXHAUST, + "id": 750, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 700, - 'type': self.CHART_TYPE_EXHAUST, + "id": 700, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 2, - 'skill_level': 5, - 'tracks': [ + "season_id": 2, + "skill_level": 5, + "tracks": [ { - 'id': 318, - 'type': self.CHART_TYPE_EXHAUST, + "id": 318, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 157, - 'type': self.CHART_TYPE_EXHAUST, + "id": 157, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 567, - 'type': self.CHART_TYPE_EXHAUST, + "id": 567, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 3, - 'skill_level': 5, - 'tracks': [ + "season_id": 3, + "skill_level": 5, + "tracks": [ { - 'id': 760, - 'type': self.CHART_TYPE_ADVANCED, + "id": 760, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 1020, - 'type': self.CHART_TYPE_EXHAUST, + "id": 1020, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 923, - 'type': self.CHART_TYPE_EXHAUST, + "id": 923, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 4, - 'skill_level': 5, - 'tracks': [ + "season_id": 4, + "skill_level": 5, + "tracks": [ { - 'id': 65, - 'type': self.CHART_TYPE_EXHAUST, + "id": 65, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 966, - 'type': self.CHART_TYPE_EXHAUST, + "id": 966, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 874, - 'type': self.CHART_TYPE_EXHAUST, + "id": 874, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 5, - 'skill_level': 5, - 'tracks': [ + "season_id": 5, + "skill_level": 5, + "tracks": [ { - 'id': 645, - 'type': self.CHART_TYPE_ADVANCED, + "id": 645, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 335, - 'type': self.CHART_TYPE_EXHAUST, + "id": 335, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 961, - 'type': self.CHART_TYPE_EXHAUST, + "id": 961, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 6, - 'skill_level': 5, - 'tracks': [ + "season_id": 6, + "skill_level": 5, + "tracks": [ { - 'id': 695, - 'type': self.CHART_TYPE_ADVANCED, + "id": 695, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 276, - 'type': self.CHART_TYPE_EXHAUST, + "id": 276, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 870, - 'type': self.CHART_TYPE_EXHAUST, + "id": 870, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 7, - 'skill_level': 5, - 'tracks': [ + "season_id": 7, + "skill_level": 5, + "tracks": [ { - 'id': 743, - 'type': self.CHART_TYPE_ADVANCED, + "id": 743, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 958, - 'type': self.CHART_TYPE_EXHAUST, + "id": 958, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 441, - 'type': self.CHART_TYPE_EXHAUST, + "id": 441, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 8, - 'skill_level': 5, - 'tracks': [ + "season_id": 8, + "skill_level": 5, + "tracks": [ { - 'id': 790, - 'type': self.CHART_TYPE_ADVANCED, + "id": 790, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 277, - 'type': self.CHART_TYPE_EXHAUST, + "id": 277, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 944, - 'type': self.CHART_TYPE_EXHAUST, + "id": 944, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 9, - 'skill_level': 5, - 'tracks': [ + "season_id": 9, + "skill_level": 5, + "tracks": [ { - 'id': 964, - 'type': self.CHART_TYPE_ADVANCED, + "id": 964, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 58, - 'type': self.CHART_TYPE_EXHAUST, + "id": 58, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 1025, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1025, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 10, - 'skill_level': 5, - 'tracks': [ + "season_id": 10, + "skill_level": 5, + "tracks": [ { - 'id': 1040, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1040, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 1200, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1200, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 895, - 'type': self.CHART_TYPE_EXHAUST, + "id": 895, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 11, - 'skill_level': 5, - 'tracks': [ + "season_id": 11, + "skill_level": 5, + "tracks": [ { - 'id': 1024, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1024, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 1201, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1201, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 1124, - 'type': self.CHART_TYPE_EXHAUST, + "id": 1124, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 12, - 'skill_level': 5, - 'tracks': [ + "season_id": 12, + "skill_level": 5, + "tracks": [ { - 'id': 1007, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1007, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 1220, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1220, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 1067, - 'type': self.CHART_TYPE_EXHAUST, + "id": 1067, + "type": self.CHART_TYPE_EXHAUST, }, ], }, # Skill LV.06 { - 'season_id': 0, - 'skill_level': 6, - 'tracks': [ + "season_id": 0, + "skill_level": 6, + "tracks": [ { - 'id': 713, - 'type': self.CHART_TYPE_EXHAUST, + "id": 713, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 40, - 'type': self.CHART_TYPE_EXHAUST, + "id": 40, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 33, - 'type': self.CHART_TYPE_EXHAUST, + "id": 33, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 1, - 'skill_level': 6, - 'tracks': [ + "season_id": 1, + "skill_level": 6, + "tracks": [ { - 'id': 230, - 'type': self.CHART_TYPE_EXHAUST, + "id": 230, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 827, - 'type': self.CHART_TYPE_EXHAUST, + "id": 827, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 146, - 'type': self.CHART_TYPE_EXHAUST, + "id": 146, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 2, - 'skill_level': 6, - 'tracks': [ + "season_id": 2, + "skill_level": 6, + "tracks": [ { - 'id': 239, - 'type': self.CHART_TYPE_EXHAUST, + "id": 239, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 375, - 'type': self.CHART_TYPE_EXHAUST, + "id": 375, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 94, - 'type': self.CHART_TYPE_EXHAUST, + "id": 94, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 3, - 'skill_level': 6, - 'tracks': [ + "season_id": 3, + "skill_level": 6, + "tracks": [ { - 'id': 80, - 'type': self.CHART_TYPE_EXHAUST, + "id": 80, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 678, - 'type': self.CHART_TYPE_EXHAUST, + "id": 678, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 928, - 'type': self.CHART_TYPE_EXHAUST, + "id": 928, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 4, - 'skill_level': 6, - 'tracks': [ + "season_id": 4, + "skill_level": 6, + "tracks": [ { - 'id': 856, - 'type': self.CHART_TYPE_EXHAUST, + "id": 856, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 488, - 'type': self.CHART_TYPE_EXHAUST, + "id": 488, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 968, - 'type': self.CHART_TYPE_EXHAUST, + "id": 968, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 5, - 'skill_level': 6, - 'tracks': [ + "season_id": 5, + "skill_level": 6, + "tracks": [ { - 'id': 172, - 'type': self.CHART_TYPE_EXHAUST, + "id": 172, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 262, - 'type': self.CHART_TYPE_EXHAUST, + "id": 262, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 781, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 781, + "type": self.CHART_TYPE_MAXIMUM, }, ], }, { - 'season_id': 6, - 'skill_level': 6, - 'tracks': [ + "season_id": 6, + "skill_level": 6, + "tracks": [ { - 'id': 998, - 'type': self.CHART_TYPE_EXHAUST, + "id": 998, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 885, - 'type': self.CHART_TYPE_EXHAUST, + "id": 885, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 400, - 'type': self.CHART_TYPE_EXHAUST, + "id": 400, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 7, - 'skill_level': 6, - 'tracks': [ + "season_id": 7, + "skill_level": 6, + "tracks": [ { - 'id': 301, - 'type': self.CHART_TYPE_EXHAUST, + "id": 301, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 879, - 'type': self.CHART_TYPE_EXHAUST, + "id": 879, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 62, - 'type': self.CHART_TYPE_EXHAUST, + "id": 62, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 8, - 'skill_level': 6, - 'tracks': [ + "season_id": 8, + "skill_level": 6, + "tracks": [ { - 'id': 897, - 'type': self.CHART_TYPE_EXHAUST, + "id": 897, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 2, - 'type': self.CHART_TYPE_EXHAUST, + "id": 2, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 986, - 'type': self.CHART_TYPE_EXHAUST, + "id": 986, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 9, - 'skill_level': 6, - 'tracks': [ + "season_id": 9, + "skill_level": 6, + "tracks": [ { - 'id': 898, - 'type': self.CHART_TYPE_EXHAUST, + "id": 898, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 962, - 'type': self.CHART_TYPE_EXHAUST, + "id": 962, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 1032, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1032, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 10, - 'skill_level': 6, - 'tracks': [ + "season_id": 10, + "skill_level": 6, + "tracks": [ { - 'id': 1115, - 'type': self.CHART_TYPE_EXHAUST, + "id": 1115, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 1184, - 'type': self.CHART_TYPE_EXHAUST, + "id": 1184, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 1230, - 'type': self.CHART_TYPE_EXHAUST, + "id": 1230, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 11, - 'skill_level': 6, - 'tracks': [ + "season_id": 11, + "skill_level": 6, + "tracks": [ { - 'id': 1154, - 'type': self.CHART_TYPE_EXHAUST, + "id": 1154, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 1114, - 'type': self.CHART_TYPE_EXHAUST, + "id": 1114, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 891, - 'type': self.CHART_TYPE_EXHAUST, + "id": 891, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 12, - 'skill_level': 6, - 'tracks': [ + "season_id": 12, + "skill_level": 6, + "tracks": [ { - 'id': 1139, - 'type': self.CHART_TYPE_EXHAUST, + "id": 1139, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 864, - 'type': self.CHART_TYPE_EXHAUST, + "id": 864, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 1010, - 'type': self.CHART_TYPE_EXHAUST, + "id": 1010, + "type": self.CHART_TYPE_EXHAUST, }, ], }, # Skill LV.07 { - 'season_id': 0, - 'skill_level': 7, - 'tracks': [ + "season_id": 0, + "skill_level": 7, + "tracks": [ { - 'id': 349, - 'type': self.CHART_TYPE_EXHAUST, + "id": 349, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 896, - 'type': self.CHART_TYPE_EXHAUST, + "id": 896, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 246, - 'type': self.CHART_TYPE_EXHAUST, + "id": 246, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 1, - 'skill_level': 7, - 'tracks': [ + "season_id": 1, + "skill_level": 7, + "tracks": [ { - 'id': 210, - 'type': self.CHART_TYPE_EXHAUST, + "id": 210, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 558, - 'type': self.CHART_TYPE_EXHAUST, + "id": 558, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 368, - 'type': self.CHART_TYPE_EXHAUST, + "id": 368, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 2, - 'skill_level': 7, - 'tracks': [ + "season_id": 2, + "skill_level": 7, + "tracks": [ { - 'id': 769, - 'type': self.CHART_TYPE_EXHAUST, + "id": 769, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 710, - 'type': self.CHART_TYPE_EXHAUST, + "id": 710, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 609, - 'type': self.CHART_TYPE_EXHAUST, + "id": 609, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 3, - 'skill_level': 7, - 'tracks': [ + "season_id": 3, + "skill_level": 7, + "tracks": [ { - 'id': 967, - 'type': self.CHART_TYPE_EXHAUST, + "id": 967, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 711, - 'type': self.CHART_TYPE_EXHAUST, + "id": 711, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 594, - 'type': self.CHART_TYPE_EXHAUST, + "id": 594, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 4, - 'skill_level': 7, - 'tracks': [ + "season_id": 4, + "skill_level": 7, + "tracks": [ { - 'id': 738, - 'type': self.CHART_TYPE_EXHAUST, + "id": 738, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 264, - 'type': self.CHART_TYPE_EXHAUST, + "id": 264, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 834, - 'type': self.CHART_TYPE_EXHAUST, + "id": 834, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 5, - 'skill_level': 7, - 'tracks': [ + "season_id": 5, + "skill_level": 7, + "tracks": [ { - 'id': 762, - 'type': self.CHART_TYPE_EXHAUST, + "id": 762, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 544, - 'type': self.CHART_TYPE_EXHAUST, + "id": 544, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 898, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 898, + "type": self.CHART_TYPE_MAXIMUM, }, ], }, { - 'season_id': 6, - 'skill_level': 7, - 'tracks': [ + "season_id": 6, + "skill_level": 7, + "tracks": [ { - 'id': 211, - 'type': self.CHART_TYPE_EXHAUST, + "id": 211, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 14, - 'type': self.CHART_TYPE_EXHAUST, + "id": 14, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 183, - 'type': self.CHART_TYPE_EXHAUST, + "id": 183, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 7, - 'skill_level': 7, - 'tracks': [ + "season_id": 7, + "skill_level": 7, + "tracks": [ { - 'id': 666, - 'type': self.CHART_TYPE_EXHAUST, + "id": 666, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 54, - 'type': self.CHART_TYPE_EXHAUST, + "id": 54, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 763, - 'type': self.CHART_TYPE_EXHAUST, + "id": 763, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 8, - 'skill_level': 7, - 'tracks': [ + "season_id": 8, + "skill_level": 7, + "tracks": [ { - 'id': 145, - 'type': self.CHART_TYPE_EXHAUST, + "id": 145, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 99, - 'type': self.CHART_TYPE_EXHAUST, + "id": 99, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 90, - 'type': self.CHART_TYPE_INFINITE, + "id": 90, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'season_id': 9, - 'skill_level': 7, - 'tracks': [ + "season_id": 9, + "skill_level": 7, + "tracks": [ { - 'id': 490, - 'type': self.CHART_TYPE_EXHAUST, + "id": 490, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 889, - 'type': self.CHART_TYPE_EXHAUST, + "id": 889, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 1042, - 'type': self.CHART_TYPE_EXHAUST, + "id": 1042, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 10, - 'skill_level': 7, - 'tracks': [ + "season_id": 10, + "skill_level": 7, + "tracks": [ { - 'id': 1156, - 'type': self.CHART_TYPE_EXHAUST, + "id": 1156, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 1138, - 'type': self.CHART_TYPE_EXHAUST, + "id": 1138, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 1091, - 'type': self.CHART_TYPE_EXHAUST, + "id": 1091, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 11, - 'skill_level': 7, - 'tracks': [ + "season_id": 11, + "skill_level": 7, + "tracks": [ { - 'id': 1012, - 'type': self.CHART_TYPE_EXHAUST, + "id": 1012, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 1248, - 'type': self.CHART_TYPE_EXHAUST, + "id": 1248, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 926, - 'type': self.CHART_TYPE_EXHAUST, + "id": 926, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 12, - 'skill_level': 7, - 'tracks': [ + "season_id": 12, + "skill_level": 7, + "tracks": [ { - 'id': 1134, - 'type': self.CHART_TYPE_EXHAUST, + "id": 1134, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 919, - 'type': self.CHART_TYPE_EXHAUST, + "id": 919, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 1250, - 'type': self.CHART_TYPE_EXHAUST, + "id": 1250, + "type": self.CHART_TYPE_EXHAUST, }, ], }, # Skill LV.08 { - 'season_id': 0, - 'skill_level': 8, - 'tracks': [ + "season_id": 0, + "skill_level": 8, + "tracks": [ { - 'id': 690, - 'type': self.CHART_TYPE_EXHAUST, + "id": 690, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 380, - 'type': self.CHART_TYPE_EXHAUST, + "id": 380, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 492, - 'type': self.CHART_TYPE_INFINITE, + "id": 492, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'season_id': 1, - 'skill_level': 8, - 'tracks': [ + "season_id": 1, + "skill_level": 8, + "tracks": [ { - 'id': 603, - 'type': self.CHART_TYPE_EXHAUST, + "id": 603, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 278, - 'type': self.CHART_TYPE_INFINITE, + "id": 278, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 557, - 'type': self.CHART_TYPE_EXHAUST, + "id": 557, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 2, - 'skill_level': 8, - 'tracks': [ + "season_id": 2, + "skill_level": 8, + "tracks": [ { - 'id': 357, - 'type': self.CHART_TYPE_EXHAUST, + "id": 357, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 562, - 'type': self.CHART_TYPE_EXHAUST, + "id": 562, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 612, - 'type': self.CHART_TYPE_EXHAUST, + "id": 612, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 3, - 'skill_level': 8, - 'tracks': [ + "season_id": 3, + "skill_level": 8, + "tracks": [ { - 'id': 26, - 'type': self.CHART_TYPE_INFINITE, + "id": 26, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 22, - 'type': self.CHART_TYPE_INFINITE, + "id": 22, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 503, - 'type': self.CHART_TYPE_EXHAUST, + "id": 503, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 4, - 'skill_level': 8, - 'tracks': [ + "season_id": 4, + "skill_level": 8, + "tracks": [ { - 'id': 945, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 945, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 639, - 'type': self.CHART_TYPE_EXHAUST, + "id": 639, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 644, - 'type': self.CHART_TYPE_EXHAUST, + "id": 644, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 5, - 'skill_level': 8, - 'tracks': [ + "season_id": 5, + "skill_level": 8, + "tracks": [ { - 'id': 521, - 'type': self.CHART_TYPE_EXHAUST, + "id": 521, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 572, - 'type': self.CHART_TYPE_EXHAUST, + "id": 572, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 173, - 'type': self.CHART_TYPE_EXHAUST, + "id": 173, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 6, - 'skill_level': 8, - 'tracks': [ + "season_id": 6, + "skill_level": 8, + "tracks": [ { - 'id': 659, - 'type': self.CHART_TYPE_EXHAUST, + "id": 659, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 749, - 'type': self.CHART_TYPE_EXHAUST, + "id": 749, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 251, - 'type': self.CHART_TYPE_EXHAUST, + "id": 251, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 7, - 'skill_level': 8, - 'tracks': [ + "season_id": 7, + "skill_level": 8, + "tracks": [ { - 'id': 361, - 'type': self.CHART_TYPE_EXHAUST, + "id": 361, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 744, - 'type': self.CHART_TYPE_EXHAUST, + "id": 744, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 831, - 'type': self.CHART_TYPE_EXHAUST, + "id": 831, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 8, - 'skill_level': 8, - 'tracks': [ + "season_id": 8, + "skill_level": 8, + "tracks": [ { - 'id': 372, - 'type': self.CHART_TYPE_EXHAUST, + "id": 372, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 747, - 'type': self.CHART_TYPE_EXHAUST, + "id": 747, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 872, - 'type': self.CHART_TYPE_EXHAUST, + "id": 872, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 9, - 'skill_level': 8, - 'tracks': [ + "season_id": 9, + "skill_level": 8, + "tracks": [ { - 'id': 971, - 'type': self.CHART_TYPE_EXHAUST, + "id": 971, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 752, - 'type': self.CHART_TYPE_EXHAUST, + "id": 752, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 1062, - 'type': self.CHART_TYPE_EXHAUST, + "id": 1062, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 10, - 'skill_level': 8, - 'tracks': [ + "season_id": 10, + "skill_level": 8, + "tracks": [ { - 'id': 336, - 'type': self.CHART_TYPE_EXHAUST, + "id": 336, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 1199, - 'type': self.CHART_TYPE_EXHAUST, + "id": 1199, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 1197, - 'type': self.CHART_TYPE_EXHAUST, + "id": 1197, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 11, - 'skill_level': 8, - 'tracks': [ + "season_id": 11, + "skill_level": 8, + "tracks": [ { - 'id': 955, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 955, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 1037, - 'type': self.CHART_TYPE_EXHAUST, + "id": 1037, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 812, - 'type': self.CHART_TYPE_EXHAUST, + "id": 812, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 12, - 'skill_level': 8, - 'tracks': [ + "season_id": 12, + "skill_level": 8, + "tracks": [ { - 'id': 596, - 'type': self.CHART_TYPE_EXHAUST, + "id": 596, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 902, - 'type': self.CHART_TYPE_EXHAUST, + "id": 902, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 844, - 'type': self.CHART_TYPE_EXHAUST, + "id": 844, + "type": self.CHART_TYPE_EXHAUST, }, ], }, # Skill LV.09 # Skill course for Season ID 0 was removed due to removed songs. { - 'season_id': 1, - 'skill_level': 9, - 'tracks': [ + "season_id": 1, + "skill_level": 9, + "tracks": [ { - 'id': 295, - 'type': self.CHART_TYPE_EXHAUST, + "id": 295, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 742, - 'type': self.CHART_TYPE_EXHAUST, + "id": 742, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 302, - 'type': self.CHART_TYPE_EXHAUST, + "id": 302, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 2, - 'skill_level': 9, - 'tracks': [ + "season_id": 2, + "skill_level": 9, + "tracks": [ { - 'id': 322, - 'type': self.CHART_TYPE_EXHAUST, + "id": 322, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 759, - 'type': self.CHART_TYPE_EXHAUST, + "id": 759, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 607, - 'type': self.CHART_TYPE_EXHAUST, + "id": 607, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 3, - 'skill_level': 9, - 'tracks': [ + "season_id": 3, + "skill_level": 9, + "tracks": [ { - 'id': 599, - 'type': self.CHART_TYPE_EXHAUST, + "id": 599, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 122, - 'type': self.CHART_TYPE_EXHAUST, + "id": 122, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 946, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 946, + "type": self.CHART_TYPE_MAXIMUM, }, ], }, { - 'season_id': 4, - 'skill_level': 9, - 'tracks': [ + "season_id": 4, + "skill_level": 9, + "tracks": [ { - 'id': 394, - 'type': self.CHART_TYPE_EXHAUST, + "id": 394, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 228, - 'type': self.CHART_TYPE_EXHAUST, + "id": 228, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 124, - 'type': self.CHART_TYPE_EXHAUST, + "id": 124, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 5, - 'skill_level': 9, - 'tracks': [ + "season_id": 5, + "skill_level": 9, + "tracks": [ { - 'id': 456, - 'type': self.CHART_TYPE_EXHAUST, + "id": 456, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 852, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 852, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 252, - 'type': self.CHART_TYPE_EXHAUST, + "id": 252, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 6, - 'skill_level': 9, - 'tracks': [ + "season_id": 6, + "skill_level": 9, + "tracks": [ { - 'id': 918, - 'type': self.CHART_TYPE_EXHAUST, + "id": 918, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 63, - 'type': self.CHART_TYPE_EXHAUST, + "id": 63, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 47, - 'type': self.CHART_TYPE_EXHAUST, + "id": 47, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 7, - 'skill_level': 9, - 'tracks': [ + "season_id": 7, + "skill_level": 9, + "tracks": [ { - 'id': 917, - 'type': self.CHART_TYPE_EXHAUST, + "id": 917, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 959, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 959, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 912, - 'type': self.CHART_TYPE_EXHAUST, + "id": 912, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 8, - 'skill_level': 9, - 'tracks': [ + "season_id": 8, + "skill_level": 9, + "tracks": [ { - 'id': 576, - 'type': self.CHART_TYPE_EXHAUST, + "id": 576, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 943, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 943, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 359, - 'type': self.CHART_TYPE_EXHAUST, + "id": 359, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 9, - 'skill_level': 9, - 'tracks': [ + "season_id": 9, + "skill_level": 9, + "tracks": [ { - 'id': 497, - 'type': self.CHART_TYPE_EXHAUST, + "id": 497, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 948, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 948, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 954, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 954, + "type": self.CHART_TYPE_MAXIMUM, }, ], }, { - 'season_id': 10, - 'skill_level': 9, - 'tracks': [ + "season_id": 10, + "skill_level": 9, + "tracks": [ { - 'id': 841, - 'type': self.CHART_TYPE_EXHAUST, + "id": 841, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 1087, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1087, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 1112, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1112, + "type": self.CHART_TYPE_MAXIMUM, }, ], }, { - 'season_id': 11, - 'skill_level': 9, - 'tracks': [ + "season_id": 11, + "skill_level": 9, + "tracks": [ { - 'id': 761, - 'type': self.CHART_TYPE_EXHAUST, + "id": 761, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 765, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 765, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 1006, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1006, + "type": self.CHART_TYPE_MAXIMUM, }, ], }, { - 'season_id': 12, - 'skill_level': 9, - 'tracks': [ + "season_id": 12, + "skill_level": 9, + "tracks": [ { - 'id': 737, - 'type': self.CHART_TYPE_EXHAUST, + "id": 737, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 887, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 887, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 933, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 933, + "type": self.CHART_TYPE_MAXIMUM, }, ], }, # Skill LV.10 { - 'season_id': 0, - 'skill_level': 10, - 'tracks': [ + "season_id": 0, + "skill_level": 10, + "tracks": [ { - 'id': 833, - 'type': self.CHART_TYPE_EXHAUST, + "id": 833, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 858, - 'type': self.CHART_TYPE_EXHAUST, + "id": 858, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 229, - 'type': self.CHART_TYPE_EXHAUST, + "id": 229, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 1, - 'skill_level': 10, - 'tracks': [ + "season_id": 1, + "skill_level": 10, + "tracks": [ { - 'id': 333, - 'type': self.CHART_TYPE_EXHAUST, + "id": 333, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 871, - 'type': self.CHART_TYPE_EXHAUST, + "id": 871, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 259, - 'type': self.CHART_TYPE_EXHAUST, + "id": 259, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 2, - 'skill_level': 10, - 'tracks': [ + "season_id": 2, + "skill_level": 10, + "tracks": [ { - 'id': 779, - 'type': self.CHART_TYPE_EXHAUST, + "id": 779, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 817, - 'type': self.CHART_TYPE_EXHAUST, + "id": 817, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 362, - 'type': self.CHART_TYPE_EXHAUST, + "id": 362, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 3, - 'skill_level': 10, - 'tracks': [ + "season_id": 3, + "skill_level": 10, + "tracks": [ { - 'id': 961, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 961, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 967, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 967, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 993, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 993, + "type": self.CHART_TYPE_MAXIMUM, }, ], }, { - 'season_id': 4, - 'skill_level': 10, - 'tracks': [ + "season_id": 4, + "skill_level": 10, + "tracks": [ { - 'id': 625, - 'type': self.CHART_TYPE_INFINITE, + "id": 625, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 214, - 'type': self.CHART_TYPE_EXHAUST, + "id": 214, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 365, - 'type': self.CHART_TYPE_EXHAUST, + "id": 365, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 5, - 'skill_level': 10, - 'tracks': [ + "season_id": 5, + "skill_level": 10, + "tracks": [ { - 'id': 966, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 966, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 876, - 'type': self.CHART_TYPE_EXHAUST, + "id": 876, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 506, - 'type': self.CHART_TYPE_EXHAUST, + "id": 506, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 6, - 'skill_level': 10, - 'tracks': [ + "season_id": 6, + "skill_level": 10, + "tracks": [ { - 'id': 641, - 'type': self.CHART_TYPE_EXHAUST, + "id": 641, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 463, - 'type': self.CHART_TYPE_EXHAUST, + "id": 463, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 712, - 'type': self.CHART_TYPE_EXHAUST, + "id": 712, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 7, - 'skill_level': 10, - 'tracks': [ + "season_id": 7, + "skill_level": 10, + "tracks": [ { - 'id': 390, - 'type': self.CHART_TYPE_EXHAUST, + "id": 390, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 655, - 'type': self.CHART_TYPE_EXHAUST, + "id": 655, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 707, - 'type': self.CHART_TYPE_EXHAUST, + "id": 707, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 8, - 'skill_level': 10, - 'tracks': [ + "season_id": 8, + "skill_level": 10, + "tracks": [ { - 'id': 922, - 'type': self.CHART_TYPE_EXHAUST, + "id": 922, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 166, - 'type': self.CHART_TYPE_EXHAUST, + "id": 166, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 670, - 'type': self.CHART_TYPE_EXHAUST, + "id": 670, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 9, - 'skill_level': 10, - 'tracks': [ + "season_id": 9, + "skill_level": 10, + "tracks": [ { - 'id': 1126, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1126, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 1034, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1034, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 834, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 834, + "type": self.CHART_TYPE_MAXIMUM, }, ], }, { - 'season_id': 10, - 'skill_level': 10, - 'tracks': [ + "season_id": 10, + "skill_level": 10, + "tracks": [ { - 'id': 1217, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1217, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 1041, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1041, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 1078, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1078, + "type": self.CHART_TYPE_MAXIMUM, }, ], }, { - 'season_id': 11, - 'skill_level': 10, - 'tracks': [ + "season_id": 11, + "skill_level": 10, + "tracks": [ { - 'id': 1237, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1237, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 1157, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1157, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 907, - 'type': self.CHART_TYPE_EXHAUST, + "id": 907, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 12, - 'skill_level': 10, - 'tracks': [ + "season_id": 12, + "skill_level": 10, + "tracks": [ { - 'id': 1228, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1228, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 881, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 881, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 1135, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1135, + "type": self.CHART_TYPE_MAXIMUM, }, ], }, # Skill LV.11 { - 'season_id': 3, - 'skill_level': 11, - 'tracks': [ + "season_id": 3, + "skill_level": 11, + "tracks": [ { - 'id': 941, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 941, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 718, - 'type': self.CHART_TYPE_EXHAUST, + "id": 718, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 816, - 'type': self.CHART_TYPE_EXHAUST, + "id": 816, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 4, - 'skill_level': 11, - 'tracks': [ + "season_id": 4, + "skill_level": 11, + "tracks": [ { - 'id': 30, - 'type': self.CHART_TYPE_INFINITE, + "id": 30, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 2, - 'type': self.CHART_TYPE_INFINITE, + "id": 2, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 540, - 'type': self.CHART_TYPE_INFINITE, + "id": 540, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'season_id': 5, - 'skill_level': 11, - 'tracks': [ + "season_id": 5, + "skill_level": 11, + "tracks": [ { - 'id': 931, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 931, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 818, - 'type': self.CHART_TYPE_EXHAUST, + "id": 818, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 810, - 'type': self.CHART_TYPE_EXHAUST, + "id": 810, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 6, - 'skill_level': 11, - 'tracks': [ + "season_id": 6, + "skill_level": 11, + "tracks": [ { - 'id': 789, - 'type': self.CHART_TYPE_EXHAUST, + "id": 789, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 634, - 'type': self.CHART_TYPE_EXHAUST, + "id": 634, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 532, - 'type': self.CHART_TYPE_EXHAUST, + "id": 532, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 7, - 'skill_level': 11, - 'tracks': [ + "season_id": 7, + "skill_level": 11, + "tracks": [ { - 'id': 808, - 'type': self.CHART_TYPE_EXHAUST, + "id": 808, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 965, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 965, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 909, - 'type': self.CHART_TYPE_INFINITE, + "id": 909, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'season_id': 9, - 'skill_level': 11, - 'tracks': [ + "season_id": 9, + "skill_level": 11, + "tracks": [ { - 'id': 1013, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1013, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 1035, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1035, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 1107, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1107, + "type": self.CHART_TYPE_MAXIMUM, }, ], }, { - 'season_id': 10, - 'skill_level': 11, - 'tracks': [ + "season_id": 10, + "skill_level": 11, + "tracks": [ { - 'id': 173, - 'type': self.CHART_TYPE_INFINITE, + "id": 173, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 151, - 'type': self.CHART_TYPE_INFINITE, + "id": 151, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 362, - 'type': self.CHART_TYPE_INFINITE, + "id": 362, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'season_id': 11, - 'skill_level': 11, - 'tracks': [ + "season_id": 11, + "skill_level": 11, + "tracks": [ { - 'id': 1060, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1060, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 1062, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1062, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 1222, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1222, + "type": self.CHART_TYPE_MAXIMUM, }, ], }, # Skill LV.Inf { - 'season_id': 3, - 'skill_level': 12, - 'tracks': [ + "season_id": 3, + "skill_level": 12, + "tracks": [ { - 'id': 654, - 'type': self.CHART_TYPE_EXHAUST, + "id": 654, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 360, - 'type': self.CHART_TYPE_INFINITE, + "id": 360, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 1028, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1028, + "type": self.CHART_TYPE_MAXIMUM, }, ], }, { - 'season_id': 5, - 'skill_level': 12, - 'tracks': [ + "season_id": 5, + "skill_level": 12, + "tracks": [ { - 'id': 709, - 'type': self.CHART_TYPE_INFINITE, + "id": 709, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 374, - 'type': self.CHART_TYPE_INFINITE, + "id": 374, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 1036, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1036, + "type": self.CHART_TYPE_MAXIMUM, }, ], }, { - 'season_id': 6, - 'skill_level': 12, - 'tracks': [ + "season_id": 6, + "skill_level": 12, + "tracks": [ { - 'id': 551, - 'type': self.CHART_TYPE_EXHAUST, + "id": 551, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 1032, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1032, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 1099, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1099, + "type": self.CHART_TYPE_MAXIMUM, }, ], }, { - 'season_id': 7, - 'skill_level': 12, - 'tracks': [ + "season_id": 7, + "skill_level": 12, + "tracks": [ { - 'id': 927, - 'type': self.CHART_TYPE_INFINITE, + "id": 927, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 525, - 'type': self.CHART_TYPE_INFINITE, + "id": 525, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 1100, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1100, + "type": self.CHART_TYPE_MAXIMUM, }, ], }, { - 'season_id': 9, - 'skill_level': 12, - 'tracks': [ + "season_id": 9, + "skill_level": 12, + "tracks": [ { - 'id': 1102, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1102, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 1148, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1148, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 1185, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1185, + "type": self.CHART_TYPE_MAXIMUM, }, ], }, { - 'season_id': 24, - 'skill_level': 12, - 'tracks': [ + "season_id": 24, + "skill_level": 12, + "tracks": [ { - 'id': 661, - 'type': self.CHART_TYPE_INFINITE, + "id": 661, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 258, - 'type': self.CHART_TYPE_INFINITE, + "id": 258, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 791, - 'type': self.CHART_TYPE_INFINITE, + "id": 791, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'season_id': 10, - 'skill_level': 12, - 'tracks': [ + "season_id": 10, + "skill_level": 12, + "tracks": [ { - 'id': 679, - 'type': self.CHART_TYPE_EXHAUST, + "id": 679, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 1178, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1178, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 1270, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1270, + "type": self.CHART_TYPE_MAXIMUM, }, ], }, # 6th KAC { - 'season_id': 13, - 'course_id': 1, - 'course_name': 'The 6th KAC挑戦コース', - 'skill_name_id': self.GAME_SKILL_NAME_ID_KAC_6TH_BODY, - 'course_type': 2, - 'tracks': [ + "season_id": 13, + "course_id": 1, + "course_name": "The 6th KAC挑戦コース", + "skill_name_id": self.GAME_SKILL_NAME_ID_KAC_6TH_BODY, + "course_type": 2, + "tracks": [ { - 'id': 806, - 'type': self.CHART_TYPE_EXHAUST, + "id": 806, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 971, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 971, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 913, - 'type': self.CHART_TYPE_INFINITE, + "id": 913, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'season_id': 14, - 'course_id': 1, - 'course_name': 'The 6th KAC挑戦コース', - 'skill_name_id': self.GAME_SKILL_NAME_ID_KAC_6TH_TECHNOLOGY, - 'course_type': 2, - 'tracks': [ + "season_id": 14, + "course_id": 1, + "course_name": "The 6th KAC挑戦コース", + "skill_name_id": self.GAME_SKILL_NAME_ID_KAC_6TH_TECHNOLOGY, + "course_type": 2, + "tracks": [ { - 'id': 758, - 'type': self.CHART_TYPE_EXHAUST, + "id": 758, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 965, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 965, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 914, - 'type': self.CHART_TYPE_INFINITE, + "id": 914, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'season_id': 15, - 'course_id': 1, - 'course_name': 'The 6th KAC挑戦コース', - 'skill_name_id': self.GAME_SKILL_NAME_ID_KAC_6TH_HEART, - 'course_type': 2, - 'tracks': [ + "season_id": 15, + "course_id": 1, + "course_name": "The 6th KAC挑戦コース", + "skill_name_id": self.GAME_SKILL_NAME_ID_KAC_6TH_HEART, + "course_type": 2, + "tracks": [ { - 'id': 814, - 'type': self.CHART_TYPE_EXHAUST, + "id": 814, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 964, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 964, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 915, - 'type': self.CHART_TYPE_INFINITE, + "id": 915, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'season_id': 13, - 'course_id': 2, - 'course_name': 'The 6th KAC挑戦コース', - 'skill_name_id': self.GAME_SKILL_NAME_ID_KAC_6TH_BODY, - 'course_type': 2, - 'tracks': [ + "season_id": 13, + "course_id": 2, + "course_name": "The 6th KAC挑戦コース", + "skill_name_id": self.GAME_SKILL_NAME_ID_KAC_6TH_BODY, + "course_type": 2, + "tracks": [ { - 'id': 806, - 'type': self.CHART_TYPE_ADVANCED, + "id": 806, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 971, - 'type': self.CHART_TYPE_ADVANCED, + "id": 971, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 913, - 'type': self.CHART_TYPE_ADVANCED, + "id": 913, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 14, - 'course_id': 2, - 'course_name': 'The 6th KAC挑戦コース', - 'skill_name_id': self.GAME_SKILL_NAME_ID_KAC_6TH_TECHNOLOGY, - 'course_type': 2, - 'tracks': [ + "season_id": 14, + "course_id": 2, + "course_name": "The 6th KAC挑戦コース", + "skill_name_id": self.GAME_SKILL_NAME_ID_KAC_6TH_TECHNOLOGY, + "course_type": 2, + "tracks": [ { - 'id': 758, - 'type': self.CHART_TYPE_ADVANCED, + "id": 758, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 965, - 'type': self.CHART_TYPE_ADVANCED, + "id": 965, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 914, - 'type': self.CHART_TYPE_ADVANCED, + "id": 914, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 15, - 'course_id': 2, - 'course_name': 'The 6th KAC挑戦コース', - 'skill_name_id': self.GAME_SKILL_NAME_ID_KAC_6TH_HEART, - 'course_type': 2, - 'tracks': [ + "season_id": 15, + "course_id": 2, + "course_name": "The 6th KAC挑戦コース", + "skill_name_id": self.GAME_SKILL_NAME_ID_KAC_6TH_HEART, + "course_type": 2, + "tracks": [ { - 'id': 814, - 'type': self.CHART_TYPE_ADVANCED, + "id": 814, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 964, - 'type': self.CHART_TYPE_ADVANCED, + "id": 964, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 915, - 'type': self.CHART_TYPE_ADVANCED, + "id": 915, + "type": self.CHART_TYPE_ADVANCED, }, ], }, # Tenkaichi courses. { - 'season_id': 16, - 'course_id': 1, - 'course_name': '天下一', - 'skill_name_id': self.GAME_SKILL_NAME_ID_TENKAICHI, - 'course_type': 3, - 'tracks': [ + "season_id": 16, + "course_id": 1, + "course_name": "天下一", + "skill_name_id": self.GAME_SKILL_NAME_ID_TENKAICHI, + "course_type": 3, + "tracks": [ { - 'id': 625, - 'type': self.CHART_TYPE_NOVICE, + "id": 625, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 697, - 'type': self.CHART_TYPE_NOVICE, + "id": 697, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 708, - 'type': self.CHART_TYPE_NOVICE, + "id": 708, + "type": self.CHART_TYPE_NOVICE, }, ], }, { - 'season_id': 17, - 'course_id': 1, - 'course_name': '天下一', - 'skill_name_id': self.GAME_SKILL_NAME_ID_TENKAICHI, - 'course_type': 3, - 'tracks': [ + "season_id": 17, + "course_id": 1, + "course_name": "天下一", + "skill_name_id": self.GAME_SKILL_NAME_ID_TENKAICHI, + "course_type": 3, + "tracks": [ { - 'id': 625, - 'type': self.CHART_TYPE_ADVANCED, + "id": 625, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 697, - 'type': self.CHART_TYPE_ADVANCED, + "id": 697, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 708, - 'type': self.CHART_TYPE_ADVANCED, + "id": 708, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 18, - 'course_id': 1, - 'course_name': '天下一', - 'skill_name_id': self.GAME_SKILL_NAME_ID_TENKAICHI, - 'course_type': 3, - 'tracks': [ + "season_id": 18, + "course_id": 1, + "course_name": "天下一", + "skill_name_id": self.GAME_SKILL_NAME_ID_TENKAICHI, + "course_type": 3, + "tracks": [ { - 'id': 625, - 'type': self.CHART_TYPE_INFINITE, + "id": 625, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 697, - 'type': self.CHART_TYPE_EXHAUST, + "id": 697, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 708, - 'type': self.CHART_TYPE_EXHAUST, + "id": 708, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'season_id': 16, - 'course_id': 2, - 'course_name': '天下一', - 'skill_name_id': self.GAME_SKILL_NAME_ID_MUSIC_FESTIVAL, - 'course_type': 3, - 'tracks': [ + "season_id": 16, + "course_id": 2, + "course_name": "天下一", + "skill_name_id": self.GAME_SKILL_NAME_ID_MUSIC_FESTIVAL, + "course_type": 3, + "tracks": [ { - 'id': 362, - 'type': self.CHART_TYPE_NOVICE, + "id": 362, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 360, - 'type': self.CHART_TYPE_NOVICE, + "id": 360, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 927, - 'type': self.CHART_TYPE_NOVICE, + "id": 927, + "type": self.CHART_TYPE_NOVICE, }, ], }, { - 'season_id': 17, - 'course_id': 2, - 'course_name': '天下一', - 'skill_name_id': self.GAME_SKILL_NAME_ID_MUSIC_FESTIVAL, - 'course_type': 3, - 'tracks': [ + "season_id": 17, + "course_id": 2, + "course_name": "天下一", + "skill_name_id": self.GAME_SKILL_NAME_ID_MUSIC_FESTIVAL, + "course_type": 3, + "tracks": [ { - 'id': 362, - 'type': self.CHART_TYPE_ADVANCED, + "id": 362, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 360, - 'type': self.CHART_TYPE_ADVANCED, + "id": 360, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 927, - 'type': self.CHART_TYPE_ADVANCED, + "id": 927, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 18, - 'course_id': 2, - 'course_name': '天下一', - 'skill_name_id': self.GAME_SKILL_NAME_ID_MUSIC_FESTIVAL, - 'course_type': 3, - 'tracks': [ + "season_id": 18, + "course_id": 2, + "course_name": "天下一", + "skill_name_id": self.GAME_SKILL_NAME_ID_MUSIC_FESTIVAL, + "course_type": 3, + "tracks": [ { - 'id': 362, - 'type': self.CHART_TYPE_EXHAUST, + "id": 362, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 360, - 'type': self.CHART_TYPE_EXHAUST, + "id": 360, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 927, - 'type': self.CHART_TYPE_EXHAUST, + "id": 927, + "type": self.CHART_TYPE_EXHAUST, }, ], }, # BMK2017 courses { - 'season_id': 19, - 'course_id': 1, - 'course_name': 'BEMANI MASTER KOREA', - 'skill_name_id': self.GAME_SKILL_NAME_ID_BMK2017, - 'tracks': [ + "season_id": 19, + "course_id": 1, + "course_name": "BEMANI MASTER KOREA", + "skill_name_id": self.GAME_SKILL_NAME_ID_BMK2017, + "tracks": [ { - 'id': 954, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 954, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 960, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 960, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 961, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 961, + "type": self.CHART_TYPE_MAXIMUM, }, ], }, # 7th KAC { - 'season_id': 20, - 'course_id': 1, - 'course_name': 'The 7th KACチャレンジコース', - 'skill_name_id': self.GAME_SKILL_NAME_ID_KAC_7TH_TIGER, - 'course_type': 4, - 'tracks': [ + "season_id": 20, + "course_id": 1, + "course_name": "The 7th KACチャレンジコース", + "skill_name_id": self.GAME_SKILL_NAME_ID_KAC_7TH_TIGER, + "course_type": 4, + "tracks": [ { - 'id': 1149, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1149, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 367, - 'type': self.CHART_TYPE_EXHAUST, + "id": 367, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 1102, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1102, + "type": self.CHART_TYPE_MAXIMUM, }, ], }, { - 'season_id': 21, - 'course_id': 1, - 'course_name': 'The 7th KACチャレンジコース', - 'skill_name_id': self.GAME_SKILL_NAME_ID_KAC_7TH_WOLF, - 'course_type': 4, - 'tracks': [ + "season_id": 21, + "course_id": 1, + "course_name": "The 7th KACチャレンジコース", + "skill_name_id": self.GAME_SKILL_NAME_ID_KAC_7TH_WOLF, + "course_type": 4, + "tracks": [ { - 'id': 1042, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1042, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 126, - 'type': self.CHART_TYPE_INFINITE, + "id": 126, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 1101, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1101, + "type": self.CHART_TYPE_MAXIMUM, }, ], }, { - 'season_id': 20, - 'course_id': 2, - 'course_name': 'The 7th KACチャレンジコース', - 'skill_name_id': self.GAME_SKILL_NAME_ID_KAC_7TH_TIGER, - 'course_type': 4, - 'tracks': [ + "season_id": 20, + "course_id": 2, + "course_name": "The 7th KACチャレンジコース", + "skill_name_id": self.GAME_SKILL_NAME_ID_KAC_7TH_TIGER, + "course_type": 4, + "tracks": [ { - 'id': 1149, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1149, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 367, - 'type': self.CHART_TYPE_ADVANCED, + "id": 367, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 1102, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1102, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'season_id': 21, - 'course_id': 2, - 'course_name': 'The 7th KACチャレンジコース', - 'skill_name_id': self.GAME_SKILL_NAME_ID_KAC_7TH_WOLF, - 'course_type': 4, - 'tracks': [ + "season_id": 21, + "course_id": 2, + "course_name": "The 7th KACチャレンジコース", + "skill_name_id": self.GAME_SKILL_NAME_ID_KAC_7TH_WOLF, + "course_type": 4, + "tracks": [ { - 'id': 1042, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1042, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 126, - 'type': self.CHART_TYPE_ADVANCED, + "id": 126, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 1101, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1101, + "type": self.CHART_TYPE_ADVANCED, }, ], }, # 8th KAC { - 'season_id': 22, - 'course_id': 1, - 'course_name': 'The 8th KACチャレンジコース', - 'skill_name_id': self.GAME_SKILL_NAME_ID_KAC_8TH, - 'course_type': 5, - 'tracks': [ + "season_id": 22, + "course_id": 1, + "course_name": "The 8th KACチャレンジコース", + "skill_name_id": self.GAME_SKILL_NAME_ID_KAC_8TH, + "course_type": 5, + "tracks": [ { - 'id': 1334, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1334, + "type": self.CHART_TYPE_MAXIMUM, }, { - 'id': 610, - 'type': self.CHART_TYPE_EXHAUST, + "id": 610, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 1033, - 'type': self.CHART_TYPE_MAXIMUM, + "id": 1033, + "type": self.CHART_TYPE_MAXIMUM, }, ], }, { - 'season_id': 23, - 'course_id': 1, - 'course_name': 'The 8th KACエンジョイコース', - 'skill_name_id': self.GAME_SKILL_NAME_ID_KAC_8TH, - 'course_type': 5, - 'tracks': [ + "season_id": 23, + "course_id": 1, + "course_name": "The 8th KACエンジョイコース", + "skill_name_id": self.GAME_SKILL_NAME_ID_KAC_8TH, + "course_type": 5, + "tracks": [ { - 'id': 1334, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1334, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 610, - 'type': self.CHART_TYPE_ADVANCED, + "id": 610, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 1033, - 'type': self.CHART_TYPE_ADVANCED, + "id": 1033, + "type": self.CHART_TYPE_ADVANCED, }, ], }, ] def handle_game_sv4_common_request(self, request: Node) -> Node: - game = Node.void('game') + game = Node.void("game") - limited = Node.void('music_limited') + limited = Node.void("music_limited") game.add_child(limited) # Song unlock config game_config = self.get_game_config() - if game_config.get_bool('force_unlock_songs'): + if game_config.get_bool("force_unlock_songs"): ids = set() songs = self.data.local.music.get_all_songs(self.game, self.version) for song in songs: - if song.data.get_int('limited') in (self.GAME_LIMITED_LOCKED, self.GAME_LIMITED_UNLOCKABLE): + if song.data.get_int("limited") in ( + self.GAME_LIMITED_LOCKED, + self.GAME_LIMITED_UNLOCKABLE, + ): ids.add((song.id, song.chart)) for (songid, chart) in ids: - info = Node.void('info') + info = Node.void("info") limited.add_child(info) - info.add_child(Node.s32('music_id', songid)) - info.add_child(Node.u8('music_type', chart)) - info.add_child(Node.u8('limited', self.GAME_LIMITED_UNLOCKED)) + info.add_child(Node.s32("music_id", songid)) + info.add_child(Node.u8("music_type", chart)) + info.add_child(Node.u8("limited", self.GAME_LIMITED_UNLOCKED)) # Catalog, maybe this is for the online store? - catalog = Node.void('catalog') + catalog = Node.void("catalog") game.add_child(catalog) for _item in []: # type: ignore - info = Node.void('info') + info = Node.void("info") catalog.add_child(info) - info.add_child(Node.u8('catalog_type', 0)) - info.add_child(Node.u32('catalog_id', 0)) - info.add_child(Node.u32('discount_rate', 0)) + info.add_child(Node.u8("catalog_type", 0)) + info.add_child(Node.u32("catalog_id", 0)) + info.add_child(Node.u32("discount_rate", 0)) # Event config - event = Node.void('event') + event = Node.void("event") game.add_child(event) def enable_event(eid: str) -> None: - evt = Node.void('info') + evt = Node.void("info") event.add_child(evt) - evt.add_child(Node.string('event_id', eid)) + evt.add_child(Node.string("event_id", eid)) - if not game_config.get_bool('disable_matching'): + if not game_config.get_bool("disable_matching"): # Matching enabled events enable_event("MATCHING_MODE") enable_event("MATCHING_MODE_FREE_IP") @@ -3302,37 +3307,39 @@ class SoundVoltexHeavenlyHaven( enable_event("DEMOGAME_PLAY") enable_event("TOTAL_MEMORIAL_ENABLE") enable_event("EVENT_IDS_SERIALCODE_TOHO_02") - if game_config.get_bool('50th_anniversary'): + if game_config.get_bool("50th_anniversary"): enable_event("KONAMI_50TH_LOGO") enable_event("KAC6TH_FINISH") enable_event("KAC7TH_FINISH") enable_event("KAC8TH_FINISH") # An old collaboration event we don't support. - reitaisai = Node.void('reitaisai2018') + reitaisai = Node.void("reitaisai2018") game.add_child(reitaisai) # Volte factory, an older event we don't support. - volte_factory = Node.void('volte_factory') + volte_factory = Node.void("volte_factory") game.add_child(volte_factory) - goods = Node.void('goods') + goods = Node.void("goods") volte_factory.add_child(goods) - stock = Node.void('stock') + stock = Node.void("stock") volte_factory.add_child(stock) # I think this is a list of purchaseable appeal cards. - appealcard = Node.void('appealcard') + appealcard = Node.void("appealcard") game.add_child(appealcard) # Event parameters (we don't support story mode). - extend = Node.void('extend') + extend = Node.void("extend") game.add_child(extend) # Available skill courses - skill_course = Node.void('skill_course') + skill_course = Node.void("skill_course") game.add_child(skill_course) - achievements = self.data.local.user.get_all_achievements(self.game, self.version, achievementtype="course") + achievements = self.data.local.user.get_all_achievements( + self.game, self.version, achievementtype="course" + ) courserates: Dict[Tuple[int, int], Dict[str, int]] = {} def getrates(season_id: int, course_id: int) -> Dict[str, int]: @@ -3340,9 +3347,9 @@ class SoundVoltexHeavenlyHaven( return courserates[(course_id, season_id)] else: return { - 'attempts': 0, - 'clears': 0, - 'total_score': 0, + "attempts": 0, + "clears": 0, + "total_score": 0, } for _, achievement in achievements: @@ -3350,10 +3357,10 @@ class SoundVoltexHeavenlyHaven( season_id = int(achievement.id / 100) rate = getrates(season_id, course_id) - rate['attempts'] += 1 - if achievement.data.get_int('clear_type') >= 2: - rate['clears'] += 1 - rate['total_score'] = achievement.data.get_int('score') + rate["attempts"] += 1 + if achievement.data.get_int("clear_type") >= 2: + rate["clears"] += 1 + rate["total_score"] = achievement.data.get_int("score") courserates[(course_id, season_id)] = rate seasons = self.__get_skill_analyzer_seasons() @@ -3361,133 +3368,177 @@ class SoundVoltexHeavenlyHaven( courses = self.__get_skill_analyzer_courses() skill_name_ids = self.__get_skill_analyzer_skill_name_ids() for course in courses: - info = Node.void('info') + info = Node.void("info") skill_course.add_child(info) - info.add_child(Node.s32('season_id', course['season_id'])) - info.add_child(Node.string('season_name', seasons[course['season_id']])) - info.add_child(Node.bool('season_new_flg', course['season_id'] in {10, 11, 12, 22, 23})) - info.add_child(Node.s16('course_id', course.get('course_id', course.get('skill_level', -1)))) - info.add_child(Node.string('course_name', course.get('course_name', skill_levels.get(course.get('skill_level', -1), '')))) + info.add_child(Node.s32("season_id", course["season_id"])) + info.add_child(Node.string("season_name", seasons[course["season_id"]])) + info.add_child( + Node.bool("season_new_flg", course["season_id"] in {10, 11, 12, 22, 23}) + ) + info.add_child( + Node.s16( + "course_id", course.get("course_id", course.get("skill_level", -1)) + ) + ) + info.add_child( + Node.string( + "course_name", + course.get( + "course_name", + skill_levels.get(course.get("skill_level", -1), ""), + ), + ) + ) # Course type 0 is skill level courses. The course type is the same as the skill level (01-12). # If skill level is specified as '0', then the course type shows up as 'OTHER' instead of Skill Lv.01-12. # Course type 2 = KAC 6th. # Course type 3 = TENKAICHI mode. # Course type 4 = KAC 7th. # Course type 5 = KAC 8th. - info.add_child(Node.s16('course_type', course.get('course_type', 0))) - info.add_child(Node.s16('skill_level', course.get('skill_level', 0))) - info.add_child(Node.s16('skill_name_id', course.get('skill_name_id', skill_name_ids.get(course.get('skill_level', -1), 0)))) - info.add_child(Node.bool('matching_assist', course.get('skill_level', -1) >= 1 and course.get('skill_level', -1) <= 7)) + info.add_child(Node.s16("course_type", course.get("course_type", 0))) + info.add_child(Node.s16("skill_level", course.get("skill_level", 0))) + info.add_child( + Node.s16( + "skill_name_id", + course.get( + "skill_name_id", + skill_name_ids.get(course.get("skill_level", -1), 0), + ), + ) + ) + info.add_child( + Node.bool( + "matching_assist", + course.get("skill_level", -1) >= 1 + and course.get("skill_level", -1) <= 7, + ) + ) # Calculate clear rate and average score - rate = getrates(course['season_id'], course.get('course_id', course.get('skill_level', -1))) - if rate['attempts'] > 0: - info.add_child(Node.s32('clear_rate', int(100.0 * (rate['clears'] / rate['attempts'])))) - info.add_child(Node.u32('avg_score', rate['total_score'] // rate['attempts'])) + rate = getrates( + course["season_id"], + course.get("course_id", course.get("skill_level", -1)), + ) + if rate["attempts"] > 0: + info.add_child( + Node.s32( + "clear_rate", int(100.0 * (rate["clears"] / rate["attempts"])) + ) + ) + info.add_child( + Node.u32("avg_score", rate["total_score"] // rate["attempts"]) + ) else: - info.add_child(Node.s32('clear_rate', 0)) - info.add_child(Node.u32('avg_score', 0)) + info.add_child(Node.s32("clear_rate", 0)) + info.add_child(Node.u32("avg_score", 0)) - for trackno, trackdata in enumerate(course['tracks']): - track = Node.void('track') + for trackno, trackdata in enumerate(course["tracks"]): + track = Node.void("track") info.add_child(track) - track.add_child(Node.s16('track_no', trackno)) - track.add_child(Node.s32('music_id', trackdata['id'])) - track.add_child(Node.s8('music_type', trackdata['type'])) + track.add_child(Node.s16("track_no", trackno)) + track.add_child(Node.s32("music_id", trackdata["id"])) + track.add_child(Node.s8("music_type", trackdata["type"])) # Museca link event that we don't support. - museca_link = Node.void('museca_link') + museca_link = Node.void("museca_link") game.add_child(museca_link) return game def handle_game_sv4_shop_request(self, request: Node) -> Node: - self.update_machine_name(request.child_value('shopname')) + self.update_machine_name(request.child_value("shopname")) # Respond with number of milliseconds until next request - game = Node.void('game') - game.add_child(Node.u32('nxt_time', 1000 * 5 * 60)) + game = Node.void("game") + game.add_child(Node.u32("nxt_time", 1000 * 5 * 60)) return game def handle_game_sv4_hiscore_request(self, request: Node) -> Node: # Grab location for local scores - locid = ID.parse_machine_id(request.child_value('locid')) + locid = ID.parse_machine_id(request.child_value("locid")) - game = Node.void('game') + game = Node.void("game") # Now, grab global and local scores as well as clear rates global_records = self.data.remote.music.get_all_records(self.game, self.version) users = { - uid: prof for (uid, prof) in self.data.local.user.get_all_profiles(self.game, self.version) + uid: prof + for (uid, prof) in self.data.local.user.get_all_profiles( + self.game, self.version + ) } - area_users = [ - uid for uid in users - if users[uid].get_int('loc', -1) == locid - ] - area_records = self.data.local.music.get_all_records(self.game, self.version, userlist=area_users) + area_users = [uid for uid in users if users[uid].get_int("loc", -1) == locid] + area_records = self.data.local.music.get_all_records( + self.game, self.version, userlist=area_users + ) clears = self.get_clear_rates() records: Dict[int, Dict[int, Dict[str, Tuple[UserID, Score]]]] = {} - missing_users = ( - [userid for (userid, _) in global_records if userid not in users] + - [userid for (userid, _) in area_records if userid not in users] - ) + missing_users = [ + userid for (userid, _) in global_records if userid not in users + ] + [userid for (userid, _) in area_records if userid not in users] for (userid, profile) in self.get_any_profiles(missing_users): users[userid] = profile for (userid, score) in global_records: if userid not in users: - raise Exception('Logic error, missing profile for user!') + raise Exception("Logic error, missing profile for user!") if score.id not in records: records[score.id] = {} if score.chart not in records[score.id]: records[score.id][score.chart] = {} - records[score.id][score.chart]['global'] = (userid, score) + records[score.id][score.chart]["global"] = (userid, score) for (userid, score) in area_records: if userid not in users: - raise Exception('Logic error, missing profile for user!') + raise Exception("Logic error, missing profile for user!") if score.id not in records: records[score.id] = {} if score.chart not in records[score.id]: records[score.id][score.chart] = {} - records[score.id][score.chart]['area'] = (userid, score) + records[score.id][score.chart]["area"] = (userid, score) # Output it to the game - highscores = Node.void('sc') + highscores = Node.void("sc") game.add_child(highscores) for musicid in records: for chart in records[musicid]: - (globaluserid, globalscore) = records[musicid][chart]['global'] + (globaluserid, globalscore) = records[musicid][chart]["global"] global_profile = users[globaluserid] - if clears[musicid][chart]['total'] > 0: - clear_rate = float(clears[musicid][chart]['clears']) / float(clears[musicid][chart]['total']) + if clears[musicid][chart]["total"] > 0: + clear_rate = float(clears[musicid][chart]["clears"]) / float( + clears[musicid][chart]["total"] + ) else: clear_rate = 0.0 - info = Node.void('d') + info = Node.void("d") highscores.add_child(info) - info.add_child(Node.u32('id', musicid)) - info.add_child(Node.u32('ty', chart)) - info.add_child(Node.string('a_sq', ID.format_extid(global_profile.extid))) - info.add_child(Node.string('a_nm', global_profile.get_str('name'))) - info.add_child(Node.u32('a_sc', globalscore.points)) - info.add_child(Node.s32('cr', int(clear_rate * 10000))) - info.add_child(Node.s32('avg_sc', clears[musicid][chart]['average'])) + info.add_child(Node.u32("id", musicid)) + info.add_child(Node.u32("ty", chart)) + info.add_child( + Node.string("a_sq", ID.format_extid(global_profile.extid)) + ) + info.add_child(Node.string("a_nm", global_profile.get_str("name"))) + info.add_child(Node.u32("a_sc", globalscore.points)) + info.add_child(Node.s32("cr", int(clear_rate * 10000))) + info.add_child(Node.s32("avg_sc", clears[musicid][chart]["average"])) - if 'area' in records[musicid][chart]: - (localuserid, localscore) = records[musicid][chart]['area'] + if "area" in records[musicid][chart]: + (localuserid, localscore) = records[musicid][chart]["area"] local_profile = users[localuserid] - info.add_child(Node.string('l_sq', ID.format_extid(local_profile.extid))) - info.add_child(Node.string('l_nm', local_profile.get_str('name'))) - info.add_child(Node.u32('l_sc', localscore.points)) + info.add_child( + Node.string("l_sq", ID.format_extid(local_profile.extid)) + ) + info.add_child(Node.string("l_nm", local_profile.get_str("name"))) + info.add_child(Node.u32("l_sc", localscore.points)) return game def handle_game_sv4_load_request(self, request: Node) -> Node: - refid = request.child_value('refid') + refid = request.child_value("refid") root = self.get_profile_by_refid(refid) if root is not None: return root @@ -3506,31 +3557,31 @@ class SoundVoltexHeavenlyHaven( profile = None if profile is not None: - root = Node.void('game') - root.add_child(Node.u8('result', 2)) - root.add_child(Node.string('name', profile.get_str('name'))) + root = Node.void("game") + root.add_child(Node.u8("result", 2)) + root.add_child(Node.string("name", profile.get_str("name"))) return root else: - root = Node.void('game') - root.add_child(Node.u8('result', 1)) + root = Node.void("game") + root.add_child(Node.u8("result", 1)) return root def handle_game_sv4_frozen_request(self, request: Node) -> Node: - game = Node.void('game') - game.add_child(Node.u8('result', 0)) + game = Node.void("game") + game.add_child(Node.u8("result", 0)) return game def handle_game_sv4_new_request(self, request: Node) -> Node: - refid = request.child_value('refid') - name = request.child_value('name') - loc = ID.parse_machine_id(request.child_value('locid')) + refid = request.child_value("refid") + name = request.child_value("name") + loc = ID.parse_machine_id(request.child_value("locid")) self.new_profile_by_refid(refid, name, loc) - root = Node.void('game') + root = Node.void("game") return root def handle_game_sv4_load_m_request(self, request: Node) -> Node: - refid = request.child_value('refid') + refid = request.child_value("refid") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -3543,29 +3594,29 @@ class SoundVoltexHeavenlyHaven( scores = [] # Output to the game - game = Node.void('game') - music = Node.void('music') + game = Node.void("game") + music = Node.void("music") game.add_child(music) for score in scores: - info = Node.void('info') + info = Node.void("info") music.add_child(info) - stats = score.data.get_dict('stats') + stats = score.data.get_dict("stats") info.add_child( Node.u32_array( - 'param', + "param", [ score.id, score.chart, score.points, - self.__db_to_game_clear_type(score.data.get_int('clear_type')), - self.__db_to_game_grade(score.data.get_int('grade')), + self.__db_to_game_clear_type(score.data.get_int("clear_type")), + self.__db_to_game_grade(score.data.get_int("grade")), 0, # 5: Any value 0, # 6: Any value - stats.get_int('btn_rate'), - stats.get_int('long_rate'), - stats.get_int('vol_rate'), + stats.get_int("btn_rate"), + stats.get_int("long_rate"), + stats.get_int("vol_rate"), 0, # 10: Any value 0, # 11: Another medal, perhaps old score medal? 0, # 12: Another grade, perhaps old score grade? @@ -3583,8 +3634,8 @@ class SoundVoltexHeavenlyHaven( return game def handle_game_sv4_load_r_request(self, request: Node) -> Node: - refid = request.child_value('refid') - game = Node.void('game') + refid = request.child_value("refid") + game = Node.void("game") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -3595,44 +3646,50 @@ class SoundVoltexHeavenlyHaven( links = self.data.local.user.get_links(self.game, self.version, userid) index = 0 for link in links: - if link.type != 'rival': + if link.type != "rival": continue other_profile = self.get_profile(link.other_userid) if other_profile is None: continue # Base information about rival - rival = Node.void('rival') + rival = Node.void("rival") game.add_child(rival) - rival.add_child(Node.s16('no', index)) - rival.add_child(Node.string('seq', ID.format_extid(other_profile.extid))) - rival.add_child(Node.string('name', other_profile.get_str('name'))) + rival.add_child(Node.s16("no", index)) + rival.add_child( + Node.string("seq", ID.format_extid(other_profile.extid)) + ) + rival.add_child(Node.string("name", other_profile.get_str("name"))) # Keep track of index index = index + 1 # Return scores for this user on random charts - scores = self.data.remote.music.get_scores(self.game, self.version, link.other_userid) + scores = self.data.remote.music.get_scores( + self.game, self.version, link.other_userid + ) for score in scores: - music = Node.void('music') + music = Node.void("music") rival.add_child(music) music.add_child( Node.u32_array( - 'param', + "param", [ score.id, score.chart, score.points, - self.__db_to_game_clear_type(score.data.get_int('clear_type')), - self.__db_to_game_grade(score.data.get_int('grade')), - ] + self.__db_to_game_clear_type( + score.data.get_int("clear_type") + ), + self.__db_to_game_grade(score.data.get_int("grade")), + ], ) ) return game def handle_game_sv4_save_request(self, request: Node) -> Node: - refid = request.child_value('refid') + refid = request.child_value("refid") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -3648,10 +3705,10 @@ class SoundVoltexHeavenlyHaven( if userid is not None and newprofile is not None: self.put_profile(userid, newprofile) - return Node.void('game') + return Node.void("game") def handle_game_sv4_save_m_request(self, request: Node) -> Node: - refid = request.child_value('refid') + refid = request.child_value("refid") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -3659,19 +3716,19 @@ class SoundVoltexHeavenlyHaven( userid = None # Doesn't matter if userid is None here, that's an anonymous score - musicid = request.child_value('music_id') - chart = request.child_value('music_type') - points = request.child_value('score') - combo = request.child_value('max_chain') - clear_type = self.__game_to_db_clear_type(request.child_value('clear_type')) - grade = self.__game_to_db_grade(request.child_value('score_grade')) + musicid = request.child_value("music_id") + chart = request.child_value("music_type") + points = request.child_value("score") + combo = request.child_value("max_chain") + clear_type = self.__game_to_db_clear_type(request.child_value("clear_type")) + grade = self.__game_to_db_grade(request.child_value("score_grade")) stats = { - 'btn_rate': request.child_value('btn_rate'), - 'long_rate': request.child_value('long_rate'), - 'vol_rate': request.child_value('vol_rate'), - 'critical': request.child_value('critical'), - 'near': request.child_value('near'), - 'error': request.child_value('error'), + "btn_rate": request.child_value("btn_rate"), + "long_rate": request.child_value("long_rate"), + "vol_rate": request.child_value("vol_rate"), + "critical": request.child_value("critical"), + "near": request.child_value("near"), + "error": request.child_value("error"), } # Save the score @@ -3687,41 +3744,41 @@ class SoundVoltexHeavenlyHaven( ) # Return a blank response - return Node.void('game') + return Node.void("game") def handle_game_sv4_play_e_request(self, request: Node) -> Node: - return Node.void('game') + return Node.void("game") def handle_game_sv4_save_e_request(self, request: Node) -> Node: # This has to do with Policy floor infection, but we don't # implement multi-game support so meh. - game = Node.void('game') + game = Node.void("game") - pbc_infection = Node.void('pbc_infection') + pbc_infection = Node.void("pbc_infection") game.add_child(pbc_infection) - for name in ['packet', 'block', 'coloris']: + for name in ["packet", "block", "coloris"]: child = Node.void(name) pbc_infection.add_child(child) - child.add_child(Node.s32('before', 0)) - child.add_child(Node.s32('after', 0)) + child.add_child(Node.s32("before", 0)) + child.add_child(Node.s32("after", 0)) - pb_infection = Node.void('pb_infection') + pb_infection = Node.void("pb_infection") game.add_child(pb_infection) - for name in ['packet', 'block']: + for name in ["packet", "block"]: child = Node.void(name) pb_infection.add_child(child) - child.add_child(Node.s32('before', 0)) - child.add_child(Node.s32('after', 0)) + child.add_child(Node.s32("before", 0)) + child.add_child(Node.s32("after", 0)) return game def handle_game_sv4_play_s_request(self, request: Node) -> Node: - root = Node.void('game') - root.add_child(Node.u32('play_id', 1)) + root = Node.void("game") + root.add_child(Node.u32("play_id", 1)) return root def handle_game_sv4_buy_request(self, request: Node) -> Node: - refid = request.child_value('refid') + refid = request.child_value("refid") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -3735,15 +3792,15 @@ class SoundVoltexHeavenlyHaven( if userid is not None and profile is not None: # Look up packets and blocks - packet = profile.get_int('packet') - block = profile.get_int('block') + packet = profile.get_int("packet") + block = profile.get_int("block") # Add on any additional we earned this round - packet = packet + (request.child_value('earned_gamecoin_packet') or 0) - block = block + (request.child_value('earned_gamecoin_block') or 0) + packet = packet + (request.child_value("earned_gamecoin_packet") or 0) + block = block + (request.child_value("earned_gamecoin_block") or 0) - currency_type = request.child_value('currency_type') - price = request.child_value('item/price') + currency_type = request.child_value("currency_type") + price = request.child_value("item/price") if isinstance(price, list): # Sometimes we end up buying more than one item at once price = sum(price) @@ -3770,14 +3827,14 @@ class SoundVoltexHeavenlyHaven( if result == 0: # Transaction is valid, update the profile with new packets and blocks - profile.replace_int('packet', packet) - profile.replace_int('block', block) + profile.replace_int("packet", packet) + profile.replace_int("block", block) self.put_profile(userid, profile) # If this was a song unlock, we should mark it as unlocked - item_type = request.child_value('item/item_type') - item_id = request.child_value('item/item_id') - param = request.child_value('item/param') + item_type = request.child_value("item/item_type") + item_id = request.child_value("item/item_id") + param = request.child_value("item/param") if not isinstance(item_type, list): # Sometimes we buy multiple things at once. Make it easier by always assuming this. @@ -3791,9 +3848,9 @@ class SoundVoltexHeavenlyHaven( self.version, userid, item_id[i], - f'item_{item_type[i]}', + f"item_{item_type[i]}", { - 'param': param[i], + "param": param[i], }, ) @@ -3803,14 +3860,14 @@ class SoundVoltexHeavenlyHaven( block = 0 result = 1 - game = Node.void('game') - game.add_child(Node.u32('gamecoin_packet', packet)) - game.add_child(Node.u32('gamecoin_block', block)) - game.add_child(Node.s8('result', result)) + game = Node.void("game") + game.add_child(Node.u32("gamecoin_packet", packet)) + game.add_child(Node.u32("gamecoin_block", block)) + game.add_child(Node.s8("result", result)) return game def handle_game_sv4_save_c_request(self, request: Node) -> Node: - refid = request.child_value('refid') + refid = request.child_value("refid") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -3818,55 +3875,57 @@ class SoundVoltexHeavenlyHaven( userid = None if userid is not None: - season_id = request.child_value('ssnid') - course_id = request.child_value('crsid') - clear_type = request.child_value('ct') - achievement_rate = request.child_value('ar') - grade = request.child_value('gr') - score = request.child_value('sc') + season_id = request.child_value("ssnid") + course_id = request.child_value("crsid") + clear_type = request.child_value("ct") + achievement_rate = request.child_value("ar") + grade = request.child_value("gr") + score = request.child_value("sc") # Do not update the course achievement when old score is greater. - old = self.data.local.user.get_achievement(self.game, self.version, userid, (season_id * 100) + course_id, 'course') - if old is not None and old.get_int('score') > score: - return Node.void('game') + old = self.data.local.user.get_achievement( + self.game, self.version, userid, (season_id * 100) + course_id, "course" + ) + if old is not None and old.get_int("score") > score: + return Node.void("game") self.data.local.user.put_achievement( self.game, self.version, userid, (season_id * 100) + course_id, - 'course', + "course", { - 'clear_type': clear_type, - 'achievement_rate': achievement_rate, - 'score': score, - 'grade': grade, + "clear_type": clear_type, + "achievement_rate": achievement_rate, + "score": score, + "grade": grade, }, ) # Return a blank response - return Node.void('game') + return Node.void("game") def format_profile(self, userid: UserID, profile: Profile) -> Node: - game = Node.void('game') + game = Node.void("game") # Generic profile stuff - game.add_child(Node.string('name', profile.get_str('name'))) - game.add_child(Node.string('code', ID.format_extid(profile.extid))) - game.add_child(Node.string('sdvx_id', ID.format_extid(profile.extid))) - game.add_child(Node.u16('appeal_id', profile.get_int('appealid'))) - game.add_child(Node.s16('skill_base_id', profile.get_int('skill_base_id'))) - game.add_child(Node.s16('skill_name_id', profile.get_int('skill_name_id'))) - game.add_child(Node.u32('gamecoin_packet', profile.get_int('packet'))) - game.add_child(Node.u32('gamecoin_block', profile.get_int('block'))) - game.add_child(Node.u32('blaster_energy', profile.get_int('blaster_energy'))) - game.add_child(Node.u32('blaster_count', profile.get_int('blaster_count'))) + game.add_child(Node.string("name", profile.get_str("name"))) + game.add_child(Node.string("code", ID.format_extid(profile.extid))) + game.add_child(Node.string("sdvx_id", ID.format_extid(profile.extid))) + game.add_child(Node.u16("appeal_id", profile.get_int("appealid"))) + game.add_child(Node.s16("skill_base_id", profile.get_int("skill_base_id"))) + game.add_child(Node.s16("skill_name_id", profile.get_int("skill_name_id"))) + game.add_child(Node.u32("gamecoin_packet", profile.get_int("packet"))) + game.add_child(Node.u32("gamecoin_block", profile.get_int("block"))) + game.add_child(Node.u32("blaster_energy", profile.get_int("blaster_energy"))) + game.add_child(Node.u32("blaster_count", profile.get_int("blaster_count"))) # Play statistics statistics = self.get_play_statistics(userid) - game.add_child(Node.u32('play_count', statistics.total_plays)) - game.add_child(Node.u32('today_count', statistics.today_plays)) - game.add_child(Node.u32('play_chain', statistics.consecutive_days)) + game.add_child(Node.u32("play_count", statistics.total_plays)) + game.add_child(Node.u32("today_count", statistics.today_plays)) + game.add_child(Node.u32("play_chain", statistics.consecutive_days)) # Also exists but we don't support: # - day_count: Number of days where this user had at least one play. @@ -3877,200 +3936,233 @@ class SoundVoltexHeavenlyHaven( # - max_week_chain: Maximum number of weeks in a row where the user had at least one play in that week. # Player options and last touched song. - lastdict = profile.get_dict('last') - game.add_child(Node.s32('last_music_id', lastdict.get_int('music_id', -1))) - game.add_child(Node.u8('last_music_type', lastdict.get_int('music_type'))) - game.add_child(Node.u8('sort_type', lastdict.get_int('sort_type'))) - game.add_child(Node.u8('narrow_down', lastdict.get_int('narrow_down'))) - game.add_child(Node.u8('headphone', lastdict.get_int('headphone'))) - game.add_child(Node.u8('gauge_option', lastdict.get_int('gauge_option'))) - game.add_child(Node.u8('ars_option', lastdict.get_int('ars_option'))) - game.add_child(Node.u8('notes_option', lastdict.get_int('notes_option'))) - game.add_child(Node.u8('early_late_disp', lastdict.get_int('early_late_disp'))) - game.add_child(Node.u8('eff_c_left', lastdict.get_int('eff_c_left'))) - game.add_child(Node.u8('eff_c_right', lastdict.get_int('eff_c_right', 1))) - game.add_child(Node.u32('lanespeed', lastdict.get_int('lanespeed'))) - game.add_child(Node.s32('hispeed', lastdict.get_int('hispeed'))) - game.add_child(Node.s32('draw_adjust', lastdict.get_int('draw_adjust'))) + lastdict = profile.get_dict("last") + game.add_child(Node.s32("last_music_id", lastdict.get_int("music_id", -1))) + game.add_child(Node.u8("last_music_type", lastdict.get_int("music_type"))) + game.add_child(Node.u8("sort_type", lastdict.get_int("sort_type"))) + game.add_child(Node.u8("narrow_down", lastdict.get_int("narrow_down"))) + game.add_child(Node.u8("headphone", lastdict.get_int("headphone"))) + game.add_child(Node.u8("gauge_option", lastdict.get_int("gauge_option"))) + game.add_child(Node.u8("ars_option", lastdict.get_int("ars_option"))) + game.add_child(Node.u8("notes_option", lastdict.get_int("notes_option"))) + game.add_child(Node.u8("early_late_disp", lastdict.get_int("early_late_disp"))) + game.add_child(Node.u8("eff_c_left", lastdict.get_int("eff_c_left"))) + game.add_child(Node.u8("eff_c_right", lastdict.get_int("eff_c_right", 1))) + game.add_child(Node.u32("lanespeed", lastdict.get_int("lanespeed"))) + game.add_child(Node.s32("hispeed", lastdict.get_int("hispeed"))) + game.add_child(Node.s32("draw_adjust", lastdict.get_int("draw_adjust"))) # Item unlocks - itemnode = Node.void('item') + itemnode = Node.void("item") game.add_child(itemnode) game_config = self.get_game_config() - achievements = self.data.local.user.get_achievements(self.game, self.version, userid) + achievements = self.data.local.user.get_achievements( + self.game, self.version, userid + ) for item in achievements: - if item.type[:5] != 'item_': + if item.type[:5] != "item_": continue itemtype = int(item.type[5:]) - if game_config.get_bool('force_unlock_songs') and itemtype == self.GAME_CATALOG_TYPE_SONG: + if ( + game_config.get_bool("force_unlock_songs") + and itemtype == self.GAME_CATALOG_TYPE_SONG + ): # Don't echo unlocked songs, we will add all of them later continue - if game_config.get_bool('force_unlock_cards') and itemtype == self.GAME_CATALOG_TYPE_APPEAL_CARD: + if ( + game_config.get_bool("force_unlock_cards") + and itemtype == self.GAME_CATALOG_TYPE_APPEAL_CARD + ): # Don't echo unlocked appeal cards, we will add all of them later continue - if game_config.get_bool('force_unlock_crew') and itemtype == self.GAME_CATALOG_TYPE_CREW: + if ( + game_config.get_bool("force_unlock_crew") + and itemtype == self.GAME_CATALOG_TYPE_CREW + ): # Don't echo unlocked crew, we will add all of them later continue - info = Node.void('info') + info = Node.void("info") itemnode.add_child(info) - info.add_child(Node.u8('type', itemtype)) - info.add_child(Node.u32('id', item.id)) - info.add_child(Node.u32('param', item.data.get_int('param'))) + info.add_child(Node.u8("type", itemtype)) + info.add_child(Node.u32("id", item.id)) + info.add_child(Node.u32("param", item.data.get_int("param"))) - if game_config.get_bool('force_unlock_songs'): + if game_config.get_bool("force_unlock_songs"): ids: Dict[int, int] = {} songs = self.data.local.music.get_all_songs(self.game, self.version) for song in songs: if song.id not in ids: ids[song.id] = 0 - if song.data.get_int('difficulty') > 0: + if song.data.get_int("difficulty") > 0: ids[song.id] = ids[song.id] | (1 << song.chart) for itemid in ids: if ids[itemid] == 0: continue - info = Node.void('info') + info = Node.void("info") itemnode.add_child(info) - info.add_child(Node.u8('type', self.GAME_CATALOG_TYPE_SONG)) - info.add_child(Node.u32('id', itemid)) - info.add_child(Node.u32('param', ids[itemid])) + info.add_child(Node.u8("type", self.GAME_CATALOG_TYPE_SONG)) + info.add_child(Node.u32("id", itemid)) + info.add_child(Node.u32("param", ids[itemid])) - if game_config.get_bool('force_unlock_cards'): + if game_config.get_bool("force_unlock_cards"): catalog = self.data.local.game.get_items(self.game, self.version) for unlock in catalog: - if unlock.type != 'appealcard': + if unlock.type != "appealcard": continue - info = Node.void('info') + info = Node.void("info") itemnode.add_child(info) - info.add_child(Node.u8('type', self.GAME_CATALOG_TYPE_APPEAL_CARD)) - info.add_child(Node.u32('id', unlock.id)) - info.add_child(Node.u32('param', 1)) + info.add_child(Node.u8("type", self.GAME_CATALOG_TYPE_APPEAL_CARD)) + info.add_child(Node.u32("id", unlock.id)) + info.add_child(Node.u32("param", 1)) - if game_config.get_bool('force_unlock_crew'): + if game_config.get_bool("force_unlock_crew"): for crewid in range(1, 999): - info = Node.void('info') + info = Node.void("info") itemnode.add_child(info) - info.add_child(Node.u8('type', self.GAME_CATALOG_TYPE_CREW)) - info.add_child(Node.u32('id', crewid)) - info.add_child(Node.u32('param', 1)) + info.add_child(Node.u8("type", self.GAME_CATALOG_TYPE_CREW)) + info.add_child(Node.u32("id", crewid)) + info.add_child(Node.u32("param", 1)) # Skill courses - skill = Node.void('skill') + skill = Node.void("skill") game.add_child(skill) skill_level = 0 for course in achievements: - if course.type != 'course': + if course.type != "course": continue course_id = course.id % 100 season_id = int(course.id / 100) - if course.data.get_int('clear_type') >= 2: + if course.data.get_int("clear_type") >= 2: # The user cleared this, lets take the highest level clear for this courselist = [ - c for c in - self.__get_skill_analyzer_courses() if - c.get('course_id', c.get('skill_level', -1)) == course_id and - c['season_id'] == season_id + c + for c in self.__get_skill_analyzer_courses() + if c.get("course_id", c.get("skill_level", -1)) == course_id + and c["season_id"] == season_id ] if len(courselist) > 0: - skill_level = max(skill_level, courselist[0]['skill_level']) + skill_level = max(skill_level, courselist[0]["skill_level"]) - course_node = Node.void('course') + course_node = Node.void("course") skill.add_child(course_node) - course_node.add_child(Node.s16('ssnid', season_id)) - course_node.add_child(Node.s16('crsid', course_id)) - course_node.add_child(Node.s32('sc', course.data.get_int('score'))) - course_node.add_child(Node.s16('ct', course.data.get_int('clear_type'))) - course_node.add_child(Node.s16('gr', course.data.get_int('grade'))) - course_node.add_child(Node.s16('ar', course.data.get_int('achievement_rate'))) - course_node.add_child(Node.s16('cnt', 1)) + course_node.add_child(Node.s16("ssnid", season_id)) + course_node.add_child(Node.s16("crsid", course_id)) + course_node.add_child(Node.s32("sc", course.data.get_int("score"))) + course_node.add_child(Node.s16("ct", course.data.get_int("clear_type"))) + course_node.add_child(Node.s16("gr", course.data.get_int("grade"))) + course_node.add_child( + Node.s16("ar", course.data.get_int("achievement_rate")) + ) + course_node.add_child(Node.s16("cnt", 1)) # Calculated skill level - game.add_child(Node.s16('skill_level', skill_level)) + game.add_child(Node.s16("skill_level", skill_level)) # Game parameters - paramnode = Node.void('param') + paramnode = Node.void("param") game.add_child(paramnode) for param in achievements: - if param.type[:6] != 'param_': + if param.type[:6] != "param_": continue paramtype = int(param.type[6:]) - info = Node.void('info') + info = Node.void("info") paramnode.add_child(info) - info.add_child(Node.s32('id', param.id)) - info.add_child(Node.s32('type', paramtype)) - info.add_child(Node.s32_array('param', param.data['param'])) # This looks to be variable, so no validation on length + info.add_child(Node.s32("id", param.id)) + info.add_child(Node.s32("type", paramtype)) + info.add_child( + Node.s32_array("param", param.data["param"]) + ) # This looks to be variable, so no validation on length # Infection nodes, we don't support these but it here for posterity. - pbc_infection = Node.void('pbc_infection') + pbc_infection = Node.void("pbc_infection") game.add_child(pbc_infection) - for name in ['packet', 'block', 'coloris']: + for name in ["packet", "block", "coloris"]: child = Node.void(name) pbc_infection.add_child(child) - child.add_child(Node.s32('before', 0)) - child.add_child(Node.s32('after', 0)) + child.add_child(Node.s32("before", 0)) + child.add_child(Node.s32("after", 0)) - pb_infection = Node.void('pb_infection') + pb_infection = Node.void("pb_infection") game.add_child(pb_infection) - for name in ['packet', 'block']: + for name in ["packet", "block"]: child = Node.void(name) pb_infection.add_child(child) - child.add_child(Node.s32('before', 0)) - child.add_child(Node.s32('after', 0)) + child.add_child(Node.s32("before", 0)) + child.add_child(Node.s32("after", 0)) return game - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() # Update blaster energy and in-game currencies - earned_gamecoin_packet = request.child_value('earned_gamecoin_packet') + earned_gamecoin_packet = request.child_value("earned_gamecoin_packet") if earned_gamecoin_packet is not None: - newprofile.replace_int('packet', newprofile.get_int('packet') + earned_gamecoin_packet) - earned_gamecoin_block = request.child_value('earned_gamecoin_block') + newprofile.replace_int( + "packet", newprofile.get_int("packet") + earned_gamecoin_packet + ) + earned_gamecoin_block = request.child_value("earned_gamecoin_block") if earned_gamecoin_block is not None: - newprofile.replace_int('block', newprofile.get_int('block') + earned_gamecoin_block) - earned_blaster_energy = request.child_value('earned_blaster_energy') + newprofile.replace_int( + "block", newprofile.get_int("block") + earned_gamecoin_block + ) + earned_blaster_energy = request.child_value("earned_blaster_energy") if earned_blaster_energy is not None: - newprofile.replace_int('blaster_energy', newprofile.get_int('blaster_energy') + earned_blaster_energy) + newprofile.replace_int( + "blaster_energy", + newprofile.get_int("blaster_energy") + earned_blaster_energy, + ) # Miscelaneous profile stuff - newprofile.replace_int('blaster_count', request.child_value('blaster_count')) - newprofile.replace_int('appealid', request.child_value('appeal_id')) - newprofile.replace_int('skill_level', request.child_value('skill_level')) - newprofile.replace_int('skill_base_id', request.child_value('skill_base_id')) - newprofile.replace_int('skill_name_id', request.child_value('skill_name_id')) + newprofile.replace_int("blaster_count", request.child_value("blaster_count")) + newprofile.replace_int("appealid", request.child_value("appeal_id")) + newprofile.replace_int("skill_level", request.child_value("skill_level")) + newprofile.replace_int("skill_base_id", request.child_value("skill_base_id")) + newprofile.replace_int("skill_name_id", request.child_value("skill_name_id")) # Update user's unlock status if we aren't force unlocked game_config = self.get_game_config() - if request.child('item') is not None: - for child in request.child('item').children: - if child.name != 'info': + if request.child("item") is not None: + for child in request.child("item").children: + if child.name != "info": continue - item_id = child.child_value('id') - item_type = child.child_value('type') - param = child.child_value('param') + item_id = child.child_value("id") + item_type = child.child_value("type") + param = child.child_value("param") - if game_config.get_bool('force_unlock_cards') and item_type == self.GAME_CATALOG_TYPE_APPEAL_CARD: + if ( + game_config.get_bool("force_unlock_cards") + and item_type == self.GAME_CATALOG_TYPE_APPEAL_CARD + ): # Don't save back appeal cards because they were force unlocked continue - if game_config.get_bool('force_unlock_songs') and item_type == self.GAME_CATALOG_TYPE_SONG: + if ( + game_config.get_bool("force_unlock_songs") + and item_type == self.GAME_CATALOG_TYPE_SONG + ): # Don't save back songs, because they were force unlocked continue - if game_config.get_bool('force_unlock_crew') and item_type == self.GAME_CATALOG_TYPE_CREW: + if ( + game_config.get_bool("force_unlock_crew") + and item_type == self.GAME_CATALOG_TYPE_CREW + ): # Don't save back crew, because they were force unlocked continue @@ -4079,51 +4171,51 @@ class SoundVoltexHeavenlyHaven( self.version, userid, item_id, - f'item_{item_type}', + f"item_{item_type}", { - 'param': param, + "param": param, }, ) # Update params - if request.child('param') is not None: - for child in request.child('param').children: - if child.name != 'info': + if request.child("param") is not None: + for child in request.child("param").children: + if child.name != "info": continue - param_id = child.child_value('id') - param_type = child.child_value('type') - param_param = child.child_value('param') + param_id = child.child_value("id") + param_type = child.child_value("type") + param_param = child.child_value("param") self.data.local.user.put_achievement( self.game, self.version, userid, param_id, - f'param_{param_type}', + f"param_{param_type}", { - 'param': param_param, + "param": param_param, }, ) # Grab last information and player options. - lastdict = newprofile.get_dict('last') - lastdict.replace_int('music_id', request.child_value('music_id')) - lastdict.replace_int('music_type', request.child_value('music_type')) - lastdict.replace_int('sort_type', request.child_value('sort_type')) - lastdict.replace_int('narrow_down', request.child_value('narrow_down')) - lastdict.replace_int('headphone', request.child_value('headphone')) - lastdict.replace_int('gauge_option', request.child_value('gauge_option')) - lastdict.replace_int('ars_option', request.child_value('ars_option')) - lastdict.replace_int('notes_option', request.child_value('notes_option')) - lastdict.replace_int('early_late_disp', request.child_value('early_late_disp')) - lastdict.replace_int('eff_c_left', request.child_value('eff_c_left')) - lastdict.replace_int('eff_c_right', request.child_value('eff_c_right')) - lastdict.replace_int('lanespeed', request.child_value('lanespeed')) - lastdict.replace_int('hispeed', request.child_value('hispeed')) - lastdict.replace_int('draw_adjust', request.child_value('draw_adjust')) + lastdict = newprofile.get_dict("last") + lastdict.replace_int("music_id", request.child_value("music_id")) + lastdict.replace_int("music_type", request.child_value("music_type")) + lastdict.replace_int("sort_type", request.child_value("sort_type")) + lastdict.replace_int("narrow_down", request.child_value("narrow_down")) + lastdict.replace_int("headphone", request.child_value("headphone")) + lastdict.replace_int("gauge_option", request.child_value("gauge_option")) + lastdict.replace_int("ars_option", request.child_value("ars_option")) + lastdict.replace_int("notes_option", request.child_value("notes_option")) + lastdict.replace_int("early_late_disp", request.child_value("early_late_disp")) + lastdict.replace_int("eff_c_left", request.child_value("eff_c_left")) + lastdict.replace_int("eff_c_right", request.child_value("eff_c_right")) + lastdict.replace_int("lanespeed", request.child_value("lanespeed")) + lastdict.replace_int("hispeed", request.child_value("hispeed")) + lastdict.replace_int("draw_adjust", request.child_value("draw_adjust")) # Save back last information gleaned from results - newprofile.replace_dict('last', lastdict) + newprofile.replace_dict("last", lastdict) # Keep track of play statistics self.update_play_statistics(userid) diff --git a/bemani/backend/sdvx/infiniteinfection.py b/bemani/backend/sdvx/infiniteinfection.py index ece96b0..12630bc 100644 --- a/bemani/backend/sdvx/infiniteinfection.py +++ b/bemani/backend/sdvx/infiniteinfection.py @@ -15,7 +15,7 @@ class SoundVoltexInfiniteInfection( SoundVoltexBase, ): - name: str = 'SOUND VOLTEX II -infinite infection-' + name: str = "SOUND VOLTEX II -infinite infection-" version: int = VersionConstants.SDVX_INFINITE_INFECTION GAME_LIMITED_LOCKED: Final[int] = 1 @@ -51,37 +51,37 @@ class SoundVoltexInfiniteInfection( Return all of our front-end modifiably settings. """ return { - 'bools': [ + "bools": [ { - 'name': 'Disable Online Matching', - 'tip': 'Disable online matching between games.', - 'category': 'game_config', - 'setting': 'disable_matching', + "name": "Disable Online Matching", + "tip": "Disable online matching between games.", + "category": "game_config", + "setting": "disable_matching", }, { - 'name': 'Force Song Unlock', - 'tip': 'Force unlock all songs.', - 'category': 'game_config', - 'setting': 'force_unlock_songs', + "name": "Force Song Unlock", + "tip": "Force unlock all songs.", + "category": "game_config", + "setting": "force_unlock_songs", }, { - 'name': 'Force Appeal Card Unlock', - 'tip': 'Force unlock all appeal cards.', - 'category': 'game_config', - 'setting': 'force_unlock_cards', + "name": "Force Appeal Card Unlock", + "tip": "Force unlock all appeal cards.", + "category": "game_config", + "setting": "force_unlock_cards", }, ], - 'ints': [ + "ints": [ { - 'name': 'BEMANI Stadium Event Phase', - 'tip': 'BEMANI Stadium event phase for all players.', - 'category': 'game_config', - 'setting': 'bemani_stadium', - 'values': { - 0: 'No Event', - 1: 'BEMANI Stadium', - 2: 'BEMANI iseki', - } + "name": "BEMANI Stadium Event Phase", + "tip": "BEMANI Stadium event phase for all players.", + "category": "game_config", + "setting": "bemani_stadium", + "values": { + 0: "No Event", + 1: "BEMANI Stadium", + 2: "BEMANI iseki", + }, }, ], } @@ -136,1760 +136,1763 @@ class SoundVoltexInfiniteInfection( def __get_skill_analyzer_seasons(self) -> Dict[int, str]: return { - 6: 'SKILL ANALYZER 第6回 (2013/12/06)', - 7: 'SKILL ANALYZER 第7回 (2014/01/10)', - 8: 'SKILL ANALYZER 第8回 (2014/02/06)', - 9: 'SKILL ANALYZER 第9回 (2014/03/06)', - 10: 'SKILL ANALYZER 第10回 (2014/04/04)', - 11: 'SKILL ANALYZER 第11回 (2014/05/01)', - 12: 'SKILL ANALYZER 第12回 (2014/06/05)', - 13: 'SKILL ANALYZER 第13回 (2014/07/04)', - 14: 'SKILL ANALYZER 第14回 (2014/08/01)', + 6: "SKILL ANALYZER 第6回 (2013/12/06)", + 7: "SKILL ANALYZER 第7回 (2014/01/10)", + 8: "SKILL ANALYZER 第8回 (2014/02/06)", + 9: "SKILL ANALYZER 第9回 (2014/03/06)", + 10: "SKILL ANALYZER 第10回 (2014/04/04)", + 11: "SKILL ANALYZER 第11回 (2014/05/01)", + 12: "SKILL ANALYZER 第12回 (2014/06/05)", + 13: "SKILL ANALYZER 第13回 (2014/07/04)", + 14: "SKILL ANALYZER 第14回 (2014/08/01)", } def __get_skill_analyzer_skill_levels(self) -> Dict[int, str]: return { - 0: 'Skill LEVEL 01 岳翔', - 1: 'Skill LEVEL 02 流星', - 2: 'Skill LEVEL 03 月衝', - 3: 'Skill LEVEL 04 瞬光', - 4: 'Skill LEVEL 05 天極', - 5: 'Skill LEVEL 06 烈風', - 6: 'Skill LEVEL 07 雷電', - 7: 'Skill LEVEL 08 麗華', - 8: 'Skill LEVEL 09 魔騎士', - 9: 'Skill LEVEL 10 剛力羅', - 10: 'Skill LEVEL 11 或帝滅斗', - 11: 'Skill LEVEL ∞(12) 暴龍天', + 0: "Skill LEVEL 01 岳翔", + 1: "Skill LEVEL 02 流星", + 2: "Skill LEVEL 03 月衝", + 3: "Skill LEVEL 04 瞬光", + 4: "Skill LEVEL 05 天極", + 5: "Skill LEVEL 06 烈風", + 6: "Skill LEVEL 07 雷電", + 7: "Skill LEVEL 08 麗華", + 8: "Skill LEVEL 09 魔騎士", + 9: "Skill LEVEL 10 剛力羅", + 10: "Skill LEVEL 11 或帝滅斗", + 11: "Skill LEVEL ∞(12) 暴龍天", } def __get_skill_analyzer_courses(self) -> List[Dict[str, Any]]: return [ { - 'level': 0, - 'season_id': 6, - 'tracks': [ + "level": 0, + "season_id": 6, + "tracks": [ { - 'id': 109, - 'type': self.CHART_TYPE_NOVICE, + "id": 109, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 24, - 'type': self.CHART_TYPE_ADVANCED, + "id": 24, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 245, - 'type': self.CHART_TYPE_ADVANCED, + "id": 245, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 6, - 'tracks': [ + "level": 1, + "season_id": 6, + "tracks": [ { - 'id': 22, - 'type': self.CHART_TYPE_ADVANCED, + "id": 22, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 313, - 'type': self.CHART_TYPE_ADVANCED, + "id": 313, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 7, - 'type': self.CHART_TYPE_EXHAUST, + "id": 7, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 2, - 'season_id': 6, - 'tracks': [ + "level": 2, + "season_id": 6, + "tracks": [ { - 'id': 4, - 'type': self.CHART_TYPE_EXHAUST, + "id": 4, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 39, - 'type': self.CHART_TYPE_ADVANCED, + "id": 39, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 322, - 'type': self.CHART_TYPE_ADVANCED, + "id": 322, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 6, - 'tracks': [ + "level": 3, + "season_id": 6, + "tracks": [ { - 'id': 134, - 'type': self.CHART_TYPE_ADVANCED, + "id": 134, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 87, - 'type': self.CHART_TYPE_EXHAUST, + "id": 87, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 314, - 'type': self.CHART_TYPE_EXHAUST, + "id": 314, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 6, - 'tracks': [ + "level": 4, + "season_id": 6, + "tracks": [ { - 'id': 126, - 'type': self.CHART_TYPE_ADVANCED, + "id": 126, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 59, - 'type': self.CHART_TYPE_EXHAUST, + "id": 59, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 23, - 'type': self.CHART_TYPE_INFINITE, + "id": 23, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'level': 5, - 'season_id': 6, - 'tracks': [ + "level": 5, + "season_id": 6, + "tracks": [ { - 'id': 86, - 'type': self.CHART_TYPE_EXHAUST, + "id": 86, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 128, - 'type': self.CHART_TYPE_EXHAUST, + "id": 128, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 2, - 'type': self.CHART_TYPE_EXHAUST, + "id": 2, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 6, - 'tracks': [ + "level": 6, + "season_id": 6, + "tracks": [ { - 'id': 256, - 'type': self.CHART_TYPE_EXHAUST, + "id": 256, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 255, - 'type': self.CHART_TYPE_EXHAUST, + "id": 255, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 246, - 'type': self.CHART_TYPE_EXHAUST, + "id": 246, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 6, - 'tracks': [ + "level": 7, + "season_id": 6, + "tracks": [ { - 'id': 96, - 'type': self.CHART_TYPE_EXHAUST, + "id": 96, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 139, - 'type': self.CHART_TYPE_EXHAUST, + "id": 139, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 216, - 'type': self.CHART_TYPE_EXHAUST, + "id": 216, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 6, - 'tracks': [ + "level": 8, + "season_id": 6, + "tracks": [ { - 'id': 244, - 'type': self.CHART_TYPE_EXHAUST, + "id": 244, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 250, - 'type': self.CHART_TYPE_EXHAUST, + "id": 250, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 180, - 'type': self.CHART_TYPE_EXHAUST, + "id": 180, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 6, - 'tracks': [ + "level": 9, + "season_id": 6, + "tracks": [ { - 'id': 7, - 'type': self.CHART_TYPE_INFINITE, + "id": 7, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 214, - 'type': self.CHART_TYPE_EXHAUST, + "id": 214, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 126, - 'type': self.CHART_TYPE_EXHAUST, + "id": 126, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 7, - 'tracks': [ + "level": 0, + "season_id": 7, + "tracks": [ { - 'id': 54, - 'type': self.CHART_TYPE_NOVICE, + "id": 54, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 221, - 'type': self.CHART_TYPE_NOVICE, + "id": 221, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 51, - 'type': self.CHART_TYPE_EXHAUST, + "id": 51, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 1, - 'season_id': 7, - 'tracks': [ + "level": 1, + "season_id": 7, + "tracks": [ { - 'id': 6, - 'type': self.CHART_TYPE_ADVANCED, + "id": 6, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 111, - 'type': self.CHART_TYPE_ADVANCED, + "id": 111, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 183, - 'type': self.CHART_TYPE_ADVANCED, + "id": 183, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 7, - 'tracks': [ + "level": 2, + "season_id": 7, + "tracks": [ { - 'id': 56, - 'type': self.CHART_TYPE_ADVANCED, + "id": 56, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 333, - 'type': self.CHART_TYPE_ADVANCED, + "id": 333, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 10, - 'type': self.CHART_TYPE_EXHAUST, + "id": 10, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 3, - 'season_id': 7, - 'tracks': [ + "level": 3, + "season_id": 7, + "tracks": [ { - 'id': 134, - 'type': self.CHART_TYPE_ADVANCED, + "id": 134, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 343, - 'type': self.CHART_TYPE_ADVANCED, + "id": 343, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 75, - 'type': self.CHART_TYPE_EXHAUST, + "id": 75, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 7, - 'tracks': [ + "level": 4, + "season_id": 7, + "tracks": [ { - 'id': 36, - 'type': self.CHART_TYPE_ADVANCED, + "id": 36, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 369, - 'type': self.CHART_TYPE_EXHAUST, + "id": 369, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 224, - 'type': self.CHART_TYPE_EXHAUST, + "id": 224, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 7, - 'tracks': [ + "level": 5, + "season_id": 7, + "tracks": [ { - 'id': 90, - 'type': self.CHART_TYPE_EXHAUST, + "id": 90, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 323, - 'type': self.CHART_TYPE_EXHAUST, + "id": 323, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 128, - 'type': self.CHART_TYPE_EXHAUST, + "id": 128, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 7, - 'tracks': [ + "level": 6, + "season_id": 7, + "tracks": [ { - 'id': 85, - 'type': self.CHART_TYPE_EXHAUST, + "id": 85, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 344, - 'type': self.CHART_TYPE_EXHAUST, + "id": 344, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 241, - 'type': self.CHART_TYPE_EXHAUST, + "id": 241, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 7, - 'tracks': [ + "level": 7, + "season_id": 7, + "tracks": [ { - 'id': 251, - 'type': self.CHART_TYPE_EXHAUST, + "id": 251, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 139, - 'type': self.CHART_TYPE_EXHAUST, + "id": 139, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 341, - 'type': self.CHART_TYPE_EXHAUST, + "id": 341, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 7, - 'tracks': [ + "level": 8, + "season_id": 7, + "tracks": [ { - 'id': 346, - 'type': self.CHART_TYPE_EXHAUST, + "id": 346, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 116, - 'type': self.CHART_TYPE_EXHAUST, + "id": 116, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 302, - 'type': self.CHART_TYPE_EXHAUST, + "id": 302, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 7, - 'tracks': [ + "level": 9, + "season_id": 7, + "tracks": [ { - 'id': 19, - 'type': self.CHART_TYPE_INFINITE, + "id": 19, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 329, - 'type': self.CHART_TYPE_EXHAUST, + "id": 329, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 289, - 'type': self.CHART_TYPE_EXHAUST, + "id": 289, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 8, - 'tracks': [ + "level": 0, + "season_id": 8, + "tracks": [ { - 'id': 54, - 'type': self.CHART_TYPE_NOVICE, + "id": 54, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 221, - 'type': self.CHART_TYPE_NOVICE, + "id": 221, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 51, - 'type': self.CHART_TYPE_EXHAUST, + "id": 51, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 1, - 'season_id': 8, - 'tracks': [ + "level": 1, + "season_id": 8, + "tracks": [ { - 'id': 6, - 'type': self.CHART_TYPE_ADVANCED, + "id": 6, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 111, - 'type': self.CHART_TYPE_ADVANCED, + "id": 111, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 183, - 'type': self.CHART_TYPE_ADVANCED, + "id": 183, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 8, - 'tracks': [ + "level": 2, + "season_id": 8, + "tracks": [ { - 'id': 56, - 'type': self.CHART_TYPE_ADVANCED, + "id": 56, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 333, - 'type': self.CHART_TYPE_ADVANCED, + "id": 333, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 10, - 'type': self.CHART_TYPE_EXHAUST, + "id": 10, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 3, - 'season_id': 8, - 'tracks': [ + "level": 3, + "season_id": 8, + "tracks": [ { - 'id': 134, - 'type': self.CHART_TYPE_ADVANCED, + "id": 134, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 343, - 'type': self.CHART_TYPE_ADVANCED, + "id": 343, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 75, - 'type': self.CHART_TYPE_EXHAUST, + "id": 75, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 8, - 'tracks': [ + "level": 4, + "season_id": 8, + "tracks": [ { - 'id': 36, - 'type': self.CHART_TYPE_ADVANCED, + "id": 36, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 369, - 'type': self.CHART_TYPE_EXHAUST, + "id": 369, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 224, - 'type': self.CHART_TYPE_EXHAUST, + "id": 224, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 8, - 'tracks': [ + "level": 5, + "season_id": 8, + "tracks": [ { - 'id': 90, - 'type': self.CHART_TYPE_EXHAUST, + "id": 90, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 323, - 'type': self.CHART_TYPE_EXHAUST, + "id": 323, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 128, - 'type': self.CHART_TYPE_EXHAUST, + "id": 128, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 8, - 'tracks': [ + "level": 6, + "season_id": 8, + "tracks": [ { - 'id': 85, - 'type': self.CHART_TYPE_EXHAUST, + "id": 85, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 344, - 'type': self.CHART_TYPE_EXHAUST, + "id": 344, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 241, - 'type': self.CHART_TYPE_EXHAUST, + "id": 241, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 8, - 'tracks': [ + "level": 7, + "season_id": 8, + "tracks": [ { - 'id': 251, - 'type': self.CHART_TYPE_EXHAUST, + "id": 251, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 139, - 'type': self.CHART_TYPE_EXHAUST, + "id": 139, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 341, - 'type': self.CHART_TYPE_EXHAUST, + "id": 341, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 8, - 'tracks': [ + "level": 8, + "season_id": 8, + "tracks": [ { - 'id': 346, - 'type': self.CHART_TYPE_EXHAUST, + "id": 346, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 116, - 'type': self.CHART_TYPE_EXHAUST, + "id": 116, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 302, - 'type': self.CHART_TYPE_EXHAUST, + "id": 302, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 8, - 'tracks': [ + "level": 9, + "season_id": 8, + "tracks": [ { - 'id': 19, - 'type': self.CHART_TYPE_INFINITE, + "id": 19, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 329, - 'type': self.CHART_TYPE_EXHAUST, + "id": 329, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 289, - 'type': self.CHART_TYPE_EXHAUST, + "id": 289, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 9, - 'tracks': [ + "level": 0, + "season_id": 9, + "tracks": [ { - 'id': 60, - 'type': self.CHART_TYPE_NOVICE, + "id": 60, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 87, - 'type': self.CHART_TYPE_ADVANCED, + "id": 87, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 328, - 'type': self.CHART_TYPE_ADVANCED, + "id": 328, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 9, - 'tracks': [ + "level": 1, + "season_id": 9, + "tracks": [ { - 'id': 278, - 'type': self.CHART_TYPE_ADVANCED, + "id": 278, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 313, - 'type': self.CHART_TYPE_ADVANCED, + "id": 313, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 41, - 'type': self.CHART_TYPE_EXHAUST, + "id": 41, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 2, - 'season_id': 9, - 'tracks': [ + "level": 2, + "season_id": 9, + "tracks": [ { - 'id': 90, - 'type': self.CHART_TYPE_ADVANCED, + "id": 90, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 80, - 'type': self.CHART_TYPE_ADVANCED, + "id": 80, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 295, - 'type': self.CHART_TYPE_ADVANCED, + "id": 295, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 9, - 'tracks': [ + "level": 3, + "season_id": 9, + "tracks": [ { - 'id': 45, - 'type': self.CHART_TYPE_EXHAUST, + "id": 45, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 44, - 'type': self.CHART_TYPE_EXHAUST, + "id": 44, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 326, - 'type': self.CHART_TYPE_ADVANCED, + "id": 326, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 4, - 'season_id': 9, - 'tracks': [ + "level": 4, + "season_id": 9, + "tracks": [ { - 'id': 258, - 'type': self.CHART_TYPE_ADVANCED, + "id": 258, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 340, - 'type': self.CHART_TYPE_EXHAUST, + "id": 340, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 23, - 'type': self.CHART_TYPE_INFINITE, + "id": 23, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'level': 5, - 'season_id': 9, - 'tracks': [ + "level": 5, + "season_id": 9, + "tracks": [ { - 'id': 90, - 'type': self.CHART_TYPE_EXHAUST, + "id": 90, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 115, - 'type': self.CHART_TYPE_EXHAUST, + "id": 115, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 288, - 'type': self.CHART_TYPE_EXHAUST, + "id": 288, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 9, - 'tracks': [ + "level": 6, + "season_id": 9, + "tracks": [ { - 'id': 57, - 'type': self.CHART_TYPE_EXHAUST, + "id": 57, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 267, - 'type': self.CHART_TYPE_EXHAUST, + "id": 267, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 246, - 'type': self.CHART_TYPE_EXHAUST, + "id": 246, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 9, - 'tracks': [ + "level": 7, + "season_id": 9, + "tracks": [ { - 'id': 304, - 'type': self.CHART_TYPE_EXHAUST, + "id": 304, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 155, - 'type': self.CHART_TYPE_EXHAUST, + "id": 155, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 373, - 'type': self.CHART_TYPE_EXHAUST, + "id": 373, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 9, - 'tracks': [ + "level": 8, + "season_id": 9, + "tracks": [ { - 'id': 122, - 'type': self.CHART_TYPE_EXHAUST, + "id": 122, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 359, - 'type': self.CHART_TYPE_EXHAUST, + "id": 359, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 247, - 'type': self.CHART_TYPE_EXHAUST, + "id": 247, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 9, - 'tracks': [ + "level": 9, + "season_id": 9, + "tracks": [ { - 'id': 221, - 'type': self.CHART_TYPE_EXHAUST, + "id": 221, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 229, - 'type': self.CHART_TYPE_EXHAUST, + "id": 229, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 363, - 'type': self.CHART_TYPE_EXHAUST, + "id": 363, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 10, - 'tracks': [ + "level": 0, + "season_id": 10, + "tracks": [ { - 'id': 365, - 'type': self.CHART_TYPE_NOVICE, + "id": 365, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 328, - 'type': self.CHART_TYPE_ADVANCED, + "id": 328, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 51, - 'type': self.CHART_TYPE_EXHAUST, + "id": 51, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 1, - 'season_id': 10, - 'tracks': [ + "level": 1, + "season_id": 10, + "tracks": [ { - 'id': 126, - 'type': self.CHART_TYPE_NOVICE, + "id": 126, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 111, - 'type': self.CHART_TYPE_ADVANCED, + "id": 111, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 41, - 'type': self.CHART_TYPE_EXHAUST, + "id": 41, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 2, - 'season_id': 10, - 'tracks': [ + "level": 2, + "season_id": 10, + "tracks": [ { - 'id': 15, - 'type': self.CHART_TYPE_EXHAUST, + "id": 15, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 322, - 'type': self.CHART_TYPE_ADVANCED, + "id": 322, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 10, - 'type': self.CHART_TYPE_EXHAUST, + "id": 10, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 3, - 'season_id': 10, - 'tracks': [ + "level": 3, + "season_id": 10, + "tracks": [ { - 'id': 259, - 'type': self.CHART_TYPE_ADVANCED, + "id": 259, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 299, - 'type': self.CHART_TYPE_ADVANCED, + "id": 299, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 22, - 'type': self.CHART_TYPE_EXHAUST, + "id": 22, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 10, - 'tracks': [ + "level": 4, + "season_id": 10, + "tracks": [ { - 'id': 258, - 'type': self.CHART_TYPE_ADVANCED, + "id": 258, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 23, - 'type': self.CHART_TYPE_INFINITE, + "id": 23, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 66, - 'type': self.CHART_TYPE_EXHAUST, + "id": 66, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 10, - 'tracks': [ + "level": 5, + "season_id": 10, + "tracks": [ { - 'id': 62, - 'type': self.CHART_TYPE_EXHAUST, + "id": 62, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 85, - 'type': self.CHART_TYPE_EXHAUST, + "id": 85, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 288, - 'type': self.CHART_TYPE_EXHAUST, + "id": 288, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 10, - 'tracks': [ + "level": 6, + "season_id": 10, + "tracks": [ { - 'id': 78, - 'type': self.CHART_TYPE_EXHAUST, + "id": 78, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 311, - 'type': self.CHART_TYPE_EXHAUST, + "id": 311, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 71, - 'type': self.CHART_TYPE_EXHAUST, + "id": 71, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 10, - 'tracks': [ + "level": 7, + "season_id": 10, + "tracks": [ { - 'id': 87, - 'type': self.CHART_TYPE_INFINITE, + "id": 87, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 341, - 'type': self.CHART_TYPE_EXHAUST, + "id": 341, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 173, - 'type': self.CHART_TYPE_EXHAUST, + "id": 173, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 10, - 'tracks': [ + "level": 8, + "season_id": 10, + "tracks": [ { - 'id': 63, - 'type': self.CHART_TYPE_EXHAUST, + "id": 63, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 228, - 'type': self.CHART_TYPE_EXHAUST, + "id": 228, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 166, - 'type': self.CHART_TYPE_EXHAUST, + "id": 166, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 10, - 'tracks': [ + "level": 9, + "season_id": 10, + "tracks": [ { - 'id': 155, - 'type': self.CHART_TYPE_INFINITE, + "id": 155, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 229, - 'type': self.CHART_TYPE_EXHAUST, + "id": 229, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 384, - 'type': self.CHART_TYPE_EXHAUST, + "id": 384, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 11, - 'tracks': [ + "level": 0, + "season_id": 11, + "tracks": [ { - 'id': 54, - 'type': self.CHART_TYPE_NOVICE, + "id": 54, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 365, - 'type': self.CHART_TYPE_NOVICE, + "id": 365, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 374, - 'type': self.CHART_TYPE_NOVICE, + "id": 374, + "type": self.CHART_TYPE_NOVICE, }, ], }, { - 'level': 1, - 'season_id': 11, - 'tracks': [ + "level": 1, + "season_id": 11, + "tracks": [ { - 'id': 126, - 'type': self.CHART_TYPE_NOVICE, + "id": 126, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 22, - 'type': self.CHART_TYPE_ADVANCED, + "id": 22, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 183, - 'type': self.CHART_TYPE_ADVANCED, + "id": 183, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 11, - 'tracks': [ + "level": 2, + "season_id": 11, + "tracks": [ { - 'id': 56, - 'type': self.CHART_TYPE_ADVANCED, + "id": 56, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 39, - 'type': self.CHART_TYPE_ADVANCED, + "id": 39, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 10, - 'type': self.CHART_TYPE_EXHAUST, + "id": 10, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 3, - 'season_id': 11, - 'tracks': [ + "level": 3, + "season_id": 11, + "tracks": [ { - 'id': 369, - 'type': self.CHART_TYPE_ADVANCED, + "id": 369, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 299, - 'type': self.CHART_TYPE_ADVANCED, + "id": 299, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 222, - 'type': self.CHART_TYPE_EXHAUST, + "id": 222, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 11, - 'tracks': [ + "level": 4, + "season_id": 11, + "tracks": [ { - 'id': 103, - 'type': self.CHART_TYPE_EXHAUST, + "id": 103, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 158, - 'type': self.CHART_TYPE_ADVANCED, + "id": 158, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 74, - 'type': self.CHART_TYPE_EXHAUST, + "id": 74, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 11, - 'tracks': [ + "level": 5, + "season_id": 11, + "tracks": [ { - 'id': 262, - 'type': self.CHART_TYPE_EXHAUST, + "id": 262, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 128, - 'type': self.CHART_TYPE_EXHAUST, + "id": 128, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 79, - 'type': self.CHART_TYPE_EXHAUST, + "id": 79, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 11, - 'tracks': [ + "level": 6, + "season_id": 11, + "tracks": [ { - 'id': 264, - 'type': self.CHART_TYPE_EXHAUST, + "id": 264, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 71, - 'type': self.CHART_TYPE_EXHAUST, + "id": 71, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 192, - 'type': self.CHART_TYPE_EXHAUST, + "id": 192, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 11, - 'tracks': [ + "level": 7, + "season_id": 11, + "tracks": [ { - 'id': 253, - 'type': self.CHART_TYPE_EXHAUST, + "id": 253, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 299, - 'type': self.CHART_TYPE_EXHAUST, + "id": 299, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 341, - 'type': self.CHART_TYPE_EXHAUST, + "id": 341, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 11, - 'tracks': [ + "level": 8, + "season_id": 11, + "tracks": [ { - 'id': 58, - 'type': self.CHART_TYPE_INFINITE, + "id": 58, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 343, - 'type': self.CHART_TYPE_EXHAUST, + "id": 343, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 269, - 'type': self.CHART_TYPE_EXHAUST, + "id": 269, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 11, - 'tracks': [ + "level": 9, + "season_id": 11, + "tracks": [ { - 'id': 116, - 'type': self.CHART_TYPE_EXHAUST, + "id": 116, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 289, - 'type': self.CHART_TYPE_EXHAUST, + "id": 289, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 376, - 'type': self.CHART_TYPE_EXHAUST, + "id": 376, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 12, - 'tracks': [ + "level": 0, + "season_id": 12, + "tracks": [ { - 'id': 189, - 'type': self.CHART_TYPE_ADVANCED, + "id": 189, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 245, - 'type': self.CHART_TYPE_ADVANCED, + "id": 245, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 367, - 'type': self.CHART_TYPE_NOVICE, + "id": 367, + "type": self.CHART_TYPE_NOVICE, }, ], }, { - 'level': 1, - 'season_id': 12, - 'tracks': [ + "level": 1, + "season_id": 12, + "tracks": [ { - 'id': 278, - 'type': self.CHART_TYPE_ADVANCED, + "id": 278, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 340, - 'type': self.CHART_TYPE_ADVANCED, + "id": 340, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 183, - 'type': self.CHART_TYPE_ADVANCED, + "id": 183, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 12, - 'tracks': [ + "level": 2, + "season_id": 12, + "tracks": [ { - 'id': 426, - 'type': self.CHART_TYPE_ADVANCED, + "id": 426, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 349, - 'type': self.CHART_TYPE_ADVANCED, + "id": 349, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 322, - 'type': self.CHART_TYPE_ADVANCED, + "id": 322, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 12, - 'tracks': [ + "level": 3, + "season_id": 12, + "tracks": [ { - 'id': 342, - 'type': self.CHART_TYPE_ADVANCED, + "id": 342, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 190, - 'type': self.CHART_TYPE_EXHAUST, + "id": 190, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 222, - 'type': self.CHART_TYPE_EXHAUST, + "id": 222, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 12, - 'tracks': [ + "level": 4, + "season_id": 12, + "tracks": [ { - 'id': 158, - 'type': self.CHART_TYPE_ADVANCED, + "id": 158, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 352, - 'type': self.CHART_TYPE_EXHAUST, + "id": 352, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 212, - 'type': self.CHART_TYPE_EXHAUST, + "id": 212, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 12, - 'tracks': [ + "level": 5, + "season_id": 12, + "tracks": [ { - 'id': 275, - 'type': self.CHART_TYPE_EXHAUST, + "id": 275, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 198, - 'type': self.CHART_TYPE_EXHAUST, + "id": 198, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 331, - 'type': self.CHART_TYPE_EXHAUST, + "id": 331, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 12, - 'tracks': [ + "level": 6, + "season_id": 12, + "tracks": [ { - 'id': 184, - 'type': self.CHART_TYPE_EXHAUST, + "id": 184, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 345, - 'type': self.CHART_TYPE_EXHAUST, + "id": 345, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 218, - 'type': self.CHART_TYPE_EXHAUST, + "id": 218, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 12, - 'tracks': [ + "level": 7, + "season_id": 12, + "tracks": [ { - 'id': 268, - 'type': self.CHART_TYPE_EXHAUST, + "id": 268, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 299, - 'type': self.CHART_TYPE_EXHAUST, + "id": 299, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 373, - 'type': self.CHART_TYPE_EXHAUST, + "id": 373, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 12, - 'tracks': [ + "level": 8, + "season_id": 12, + "tracks": [ { - 'id': 244, - 'type': self.CHART_TYPE_EXHAUST, + "id": 244, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 414, - 'type': self.CHART_TYPE_EXHAUST, + "id": 414, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 269, - 'type': self.CHART_TYPE_EXHAUST, + "id": 269, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 12, - 'tracks': [ + "level": 9, + "season_id": 12, + "tracks": [ { - 'id': 408, - 'type': self.CHART_TYPE_EXHAUST, + "id": 408, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 376, - 'type': self.CHART_TYPE_EXHAUST, + "id": 376, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 362, - 'type': self.CHART_TYPE_EXHAUST, + "id": 362, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 13, - 'tracks': [ + "level": 0, + "season_id": 13, + "tracks": [ { - 'id': 189, - 'type': self.CHART_TYPE_ADVANCED, + "id": 189, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 219, - 'type': self.CHART_TYPE_ADVANCED, + "id": 219, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 367, - 'type': self.CHART_TYPE_NOVICE, + "id": 367, + "type": self.CHART_TYPE_NOVICE, }, ], }, { - 'level': 1, - 'season_id': 13, - 'tracks': [ + "level": 1, + "season_id": 13, + "tracks": [ { - 'id': 278, - 'type': self.CHART_TYPE_ADVANCED, + "id": 278, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 340, - 'type': self.CHART_TYPE_ADVANCED, + "id": 340, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 313, - 'type': self.CHART_TYPE_ADVANCED, + "id": 313, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 13, - 'tracks': [ + "level": 2, + "season_id": 13, + "tracks": [ { - 'id': 90, - 'type': self.CHART_TYPE_ADVANCED, + "id": 90, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 223, - 'type': self.CHART_TYPE_ADVANCED, + "id": 223, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 322, - 'type': self.CHART_TYPE_ADVANCED, + "id": 322, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 13, - 'tracks': [ + "level": 3, + "season_id": 13, + "tracks": [ { - 'id': 299, - 'type': self.CHART_TYPE_ADVANCED, + "id": 299, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 407, - 'type': self.CHART_TYPE_ADVANCED, + "id": 407, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 77, - 'type': self.CHART_TYPE_EXHAUST, + "id": 77, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 13, - 'tracks': [ + "level": 4, + "season_id": 13, + "tracks": [ { - 'id': 36, - 'type': self.CHART_TYPE_ADVANCED, + "id": 36, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 92, - 'type': self.CHART_TYPE_EXHAUST, + "id": 92, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 337, - 'type': self.CHART_TYPE_EXHAUST, + "id": 337, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 5, - 'season_id': 13, - 'tracks': [ + "level": 5, + "season_id": 13, + "tracks": [ { - 'id': 8, - 'type': self.CHART_TYPE_EXHAUST, + "id": 8, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 375, - 'type': self.CHART_TYPE_EXHAUST, + "id": 375, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 426, - 'type': self.CHART_TYPE_EXHAUST, + "id": 426, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 6, - 'season_id': 13, - 'tracks': [ + "level": 6, + "season_id": 13, + "tracks": [ { - 'id': 401, - 'type': self.CHART_TYPE_EXHAUST, + "id": 401, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 345, - 'type': self.CHART_TYPE_EXHAUST, + "id": 345, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 290, - 'type': self.CHART_TYPE_EXHAUST, + "id": 290, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 13, - 'tracks': [ + "level": 7, + "season_id": 13, + "tracks": [ { - 'id': 432, - 'type': self.CHART_TYPE_EXHAUST, + "id": 432, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 72, - 'type': self.CHART_TYPE_INFINITE, + "id": 72, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 373, - 'type': self.CHART_TYPE_EXHAUST, + "id": 373, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 13, - 'tracks': [ + "level": 8, + "season_id": 13, + "tracks": [ { - 'id': 125, - 'type': self.CHART_TYPE_EXHAUST, + "id": 125, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 302, - 'type': self.CHART_TYPE_EXHAUST, + "id": 302, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 252, - 'type': self.CHART_TYPE_EXHAUST, + "id": 252, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 13, - 'tracks': [ + "level": 9, + "season_id": 13, + "tracks": [ { - 'id': 247, - 'type': self.CHART_TYPE_EXHAUST, + "id": 247, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 437, - 'type': self.CHART_TYPE_EXHAUST, + "id": 437, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 342, - 'type': self.CHART_TYPE_EXHAUST, + "id": 342, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 0, - 'season_id': 14, - 'tracks': [ + "level": 0, + "season_id": 14, + "tracks": [ { - 'id': 228, - 'type': self.CHART_TYPE_NOVICE, + "id": 228, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 374, - 'type': self.CHART_TYPE_NOVICE, + "id": 374, + "type": self.CHART_TYPE_NOVICE, }, { - 'id': 24, - 'type': self.CHART_TYPE_ADVANCED, + "id": 24, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 1, - 'season_id': 14, - 'tracks': [ + "level": 1, + "season_id": 14, + "tracks": [ { - 'id': 76, - 'type': self.CHART_TYPE_ADVANCED, + "id": 76, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 8, - 'type': self.CHART_TYPE_ADVANCED, + "id": 8, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 309, - 'type': self.CHART_TYPE_ADVANCED, + "id": 309, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 2, - 'season_id': 14, - 'tracks': [ + "level": 2, + "season_id": 14, + "tracks": [ { - 'id': 412, - 'type': self.CHART_TYPE_ADVANCED, + "id": 412, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 155, - 'type': self.CHART_TYPE_ADVANCED, + "id": 155, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 99, - 'type': self.CHART_TYPE_ADVANCED, + "id": 99, + "type": self.CHART_TYPE_ADVANCED, }, ], }, { - 'level': 3, - 'season_id': 14, - 'tracks': [ + "level": 3, + "season_id": 14, + "tracks": [ { - 'id': 269, - 'type': self.CHART_TYPE_ADVANCED, + "id": 269, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 24, - 'type': self.CHART_TYPE_EXHAUST, + "id": 24, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 171, - 'type': self.CHART_TYPE_EXHAUST, + "id": 171, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 4, - 'season_id': 14, - 'tracks': [ + "level": 4, + "season_id": 14, + "tracks": [ { - 'id': 258, - 'type': self.CHART_TYPE_ADVANCED, + "id": 258, + "type": self.CHART_TYPE_ADVANCED, }, { - 'id': 92, - 'type': self.CHART_TYPE_EXHAUST, + "id": 92, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 34, - 'type': self.CHART_TYPE_INFINITE, + "id": 34, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'level': 5, - 'season_id': 14, - 'tracks': [ + "level": 5, + "season_id": 14, + "tracks": [ { - 'id': 42, - 'type': self.CHART_TYPE_EXHAUST, + "id": 42, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 275, - 'type': self.CHART_TYPE_EXHAUST, + "id": 275, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 480, - 'type': self.CHART_TYPE_INFINITE, + "id": 480, + "type": self.CHART_TYPE_INFINITE, }, ], }, { - 'level': 6, - 'season_id': 14, - 'tracks': [ + "level": 6, + "season_id": 14, + "tracks": [ { - 'id': 170, - 'type': self.CHART_TYPE_EXHAUST, + "id": 170, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 264, - 'type': self.CHART_TYPE_EXHAUST, + "id": 264, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 307, - 'type': self.CHART_TYPE_EXHAUST, + "id": 307, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 7, - 'season_id': 14, - 'tracks': [ + "level": 7, + "season_id": 14, + "tracks": [ { - 'id': 253, - 'type': self.CHART_TYPE_EXHAUST, + "id": 253, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 72, - 'type': self.CHART_TYPE_INFINITE, + "id": 72, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 430, - 'type': self.CHART_TYPE_EXHAUST, + "id": 430, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 8, - 'season_id': 14, - 'tracks': [ + "level": 8, + "season_id": 14, + "tracks": [ { - 'id': 63, - 'type': self.CHART_TYPE_EXHAUST, + "id": 63, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 343, - 'type': self.CHART_TYPE_EXHAUST, + "id": 343, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 220, - 'type': self.CHART_TYPE_EXHAUST, + "id": 220, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 9, - 'season_id': 14, - 'tracks': [ + "level": 9, + "season_id": 14, + "tracks": [ { - 'id': 413, - 'type': self.CHART_TYPE_EXHAUST, + "id": 413, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 437, - 'type': self.CHART_TYPE_EXHAUST, + "id": 437, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 362, - 'type': self.CHART_TYPE_EXHAUST, + "id": 362, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 10, - 'season_id': 14, - 'tracks': [ + "level": 10, + "season_id": 14, + "tracks": [ { - 'id': 258, - 'type': self.CHART_TYPE_EXHAUST, + "id": 258, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 374, - 'type': self.CHART_TYPE_EXHAUST, + "id": 374, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 360, - 'type': self.CHART_TYPE_EXHAUST, + "id": 360, + "type": self.CHART_TYPE_EXHAUST, }, ], }, { - 'level': 11, - 'season_id': 14, - 'tracks': [ + "level": 11, + "season_id": 14, + "tracks": [ { - 'id': 366, - 'type': self.CHART_TYPE_EXHAUST, + "id": 366, + "type": self.CHART_TYPE_EXHAUST, }, { - 'id': 126, - 'type': self.CHART_TYPE_INFINITE, + "id": 126, + "type": self.CHART_TYPE_INFINITE, }, { - 'id': 367, - 'type': self.CHART_TYPE_EXHAUST, + "id": 367, + "type": self.CHART_TYPE_EXHAUST, }, ], }, ] def handle_game_2_exception_request(self, request: Node) -> Node: - return Node.void('game_2') + return Node.void("game_2") def handle_game_2_play_e_request(self, request: Node) -> Node: - return Node.void('game_2') + return Node.void("game_2") def handle_game_2_save_e_request(self, request: Node) -> Node: # This has to do with Policy Break against ReflecBeat and # floor infection, but we don't implement multi-game support so meh. - return Node.void('game_2') + return Node.void("game_2") def handle_game_2_lounge_request(self, request: Node) -> Node: - game = Node.void('game_2') + game = Node.void("game_2") # Refresh interval in seconds. - game.add_child(Node.u32('interval', 10)) + game.add_child(Node.u32("interval", 10)) return game def handle_game_2_entry_s_request(self, request: Node) -> Node: - game = Node.void('game_2') + game = Node.void("game_2") # This should be created on the fly for a lobby that we're in. - game.add_child(Node.u32('entry_id', 1)) + game.add_child(Node.u32("entry_id", 1)) return game def handle_game_2_entry_e_request(self, request: Node) -> Node: # Lobby destroy method, eid node (u32) should be used # to destroy any open lobbies. - return Node.void('game_2') + return Node.void("game_2") def handle_game_2_shop_request(self, request: Node) -> Node: - self.update_machine_name(request.child_value('shopname')) + self.update_machine_name(request.child_value("shopname")) # Respond with number of milliseconds until next request - game = Node.void('game_2') - game.add_child(Node.u32('nxt_time', 1000 * 5 * 60)) + game = Node.void("game_2") + game.add_child(Node.u32("nxt_time", 1000 * 5 * 60)) return game def handle_game_2_common_request(self, request: Node) -> Node: - game = Node.void('game_2') - limited = Node.void('music_limited') + game = Node.void("game_2") + limited = Node.void("music_limited") game.add_child(limited) # Song unlock config game_config = self.get_game_config() - if game_config.get_bool('force_unlock_songs'): + if game_config.get_bool("force_unlock_songs"): ids = set() songs = self.data.local.music.get_all_songs(self.game, self.version) for song in songs: - if song.data.get_int('limited') in (self.GAME_LIMITED_LOCKED, self.GAME_LIMITED_UNLOCKABLE): + if song.data.get_int("limited") in ( + self.GAME_LIMITED_LOCKED, + self.GAME_LIMITED_UNLOCKABLE, + ): ids.add((song.id, song.chart)) for (songid, chart) in ids: - info = Node.void('info') + info = Node.void("info") limited.add_child(info) - info.add_child(Node.s32('music_id', songid)) - info.add_child(Node.u8('music_type', chart)) - info.add_child(Node.u8('limited', self.GAME_LIMITED_UNLOCKED)) + info.add_child(Node.s32("music_id", songid)) + info.add_child(Node.u8("music_type", chart)) + info.add_child(Node.u8("limited", self.GAME_LIMITED_UNLOCKED)) # Event config - event = Node.void('event') + event = Node.void("event") game.add_child(event) def enable_event(eid: int) -> None: - evt = Node.void('info') + evt = Node.void("info") event.add_child(evt) - evt.add_child(Node.u32('event_id', eid)) + evt.add_child(Node.u32("event_id", eid)) - if not game_config.get_bool('disable_matching'): + if not game_config.get_bool("disable_matching"): enable_event(1) # Matching enabled enable_event(2) # BEMANI Academy enable_event(3) # Floor Infection @@ -1897,171 +1900,185 @@ class SoundVoltexInfiniteInfection( enable_event(9) # Policy Break enable_event(14) # Enable pages on Skill Analyzer - stadium = game_config.get_int('bemani_stadium') + stadium = game_config.get_int("bemani_stadium") if stadium == 1: enable_event(18) # BEMANI Stadium (mutually exclusive with BEMANI iseki) if stadium == 2: enable_event(51) # BEMANI iseki (mutually exclusive with BEMANI Stadium) # In-game purchases catalog config (this isn't necessary for SDVX 2 to work). - catalog = Node.void('catalog') + catalog = Node.void("catalog") game.add_child(catalog) songunlocks = self.data.local.game.get_items(self.game, self.version) for unlock in songunlocks: - if unlock.type == 'song_unlock': - info = Node.void('info') + if unlock.type == "song_unlock": + info = Node.void("info") catalog.add_child(info) - info.add_child(Node.u8('catalog_type', self.GAME_CATALOG_TYPE_SONG)) - info.add_child(Node.u32('catalog_id', unlock.id)) - info.add_child(Node.u32('currency_type', self.GAME_CURRENCY_BLOCKS)) - info.add_child(Node.u32('price', unlock.data.get_int('blocks'))) - elif unlock.type == 'special_unlock': - info = Node.void('info') + info.add_child(Node.u8("catalog_type", self.GAME_CATALOG_TYPE_SONG)) + info.add_child(Node.u32("catalog_id", unlock.id)) + info.add_child(Node.u32("currency_type", self.GAME_CURRENCY_BLOCKS)) + info.add_child(Node.u32("price", unlock.data.get_int("blocks"))) + elif unlock.type == "special_unlock": + info = Node.void("info") catalog.add_child(info) - info.add_child(Node.u8('catalog_type', self.GAME_CATALOG_TYPE_SPECIAL_SONG)) - info.add_child(Node.u32('catalog_id', unlock.id)) - info.add_child(Node.u32('currency_type', self.GAME_CURRENCY_BLOCKS)) - info.add_child(Node.u32('price', unlock.data.get_int('blocks'))) + info.add_child( + Node.u8("catalog_type", self.GAME_CATALOG_TYPE_SPECIAL_SONG) + ) + info.add_child(Node.u32("catalog_id", unlock.id)) + info.add_child(Node.u32("currency_type", self.GAME_CURRENCY_BLOCKS)) + info.add_child(Node.u32("price", unlock.data.get_int("blocks"))) # Skill Analyzer config - skill_course = Node.void('skill_course') + skill_course = Node.void("skill_course") game.add_child(skill_course) seasons = self.__get_skill_analyzer_seasons() skillnames = self.__get_skill_analyzer_skill_levels() last_season = max(seasons.keys()) for course in self.__get_skill_analyzer_courses(): - info = Node.void('info') + info = Node.void("info") skill_course.add_child(info) - info.add_child(Node.s16('course_id', course['level'])) - info.add_child(Node.s16('level', course['level'])) - info.add_child(Node.s32('season_id', course['season_id'])) - info.add_child(Node.string('season_name', seasons[course['season_id']])) - info.add_child(Node.bool('season_new_flg', course['season_id'] == last_season)) - info.add_child(Node.string('course_name', skillnames[course['level']])) - info.add_child(Node.s16('course_type', 0)) - info.add_child(Node.s16('skill_name_id', course['level'])) - info.add_child(Node.bool('matching_assist', course['level'] <= 6)) - info.add_child(Node.s16('gauge_type', self.GAME_GAUGE_TYPE_SKILL)) - info.add_child(Node.s16('paseli_type', 0)) + info.add_child(Node.s16("course_id", course["level"])) + info.add_child(Node.s16("level", course["level"])) + info.add_child(Node.s32("season_id", course["season_id"])) + info.add_child(Node.string("season_name", seasons[course["season_id"]])) + info.add_child( + Node.bool("season_new_flg", course["season_id"] == last_season) + ) + info.add_child(Node.string("course_name", skillnames[course["level"]])) + info.add_child(Node.s16("course_type", 0)) + info.add_child(Node.s16("skill_name_id", course["level"])) + info.add_child(Node.bool("matching_assist", course["level"] <= 6)) + info.add_child(Node.s16("gauge_type", self.GAME_GAUGE_TYPE_SKILL)) + info.add_child(Node.s16("paseli_type", 0)) trackno = 0 - for trackdata in course['tracks']: - track = Node.void('track') + for trackdata in course["tracks"]: + track = Node.void("track") info.add_child(track) - track.add_child(Node.s16('track_no', trackno)) - track.add_child(Node.s32('music_id', trackdata['id'])) - track.add_child(Node.s8('music_type', trackdata['type'])) + track.add_child(Node.s16("track_no", trackno)) + track.add_child(Node.s32("music_id", trackdata["id"])) + track.add_child(Node.s8("music_type", trackdata["type"])) trackno = trackno + 1 return game def handle_game_2_hiscore_request(self, request: Node) -> Node: # Grab location for local scores - locid = ID.parse_machine_id(request.child_value('locid')) + locid = ID.parse_machine_id(request.child_value("locid")) # Start the response packet - game = Node.void('game_2') + game = Node.void("game_2") # First, grab hit chart playcounts = self.data.local.music.get_hit_chart(self.game, self.version, 1024) - hitchart = Node.void('hitchart') + hitchart = Node.void("hitchart") game.add_child(hitchart) for (songid, count) in playcounts: - info = Node.void('info') + info = Node.void("info") hitchart.add_child(info) - info.add_child(Node.u32('id', songid)) - info.add_child(Node.u32('cnt', count)) + info.add_child(Node.u32("id", songid)) + info.add_child(Node.u32("cnt", count)) # Now, grab user records records = self.data.remote.music.get_all_records(self.game, self.version) missing_users = [userid for (userid, _) in records] - users = {userid: profile for (userid, profile) in self.get_any_profiles(missing_users)} + users = { + userid: profile + for (userid, profile) in self.get_any_profiles(missing_users) + } - hiscore_allover = Node.void('hiscore_allover') + hiscore_allover = Node.void("hiscore_allover") game.add_child(hiscore_allover) # Output records for (userid, score) in records: - info = Node.void('info') + info = Node.void("info") if userid not in users: - raise Exception('Logic error, missing profile for user!') + raise Exception("Logic error, missing profile for user!") profile = users[userid] - info.add_child(Node.u32('id', score.id)) - info.add_child(Node.u32('type', score.chart)) - info.add_child(Node.string('name', profile.get_str('name'))) - info.add_child(Node.string('code', ID.format_extid(profile.extid))) - info.add_child(Node.u32('score', score.points)) + info.add_child(Node.u32("id", score.id)) + info.add_child(Node.u32("type", score.chart)) + info.add_child(Node.string("name", profile.get_str("name"))) + info.add_child(Node.string("code", ID.format_extid(profile.extid))) + info.add_child(Node.u32("score", score.points)) # Add to global scores hiscore_allover.add_child(info) # Now, grab local records area_users = [ - uid for (uid, prof) in self.data.local.user.get_all_profiles(self.game, self.version) - if prof.get_int('loc', -1) == locid + uid + for (uid, prof) in self.data.local.user.get_all_profiles( + self.game, self.version + ) + if prof.get_int("loc", -1) == locid ] - records = self.data.local.music.get_all_records(self.game, self.version, userlist=area_users) + records = self.data.local.music.get_all_records( + self.game, self.version, userlist=area_users + ) missing_users = [userid for (userid, _) in records if userid not in users] for (userid, profile) in self.get_any_profiles(missing_users): users[userid] = profile - hiscore_location = Node.void('hiscore_location') + hiscore_location = Node.void("hiscore_location") game.add_child(hiscore_location) # Output records for (userid, score) in records: - info = Node.void('info') + info = Node.void("info") if userid not in users: - raise Exception('Logic error, missing profile for user!') + raise Exception("Logic error, missing profile for user!") profile = users[userid] - info.add_child(Node.u32('id', score.id)) - info.add_child(Node.u32('type', score.chart)) - info.add_child(Node.string('name', profile.get_str('name'))) - info.add_child(Node.string('code', ID.format_extid(profile.extid))) - info.add_child(Node.u32('score', score.points)) + info.add_child(Node.u32("id", score.id)) + info.add_child(Node.u32("type", score.chart)) + info.add_child(Node.string("name", profile.get_str("name"))) + info.add_child(Node.string("code", ID.format_extid(profile.extid))) + info.add_child(Node.u32("score", score.points)) # Add to local scores hiscore_location.add_child(info) # Now, grab clear rates - clear_rate = Node.void('clear_rate') + clear_rate = Node.void("clear_rate") game.add_child(clear_rate) clears = self.get_clear_rates() for songid in clears: for chart in clears[songid]: - if clears[songid][chart]['total'] > 0: - rate = float(clears[songid][chart]['clears']) / float(clears[songid][chart]['total']) - dnode = Node.void('d') + if clears[songid][chart]["total"] > 0: + rate = float(clears[songid][chart]["clears"]) / float( + clears[songid][chart]["total"] + ) + dnode = Node.void("d") clear_rate.add_child(dnode) - dnode.add_child(Node.u32('id', songid)) - dnode.add_child(Node.u32('type', chart)) - dnode.add_child(Node.s16('cr', int(rate * 10000))) + dnode.add_child(Node.u32("id", songid)) + dnode.add_child(Node.u32("type", chart)) + dnode.add_child(Node.s16("cr", int(rate * 10000))) return game def handle_game_2_new_request(self, request: Node) -> Node: - refid = request.child_value('refid') - name = request.child_value('name') - loc = ID.parse_machine_id(request.child_value('locid')) + refid = request.child_value("refid") + name = request.child_value("name") + loc = ID.parse_machine_id(request.child_value("locid")) self.new_profile_by_refid(refid, name, loc) - root = Node.void('game_2') + root = Node.void("game_2") return root def handle_game_2_frozen_request(self, request: Node) -> Node: - game = Node.void('game_2') - game.add_child(Node.u8('result', 0)) + game = Node.void("game_2") + game.add_child(Node.u8("result", 0)) return game def handle_game_2_load_request(self, request: Node) -> Node: - refid = request.child_value('refid') + refid = request.child_value("refid") root = self.get_profile_by_refid(refid) if root is not None: return root @@ -2080,17 +2097,17 @@ class SoundVoltexInfiniteInfection( profile = None if profile is not None: - root = Node.void('game_2') - root.add_child(Node.u8('result', 2)) - root.add_child(Node.string('name', profile.get_str('name'))) + root = Node.void("game_2") + root.add_child(Node.u8("result", 2)) + root.add_child(Node.string("name", profile.get_str("name"))) return root else: - root = Node.void('game_2') - root.add_child(Node.u8('result', 1)) + root = Node.void("game_2") + root.add_child(Node.u8("result", 1)) return root def handle_game_2_save_request(self, request: Node) -> Node: - refid = request.child_value('refid') + refid = request.child_value("refid") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -2106,10 +2123,10 @@ class SoundVoltexInfiniteInfection( if userid is not None and newprofile is not None: self.put_profile(userid, newprofile) - return Node.void('game_2') + return Node.void("game_2") def handle_game_2_load_m_request(self, request: Node) -> Node: - refid = request.child_value('dataid') + refid = request.child_value("dataid") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -2122,28 +2139,37 @@ class SoundVoltexInfiniteInfection( scores = [] # Output to the game - game = Node.void('game_2') - new = Node.void('new') + game = Node.void("game_2") + new = Node.void("new") game.add_child(new) for score in scores: - music = Node.void('music') + music = Node.void("music") new.add_child(music) - music.add_child(Node.u32('music_id', score.id)) - music.add_child(Node.u32('music_type', score.chart)) - music.add_child(Node.u32('score', score.points)) - music.add_child(Node.u32('cnt', score.plays)) - music.add_child(Node.u32('clear_type', self.__db_to_game_clear_type(score.data.get_int('clear_type')))) - music.add_child(Node.u32('score_grade', self.__db_to_game_grade(score.data.get_int('grade')))) - stats = score.data.get_dict('stats') - music.add_child(Node.u32('btn_rate', stats.get_int('btn_rate'))) - music.add_child(Node.u32('long_rate', stats.get_int('long_rate'))) - music.add_child(Node.u32('vol_rate', stats.get_int('vol_rate'))) + music.add_child(Node.u32("music_id", score.id)) + music.add_child(Node.u32("music_type", score.chart)) + music.add_child(Node.u32("score", score.points)) + music.add_child(Node.u32("cnt", score.plays)) + music.add_child( + Node.u32( + "clear_type", + self.__db_to_game_clear_type(score.data.get_int("clear_type")), + ) + ) + music.add_child( + Node.u32( + "score_grade", self.__db_to_game_grade(score.data.get_int("grade")) + ) + ) + stats = score.data.get_dict("stats") + music.add_child(Node.u32("btn_rate", stats.get_int("btn_rate"))) + music.add_child(Node.u32("long_rate", stats.get_int("long_rate"))) + music.add_child(Node.u32("vol_rate", stats.get_int("vol_rate"))) return game def handle_game_2_save_m_request(self, request: Node) -> Node: - refid = request.child_value('refid') + refid = request.child_value("refid") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -2151,19 +2177,19 @@ class SoundVoltexInfiniteInfection( userid = None # Doesn't matter if userid is None here, that's an anonymous score - musicid = request.child_value('music_id') - chart = request.child_value('music_type') - points = request.child_value('score') - combo = request.child_value('max_chain') - clear_type = self.__game_to_db_clear_type(request.child_value('clear_type')) - grade = self.__game_to_db_grade(request.child_value('score_grade')) + musicid = request.child_value("music_id") + chart = request.child_value("music_type") + points = request.child_value("score") + combo = request.child_value("max_chain") + clear_type = self.__game_to_db_clear_type(request.child_value("clear_type")) + grade = self.__game_to_db_grade(request.child_value("score_grade")) stats = { - 'btn_rate': request.child_value('btn_rate'), - 'long_rate': request.child_value('long_rate'), - 'vol_rate': request.child_value('vol_rate'), - 'critical': request.child_value('critical'), - 'near': request.child_value('near'), - 'error': request.child_value('error'), + "btn_rate": request.child_value("btn_rate"), + "long_rate": request.child_value("long_rate"), + "vol_rate": request.child_value("vol_rate"), + "critical": request.child_value("critical"), + "near": request.child_value("near"), + "error": request.child_value("error"), } # Save the score @@ -2179,10 +2205,10 @@ class SoundVoltexInfiniteInfection( ) # Return a blank response - return Node.void('game_2') + return Node.void("game_2") def handle_game_2_save_c_request(self, request: Node) -> Node: - refid = request.child_value('dataid') + refid = request.child_value("dataid") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -2190,33 +2216,35 @@ class SoundVoltexInfiniteInfection( userid = None if userid is not None: - course_id = request.child_value('crsid') - clear_type = request.child_value('ct') - achievement_rate = request.child_value('ar') - season_id = request.child_value('ssnid') + course_id = request.child_value("crsid") + clear_type = request.child_value("ct") + achievement_rate = request.child_value("ar") + season_id = request.child_value("ssnid") # Do not update the course achievement when old achievement rate is greater. - old = self.data.local.user.get_achievement(self.game, self.version, userid, (season_id * 100) + course_id, 'course') - if old is not None and old.get_int('achievement_rate') > achievement_rate: - return Node.void('game_2') + old = self.data.local.user.get_achievement( + self.game, self.version, userid, (season_id * 100) + course_id, "course" + ) + if old is not None and old.get_int("achievement_rate") > achievement_rate: + return Node.void("game_2") self.data.local.user.put_achievement( self.game, self.version, userid, (season_id * 100) + course_id, - 'course', + "course", { - 'clear_type': clear_type, - 'achievement_rate': achievement_rate, + "clear_type": clear_type, + "achievement_rate": achievement_rate, }, ) # Return a blank response - return Node.void('game_2') + return Node.void("game_2") def handle_game_2_buy_request(self, request: Node) -> Node: - refid = request.child_value('refid') + refid = request.child_value("refid") if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) @@ -2230,15 +2258,15 @@ class SoundVoltexInfiniteInfection( if userid is not None and profile is not None: # Look up packets and blocks - packet = profile.get_int('packet') - block = profile.get_int('block') + packet = profile.get_int("packet") + block = profile.get_int("block") # Add on any additional we earned this round - packet = packet + (request.child_value('earned_gamecoin_packet') or 0) - block = block + (request.child_value('earned_gamecoin_block') or 0) + packet = packet + (request.child_value("earned_gamecoin_packet") or 0) + block = block + (request.child_value("earned_gamecoin_block") or 0) - currency_type = request.child_value('currency_type') - price = request.child_value('price') + currency_type = request.child_value("currency_type") + price = request.child_value("price") if currency_type == self.GAME_CURRENCY_PACKETS: # This is a valid purchase @@ -2262,23 +2290,23 @@ class SoundVoltexInfiniteInfection( if result == 0: # Transaction is valid, update the profile with new packets and blocks - profile.replace_int('packet', packet) - profile.replace_int('block', block) + profile.replace_int("packet", packet) + profile.replace_int("block", block) self.put_profile(userid, profile) # If this was a song unlock, we should mark it as unlocked - item_type = request.child_value('item_type') - item_id = request.child_value('item_id') - param = request.child_value('param') + item_type = request.child_value("item_type") + item_id = request.child_value("item_id") + param = request.child_value("param") self.data.local.user.put_achievement( self.game, self.version, userid, item_id, - f'item_{item_type}', + f"item_{item_type}", { - 'param': param, + "param": param, }, ) @@ -2288,55 +2316,59 @@ class SoundVoltexInfiniteInfection( block = 0 result = 1 - game = Node.void('game_2') - game.add_child(Node.u32('gamecoin_packet', packet)) - game.add_child(Node.u32('gamecoin_block', block)) - game.add_child(Node.s8('result', result)) + game = Node.void("game_2") + game.add_child(Node.u32("gamecoin_packet", packet)) + game.add_child(Node.u32("gamecoin_block", block)) + game.add_child(Node.s8("result", result)) return game def format_profile(self, userid: UserID, profile: Profile) -> Node: - game = Node.void('game_2') + game = Node.void("game_2") # Generic profile stuff - game.add_child(Node.string('name', profile.get_str('name'))) - game.add_child(Node.string('code', ID.format_extid(profile.extid))) - game.add_child(Node.u32('gamecoin_packet', profile.get_int('packet'))) - game.add_child(Node.u32('gamecoin_block', profile.get_int('block'))) - game.add_child(Node.s16('skill_name_id', profile.get_int('skill_name_id', -1))) - game.add_child(Node.s32_array('hidden_param', profile.get_int_array('hidden_param', 20))) - game.add_child(Node.u32('blaster_energy', profile.get_int('blaster_energy'))) - game.add_child(Node.u32('blaster_count', profile.get_int('blaster_count'))) + game.add_child(Node.string("name", profile.get_str("name"))) + game.add_child(Node.string("code", ID.format_extid(profile.extid))) + game.add_child(Node.u32("gamecoin_packet", profile.get_int("packet"))) + game.add_child(Node.u32("gamecoin_block", profile.get_int("block"))) + game.add_child(Node.s16("skill_name_id", profile.get_int("skill_name_id", -1))) + game.add_child( + Node.s32_array("hidden_param", profile.get_int_array("hidden_param", 20)) + ) + game.add_child(Node.u32("blaster_energy", profile.get_int("blaster_energy"))) + game.add_child(Node.u32("blaster_count", profile.get_int("blaster_count"))) # Play statistics statistics = self.get_play_statistics(userid) - game.add_child(Node.u32('play_count', statistics.total_plays)) - game.add_child(Node.u32('daily_count', statistics.today_plays)) - game.add_child(Node.u32('play_chain', statistics.consecutive_days)) + game.add_child(Node.u32("play_count", statistics.total_plays)) + game.add_child(Node.u32("daily_count", statistics.today_plays)) + game.add_child(Node.u32("play_chain", statistics.consecutive_days)) # Last played stuff - if 'last' in profile: - lastdict = profile.get_dict('last') - last = Node.void('last') + if "last" in profile: + lastdict = profile.get_dict("last") + last = Node.void("last") game.add_child(last) - last.add_child(Node.s32('music_id', lastdict.get_int('music_id', -1))) - last.add_child(Node.u8('music_type', lastdict.get_int('music_type'))) - last.add_child(Node.u8('sort_type', lastdict.get_int('sort_type'))) - last.add_child(Node.u8('narrow_down', lastdict.get_int('narrow_down'))) - last.add_child(Node.u8('headphone', lastdict.get_int('headphone'))) - last.add_child(Node.u8('hispeed', lastdict.get_int('hispeed', 8))) - last.add_child(Node.u16('appeal_id', lastdict.get_int('appeal_id', 1001))) - last.add_child(Node.u16('comment_id', lastdict.get_int('comment_id'))) - last.add_child(Node.u8('gauge_option', lastdict.get_int('gauge_option'))) + last.add_child(Node.s32("music_id", lastdict.get_int("music_id", -1))) + last.add_child(Node.u8("music_type", lastdict.get_int("music_type"))) + last.add_child(Node.u8("sort_type", lastdict.get_int("sort_type"))) + last.add_child(Node.u8("narrow_down", lastdict.get_int("narrow_down"))) + last.add_child(Node.u8("headphone", lastdict.get_int("headphone"))) + last.add_child(Node.u8("hispeed", lastdict.get_int("hispeed", 8))) + last.add_child(Node.u16("appeal_id", lastdict.get_int("appeal_id", 1001))) + last.add_child(Node.u16("comment_id", lastdict.get_int("comment_id"))) + last.add_child(Node.u8("gauge_option", lastdict.get_int("gauge_option"))) # Item unlocks - itemnode = Node.void('item') + itemnode = Node.void("item") game.add_child(itemnode) game_config = self.get_game_config() - achievements = self.data.local.user.get_achievements(self.game, self.version, userid) + achievements = self.data.local.user.get_achievements( + self.game, self.version, userid + ) for item in achievements: - if item.type[:5] != 'item_': + if item.type[:5] != "item_": continue itemtype = int(item.type[5:]) @@ -2344,153 +2376,166 @@ class SoundVoltexInfiniteInfection( # Type 1 is appeal cards, and the game saves this for non-default cards but # we take care of this below. continue - if itemtype == self.GAME_CATALOG_TYPE_SONG and game_config.get_bool('force_unlock_songs'): + if itemtype == self.GAME_CATALOG_TYPE_SONG and game_config.get_bool( + "force_unlock_songs" + ): # We will echo this below in the force unlock song section continue - info = Node.void('info') + info = Node.void("info") itemnode.add_child(info) - info.add_child(Node.u8('type', itemtype)) - info.add_child(Node.u32('id', item.id)) - info.add_child(Node.u32('param', item.data.get_int('param'))) + info.add_child(Node.u8("type", itemtype)) + info.add_child(Node.u32("id", item.id)) + info.add_child(Node.u32("param", item.data.get_int("param"))) - if game_config.get_bool('force_unlock_songs'): + if game_config.get_bool("force_unlock_songs"): ids: Dict[int, int] = {} songs = self.data.local.music.get_all_songs(self.game, self.version) for song in songs: if song.id not in ids: ids[song.id] = 0 - if song.data.get_int('difficulty') > 0: + if song.data.get_int("difficulty") > 0: ids[song.id] = ids[song.id] | (1 << song.chart) for itemid in ids: if ids[itemid] == 0: continue - info = Node.void('info') + info = Node.void("info") itemnode.add_child(info) - info.add_child(Node.u8('type', self.GAME_CATALOG_TYPE_SONG)) - info.add_child(Node.u32('id', itemid)) - info.add_child(Node.u32('param', ids[itemid])) + info.add_child(Node.u8("type", self.GAME_CATALOG_TYPE_SONG)) + info.add_child(Node.u32("id", itemid)) + info.add_child(Node.u32("param", ids[itemid])) # Appeal card unlocks - appealcard = Node.void('appealcard') + appealcard = Node.void("appealcard") game.add_child(appealcard) - if not game_config.get_bool('force_unlock_cards'): + if not game_config.get_bool("force_unlock_cards"): for card in achievements: - if card.type != 'appealcard': + if card.type != "appealcard": continue - info = Node.void('info') + info = Node.void("info") appealcard.add_child(info) - info.add_child(Node.u32('id', card.id)) - info.add_child(Node.u32('count', card.data.get_int('count'))) + info.add_child(Node.u32("id", card.id)) + info.add_child(Node.u32("count", card.data.get_int("count"))) else: catalog = self.data.local.game.get_items(self.game, self.version) for unlock in catalog: - if unlock.type != 'appealcard': + if unlock.type != "appealcard": continue - info = Node.void('info') + info = Node.void("info") appealcard.add_child(info) - info.add_child(Node.u32('id', unlock.id)) - info.add_child(Node.u32('count', 0)) + info.add_child(Node.u32("id", unlock.id)) + info.add_child(Node.u32("count", 0)) # Skill courses - skill = Node.void('skill') + skill = Node.void("skill") game.add_child(skill) - course_all = Node.void('course_all') + course_all = Node.void("course_all") skill.add_child(course_all) for course in achievements: - if course.type != 'course': + if course.type != "course": continue course_id = course.id % 100 season_id = int(course.id / 100) - info = Node.void('d') + info = Node.void("d") course_all.add_child(info) - info.add_child(Node.s16('crsid', course_id)) - info.add_child(Node.s16('ct', course.data.get_int('clear_type'))) - info.add_child(Node.s16('ar', course.data.get_int('achievement_rate'))) - info.add_child(Node.s32('ssnid', season_id)) + info.add_child(Node.s16("crsid", course_id)) + info.add_child(Node.s16("ct", course.data.get_int("clear_type"))) + info.add_child(Node.s16("ar", course.data.get_int("achievement_rate"))) + info.add_child(Node.s32("ssnid", season_id)) return game - def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: + def unformat_profile( + self, userid: UserID, request: Node, oldprofile: Profile + ) -> Profile: newprofile = oldprofile.clone() # Update blaster energy and in-game currencies - earned_gamecoin_packet = request.child_value('earned_gamecoin_packet') + earned_gamecoin_packet = request.child_value("earned_gamecoin_packet") if earned_gamecoin_packet is not None: - newprofile.replace_int('packet', newprofile.get_int('packet') + earned_gamecoin_packet) - earned_gamecoin_block = request.child_value('earned_gamecoin_block') + newprofile.replace_int( + "packet", newprofile.get_int("packet") + earned_gamecoin_packet + ) + earned_gamecoin_block = request.child_value("earned_gamecoin_block") if earned_gamecoin_block is not None: - newprofile.replace_int('block', newprofile.get_int('block') + earned_gamecoin_block) - earned_blaster_energy = request.child_value('earned_blaster_energy') + newprofile.replace_int( + "block", newprofile.get_int("block") + earned_gamecoin_block + ) + earned_blaster_energy = request.child_value("earned_blaster_energy") if earned_blaster_energy is not None: - newprofile.replace_int('blaster_energy', newprofile.get_int('blaster_energy') + earned_blaster_energy) + newprofile.replace_int( + "blaster_energy", + newprofile.get_int("blaster_energy") + earned_blaster_energy, + ) # Miscelaneous stuff - newprofile.replace_int('blaster_count', request.child_value('blaster_count')) - newprofile.replace_int('skill_name_id', request.child_value('skill_name_id')) - newprofile.replace_int_array('hidden_param', 20, request.child_value('hidden_param')) + newprofile.replace_int("blaster_count", request.child_value("blaster_count")) + newprofile.replace_int("skill_name_id", request.child_value("skill_name_id")) + newprofile.replace_int_array( + "hidden_param", 20, request.child_value("hidden_param") + ) # Update user's unlock status if we aren't force unlocked game_config = self.get_game_config() - if not game_config.get_bool('force_unlock_cards'): - for child in request.child('appealcard').children: - if child.name != 'info': + if not game_config.get_bool("force_unlock_cards"): + for child in request.child("appealcard").children: + if child.name != "info": continue - appealid = child.child_value('id') - count = child.child_value('count') + appealid = child.child_value("id") + count = child.child_value("count") self.data.local.user.put_achievement( self.game, self.version, userid, appealid, - 'appealcard', + "appealcard", { - 'count': count, + "count": count, }, ) - if not game_config.get_bool('force_unlock_songs'): - for child in request.child('item').children: - if child.name != 'info': + if not game_config.get_bool("force_unlock_songs"): + for child in request.child("item").children: + if child.name != "info": continue - item_id = child.child_value('id') - item_type = child.child_value('type') - param = child.child_value('param') + item_id = child.child_value("id") + item_type = child.child_value("type") + param = child.child_value("param") self.data.local.user.put_achievement( self.game, self.version, userid, item_id, - f'item_{item_type}', + f"item_{item_type}", { - 'param': param, + "param": param, }, ) # Grab last information. - lastdict = newprofile.get_dict('last') - lastdict.replace_int('headphone', request.child_value('headphone')) - lastdict.replace_int('hispeed', request.child_value('hispeed')) - lastdict.replace_int('appeal_id', request.child_value('appeal_id')) - lastdict.replace_int('comment_id', request.child_value('comment_id')) - lastdict.replace_int('music_id', request.child_value('music_id')) - lastdict.replace_int('music_type', request.child_value('music_type')) - lastdict.replace_int('sort_type', request.child_value('sort_type')) - lastdict.replace_int('narrow_down', request.child_value('narrow_down')) - lastdict.replace_int('gauge_option', request.child_value('gauge_option')) + lastdict = newprofile.get_dict("last") + lastdict.replace_int("headphone", request.child_value("headphone")) + lastdict.replace_int("hispeed", request.child_value("hispeed")) + lastdict.replace_int("appeal_id", request.child_value("appeal_id")) + lastdict.replace_int("comment_id", request.child_value("comment_id")) + lastdict.replace_int("music_id", request.child_value("music_id")) + lastdict.replace_int("music_type", request.child_value("music_type")) + lastdict.replace_int("sort_type", request.child_value("sort_type")) + lastdict.replace_int("narrow_down", request.child_value("narrow_down")) + lastdict.replace_int("gauge_option", request.child_value("gauge_option")) # Save back last information gleaned from results - newprofile.replace_dict('last', lastdict) + newprofile.replace_dict("last", lastdict) # Keep track of play statistics self.update_play_statistics(userid) diff --git a/bemani/client/base.py b/bemani/client/base.py index 9139c1b..effb4d6 100644 --- a/bemani/client/base.py +++ b/bemani/client/base.py @@ -19,10 +19,12 @@ class BaseClient: CARD_BAD_PIN: Final[int] = 116 CARD_NOT_ALLOWED: Final[int] = 110 - CORRECT_PASSWORD: Final[str] = '1234' - WRONG_PASSWORD: Final[str] = '4321' + CORRECT_PASSWORD: Final[str] = "1234" + WRONG_PASSWORD: Final[str] = "4321" - def __init__(self, proto: ClientProtocol, pcbid: str, config: Dict[str, Any]) -> None: + def __init__( + self, proto: ClientProtocol, pcbid: str, config: Dict[str, Any] + ) -> None: self.__proto = proto self.pcbid = pcbid self.config = config @@ -31,15 +33,15 @@ class BaseClient: return "E004" + random_hex_string(12, caps=True) def call_node(self) -> Node: - call = Node.void('call') - call.set_attribute('model', self.config['model']) - call.set_attribute('srcid', self.pcbid) - call.set_attribute('tag', random_hex_string(8)) + call = Node.void("call") + call.set_attribute("model", self.config["model"]) + call.set_attribute("srcid", self.pcbid) + call.set_attribute("tag", random_hex_string(8)) return call def exchange(self, path: str, tree: Node) -> Node: module = tree.children[0].name - method = tree.children[0].attribute('method') + method = tree.children[0].attribute("method") return self.__proto.exchange( f'{path}?model={self.config["model"]}&module={module}&method={method}', @@ -47,12 +49,12 @@ class BaseClient: ) def __assert_path(self, root: Node, path: str) -> bool: - parts = path.split('/') + parts = path.split("/") children = [root] node: Optional[Node] = None for part in parts: - if part[0] == '@': + if part[0] == "@": # Verify attribute, should be last part in chain so # assume its the first node if node is None: @@ -88,77 +90,79 @@ class BaseClient: """ if not self.__assert_path(root, path): - raise Exception(f'Path \'{path}\' not found in root node:\n{root}') + raise Exception(f"Path '{path}' not found in root node:\n{root}") - def verify_services_get(self, expected_services: List[str]=[], include_net: bool = False) -> None: + def verify_services_get( + self, expected_services: List[str] = [], include_net: bool = False + ) -> None: call = self.call_node() # Construct node - services = Node.void('services') + services = Node.void("services") call.add_child(services) - services.set_attribute('method', 'get') + services.set_attribute("method", "get") - if self.config['avs'] is not None: + if self.config["avs"] is not None: # Some older games don't include this info - info = Node.void('info') + info = Node.void("info") services.add_child(info) - info.add_child(Node.string('AVS2', self.config['avs'])) + info.add_child(Node.string("AVS2", self.config["avs"])) if include_net: - net = Node.void('net') + net = Node.void("net") services.add_child(net) - iface = Node.void('if') + iface = Node.void("if") net.add_child(iface) - iface.add_child(Node.u8('id', 0)) - iface.add_child(Node.bool('valid', True)) - iface.add_child(Node.u8('type', 1)) - iface.add_child(Node.u8_array('mac', [1, 2, 3, 4, 5, 6])) - iface.add_child(Node.ipv4('addr', '10.0.0.100')) - iface.add_child(Node.ipv4('bcast', '10.0.0.255')) - iface.add_child(Node.ipv4('netmask', '255.255.255.0')) - iface.add_child(Node.ipv4('gateway', '10.0.0.1')) - iface.add_child(Node.ipv4('dhcp', '10.0.0.1')) + iface.add_child(Node.u8("id", 0)) + iface.add_child(Node.bool("valid", True)) + iface.add_child(Node.u8("type", 1)) + iface.add_child(Node.u8_array("mac", [1, 2, 3, 4, 5, 6])) + iface.add_child(Node.ipv4("addr", "10.0.0.100")) + iface.add_child(Node.ipv4("bcast", "10.0.0.255")) + iface.add_child(Node.ipv4("netmask", "255.255.255.0")) + iface.add_child(Node.ipv4("gateway", "10.0.0.1")) + iface.add_child(Node.ipv4("dhcp", "10.0.0.1")) # Swap with server - resp = self.exchange('core/services', call) + resp = self.exchange("core/services", call) # Verify that response is correct self.assert_path(resp, "response/services") - items = resp.child('services').children + items = resp.child("services").children returned_services = [] for item in items: # Make sure it is an item with a url component - self.assert_path(item, 'item/@url') + self.assert_path(item, "item/@url") # Get list of services provided - returned_services.append(item.attribute('name')) + returned_services.append(item.attribute("name")) for service in expected_services: if service not in returned_services: - raise Exception(f'Service \'{service}\' expected but not returned') + raise Exception(f"Service '{service}' expected but not returned") def verify_pcbtracker_alive(self, ecflag: int = 1) -> bool: call = self.call_node() # Construct node - pcbtracker = Node.void('pcbtracker') + pcbtracker = Node.void("pcbtracker") call.add_child(pcbtracker) - pcbtracker.set_attribute('accountid', self.pcbid) - pcbtracker.set_attribute('ecflag', str(ecflag)) - pcbtracker.set_attribute('hardid', '01000027584F6D3A') - pcbtracker.set_attribute('method', 'alive') - pcbtracker.set_attribute('softid', '00010203040506070809') + pcbtracker.set_attribute("accountid", self.pcbid) + pcbtracker.set_attribute("ecflag", str(ecflag)) + pcbtracker.set_attribute("hardid", "01000027584F6D3A") + pcbtracker.set_attribute("method", "alive") + pcbtracker.set_attribute("softid", "00010203040506070809") # Swap with server - resp = self.exchange('core/pcbtracker', call) + resp = self.exchange("core/pcbtracker", call) # Verify that response is correct self.assert_path(resp, "response/pcbtracker/@ecenable") # Print out setting - enable = int(resp.child('pcbtracker').attribute('ecenable')) + enable = int(resp.child("pcbtracker").attribute("ecenable")) if enable != 0: return True return False @@ -167,12 +171,12 @@ class BaseClient: call = self.call_node() # Construct node - message = Node.void('message') + message = Node.void("message") call.add_child(message) - message.set_attribute('method', 'get') + message.set_attribute("method", "get") # Swap with server - resp = self.exchange('core/message', call) + resp = self.exchange("core/message", call) # Verify that response is correct self.assert_path(resp, "response/message/@status") @@ -181,13 +185,13 @@ class BaseClient: call = self.call_node() # Construct node - dlstatus = Node.void('dlstatus') + dlstatus = Node.void("dlstatus") call.add_child(dlstatus) - dlstatus.set_attribute('method', 'progress') - dlstatus.add_child(Node.s32('progress', 0)) + dlstatus.set_attribute("method", "progress") + dlstatus.add_child(Node.s32("progress", 0)) # Swap with server - resp = self.exchange('core/dlstatus', call) + resp = self.exchange("core/dlstatus", call) # Verify that response is correct self.assert_path(resp, "response/dlstatus/@status") @@ -196,28 +200,28 @@ class BaseClient: call = self.call_node() # Construct node - package = Node.void('package') + package = Node.void("package") call.add_child(package) - package.set_attribute('method', 'list') - package.set_attribute('pkgtype', 'all') + package.set_attribute("method", "list") + package.set_attribute("pkgtype", "all") # Swap with server - resp = self.exchange('core/package', call) + resp = self.exchange("core/package", call) # Verify that response is correct self.assert_path(resp, "response/package") - def verify_facility_get(self, encoding: str='SHIFT_JIS') -> str: + def verify_facility_get(self, encoding: str = "SHIFT_JIS") -> str: call = self.call_node() # Construct node - facility = Node.void('facility') + facility = Node.void("facility") call.add_child(facility) - facility.set_attribute('encoding', encoding) - facility.set_attribute('method', 'get') + facility.set_attribute("encoding", encoding) + facility.set_attribute("method", "get") # Swap with server - resp = self.exchange('core/facility', call) + resp = self.exchange("core/facility", call) # Verify that response is correct self.assert_path(resp, "response/facility/location/id") @@ -226,199 +230,220 @@ class BaseClient: self.assert_path(resp, "response/facility/public") self.assert_path(resp, "response/facility/share") - return resp.child_value('facility/location/id') + return resp.child_value("facility/location/id") def verify_pcbevent_put(self) -> None: call = self.call_node() # Construct node - pcbevent = Node.void('pcbevent') + pcbevent = Node.void("pcbevent") call.add_child(pcbevent) - pcbevent.set_attribute('method', 'put') - pcbevent.add_child(Node.time('time', int(time.time()))) - pcbevent.add_child(Node.u32('seq', 0)) + pcbevent.set_attribute("method", "put") + pcbevent.add_child(Node.time("time", int(time.time()))) + pcbevent.add_child(Node.u32("seq", 0)) - item = Node.void('item') + item = Node.void("item") pcbevent.add_child(item) - item.add_child(Node.string('name', 'boot')) - item.add_child(Node.s32('value', 1)) - item.add_child(Node.time('time', int(time.time()))) + item.add_child(Node.string("name", "boot")) + item.add_child(Node.s32("value", 1)) + item.add_child(Node.time("time", int(time.time()))) # Swap with server - resp = self.exchange('core/pcbevent', call) + resp = self.exchange("core/pcbevent", call) # Verify that response is correct self.assert_path(resp, "response/pcbevent") - def verify_cardmng_inquire(self, card_id: str, msg_type: str, paseli_enabled: bool) -> Optional[str]: + def verify_cardmng_inquire( + self, card_id: str, msg_type: str, paseli_enabled: bool + ) -> Optional[str]: call = self.call_node() # Construct node - cardmng = Node.void('cardmng') + cardmng = Node.void("cardmng") call.add_child(cardmng) - cardmng.set_attribute('cardid', card_id) - cardmng.set_attribute('cardtype', '1') - cardmng.set_attribute('method', 'inquire') - cardmng.set_attribute('update', '0') - if msg_type == 'new' and 'old_profile_model' in self.config: - cardmng.set_attribute('model', self.config['old_profile_model']) + cardmng.set_attribute("cardid", card_id) + cardmng.set_attribute("cardtype", "1") + cardmng.set_attribute("method", "inquire") + cardmng.set_attribute("update", "0") + if msg_type == "new" and "old_profile_model" in self.config: + cardmng.set_attribute("model", self.config["old_profile_model"]) # Swap with server - resp = self.exchange('core/cardmng', call) + resp = self.exchange("core/cardmng", call) - if msg_type == 'unregistered': + if msg_type == "unregistered": # Verify that response is correct self.assert_path(resp, "response/cardmng/@status") # Verify that we weren't found - status = int(resp.child('cardmng').attribute('status')) + status = int(resp.child("cardmng").attribute("status")) if status != self.CARD_NEW: - raise Exception(f'Card \'{card_id}\' returned invalid status \'{status}\'') + raise Exception(f"Card '{card_id}' returned invalid status '{status}'") # Nothing to return return None - elif msg_type == 'new': + elif msg_type == "new": # Verify that response is correct self.assert_path(resp, "response/cardmng/@refid") self.assert_path(resp, "response/cardmng/@binded") self.assert_path(resp, "response/cardmng/@newflag") self.assert_path(resp, "response/cardmng/@ecflag") - binded = int(resp.child('cardmng').attribute('binded')) - newflag = int(resp.child('cardmng').attribute('newflag')) - ecflag = int(resp.child('cardmng').attribute('ecflag')) + binded = int(resp.child("cardmng").attribute("binded")) + newflag = int(resp.child("cardmng").attribute("newflag")) + ecflag = int(resp.child("cardmng").attribute("ecflag")) if binded != 0: - raise Exception(f'Card \'{card_id}\' returned invalid binded value \'{binded}\'') + raise Exception( + f"Card '{card_id}' returned invalid binded value '{binded}'" + ) if newflag != 1: - raise Exception(f'Card \'{card_id}\' returned invalid newflag value \'{newflag}\'') + raise Exception( + f"Card '{card_id}' returned invalid newflag value '{newflag}'" + ) if ecflag != (1 if paseli_enabled else 0): - raise Exception(f'Card \'{card_id}\' returned invalid ecflag value \'{newflag}\'') + raise Exception( + f"Card '{card_id}' returned invalid ecflag value '{newflag}'" + ) # Return the refid - return resp.child('cardmng').attribute('refid') - elif msg_type == 'query': + return resp.child("cardmng").attribute("refid") + elif msg_type == "query": # Verify that response is correct self.assert_path(resp, "response/cardmng/@refid") self.assert_path(resp, "response/cardmng/@binded") self.assert_path(resp, "response/cardmng/@newflag") self.assert_path(resp, "response/cardmng/@ecflag") - binded = int(resp.child('cardmng').attribute('binded')) - newflag = int(resp.child('cardmng').attribute('newflag')) - ecflag = int(resp.child('cardmng').attribute('ecflag')) + binded = int(resp.child("cardmng").attribute("binded")) + newflag = int(resp.child("cardmng").attribute("newflag")) + ecflag = int(resp.child("cardmng").attribute("ecflag")) if binded != 1: - raise Exception(f'Card \'{card_id}\' returned invalid binded value \'{binded}\'') + raise Exception( + f"Card '{card_id}' returned invalid binded value '{binded}'" + ) if newflag != 0: - raise Exception(f'Card \'{card_id}\' returned invalid newflag value \'{newflag}\'') + raise Exception( + f"Card '{card_id}' returned invalid newflag value '{newflag}'" + ) if ecflag != (1 if paseli_enabled else 0): - raise Exception(f'Card \'{card_id}\' returned invalid ecflag value \'{newflag}\'') + raise Exception( + f"Card '{card_id}' returned invalid ecflag value '{newflag}'" + ) # Return the refid - return resp.child('cardmng').attribute('refid') + return resp.child("cardmng").attribute("refid") else: - raise Exception(f'Unrecognized message type \'{msg_type}\'') + raise Exception(f"Unrecognized message type '{msg_type}'") def verify_cardmng_getrefid(self, card_id: str) -> str: call = self.call_node() # Construct node - cardmng = Node.void('cardmng') + cardmng = Node.void("cardmng") call.add_child(cardmng) - cardmng.set_attribute('cardid', card_id) - cardmng.set_attribute('cardtype', '1') - cardmng.set_attribute('method', 'getrefid') - cardmng.set_attribute('newflag', '0') - cardmng.set_attribute('passwd', self.CORRECT_PASSWORD) + cardmng.set_attribute("cardid", card_id) + cardmng.set_attribute("cardtype", "1") + cardmng.set_attribute("method", "getrefid") + cardmng.set_attribute("newflag", "0") + cardmng.set_attribute("passwd", self.CORRECT_PASSWORD) # Swap with server - resp = self.exchange('core/cardmng', call) + resp = self.exchange("core/cardmng", call) # Verify that response is correct self.assert_path(resp, "response/cardmng/@refid") - return resp.child('cardmng').attribute('refid') + return resp.child("cardmng").attribute("refid") def verify_cardmng_authpass(self, ref_id: str, correct: bool) -> None: call = self.call_node() # Construct node - cardmng = Node.void('cardmng') + cardmng = Node.void("cardmng") call.add_child(cardmng) - cardmng.set_attribute('method', 'authpass') - cardmng.set_attribute('pass', self.CORRECT_PASSWORD if correct else self.CORRECT_PASSWORD[::-1]) - cardmng.set_attribute('refid', ref_id) + cardmng.set_attribute("method", "authpass") + cardmng.set_attribute( + "pass", self.CORRECT_PASSWORD if correct else self.CORRECT_PASSWORD[::-1] + ) + cardmng.set_attribute("refid", ref_id) # Swap with server - resp = self.exchange('core/cardmng', call) + resp = self.exchange("core/cardmng", call) # Verify that response is correct self.assert_path(resp, "response/cardmng/@status") - status = int(resp.child('cardmng').attribute('status')) + status = int(resp.child("cardmng").attribute("status")) if status != (self.CARD_OK if correct else self.CARD_BAD_PIN): - raise Exception(f'Ref ID \'{ref_id}\' returned invalid status \'{status}\'') + raise Exception(f"Ref ID '{ref_id}' returned invalid status '{status}'") def verify_eacoin_checkin(self, card_id: str) -> Tuple[str, int]: call = self.call_node() # Construct node - eacoin = Node.void('eacoin') + eacoin = Node.void("eacoin") call.add_child(eacoin) - eacoin.set_attribute('method', 'checkin') - eacoin.add_child(Node.string('cardtype', '1')) - eacoin.add_child(Node.string('cardid', card_id)) - eacoin.add_child(Node.string('passwd', self.CORRECT_PASSWORD)) - eacoin.add_child(Node.string('ectype', '1')) + eacoin.set_attribute("method", "checkin") + eacoin.add_child(Node.string("cardtype", "1")) + eacoin.add_child(Node.string("cardid", card_id)) + eacoin.add_child(Node.string("passwd", self.CORRECT_PASSWORD)) + eacoin.add_child(Node.string("ectype", "1")) # Swap with server - resp = self.exchange('core/eacoin', call) + resp = self.exchange("core/eacoin", call) # Verify that response is correct self.assert_path(resp, "response/eacoin/sessid") self.assert_path(resp, "response/eacoin/balance") - return (resp.child('eacoin').child_value('sessid'), resp.child('eacoin').child_value('balance')) + return ( + resp.child("eacoin").child_value("sessid"), + resp.child("eacoin").child_value("balance"), + ) def verify_eacoin_consume(self, sessid: str, balance: int, amount: int) -> None: call = self.call_node() # Construct node - eacoin = Node.void('eacoin') + eacoin = Node.void("eacoin") call.add_child(eacoin) - eacoin.set_attribute('method', 'consume') - eacoin.add_child(Node.string('sessid', sessid)) - eacoin.add_child(Node.s16('sequence', 0)) - eacoin.add_child(Node.s32('payment', amount)) - eacoin.add_child(Node.s32('service', 0)) - eacoin.add_child(Node.string('itemtype', '0')) - eacoin.add_child(Node.string('detail', '/eacoin/start_pt1')) + eacoin.set_attribute("method", "consume") + eacoin.add_child(Node.string("sessid", sessid)) + eacoin.add_child(Node.s16("sequence", 0)) + eacoin.add_child(Node.s32("payment", amount)) + eacoin.add_child(Node.s32("service", 0)) + eacoin.add_child(Node.string("itemtype", "0")) + eacoin.add_child(Node.string("detail", "/eacoin/start_pt1")) # Swap with server - resp = self.exchange('core/eacoin', call) + resp = self.exchange("core/eacoin", call) # Verify that response is correct self.assert_path(resp, "response/eacoin/balance") - newbalance = resp.child('eacoin').child_value('balance') + newbalance = resp.child("eacoin").child_value("balance") if balance - amount != newbalance: - raise Exception(f"Expected to get back balance {balance - amount} but got {newbalance}") + raise Exception( + f"Expected to get back balance {balance - amount} but got {newbalance}" + ) def verify_eacoin_checkout(self, session: str) -> None: call = self.call_node() # Construct node - eacoin = Node.void('eacoin') + eacoin = Node.void("eacoin") call.add_child(eacoin) - eacoin.set_attribute('method', 'checkout') - eacoin.add_child(Node.string('sessid', session)) + eacoin.set_attribute("method", "checkout") + eacoin.add_child(Node.string("sessid", session)) # Swap with server - resp = self.exchange('core/eacoin', call) + resp = self.exchange("core/eacoin", call) # Verify that response is correct self.assert_path(resp, "response/eacoin/@status") def verify(self, cardid: Optional[str]) -> None: - raise Exception('Override in subclass!') + raise Exception("Override in subclass!") diff --git a/bemani/client/bishi/bishi.py b/bemani/client/bishi/bishi.py index 1ac348a..021fbaa 100644 --- a/bemani/client/bishi/bishi.py +++ b/bemani/client/bishi/bishi.py @@ -7,30 +7,30 @@ from bemani.protocol import Node class TheStarBishiBashiClient(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_eventlog_write(self, location: str) -> None: call = self.call_node() # Construct node - eventlog = Node.void('eventlog') + eventlog = Node.void("eventlog") call.add_child(eventlog) - eventlog.set_attribute('method', 'write') - eventlog.add_child(Node.u32('retrycnt', 0)) - data = Node.void('data') + eventlog.set_attribute("method", "write") + eventlog.add_child(Node.u32("retrycnt", 0)) + data = Node.void("data") eventlog.add_child(data) - data.add_child(Node.string('eventid', 'S_PWRON')) - data.add_child(Node.s32('eventorder', 0)) - data.add_child(Node.u64('pcbtime', int(time.time() * 1000))) - data.add_child(Node.s64('gamesession', -1)) - data.add_child(Node.string('strdata1', '1.7.6')) - data.add_child(Node.string('strdata2', '')) - data.add_child(Node.s64('numdata1', 1)) - data.add_child(Node.s64('numdata2', 0)) - data.add_child(Node.string('locationid', location)) + data.add_child(Node.string("eventid", "S_PWRON")) + data.add_child(Node.s32("eventorder", 0)) + data.add_child(Node.u64("pcbtime", int(time.time() * 1000))) + data.add_child(Node.s64("gamesession", -1)) + data.add_child(Node.string("strdata1", "1.7.6")) + data.add_child(Node.string("strdata2", "")) + data.add_child(Node.s64("numdata1", 1)) + data.add_child(Node.s64("numdata2", 0)) + data.add_child(Node.string("locationid", location)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/eventlog/gamesession") @@ -42,17 +42,17 @@ class TheStarBishiBashiClient(BaseClient): call = self.call_node() # Construct node - system = Node.void('system') + system = Node.void("system") call.add_child(system) - system.set_attribute('method', 'getmaster') - data = Node.void('data') + system.set_attribute("method", "getmaster") + data = Node.void("data") system.add_child(data) - data.add_child(Node.string('gamekind', 'IBB')) - data.add_child(Node.string('datatype', 'S_SRVMSG')) - data.add_child(Node.string('datakey', 'INFO')) + data.add_child(Node.string("gamekind", "IBB")) + data.add_child(Node.string("datatype", "S_SRVMSG")) + data.add_child(Node.string("datakey", "INFO")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/system/result") @@ -62,116 +62,116 @@ class TheStarBishiBashiClient(BaseClient): # Set up profile write profiledata = [ - b'ffffffff', - b'IBBDAT00', - b'1', - b'0', - b'0', - b'0', - b'0', - b'0', - b'e474c1b', - b'0', - b'0', - b'ff', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'', - b'', - b'', - b'\x96\xa2\x90\xdd\x92\xe8', - b'\x8d\x81\x8d`', - b'', - b'', - b'', + b"ffffffff", + b"IBBDAT00", + b"1", + b"0", + b"0", + b"0", + b"0", + b"0", + b"e474c1b", + b"0", + b"0", + b"ff", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"", + b"", + b"", + b"\x96\xa2\x90\xdd\x92\xe8", + b"\x8d\x81\x8d`", + b"", + b"", + b"", ] - if msg_type == 'new': + if msg_type == "new": # New profile gets blank name, because we save over it at the end of the round. - profiledata[27] = b'' - elif msg_type == 'existing': + profiledata[27] = b"" + elif msg_type == "existing": # Exiting profile gets our hardcoded name saved. - profiledata[27] = self.NAME.encode('shift-jis') + profiledata[27] = self.NAME.encode("shift-jis") # Construct node - playerdata = Node.void('playerdata') + playerdata = Node.void("playerdata") call.add_child(playerdata) - playerdata.set_attribute('method', 'usergamedata_send') - playerdata.add_child(Node.u32('retrycnt', 0)) + playerdata.set_attribute("method", "usergamedata_send") + playerdata.add_child(Node.u32("retrycnt", 0)) - data = Node.void('data') + data = Node.void("data") playerdata.add_child(data) - data.add_child(Node.string('eaid', ref_id)) - data.add_child(Node.string('gamekind', 'IBB')) - data.add_child(Node.u32('datanum', 1)) - record = Node.void('record') + data.add_child(Node.string("eaid", ref_id)) + data.add_child(Node.string("gamekind", "IBB")) + data.add_child(Node.u32("datanum", 1)) + record = Node.void("record") data.add_child(record) - d = Node.string('d', base64.b64encode(b','.join(profiledata)).decode('ascii')) + d = Node.string("d", base64.b64encode(b",".join(profiledata)).decode("ascii")) record.add_child(d) - d.add_child(Node.string('bin1', '')) + d.add_child(Node.string("bin1", "")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/playerdata/result") def verify_usergamedata_recv(self, ref_id: str) -> str: call = self.call_node() # Construct node - playerdata = Node.void('playerdata') + playerdata = Node.void("playerdata") call.add_child(playerdata) - playerdata.set_attribute('method', 'usergamedata_recv') - data = Node.void('data') + playerdata.set_attribute("method", "usergamedata_recv") + data = Node.void("data") playerdata.add_child(data) - data.add_child(Node.string('eaid', ref_id)) - data.add_child(Node.string('gamekind', 'IBB')) - data.add_child(Node.u32('recv_num', 1)) - data.add_child(Node.string('recv_csv', 'IBBDAT00,3fffffffff')) + data.add_child(Node.string("eaid", ref_id)) + data.add_child(Node.string("gamekind", "IBB")) + data.add_child(Node.u32("recv_num", 1)) + data.add_child(Node.string("recv_csv", "IBBDAT00,3fffffffff")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/playerdata/result") self.assert_path(resp, "response/playerdata/player/record/d/bin1") self.assert_path(resp, "response/playerdata/player/record_num") # Grab binary data, parse out name - bindata = resp.child_value('playerdata/player/record/d') - profiledata = base64.b64decode(bindata).split(b',') + bindata = resp.child_value("playerdata/player/record/d") + profiledata = base64.b64decode(bindata).split(b",") # We lob off the first two values in returning profile, so the name is offset by two - return profiledata[25].decode('shift-jis') + return profiledata[25].decode("shift-jis") def verify(self, cardid: Optional[str]) -> None: # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -190,33 +190,43 @@ class TheStarBishiBashiClient(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Bishi doesn't read a new profile, it just writes out CSV for a blank one - self.verify_usergamedata_send(ref_id, msg_type='new') + self.verify_usergamedata_send(ref_id, msg_type="new") else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") if cardid is None: # Verify profile saving name = self.verify_usergamedata_recv(ref_id) - if name != '': - raise Exception('New profile has a name associated with it!') + if name != "": + raise Exception("New profile has a name associated with it!") - self.verify_usergamedata_send(ref_id, msg_type='existing') + self.verify_usergamedata_send(ref_id, msg_type="existing") name = self.verify_usergamedata_recv(ref_id) if name != self.NAME: - raise Exception('Existing profile has no name associated with it!') + raise Exception("Existing profile has no name associated with it!") else: print("Skipping score checks for existing card") diff --git a/bemani/client/common.py b/bemani/client/common.py index 7e24d12..3f81c37 100644 --- a/bemani/client/common.py +++ b/bemani/client/common.py @@ -1,9 +1,9 @@ import random -def random_hex_string(length: int, caps: bool=False) -> str: +def random_hex_string(length: int, caps: bool = False) -> str: if caps: - string = '0123456789ABCDEF' + string = "0123456789ABCDEF" else: - string = '0123456789abcdef' - return ''.join([random.choice(string) for x in range(length)]) + string = "0123456789abcdef" + return "".join([random.choice(string) for x in range(length)]) diff --git a/bemani/client/ddr/ddr2013.py b/bemani/client/ddr/ddr2013.py index f23d7d2..686a28f 100644 --- a/bemani/client/ddr/ddr2013.py +++ b/bemani/client/ddr/ddr2013.py @@ -7,53 +7,53 @@ from bemani.protocol import Node class DDR2013Client(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_game_shop(self, loc: str) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'shop') - game.set_attribute('area', '51') - game.set_attribute('boot', '34') - game.set_attribute('close', '0') - game.set_attribute('close_t', '0') - game.set_attribute('coin', '02.01.--.--.01.G') - game.set_attribute('diff', '3') - game.set_attribute('during', '1') - game.set_attribute('edit_cnt', '0') - game.set_attribute('edit_used', '1') - game.set_attribute('first', '1') - game.set_attribute('ip', '1.5.7.3') - game.set_attribute('is_paseli', '1') - game.set_attribute('loc', loc) - game.set_attribute('mac', '00:11:22:33:44:55') - game.set_attribute('machine', '2') - game.set_attribute('name', 'TEST') - game.set_attribute('pay', '0') - game.set_attribute('region', '.') - game.set_attribute('soft', self.config['model']) - game.set_attribute('softid', self.pcbid) - game.set_attribute('stage', '1') - game.set_attribute('time', '60') - game.set_attribute('type', '0') - game.set_attribute('ver', '2014032400') + game.set_attribute("method", "shop") + game.set_attribute("area", "51") + game.set_attribute("boot", "34") + game.set_attribute("close", "0") + game.set_attribute("close_t", "0") + game.set_attribute("coin", "02.01.--.--.01.G") + game.set_attribute("diff", "3") + game.set_attribute("during", "1") + game.set_attribute("edit_cnt", "0") + game.set_attribute("edit_used", "1") + game.set_attribute("first", "1") + game.set_attribute("ip", "1.5.7.3") + game.set_attribute("is_paseli", "1") + game.set_attribute("loc", loc) + game.set_attribute("mac", "00:11:22:33:44:55") + game.set_attribute("machine", "2") + game.set_attribute("name", "TEST") + game.set_attribute("pay", "0") + game.set_attribute("region", ".") + game.set_attribute("soft", self.config["model"]) + game.set_attribute("softid", self.pcbid) + game.set_attribute("stage", "1") + game.set_attribute("time", "60") + game.set_attribute("type", "0") + game.set_attribute("ver", "2014032400") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/@stop") def verify_game_common(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'common') - game.set_attribute('ver', '2014032400') + game.set_attribute("method", "common") + game.set_attribute("ver", "2014032400") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/flag/@id") @@ -64,159 +64,159 @@ class DDR2013Client(BaseClient): def verify_game_hiscore(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'hiscore') - game.set_attribute('ver', '2014032400') + game.set_attribute("method", "hiscore") + game.set_attribute("ver", "2014032400") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") - for child in resp.child('game').children: - self.assert_path(child, 'music/@reclink_num') - self.assert_path(child, 'music/type/@diff') - self.assert_path(child, 'music/type/name') - self.assert_path(child, 'music/type/score') - self.assert_path(child, 'music/type/area') - self.assert_path(child, 'music/type/rank') - self.assert_path(child, 'music/type/combo_type') - self.assert_path(child, 'music/type/code') + for child in resp.child("game").children: + self.assert_path(child, "music/@reclink_num") + self.assert_path(child, "music/type/@diff") + self.assert_path(child, "music/type/name") + self.assert_path(child, "music/type/score") + self.assert_path(child, "music/type/area") + self.assert_path(child, "music/type/rank") + self.assert_path(child, "music/type/combo_type") + self.assert_path(child, "music/type/code") def verify_game_area_hiscore(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'area_hiscore') - game.set_attribute('shop_area', '51') - game.set_attribute('ver', '2014032400') + game.set_attribute("method", "area_hiscore") + game.set_attribute("shop_area", "51") + game.set_attribute("ver", "2014032400") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") - for child in resp.child('game').children: - self.assert_path(child, 'music/@reclink_num') - self.assert_path(child, 'music/type/@diff') - self.assert_path(child, 'music/type/name') - self.assert_path(child, 'music/type/score') - self.assert_path(child, 'music/type/area') - self.assert_path(child, 'music/type/rank') - self.assert_path(child, 'music/type/combo_type') - self.assert_path(child, 'music/type/code') + for child in resp.child("game").children: + self.assert_path(child, "music/@reclink_num") + self.assert_path(child, "music/type/@diff") + self.assert_path(child, "music/type/name") + self.assert_path(child, "music/type/score") + self.assert_path(child, "music/type/area") + self.assert_path(child, "music/type/rank") + self.assert_path(child, "music/type/combo_type") + self.assert_path(child, "music/type/code") def verify_game_message(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'message') - game.set_attribute('ver', '2014032400') + game.set_attribute("method", "message") + game.set_attribute("ver", "2014032400") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") def verify_game_ranking(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'ranking') - game.set_attribute('max', '10') - game.set_attribute('ver', '2014032400') + game.set_attribute("method", "ranking") + game.set_attribute("max", "10") + game.set_attribute("ver", "2014032400") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") def verify_game_log(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'log') - game.set_attribute('type', '0') - game.set_attribute('soft', self.config['model']) - game.set_attribute('softid', self.pcbid) - game.set_attribute('ver', '2014032400') - game.set_attribute('boot', '34') - game.set_attribute('mac', '00:11:22:33:44:55') - clear = Node.void('clear') + game.set_attribute("method", "log") + game.set_attribute("type", "0") + game.set_attribute("soft", self.config["model"]) + game.set_attribute("softid", self.pcbid) + game.set_attribute("ver", "2014032400") + game.set_attribute("boot", "34") + game.set_attribute("mac", "00:11:22:33:44:55") + clear = Node.void("clear") game.add_child(clear) - clear.set_attribute('book', '0') - clear.set_attribute('edit', '0') - clear.set_attribute('rank', '0') - clear.set_attribute('set', '0') - auto = Node.void('auto') + clear.set_attribute("book", "0") + clear.set_attribute("edit", "0") + clear.set_attribute("rank", "0") + clear.set_attribute("set", "0") + auto = Node.void("auto") game.add_child(auto) - auto.set_attribute('book', '1') - auto.set_attribute('edit', '1') - auto.set_attribute('rank', '1') - auto.set_attribute('set', '1') + auto.set_attribute("book", "1") + auto.set_attribute("edit", "1") + auto.set_attribute("rank", "1") + auto.set_attribute("set", "1") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") def verify_game_tax_info(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'tax_info') - game.set_attribute('ver', '2014032400') + game.set_attribute("method", "tax_info") + game.set_attribute("ver", "2014032400") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/tax_info/@tax_phase") def verify_game_recorder(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'recorder') - game.set_attribute('assert_cnt', '0') - game.set_attribute('assert_info', '') - game.set_attribute('assert_path', '') - game.set_attribute('assert_time', '0') - game.set_attribute('boot_time', '1706151228') - game.set_attribute('cnt_demo', '1') - game.set_attribute('cnt_music', '1') - game.set_attribute('cnt_play', '0') - game.set_attribute('last_mid', '481') - game.set_attribute('last_seq', '36') - game.set_attribute('last_step', '0') - game.set_attribute('last_time', '1706151235') - game.set_attribute('softcode', self.config['model']) - game.set_attribute('temp_seq', '15') - game.set_attribute('temp_step', '8') - game.set_attribute('temp_time', '1706151234') - game.set_attribute('wd_restart', '0') + game.set_attribute("method", "recorder") + game.set_attribute("assert_cnt", "0") + game.set_attribute("assert_info", "") + game.set_attribute("assert_path", "") + game.set_attribute("assert_time", "0") + game.set_attribute("boot_time", "1706151228") + game.set_attribute("cnt_demo", "1") + game.set_attribute("cnt_music", "1") + game.set_attribute("cnt_play", "0") + game.set_attribute("last_mid", "481") + game.set_attribute("last_seq", "36") + game.set_attribute("last_step", "0") + game.set_attribute("last_time", "1706151235") + game.set_attribute("softcode", self.config["model"]) + game.set_attribute("temp_seq", "15") + game.set_attribute("temp_step", "8") + game.set_attribute("temp_time", "1706151234") + game.set_attribute("wd_restart", "0") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") def verify_game_lock(self, ref_id: str, play: int) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('refid', ref_id) - game.set_attribute('method', 'lock') - game.set_attribute('ver', '2014032400') - game.set_attribute('play', str(play)) + game.set_attribute("refid", ref_id) + game.set_attribute("method", "lock") + game.set_attribute("ver", "2014032400") + game.set_attribute("play", str(play)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/@now_login") @@ -225,34 +225,34 @@ class DDR2013Client(BaseClient): # Pad the name to 8 characters name = self.NAME[:8] while len(name) < 8: - name = name + ' ' + name = name + " " call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'new') - game.set_attribute('ver', '2014032400') - game.set_attribute('name', name) - game.set_attribute('area', '51') - game.set_attribute('old', '0') - game.set_attribute('refid', ref_id) + game.set_attribute("method", "new") + game.set_attribute("ver", "2014032400") + game.set_attribute("name", name) + game.set_attribute("area", "51") + game.set_attribute("old", "0") + game.set_attribute("refid", ref_id) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") def verify_game_load_daily(self, ref_id: str) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'load_daily') - game.set_attribute('ver', '2014032400') - game.set_attribute('refid', ref_id) + game.set_attribute("method", "load_daily") + game.set_attribute("ver", "2014032400") + game.set_attribute("refid", ref_id) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/game/daycount/@playcount") self.assert_path(resp, "response/game/dailycombo/@daily_combo") @@ -260,20 +260,20 @@ class DDR2013Client(BaseClient): def verify_game_load(self, ref_id: str, msg_type: str) -> Dict[str, Any]: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'load') - game.set_attribute('ver', '2014032400') - game.set_attribute('refid', ref_id) + game.set_attribute("method", "load") + game.set_attribute("ver", "2014032400") + game.set_attribute("refid", ref_id) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) - if msg_type == 'new': + if msg_type == "new": # Verify that response is correct self.assert_path(resp, "response/game/@none") return {} - if msg_type == 'existing': + if msg_type == "existing": # Verify existing profile and return info self.assert_path(resp, "response/game/seq") self.assert_path(resp, "response/game/code") @@ -340,109 +340,111 @@ class DDR2013Client(BaseClient): for i in range(55): self.assert_path(resp, f"response/game/play_area/@play_cnt{i}") - gr_s = resp.child('game/gr_s') - gr_d = resp.child('game/gr_d') + gr_s = resp.child("game/gr_s") + gr_d = resp.child("game/gr_d") return { - 'name': resp.child_value('game/name'), - 'ext_id': resp.child_value('game/code'), - 'single_plays': resp.child_value('game/cnt_s'), - 'double_plays': resp.child_value('game/cnt_d'), - 'groove_single': [ - int(gr_s.attribute('gr1')), - int(gr_s.attribute('gr2')), - int(gr_s.attribute('gr3')), - int(gr_s.attribute('gr4')), - int(gr_s.attribute('gr5')), + "name": resp.child_value("game/name"), + "ext_id": resp.child_value("game/code"), + "single_plays": resp.child_value("game/cnt_s"), + "double_plays": resp.child_value("game/cnt_d"), + "groove_single": [ + int(gr_s.attribute("gr1")), + int(gr_s.attribute("gr2")), + int(gr_s.attribute("gr3")), + int(gr_s.attribute("gr4")), + int(gr_s.attribute("gr5")), ], - 'groove_double': [ - int(gr_d.attribute('gr1')), - int(gr_d.attribute('gr2')), - int(gr_d.attribute('gr3')), - int(gr_d.attribute('gr4')), - int(gr_d.attribute('gr5')), + "groove_double": [ + int(gr_d.attribute("gr1")), + int(gr_d.attribute("gr2")), + int(gr_d.attribute("gr3")), + int(gr_d.attribute("gr4")), + int(gr_d.attribute("gr5")), ], } - raise Exception('Unknown load type!') + raise Exception("Unknown load type!") def verify_game_load_m(self, ref_id: str) -> Dict[int, Dict[int, Dict[str, Any]]]: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('ver', '2014032400') - game.set_attribute('all', '1') - game.set_attribute('refid', ref_id) - game.set_attribute('method', 'load_m') + game.set_attribute("ver", "2014032400") + game.set_attribute("all", "1") + game.set_attribute("refid", ref_id) + game.set_attribute("method", "load_m") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct scores: Dict[int, Dict[int, Dict[str, Any]]] = {} self.assert_path(resp, "response/game") - for child in resp.child('game').children: - self.assert_path(child, 'music/@reclink') - reclink = int(child.attribute('reclink')) + for child in resp.child("game").children: + self.assert_path(child, "music/@reclink") + reclink = int(child.attribute("reclink")) for typenode in child.children: - self.assert_path(typenode, 'type/@diff') - self.assert_path(typenode, 'type/score') - self.assert_path(typenode, 'type/count') - self.assert_path(typenode, 'type/rank') - self.assert_path(typenode, 'type/combo_type') - chart = int(typenode.attribute('diff')) + self.assert_path(typenode, "type/@diff") + self.assert_path(typenode, "type/score") + self.assert_path(typenode, "type/count") + self.assert_path(typenode, "type/rank") + self.assert_path(typenode, "type/combo_type") + chart = int(typenode.attribute("diff")) vals = { - 'score': typenode.child_value('score'), - 'count': typenode.child_value('count'), - 'rank': typenode.child_value('rank'), - 'halo': typenode.child_value('combo_type'), + "score": typenode.child_value("score"), + "count": typenode.child_value("count"), + "rank": typenode.child_value("rank"), + "halo": typenode.child_value("combo_type"), } if reclink not in scores: scores[reclink] = {} scores[reclink][chart] = vals return scores - def verify_game_save(self, ref_id: str, style: int, gauge: Optional[List[int]]=None) -> None: + def verify_game_save( + self, ref_id: str, style: int, gauge: Optional[List[int]] = None + ) -> None: gauge = gauge or [0, 0, 0, 0, 0] call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'save') - game.set_attribute('refid', ref_id) - game.set_attribute('ver', '2014032400') - game.set_attribute('shop_area', '51') - last = Node.void('last') + game.set_attribute("method", "save") + game.set_attribute("refid", ref_id) + game.set_attribute("ver", "2014032400") + game.set_attribute("shop_area", "51") + last = Node.void("last") game.add_child(last) - last.set_attribute('mode', '1') - last.set_attribute('style', str(style)) - gr = Node.void('gr') + last.set_attribute("mode", "1") + last.set_attribute("style", str(style)) + gr = Node.void("gr") game.add_child(gr) - gr.set_attribute('gr1', str(gauge[0])) - gr.set_attribute('gr2', str(gauge[1])) - gr.set_attribute('gr3', str(gauge[2])) - gr.set_attribute('gr4', str(gauge[3])) - gr.set_attribute('gr5', str(gauge[4])) + gr.set_attribute("gr1", str(gauge[0])) + gr.set_attribute("gr2", str(gauge[1])) + gr.set_attribute("gr3", str(gauge[2])) + gr.set_attribute("gr4", str(gauge[3])) + gr.set_attribute("gr5", str(gauge[4])) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") def verify_game_score(self, ref_id: str, songid: int, chart: int) -> List[int]: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'score') - game.set_attribute('mid', str(songid)) - game.set_attribute('refid', ref_id) - game.set_attribute('ver', '2014032400') - game.set_attribute('type', str(chart)) + game.set_attribute("method", "score") + game.set_attribute("mid", str(songid)) + game.set_attribute("refid", ref_id) + game.set_attribute("ver", "2014032400") + game.set_attribute("type", str(chart)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/@sc1") @@ -451,48 +453,50 @@ class DDR2013Client(BaseClient): self.assert_path(resp, "response/game/@sc4") self.assert_path(resp, "response/game/@sc5") return [ - int(resp.child('game').attribute('sc1')), - int(resp.child('game').attribute('sc2')), - int(resp.child('game').attribute('sc3')), - int(resp.child('game').attribute('sc4')), - int(resp.child('game').attribute('sc5')), + int(resp.child("game").attribute("sc1")), + int(resp.child("game").attribute("sc2")), + int(resp.child("game").attribute("sc3")), + int(resp.child("game").attribute("sc4")), + int(resp.child("game").attribute("sc5")), ] - def verify_game_save_m(self, ref_id: str, ext_id: str, score: Dict[str, Any]) -> None: + def verify_game_save_m( + self, ref_id: str, ext_id: str, score: Dict[str, Any] + ) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'save_m') - game.set_attribute('diff', '12345') - game.set_attribute('mtype', str(score['chart'])) - game.set_attribute('mid', str(score['id'])) - game.set_attribute('refid', ref_id) - game.set_attribute('ver', '2014032400') - data = Node.void('data') + game.set_attribute("method", "save_m") + game.set_attribute("diff", "12345") + game.set_attribute("mtype", str(score["chart"])) + game.set_attribute("mid", str(score["id"])) + game.set_attribute("refid", ref_id) + game.set_attribute("ver", "2014032400") + data = Node.void("data") game.add_child(data) - data.set_attribute('score', str(score['score'])) - data.set_attribute('rank', str(score['rank'])) - data.set_attribute('shop_area', '0') - data.set_attribute('playmode', '1') - data.set_attribute('combo', str(score['combo'])) - data.set_attribute('phase', '1') - data.set_attribute('full', '1' if score['halo'] >= 1 else '0') - data.set_attribute('great_fc', '1' if score['halo'] == 1 else '0') - data.set_attribute('good_fc', '1' if score['halo'] == 4 else '0') - data.set_attribute('perf_fc', '1' if score['halo'] == 2 else '0') - player = Node.void('player') + data.set_attribute("score", str(score["score"])) + data.set_attribute("rank", str(score["rank"])) + data.set_attribute("shop_area", "0") + data.set_attribute("playmode", "1") + data.set_attribute("combo", str(score["combo"])) + data.set_attribute("phase", "1") + data.set_attribute("full", "1" if score["halo"] >= 1 else "0") + data.set_attribute("great_fc", "1" if score["halo"] == 1 else "0") + data.set_attribute("good_fc", "1" if score["halo"] == 4 else "0") + data.set_attribute("perf_fc", "1" if score["halo"] == 2 else "0") + player = Node.void("player") game.add_child(player) - player.set_attribute('playcnt', '123') - player.set_attribute('code', ext_id) - option = Node.void('option') + player.set_attribute("playcnt", "123") + player.set_attribute("code", ext_id) + option = Node.void("option") game.add_child(option) - option.set_attribute('opt0', '6') - option.set_attribute('opt6', '1') - game.add_child(Node.u8_array('trace', [0] * 512)) - game.add_child(Node.u32('size', 512)) + option.set_attribute("opt0", "6") + option.set_attribute("opt6", "1") + game.add_child(Node.u8_array("trace", [0] * 512)) + game.add_child(Node.u32("size", 512)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") @@ -501,26 +505,26 @@ class DDR2013Client(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() self.verify_message_get() self.verify_package_list() - location = self.verify_facility_get('EUC_JP') + location = self.verify_facility_get("EUC_JP") self.verify_pcbevent_put() self.verify_game_shop(location) self.verify_game_common() @@ -540,24 +544,34 @@ class DDR2013Client(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Bishi doesn't read a new profile, it just writes out CSV for a blank one - self.verify_game_load(ref_id, msg_type='new') + self.verify_game_load(ref_id, msg_type="new") self.verify_game_new(ref_id) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Verify locking and unlocking profile ability self.verify_game_lock(ref_id, 1) @@ -565,52 +579,52 @@ class DDR2013Client(BaseClient): if cardid is None: # Verify empty profile - profile = self.verify_game_load(ref_id, msg_type='existing') - ext_id = str(profile['ext_id']) - if profile['name'] != self.NAME: - raise Exception('Profile has invalid name associated with it!') - if profile['single_plays'] != 0: - raise Exception('Profile has plays on single already!') - if profile['double_plays'] != 0: - raise Exception('Profile has plays on double already!') - if any([g != 0 for g in profile['groove_single']]): - raise Exception('Profile has single groove gauge values already!') - if any([g != 0 for g in profile['groove_double']]): - raise Exception('Profile has double groove gauge values already!') + profile = self.verify_game_load(ref_id, msg_type="existing") + ext_id = str(profile["ext_id"]) + if profile["name"] != self.NAME: + raise Exception("Profile has invalid name associated with it!") + if profile["single_plays"] != 0: + raise Exception("Profile has plays on single already!") + if profile["double_plays"] != 0: + raise Exception("Profile has plays on double already!") + if any([g != 0 for g in profile["groove_single"]]): + raise Exception("Profile has single groove gauge values already!") + if any([g != 0 for g in profile["groove_double"]]): + raise Exception("Profile has double groove gauge values already!") # Verify empty scores scores = self.verify_game_load_m(ref_id) if len(scores) > 0: - raise Exception('Scores exist on new profile!') + raise Exception("Scores exist on new profile!") self.verify_game_load_daily(ref_id) # Verify profile saving self.verify_game_save(ref_id, 0, [1, 2, 3, 4, 5]) - profile = self.verify_game_load(ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception('Profile has invalid name associated with it!') - if profile['single_plays'] != 1: - raise Exception('Profile has invalid plays on single!') - if profile['double_plays'] != 0: - raise Exception('Profile has invalid plays on double!') - if profile['groove_single'] != [1, 2, 3, 4, 5]: - raise Exception('Profile has invalid single groove gauge values!') - if any([g != 0 for g in profile['groove_double']]): - raise Exception('Profile has invalid double groove gauge values!') + profile = self.verify_game_load(ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception("Profile has invalid name associated with it!") + if profile["single_plays"] != 1: + raise Exception("Profile has invalid plays on single!") + if profile["double_plays"] != 0: + raise Exception("Profile has invalid plays on double!") + if profile["groove_single"] != [1, 2, 3, 4, 5]: + raise Exception("Profile has invalid single groove gauge values!") + if any([g != 0 for g in profile["groove_double"]]): + raise Exception("Profile has invalid double groove gauge values!") self.verify_game_save(ref_id, 1, [5, 4, 3, 2, 1]) - profile = self.verify_game_load(ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception('Profile has invalid name associated with it!') - if profile['single_plays'] != 1: - raise Exception('Profile has invalid plays on single!') - if profile['double_plays'] != 1: - raise Exception('Profile has invalid plays on double!') - if profile['groove_single'] != [1, 2, 3, 4, 5]: - raise Exception('Profile has invalid single groove gauge values!') - if profile['groove_double'] != [5, 4, 3, 2, 1]: - raise Exception('Profile has invalid double groove gauge values!') + profile = self.verify_game_load(ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception("Profile has invalid name associated with it!") + if profile["single_plays"] != 1: + raise Exception("Profile has invalid plays on single!") + if profile["double_plays"] != 1: + raise Exception("Profile has invalid plays on double!") + if profile["groove_single"] != [1, 2, 3, 4, 5]: + raise Exception("Profile has invalid single groove gauge values!") + if profile["groove_double"] != [5, 4, 3, 2, 1]: + raise Exception("Profile has invalid double groove gauge values!") # Now, write some scores and verify saving for phase in [1, 2]: @@ -618,104 +632,120 @@ class DDR2013Client(BaseClient): dummyscores = [ # An okay score on a chart { - 'id': 593, - 'chart': 3, - 'score': 800000, - 'combo': 123, - 'rank': 4, - 'halo': 1, + "id": 593, + "chart": 3, + "score": 800000, + "combo": 123, + "rank": 4, + "halo": 1, }, # A good score on an easier chart same song { - 'id': 593, - 'chart': 2, - 'score': 990000, - 'combo': 321, - 'rank': 2, - 'halo': 2, + "id": 593, + "chart": 2, + "score": 990000, + "combo": 321, + "rank": 2, + "halo": 2, }, # A perfect score { - 'id': 483, - 'chart': 3, - 'score': 1000000, - 'combo': 400, - 'rank': 1, - 'halo': 3, + "id": 483, + "chart": 3, + "score": 1000000, + "combo": 400, + "rank": 1, + "halo": 3, }, # A bad score { - 'id': 483, - 'chart': 2, - 'score': 100000, - 'combo': 5, - 'rank': 7, - 'halo': 0, + "id": 483, + "chart": 2, + "score": 100000, + "combo": 5, + "rank": 7, + "halo": 0, }, { - 'id': 483, - 'chart': 1, - 'score': 60000, - 'combo': 5, - 'rank': 6, - 'halo': 4, + "id": 483, + "chart": 1, + "score": 60000, + "combo": 5, + "rank": 6, + "halo": 4, }, ] if phase == 2: dummyscores = [ # A better score on a chart { - 'id': 593, - 'chart': 3, - 'score': 850000, - 'combo': 234, - 'rank': 3, - 'halo': 2, + "id": 593, + "chart": 3, + "score": 850000, + "combo": 234, + "rank": 3, + "halo": 2, }, # A worse score on another chart { - 'id': 593, - 'chart': 2, - 'score': 980000, - 'combo': 300, - 'rank': 3, - 'halo': 0, - 'expected_score': 990000, - 'expected_rank': 2, - 'expected_halo': 2, + "id": 593, + "chart": 2, + "score": 980000, + "combo": 300, + "rank": 3, + "halo": 0, + "expected_score": 990000, + "expected_rank": 2, + "expected_halo": 2, }, ] # Verify empty scores for starters if phase == 1: for score in dummyscores: - last_five = self.verify_game_score(ref_id, score['id'], score['chart']) + last_five = self.verify_game_score( + ref_id, score["id"], score["chart"] + ) if any([s != 0 for s in last_five]): - raise Exception('Score already found on song not played yet!') + raise Exception( + "Score already found on song not played yet!" + ) for score in dummyscores: self.verify_game_save_m(ref_id, ext_id, score) scores = self.verify_game_load_m(ref_id) for score in dummyscores: - data = scores.get(score['id'], {}).get(score['chart'], None) + data = scores.get(score["id"], {}).get(score["chart"], None) if data is None: - raise Exception(f'Expected to get score back for song {score["id"]} chart {score["chart"]}!') + raise Exception( + f'Expected to get score back for song {score["id"]} chart {score["chart"]}!' + ) # Verify the attributes of the score - expected_score = score.get('expected_score', score['score']) - expected_rank = score.get('expected_rank', score['rank']) - expected_halo = score.get('expected_halo', score['halo']) + expected_score = score.get("expected_score", score["score"]) + expected_rank = score.get("expected_rank", score["rank"]) + expected_halo = score.get("expected_halo", score["halo"]) - if data['score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["score"]}\'') - if data['rank'] != expected_rank: - raise Exception(f'Expected a rank of \'{expected_rank}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got rank \'{data["rank"]}\'') - if data['halo'] != expected_halo: - raise Exception(f'Expected a halo of \'{expected_halo}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got halo \'{data["halo"]}\'') + if data["score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["score"]}\'' + ) + if data["rank"] != expected_rank: + raise Exception( + f'Expected a rank of \'{expected_rank}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got rank \'{data["rank"]}\'' + ) + if data["halo"] != expected_halo: + raise Exception( + f'Expected a halo of \'{expected_halo}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got halo \'{data["halo"]}\'' + ) # Verify that the last score is our score - last_five = self.verify_game_score(ref_id, score['id'], score['chart']) - if last_five[0] != score['score']: - raise Exception(f'Invalid score returned for last five scores on song {score["id"]} chart {score["chart"]}!') + last_five = self.verify_game_score( + ref_id, score["id"], score["chart"] + ) + if last_five[0] != score["score"]: + raise Exception( + f'Invalid score returned for last five scores on song {score["id"]} chart {score["chart"]}!' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) diff --git a/bemani/client/ddr/ddr2014.py b/bemani/client/ddr/ddr2014.py index 1979069..acf2c43 100644 --- a/bemani/client/ddr/ddr2014.py +++ b/bemani/client/ddr/ddr2014.py @@ -7,54 +7,54 @@ from bemani.protocol import Node class DDR2014Client(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_game_shop(self, loc: str) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'shop') - game.set_attribute('area', '51') - game.set_attribute('boot', '34') - game.set_attribute('close', '0') - game.set_attribute('close_t', '0') - game.set_attribute('coin', '02.01.--.--.01.G') - game.set_attribute('diff', '3') - game.set_attribute('during', '1') - game.set_attribute('edit_cnt', '0') - game.set_attribute('edit_used', '1') - game.set_attribute('first', '1') - game.set_attribute('ip', '1.5.7.3') - game.set_attribute('is_freefirstplay', '1') - game.set_attribute('is_paseli', '1') - game.set_attribute('loc', loc) - game.set_attribute('mac', '00:11:22:33:44:55') - game.set_attribute('machine', '2') - game.set_attribute('name', 'TEST') - game.set_attribute('pay', '0') - game.set_attribute('region', '.') - game.set_attribute('soft', self.config['model']) - game.set_attribute('softid', self.pcbid) - game.set_attribute('stage', '1') - game.set_attribute('time', '60') - game.set_attribute('type', '0') - game.set_attribute('ver', '2014102700') + game.set_attribute("method", "shop") + game.set_attribute("area", "51") + game.set_attribute("boot", "34") + game.set_attribute("close", "0") + game.set_attribute("close_t", "0") + game.set_attribute("coin", "02.01.--.--.01.G") + game.set_attribute("diff", "3") + game.set_attribute("during", "1") + game.set_attribute("edit_cnt", "0") + game.set_attribute("edit_used", "1") + game.set_attribute("first", "1") + game.set_attribute("ip", "1.5.7.3") + game.set_attribute("is_freefirstplay", "1") + game.set_attribute("is_paseli", "1") + game.set_attribute("loc", loc) + game.set_attribute("mac", "00:11:22:33:44:55") + game.set_attribute("machine", "2") + game.set_attribute("name", "TEST") + game.set_attribute("pay", "0") + game.set_attribute("region", ".") + game.set_attribute("soft", self.config["model"]) + game.set_attribute("softid", self.pcbid) + game.set_attribute("stage", "1") + game.set_attribute("time", "60") + game.set_attribute("type", "0") + game.set_attribute("ver", "2014102700") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/@stop") def verify_game_common(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'common') - game.set_attribute('ver', '2014102700') + game.set_attribute("method", "common") + game.set_attribute("ver", "2014102700") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/flag/@id") @@ -67,159 +67,159 @@ class DDR2014Client(BaseClient): def verify_game_hiscore(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'hiscore') - game.set_attribute('ver', '2014102700') + game.set_attribute("method", "hiscore") + game.set_attribute("ver", "2014102700") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") - for child in resp.child('game').children: - self.assert_path(child, 'music/@reclink_num') - self.assert_path(child, 'music/type/@diff') - self.assert_path(child, 'music/type/name') - self.assert_path(child, 'music/type/score') - self.assert_path(child, 'music/type/area') - self.assert_path(child, 'music/type/rank') - self.assert_path(child, 'music/type/combo_type') - self.assert_path(child, 'music/type/code') + for child in resp.child("game").children: + self.assert_path(child, "music/@reclink_num") + self.assert_path(child, "music/type/@diff") + self.assert_path(child, "music/type/name") + self.assert_path(child, "music/type/score") + self.assert_path(child, "music/type/area") + self.assert_path(child, "music/type/rank") + self.assert_path(child, "music/type/combo_type") + self.assert_path(child, "music/type/code") def verify_game_area_hiscore(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'area_hiscore') - game.set_attribute('shop_area', '51') - game.set_attribute('ver', '2014102700') + game.set_attribute("method", "area_hiscore") + game.set_attribute("shop_area", "51") + game.set_attribute("ver", "2014102700") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") - for child in resp.child('game').children: - self.assert_path(child, 'music/@reclink_num') - self.assert_path(child, 'music/type/@diff') - self.assert_path(child, 'music/type/name') - self.assert_path(child, 'music/type/score') - self.assert_path(child, 'music/type/area') - self.assert_path(child, 'music/type/rank') - self.assert_path(child, 'music/type/combo_type') - self.assert_path(child, 'music/type/code') + for child in resp.child("game").children: + self.assert_path(child, "music/@reclink_num") + self.assert_path(child, "music/type/@diff") + self.assert_path(child, "music/type/name") + self.assert_path(child, "music/type/score") + self.assert_path(child, "music/type/area") + self.assert_path(child, "music/type/rank") + self.assert_path(child, "music/type/combo_type") + self.assert_path(child, "music/type/code") def verify_game_message(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'message') - game.set_attribute('ver', '2014102700') + game.set_attribute("method", "message") + game.set_attribute("ver", "2014102700") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") def verify_game_ranking(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'ranking') - game.set_attribute('max', '10') - game.set_attribute('ver', '2014102700') + game.set_attribute("method", "ranking") + game.set_attribute("max", "10") + game.set_attribute("ver", "2014102700") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") def verify_game_log(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'log') - game.set_attribute('type', '0') - game.set_attribute('soft', self.config['model']) - game.set_attribute('softid', self.pcbid) - game.set_attribute('ver', '2014102700') - game.set_attribute('boot', '34') - game.set_attribute('mac', '00:11:22:33:44:55') - clear = Node.void('clear') + game.set_attribute("method", "log") + game.set_attribute("type", "0") + game.set_attribute("soft", self.config["model"]) + game.set_attribute("softid", self.pcbid) + game.set_attribute("ver", "2014102700") + game.set_attribute("boot", "34") + game.set_attribute("mac", "00:11:22:33:44:55") + clear = Node.void("clear") game.add_child(clear) - clear.set_attribute('book', '0') - clear.set_attribute('edit', '0') - clear.set_attribute('rank', '0') - clear.set_attribute('set', '0') - auto = Node.void('auto') + clear.set_attribute("book", "0") + clear.set_attribute("edit", "0") + clear.set_attribute("rank", "0") + clear.set_attribute("set", "0") + auto = Node.void("auto") game.add_child(auto) - auto.set_attribute('book', '1') - auto.set_attribute('edit', '1') - auto.set_attribute('rank', '1') - auto.set_attribute('set', '1') + auto.set_attribute("book", "1") + auto.set_attribute("edit", "1") + auto.set_attribute("rank", "1") + auto.set_attribute("set", "1") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") def verify_game_tax_info(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'tax_info') - game.set_attribute('ver', '2014102700') + game.set_attribute("method", "tax_info") + game.set_attribute("ver", "2014102700") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/tax_info/@tax_phase") def verify_game_recorder(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'recorder') - game.set_attribute('assert_cnt', '0') - game.set_attribute('assert_info', '') - game.set_attribute('assert_path', '') - game.set_attribute('assert_time', '0') - game.set_attribute('boot_time', '1706151228') - game.set_attribute('cnt_demo', '1') - game.set_attribute('cnt_music', '1') - game.set_attribute('cnt_play', '0') - game.set_attribute('last_mid', '481') - game.set_attribute('last_seq', '36') - game.set_attribute('last_step', '0') - game.set_attribute('last_time', '1706151235') - game.set_attribute('softcode', self.config['model']) - game.set_attribute('temp_seq', '15') - game.set_attribute('temp_step', '8') - game.set_attribute('temp_time', '1706151234') - game.set_attribute('wd_restart', '0') + game.set_attribute("method", "recorder") + game.set_attribute("assert_cnt", "0") + game.set_attribute("assert_info", "") + game.set_attribute("assert_path", "") + game.set_attribute("assert_time", "0") + game.set_attribute("boot_time", "1706151228") + game.set_attribute("cnt_demo", "1") + game.set_attribute("cnt_music", "1") + game.set_attribute("cnt_play", "0") + game.set_attribute("last_mid", "481") + game.set_attribute("last_seq", "36") + game.set_attribute("last_step", "0") + game.set_attribute("last_time", "1706151235") + game.set_attribute("softcode", self.config["model"]) + game.set_attribute("temp_seq", "15") + game.set_attribute("temp_step", "8") + game.set_attribute("temp_time", "1706151234") + game.set_attribute("wd_restart", "0") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") def verify_game_lock(self, ref_id: str, play: int) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('refid', ref_id) - game.set_attribute('method', 'lock') - game.set_attribute('ver', '2014102700') - game.set_attribute('play', str(play)) + game.set_attribute("refid", ref_id) + game.set_attribute("method", "lock") + game.set_attribute("ver", "2014102700") + game.set_attribute("play", str(play)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/@now_login") @@ -228,34 +228,34 @@ class DDR2014Client(BaseClient): # Pad the name to 8 characters name = self.NAME[:8] while len(name) < 8: - name = name + ' ' + name = name + " " call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'new') - game.set_attribute('ver', '2014102700') - game.set_attribute('name', name) - game.set_attribute('area', '51') - game.set_attribute('old', '0') - game.set_attribute('refid', ref_id) + game.set_attribute("method", "new") + game.set_attribute("ver", "2014102700") + game.set_attribute("name", name) + game.set_attribute("area", "51") + game.set_attribute("old", "0") + game.set_attribute("refid", ref_id) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") def verify_game_load_daily(self, ref_id: str) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'load_daily') - game.set_attribute('ver', '2014102700') - game.set_attribute('refid', ref_id) + game.set_attribute("method", "load_daily") + game.set_attribute("ver", "2014102700") + game.set_attribute("refid", ref_id) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/game/daycount/@playcount") self.assert_path(resp, "response/game/dailycombo/@daily_combo") @@ -263,20 +263,20 @@ class DDR2014Client(BaseClient): def verify_game_load(self, ref_id: str, msg_type: str) -> Dict[str, Any]: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'load') - game.set_attribute('ver', '2014102700') - game.set_attribute('refid', ref_id) + game.set_attribute("method", "load") + game.set_attribute("ver", "2014102700") + game.set_attribute("refid", ref_id) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) - if msg_type == 'new': + if msg_type == "new": # Verify that response is correct self.assert_path(resp, "response/game/@none") return {} - if msg_type == 'existing': + if msg_type == "existing": # Verify existing profile and return info self.assert_path(resp, "response/game/seq") self.assert_path(resp, "response/game/code") @@ -345,63 +345,63 @@ class DDR2014Client(BaseClient): for i in range(55): self.assert_path(resp, f"response/game/play_area/@play_cnt{i}") - gr_s = resp.child('game/gr_s') - gr_d = resp.child('game/gr_d') + gr_s = resp.child("game/gr_s") + gr_d = resp.child("game/gr_d") return { - 'name': resp.child_value('game/name'), - 'ext_id': resp.child_value('game/code'), - 'single_plays': resp.child_value('game/cnt_s'), - 'double_plays': resp.child_value('game/cnt_d'), - 'groove_single': [ - int(gr_s.attribute('gr1')), - int(gr_s.attribute('gr2')), - int(gr_s.attribute('gr3')), - int(gr_s.attribute('gr4')), - int(gr_s.attribute('gr5')), + "name": resp.child_value("game/name"), + "ext_id": resp.child_value("game/code"), + "single_plays": resp.child_value("game/cnt_s"), + "double_plays": resp.child_value("game/cnt_d"), + "groove_single": [ + int(gr_s.attribute("gr1")), + int(gr_s.attribute("gr2")), + int(gr_s.attribute("gr3")), + int(gr_s.attribute("gr4")), + int(gr_s.attribute("gr5")), ], - 'groove_double': [ - int(gr_d.attribute('gr1')), - int(gr_d.attribute('gr2')), - int(gr_d.attribute('gr3')), - int(gr_d.attribute('gr4')), - int(gr_d.attribute('gr5')), + "groove_double": [ + int(gr_d.attribute("gr1")), + int(gr_d.attribute("gr2")), + int(gr_d.attribute("gr3")), + int(gr_d.attribute("gr4")), + int(gr_d.attribute("gr5")), ], } - raise Exception('Unknown load type!') + raise Exception("Unknown load type!") def verify_game_load_m(self, ref_id: str) -> Dict[int, Dict[int, Dict[str, Any]]]: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('ver', '2014102700') - game.set_attribute('all', '1') - game.set_attribute('refid', ref_id) - game.set_attribute('method', 'load_m') + game.set_attribute("ver", "2014102700") + game.set_attribute("all", "1") + game.set_attribute("refid", ref_id) + game.set_attribute("method", "load_m") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct scores: Dict[int, Dict[int, Dict[str, Any]]] = {} self.assert_path(resp, "response/game") - for child in resp.child('game').children: - self.assert_path(child, 'music/@reclink') - reclink = int(child.attribute('reclink')) + for child in resp.child("game").children: + self.assert_path(child, "music/@reclink") + reclink = int(child.attribute("reclink")) for typenode in child.children: - self.assert_path(typenode, 'type/@diff') - self.assert_path(typenode, 'type/score') - self.assert_path(typenode, 'type/count') - self.assert_path(typenode, 'type/rank') - self.assert_path(typenode, 'type/combo_type') - chart = int(typenode.attribute('diff')) + self.assert_path(typenode, "type/@diff") + self.assert_path(typenode, "type/score") + self.assert_path(typenode, "type/count") + self.assert_path(typenode, "type/rank") + self.assert_path(typenode, "type/combo_type") + chart = int(typenode.attribute("diff")) vals = { - 'score': typenode.child_value('score'), - 'count': typenode.child_value('count'), - 'rank': typenode.child_value('rank'), - 'halo': typenode.child_value('combo_type'), + "score": typenode.child_value("score"), + "count": typenode.child_value("count"), + "rank": typenode.child_value("rank"), + "halo": typenode.child_value("combo_type"), } if reclink not in scores: scores[reclink] = {} @@ -410,91 +410,95 @@ class DDR2014Client(BaseClient): def verify_game_load_edit(self, ref_id: str) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('ver', '2014102700') - game.set_attribute('pid', '0') - game.set_attribute('refid', ref_id) - game.set_attribute('method', 'load_edit') + game.set_attribute("ver", "2014102700") + game.set_attribute("pid", "0") + game.set_attribute("refid", ref_id) + game.set_attribute("method", "load_edit") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") - def verify_game_save(self, ref_id: str, style: int, gauge: Optional[List[int]]=None) -> None: + def verify_game_save( + self, ref_id: str, style: int, gauge: Optional[List[int]] = None + ) -> None: gauge = gauge or [0, 0, 0, 0, 0] call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'save') - game.set_attribute('refid', ref_id) - game.set_attribute('ver', '2014102700') - game.set_attribute('shop_area', '51') - last = Node.void('last') + game.set_attribute("method", "save") + game.set_attribute("refid", ref_id) + game.set_attribute("ver", "2014102700") + game.set_attribute("shop_area", "51") + last = Node.void("last") game.add_child(last) - last.set_attribute('mode', '1') - last.set_attribute('style', str(style)) - gr = Node.void('gr') + last.set_attribute("mode", "1") + last.set_attribute("style", str(style)) + gr = Node.void("gr") game.add_child(gr) - gr.set_attribute('gr1', str(gauge[0])) - gr.set_attribute('gr2', str(gauge[1])) - gr.set_attribute('gr3', str(gauge[2])) - gr.set_attribute('gr4', str(gauge[3])) - gr.set_attribute('gr5', str(gauge[4])) + gr.set_attribute("gr1", str(gauge[0])) + gr.set_attribute("gr2", str(gauge[1])) + gr.set_attribute("gr3", str(gauge[2])) + gr.set_attribute("gr4", str(gauge[3])) + gr.set_attribute("gr5", str(gauge[4])) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") - def verify_game_save_m(self, ref_id: str, ext_id: str, score: Dict[str, Any]) -> None: + def verify_game_save_m( + self, ref_id: str, ext_id: str, score: Dict[str, Any] + ) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'save_m') - game.set_attribute('diff', '12345') - game.set_attribute('mtype', str(score['chart'])) - game.set_attribute('mid', str(score['id'])) - game.set_attribute('refid', ref_id) - game.set_attribute('ver', '2014102700') - data = Node.void('data') + game.set_attribute("method", "save_m") + game.set_attribute("diff", "12345") + game.set_attribute("mtype", str(score["chart"])) + game.set_attribute("mid", str(score["id"])) + game.set_attribute("refid", ref_id) + game.set_attribute("ver", "2014102700") + data = Node.void("data") game.add_child(data) - data.set_attribute('score', str(score['score'])) - data.set_attribute('rank', str(score['rank'])) - data.set_attribute('shop_area', '0') - data.set_attribute('playmode', '1') - data.set_attribute('combo', str(score['combo'])) - data.set_attribute('phase', '1') - data.set_attribute('style', '0') - data.set_attribute('full', '1' if score['halo'] >= 1 else '0') - data.set_attribute('great_fc', '1' if score['halo'] == 1 else '0') - data.set_attribute('good_fc', '1' if score['halo'] == 4 else '0') - data.set_attribute('perf_fc', '1' if score['halo'] == 2 else '0') - gauge = Node.void('gauge') + data.set_attribute("score", str(score["score"])) + data.set_attribute("rank", str(score["rank"])) + data.set_attribute("shop_area", "0") + data.set_attribute("playmode", "1") + data.set_attribute("combo", str(score["combo"])) + data.set_attribute("phase", "1") + data.set_attribute("style", "0") + data.set_attribute("full", "1" if score["halo"] >= 1 else "0") + data.set_attribute("great_fc", "1" if score["halo"] == 1 else "0") + data.set_attribute("good_fc", "1" if score["halo"] == 4 else "0") + data.set_attribute("perf_fc", "1" if score["halo"] == 2 else "0") + gauge = Node.void("gauge") game.add_child(gauge) - gauge.set_attribute('life8', '0') - gauge.set_attribute('assist', '0') - gauge.set_attribute('risky', '0') - gauge.set_attribute('life4', '0') - gauge.set_attribute('hard', '0') - player = Node.void('player') + gauge.set_attribute("life8", "0") + gauge.set_attribute("assist", "0") + gauge.set_attribute("risky", "0") + gauge.set_attribute("life4", "0") + gauge.set_attribute("hard", "0") + player = Node.void("player") game.add_child(player) - player.set_attribute('playcnt', '123') - player.set_attribute('code', ext_id) - option = Node.void('option_02') + player.set_attribute("playcnt", "123") + player.set_attribute("code", ext_id) + option = Node.void("option_02") game.add_child(option) - option.set_attribute('opt02_0', '6') - option.set_attribute('opt02_6', '1') - option.set_attribute('opt02_13', '2') - game.add_child(Node.u8_array('trace', [0] * 512)) - game.add_child(Node.u32('size', 512)) + option.set_attribute("opt02_0", "6") + option.set_attribute("opt02_6", "1") + option.set_attribute("opt02_13", "2") + game.add_child(Node.u8_array("trace", [0] * 512)) + game.add_child(Node.u32("size", 512)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") @@ -503,26 +507,26 @@ class DDR2014Client(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() self.verify_message_get() self.verify_package_list() - location = self.verify_facility_get('EUC_JP') + location = self.verify_facility_get("EUC_JP") self.verify_pcbevent_put() self.verify_game_recorder() self.verify_game_tax_info() @@ -542,24 +546,34 @@ class DDR2014Client(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Bishi doesn't read a new profile, it just writes out CSV for a blank one - self.verify_game_load(ref_id, msg_type='new') + self.verify_game_load(ref_id, msg_type="new") self.verify_game_new(ref_id) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Verify locking and unlocking profile ability self.verify_game_lock(ref_id, 1) @@ -567,53 +581,53 @@ class DDR2014Client(BaseClient): if cardid is None: # Verify empty profile - profile = self.verify_game_load(ref_id, msg_type='existing') - ext_id = str(profile['ext_id']) - if profile['name'] != self.NAME: - raise Exception('Profile has invalid name associated with it!') - if profile['single_plays'] != 0: - raise Exception('Profile has plays on single already!') - if profile['double_plays'] != 0: - raise Exception('Profile has plays on double already!') - if any([g != 0 for g in profile['groove_single']]): - raise Exception('Profile has single groove gauge values already!') - if any([g != 0 for g in profile['groove_double']]): - raise Exception('Profile has double groove gauge values already!') + profile = self.verify_game_load(ref_id, msg_type="existing") + ext_id = str(profile["ext_id"]) + if profile["name"] != self.NAME: + raise Exception("Profile has invalid name associated with it!") + if profile["single_plays"] != 0: + raise Exception("Profile has plays on single already!") + if profile["double_plays"] != 0: + raise Exception("Profile has plays on double already!") + if any([g != 0 for g in profile["groove_single"]]): + raise Exception("Profile has single groove gauge values already!") + if any([g != 0 for g in profile["groove_double"]]): + raise Exception("Profile has double groove gauge values already!") # Verify empty scores scores = self.verify_game_load_m(ref_id) if len(scores) > 0: - raise Exception('Scores exist on new profile!') + raise Exception("Scores exist on new profile!") self.verify_game_load_edit(ref_id) self.verify_game_load_daily(ref_id) # Verify profile saving self.verify_game_save(ref_id, 0, [1, 2, 3, 4, 5]) - profile = self.verify_game_load(ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception('Profile has invalid name associated with it!') - if profile['single_plays'] != 1: - raise Exception('Profile has invalid plays on single!') - if profile['double_plays'] != 0: - raise Exception('Profile has invalid plays on double!') - if profile['groove_single'] != [1, 2, 3, 4, 5]: - raise Exception('Profile has invalid single groove gauge values!') - if any([g != 0 for g in profile['groove_double']]): - raise Exception('Profile has invalid double groove gauge values!') + profile = self.verify_game_load(ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception("Profile has invalid name associated with it!") + if profile["single_plays"] != 1: + raise Exception("Profile has invalid plays on single!") + if profile["double_plays"] != 0: + raise Exception("Profile has invalid plays on double!") + if profile["groove_single"] != [1, 2, 3, 4, 5]: + raise Exception("Profile has invalid single groove gauge values!") + if any([g != 0 for g in profile["groove_double"]]): + raise Exception("Profile has invalid double groove gauge values!") self.verify_game_save(ref_id, 1, [5, 4, 3, 2, 1]) - profile = self.verify_game_load(ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception('Profile has invalid name associated with it!') - if profile['single_plays'] != 1: - raise Exception('Profile has invalid plays on single!') - if profile['double_plays'] != 1: - raise Exception('Profile has invalid plays on double!') - if profile['groove_single'] != [1, 2, 3, 4, 5]: - raise Exception('Profile has invalid single groove gauge values!') - if profile['groove_double'] != [5, 4, 3, 2, 1]: - raise Exception('Profile has invalid double groove gauge values!') + profile = self.verify_game_load(ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception("Profile has invalid name associated with it!") + if profile["single_plays"] != 1: + raise Exception("Profile has invalid plays on single!") + if profile["double_plays"] != 1: + raise Exception("Profile has invalid plays on double!") + if profile["groove_single"] != [1, 2, 3, 4, 5]: + raise Exception("Profile has invalid single groove gauge values!") + if profile["groove_double"] != [5, 4, 3, 2, 1]: + raise Exception("Profile has invalid double groove gauge values!") # Now, write some scores and verify saving for phase in [1, 2]: @@ -621,71 +635,71 @@ class DDR2014Client(BaseClient): dummyscores = [ # An okay score on a chart { - 'id': 593, - 'chart': 3, - 'score': 800000, - 'combo': 123, - 'rank': 4, - 'halo': 1, + "id": 593, + "chart": 3, + "score": 800000, + "combo": 123, + "rank": 4, + "halo": 1, }, # A good score on an easier chart same song { - 'id': 593, - 'chart': 2, - 'score': 990000, - 'combo': 321, - 'rank': 2, - 'halo': 2, + "id": 593, + "chart": 2, + "score": 990000, + "combo": 321, + "rank": 2, + "halo": 2, }, # A perfect score { - 'id': 483, - 'chart': 3, - 'score': 1000000, - 'combo': 400, - 'rank': 1, - 'halo': 3, + "id": 483, + "chart": 3, + "score": 1000000, + "combo": 400, + "rank": 1, + "halo": 3, }, # A bad score { - 'id': 483, - 'chart': 2, - 'score': 100000, - 'combo': 5, - 'rank': 7, - 'halo': 0, + "id": 483, + "chart": 2, + "score": 100000, + "combo": 5, + "rank": 7, + "halo": 0, }, { - 'id': 483, - 'chart': 1, - 'score': 60000, - 'combo': 5, - 'rank': 6, - 'halo': 4, + "id": 483, + "chart": 1, + "score": 60000, + "combo": 5, + "rank": 6, + "halo": 4, }, ] if phase == 2: dummyscores = [ # A better score on a chart { - 'id': 593, - 'chart': 3, - 'score': 850000, - 'combo': 234, - 'rank': 3, - 'halo': 2, + "id": 593, + "chart": 3, + "score": 850000, + "combo": 234, + "rank": 3, + "halo": 2, }, # A worse score on another chart { - 'id': 593, - 'chart': 2, - 'score': 980000, - 'combo': 300, - 'rank': 3, - 'halo': 0, - 'expected_score': 990000, - 'expected_rank': 2, - 'expected_halo': 2, + "id": 593, + "chart": 2, + "score": 980000, + "combo": 300, + "rank": 3, + "halo": 0, + "expected_score": 990000, + "expected_rank": 2, + "expected_halo": 2, }, ] @@ -693,21 +707,29 @@ class DDR2014Client(BaseClient): self.verify_game_save_m(ref_id, ext_id, score) scores = self.verify_game_load_m(ref_id) for score in dummyscores: - data = scores.get(score['id'], {}).get(score['chart'], None) + data = scores.get(score["id"], {}).get(score["chart"], None) if data is None: - raise Exception(f'Expected to get score back for song {score["id"]} chart {score["chart"]}!') + raise Exception( + f'Expected to get score back for song {score["id"]} chart {score["chart"]}!' + ) # Verify the attributes of the score - expected_score = score.get('expected_score', score['score']) - expected_rank = score.get('expected_rank', score['rank']) - expected_halo = score.get('expected_halo', score['halo']) + expected_score = score.get("expected_score", score["score"]) + expected_rank = score.get("expected_rank", score["rank"]) + expected_halo = score.get("expected_halo", score["halo"]) - if data['score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["score"]}\'') - if data['rank'] != expected_rank: - raise Exception(f'Expected a rank of \'{expected_rank}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got rank \'{data["rank"]}\'') - if data['halo'] != expected_halo: - raise Exception(f'Expected a halo of \'{expected_halo}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got halo \'{data["halo"]}\'') + if data["score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["score"]}\'' + ) + if data["rank"] != expected_rank: + raise Exception( + f'Expected a rank of \'{expected_rank}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got rank \'{data["rank"]}\'' + ) + if data["halo"] != expected_halo: + raise Exception( + f'Expected a halo of \'{expected_halo}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got halo \'{data["halo"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) diff --git a/bemani/client/ddr/ddrace.py b/bemani/client/ddr/ddrace.py index 369ea50..576970c 100644 --- a/bemani/client/ddr/ddrace.py +++ b/bemani/client/ddr/ddrace.py @@ -9,34 +9,34 @@ from bemani.protocol import Node def b64str(string: str) -> str: - return base64.b64encode(string.encode()).decode('ascii') + return base64.b64encode(string.encode()).decode("ascii") class DDRAceClient(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_eventlog_write(self, location: str) -> None: call = self.call_node() # Construct node - eventlog = Node.void('eventlog') + eventlog = Node.void("eventlog") call.add_child(eventlog) - eventlog.set_attribute('method', 'write') - eventlog.add_child(Node.u32('retrycnt', 0)) - data = Node.void('data') + eventlog.set_attribute("method", "write") + eventlog.add_child(Node.u32("retrycnt", 0)) + data = Node.void("data") eventlog.add_child(data) - data.add_child(Node.string('eventid', 'S_PWRON')) - data.add_child(Node.s32('eventorder', 0)) - data.add_child(Node.u64('pcbtime', int(time.time() * 1000))) - data.add_child(Node.s64('gamesession', -1)) - data.add_child(Node.string('strdata1', b64str('2.4.0'))) - data.add_child(Node.string('strdata2', '')) - data.add_child(Node.s64('numdata1', 1)) - data.add_child(Node.s64('numdata2', 0)) - data.add_child(Node.string('locationid', location)) + data.add_child(Node.string("eventid", "S_PWRON")) + data.add_child(Node.s32("eventorder", 0)) + data.add_child(Node.u64("pcbtime", int(time.time() * 1000))) + data.add_child(Node.s64("gamesession", -1)) + data.add_child(Node.string("strdata1", b64str("2.4.0"))) + data.add_child(Node.string("strdata2", "")) + data.add_child(Node.s64("numdata1", 1)) + data.add_child(Node.s64("numdata2", 0)) + data.add_child(Node.string("locationid", location)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/eventlog/gamesession") @@ -48,19 +48,19 @@ class DDRAceClient(BaseClient): call = self.call_node() # Construct node - system = Node.void('system') + system = Node.void("system") call.add_child(system) - system.set_attribute('method', 'convcardnumber') - info = Node.void('info') + system.set_attribute("method", "convcardnumber") + info = Node.void("info") system.add_child(info) - info.add_child(Node.s32('version', 1)) - data = Node.void('data') + info.add_child(Node.s32("version", 1)) + data = Node.void("data") system.add_child(data) - data.add_child(Node.string('card_id', cardno)) - data.add_child(Node.s32('card_type', 1)) + data.add_child(Node.string("card_id", cardno)) + data.add_child(Node.s32("card_type", 1)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/system/data/card_number") @@ -70,26 +70,26 @@ class DDRAceClient(BaseClient): call = self.call_node() # Construct node - playerdata = Node.void('playerdata') + playerdata = Node.void("playerdata") call.add_child(playerdata) - playerdata.set_attribute('method', 'usergamedata_advanced') - playerdata.add_child(Node.u32('retrycnt', 0)) - info = Node.void('info') + playerdata.set_attribute("method", "usergamedata_advanced") + playerdata.add_child(Node.u32("retrycnt", 0)) + info = Node.void("info") playerdata.add_child(info) - info.add_child(Node.s32('version', 1)) - data = Node.void('data') + info.add_child(Node.s32("version", 1)) + data = Node.void("data") playerdata.add_child(data) - data.add_child(Node.string('mode', 'usernew')) - data.add_child(Node.string('shoparea', '.')) - data.add_child(Node.s64('gamesession', 123456)) - data.add_child(Node.string('refid', refid)) - data.add_child(Node.string('dataid', refid)) - data.add_child(Node.string('gamekind', 'MDX')) - data.add_child(Node.string('pcbid', self.pcbid)) - data.add_child(Node.void('record')) + data.add_child(Node.string("mode", "usernew")) + data.add_child(Node.string("shoparea", ".")) + data.add_child(Node.s64("gamesession", 123456)) + data.add_child(Node.string("refid", refid)) + data.add_child(Node.string("dataid", refid)) + data.add_child(Node.string("gamekind", "MDX")) + data.add_child(Node.string("pcbid", self.pcbid)) + data.add_child(Node.void("record")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/playerdata/seq") @@ -97,32 +97,34 @@ class DDRAceClient(BaseClient): self.assert_path(resp, "response/playerdata/shoparea") self.assert_path(resp, "response/playerdata/result") - return resp.child_value('playerdata/code') + return resp.child_value("playerdata/code") - def verify_playerdata_usergamedata_advanced_ghostload(self, refid: str, ghostid: int) -> Dict[str, Any]: + def verify_playerdata_usergamedata_advanced_ghostload( + self, refid: str, ghostid: int + ) -> Dict[str, Any]: call = self.call_node() # Construct node - playerdata = Node.void('playerdata') + playerdata = Node.void("playerdata") call.add_child(playerdata) - playerdata.set_attribute('method', 'usergamedata_advanced') - playerdata.add_child(Node.u32('retrycnt', 0)) - info = Node.void('info') + playerdata.set_attribute("method", "usergamedata_advanced") + playerdata.add_child(Node.u32("retrycnt", 0)) + info = Node.void("info") playerdata.add_child(info) - info.add_child(Node.s32('version', 1)) - data = Node.void('data') + info.add_child(Node.s32("version", 1)) + data = Node.void("data") playerdata.add_child(data) - data.add_child(Node.string('mode', 'ghostload')) - data.add_child(Node.s32('ghostid', ghostid)) - data.add_child(Node.s64('gamesession', 123456)) - data.add_child(Node.string('refid', refid)) - data.add_child(Node.string('dataid', refid)) - data.add_child(Node.string('gamekind', 'MDX')) - data.add_child(Node.string('pcbid', self.pcbid)) - data.add_child(Node.void('record')) + data.add_child(Node.string("mode", "ghostload")) + data.add_child(Node.s32("ghostid", ghostid)) + data.add_child(Node.s64("gamesession", 123456)) + data.add_child(Node.string("refid", refid)) + data.add_child(Node.string("dataid", refid)) + data.add_child(Node.string("gamekind", "MDX")) + data.add_child(Node.string("pcbid", self.pcbid)) + data.add_child(Node.void("record")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/playerdata/ghostdata/code") @@ -132,40 +134,42 @@ class DDRAceClient(BaseClient): self.assert_path(resp, "response/playerdata/ghostdata/ghost") return { - 'extid': resp.child_value('playerdata/ghostdata/code'), - 'id': resp.child_value('playerdata/ghostdata/mcode'), - 'chart': resp.child_value('playerdata/ghostdata/notetype'), - 'ghost': resp.child_value('playerdata/ghostdata/ghost'), + "extid": resp.child_value("playerdata/ghostdata/code"), + "id": resp.child_value("playerdata/ghostdata/mcode"), + "chart": resp.child_value("playerdata/ghostdata/notetype"), + "ghost": resp.child_value("playerdata/ghostdata/ghost"), } - def verify_playerdata_usergamedata_advanced_rivalload(self, refid: str, loadflag: int) -> None: + def verify_playerdata_usergamedata_advanced_rivalload( + self, refid: str, loadflag: int + ) -> None: call = self.call_node() # Construct node - playerdata = Node.void('playerdata') + playerdata = Node.void("playerdata") call.add_child(playerdata) - playerdata.set_attribute('method', 'usergamedata_advanced') - playerdata.add_child(Node.u32('retrycnt', 0)) - info = Node.void('info') + playerdata.set_attribute("method", "usergamedata_advanced") + playerdata.add_child(Node.u32("retrycnt", 0)) + info = Node.void("info") playerdata.add_child(info) - info.add_child(Node.s32('version', 1)) - data = Node.void('data') + info.add_child(Node.s32("version", 1)) + data = Node.void("data") playerdata.add_child(data) - data.add_child(Node.string('mode', 'rivalload')) - data.add_child(Node.u64('targettime', Time.now() * 1000)) - data.add_child(Node.string('shoparea', '.')) - data.add_child(Node.bool('isdouble', False)) - data.add_child(Node.s32('loadflag', loadflag)) - data.add_child(Node.s32('ddrcode', 0)) - data.add_child(Node.s64('gamesession', 123456)) - data.add_child(Node.string('refid', refid)) - data.add_child(Node.string('dataid', refid)) - data.add_child(Node.string('gamekind', 'MDX')) - data.add_child(Node.string('pcbid', self.pcbid)) - data.add_child(Node.void('record')) + data.add_child(Node.string("mode", "rivalload")) + data.add_child(Node.u64("targettime", Time.now() * 1000)) + data.add_child(Node.string("shoparea", ".")) + data.add_child(Node.bool("isdouble", False)) + data.add_child(Node.s32("loadflag", loadflag)) + data.add_child(Node.s32("ddrcode", 0)) + data.add_child(Node.s64("gamesession", 123456)) + data.add_child(Node.string("refid", refid)) + data.add_child(Node.string("dataid", refid)) + data.add_child(Node.string("gamekind", "MDX")) + data.add_child(Node.string("pcbid", self.pcbid)) + data.add_child(Node.void("record")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/playerdata/data/recordtype") @@ -184,487 +188,507 @@ class DDRAceClient(BaseClient): self.assert_path(resp, "response/playerdata/data/record/score") self.assert_path(resp, "response/playerdata/data/record/ghostid") - if resp.child_value('playerdata/data/recordtype') != loadflag: - raise Exception('Invalid record type returned!') + if resp.child_value("playerdata/data/recordtype") != loadflag: + raise Exception("Invalid record type returned!") - def verify_playerdata_usergamedata_advanced_userload(self, refid: str) -> Tuple[bool, List[Dict[str, Any]]]: + def verify_playerdata_usergamedata_advanced_userload( + self, refid: str + ) -> Tuple[bool, List[Dict[str, Any]]]: call = self.call_node() # Construct node - playerdata = Node.void('playerdata') + playerdata = Node.void("playerdata") call.add_child(playerdata) - playerdata.set_attribute('method', 'usergamedata_advanced') - playerdata.add_child(Node.u32('retrycnt', 0)) - info = Node.void('info') + playerdata.set_attribute("method", "usergamedata_advanced") + playerdata.add_child(Node.u32("retrycnt", 0)) + info = Node.void("info") playerdata.add_child(info) - info.add_child(Node.s32('version', 1)) - data = Node.void('data') + info.add_child(Node.s32("version", 1)) + data = Node.void("data") playerdata.add_child(data) - data.add_child(Node.string('mode', 'userload')) - data.add_child(Node.s64('gamesession', 123456)) - data.add_child(Node.string('refid', refid)) - data.add_child(Node.string('dataid', refid)) - data.add_child(Node.string('gamekind', 'MDX')) - data.add_child(Node.string('pcbid', self.pcbid)) - data.add_child(Node.void('record')) + data.add_child(Node.string("mode", "userload")) + data.add_child(Node.s64("gamesession", 123456)) + data.add_child(Node.string("refid", refid)) + data.add_child(Node.string("dataid", refid)) + data.add_child(Node.string("gamekind", "MDX")) + data.add_child(Node.string("pcbid", self.pcbid)) + data.add_child(Node.void("record")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/playerdata/result") self.assert_path(resp, "response/playerdata/is_new") music = [] - for child in resp.child('playerdata').children: - if child.name != 'music': + for child in resp.child("playerdata").children: + if child.name != "music": continue - songid = child.child_value('mcode') + songid = child.child_value("mcode") chart = 0 for note in child.children: - if note.name != 'note': + if note.name != "note": continue - if note.child_value('count') != 0: + if note.child_value("count") != 0: # Actual song - music.append({ - 'id': songid, - 'chart': chart, - 'rank': note.child_value('rank'), - 'halo': note.child_value('clearkind'), - 'score': note.child_value('score'), - 'ghostid': note.child_value('ghostid'), - }) + music.append( + { + "id": songid, + "chart": chart, + "rank": note.child_value("rank"), + "halo": note.child_value("clearkind"), + "score": note.child_value("score"), + "ghostid": note.child_value("ghostid"), + } + ) chart = chart + 1 return ( - resp.child_value('playerdata/is_new'), + resp.child_value("playerdata/is_new"), music, ) - def verify_playerdata_usergamedata_advanced_inheritance(self, refid: str, locid: str) -> None: + def verify_playerdata_usergamedata_advanced_inheritance( + self, refid: str, locid: str + ) -> None: call = self.call_node() # Construct node - playerdata = Node.void('playerdata') + playerdata = Node.void("playerdata") call.add_child(playerdata) - playerdata.set_attribute('method', 'usergamedata_advanced') - playerdata.add_child(Node.u32('retrycnt', 0)) - info = Node.void('info') + playerdata.set_attribute("method", "usergamedata_advanced") + playerdata.add_child(Node.u32("retrycnt", 0)) + info = Node.void("info") playerdata.add_child(info) - info.add_child(Node.s32('version', 1)) - data = Node.void('data') + info.add_child(Node.s32("version", 1)) + data = Node.void("data") playerdata.add_child(data) - data.add_child(Node.string('mode', 'inheritance')) - data.add_child(Node.string('locid', locid)) - data.add_child(Node.s64('gamesession', 123456)) - data.add_child(Node.string('refid', refid)) - data.add_child(Node.string('dataid', refid)) - data.add_child(Node.string('gamekind', 'MDX')) - data.add_child(Node.string('pcbid', self.pcbid)) - data.add_child(Node.void('record')) + data.add_child(Node.string("mode", "inheritance")) + data.add_child(Node.string("locid", locid)) + data.add_child(Node.s64("gamesession", 123456)) + data.add_child(Node.string("refid", refid)) + data.add_child(Node.string("dataid", refid)) + data.add_child(Node.string("gamekind", "MDX")) + data.add_child(Node.string("pcbid", self.pcbid)) + data.add_child(Node.void("record")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/playerdata/InheritanceStatus") self.assert_path(resp, "response/playerdata/result") - def verify_playerdata_usergamedata_advanced_usersave(self, refid: str, extid: int, locid: str, score: Dict[str, Any], scorepos: int=0) -> None: + def verify_playerdata_usergamedata_advanced_usersave( + self, + refid: str, + extid: int, + locid: str, + score: Dict[str, Any], + scorepos: int = 0, + ) -> None: call = self.call_node() # Construct node - playerdata = Node.void('playerdata') + playerdata = Node.void("playerdata") call.add_child(playerdata) - playerdata.set_attribute('method', 'usergamedata_advanced') - playerdata.add_child(Node.u32('retrycnt', 0)) - info = Node.void('info') + playerdata.set_attribute("method", "usergamedata_advanced") + playerdata.add_child(Node.u32("retrycnt", 0)) + info = Node.void("info") playerdata.add_child(info) - info.add_child(Node.s32('version', 1)) - data = Node.void('data') + info.add_child(Node.s32("version", 1)) + data = Node.void("data") playerdata.add_child(data) - data.add_child(Node.string('mode', 'usersave')) - data.add_child(Node.string('name', self.NAME)) - data.add_child(Node.s32('ddrcode', extid)) - data.add_child(Node.s32('playside', 1)) - data.add_child(Node.s32('playstyle', 0)) - data.add_child(Node.s32('area', 58)) - data.add_child(Node.s32('weight100', 0)) - data.add_child(Node.string('shopname', 'gmw=')) - data.add_child(Node.bool('ispremium', False)) - data.add_child(Node.bool('iseapass', True)) - data.add_child(Node.bool('istakeover', False)) - data.add_child(Node.bool('isrepeater', False)) - data.add_child(Node.bool('isgameover', scorepos < 0)) - data.add_child(Node.string('locid', locid)) - data.add_child(Node.string('shoparea', '.')) - data.add_child(Node.s64('gamesession', 123456)) - data.add_child(Node.string('refid', refid)) - data.add_child(Node.string('dataid', refid)) - data.add_child(Node.string('gamekind', 'MDX')) - data.add_child(Node.string('pcbid', self.pcbid)) - data.add_child(Node.void('record')) + data.add_child(Node.string("mode", "usersave")) + data.add_child(Node.string("name", self.NAME)) + data.add_child(Node.s32("ddrcode", extid)) + data.add_child(Node.s32("playside", 1)) + data.add_child(Node.s32("playstyle", 0)) + data.add_child(Node.s32("area", 58)) + data.add_child(Node.s32("weight100", 0)) + data.add_child(Node.string("shopname", "gmw=")) + data.add_child(Node.bool("ispremium", False)) + data.add_child(Node.bool("iseapass", True)) + data.add_child(Node.bool("istakeover", False)) + data.add_child(Node.bool("isrepeater", False)) + data.add_child(Node.bool("isgameover", scorepos < 0)) + data.add_child(Node.string("locid", locid)) + data.add_child(Node.string("shoparea", ".")) + data.add_child(Node.s64("gamesession", 123456)) + data.add_child(Node.string("refid", refid)) + data.add_child(Node.string("dataid", refid)) + data.add_child(Node.string("gamekind", "MDX")) + data.add_child(Node.string("pcbid", self.pcbid)) + data.add_child(Node.void("record")) for i in range(5): if i == scorepos: # Fill in score here - note = Node.void('note') + note = Node.void("note") data.add_child(note) - note.add_child(Node.u8('stagenum', i + 1)) - note.add_child(Node.u32('mcode', score['id'])) - note.add_child(Node.u8('notetype', score['chart'])) - note.add_child(Node.u8('rank', score['rank'])) - note.add_child(Node.u8('clearkind', score['halo'])) - note.add_child(Node.s32('score', score['score'])) - note.add_child(Node.s32('exscore', 0)) - note.add_child(Node.s32('maxcombo', 0)) - note.add_child(Node.s32('life', 0)) - note.add_child(Node.s32('fastcount', 0)) - note.add_child(Node.s32('slowcount', 0)) - note.add_child(Node.s32('judge_marvelous', 0)) - note.add_child(Node.s32('judge_perfect', 0)) - note.add_child(Node.s32('judge_great', 0)) - note.add_child(Node.s32('judge_good', 0)) - note.add_child(Node.s32('judge_boo', 0)) - note.add_child(Node.s32('judge_miss', 0)) - note.add_child(Node.s32('judge_ok', 0)) - note.add_child(Node.s32('judge_ng', 0)) - note.add_child(Node.s32('calorie', 0)) - note.add_child(Node.s32('ghostsize', len(score['ghost']))) - note.add_child(Node.string('ghost', score['ghost'])) - note.add_child(Node.u8('opt_speed', 0)) - note.add_child(Node.u8('opt_boost', 0)) - note.add_child(Node.u8('opt_appearance', 0)) - note.add_child(Node.u8('opt_turn', 0)) - note.add_child(Node.u8('opt_dark', 0)) - note.add_child(Node.u8('opt_scroll', 0)) - note.add_child(Node.u8('opt_arrowcolor', 0)) - note.add_child(Node.u8('opt_cut', 0)) - note.add_child(Node.u8('opt_freeze', 0)) - note.add_child(Node.u8('opt_jump', 0)) - note.add_child(Node.u8('opt_arrowshape', 0)) - note.add_child(Node.u8('opt_filter', 0)) - note.add_child(Node.u8('opt_guideline', 0)) - note.add_child(Node.u8('opt_gauge', 0)) - note.add_child(Node.u8('opt_judgepriority', 0)) - note.add_child(Node.u8('opt_timing', 0)) - note.add_child(Node.string('basename', '')) - note.add_child(Node.string('title_b64', '')) - note.add_child(Node.string('artist_b64', '')) - note.add_child(Node.u16('bpmMax', 0)) - note.add_child(Node.u16('bpmMin', 0)) - note.add_child(Node.u8('level', 0)) - note.add_child(Node.u8('series', 0)) - note.add_child(Node.u32('bemaniFlag', 0)) - note.add_child(Node.u32('genreFlag', 0)) - note.add_child(Node.u8('limited', 0)) - note.add_child(Node.u8('region', 0)) - note.add_child(Node.s32('gr_voltage', 0)) - note.add_child(Node.s32('gr_stream', 0)) - note.add_child(Node.s32('gr_chaos', 0)) - note.add_child(Node.s32('gr_freeze', 0)) - note.add_child(Node.s32('gr_air', 0)) - note.add_child(Node.bool('share', False)) - note.add_child(Node.u64('endtime', 0)) - note.add_child(Node.s32('folder', 0)) + note.add_child(Node.u8("stagenum", i + 1)) + note.add_child(Node.u32("mcode", score["id"])) + note.add_child(Node.u8("notetype", score["chart"])) + note.add_child(Node.u8("rank", score["rank"])) + note.add_child(Node.u8("clearkind", score["halo"])) + note.add_child(Node.s32("score", score["score"])) + note.add_child(Node.s32("exscore", 0)) + note.add_child(Node.s32("maxcombo", 0)) + note.add_child(Node.s32("life", 0)) + note.add_child(Node.s32("fastcount", 0)) + note.add_child(Node.s32("slowcount", 0)) + note.add_child(Node.s32("judge_marvelous", 0)) + note.add_child(Node.s32("judge_perfect", 0)) + note.add_child(Node.s32("judge_great", 0)) + note.add_child(Node.s32("judge_good", 0)) + note.add_child(Node.s32("judge_boo", 0)) + note.add_child(Node.s32("judge_miss", 0)) + note.add_child(Node.s32("judge_ok", 0)) + note.add_child(Node.s32("judge_ng", 0)) + note.add_child(Node.s32("calorie", 0)) + note.add_child(Node.s32("ghostsize", len(score["ghost"]))) + note.add_child(Node.string("ghost", score["ghost"])) + note.add_child(Node.u8("opt_speed", 0)) + note.add_child(Node.u8("opt_boost", 0)) + note.add_child(Node.u8("opt_appearance", 0)) + note.add_child(Node.u8("opt_turn", 0)) + note.add_child(Node.u8("opt_dark", 0)) + note.add_child(Node.u8("opt_scroll", 0)) + note.add_child(Node.u8("opt_arrowcolor", 0)) + note.add_child(Node.u8("opt_cut", 0)) + note.add_child(Node.u8("opt_freeze", 0)) + note.add_child(Node.u8("opt_jump", 0)) + note.add_child(Node.u8("opt_arrowshape", 0)) + note.add_child(Node.u8("opt_filter", 0)) + note.add_child(Node.u8("opt_guideline", 0)) + note.add_child(Node.u8("opt_gauge", 0)) + note.add_child(Node.u8("opt_judgepriority", 0)) + note.add_child(Node.u8("opt_timing", 0)) + note.add_child(Node.string("basename", "")) + note.add_child(Node.string("title_b64", "")) + note.add_child(Node.string("artist_b64", "")) + note.add_child(Node.u16("bpmMax", 0)) + note.add_child(Node.u16("bpmMin", 0)) + note.add_child(Node.u8("level", 0)) + note.add_child(Node.u8("series", 0)) + note.add_child(Node.u32("bemaniFlag", 0)) + note.add_child(Node.u32("genreFlag", 0)) + note.add_child(Node.u8("limited", 0)) + note.add_child(Node.u8("region", 0)) + note.add_child(Node.s32("gr_voltage", 0)) + note.add_child(Node.s32("gr_stream", 0)) + note.add_child(Node.s32("gr_chaos", 0)) + note.add_child(Node.s32("gr_freeze", 0)) + note.add_child(Node.s32("gr_air", 0)) + note.add_child(Node.bool("share", False)) + note.add_child(Node.u64("endtime", 0)) + note.add_child(Node.s32("folder", 0)) else: - note = Node.void('note') + note = Node.void("note") data.add_child(note) - note.add_child(Node.u8('stagenum', 0)) - note.add_child(Node.u32('mcode', 0)) - note.add_child(Node.u8('notetype', 0)) - note.add_child(Node.u8('rank', 0)) - note.add_child(Node.u8('clearkind', 0)) - note.add_child(Node.s32('score', 0)) - note.add_child(Node.s32('exscore', 0)) - note.add_child(Node.s32('maxcombo', 0)) - note.add_child(Node.s32('life', 0)) - note.add_child(Node.s32('fastcount', 0)) - note.add_child(Node.s32('slowcount', 0)) - note.add_child(Node.s32('judge_marvelous', 0)) - note.add_child(Node.s32('judge_perfect', 0)) - note.add_child(Node.s32('judge_great', 0)) - note.add_child(Node.s32('judge_good', 0)) - note.add_child(Node.s32('judge_boo', 0)) - note.add_child(Node.s32('judge_miss', 0)) - note.add_child(Node.s32('judge_ok', 0)) - note.add_child(Node.s32('judge_ng', 0)) - note.add_child(Node.s32('calorie', 0)) - note.add_child(Node.s32('ghostsize', 0)) - note.add_child(Node.string('ghost', '')) - note.add_child(Node.u8('opt_speed', 0)) - note.add_child(Node.u8('opt_boost', 0)) - note.add_child(Node.u8('opt_appearance', 0)) - note.add_child(Node.u8('opt_turn', 0)) - note.add_child(Node.u8('opt_dark', 0)) - note.add_child(Node.u8('opt_scroll', 0)) - note.add_child(Node.u8('opt_arrowcolor', 0)) - note.add_child(Node.u8('opt_cut', 0)) - note.add_child(Node.u8('opt_freeze', 0)) - note.add_child(Node.u8('opt_jump', 0)) - note.add_child(Node.u8('opt_arrowshape', 0)) - note.add_child(Node.u8('opt_filter', 0)) - note.add_child(Node.u8('opt_guideline', 0)) - note.add_child(Node.u8('opt_gauge', 0)) - note.add_child(Node.u8('opt_judgepriority', 0)) - note.add_child(Node.u8('opt_timing', 0)) - note.add_child(Node.string('basename', '')) - note.add_child(Node.string('title_b64', '')) - note.add_child(Node.string('artist_b64', '')) - note.add_child(Node.u16('bpmMax', 0)) - note.add_child(Node.u16('bpmMin', 0)) - note.add_child(Node.u8('level', 0)) - note.add_child(Node.u8('series', 0)) - note.add_child(Node.u32('bemaniFlag', 0)) - note.add_child(Node.u32('genreFlag', 0)) - note.add_child(Node.u8('limited', 0)) - note.add_child(Node.u8('region', 0)) - note.add_child(Node.s32('gr_voltage', 0)) - note.add_child(Node.s32('gr_stream', 0)) - note.add_child(Node.s32('gr_chaos', 0)) - note.add_child(Node.s32('gr_freeze', 0)) - note.add_child(Node.s32('gr_air', 0)) - note.add_child(Node.bool('share', False)) - note.add_child(Node.u64('endtime', 0)) - note.add_child(Node.s32('folder', 0)) + note.add_child(Node.u8("stagenum", 0)) + note.add_child(Node.u32("mcode", 0)) + note.add_child(Node.u8("notetype", 0)) + note.add_child(Node.u8("rank", 0)) + note.add_child(Node.u8("clearkind", 0)) + note.add_child(Node.s32("score", 0)) + note.add_child(Node.s32("exscore", 0)) + note.add_child(Node.s32("maxcombo", 0)) + note.add_child(Node.s32("life", 0)) + note.add_child(Node.s32("fastcount", 0)) + note.add_child(Node.s32("slowcount", 0)) + note.add_child(Node.s32("judge_marvelous", 0)) + note.add_child(Node.s32("judge_perfect", 0)) + note.add_child(Node.s32("judge_great", 0)) + note.add_child(Node.s32("judge_good", 0)) + note.add_child(Node.s32("judge_boo", 0)) + note.add_child(Node.s32("judge_miss", 0)) + note.add_child(Node.s32("judge_ok", 0)) + note.add_child(Node.s32("judge_ng", 0)) + note.add_child(Node.s32("calorie", 0)) + note.add_child(Node.s32("ghostsize", 0)) + note.add_child(Node.string("ghost", "")) + note.add_child(Node.u8("opt_speed", 0)) + note.add_child(Node.u8("opt_boost", 0)) + note.add_child(Node.u8("opt_appearance", 0)) + note.add_child(Node.u8("opt_turn", 0)) + note.add_child(Node.u8("opt_dark", 0)) + note.add_child(Node.u8("opt_scroll", 0)) + note.add_child(Node.u8("opt_arrowcolor", 0)) + note.add_child(Node.u8("opt_cut", 0)) + note.add_child(Node.u8("opt_freeze", 0)) + note.add_child(Node.u8("opt_jump", 0)) + note.add_child(Node.u8("opt_arrowshape", 0)) + note.add_child(Node.u8("opt_filter", 0)) + note.add_child(Node.u8("opt_guideline", 0)) + note.add_child(Node.u8("opt_gauge", 0)) + note.add_child(Node.u8("opt_judgepriority", 0)) + note.add_child(Node.u8("opt_timing", 0)) + note.add_child(Node.string("basename", "")) + note.add_child(Node.string("title_b64", "")) + note.add_child(Node.string("artist_b64", "")) + note.add_child(Node.u16("bpmMax", 0)) + note.add_child(Node.u16("bpmMin", 0)) + note.add_child(Node.u8("level", 0)) + note.add_child(Node.u8("series", 0)) + note.add_child(Node.u32("bemaniFlag", 0)) + note.add_child(Node.u32("genreFlag", 0)) + note.add_child(Node.u8("limited", 0)) + note.add_child(Node.u8("region", 0)) + note.add_child(Node.s32("gr_voltage", 0)) + note.add_child(Node.s32("gr_stream", 0)) + note.add_child(Node.s32("gr_chaos", 0)) + note.add_child(Node.s32("gr_freeze", 0)) + note.add_child(Node.s32("gr_air", 0)) + note.add_child(Node.bool("share", False)) + note.add_child(Node.u64("endtime", 0)) + note.add_child(Node.s32("folder", 0)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/playerdata/result") - def verify_usergamedata_send(self, ref_id: str, ext_id: int, msg_type: str, send_only_common: bool=False) -> None: + def verify_usergamedata_send( + self, ref_id: str, ext_id: int, msg_type: str, send_only_common: bool = False + ) -> None: call = self.call_node() # Set up profile write profiledata = { - 'COMMON': [ - b'1', - b'0', # shoparea spot, filled in below - b'3c880f8', - b'1', - b'0', - b'0', - b'0', - b'0', - b'0', - b'ffffffffffffffff', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'', # Name spot, filled in below - ID.format_extid(ext_id).encode('ascii'), - b'', - b'', - b'', - b'', - b'', - b'', + "COMMON": [ + b"1", + b"0", # shoparea spot, filled in below + b"3c880f8", + b"1", + b"0", + b"0", + b"0", + b"0", + b"0", + b"ffffffffffffffff", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"", # Name spot, filled in below + ID.format_extid(ext_id).encode("ascii"), + b"", + b"", + b"", + b"", + b"", + b"", ], - 'OPTION': [ - b'0', - b'3', - b'0', - b'0', - b'0', - b'0', - b'0', - b'3', - b'0', - b'0', - b'0', - b'0', - b'1', - b'2', - b'0', - b'0', - b'0', - b'10.000000', - b'10.000000', - b'10.000000', - b'10.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'', - b'', - b'', - b'', - b'', - b'', - b'', - b'', + "OPTION": [ + b"0", + b"3", + b"0", + b"0", + b"0", + b"0", + b"0", + b"3", + b"0", + b"0", + b"0", + b"0", + b"1", + b"2", + b"0", + b"0", + b"0", + b"10.000000", + b"10.000000", + b"10.000000", + b"10.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"", + b"", + b"", + b"", + b"", + b"", + b"", + b"", ], - 'LAST': [ - b'1', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'', - b'', - b'', - b'', - b'', - b'', - b'', - b'', + "LAST": [ + b"1", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"", + b"", + b"", + b"", + b"", + b"", + b"", + b"", + ], + "RIVAL": [ + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"", + b"", + b"", + b"", + b"", + b"", + b"", + b"", ], - 'RIVAL': [ - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'', - b'', - b'', - b'', - b'', - b'', - b'', - b'', - ] } - if msg_type == 'new': + if msg_type == "new": # New profile gets blank name, because we save over it at the end of the round. - profiledata['COMMON'][1] = b'0' - profiledata['COMMON'][25] = b'' + profiledata["COMMON"][1] = b"0" + profiledata["COMMON"][25] = b"" - elif msg_type == 'existing': + elif msg_type == "existing": # Exiting profile gets our hardcoded name saved. - profiledata['COMMON'][1] = b'3a' - profiledata['COMMON'][25] = self.NAME.encode('shift-jis') + profiledata["COMMON"][1] = b"3a" + profiledata["COMMON"][25] = self.NAME.encode("shift-jis") else: - raise Exception(f'Unknown message type {msg_type}!') + raise Exception(f"Unknown message type {msg_type}!") if send_only_common: - profiledata = {'COMMON': profiledata['COMMON']} + profiledata = {"COMMON": profiledata["COMMON"]} # Construct node - playerdata = Node.void('playerdata') + playerdata = Node.void("playerdata") call.add_child(playerdata) - playerdata.set_attribute('method', 'usergamedata_send') - playerdata.add_child(Node.u32('retrycnt', 0)) - info = Node.void('info') + playerdata.set_attribute("method", "usergamedata_send") + playerdata.add_child(Node.u32("retrycnt", 0)) + info = Node.void("info") playerdata.add_child(info) - info.add_child(Node.s32('version', 1)) - data = Node.void('data') + info.add_child(Node.s32("version", 1)) + data = Node.void("data") playerdata.add_child(data) - data.add_child(Node.string('refid', ref_id)) - data.add_child(Node.string('dataid', ref_id)) - data.add_child(Node.string('gamekind', 'MDX')) - data.add_child(Node.u32('datanum', len(profiledata.keys()))) - record = Node.void('record') + data.add_child(Node.string("refid", ref_id)) + data.add_child(Node.string("dataid", ref_id)) + data.add_child(Node.string("gamekind", "MDX")) + data.add_child(Node.u32("datanum", len(profiledata.keys()))) + record = Node.void("record") data.add_child(record) for ptype in profiledata: - profile = [b'ffffffff', ptype.encode('ascii')] + profiledata[ptype] - d = Node.string('d', base64.b64encode(b','.join(profile)).decode('ascii')) + profile = [b"ffffffff", ptype.encode("ascii")] + profiledata[ptype] + d = Node.string("d", base64.b64encode(b",".join(profile)).decode("ascii")) record.add_child(d) - d.add_child(Node.string('bin1', '')) + d.add_child(Node.string("bin1", "")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/playerdata/result") def verify_usergamedata_recv(self, ref_id: str) -> str: call = self.call_node() # Construct node - playerdata = Node.void('playerdata') + playerdata = Node.void("playerdata") call.add_child(playerdata) - playerdata.set_attribute('method', 'usergamedata_recv') - info = Node.void('info') + playerdata.set_attribute("method", "usergamedata_recv") + info = Node.void("info") playerdata.add_child(info) - info.add_child(Node.s32('version', 1)) - data = Node.void('data') + info.add_child(Node.s32("version", 1)) + data = Node.void("data") playerdata.add_child(data) - data.add_child(Node.string('refid', ref_id)) - data.add_child(Node.string('dataid', ref_id)) - data.add_child(Node.string('gamekind', 'MDX')) - data.add_child(Node.u32('recv_num', 4)) - data.add_child(Node.string('recv_csv', 'COMMON,3fffffffff,OPTION,3fffffffff,LAST,3fffffffff,RIVAL,3fffffffff')) + data.add_child(Node.string("refid", ref_id)) + data.add_child(Node.string("dataid", ref_id)) + data.add_child(Node.string("gamekind", "MDX")) + data.add_child(Node.u32("recv_num", 4)) + data.add_child( + Node.string( + "recv_csv", + "COMMON,3fffffffff,OPTION,3fffffffff,LAST,3fffffffff,RIVAL,3fffffffff", + ) + ) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/playerdata/result") self.assert_path(resp, "response/playerdata/player/record/d/bin1") self.assert_path(resp, "response/playerdata/player/record_num") profiles = 0 - name = '' - for child in resp.child('playerdata/player/record').children: - if child.name != 'd': + name = "" + for child in resp.child("playerdata/player/record").children: + if child.name != "d": continue if profiles == 0: bindata = child.value - profiledata = base64.b64decode(bindata).split(b',') - name = profiledata[25].decode('ascii') + profiledata = base64.b64decode(bindata).split(b",") + name = profiledata[25].decode("ascii") profiles = profiles + 1 if profiles != 4: - raise Exception('Didn\'t receive all four profiles in the right order!') + raise Exception("Didn't receive all four profiles in the right order!") return name @@ -672,20 +696,20 @@ class DDRAceClient(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -696,11 +720,13 @@ class DDRAceClient(BaseClient): self.verify_eventlog_write(location) # Verify the game-wide packets Ace insists on sending before profile load - is_new, music = self.verify_playerdata_usergamedata_advanced_userload('X0000000000000000000000000123456') + is_new, music = self.verify_playerdata_usergamedata_advanced_userload( + "X0000000000000000000000000123456" + ) if not is_new: - raise Exception('Fake profiles should be new!') + raise Exception("Fake profiles should be new!") if len(music) > 0: - raise Exception('Fake profiles should have no scores associated!') + raise Exception("Fake profiles should have no scores associated!") # Verify card registration and profile lookup if cardid is not None: @@ -710,39 +736,53 @@ class DDRAceClient(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) self.verify_system_convcardnumber(card) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") extid = self.verify_playerdata_usergamedata_advanced_usernew(ref_id) - self.verify_usergamedata_send(ref_id, extid, 'new') + self.verify_usergamedata_send(ref_id, extid, "new") self.verify_playerdata_usergamedata_advanced_inheritance(ref_id, location) name = self.verify_usergamedata_recv(ref_id) - if name != '': - raise Exception('Name stored on profile we just created!') - self.verify_usergamedata_send(ref_id, extid, 'existing', send_only_common=True) + if name != "": + raise Exception("Name stored on profile we just created!") + self.verify_usergamedata_send( + ref_id, extid, "existing", send_only_common=True + ) name = self.verify_usergamedata_recv(ref_id) if name != self.NAME: - raise Exception('Name stored on profile is incorrect!') + raise Exception("Name stored on profile is incorrect!") else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") if cardid is None: - is_new, music = self.verify_playerdata_usergamedata_advanced_userload(ref_id) + is_new, music = self.verify_playerdata_usergamedata_advanced_userload( + ref_id + ) if is_new: - raise Exception('Profile should not be new!') + raise Exception("Profile should not be new!") if len(music) > 0: - raise Exception('Created profile should have no scores associated!') + raise Exception("Created profile should have no scores associated!") # Verify score saving and updating for phase in [1, 2]: @@ -750,64 +790,64 @@ class DDRAceClient(BaseClient): dummyscores = [ # An okay score on a chart { - 'id': 10, - 'chart': 3, - 'rank': 5, - 'halo': 6, - 'score': 765432, - 'ghost': '765432', + "id": 10, + "chart": 3, + "rank": 5, + "halo": 6, + "score": 765432, + "ghost": "765432", }, # A good score on an easier chart of the same song { - 'id': 10, - 'chart': 2, - 'rank': 2, - 'halo': 8, - 'score': 876543, - 'ghost': '876543', + "id": 10, + "chart": 2, + "rank": 2, + "halo": 8, + "score": 876543, + "ghost": "876543", }, # A bad score on a hard chart { - 'id': 479, - 'chart': 2, - 'rank': 11, - 'halo': 6, - 'score': 654321, - 'ghost': '654321', + "id": 479, + "chart": 2, + "rank": 11, + "halo": 6, + "score": 654321, + "ghost": "654321", }, # A terrible score on an easy chart { - 'id': 479, - 'chart': 1, - 'rank': 15, - 'halo': 6, - 'score': 123456, - 'ghost': '123456', + "id": 479, + "chart": 1, + "rank": 15, + "halo": 6, + "score": 123456, + "ghost": "123456", }, ] if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 10, - 'chart': 3, - 'rank': 4, - 'halo': 7, - 'score': 888888, - 'ghost': '888888', + "id": 10, + "chart": 3, + "rank": 4, + "halo": 7, + "score": 888888, + "ghost": "888888", }, # A worse score on another same chart { - 'id': 10, - 'chart': 2, - 'rank': 3, - 'halo': 7, - 'score': 654321, - 'ghost': '654321', - 'expected_score': 876543, - 'expected_halo': 8, - 'expected_rank': 2, - 'expected_ghost': '876543', + "id": 10, + "chart": 2, + "rank": 3, + "halo": 7, + "score": 654321, + "ghost": "654321", + "expected_score": 876543, + "expected_halo": 8, + "expected_rank": 2, + "expected_ghost": "876543", }, ] @@ -822,57 +862,80 @@ class DDRAceClient(BaseClient): ) pos = pos + 1 - is_new, scores = self.verify_playerdata_usergamedata_advanced_userload(ref_id) + is_new, scores = self.verify_playerdata_usergamedata_advanced_userload( + ref_id + ) if is_new: - raise Exception('Profile should not be new!') + raise Exception("Profile should not be new!") if len(scores) == 0: - raise Exception('Expected some scores after saving!') + raise Exception("Expected some scores after saving!") for expected in dummyscores: actual = None for received in scores: - if received['id'] == expected['id'] and received['chart'] == expected['chart']: + if ( + received["id"] == expected["id"] + and received["chart"] == expected["chart"] + ): actual = received break if actual is None: - raise Exception(f"Didn't find song {expected['id']} chart {expected['chart']} in response!") + raise Exception( + f"Didn't find song {expected['id']} chart {expected['chart']} in response!" + ) - if 'expected_score' in expected: - expected_score = expected['expected_score'] + if "expected_score" in expected: + expected_score = expected["expected_score"] else: - expected_score = expected['score'] - if 'expected_rank' in expected: - expected_rank = expected['expected_rank'] + expected_score = expected["score"] + if "expected_rank" in expected: + expected_rank = expected["expected_rank"] else: - expected_rank = expected['rank'] - if 'expected_halo' in expected: - expected_halo = expected['expected_halo'] + expected_rank = expected["rank"] + if "expected_halo" in expected: + expected_halo = expected["expected_halo"] else: - expected_halo = expected['halo'] + expected_halo = expected["halo"] - if actual['score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'') - if actual['rank'] != expected_rank: - raise Exception(f'Expected a rank of \'{expected_rank}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got rank \'{actual["rank"]}\'') - if actual['halo'] != expected_halo: - raise Exception(f'Expected a halo of \'{expected_halo}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got halo \'{actual["halo"]}\'') + if actual["score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'' + ) + if actual["rank"] != expected_rank: + raise Exception( + f'Expected a rank of \'{expected_rank}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got rank \'{actual["rank"]}\'' + ) + if actual["halo"] != expected_halo: + raise Exception( + f'Expected a halo of \'{expected_halo}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got halo \'{actual["halo"]}\'' + ) # Now verify that the ghost for this score is what we saved - ghost = self.verify_playerdata_usergamedata_advanced_ghostload(ref_id, received['ghostid']) - if 'expected_ghost' in expected: - expected_ghost = expected['expected_ghost'] + ghost = self.verify_playerdata_usergamedata_advanced_ghostload( + ref_id, received["ghostid"] + ) + if "expected_ghost" in expected: + expected_ghost = expected["expected_ghost"] else: - expected_ghost = expected['ghost'] + expected_ghost = expected["ghost"] - if ghost['id'] != received['id']: - raise Exception(f'Wrong song ID \'{ghost["id"]}\' returned for ghost, expected ID \'{received["id"]}\'') - if ghost['chart'] != received['chart']: - raise Exception(f'Wrong song chart \'{ghost["chart"]}\' returned for ghost, expected chart \'{received["chart"]}\'') - if ghost['ghost'] != expected_ghost: - raise Exception(f'Wrong ghost data \'{ghost["ghost"]}\' returned for ghost, expected \'{expected_ghost}\'') - if ghost['extid'] != extid: - raise Exception(f'Wrong extid \'{ghost["extid"]}\' returned for ghost, expected \'{extid}\'') + if ghost["id"] != received["id"]: + raise Exception( + f'Wrong song ID \'{ghost["id"]}\' returned for ghost, expected ID \'{received["id"]}\'' + ) + if ghost["chart"] != received["chart"]: + raise Exception( + f'Wrong song chart \'{ghost["chart"]}\' returned for ghost, expected chart \'{received["chart"]}\'' + ) + if ghost["ghost"] != expected_ghost: + raise Exception( + f'Wrong ghost data \'{ghost["ghost"]}\' returned for ghost, expected \'{expected_ghost}\'' + ) + if ghost["extid"] != extid: + raise Exception( + f'Wrong extid \'{ghost["extid"]}\' returned for ghost, expected \'{extid}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) @@ -889,9 +952,15 @@ class DDRAceClient(BaseClient): print("Skipping score checks for existing card") # Verify global scores now that we've inserted some - self.verify_playerdata_usergamedata_advanced_rivalload('X0000000000000000000000000123456', 1) - self.verify_playerdata_usergamedata_advanced_rivalload('X0000000000000000000000000123456', 2) - self.verify_playerdata_usergamedata_advanced_rivalload('X0000000000000000000000000123456', 4) + self.verify_playerdata_usergamedata_advanced_rivalload( + "X0000000000000000000000000123456", 1 + ) + self.verify_playerdata_usergamedata_advanced_rivalload( + "X0000000000000000000000000123456", 2 + ) + self.verify_playerdata_usergamedata_advanced_rivalload( + "X0000000000000000000000000123456", 4 + ) # Verify paseli handling if paseli_enabled: diff --git a/bemani/client/ddr/ddrx2.py b/bemani/client/ddr/ddrx2.py index b9176cd..13ff837 100644 --- a/bemani/client/ddr/ddrx2.py +++ b/bemani/client/ddr/ddrx2.py @@ -7,69 +7,69 @@ from bemani.protocol import Node class DDRX2Client(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_cardmng_getkeepspan(self) -> None: call = self.call_node() # Calculate model node - model = ':'.join(self.config['model'].split(':')[:4]) + model = ":".join(self.config["model"].split(":")[:4]) # Construct node - cardmng = Node.void('cardmng') - cardmng.set_attribute('method', 'getkeepspan') - cardmng.set_attribute('model', model) + cardmng = Node.void("cardmng") + cardmng.set_attribute("method", "getkeepspan") + cardmng.set_attribute("model", model) call.add_child(cardmng) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/cardmng/@keepspan") def verify_game_shop(self, loc: str) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'shop') - game.set_attribute('diff', '3') - game.set_attribute('time', '60') - game.set_attribute('close', '0') - game.set_attribute('during', '1') - game.set_attribute('stage', '1') - game.set_attribute('ver', '1') - game.set_attribute('machine', '2') - game.set_attribute('area', '0') - game.set_attribute('soft', self.config['model']) - game.set_attribute('close_t', '0') - game.set_attribute('region', '.') - game.set_attribute('is_paseli', '1') - game.set_attribute('ip', '1.5.7.3') - game.set_attribute('pay', '0') - game.set_attribute('softid', self.pcbid) - game.set_attribute('first', '1') - game.set_attribute('boot', '34') - game.set_attribute('type', '0') - game.set_attribute('coin', '02.01.--.--.01.G') - game.set_attribute('name', 'TEST') - game.set_attribute('mac', '00:11:22:33:44:55') - game.set_attribute('loc', loc) + game.set_attribute("method", "shop") + game.set_attribute("diff", "3") + game.set_attribute("time", "60") + game.set_attribute("close", "0") + game.set_attribute("during", "1") + game.set_attribute("stage", "1") + game.set_attribute("ver", "1") + game.set_attribute("machine", "2") + game.set_attribute("area", "0") + game.set_attribute("soft", self.config["model"]) + game.set_attribute("close_t", "0") + game.set_attribute("region", ".") + game.set_attribute("is_paseli", "1") + game.set_attribute("ip", "1.5.7.3") + game.set_attribute("pay", "0") + game.set_attribute("softid", self.pcbid) + game.set_attribute("first", "1") + game.set_attribute("boot", "34") + game.set_attribute("type", "0") + game.set_attribute("coin", "02.01.--.--.01.G") + game.set_attribute("name", "TEST") + game.set_attribute("mac", "00:11:22:33:44:55") + game.set_attribute("loc", loc) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/@stop") def verify_game_common(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'common') - game.set_attribute('ver', '1') + game.set_attribute("method", "common") + game.set_attribute("ver", "1") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/flag/@id") @@ -80,92 +80,92 @@ class DDRX2Client(BaseClient): def verify_game_hiscore(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'hiscore') - game.set_attribute('ver', '1') + game.set_attribute("method", "hiscore") + game.set_attribute("ver", "1") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") - for child in resp.child('game').children: - self.assert_path(child, 'music/@reclink_num') - self.assert_path(child, 'music/type/@diff') - self.assert_path(child, 'music/type/name') - self.assert_path(child, 'music/type/score') - self.assert_path(child, 'music/type/area') - self.assert_path(child, 'music/type/rank') - self.assert_path(child, 'music/type/combo_type') + for child in resp.child("game").children: + self.assert_path(child, "music/@reclink_num") + self.assert_path(child, "music/type/@diff") + self.assert_path(child, "music/type/name") + self.assert_path(child, "music/type/score") + self.assert_path(child, "music/type/area") + self.assert_path(child, "music/type/rank") + self.assert_path(child, "music/type/combo_type") def verify_game_message(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'message') - game.set_attribute('ver', '1') + game.set_attribute("method", "message") + game.set_attribute("ver", "1") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") def verify_game_ranking(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'ranking') - game.set_attribute('ver', '1') + game.set_attribute("method", "ranking") + game.set_attribute("ver", "1") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") def verify_game_log(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'log') - game.set_attribute('type', '0') - game.set_attribute('soft', self.config['model']) - game.set_attribute('softid', self.pcbid) - game.set_attribute('ver', '1') - game.set_attribute('boot', '34') - game.set_attribute('mac', '00:11:22:33:44:55') - clear = Node.void('clear') + game.set_attribute("method", "log") + game.set_attribute("type", "0") + game.set_attribute("soft", self.config["model"]) + game.set_attribute("softid", self.pcbid) + game.set_attribute("ver", "1") + game.set_attribute("boot", "34") + game.set_attribute("mac", "00:11:22:33:44:55") + clear = Node.void("clear") game.add_child(clear) - clear.set_attribute('book', '0') - clear.set_attribute('edit', '0') - clear.set_attribute('rank', '0') - clear.set_attribute('set', '0') - auto = Node.void('auto') + clear.set_attribute("book", "0") + clear.set_attribute("edit", "0") + clear.set_attribute("rank", "0") + clear.set_attribute("set", "0") + auto = Node.void("auto") game.add_child(auto) - auto.set_attribute('book', '1') - auto.set_attribute('edit', '1') - auto.set_attribute('rank', '1') - auto.set_attribute('set', '1') + auto.set_attribute("book", "1") + auto.set_attribute("edit", "1") + auto.set_attribute("rank", "1") + auto.set_attribute("set", "1") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") def verify_game_lock(self, ref_id: str, play: int) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('refid', ref_id) - game.set_attribute('method', 'lock') - game.set_attribute('ver', '1') - game.set_attribute('play', str(play)) + game.set_attribute("refid", ref_id) + game.set_attribute("method", "lock") + game.set_attribute("ver", "1") + game.set_attribute("play", str(play)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/@now_login") @@ -174,40 +174,40 @@ class DDRX2Client(BaseClient): # Pad the name to 8 characters name = self.NAME[:8] while len(name) < 8: - name = name + ' ' + name = name + " " call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'new') - game.set_attribute('ver', '1') - game.set_attribute('name', name) - game.set_attribute('area', '51') - game.set_attribute('old', '0') - game.set_attribute('refid', ref_id) + game.set_attribute("method", "new") + game.set_attribute("ver", "1") + game.set_attribute("name", name) + game.set_attribute("area", "51") + game.set_attribute("old", "0") + game.set_attribute("refid", ref_id) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") def verify_game_load(self, ref_id: str, msg_type: str) -> Dict[str, Any]: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'load') - game.set_attribute('ver', '1') - game.set_attribute('refid', ref_id) + game.set_attribute("method", "load") + game.set_attribute("ver", "1") + game.set_attribute("refid", ref_id) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) - if msg_type == 'new': + if msg_type == "new": # Verify that response is correct self.assert_path(resp, "response/game/@none") return {} - if msg_type == 'existing': + if msg_type == "existing": # Verify existing profile and return info self.assert_path(resp, "response/game/seq") self.assert_path(resp, "response/game/code") @@ -253,62 +253,62 @@ class DDRX2Client(BaseClient): self.assert_path(resp, "response/game/flag") self.assert_path(resp, "response/game/rank") - gr_s = resp.child('game/gr_s') - gr_d = resp.child('game/gr_d') + gr_s = resp.child("game/gr_s") + gr_d = resp.child("game/gr_d") return { - 'name': resp.child_value('game/name'), - 'single_plays': resp.child_value('game/cnt_s'), - 'double_plays': resp.child_value('game/cnt_d'), - 'groove_single': [ - int(gr_s.attribute('gr1')), - int(gr_s.attribute('gr2')), - int(gr_s.attribute('gr3')), - int(gr_s.attribute('gr4')), - int(gr_s.attribute('gr5')), + "name": resp.child_value("game/name"), + "single_plays": resp.child_value("game/cnt_s"), + "double_plays": resp.child_value("game/cnt_d"), + "groove_single": [ + int(gr_s.attribute("gr1")), + int(gr_s.attribute("gr2")), + int(gr_s.attribute("gr3")), + int(gr_s.attribute("gr4")), + int(gr_s.attribute("gr5")), ], - 'groove_double': [ - int(gr_d.attribute('gr1')), - int(gr_d.attribute('gr2')), - int(gr_d.attribute('gr3')), - int(gr_d.attribute('gr4')), - int(gr_d.attribute('gr5')), + "groove_double": [ + int(gr_d.attribute("gr1")), + int(gr_d.attribute("gr2")), + int(gr_d.attribute("gr3")), + int(gr_d.attribute("gr4")), + int(gr_d.attribute("gr5")), ], } - raise Exception('Unknown load type!') + raise Exception("Unknown load type!") def verify_game_load_m(self, ref_id: str) -> Dict[int, Dict[int, Dict[str, Any]]]: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('ver', '1') - game.set_attribute('all', '1') - game.set_attribute('refid', ref_id) - game.set_attribute('method', 'load_m') + game.set_attribute("ver", "1") + game.set_attribute("all", "1") + game.set_attribute("refid", ref_id) + game.set_attribute("method", "load_m") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct scores: Dict[int, Dict[int, Dict[str, Any]]] = {} self.assert_path(resp, "response/game") - for child in resp.child('game').children: - self.assert_path(child, 'music/@reclink') - reclink = int(child.attribute('reclink')) + for child in resp.child("game").children: + self.assert_path(child, "music/@reclink") + reclink = int(child.attribute("reclink")) for typenode in child.children: - self.assert_path(typenode, 'type/@diff') - self.assert_path(typenode, 'type/score') - self.assert_path(typenode, 'type/count') - self.assert_path(typenode, 'type/rank') - self.assert_path(typenode, 'type/combo_type') - chart = int(typenode.attribute('diff')) + self.assert_path(typenode, "type/@diff") + self.assert_path(typenode, "type/score") + self.assert_path(typenode, "type/count") + self.assert_path(typenode, "type/rank") + self.assert_path(typenode, "type/combo_type") + chart = int(typenode.attribute("diff")) vals = { - 'score': typenode.child_value('score'), - 'count': typenode.child_value('count'), - 'rank': typenode.child_value('rank'), - 'halo': typenode.child_value('combo_type'), + "score": typenode.child_value("score"), + "count": typenode.child_value("count"), + "rank": typenode.child_value("rank"), + "halo": typenode.child_value("combo_type"), } if reclink not in scores: scores[reclink] = {} @@ -317,28 +317,28 @@ class DDRX2Client(BaseClient): def verify_game_load_c(self, ref_id: str) -> Dict[int, Dict[int, Dict[str, Any]]]: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'load_c') - game.set_attribute('refid', ref_id) - game.set_attribute('ver', '1') + game.set_attribute("method", "load_c") + game.set_attribute("refid", ref_id) + game.set_attribute("ver", "1") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) courses: Dict[int, Dict[int, Dict[str, Any]]] = {} self.assert_path(resp, "response/game/course") - courseblob = resp.child_value('game/course') + courseblob = resp.child_value("game/course") index = 0 - for chunk in [courseblob[i:(i + 8)] for i in range(0, len(courseblob), 8)]: + for chunk in [courseblob[i : (i + 8)] for i in range(0, len(courseblob), 8)]: if any([v != 0 for v in chunk]): course = int(index / 4) chart = index % 4 vals = { - 'score': chunk[0] * 10000 + chunk[1], - 'combo': chunk[2], - 'rank': chunk[3], - 'stage': chunk[5], - 'combo_type': chunk[6], + "score": chunk[0] * 10000 + chunk[1], + "combo": chunk[2], + "rank": chunk[3], + "stage": chunk[5], + "combo_type": chunk[6], } if course not in courses: courses[course] = {} @@ -347,45 +347,47 @@ class DDRX2Client(BaseClient): index = index + 1 return courses - def verify_game_save(self, ref_id: str, style: int, gauge: Optional[List[int]]=None) -> None: + def verify_game_save( + self, ref_id: str, style: int, gauge: Optional[List[int]] = None + ) -> None: gauge = gauge or [0, 0, 0, 0, 0] call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'save') - game.set_attribute('refid', ref_id) - game.set_attribute('ver', '1') - last = Node.void('last') + game.set_attribute("method", "save") + game.set_attribute("refid", ref_id) + game.set_attribute("ver", "1") + last = Node.void("last") game.add_child(last) - last.set_attribute('mode', '1') - last.set_attribute('style', str(style)) - gr = Node.void('gr') + last.set_attribute("mode", "1") + last.set_attribute("style", str(style)) + gr = Node.void("gr") game.add_child(gr) - gr.set_attribute('gr1', str(gauge[0])) - gr.set_attribute('gr2', str(gauge[1])) - gr.set_attribute('gr3', str(gauge[2])) - gr.set_attribute('gr4', str(gauge[3])) - gr.set_attribute('gr5', str(gauge[4])) + gr.set_attribute("gr1", str(gauge[0])) + gr.set_attribute("gr2", str(gauge[1])) + gr.set_attribute("gr3", str(gauge[2])) + gr.set_attribute("gr4", str(gauge[3])) + gr.set_attribute("gr5", str(gauge[4])) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") def verify_game_score(self, ref_id: str, songid: int, chart: int) -> List[int]: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'score') - game.set_attribute('mid', str(songid)) - game.set_attribute('refid', ref_id) - game.set_attribute('ver', '1') - game.set_attribute('type', str(chart)) + game.set_attribute("method", "score") + game.set_attribute("mid", str(songid)) + game.set_attribute("refid", ref_id) + game.set_attribute("ver", "1") + game.set_attribute("type", str(chart)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/@sc1") @@ -394,67 +396,67 @@ class DDRX2Client(BaseClient): self.assert_path(resp, "response/game/@sc4") self.assert_path(resp, "response/game/@sc5") return [ - int(resp.child('game').attribute('sc1')), - int(resp.child('game').attribute('sc2')), - int(resp.child('game').attribute('sc3')), - int(resp.child('game').attribute('sc4')), - int(resp.child('game').attribute('sc5')), + int(resp.child("game").attribute("sc1")), + int(resp.child("game").attribute("sc2")), + int(resp.child("game").attribute("sc3")), + int(resp.child("game").attribute("sc4")), + int(resp.child("game").attribute("sc5")), ] def verify_game_save_m(self, ref_id: str, score: Dict[str, Any]) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('refid', ref_id) - game.set_attribute('ver', '1') - game.set_attribute('mtype', str(score['chart'])) - game.set_attribute('mid', str(score['id'])) - game.set_attribute('method', 'save_m') - data = Node.void('data') + game.set_attribute("refid", ref_id) + game.set_attribute("ver", "1") + game.set_attribute("mtype", str(score["chart"])) + game.set_attribute("mid", str(score["id"])) + game.set_attribute("method", "save_m") + data = Node.void("data") game.add_child(data) - data.set_attribute('perf', '1' if score['halo'] >= 2 else '0') - data.set_attribute('score', str(score['score'])) - data.set_attribute('rank', str(score['rank'])) - data.set_attribute('phase', '1') - data.set_attribute('full', '1' if score['halo'] >= 1 else '0') - data.set_attribute('combo', str(score['combo'])) - option = Node.void('option') + data.set_attribute("perf", "1" if score["halo"] >= 2 else "0") + data.set_attribute("score", str(score["score"])) + data.set_attribute("rank", str(score["rank"])) + data.set_attribute("phase", "1") + data.set_attribute("full", "1" if score["halo"] >= 1 else "0") + data.set_attribute("combo", str(score["combo"])) + option = Node.void("option") game.add_child(option) - option.set_attribute('opt0', '6') - option.set_attribute('opt6', '1') - game.add_child(Node.u8_array('trace', [0] * 512)) - game.add_child(Node.u32('size', 512)) + option.set_attribute("opt0", "6") + option.set_attribute("opt6", "1") + game.add_child(Node.u8_array("trace", [0] * 512)) + game.add_child(Node.u32("size", 512)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") def verify_game_save_c(self, ref_id: str, course: Dict[str, Any]) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('ctype', str(course['chart'])) - game.set_attribute('cid', str(course['id'])) - game.set_attribute('method', 'save_c') - game.set_attribute('ver', '1') - game.set_attribute('refid', ref_id) - data = Node.void('data') + game.set_attribute("ctype", str(course["chart"])) + game.set_attribute("cid", str(course["id"])) + game.set_attribute("method", "save_c") + game.set_attribute("ver", "1") + game.set_attribute("refid", ref_id) + data = Node.void("data") game.add_child(data) - data.set_attribute('combo_type', str(course['combo_type'])) - data.set_attribute('clear', '1') - data.set_attribute('combo', str(course['combo'])) - data.set_attribute('opt', '32774') - data.set_attribute('per', '995') - data.set_attribute('score', str(course['score'])) - data.set_attribute('stage', str(course['stage'])) - data.set_attribute('rank', str(course['rank'])) - game.add_child(Node.u8_array('trace', [0] * 4096)) - game.add_child(Node.u32('size', 4096)) + data.set_attribute("combo_type", str(course["combo_type"])) + data.set_attribute("clear", "1") + data.set_attribute("combo", str(course["combo"])) + data.set_attribute("opt", "32774") + data.set_attribute("per", "995") + data.set_attribute("score", str(course["score"])) + data.set_attribute("stage", str(course["stage"])) + data.set_attribute("rank", str(course["rank"])) + game.add_child(Node.u8_array("trace", [0] * 4096)) + game.add_child(Node.u32("size", 4096)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") @@ -463,20 +465,20 @@ class DDRX2Client(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -500,24 +502,34 @@ class DDRX2Client(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Bishi doesn't read a new profile, it just writes out CSV for a blank one - self.verify_game_load(ref_id, msg_type='new') + self.verify_game_load(ref_id, msg_type="new") self.verify_game_new(ref_id) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Verify locking and unlocking profile ability self.verify_game_lock(ref_id, 1) @@ -525,54 +537,54 @@ class DDRX2Client(BaseClient): if cardid is None: # Verify empty profile - profile = self.verify_game_load(ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception('Profile has invalid name associated with it!') - if profile['single_plays'] != 0: - raise Exception('Profile has plays on single already!') - if profile['double_plays'] != 0: - raise Exception('Profile has plays on double already!') - if any([g != 0 for g in profile['groove_single']]): - raise Exception('Profile has single groove gauge values already!') - if any([g != 0 for g in profile['groove_double']]): - raise Exception('Profile has double groove gauge values already!') + profile = self.verify_game_load(ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception("Profile has invalid name associated with it!") + if profile["single_plays"] != 0: + raise Exception("Profile has plays on single already!") + if profile["double_plays"] != 0: + raise Exception("Profile has plays on double already!") + if any([g != 0 for g in profile["groove_single"]]): + raise Exception("Profile has single groove gauge values already!") + if any([g != 0 for g in profile["groove_double"]]): + raise Exception("Profile has double groove gauge values already!") # Verify empty scores scores = self.verify_game_load_m(ref_id) if len(scores) > 0: - raise Exception('Scores exist on new profile!') + raise Exception("Scores exist on new profile!") # Verify empty courses courses = self.verify_game_load_c(ref_id) if len(courses) > 0: - raise Exception('Courses exist on new profile!') + raise Exception("Courses exist on new profile!") # Verify profile saving self.verify_game_save(ref_id, 0, [1, 2, 3, 4, 5]) - profile = self.verify_game_load(ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception('Profile has invalid name associated with it!') - if profile['single_plays'] != 1: - raise Exception('Profile has invalid plays on single!') - if profile['double_plays'] != 0: - raise Exception('Profile has invalid plays on double!') - if profile['groove_single'] != [1, 2, 3, 4, 5]: - raise Exception('Profile has invalid single groove gauge values!') - if any([g != 0 for g in profile['groove_double']]): - raise Exception('Profile has invalid double groove gauge values!') + profile = self.verify_game_load(ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception("Profile has invalid name associated with it!") + if profile["single_plays"] != 1: + raise Exception("Profile has invalid plays on single!") + if profile["double_plays"] != 0: + raise Exception("Profile has invalid plays on double!") + if profile["groove_single"] != [1, 2, 3, 4, 5]: + raise Exception("Profile has invalid single groove gauge values!") + if any([g != 0 for g in profile["groove_double"]]): + raise Exception("Profile has invalid double groove gauge values!") self.verify_game_save(ref_id, 1, [5, 4, 3, 2, 1]) - profile = self.verify_game_load(ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception('Profile has invalid name associated with it!') - if profile['single_plays'] != 1: - raise Exception('Profile has invalid plays on single!') - if profile['double_plays'] != 1: - raise Exception('Profile has invalid plays on double!') - if profile['groove_single'] != [1, 2, 3, 4, 5]: - raise Exception('Profile has invalid single groove gauge values!') - if profile['groove_double'] != [5, 4, 3, 2, 1]: - raise Exception('Profile has invalid double groove gauge values!') + profile = self.verify_game_load(ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception("Profile has invalid name associated with it!") + if profile["single_plays"] != 1: + raise Exception("Profile has invalid plays on single!") + if profile["double_plays"] != 1: + raise Exception("Profile has invalid plays on double!") + if profile["groove_single"] != [1, 2, 3, 4, 5]: + raise Exception("Profile has invalid single groove gauge values!") + if profile["groove_double"] != [5, 4, 3, 2, 1]: + raise Exception("Profile has invalid double groove gauge values!") # Now, write some scores and verify saving for phase in [1, 2]: @@ -580,96 +592,112 @@ class DDRX2Client(BaseClient): dummyscores = [ # An okay score on a chart { - 'id': 524, - 'chart': 3, - 'score': 800000, - 'combo': 123, - 'rank': 4, - 'halo': 1, + "id": 524, + "chart": 3, + "score": 800000, + "combo": 123, + "rank": 4, + "halo": 1, }, # A good score on an easier chart same song { - 'id': 524, - 'chart': 2, - 'score': 990000, - 'combo': 321, - 'rank': 2, - 'halo': 2, + "id": 524, + "chart": 2, + "score": 990000, + "combo": 321, + "rank": 2, + "halo": 2, }, # A perfect score { - 'id': 483, - 'chart': 3, - 'score': 1000000, - 'combo': 400, - 'rank': 1, - 'halo': 3, + "id": 483, + "chart": 3, + "score": 1000000, + "combo": 400, + "rank": 1, + "halo": 3, }, # A bad score { - 'id': 483, - 'chart': 2, - 'score': 100000, - 'combo': 5, - 'rank': 7, - 'halo': 0, + "id": 483, + "chart": 2, + "score": 100000, + "combo": 5, + "rank": 7, + "halo": 0, }, ] if phase == 2: dummyscores = [ # A better score on a chart { - 'id': 524, - 'chart': 3, - 'score': 850000, - 'combo': 234, - 'rank': 3, - 'halo': 2, + "id": 524, + "chart": 3, + "score": 850000, + "combo": 234, + "rank": 3, + "halo": 2, }, # A worse score on another chart { - 'id': 524, - 'chart': 2, - 'score': 980000, - 'combo': 300, - 'rank': 3, - 'halo': 0, - 'expected_score': 990000, - 'expected_rank': 2, - 'expected_halo': 2, + "id": 524, + "chart": 2, + "score": 980000, + "combo": 300, + "rank": 3, + "halo": 0, + "expected_score": 990000, + "expected_rank": 2, + "expected_halo": 2, }, ] # Verify empty scores for starters if phase == 1: for score in dummyscores: - last_five = self.verify_game_score(ref_id, score['id'], score['chart']) + last_five = self.verify_game_score( + ref_id, score["id"], score["chart"] + ) if any([s != 0 for s in last_five]): - raise Exception('Score already found on song not played yet!') + raise Exception( + "Score already found on song not played yet!" + ) for score in dummyscores: self.verify_game_save_m(ref_id, score) scores = self.verify_game_load_m(ref_id) for score in dummyscores: - data = scores.get(score['id'], {}).get(score['chart'], None) + data = scores.get(score["id"], {}).get(score["chart"], None) if data is None: - raise Exception(f'Expected to get score back for song {score["id"]} chart {score["chart"]}!') + raise Exception( + f'Expected to get score back for song {score["id"]} chart {score["chart"]}!' + ) # Verify the attributes of the score - expected_score = score.get('expected_score', score['score']) - expected_rank = score.get('expected_rank', score['rank']) - expected_halo = score.get('expected_halo', score['halo']) + expected_score = score.get("expected_score", score["score"]) + expected_rank = score.get("expected_rank", score["rank"]) + expected_halo = score.get("expected_halo", score["halo"]) - if data['score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["score"]}\'') - if data['rank'] != expected_rank: - raise Exception(f'Expected a rank of \'{expected_rank}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got rank \'{data["rank"]}\'') - if data['halo'] != expected_halo: - raise Exception(f'Expected a halo of \'{expected_halo}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got halo \'{data["halo"]}\'') + if data["score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["score"]}\'' + ) + if data["rank"] != expected_rank: + raise Exception( + f'Expected a rank of \'{expected_rank}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got rank \'{data["rank"]}\'' + ) + if data["halo"] != expected_halo: + raise Exception( + f'Expected a halo of \'{expected_halo}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got halo \'{data["halo"]}\'' + ) # Verify that the last score is our score - last_five = self.verify_game_score(ref_id, score['id'], score['chart']) - if last_five[0] != score['score']: - raise Exception(f'Invalid score returned for last five scores on song {score["id"]} chart {score["chart"]}!') + last_five = self.verify_game_score( + ref_id, score["id"], score["chart"] + ) + if last_five[0] != score["score"]: + raise Exception( + f'Invalid score returned for last five scores on song {score["id"]} chart {score["chart"]}!' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) @@ -680,50 +708,50 @@ class DDRX2Client(BaseClient): dummycourses = [ # An okay score on a course { - 'id': 5, - 'chart': 3, - 'score': 800000, - 'combo': 123, - 'rank': 4, - 'stage': 5, - 'combo_type': 1, + "id": 5, + "chart": 3, + "score": 800000, + "combo": 123, + "rank": 4, + "stage": 5, + "combo_type": 1, }, # A good score on a different coruse { - 'id': 7, - 'chart': 2, - 'score': 600000, - 'combo': 23, - 'rank': 5, - 'stage': 5, - 'combo_type': 0, + "id": 7, + "chart": 2, + "score": 600000, + "combo": 23, + "rank": 5, + "stage": 5, + "combo_type": 0, }, ] if phase == 2: dummycourses = [ # A better score on the same course { - 'id': 5, - 'chart': 3, - 'score': 900000, - 'combo': 234, - 'rank': 3, - 'stage': 5, - 'combo_type': 1, + "id": 5, + "chart": 3, + "score": 900000, + "combo": 234, + "rank": 3, + "stage": 5, + "combo_type": 1, }, # A worse score on a different same course { - 'id': 7, - 'chart': 2, - 'score': 500000, - 'combo': 12, - 'rank': 7, - 'stage': 4, - 'combo_type': 0, - 'expected_score': 600000, - 'expected_combo': 23, - 'expected_rank': 5, - 'expected_stage': 5, + "id": 7, + "chart": 2, + "score": 500000, + "combo": 12, + "rank": 7, + "stage": 4, + "combo_type": 0, + "expected_score": 600000, + "expected_combo": 23, + "expected_rank": 5, + "expected_stage": 5, }, ] @@ -731,26 +759,40 @@ class DDRX2Client(BaseClient): self.verify_game_save_c(ref_id, course) courses = self.verify_game_load_c(ref_id) for course in dummycourses: - data = courses.get(course['id'], {}).get(course['chart'], None) + data = courses.get(course["id"], {}).get(course["chart"], None) if data is None: - raise Exception(f'Expected to get course back for course {course["id"]} chart {course["chart"]}!') + raise Exception( + f'Expected to get course back for course {course["id"]} chart {course["chart"]}!' + ) - expected_score = course.get('expected_score', course['score']) - expected_combo = course.get('expected_combo', course['combo']) - expected_rank = course.get('expected_rank', course['rank']) - expected_stage = course.get('expected_stage', course['stage']) - expected_combo_type = course.get('expected_combo_type', course['combo_type']) + expected_score = course.get("expected_score", course["score"]) + expected_combo = course.get("expected_combo", course["combo"]) + expected_rank = course.get("expected_rank", course["rank"]) + expected_stage = course.get("expected_stage", course["stage"]) + expected_combo_type = course.get( + "expected_combo_type", course["combo_type"] + ) - if data['score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for course \'{course["id"]}\' chart \'{course["chart"]}\' but got score \'{data["score"]}\'') - if data['combo'] != expected_combo: - raise Exception(f'Expected a combo of \'{expected_combo}\' for course \'{course["id"]}\' chart \'{course["chart"]}\' but got combo \'{data["combo"]}\'') - if data['rank'] != expected_rank: - raise Exception(f'Expected a rank of \'{expected_rank}\' for course \'{course["id"]}\' chart \'{course["chart"]}\' but got rank \'{data["rank"]}\'') - if data['stage'] != expected_stage: - raise Exception(f'Expected a stage of \'{expected_stage}\' for course \'{course["id"]}\' chart \'{course["chart"]}\' but got stage \'{data["stage"]}\'') - if data['combo_type'] != expected_combo_type: - raise Exception(f'Expected a combo_type of \'{expected_combo_type}\' for course \'{course["id"]}\' chart \'{course["chart"]}\' but got combo_type \'{data["combo_type"]}\'') + if data["score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for course \'{course["id"]}\' chart \'{course["chart"]}\' but got score \'{data["score"]}\'' + ) + if data["combo"] != expected_combo: + raise Exception( + f'Expected a combo of \'{expected_combo}\' for course \'{course["id"]}\' chart \'{course["chart"]}\' but got combo \'{data["combo"]}\'' + ) + if data["rank"] != expected_rank: + raise Exception( + f'Expected a rank of \'{expected_rank}\' for course \'{course["id"]}\' chart \'{course["chart"]}\' but got rank \'{data["rank"]}\'' + ) + if data["stage"] != expected_stage: + raise Exception( + f'Expected a stage of \'{expected_stage}\' for course \'{course["id"]}\' chart \'{course["chart"]}\' but got stage \'{data["stage"]}\'' + ) + if data["combo_type"] != expected_combo_type: + raise Exception( + f'Expected a combo_type of \'{expected_combo_type}\' for course \'{course["id"]}\' chart \'{course["chart"]}\' but got combo_type \'{data["combo_type"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) diff --git a/bemani/client/ddr/ddrx3.py b/bemani/client/ddr/ddrx3.py index 617ddab..0e22dcb 100644 --- a/bemani/client/ddr/ddrx3.py +++ b/bemani/client/ddr/ddrx3.py @@ -7,69 +7,69 @@ from bemani.protocol import Node class DDRX3Client(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_cardmng_getkeepspan(self) -> None: call = self.call_node() # Calculate model node - model = ':'.join(self.config['model'].split(':')[:4]) + model = ":".join(self.config["model"].split(":")[:4]) # Construct node - cardmng = Node.void('cardmng') - cardmng.set_attribute('method', 'getkeepspan') - cardmng.set_attribute('model', model) + cardmng = Node.void("cardmng") + cardmng.set_attribute("method", "getkeepspan") + cardmng.set_attribute("model", model) call.add_child(cardmng) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/cardmng/@keepspan") def verify_game_shop(self, loc: str) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'shop') - game.set_attribute('area', '51') - game.set_attribute('boot', '34') - game.set_attribute('close', '0') - game.set_attribute('close_t', '0') - game.set_attribute('coin', '02.01.--.--.01.G') - game.set_attribute('diff', '3') - game.set_attribute('during', '1') - game.set_attribute('first', '1') - game.set_attribute('ip', '1.5.7.3') - game.set_attribute('is_paseli', '1') - game.set_attribute('loc', loc) - game.set_attribute('mac', '00:11:22:33:44:55') - game.set_attribute('machine', '2') - game.set_attribute('name', 'TEST') - game.set_attribute('pay', '0') - game.set_attribute('region', '.') - game.set_attribute('soft', self.config['model']) - game.set_attribute('softid', self.pcbid) - game.set_attribute('stage', '1') - game.set_attribute('time', '60') - game.set_attribute('type', '0') - game.set_attribute('ver', '2012092400') + game.set_attribute("method", "shop") + game.set_attribute("area", "51") + game.set_attribute("boot", "34") + game.set_attribute("close", "0") + game.set_attribute("close_t", "0") + game.set_attribute("coin", "02.01.--.--.01.G") + game.set_attribute("diff", "3") + game.set_attribute("during", "1") + game.set_attribute("first", "1") + game.set_attribute("ip", "1.5.7.3") + game.set_attribute("is_paseli", "1") + game.set_attribute("loc", loc) + game.set_attribute("mac", "00:11:22:33:44:55") + game.set_attribute("machine", "2") + game.set_attribute("name", "TEST") + game.set_attribute("pay", "0") + game.set_attribute("region", ".") + game.set_attribute("soft", self.config["model"]) + game.set_attribute("softid", self.pcbid) + game.set_attribute("stage", "1") + game.set_attribute("time", "60") + game.set_attribute("type", "0") + game.set_attribute("ver", "2012092400") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/@stop") def verify_game_common(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'common') - game.set_attribute('ver', '2012092400') + game.set_attribute("method", "common") + game.set_attribute("ver", "2012092400") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/flag/@id") @@ -80,117 +80,117 @@ class DDRX3Client(BaseClient): def verify_game_hiscore(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'hiscore') - game.set_attribute('ver', '2012092400') + game.set_attribute("method", "hiscore") + game.set_attribute("ver", "2012092400") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") - for child in resp.child('game').children: - self.assert_path(child, 'music/@reclink_num') - self.assert_path(child, 'music/type/@diff') - self.assert_path(child, 'music/type/name') - self.assert_path(child, 'music/type/score') - self.assert_path(child, 'music/type/area') - self.assert_path(child, 'music/type/rank') - self.assert_path(child, 'music/type/combo_type') - self.assert_path(child, 'music/type/code') + for child in resp.child("game").children: + self.assert_path(child, "music/@reclink_num") + self.assert_path(child, "music/type/@diff") + self.assert_path(child, "music/type/name") + self.assert_path(child, "music/type/score") + self.assert_path(child, "music/type/area") + self.assert_path(child, "music/type/rank") + self.assert_path(child, "music/type/combo_type") + self.assert_path(child, "music/type/code") def verify_game_area_hiscore(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'area_hiscore') - game.set_attribute('shop_area', '51') - game.set_attribute('ver', '2012092400') + game.set_attribute("method", "area_hiscore") + game.set_attribute("shop_area", "51") + game.set_attribute("ver", "2012092400") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") - for child in resp.child('game').children: - self.assert_path(child, 'music/@reclink_num') - self.assert_path(child, 'music/type/@diff') - self.assert_path(child, 'music/type/name') - self.assert_path(child, 'music/type/score') - self.assert_path(child, 'music/type/area') - self.assert_path(child, 'music/type/rank') - self.assert_path(child, 'music/type/combo_type') - self.assert_path(child, 'music/type/code') + for child in resp.child("game").children: + self.assert_path(child, "music/@reclink_num") + self.assert_path(child, "music/type/@diff") + self.assert_path(child, "music/type/name") + self.assert_path(child, "music/type/score") + self.assert_path(child, "music/type/area") + self.assert_path(child, "music/type/rank") + self.assert_path(child, "music/type/combo_type") + self.assert_path(child, "music/type/code") def verify_game_message(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'message') - game.set_attribute('ver', '2012092400') + game.set_attribute("method", "message") + game.set_attribute("ver", "2012092400") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") def verify_game_ranking(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'ranking') - game.set_attribute('max', '10') - game.set_attribute('ver', '2012092400') + game.set_attribute("method", "ranking") + game.set_attribute("max", "10") + game.set_attribute("ver", "2012092400") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") def verify_game_log(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'log') - game.set_attribute('type', '0') - game.set_attribute('soft', self.config['model']) - game.set_attribute('softid', self.pcbid) - game.set_attribute('ver', '2012092400') - game.set_attribute('boot', '34') - game.set_attribute('mac', '00:11:22:33:44:55') - clear = Node.void('clear') + game.set_attribute("method", "log") + game.set_attribute("type", "0") + game.set_attribute("soft", self.config["model"]) + game.set_attribute("softid", self.pcbid) + game.set_attribute("ver", "2012092400") + game.set_attribute("boot", "34") + game.set_attribute("mac", "00:11:22:33:44:55") + clear = Node.void("clear") game.add_child(clear) - clear.set_attribute('book', '0') - clear.set_attribute('edit', '0') - clear.set_attribute('rank', '0') - clear.set_attribute('set', '0') - auto = Node.void('auto') + clear.set_attribute("book", "0") + clear.set_attribute("edit", "0") + clear.set_attribute("rank", "0") + clear.set_attribute("set", "0") + auto = Node.void("auto") game.add_child(auto) - auto.set_attribute('book', '1') - auto.set_attribute('edit', '1') - auto.set_attribute('rank', '1') - auto.set_attribute('set', '1') + auto.set_attribute("book", "1") + auto.set_attribute("edit", "1") + auto.set_attribute("rank", "1") + auto.set_attribute("set", "1") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") def verify_game_lock(self, ref_id: str, play: int) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('refid', ref_id) - game.set_attribute('method', 'lock') - game.set_attribute('ver', '2012092400') - game.set_attribute('play', str(play)) + game.set_attribute("refid", ref_id) + game.set_attribute("method", "lock") + game.set_attribute("ver", "2012092400") + game.set_attribute("play", str(play)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/@now_login") @@ -199,34 +199,34 @@ class DDRX3Client(BaseClient): # Pad the name to 8 characters name = self.NAME[:8] while len(name) < 8: - name = name + ' ' + name = name + " " call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'new') - game.set_attribute('ver', '2012092400') - game.set_attribute('name', name) - game.set_attribute('area', '51') - game.set_attribute('old', '0') - game.set_attribute('refid', ref_id) + game.set_attribute("method", "new") + game.set_attribute("ver", "2012092400") + game.set_attribute("name", name) + game.set_attribute("area", "51") + game.set_attribute("old", "0") + game.set_attribute("refid", ref_id) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") def verify_game_load_daily(self, ref_id: str) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'load_daily') - game.set_attribute('ver', '2012092400') - game.set_attribute('refid', ref_id) + game.set_attribute("method", "load_daily") + game.set_attribute("ver", "2012092400") + game.set_attribute("refid", ref_id) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/game/daycount/@playcount") self.assert_path(resp, "response/game/dailycombo/@daily_combo") @@ -234,20 +234,20 @@ class DDRX3Client(BaseClient): def verify_game_load(self, ref_id: str, msg_type: str) -> Dict[str, Any]: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'load') - game.set_attribute('ver', '2012092400') - game.set_attribute('refid', ref_id) + game.set_attribute("method", "load") + game.set_attribute("ver", "2012092400") + game.set_attribute("refid", ref_id) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) - if msg_type == 'new': + if msg_type == "new": # Verify that response is correct self.assert_path(resp, "response/game/@none") return {} - if msg_type == 'existing': + if msg_type == "existing": # Verify existing profile and return info self.assert_path(resp, "response/game/seq") self.assert_path(resp, "response/game/code") @@ -314,68 +314,68 @@ class DDRX3Client(BaseClient): for i in range(55): self.assert_path(resp, f"response/game/play_area/@play_cnt{i}") - gr_s = resp.child('game/gr_s') - gr_d = resp.child('game/gr_d') + gr_s = resp.child("game/gr_s") + gr_d = resp.child("game/gr_d") return { - 'name': resp.child_value('game/name'), - 'single_plays': resp.child_value('game/cnt_s'), - 'double_plays': resp.child_value('game/cnt_d'), - 'groove_single': [ - int(gr_s.attribute('gr1')), - int(gr_s.attribute('gr2')), - int(gr_s.attribute('gr3')), - int(gr_s.attribute('gr4')), - int(gr_s.attribute('gr5')), + "name": resp.child_value("game/name"), + "single_plays": resp.child_value("game/cnt_s"), + "double_plays": resp.child_value("game/cnt_d"), + "groove_single": [ + int(gr_s.attribute("gr1")), + int(gr_s.attribute("gr2")), + int(gr_s.attribute("gr3")), + int(gr_s.attribute("gr4")), + int(gr_s.attribute("gr5")), ], - 'groove_double': [ - int(gr_d.attribute('gr1')), - int(gr_d.attribute('gr2')), - int(gr_d.attribute('gr3')), - int(gr_d.attribute('gr4')), - int(gr_d.attribute('gr5')), + "groove_double": [ + int(gr_d.attribute("gr1")), + int(gr_d.attribute("gr2")), + int(gr_d.attribute("gr3")), + int(gr_d.attribute("gr4")), + int(gr_d.attribute("gr5")), ], } - raise Exception('Unknown load type!') + raise Exception("Unknown load type!") def verify_game_load_m(self, ref_id: str) -> Dict[int, Dict[int, Dict[str, Any]]]: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('ver', '2012092400') - game.set_attribute('all', '1') - game.set_attribute('refid', ref_id) - game.set_attribute('method', 'load_m') + game.set_attribute("ver", "2012092400") + game.set_attribute("all", "1") + game.set_attribute("refid", ref_id) + game.set_attribute("method", "load_m") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct scores: Dict[int, Dict[int, Dict[str, Any]]] = {} self.assert_path(resp, "response/game") - for child in resp.child('game').children: - self.assert_path(child, 'music/@reclink') - reclink = int(child.attribute('reclink')) + for child in resp.child("game").children: + self.assert_path(child, "music/@reclink") + reclink = int(child.attribute("reclink")) for typenode in child.children: - self.assert_path(typenode, 'type/@diff') - self.assert_path(typenode, 'type/score') - self.assert_path(typenode, 'type/count') - self.assert_path(typenode, 'type/rank') - self.assert_path(typenode, 'type/combo_type') - self.assert_path(typenode, 'type/score_2nd') - self.assert_path(typenode, 'type/rank_2nd') - self.assert_path(typenode, 'type/cnt_2nd') - chart = int(typenode.attribute('diff')) + self.assert_path(typenode, "type/@diff") + self.assert_path(typenode, "type/score") + self.assert_path(typenode, "type/count") + self.assert_path(typenode, "type/rank") + self.assert_path(typenode, "type/combo_type") + self.assert_path(typenode, "type/score_2nd") + self.assert_path(typenode, "type/rank_2nd") + self.assert_path(typenode, "type/cnt_2nd") + chart = int(typenode.attribute("diff")) vals = { - 'score': typenode.child_value('score'), - 'count': typenode.child_value('count'), - 'rank': typenode.child_value('rank'), - 'halo': typenode.child_value('combo_type'), - 'score_2nd': typenode.child_value('score_2nd'), - 'rank_2nd': typenode.child_value('rank_2nd'), - 'cnt_2nd': typenode.child_value('cnt_2nd'), + "score": typenode.child_value("score"), + "count": typenode.child_value("count"), + "rank": typenode.child_value("rank"), + "halo": typenode.child_value("combo_type"), + "score_2nd": typenode.child_value("score_2nd"), + "rank_2nd": typenode.child_value("rank_2nd"), + "cnt_2nd": typenode.child_value("cnt_2nd"), } if reclink not in scores: scores[reclink] = {} @@ -384,28 +384,28 @@ class DDRX3Client(BaseClient): def verify_game_load_c(self, ref_id: str) -> Dict[int, Dict[int, Dict[str, Any]]]: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'load_c') - game.set_attribute('refid', ref_id) - game.set_attribute('ver', '2012092400') + game.set_attribute("method", "load_c") + game.set_attribute("refid", ref_id) + game.set_attribute("ver", "2012092400") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) courses: Dict[int, Dict[int, Dict[str, Any]]] = {} self.assert_path(resp, "response/game/course") - courseblob = resp.child_value('game/course') + courseblob = resp.child_value("game/course") index = 0 - for chunk in [courseblob[i:(i + 8)] for i in range(0, len(courseblob), 8)]: + for chunk in [courseblob[i : (i + 8)] for i in range(0, len(courseblob), 8)]: if any([v != 0 for v in chunk]): course = int(index / 4) chart = index % 4 vals = { - 'score': chunk[0] * 10000 + chunk[1], - 'combo': chunk[2], - 'rank': chunk[3], - 'stage': chunk[5], - 'combo_type': chunk[6], + "score": chunk[0] * 10000 + chunk[1], + "combo": chunk[2], + "rank": chunk[3], + "stage": chunk[5], + "combo_type": chunk[6], } if course not in courses: courses[course] = {} @@ -414,46 +414,48 @@ class DDRX3Client(BaseClient): index = index + 1 return courses - def verify_game_save(self, ref_id: str, style: int, gauge: Optional[List[int]]=None) -> None: + def verify_game_save( + self, ref_id: str, style: int, gauge: Optional[List[int]] = None + ) -> None: gauge = gauge or [0, 0, 0, 0, 0] call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'save') - game.set_attribute('refid', ref_id) - game.set_attribute('ver', '2012092400') - game.set_attribute('shop_area', '51') - last = Node.void('last') + game.set_attribute("method", "save") + game.set_attribute("refid", ref_id) + game.set_attribute("ver", "2012092400") + game.set_attribute("shop_area", "51") + last = Node.void("last") game.add_child(last) - last.set_attribute('mode', '1') - last.set_attribute('style', str(style)) - gr = Node.void('gr') + last.set_attribute("mode", "1") + last.set_attribute("style", str(style)) + gr = Node.void("gr") game.add_child(gr) - gr.set_attribute('gr1', str(gauge[0])) - gr.set_attribute('gr2', str(gauge[1])) - gr.set_attribute('gr3', str(gauge[2])) - gr.set_attribute('gr4', str(gauge[3])) - gr.set_attribute('gr5', str(gauge[4])) + gr.set_attribute("gr1", str(gauge[0])) + gr.set_attribute("gr2", str(gauge[1])) + gr.set_attribute("gr3", str(gauge[2])) + gr.set_attribute("gr4", str(gauge[3])) + gr.set_attribute("gr5", str(gauge[4])) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") def verify_game_score(self, ref_id: str, songid: int, chart: int) -> List[int]: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'score') - game.set_attribute('mid', str(songid)) - game.set_attribute('refid', ref_id) - game.set_attribute('ver', '2012092400') - game.set_attribute('type', str(chart)) + game.set_attribute("method", "score") + game.set_attribute("mid", str(songid)) + game.set_attribute("refid", ref_id) + game.set_attribute("ver", "2012092400") + game.set_attribute("type", str(chart)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/@sc1") @@ -462,75 +464,75 @@ class DDRX3Client(BaseClient): self.assert_path(resp, "response/game/@sc4") self.assert_path(resp, "response/game/@sc5") return [ - int(resp.child('game').attribute('sc1')), - int(resp.child('game').attribute('sc2')), - int(resp.child('game').attribute('sc3')), - int(resp.child('game').attribute('sc4')), - int(resp.child('game').attribute('sc5')), + int(resp.child("game").attribute("sc1")), + int(resp.child("game").attribute("sc2")), + int(resp.child("game").attribute("sc3")), + int(resp.child("game").attribute("sc4")), + int(resp.child("game").attribute("sc5")), ] def verify_game_save_m(self, ref_id: str, score: Dict[str, Any]) -> None: - if score['score'] == 0 and score['score_2nd'] == 0: - raise Exception('Must store either 2ndMIX or regular score!') - if score['score'] != 0 and score['score_2nd'] != 0: - raise Exception('Must store either 2ndMIX or regular score!') + if score["score"] == 0 and score["score_2nd"] == 0: + raise Exception("Must store either 2ndMIX or regular score!") + if score["score"] != 0 and score["score_2nd"] != 0: + raise Exception("Must store either 2ndMIX or regular score!") call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('refid', ref_id) - game.set_attribute('ver', '2012092400') - game.set_attribute('mtype', str(score['chart'])) - game.set_attribute('mid', str(score['id'])) - game.set_attribute('method', 'save_m') - data = Node.void('data') + game.set_attribute("refid", ref_id) + game.set_attribute("ver", "2012092400") + game.set_attribute("mtype", str(score["chart"])) + game.set_attribute("mid", str(score["id"])) + game.set_attribute("method", "save_m") + data = Node.void("data") game.add_child(data) - data.set_attribute('perf', '1' if score['halo'] >= 2 else '0') - data.set_attribute('score', str(score['score'])) - data.set_attribute('rank', str(score['rank'])) - data.set_attribute('phase', '1') - data.set_attribute('full', '1' if score['halo'] >= 1 else '0') - data.set_attribute('combo', str(score['combo'])) - data.set_attribute('playmode', str(1 if score['score'] > 0 else 5)) - data.set_attribute('rank_2nd', str(score['rank_2nd'])) - data.set_attribute('score_2nd', str(score['score_2nd'])) - data.set_attribute('combo_2nd', str(score['combo_2nd'])) - option = Node.void('option') + data.set_attribute("perf", "1" if score["halo"] >= 2 else "0") + data.set_attribute("score", str(score["score"])) + data.set_attribute("rank", str(score["rank"])) + data.set_attribute("phase", "1") + data.set_attribute("full", "1" if score["halo"] >= 1 else "0") + data.set_attribute("combo", str(score["combo"])) + data.set_attribute("playmode", str(1 if score["score"] > 0 else 5)) + data.set_attribute("rank_2nd", str(score["rank_2nd"])) + data.set_attribute("score_2nd", str(score["score_2nd"])) + data.set_attribute("combo_2nd", str(score["combo_2nd"])) + option = Node.void("option") game.add_child(option) - option.set_attribute('opt0', '6') - option.set_attribute('opt6', '1') - game.add_child(Node.u8_array('trace', [0] * 512)) - game.add_child(Node.u32('size', 512)) + option.set_attribute("opt0", "6") + option.set_attribute("opt6", "1") + game.add_child(Node.u8_array("trace", [0] * 512)) + game.add_child(Node.u32("size", 512)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") def verify_game_save_c(self, ref_id: str, course: Dict[str, Any]) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('ctype', str(course['chart'])) - game.set_attribute('cid', str(course['id'])) - game.set_attribute('method', 'save_c') - game.set_attribute('ver', '2012092400') - game.set_attribute('refid', ref_id) - data = Node.void('data') + game.set_attribute("ctype", str(course["chart"])) + game.set_attribute("cid", str(course["id"])) + game.set_attribute("method", "save_c") + game.set_attribute("ver", "2012092400") + game.set_attribute("refid", ref_id) + data = Node.void("data") game.add_child(data) - data.set_attribute('combo_type', str(course['combo_type'])) - data.set_attribute('clear', '1') - data.set_attribute('combo', str(course['combo'])) - data.set_attribute('opt', '32774') - data.set_attribute('per', '995') - data.set_attribute('score', str(course['score'])) - data.set_attribute('stage', str(course['stage'])) - data.set_attribute('rank', str(course['rank'])) - game.add_child(Node.u8_array('trace', [0] * 4096)) - game.add_child(Node.u32('size', 4096)) + data.set_attribute("combo_type", str(course["combo_type"])) + data.set_attribute("clear", "1") + data.set_attribute("combo", str(course["combo"])) + data.set_attribute("opt", "32774") + data.set_attribute("per", "995") + data.set_attribute("score", str(course["score"])) + data.set_attribute("stage", str(course["stage"])) + data.set_attribute("rank", str(course["rank"])) + game.add_child(Node.u8_array("trace", [0] * 4096)) + game.add_child(Node.u32("size", 4096)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") @@ -539,26 +541,26 @@ class DDRX3Client(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() self.verify_message_get() self.verify_package_list() - location = self.verify_facility_get('EUC_JP') + location = self.verify_facility_get("EUC_JP") self.verify_pcbevent_put() self.verify_cardmng_getkeepspan() self.verify_game_shop(location) @@ -577,24 +579,34 @@ class DDRX3Client(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Bishi doesn't read a new profile, it just writes out CSV for a blank one - self.verify_game_load(ref_id, msg_type='new') + self.verify_game_load(ref_id, msg_type="new") self.verify_game_new(ref_id) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Verify locking and unlocking profile ability self.verify_game_lock(ref_id, 1) @@ -602,56 +614,56 @@ class DDRX3Client(BaseClient): if cardid is None: # Verify empty profile - profile = self.verify_game_load(ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception('Profile has invalid name associated with it!') - if profile['single_plays'] != 0: - raise Exception('Profile has plays on single already!') - if profile['double_plays'] != 0: - raise Exception('Profile has plays on double already!') - if any([g != 0 for g in profile['groove_single']]): - raise Exception('Profile has single groove gauge values already!') - if any([g != 0 for g in profile['groove_double']]): - raise Exception('Profile has double groove gauge values already!') + profile = self.verify_game_load(ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception("Profile has invalid name associated with it!") + if profile["single_plays"] != 0: + raise Exception("Profile has plays on single already!") + if profile["double_plays"] != 0: + raise Exception("Profile has plays on double already!") + if any([g != 0 for g in profile["groove_single"]]): + raise Exception("Profile has single groove gauge values already!") + if any([g != 0 for g in profile["groove_double"]]): + raise Exception("Profile has double groove gauge values already!") # Verify empty scores scores = self.verify_game_load_m(ref_id) if len(scores) > 0: - raise Exception('Scores exist on new profile!') + raise Exception("Scores exist on new profile!") # Verify empty courses courses = self.verify_game_load_c(ref_id) if len(courses) > 0: - raise Exception('Courses exist on new profile!') + raise Exception("Courses exist on new profile!") self.verify_game_load_daily(ref_id) # Verify profile saving self.verify_game_save(ref_id, 0, [1, 2, 3, 4, 5]) - profile = self.verify_game_load(ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception('Profile has invalid name associated with it!') - if profile['single_plays'] != 1: - raise Exception('Profile has invalid plays on single!') - if profile['double_plays'] != 0: - raise Exception('Profile has invalid plays on double!') - if profile['groove_single'] != [1, 2, 3, 4, 5]: - raise Exception('Profile has invalid single groove gauge values!') - if any([g != 0 for g in profile['groove_double']]): - raise Exception('Profile has invalid double groove gauge values!') + profile = self.verify_game_load(ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception("Profile has invalid name associated with it!") + if profile["single_plays"] != 1: + raise Exception("Profile has invalid plays on single!") + if profile["double_plays"] != 0: + raise Exception("Profile has invalid plays on double!") + if profile["groove_single"] != [1, 2, 3, 4, 5]: + raise Exception("Profile has invalid single groove gauge values!") + if any([g != 0 for g in profile["groove_double"]]): + raise Exception("Profile has invalid double groove gauge values!") self.verify_game_save(ref_id, 1, [5, 4, 3, 2, 1]) - profile = self.verify_game_load(ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception('Profile has invalid name associated with it!') - if profile['single_plays'] != 1: - raise Exception('Profile has invalid plays on single!') - if profile['double_plays'] != 1: - raise Exception('Profile has invalid plays on double!') - if profile['groove_single'] != [1, 2, 3, 4, 5]: - raise Exception('Profile has invalid single groove gauge values!') - if profile['groove_double'] != [5, 4, 3, 2, 1]: - raise Exception('Profile has invalid double groove gauge values!') + profile = self.verify_game_load(ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception("Profile has invalid name associated with it!") + if profile["single_plays"] != 1: + raise Exception("Profile has invalid plays on single!") + if profile["double_plays"] != 1: + raise Exception("Profile has invalid plays on double!") + if profile["groove_single"] != [1, 2, 3, 4, 5]: + raise Exception("Profile has invalid single groove gauge values!") + if profile["groove_double"] != [5, 4, 3, 2, 1]: + raise Exception("Profile has invalid double groove gauge values!") # Now, write some scores and verify saving for phase in [1, 2]: @@ -659,148 +671,172 @@ class DDRX3Client(BaseClient): dummyscores = [ # An okay score on a chart { - 'id': 524, - 'chart': 3, - 'score': 800000, - 'combo': 123, - 'rank': 4, - 'halo': 1, - 'score_2nd': 0, - 'combo_2nd': 0, - 'rank_2nd': 0, + "id": 524, + "chart": 3, + "score": 800000, + "combo": 123, + "rank": 4, + "halo": 1, + "score_2nd": 0, + "combo_2nd": 0, + "rank_2nd": 0, }, # A good score on an easier chart same song { - 'id': 524, - 'chart': 2, - 'score': 990000, - 'combo': 321, - 'rank': 2, - 'halo': 2, - 'score_2nd': 0, - 'combo_2nd': 0, - 'rank_2nd': 0, + "id": 524, + "chart": 2, + "score": 990000, + "combo": 321, + "rank": 2, + "halo": 2, + "score_2nd": 0, + "combo_2nd": 0, + "rank_2nd": 0, }, # A perfect score { - 'id': 483, - 'chart': 3, - 'score': 1000000, - 'combo': 400, - 'rank': 1, - 'halo': 3, - 'score_2nd': 0, - 'combo_2nd': 0, - 'rank_2nd': 0, + "id": 483, + "chart": 3, + "score": 1000000, + "combo": 400, + "rank": 1, + "halo": 3, + "score_2nd": 0, + "combo_2nd": 0, + "rank_2nd": 0, }, # A bad score { - 'id': 483, - 'chart': 2, - 'score': 100000, - 'combo': 5, - 'rank': 7, - 'halo': 0, - 'score_2nd': 0, - 'combo_2nd': 0, - 'rank_2nd': 0, + "id": 483, + "chart": 2, + "score": 100000, + "combo": 5, + "rank": 7, + "halo": 0, + "score_2nd": 0, + "combo_2nd": 0, + "rank_2nd": 0, }, # A score on 2ndMIX { - 'id': 483, - 'chart': 3, - 'score': 0, - 'combo': 0, - 'rank': 0, - 'halo': 0, - 'score_2nd': 21608500, - 'combo_2nd': 158, - 'rank_2nd': 0, + "id": 483, + "chart": 3, + "score": 0, + "combo": 0, + "rank": 0, + "halo": 0, + "score_2nd": 21608500, + "combo_2nd": 158, + "rank_2nd": 0, }, ] if phase == 2: dummyscores = [ # A better score on a chart { - 'id': 524, - 'chart': 3, - 'score': 850000, - 'combo': 234, - 'rank': 3, - 'halo': 2, - 'score_2nd': 0, - 'combo_2nd': 0, - 'rank_2nd': 0, + "id": 524, + "chart": 3, + "score": 850000, + "combo": 234, + "rank": 3, + "halo": 2, + "score_2nd": 0, + "combo_2nd": 0, + "rank_2nd": 0, }, # A worse score on another chart { - 'id': 524, - 'chart': 2, - 'score': 980000, - 'combo': 300, - 'rank': 3, - 'halo': 0, - 'expected_score': 990000, - 'expected_rank': 2, - 'expected_halo': 2, - 'score_2nd': 0, - 'combo_2nd': 0, - 'rank_2nd': 0, + "id": 524, + "chart": 2, + "score": 980000, + "combo": 300, + "rank": 3, + "halo": 0, + "expected_score": 990000, + "expected_rank": 2, + "expected_halo": 2, + "score_2nd": 0, + "combo_2nd": 0, + "rank_2nd": 0, }, # A worse score on 2ndMIX { - 'id': 483, - 'chart': 3, - 'score': 0, - 'combo': 0, - 'rank': 0, - 'halo': 0, - 'score_2nd': 11608500, - 'combo_2nd': 125, - 'rank_2nd': 1, - 'expected_score_2nd': 21608500, - 'expected_rank_2nd': 0, + "id": 483, + "chart": 3, + "score": 0, + "combo": 0, + "rank": 0, + "halo": 0, + "score_2nd": 11608500, + "combo_2nd": 125, + "rank_2nd": 1, + "expected_score_2nd": 21608500, + "expected_rank_2nd": 0, }, ] # Verify empty scores for starters if phase == 1: for score in dummyscores: - last_five = self.verify_game_score(ref_id, score['id'], score['chart']) + last_five = self.verify_game_score( + ref_id, score["id"], score["chart"] + ) if any([s != 0 for s in last_five]): - raise Exception('Score already found on song not played yet!') + raise Exception( + "Score already found on song not played yet!" + ) for score in dummyscores: self.verify_game_save_m(ref_id, score) scores = self.verify_game_load_m(ref_id) for score in dummyscores: - data = scores.get(score['id'], {}).get(score['chart'], None) + data = scores.get(score["id"], {}).get(score["chart"], None) if data is None: - raise Exception(f'Expected to get score back for song {score["id"]} chart {score["chart"]}!') + raise Exception( + f'Expected to get score back for song {score["id"]} chart {score["chart"]}!' + ) # Verify the attributes of the score - expected_score = score.get('expected_score', score['score']) - expected_rank = score.get('expected_rank', score['rank']) - expected_halo = score.get('expected_halo', score['halo']) - expected_score_2nd = score.get('expected_score_2nd', score['score_2nd']) - expected_rank_2nd = score.get('expected_rank_2nd', score['rank_2nd']) + expected_score = score.get("expected_score", score["score"]) + expected_rank = score.get("expected_rank", score["rank"]) + expected_halo = score.get("expected_halo", score["halo"]) + expected_score_2nd = score.get( + "expected_score_2nd", score["score_2nd"] + ) + expected_rank_2nd = score.get( + "expected_rank_2nd", score["rank_2nd"] + ) - if score['score'] != 0: - if data['score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["score"]}\'') - if data['rank'] != expected_rank: - raise Exception(f'Expected a rank of \'{expected_rank}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got rank \'{data["rank"]}\'') - if data['halo'] != expected_halo: - raise Exception(f'Expected a halo of \'{expected_halo}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got halo \'{data["halo"]}\'') + if score["score"] != 0: + if data["score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["score"]}\'' + ) + if data["rank"] != expected_rank: + raise Exception( + f'Expected a rank of \'{expected_rank}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got rank \'{data["rank"]}\'' + ) + if data["halo"] != expected_halo: + raise Exception( + f'Expected a halo of \'{expected_halo}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got halo \'{data["halo"]}\'' + ) # Verify that the last score is our score - last_five = self.verify_game_score(ref_id, score['id'], score['chart']) - if last_five[0] != score['score']: - raise Exception(f'Invalid score returned for last five scores on song {score["id"]} chart {score["chart"]}!') - if score['score_2nd'] != 0: - if data['score_2nd'] != expected_score_2nd: - raise Exception(f'Expected a score_2nd of \'{expected_score_2nd}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score_2nd \'{data["score_2nd"]}\'') - if data['rank_2nd'] != expected_rank_2nd: - raise Exception(f'Expected a rank_2nd of \'{expected_rank_2nd}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got rank_2nd \'{data["rank_2nd"]}\'') + last_five = self.verify_game_score( + ref_id, score["id"], score["chart"] + ) + if last_five[0] != score["score"]: + raise Exception( + f'Invalid score returned for last five scores on song {score["id"]} chart {score["chart"]}!' + ) + if score["score_2nd"] != 0: + if data["score_2nd"] != expected_score_2nd: + raise Exception( + f'Expected a score_2nd of \'{expected_score_2nd}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score_2nd \'{data["score_2nd"]}\'' + ) + if data["rank_2nd"] != expected_rank_2nd: + raise Exception( + f'Expected a rank_2nd of \'{expected_rank_2nd}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got rank_2nd \'{data["rank_2nd"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) @@ -811,50 +847,50 @@ class DDRX3Client(BaseClient): dummycourses = [ # An okay score on a course { - 'id': 5, - 'chart': 3, - 'score': 800000, - 'combo': 123, - 'rank': 4, - 'stage': 5, - 'combo_type': 1, + "id": 5, + "chart": 3, + "score": 800000, + "combo": 123, + "rank": 4, + "stage": 5, + "combo_type": 1, }, # A good score on a different coruse { - 'id': 7, - 'chart': 2, - 'score': 600000, - 'combo': 23, - 'rank': 5, - 'stage': 5, - 'combo_type': 0, + "id": 7, + "chart": 2, + "score": 600000, + "combo": 23, + "rank": 5, + "stage": 5, + "combo_type": 0, }, ] if phase == 2: dummycourses = [ # A better score on the same course { - 'id': 5, - 'chart': 3, - 'score': 900000, - 'combo': 234, - 'rank': 3, - 'stage': 5, - 'combo_type': 1, + "id": 5, + "chart": 3, + "score": 900000, + "combo": 234, + "rank": 3, + "stage": 5, + "combo_type": 1, }, # A worse score on a different same course { - 'id': 7, - 'chart': 2, - 'score': 500000, - 'combo': 12, - 'rank': 7, - 'stage': 4, - 'combo_type': 0, - 'expected_score': 600000, - 'expected_combo': 23, - 'expected_rank': 5, - 'expected_stage': 5, + "id": 7, + "chart": 2, + "score": 500000, + "combo": 12, + "rank": 7, + "stage": 4, + "combo_type": 0, + "expected_score": 600000, + "expected_combo": 23, + "expected_rank": 5, + "expected_stage": 5, }, ] @@ -862,26 +898,40 @@ class DDRX3Client(BaseClient): self.verify_game_save_c(ref_id, course) courses = self.verify_game_load_c(ref_id) for course in dummycourses: - data = courses.get(course['id'], {}).get(course['chart'], None) + data = courses.get(course["id"], {}).get(course["chart"], None) if data is None: - raise Exception(f'Expected to get course back for course {course["id"]} chart {course["chart"]}!') + raise Exception( + f'Expected to get course back for course {course["id"]} chart {course["chart"]}!' + ) - expected_score = course.get('expected_score', course['score']) - expected_combo = course.get('expected_combo', course['combo']) - expected_rank = course.get('expected_rank', course['rank']) - expected_stage = course.get('expected_stage', course['stage']) - expected_combo_type = course.get('expected_combo_type', course['combo_type']) + expected_score = course.get("expected_score", course["score"]) + expected_combo = course.get("expected_combo", course["combo"]) + expected_rank = course.get("expected_rank", course["rank"]) + expected_stage = course.get("expected_stage", course["stage"]) + expected_combo_type = course.get( + "expected_combo_type", course["combo_type"] + ) - if data['score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for course \'{course["id"]}\' chart \'{course["chart"]}\' but got score \'{data["score"]}\'') - if data['combo'] != expected_combo: - raise Exception(f'Expected a combo of \'{expected_combo}\' for course \'{course["id"]}\' chart \'{course["chart"]}\' but got combo \'{data["combo"]}\'') - if data['rank'] != expected_rank: - raise Exception(f'Expected a rank of \'{expected_rank}\' for course \'{course["id"]}\' chart \'{course["chart"]}\' but got rank \'{data["rank"]}\'') - if data['stage'] != expected_stage: - raise Exception(f'Expected a stage of \'{expected_stage}\' for course \'{course["id"]}\' chart \'{course["chart"]}\' but got stage \'{data["stage"]}\'') - if data['combo_type'] != expected_combo_type: - raise Exception(f'Expected a combo_type of \'{expected_combo_type}\' for course \'{course["id"]}\' chart \'{course["chart"]}\' but got combo_type \'{data["combo_type"]}\'') + if data["score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for course \'{course["id"]}\' chart \'{course["chart"]}\' but got score \'{data["score"]}\'' + ) + if data["combo"] != expected_combo: + raise Exception( + f'Expected a combo of \'{expected_combo}\' for course \'{course["id"]}\' chart \'{course["chart"]}\' but got combo \'{data["combo"]}\'' + ) + if data["rank"] != expected_rank: + raise Exception( + f'Expected a rank of \'{expected_rank}\' for course \'{course["id"]}\' chart \'{course["chart"]}\' but got rank \'{data["rank"]}\'' + ) + if data["stage"] != expected_stage: + raise Exception( + f'Expected a stage of \'{expected_stage}\' for course \'{course["id"]}\' chart \'{course["chart"]}\' but got stage \'{data["stage"]}\'' + ) + if data["combo_type"] != expected_combo_type: + raise Exception( + f'Expected a combo_type of \'{expected_combo_type}\' for course \'{course["id"]}\' chart \'{course["chart"]}\' but got combo_type \'{data["combo_type"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) diff --git a/bemani/client/iidx/cannonballers.py b/bemani/client/iidx/cannonballers.py index 9a37a2a..47b3d41 100644 --- a/bemani/client/iidx/cannonballers.py +++ b/bemani/client/iidx/cannonballers.py @@ -7,19 +7,19 @@ from bemani.protocol import Node class IIDXCannonBallersClient(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_iidx25shop_getname(self, lid: str) -> str: call = self.call_node() # Construct node - IIDX25shop = Node.void('IIDX25shop') + IIDX25shop = Node.void("IIDX25shop") call.add_child(IIDX25shop) - IIDX25shop.set_attribute('method', 'getname') - IIDX25shop.set_attribute('lid', lid) + IIDX25shop.set_attribute("method", "getname") + IIDX25shop.set_attribute("lid", lid) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX25shop/@opname") @@ -28,25 +28,25 @@ class IIDXCannonBallersClient(BaseClient): self.assert_path(resp, "response/IIDX25shop/@hr") self.assert_path(resp, "response/IIDX25shop/@mi") - return resp.child('IIDX25shop').attribute('opname') + return resp.child("IIDX25shop").attribute("opname") def verify_iidx25shop_savename(self, lid: str, name: str) -> None: call = self.call_node() # Construct node - IIDX25shop = Node.void('IIDX25shop') - IIDX25shop.set_attribute('lid', lid) - IIDX25shop.set_attribute('pid', '51') - IIDX25shop.set_attribute('method', 'savename') - IIDX25shop.set_attribute('cls_opt', '0') - IIDX25shop.set_attribute('ccode', 'US') - IIDX25shop.set_attribute('opname', name) - IIDX25shop.set_attribute('rcode', '.') + IIDX25shop = Node.void("IIDX25shop") + IIDX25shop.set_attribute("lid", lid) + IIDX25shop.set_attribute("pid", "51") + IIDX25shop.set_attribute("method", "savename") + IIDX25shop.set_attribute("cls_opt", "0") + IIDX25shop.set_attribute("ccode", "US") + IIDX25shop.set_attribute("opname", name) + IIDX25shop.set_attribute("rcode", ".") call.add_child(IIDX25shop) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX25shop") @@ -55,12 +55,12 @@ class IIDXCannonBallersClient(BaseClient): call = self.call_node() # Construct node - IIDX25pc = Node.void('IIDX25pc') + IIDX25pc = Node.void("IIDX25pc") call.add_child(IIDX25pc) - IIDX25pc.set_attribute('method', 'common') + IIDX25pc.set_attribute("method", "common") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX25pc/ir/@beat") @@ -76,34 +76,36 @@ class IIDXCannonBallersClient(BaseClient): call = self.call_node() # Construct node - IIDX25pc = Node.void('IIDX25music') + IIDX25pc = Node.void("IIDX25music") call.add_child(IIDX25pc) - IIDX25pc.set_attribute('method', 'crate') + IIDX25pc.set_attribute("method", "crate") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX25music") for child in resp.child("IIDX25music").children: - if child.name != 'c': - raise Exception(f'Invalid node {child} in clear rate response!') + if child.name != "c": + raise Exception(f"Invalid node {child} in clear rate response!") if len(child.value) != 12: - raise Exception(f'Invalid node data {child} in clear rate response!') + raise Exception(f"Invalid node data {child} in clear rate response!") for v in child.value: if v < 0 or v > 1001: - raise Exception(f'Invalid clear percent {child} in clear rate response!') + raise Exception( + f"Invalid clear percent {child} in clear rate response!" + ) def verify_iidx25shop_getconvention(self, lid: str) -> None: call = self.call_node() # Construct node - IIDX25pc = Node.void('IIDX25shop') + IIDX25pc = Node.void("IIDX25shop") call.add_child(IIDX25pc) - IIDX25pc.set_attribute('method', 'getconvention') - IIDX25pc.set_attribute('lid', lid) + IIDX25pc.set_attribute("method", "getconvention") + IIDX25pc.set_attribute("lid", lid) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX25shop/valid") @@ -116,15 +118,15 @@ class IIDXCannonBallersClient(BaseClient): call = self.call_node() # Construct node - IIDX25pc = Node.void('IIDX25pc') + IIDX25pc = Node.void("IIDX25pc") call.add_child(IIDX25pc) - IIDX25pc.set_attribute('iidxid', str(extid)) - IIDX25pc.set_attribute('lid', lid) - IIDX25pc.set_attribute('method', 'visit') - IIDX25pc.set_attribute('pid', '51') + IIDX25pc.set_attribute("iidxid", str(extid)) + IIDX25pc.set_attribute("lid", lid) + IIDX25pc.set_attribute("method", "visit") + IIDX25pc.set_attribute("pid", "51") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX25pc/@aflg") @@ -139,14 +141,14 @@ class IIDXCannonBallersClient(BaseClient): call = self.call_node() # Construct node - IIDX25pc = Node.void('IIDX25ranking') + IIDX25pc = Node.void("IIDX25ranking") call.add_child(IIDX25pc) - IIDX25pc.set_attribute('method', 'getranker') - IIDX25pc.set_attribute('lid', lid) - IIDX25pc.set_attribute('clid', str(clid)) + IIDX25pc.set_attribute("method", "getranker") + IIDX25pc.set_attribute("lid", lid) + IIDX25pc.set_attribute("clid", str(clid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX25ranking") @@ -155,40 +157,42 @@ class IIDXCannonBallersClient(BaseClient): call = self.call_node() # Construct node - IIDX25pc = Node.void('IIDX25shop') + IIDX25pc = Node.void("IIDX25shop") call.add_child(IIDX25pc) - IIDX25pc.set_attribute('method', 'sentinfo') - IIDX25pc.set_attribute('lid', lid) - IIDX25pc.set_attribute('bflg', '1') - IIDX25pc.set_attribute('bnum', '2') - IIDX25pc.set_attribute('ioid', '0') - IIDX25pc.set_attribute('company_code', '') - IIDX25pc.set_attribute('consumer_code', '') - IIDX25pc.set_attribute('location_name', shop_name) - IIDX25pc.set_attribute('tax_phase', '0') + IIDX25pc.set_attribute("method", "sentinfo") + IIDX25pc.set_attribute("lid", lid) + IIDX25pc.set_attribute("bflg", "1") + IIDX25pc.set_attribute("bnum", "2") + IIDX25pc.set_attribute("ioid", "0") + IIDX25pc.set_attribute("company_code", "") + IIDX25pc.set_attribute("consumer_code", "") + IIDX25pc.set_attribute("location_name", shop_name) + IIDX25pc.set_attribute("tax_phase", "0") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX25shop") - def verify_iidx25pc_get(self, ref_id: str, card_id: str, lid: str) -> Dict[str, Any]: + def verify_iidx25pc_get( + self, ref_id: str, card_id: str, lid: str + ) -> Dict[str, Any]: call = self.call_node() # Construct node - IIDX25pc = Node.void('IIDX25pc') + IIDX25pc = Node.void("IIDX25pc") call.add_child(IIDX25pc) - IIDX25pc.set_attribute('rid', ref_id) - IIDX25pc.set_attribute('did', ref_id) - IIDX25pc.set_attribute('pid', '51') - IIDX25pc.set_attribute('lid', lid) - IIDX25pc.set_attribute('cid', card_id) - IIDX25pc.set_attribute('method', 'get') - IIDX25pc.set_attribute('ctype', '1') + IIDX25pc.set_attribute("rid", ref_id) + IIDX25pc.set_attribute("did", ref_id) + IIDX25pc.set_attribute("pid", "51") + IIDX25pc.set_attribute("lid", lid) + IIDX25pc.set_attribute("cid", card_id) + IIDX25pc.set_attribute("method", "get") + IIDX25pc.set_attribute("ctype", "1") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that the response is correct self.assert_path(resp, "response/IIDX25pc/pcdata/@name") @@ -217,14 +221,14 @@ class IIDXCannonBallersClient(BaseClient): self.assert_path(resp, "response/IIDX25pc/extra_favorite/dp_mlist") self.assert_path(resp, "response/IIDX25pc/extra_favorite/dp_clist") - name = resp.child('IIDX25pc/pcdata').attribute('name') + name = resp.child("IIDX25pc/pcdata").attribute("name") if name != self.NAME: - raise Exception(f'Invalid name \'{name}\' returned for Ref ID \'{ref_id}\'') + raise Exception(f"Invalid name '{name}' returned for Ref ID '{ref_id}'") # Extract and return account data ir_data: Dict[int, Dict[int, Dict[str, int]]] = {} - for child in resp.child('IIDX25pc/ir_data').children: - if child.name == 'e': + for child in resp.child("IIDX25pc/ir_data").children: + if child.name == "e": course_id = child.value[0] course_chart = child.value[1] clear_status = child.value[2] @@ -234,14 +238,14 @@ class IIDXCannonBallersClient(BaseClient): if course_id not in ir_data: ir_data[course_id] = {} ir_data[course_id][course_chart] = { - 'clear_status': clear_status, - 'pgnum': pgnum, - 'gnum': gnum, + "clear_status": clear_status, + "pgnum": pgnum, + "gnum": gnum, } secret_course_data: Dict[int, Dict[int, Dict[str, int]]] = {} - for child in resp.child('IIDX25pc/secret_course_data').children: - if child.name == 'e': + for child in resp.child("IIDX25pc/secret_course_data").children: + if child.name == "e": course_id = child.value[0] course_chart = child.value[1] clear_status = child.value[2] @@ -251,71 +255,75 @@ class IIDXCannonBallersClient(BaseClient): if course_id not in secret_course_data: secret_course_data[course_id] = {} secret_course_data[course_id][course_chart] = { - 'clear_status': clear_status, - 'pgnum': pgnum, - 'gnum': gnum, + "clear_status": clear_status, + "pgnum": pgnum, + "gnum": gnum, } classic_course_data: Dict[int, Dict[int, Dict[str, int]]] = {} # noqa: E701 - for child in resp.child('IIDX25pc/classic_course_data').children: - if child.name == 'score_data': - course_id = int(child.attribute('course_id')) - course_chart = int(child.attribute('play_style')) - clear_status = int(child.attribute('cflg')) - pgnum = int(child.attribute('pgnum')) - gnum = int(child.attribute('gnum')) + for child in resp.child("IIDX25pc/classic_course_data").children: + if child.name == "score_data": + course_id = int(child.attribute("course_id")) + course_chart = int(child.attribute("play_style")) + clear_status = int(child.attribute("cflg")) + pgnum = int(child.attribute("pgnum")) + gnum = int(child.attribute("gnum")) if course_id not in classic_course_data: classic_course_data[course_id] = {} classic_course_data[course_id][course_chart] = { - 'clear_status': clear_status, - 'pgnum': pgnum, - 'gnum': gnum, + "clear_status": clear_status, + "pgnum": pgnum, + "gnum": gnum, } expert_point: Dict[int, Dict[str, int]] = {} - for child in resp.child('IIDX25pc/expert_point').children: - if child.name == 'detail': - expert_point[int(child.attribute('course_id'))] = { - 'n_point': int(child.attribute('n_point')), - 'h_point': int(child.attribute('h_point')), - 'a_point': int(child.attribute('a_point')), + for child in resp.child("IIDX25pc/expert_point").children: + if child.name == "detail": + expert_point[int(child.attribute("course_id"))] = { + "n_point": int(child.attribute("n_point")), + "h_point": int(child.attribute("h_point")), + "a_point": int(child.attribute("a_point")), } return { - 'extid': int(resp.child('IIDX25pc/pcdata').attribute('id')), - 'sp_dan': int(resp.child('IIDX25pc/grade').attribute('sgid')), - 'dp_dan': int(resp.child('IIDX25pc/grade').attribute('dgid')), - 'deller': int(resp.child('IIDX25pc/deller').attribute('deller')), - 'ir_data': ir_data, - 'secret_course_data': secret_course_data, - 'classic_course_data': classic_course_data, - 'expert_point': expert_point, + "extid": int(resp.child("IIDX25pc/pcdata").attribute("id")), + "sp_dan": int(resp.child("IIDX25pc/grade").attribute("sgid")), + "dp_dan": int(resp.child("IIDX25pc/grade").attribute("dgid")), + "deller": int(resp.child("IIDX25pc/deller").attribute("deller")), + "ir_data": ir_data, + "secret_course_data": secret_course_data, + "classic_course_data": classic_course_data, + "expert_point": expert_point, } - def verify_iidx25music_getrank(self, extid: int) -> Dict[int, Dict[int, Dict[str, int]]]: + def verify_iidx25music_getrank( + self, extid: int + ) -> Dict[int, Dict[int, Dict[str, int]]]: scores: Dict[int, Dict[int, Dict[str, int]]] = {} for cltype in [0, 1]: # singles, doubles call = self.call_node() # Construct node - IIDX25music = Node.void('IIDX25music') + IIDX25music = Node.void("IIDX25music") call.add_child(IIDX25music) - IIDX25music.set_attribute('method', 'getrank') - IIDX25music.set_attribute('iidxid', str(extid)) - IIDX25music.set_attribute('cltype', str(cltype)) + IIDX25music.set_attribute("method", "getrank") + IIDX25music.set_attribute("iidxid", str(extid)) + IIDX25music.set_attribute("cltype", str(cltype)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX25music/style") - if int(resp.child('IIDX25music/style').attribute('type')) != cltype: - raise Exception('Returned wrong clear type for IIDX25music.getrank!') + if int(resp.child("IIDX25music/style").attribute("type")) != cltype: + raise Exception("Returned wrong clear type for IIDX25music.getrank!") - for child in resp.child('IIDX25music').children: - if child.name == 'm': + for child in resp.child("IIDX25music").children: + if child.name == "m": if child.value[0] != -1: - raise Exception('Got non-self score back when requesting only our scores!') + raise Exception( + "Got non-self score back when requesting only our scores!" + ) music_id = child.value[1] normal_clear_status = child.value[2] @@ -341,196 +349,206 @@ class IIDXCannonBallersClient(BaseClient): scores[music_id] = {} scores[music_id][normal] = { - 'clear_status': normal_clear_status, - 'ex_score': normal_ex_score, - 'miss_count': normal_miss_count, + "clear_status": normal_clear_status, + "ex_score": normal_ex_score, + "miss_count": normal_miss_count, } scores[music_id][hyper] = { - 'clear_status': hyper_clear_status, - 'ex_score': hyper_ex_score, - 'miss_count': hyper_miss_count, + "clear_status": hyper_clear_status, + "ex_score": hyper_ex_score, + "miss_count": hyper_miss_count, } scores[music_id][another] = { - 'clear_status': another_clear_status, - 'ex_score': another_ex_score, - 'miss_count': another_miss_count, + "clear_status": another_clear_status, + "ex_score": another_ex_score, + "miss_count": another_miss_count, } - elif child.name == 'b': + elif child.name == "b": music_id = child.value[0] clear_status = child.value[1] scores[music_id][6] = { - 'clear_status': clear_status, - 'ex_score': -1, - 'miss_count': -1, + "clear_status": clear_status, + "ex_score": -1, + "miss_count": -1, } return scores - def verify_iidx25pc_save(self, extid: int, card: str, lid: str, expert_point: Optional[Dict[str, int]]=None) -> None: + def verify_iidx25pc_save( + self, + extid: int, + card: str, + lid: str, + expert_point: Optional[Dict[str, int]] = None, + ) -> None: call = self.call_node() # Construct node - IIDX25pc = Node.void('IIDX25pc') + IIDX25pc = Node.void("IIDX25pc") call.add_child(IIDX25pc) - IIDX25pc.set_attribute('iidxid', str(extid)) - IIDX25pc.set_attribute('lid', lid) - IIDX25pc.set_attribute('cid', card) - IIDX25pc.set_attribute('bookkeep', '0') - IIDX25pc.set_attribute('cltype', '0') - IIDX25pc.set_attribute('ctype', '2') - IIDX25pc.set_attribute('d_achi', '0') - IIDX25pc.set_attribute('d_auto_scrach', '0') - IIDX25pc.set_attribute('d_camera_layout', '0') - IIDX25pc.set_attribute('d_disp_judge', '0') - IIDX25pc.set_attribute('d_exscore', '0') - IIDX25pc.set_attribute('d_gauge_disp', '0') - IIDX25pc.set_attribute('d_gno', '0') - IIDX25pc.set_attribute('d_graph_score', '0') - IIDX25pc.set_attribute('d_gtype', '0') - IIDX25pc.set_attribute('d_hispeed', '1.000000') - IIDX25pc.set_attribute('d_judge', '0') - IIDX25pc.set_attribute('d_judgeAdj', '0') - IIDX25pc.set_attribute('d_lane_brignt', '0') - IIDX25pc.set_attribute('d_lift', '0') - IIDX25pc.set_attribute('d_notes', '0.000000') - IIDX25pc.set_attribute('d_opstyle', '0') - IIDX25pc.set_attribute('d_pace', '0') - IIDX25pc.set_attribute('d_sdlen', '0') - IIDX25pc.set_attribute('d_sdtype', '0') - IIDX25pc.set_attribute('d_sorttype', '0') - IIDX25pc.set_attribute('d_timing', '0') - IIDX25pc.set_attribute('d_tune', '0') - IIDX25pc.set_attribute('dp_opt', '16384') - IIDX25pc.set_attribute('dp_opt2', '16384') - IIDX25pc.set_attribute('gpos', '0') - IIDX25pc.set_attribute('method', 'save') - IIDX25pc.set_attribute('mode', '0') - IIDX25pc.set_attribute('pmode', '0') - IIDX25pc.set_attribute('rtype', '0') - IIDX25pc.set_attribute('s_achi', '1') - IIDX25pc.set_attribute('s_auto_scrach', '0') - IIDX25pc.set_attribute('s_camera_layout', '0') - IIDX25pc.set_attribute('s_disp_judge', '0') - IIDX25pc.set_attribute('s_exscore', '0') - IIDX25pc.set_attribute('s_gauge_disp', '0') - IIDX25pc.set_attribute('s_gno', '1') - IIDX25pc.set_attribute('s_graph_score', '0') - IIDX25pc.set_attribute('s_gtype', '0') - IIDX25pc.set_attribute('s_hispeed', '2.794393') - IIDX25pc.set_attribute('s_judge', '0') - IIDX25pc.set_attribute('s_judgeAdj', '0') - IIDX25pc.set_attribute('s_lane_brignt', '0') - IIDX25pc.set_attribute('s_lift', '60') - IIDX25pc.set_attribute('s_notes', '104.859825') - IIDX25pc.set_attribute('s_opstyle', '1') - IIDX25pc.set_attribute('s_pace', '0') - IIDX25pc.set_attribute('s_sdlen', '227') - IIDX25pc.set_attribute('s_sdtype', '1') - IIDX25pc.set_attribute('s_sorttype', '0') - IIDX25pc.set_attribute('s_timing', '0') - IIDX25pc.set_attribute('s_tune', '1') - IIDX25pc.set_attribute('sp_opt', '139264') - deller = Node.void('deller') + IIDX25pc.set_attribute("iidxid", str(extid)) + IIDX25pc.set_attribute("lid", lid) + IIDX25pc.set_attribute("cid", card) + IIDX25pc.set_attribute("bookkeep", "0") + IIDX25pc.set_attribute("cltype", "0") + IIDX25pc.set_attribute("ctype", "2") + IIDX25pc.set_attribute("d_achi", "0") + IIDX25pc.set_attribute("d_auto_scrach", "0") + IIDX25pc.set_attribute("d_camera_layout", "0") + IIDX25pc.set_attribute("d_disp_judge", "0") + IIDX25pc.set_attribute("d_exscore", "0") + IIDX25pc.set_attribute("d_gauge_disp", "0") + IIDX25pc.set_attribute("d_gno", "0") + IIDX25pc.set_attribute("d_graph_score", "0") + IIDX25pc.set_attribute("d_gtype", "0") + IIDX25pc.set_attribute("d_hispeed", "1.000000") + IIDX25pc.set_attribute("d_judge", "0") + IIDX25pc.set_attribute("d_judgeAdj", "0") + IIDX25pc.set_attribute("d_lane_brignt", "0") + IIDX25pc.set_attribute("d_lift", "0") + IIDX25pc.set_attribute("d_notes", "0.000000") + IIDX25pc.set_attribute("d_opstyle", "0") + IIDX25pc.set_attribute("d_pace", "0") + IIDX25pc.set_attribute("d_sdlen", "0") + IIDX25pc.set_attribute("d_sdtype", "0") + IIDX25pc.set_attribute("d_sorttype", "0") + IIDX25pc.set_attribute("d_timing", "0") + IIDX25pc.set_attribute("d_tune", "0") + IIDX25pc.set_attribute("dp_opt", "16384") + IIDX25pc.set_attribute("dp_opt2", "16384") + IIDX25pc.set_attribute("gpos", "0") + IIDX25pc.set_attribute("method", "save") + IIDX25pc.set_attribute("mode", "0") + IIDX25pc.set_attribute("pmode", "0") + IIDX25pc.set_attribute("rtype", "0") + IIDX25pc.set_attribute("s_achi", "1") + IIDX25pc.set_attribute("s_auto_scrach", "0") + IIDX25pc.set_attribute("s_camera_layout", "0") + IIDX25pc.set_attribute("s_disp_judge", "0") + IIDX25pc.set_attribute("s_exscore", "0") + IIDX25pc.set_attribute("s_gauge_disp", "0") + IIDX25pc.set_attribute("s_gno", "1") + IIDX25pc.set_attribute("s_graph_score", "0") + IIDX25pc.set_attribute("s_gtype", "0") + IIDX25pc.set_attribute("s_hispeed", "2.794393") + IIDX25pc.set_attribute("s_judge", "0") + IIDX25pc.set_attribute("s_judgeAdj", "0") + IIDX25pc.set_attribute("s_lane_brignt", "0") + IIDX25pc.set_attribute("s_lift", "60") + IIDX25pc.set_attribute("s_notes", "104.859825") + IIDX25pc.set_attribute("s_opstyle", "1") + IIDX25pc.set_attribute("s_pace", "0") + IIDX25pc.set_attribute("s_sdlen", "227") + IIDX25pc.set_attribute("s_sdtype", "1") + IIDX25pc.set_attribute("s_sorttype", "0") + IIDX25pc.set_attribute("s_timing", "0") + IIDX25pc.set_attribute("s_tune", "1") + IIDX25pc.set_attribute("sp_opt", "139264") + deller = Node.void("deller") IIDX25pc.add_child(deller) - deller.set_attribute('deller', '150') + deller.set_attribute("deller", "150") if expert_point is not None: - epnode = Node.void('expert_point') - epnode.set_attribute('h_point', str(expert_point['h_point'])) - epnode.set_attribute('course_id', str(expert_point['course_id'])) - epnode.set_attribute('n_point', str(expert_point['n_point'])) - epnode.set_attribute('a_point', str(expert_point['a_point'])) + epnode = Node.void("expert_point") + epnode.set_attribute("h_point", str(expert_point["h_point"])) + epnode.set_attribute("course_id", str(expert_point["course_id"])) + epnode.set_attribute("n_point", str(expert_point["n_point"])) + epnode.set_attribute("a_point", str(expert_point["a_point"])) IIDX25pc.add_child(epnode) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX25pc") - def verify_iidx25music_reg(self, extid: int, lid: str, score: Dict[str, Any]) -> None: + def verify_iidx25music_reg( + self, extid: int, lid: str, score: Dict[str, Any] + ) -> None: call = self.call_node() # Construct node - IIDX25music = Node.void('IIDX25music') + IIDX25music = Node.void("IIDX25music") call.add_child(IIDX25music) - IIDX25music.set_attribute('convid', '-1') - IIDX25music.set_attribute('iidxid', str(extid)) - IIDX25music.set_attribute('pgnum', str(score['pgnum'])) - IIDX25music.set_attribute('pid', '51') - IIDX25music.set_attribute('rankside', '1') - IIDX25music.set_attribute('cflg', str(score['clear_status'])) - IIDX25music.set_attribute('method', 'reg') - IIDX25music.set_attribute('gnum', str(score['gnum'])) - IIDX25music.set_attribute('clid', str(score['chart'])) - IIDX25music.set_attribute('mnum', str(score['mnum'])) - IIDX25music.set_attribute('is_death', '0') - IIDX25music.set_attribute('theory', '0') - IIDX25music.set_attribute('dj_level', '1') - IIDX25music.set_attribute('location_id', lid) - IIDX25music.set_attribute('mid', str(score['id'])) - IIDX25music.add_child(Node.binary('ghost', bytes([1] * 64))) + IIDX25music.set_attribute("convid", "-1") + IIDX25music.set_attribute("iidxid", str(extid)) + IIDX25music.set_attribute("pgnum", str(score["pgnum"])) + IIDX25music.set_attribute("pid", "51") + IIDX25music.set_attribute("rankside", "1") + IIDX25music.set_attribute("cflg", str(score["clear_status"])) + IIDX25music.set_attribute("method", "reg") + IIDX25music.set_attribute("gnum", str(score["gnum"])) + IIDX25music.set_attribute("clid", str(score["chart"])) + IIDX25music.set_attribute("mnum", str(score["mnum"])) + IIDX25music.set_attribute("is_death", "0") + IIDX25music.set_attribute("theory", "0") + IIDX25music.set_attribute("dj_level", "1") + IIDX25music.set_attribute("location_id", lid) + IIDX25music.set_attribute("mid", str(score["id"])) + IIDX25music.add_child(Node.binary("ghost", bytes([1] * 64))) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX25music/shopdata/@rank") self.assert_path(resp, "response/IIDX25music/ranklist/data") - def verify_iidx25music_appoint(self, extid: int, musicid: int, chart: int) -> Tuple[int, bytes]: + def verify_iidx25music_appoint( + self, extid: int, musicid: int, chart: int + ) -> Tuple[int, bytes]: call = self.call_node() # Construct node - IIDX25music = Node.void('IIDX25music') + IIDX25music = Node.void("IIDX25music") call.add_child(IIDX25music) - IIDX25music.set_attribute('clid', str(chart)) - IIDX25music.set_attribute('method', 'appoint') - IIDX25music.set_attribute('ctype', '0') - IIDX25music.set_attribute('iidxid', str(extid)) - IIDX25music.set_attribute('subtype', '') - IIDX25music.set_attribute('mid', str(musicid)) + IIDX25music.set_attribute("clid", str(chart)) + IIDX25music.set_attribute("method", "appoint") + IIDX25music.set_attribute("ctype", "0") + IIDX25music.set_attribute("iidxid", str(extid)) + IIDX25music.set_attribute("subtype", "") + IIDX25music.set_attribute("mid", str(musicid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX25music/mydata/@score") return ( - int(resp.child('IIDX25music/mydata').attribute('score')), - resp.child_value('IIDX25music/mydata'), + int(resp.child("IIDX25music/mydata").attribute("score")), + resp.child_value("IIDX25music/mydata"), ) def verify_iidx25pc_reg(self, ref_id: str, card_id: str, lid: str) -> int: call = self.call_node() # Construct node - IIDX25pc = Node.void('IIDX25pc') + IIDX25pc = Node.void("IIDX25pc") call.add_child(IIDX25pc) - IIDX25pc.set_attribute('lid', lid) - IIDX25pc.set_attribute('pid', '51') - IIDX25pc.set_attribute('method', 'reg') - IIDX25pc.set_attribute('cid', card_id) - IIDX25pc.set_attribute('did', ref_id) - IIDX25pc.set_attribute('rid', ref_id) - IIDX25pc.set_attribute('name', self.NAME) + IIDX25pc.set_attribute("lid", lid) + IIDX25pc.set_attribute("pid", "51") + IIDX25pc.set_attribute("method", "reg") + IIDX25pc.set_attribute("cid", card_id) + IIDX25pc.set_attribute("did", ref_id) + IIDX25pc.set_attribute("rid", ref_id) + IIDX25pc.set_attribute("name", self.NAME) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX25pc/@id") self.assert_path(resp, "response/IIDX25pc/@id_str") - return int(resp.child('IIDX25pc').attribute('id')) + return int(resp.child("IIDX25pc").attribute("id")) def verify_iidx25pc_playstart(self) -> None: call = self.call_node() # Construct node - IIDX25pc = Node.void('IIDX25pc') - IIDX25pc.set_attribute('method', 'playstart') - IIDX25pc.set_attribute('side', '1') + IIDX25pc = Node.void("IIDX25pc") + IIDX25pc.set_attribute("method", "playstart") + IIDX25pc.set_attribute("side", "1") call.add_child(IIDX25pc) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX25pc") @@ -539,19 +557,19 @@ class IIDXCannonBallersClient(BaseClient): call = self.call_node() # Construct node - IIDX25music = Node.void('IIDX25music') - IIDX25music.set_attribute('opt', '64') - IIDX25music.set_attribute('clid', str(score['chart'])) - IIDX25music.set_attribute('mid', str(score['id'])) - IIDX25music.set_attribute('gnum', str(score['gnum'])) - IIDX25music.set_attribute('cflg', str(score['clear_status'])) - IIDX25music.set_attribute('pgnum', str(score['pgnum'])) - IIDX25music.set_attribute('pid', '51') - IIDX25music.set_attribute('method', 'play') + IIDX25music = Node.void("IIDX25music") + IIDX25music.set_attribute("opt", "64") + IIDX25music.set_attribute("clid", str(score["chart"])) + IIDX25music.set_attribute("mid", str(score["id"])) + IIDX25music.set_attribute("gnum", str(score["gnum"])) + IIDX25music.set_attribute("cflg", str(score["clear_status"])) + IIDX25music.set_attribute("pgnum", str(score["pgnum"])) + IIDX25music.set_attribute("pid", "51") + IIDX25music.set_attribute("method", "play") call.add_child(IIDX25music) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX25music/@clid") @@ -563,20 +581,20 @@ class IIDXCannonBallersClient(BaseClient): call = self.call_node() # Construct node - IIDX25pc = Node.void('IIDX25pc') - IIDX25pc.set_attribute('cltype', '0') - IIDX25pc.set_attribute('bookkeep', '0') - IIDX25pc.set_attribute('mode', '1') - IIDX25pc.set_attribute('pay_coin', '1') - IIDX25pc.set_attribute('method', 'playend') - IIDX25pc.set_attribute('company_code', '') - IIDX25pc.set_attribute('consumer_code', '') - IIDX25pc.set_attribute('location_name', shop_name) - IIDX25pc.set_attribute('lid', lid) + IIDX25pc = Node.void("IIDX25pc") + IIDX25pc.set_attribute("cltype", "0") + IIDX25pc.set_attribute("bookkeep", "0") + IIDX25pc.set_attribute("mode", "1") + IIDX25pc.set_attribute("pay_coin", "1") + IIDX25pc.set_attribute("method", "playend") + IIDX25pc.set_attribute("company_code", "") + IIDX25pc.set_attribute("consumer_code", "") + IIDX25pc.set_attribute("location_name", shop_name) + IIDX25pc.set_attribute("lid", lid) call.add_child(IIDX25pc) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX25pc") @@ -585,68 +603,72 @@ class IIDXCannonBallersClient(BaseClient): call = self.call_node() # Construct node - IIDX25music = Node.void('IIDX25music') - IIDX25music.set_attribute('gnum', str(score['gnum'])) - IIDX25music.set_attribute('iidxid', str(iidxid)) - IIDX25music.set_attribute('mid', str(score['id'])) - IIDX25music.set_attribute('method', 'breg') - IIDX25music.set_attribute('pgnum', str(score['pgnum'])) - IIDX25music.set_attribute('cflg', str(score['clear_status'])) + IIDX25music = Node.void("IIDX25music") + IIDX25music.set_attribute("gnum", str(score["gnum"])) + IIDX25music.set_attribute("iidxid", str(iidxid)) + IIDX25music.set_attribute("mid", str(score["id"])) + IIDX25music.set_attribute("method", "breg") + IIDX25music.set_attribute("pgnum", str(score["pgnum"])) + IIDX25music.set_attribute("cflg", str(score["clear_status"])) call.add_child(IIDX25music) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX25music") - def verify_iidx25grade_raised(self, iidxid: int, shop_name: str, dantype: str) -> None: + def verify_iidx25grade_raised( + self, iidxid: int, shop_name: str, dantype: str + ) -> None: call = self.call_node() # Construct node - IIDX25grade = Node.void('IIDX25grade') - IIDX25grade.set_attribute('opname', shop_name) - IIDX25grade.set_attribute('is_mirror', '0') - IIDX25grade.set_attribute('oppid', '51') - IIDX25grade.set_attribute('achi', '50') - IIDX25grade.set_attribute('cstage', '4') - IIDX25grade.set_attribute('gid', '5') - IIDX25grade.set_attribute('iidxid', str(iidxid)) - IIDX25grade.set_attribute('gtype', '0' if dantype == 'sp' else '1') - IIDX25grade.set_attribute('is_ex', '0') - IIDX25grade.set_attribute('pside', '0') - IIDX25grade.set_attribute('method', 'raised') + IIDX25grade = Node.void("IIDX25grade") + IIDX25grade.set_attribute("opname", shop_name) + IIDX25grade.set_attribute("is_mirror", "0") + IIDX25grade.set_attribute("oppid", "51") + IIDX25grade.set_attribute("achi", "50") + IIDX25grade.set_attribute("cstage", "4") + IIDX25grade.set_attribute("gid", "5") + IIDX25grade.set_attribute("iidxid", str(iidxid)) + IIDX25grade.set_attribute("gtype", "0" if dantype == "sp" else "1") + IIDX25grade.set_attribute("is_ex", "0") + IIDX25grade.set_attribute("pside", "0") + IIDX25grade.set_attribute("method", "raised") call.add_child(IIDX25grade) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX25grade/@pnum") - def verify_iidx25ranking_entry(self, iidxid: int, shop_name: str, coursetype: str) -> None: + def verify_iidx25ranking_entry( + self, iidxid: int, shop_name: str, coursetype: str + ) -> None: call = self.call_node() # Construct node - IIDX25ranking = Node.void('IIDX25ranking') - IIDX25ranking.set_attribute('opname', shop_name) - IIDX25ranking.set_attribute('clr', '4') - IIDX25ranking.set_attribute('pgnum', '1771') - IIDX25ranking.set_attribute('coid', '2') - IIDX25ranking.set_attribute('method', 'entry') - IIDX25ranking.set_attribute('opt', '8208') - IIDX25ranking.set_attribute('opt2', '0') - IIDX25ranking.set_attribute('oppid', '51') - IIDX25ranking.set_attribute('cstage', '4') - IIDX25ranking.set_attribute('gnum', '967') - IIDX25ranking.set_attribute('pside', '1') - IIDX25ranking.set_attribute('clid', '1') - IIDX25ranking.set_attribute('regist_type', '0' if coursetype == 'ir' else '1') - IIDX25ranking.set_attribute('iidxid', str(iidxid)) + IIDX25ranking = Node.void("IIDX25ranking") + IIDX25ranking.set_attribute("opname", shop_name) + IIDX25ranking.set_attribute("clr", "4") + IIDX25ranking.set_attribute("pgnum", "1771") + IIDX25ranking.set_attribute("coid", "2") + IIDX25ranking.set_attribute("method", "entry") + IIDX25ranking.set_attribute("opt", "8208") + IIDX25ranking.set_attribute("opt2", "0") + IIDX25ranking.set_attribute("oppid", "51") + IIDX25ranking.set_attribute("cstage", "4") + IIDX25ranking.set_attribute("gnum", "967") + IIDX25ranking.set_attribute("pside", "1") + IIDX25ranking.set_attribute("clid", "1") + IIDX25ranking.set_attribute("regist_type", "0" if coursetype == "ir" else "1") + IIDX25ranking.set_attribute("iidxid", str(iidxid)) call.add_child(IIDX25ranking) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX25ranking/@anum") @@ -656,120 +678,124 @@ class IIDXCannonBallersClient(BaseClient): call = self.call_node() # Construct node - IIDX25ranking = Node.void('IIDX25ranking') - IIDX25ranking.set_attribute('clear_stage', '4') - IIDX25ranking.set_attribute('clear_flg', '4') - IIDX25ranking.set_attribute('course_id', '2') - IIDX25ranking.set_attribute('score', '4509') - IIDX25ranking.set_attribute('gnum', '967') - IIDX25ranking.set_attribute('iidx_id', str(iidxid)) - IIDX25ranking.set_attribute('method', 'classicentry') - IIDX25ranking.set_attribute('pgnum', '1771') - IIDX25ranking.set_attribute('play_style', '1') + IIDX25ranking = Node.void("IIDX25ranking") + IIDX25ranking.set_attribute("clear_stage", "4") + IIDX25ranking.set_attribute("clear_flg", "4") + IIDX25ranking.set_attribute("course_id", "2") + IIDX25ranking.set_attribute("score", "4509") + IIDX25ranking.set_attribute("gnum", "967") + IIDX25ranking.set_attribute("iidx_id", str(iidxid)) + IIDX25ranking.set_attribute("method", "classicentry") + IIDX25ranking.set_attribute("pgnum", "1771") + IIDX25ranking.set_attribute("play_style", "1") call.add_child(IIDX25ranking) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX25ranking/@status") def verify_iidx25music_arenacpu(self) -> None: call = self.call_node() - IIDX25music = Node.void('IIDX25music') + IIDX25music = Node.void("IIDX25music") call.add_child(IIDX25music) - IIDX25music.set_attribute('method', 'arenaCPU') - music_list = Node.void('music_list') + IIDX25music.set_attribute("method", "arenaCPU") + music_list = Node.void("music_list") IIDX25music.add_child(music_list) - music_list.add_child(Node.s32('index', 0)) - music_list.add_child(Node.s32('music_id', 26072)) - music_list.add_child(Node.s32('note_style', 0)) - music_list.add_child(Node.s32('note_grade', 2)) - music_list.add_child(Node.s32('total_notes', 1857)) - music_list_1 = Node.void('music_list') + music_list.add_child(Node.s32("index", 0)) + music_list.add_child(Node.s32("music_id", 26072)) + music_list.add_child(Node.s32("note_style", 0)) + music_list.add_child(Node.s32("note_grade", 2)) + music_list.add_child(Node.s32("total_notes", 1857)) + music_list_1 = Node.void("music_list") IIDX25music.add_child(music_list_1) - music_list_1.add_child(Node.s32('index', 1)) - music_list_1.add_child(Node.s32('music_id', 25020)) - music_list_1.add_child(Node.s32('note_style', 0)) - music_list_1.add_child(Node.s32('note_grade', 0)) - music_list_1.add_child(Node.s32('total_notes', 483)) - music_list_2 = Node.void('music_list') + music_list_1.add_child(Node.s32("index", 1)) + music_list_1.add_child(Node.s32("music_id", 25020)) + music_list_1.add_child(Node.s32("note_style", 0)) + music_list_1.add_child(Node.s32("note_grade", 0)) + music_list_1.add_child(Node.s32("total_notes", 483)) + music_list_2 = Node.void("music_list") IIDX25music.add_child(music_list_2) - music_list_2.add_child(Node.s32('index', 2)) - music_list_2.add_child(Node.s32('music_id', 2000)) - music_list_2.add_child(Node.s32('note_style', 0)) - music_list_2.add_child(Node.s32('note_grade', 0)) - music_list_2.add_child(Node.s32('total_notes', 360)) - music_list_3 = Node.void('music_list') + music_list_2.add_child(Node.s32("index", 2)) + music_list_2.add_child(Node.s32("music_id", 2000)) + music_list_2.add_child(Node.s32("note_style", 0)) + music_list_2.add_child(Node.s32("note_grade", 0)) + music_list_2.add_child(Node.s32("total_notes", 360)) + music_list_3 = Node.void("music_list") IIDX25music.add_child(music_list_3) - music_list_3.add_child(Node.s32('index', 3)) - music_list_3.add_child(Node.s32('music_id', 19020)) - music_list_3.add_child(Node.s32('note_style', 0)) - music_list_3.add_child(Node.s32('note_grade', 0)) - music_list_3.add_child(Node.s32('total_notes', 404)) - cpu_list = Node.void('cpu_list') + music_list_3.add_child(Node.s32("index", 3)) + music_list_3.add_child(Node.s32("music_id", 19020)) + music_list_3.add_child(Node.s32("note_style", 0)) + music_list_3.add_child(Node.s32("note_grade", 0)) + music_list_3.add_child(Node.s32("total_notes", 404)) + cpu_list = Node.void("cpu_list") IIDX25music.add_child(cpu_list) - cpu_list.add_child(Node.s32('index', 0)) - cpu_list.add_child(Node.s32('play_style', 0)) - cpu_list.add_child(Node.s32('arena_class', 0)) - cpu_list.add_child(Node.s32('grade_id', -1)) - cpu_list_1 = Node.void('cpu_list') + cpu_list.add_child(Node.s32("index", 0)) + cpu_list.add_child(Node.s32("play_style", 0)) + cpu_list.add_child(Node.s32("arena_class", 0)) + cpu_list.add_child(Node.s32("grade_id", -1)) + cpu_list_1 = Node.void("cpu_list") IIDX25music.add_child(cpu_list_1) - cpu_list_1.add_child(Node.s32('index', 1)) - cpu_list_1.add_child(Node.s32('play_style', 0)) - cpu_list_1.add_child(Node.s32('arena_class', 0)) - cpu_list_1.add_child(Node.s32('grade_id', 6)) - cpu_list_2 = Node.void('cpu_list') + cpu_list_1.add_child(Node.s32("index", 1)) + cpu_list_1.add_child(Node.s32("play_style", 0)) + cpu_list_1.add_child(Node.s32("arena_class", 0)) + cpu_list_1.add_child(Node.s32("grade_id", 6)) + cpu_list_2 = Node.void("cpu_list") IIDX25music.add_child(cpu_list_2) - cpu_list_2.add_child(Node.s32('index', 2)) - cpu_list_2.add_child(Node.s32('play_style', 0)) - cpu_list_2.add_child(Node.s32('arena_class', 0)) - cpu_list_2.add_child(Node.s32('grade_id', 6)) - cpu_list_3 = Node.void('cpu_list') + cpu_list_2.add_child(Node.s32("index", 2)) + cpu_list_2.add_child(Node.s32("play_style", 0)) + cpu_list_2.add_child(Node.s32("arena_class", 0)) + cpu_list_2.add_child(Node.s32("grade_id", 6)) + cpu_list_3 = Node.void("cpu_list") IIDX25music.add_child(cpu_list_3) - cpu_list_3.add_child(Node.s32('index', 3)) - cpu_list_3.add_child(Node.s32('play_style', 0)) - cpu_list_3.add_child(Node.s32('arena_class', 0)) - cpu_list_3.add_child(Node.s32('grade_id', 6)) + cpu_list_3.add_child(Node.s32("index", 3)) + cpu_list_3.add_child(Node.s32("play_style", 0)) + cpu_list_3.add_child(Node.s32("arena_class", 0)) + cpu_list_3.add_child(Node.s32("grade_id", 6)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) - self.assert_path(resp, 'response/IIDX25music/cpu_score_list/score_list/score') - self.assert_path(resp, 'response/IIDX25music/cpu_score_list/score_list/enable_score') - self.assert_path(resp, 'response/IIDX25music/cpu_score_list/score_list/ghost') - self.assert_path(resp, 'response/IIDX25music/cpu_score_list/score_list/enable_ghost') + self.assert_path(resp, "response/IIDX25music/cpu_score_list/score_list/score") + self.assert_path( + resp, "response/IIDX25music/cpu_score_list/score_list/enable_score" + ) + self.assert_path(resp, "response/IIDX25music/cpu_score_list/score_list/ghost") + self.assert_path( + resp, "response/IIDX25music/cpu_score_list/score_list/enable_ghost" + ) def verify_iidx25gamesystem_systeminfo(self, lid: str) -> None: call = self.call_node() - IIDX25gameSystem = Node.void('IIDX25gameSystem') + IIDX25gameSystem = Node.void("IIDX25gameSystem") call.add_child(IIDX25gameSystem) - IIDX25gameSystem.set_attribute('method', 'systemInfo') - IIDX25gameSystem.add_child(Node.s32('ver', 7)) - IIDX25gameSystem.add_child(Node.string('location_id', lid)) + IIDX25gameSystem.set_attribute("method", "systemInfo") + IIDX25gameSystem.add_child(Node.s32("ver", 7)) + IIDX25gameSystem.add_child(Node.string("location_id", lid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) - self.assert_path(resp, 'response/IIDX25gameSystem/arena_schedule/phase') + self.assert_path(resp, "response/IIDX25gameSystem/arena_schedule/phase") def verify(self, cardid: Optional[str]) -> None: # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -782,7 +808,7 @@ class IIDXCannonBallersClient(BaseClient): self.verify_iidx25music_crate() self.verify_iidx25shop_getconvention(lid) self.verify_iidx25ranking_getranker(lid) - self.verify_iidx25shop_sentinfo(lid, 'newname1') + self.verify_iidx25shop_sentinfo(lid, "newname1") self.verify_iidx25gamesystem_systeminfo(lid) self.verify_iidx25music_arenacpu() @@ -794,227 +820,293 @@ class IIDXCannonBallersClient(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") self.verify_iidx25pc_reg(ref_id, card, lid) self.verify_iidx25pc_get(ref_id, card, lid) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") if cardid is None: # Verify score handling profile = self.verify_iidx25pc_get(ref_id, card, lid) - if profile['sp_dan'] != -1: - raise Exception('Somehow has SP DAN ranking on new profile!') - if profile['dp_dan'] != -1: - raise Exception('Somehow has DP DAN ranking on new profile!') - if profile['deller'] != 0: - raise Exception('Somehow has deller on new profile!') - if len(profile['ir_data'].keys()) > 0: - raise Exception('Somehow has internet ranking data on new profile!') - if len(profile['secret_course_data'].keys()) > 0: - raise Exception('Somehow has secret course data on new profile!') - if len(profile['expert_point'].keys()) > 0: - raise Exception('Somehow has expert point data on new profile!') - scores = self.verify_iidx25music_getrank(profile['extid']) + if profile["sp_dan"] != -1: + raise Exception("Somehow has SP DAN ranking on new profile!") + if profile["dp_dan"] != -1: + raise Exception("Somehow has DP DAN ranking on new profile!") + if profile["deller"] != 0: + raise Exception("Somehow has deller on new profile!") + if len(profile["ir_data"].keys()) > 0: + raise Exception("Somehow has internet ranking data on new profile!") + if len(profile["secret_course_data"].keys()) > 0: + raise Exception("Somehow has secret course data on new profile!") + if len(profile["expert_point"].keys()) > 0: + raise Exception("Somehow has expert point data on new profile!") + scores = self.verify_iidx25music_getrank(profile["extid"]) if len(scores.keys()) > 0: - raise Exception('Somehow have scores on a new profile!') + raise Exception("Somehow have scores on a new profile!") for phase in [1, 2]: if phase == 1: dummyscores = [ # An okay score on a chart { - 'id': 1000, - 'chart': 2, - 'clear_status': 4, - 'pgnum': 123, - 'gnum': 123, - 'mnum': 5, + "id": 1000, + "chart": 2, + "clear_status": 4, + "pgnum": 123, + "gnum": 123, + "mnum": 5, }, # A good score on an easier chart of the same song { - 'id': 1000, - 'chart': 0, - 'clear_status': 7, - 'pgnum': 246, - 'gnum': 0, - 'mnum': 0, + "id": 1000, + "chart": 0, + "clear_status": 7, + "pgnum": 246, + "gnum": 0, + "mnum": 0, }, # A bad score on a hard chart { - 'id': 1003, - 'chart': 2, - 'clear_status': 1, - 'pgnum': 10, - 'gnum': 20, - 'mnum': 50, + "id": 1003, + "chart": 2, + "clear_status": 1, + "pgnum": 10, + "gnum": 20, + "mnum": 50, }, # A terrible score on an easy chart { - 'id': 1003, - 'chart': 0, - 'clear_status': 1, - 'pgnum': 2, - 'gnum': 5, - 'mnum': 75, + "id": 1003, + "chart": 0, + "clear_status": 1, + "pgnum": 2, + "gnum": 5, + "mnum": 75, }, ] if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 1000, - 'chart': 2, - 'clear_status': 5, - 'pgnum': 234, - 'gnum': 234, - 'mnum': 3, + "id": 1000, + "chart": 2, + "clear_status": 5, + "pgnum": 234, + "gnum": 234, + "mnum": 3, }, # A worse score on another same chart { - 'id': 1000, - 'chart': 0, - 'clear_status': 4, - 'pgnum': 123, - 'gnum': 123, - 'mnum': 35, - 'expected_clear_status': 7, - 'expected_ex_score': 492, - 'expected_miss_count': 0, + "id": 1000, + "chart": 0, + "clear_status": 4, + "pgnum": 123, + "gnum": 123, + "mnum": 35, + "expected_clear_status": 7, + "expected_ex_score": 492, + "expected_miss_count": 0, }, ] for dummyscore in dummyscores: - self.verify_iidx25music_reg(profile['extid'], lid, dummyscore) - self.verify_iidx25pc_visit(profile['extid'], lid) - self.verify_iidx25pc_save(profile['extid'], card, lid) - scores = self.verify_iidx25music_getrank(profile['extid']) + self.verify_iidx25music_reg(profile["extid"], lid, dummyscore) + self.verify_iidx25pc_visit(profile["extid"], lid) + self.verify_iidx25pc_save(profile["extid"], card, lid) + scores = self.verify_iidx25music_getrank(profile["extid"]) for score in dummyscores: - data = scores.get(score['id'], {}).get(score['chart'], None) + data = scores.get(score["id"], {}).get(score["chart"], None) if data is None: - raise Exception(f'Expected to get score back for song {score["id"]} chart {score["chart"]}!') + raise Exception( + f'Expected to get score back for song {score["id"]} chart {score["chart"]}!' + ) - if 'expected_ex_score' in score: - expected_score = score['expected_ex_score'] + if "expected_ex_score" in score: + expected_score = score["expected_ex_score"] else: - expected_score = (score['pgnum'] * 2) + score['gnum'] - if 'expected_clear_status' in score: - expected_clear_status = score['expected_clear_status'] + expected_score = (score["pgnum"] * 2) + score["gnum"] + if "expected_clear_status" in score: + expected_clear_status = score["expected_clear_status"] else: - expected_clear_status = score['clear_status'] - if 'expected_miss_count' in score: - expected_miss_count = score['expected_miss_count'] + expected_clear_status = score["clear_status"] + if "expected_miss_count" in score: + expected_miss_count = score["expected_miss_count"] else: - expected_miss_count = score['mnum'] + expected_miss_count = score["mnum"] - if data['ex_score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\'') - if data['clear_status'] != expected_clear_status: - raise Exception(f'Expected a clear status of \'{expected_clear_status}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got clear status \'{data["clear_status"]}\'') - if data['miss_count'] != expected_miss_count: - raise Exception(f'Expected a miss count of \'{expected_miss_count}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got miss count \'{data["miss_count"]}\'') + if data["ex_score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\'' + ) + if data["clear_status"] != expected_clear_status: + raise Exception( + f'Expected a clear status of \'{expected_clear_status}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got clear status \'{data["clear_status"]}\'' + ) + if data["miss_count"] != expected_miss_count: + raise Exception( + f'Expected a miss count of \'{expected_miss_count}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got miss count \'{data["miss_count"]}\'' + ) # Verify we can fetch our own ghost - ex_score, ghost = self.verify_iidx25music_appoint(profile['extid'], score['id'], score['chart']) + ex_score, ghost = self.verify_iidx25music_appoint( + profile["extid"], score["id"], score["chart"] + ) if ex_score != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\'') + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\'' + ) if len(ghost) != 64: - raise Exception(f'Wrong ghost length {len(ghost)} for ghost!') + raise Exception(f"Wrong ghost length {len(ghost)} for ghost!") for g in ghost: if g != 0x01: - raise Exception(f'Got back wrong ghost data for song \'{score["id"]}\' chart \'{score["chart"]}\'') + raise Exception( + f'Got back wrong ghost data for song \'{score["id"]}\' chart \'{score["chart"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) # Verify that we can save/load expert points - self.verify_iidx25pc_save(profile['extid'], card, lid, {'course_id': 1, 'n_point': 0, 'h_point': 500, 'a_point': 0}) + self.verify_iidx25pc_save( + profile["extid"], + card, + lid, + {"course_id": 1, "n_point": 0, "h_point": 500, "a_point": 0}, + ) profile = self.verify_iidx25pc_get(ref_id, card, lid) - if sorted(profile['expert_point'].keys()) != [1]: - raise Exception('Got back wrong number of expert course points!') - if profile['expert_point'][1] != {'n_point': 0, 'h_point': 500, 'a_point': 0}: - raise Exception('Got back wrong expert points after saving!') - self.verify_iidx25pc_save(profile['extid'], card, lid, {'course_id': 1, 'n_point': 0, 'h_point': 1000, 'a_point': 0}) + if sorted(profile["expert_point"].keys()) != [1]: + raise Exception("Got back wrong number of expert course points!") + if profile["expert_point"][1] != { + "n_point": 0, + "h_point": 500, + "a_point": 0, + }: + raise Exception("Got back wrong expert points after saving!") + self.verify_iidx25pc_save( + profile["extid"], + card, + lid, + {"course_id": 1, "n_point": 0, "h_point": 1000, "a_point": 0}, + ) profile = self.verify_iidx25pc_get(ref_id, card, lid) - if sorted(profile['expert_point'].keys()) != [1]: - raise Exception('Got back wrong number of expert course points!') - if profile['expert_point'][1] != {'n_point': 0, 'h_point': 1000, 'a_point': 0}: - raise Exception('Got back wrong expert points after saving!') - self.verify_iidx25pc_save(profile['extid'], card, lid, {'course_id': 2, 'n_point': 0, 'h_point': 0, 'a_point': 500}) + if sorted(profile["expert_point"].keys()) != [1]: + raise Exception("Got back wrong number of expert course points!") + if profile["expert_point"][1] != { + "n_point": 0, + "h_point": 1000, + "a_point": 0, + }: + raise Exception("Got back wrong expert points after saving!") + self.verify_iidx25pc_save( + profile["extid"], + card, + lid, + {"course_id": 2, "n_point": 0, "h_point": 0, "a_point": 500}, + ) profile = self.verify_iidx25pc_get(ref_id, card, lid) - if sorted(profile['expert_point'].keys()) != [1, 2]: - raise Exception('Got back wrong number of expert course points!') - if profile['expert_point'][1] != {'n_point': 0, 'h_point': 1000, 'a_point': 0}: - raise Exception('Got back wrong expert points after saving!') - if profile['expert_point'][2] != {'n_point': 0, 'h_point': 0, 'a_point': 500}: - raise Exception('Got back wrong expert points after saving!') + if sorted(profile["expert_point"].keys()) != [1, 2]: + raise Exception("Got back wrong number of expert course points!") + if profile["expert_point"][1] != { + "n_point": 0, + "h_point": 1000, + "a_point": 0, + }: + raise Exception("Got back wrong expert points after saving!") + if profile["expert_point"][2] != { + "n_point": 0, + "h_point": 0, + "a_point": 500, + }: + raise Exception("Got back wrong expert points after saving!") # Verify that a player without a card can play self.verify_iidx25pc_playstart() - self.verify_iidx25music_play({ - 'id': 1000, - 'chart': 2, - 'clear_status': 4, - 'pgnum': 123, - 'gnum': 123, - }) - self.verify_iidx25pc_playend(lid, 'newname1') + self.verify_iidx25music_play( + { + "id": 1000, + "chart": 2, + "clear_status": 4, + "pgnum": 123, + "gnum": 123, + } + ) + self.verify_iidx25pc_playend(lid, "newname1") # Verify shop name change setting - self.verify_iidx25shop_savename(lid, 'newname1') + self.verify_iidx25shop_savename(lid, "newname1") newname = self.verify_iidx25shop_getname(lid) - if newname != 'newname1': - raise Exception('Invalid shop name returned after change!') - self.verify_iidx25shop_savename(lid, 'newname2') + if newname != "newname1": + raise Exception("Invalid shop name returned after change!") + self.verify_iidx25shop_savename(lid, "newname2") newname = self.verify_iidx25shop_getname(lid) - if newname != 'newname2': - raise Exception('Invalid shop name returned after change!') + if newname != "newname2": + raise Exception("Invalid shop name returned after change!") # Verify beginner score saving - self.verify_iidx25music_breg(profile['extid'], { - 'id': 1000, - 'clear_status': 4, - 'pgnum': 123, - 'gnum': 123, - }) - scores = self.verify_iidx25music_getrank(profile['extid']) + self.verify_iidx25music_breg( + profile["extid"], + { + "id": 1000, + "clear_status": 4, + "pgnum": 123, + "gnum": 123, + }, + ) + scores = self.verify_iidx25music_getrank(profile["extid"]) if 1000 not in scores: - raise Exception(f'Didn\'t get expected scores back for song {1000} beginner chart!') + raise Exception( + f"Didn't get expected scores back for song {1000} beginner chart!" + ) if 6 not in scores[1000]: - raise Exception(f'Didn\'t get beginner score back for song {1000}!') - if scores[1000][6] != {'clear_status': 4, 'ex_score': -1, 'miss_count': -1}: - raise Exception('Didn\'t get correct status back from beginner save!') + raise Exception(f"Didn't get beginner score back for song {1000}!") + if scores[1000][6] != {"clear_status": 4, "ex_score": -1, "miss_count": -1}: + raise Exception("Didn't get correct status back from beginner save!") # Verify DAN score saving and loading - self.verify_iidx25grade_raised(profile['extid'], newname, 'sp') - self.verify_iidx25grade_raised(profile['extid'], newname, 'dp') + self.verify_iidx25grade_raised(profile["extid"], newname, "sp") + self.verify_iidx25grade_raised(profile["extid"], newname, "dp") profile = self.verify_iidx25pc_get(ref_id, card, lid) - if profile['sp_dan'] != 5: - raise Exception('Got wrong DAN score back for SP!') - if profile['dp_dan'] != 5: - raise Exception('Got wrong DAN score back for DP!') + if profile["sp_dan"] != 5: + raise Exception("Got wrong DAN score back for SP!") + if profile["dp_dan"] != 5: + raise Exception("Got wrong DAN score back for DP!") # Verify secret course and internet ranking course saving - self.verify_iidx25ranking_entry(profile['extid'], newname, 'ir') - self.verify_iidx25ranking_entry(profile['extid'], newname, 'secret') - self.verify_iidx25ranking_classicentry(profile['extid']) + self.verify_iidx25ranking_entry(profile["extid"], newname, "ir") + self.verify_iidx25ranking_entry(profile["extid"], newname, "secret") + self.verify_iidx25ranking_classicentry(profile["extid"]) profile = self.verify_iidx25pc_get(ref_id, card, lid) - for ptype in ['ir_data', 'secret_course_data', 'classic_course_data']: - if profile[ptype] != {2: {1: {'clear_status': 4, 'pgnum': 1771, 'gnum': 967}}}: - raise Exception(f'Invalid data {profile[ptype]} returned on profile load for {ptype}!') + for ptype in ["ir_data", "secret_course_data", "classic_course_data"]: + if profile[ptype] != { + 2: {1: {"clear_status": 4, "pgnum": 1771, "gnum": 967}} + }: + raise Exception( + f"Invalid data {profile[ptype]} returned on profile load for {ptype}!" + ) else: print("Skipping score checks for existing card") diff --git a/bemani/client/iidx/copula.py b/bemani/client/iidx/copula.py index 13ec61d..1385976 100644 --- a/bemani/client/iidx/copula.py +++ b/bemani/client/iidx/copula.py @@ -7,44 +7,44 @@ from bemani.protocol import Node class IIDXCopulaClient(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_iidx23shop_getname(self, lid: str) -> str: call = self.call_node() # Construct node - IIDX23shop = Node.void('IIDX23shop') + IIDX23shop = Node.void("IIDX23shop") call.add_child(IIDX23shop) - IIDX23shop.set_attribute('method', 'getname') - IIDX23shop.set_attribute('lid', lid) + IIDX23shop.set_attribute("method", "getname") + IIDX23shop.set_attribute("lid", lid) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX23shop/@opname") self.assert_path(resp, "response/IIDX23shop/@pid") self.assert_path(resp, "response/IIDX23shop/@cls_opt") - return resp.child('IIDX23shop').attribute('opname') + return resp.child("IIDX23shop").attribute("opname") def verify_iidx23shop_savename(self, lid: str, name: str) -> None: call = self.call_node() # Construct node - IIDX23shop = Node.void('IIDX23shop') - IIDX23shop.set_attribute('lid', lid) - IIDX23shop.set_attribute('pid', '51') - IIDX23shop.set_attribute('method', 'savename') - IIDX23shop.set_attribute('cls_opt', '0') - IIDX23shop.set_attribute('ccode', 'US') - IIDX23shop.set_attribute('opname', name) - IIDX23shop.set_attribute('rcode', '.') + IIDX23shop = Node.void("IIDX23shop") + IIDX23shop.set_attribute("lid", lid) + IIDX23shop.set_attribute("pid", "51") + IIDX23shop.set_attribute("method", "savename") + IIDX23shop.set_attribute("cls_opt", "0") + IIDX23shop.set_attribute("ccode", "US") + IIDX23shop.set_attribute("opname", name) + IIDX23shop.set_attribute("rcode", ".") call.add_child(IIDX23shop) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX23shop") @@ -53,12 +53,12 @@ class IIDXCopulaClient(BaseClient): call = self.call_node() # Construct node - IIDX23pc = Node.void('IIDX23pc') + IIDX23pc = Node.void("IIDX23pc") call.add_child(IIDX23pc) - IIDX23pc.set_attribute('method', 'common') + IIDX23pc.set_attribute("method", "common") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX23pc/ir/@beat") @@ -75,34 +75,36 @@ class IIDXCopulaClient(BaseClient): call = self.call_node() # Construct node - IIDX23pc = Node.void('IIDX23music') + IIDX23pc = Node.void("IIDX23music") call.add_child(IIDX23pc) - IIDX23pc.set_attribute('method', 'crate') + IIDX23pc.set_attribute("method", "crate") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX23music") for child in resp.child("IIDX23music").children: - if child.name != 'c': - raise Exception(f'Invalid node {child} in clear rate response!') + if child.name != "c": + raise Exception(f"Invalid node {child} in clear rate response!") if len(child.value) != 12: - raise Exception(f'Invalid node data {child} in clear rate response!') + raise Exception(f"Invalid node data {child} in clear rate response!") for v in child.value: if v < 0 or v > 101: - raise Exception(f'Invalid clear percent {child} in clear rate response!') + raise Exception( + f"Invalid clear percent {child} in clear rate response!" + ) def verify_iidx23shop_getconvention(self, lid: str) -> None: call = self.call_node() # Construct node - IIDX23pc = Node.void('IIDX23shop') + IIDX23pc = Node.void("IIDX23shop") call.add_child(IIDX23pc) - IIDX23pc.set_attribute('method', 'getconvention') - IIDX23pc.set_attribute('lid', lid) + IIDX23pc.set_attribute("method", "getconvention") + IIDX23pc.set_attribute("lid", lid) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX23shop/valid") @@ -115,15 +117,15 @@ class IIDXCopulaClient(BaseClient): call = self.call_node() # Construct node - IIDX23pc = Node.void('IIDX23pc') + IIDX23pc = Node.void("IIDX23pc") call.add_child(IIDX23pc) - IIDX23pc.set_attribute('iidxid', str(extid)) - IIDX23pc.set_attribute('lid', lid) - IIDX23pc.set_attribute('method', 'visit') - IIDX23pc.set_attribute('pid', '51') + IIDX23pc.set_attribute("iidxid", str(extid)) + IIDX23pc.set_attribute("lid", lid) + IIDX23pc.set_attribute("method", "visit") + IIDX23pc.set_attribute("pid", "51") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX23pc/@aflg") @@ -138,14 +140,14 @@ class IIDXCopulaClient(BaseClient): call = self.call_node() # Construct node - IIDX23pc = Node.void('IIDX23ranking') + IIDX23pc = Node.void("IIDX23ranking") call.add_child(IIDX23pc) - IIDX23pc.set_attribute('method', 'getranker') - IIDX23pc.set_attribute('lid', lid) - IIDX23pc.set_attribute('clid', str(clid)) + IIDX23pc.set_attribute("method", "getranker") + IIDX23pc.set_attribute("lid", lid) + IIDX23pc.set_attribute("clid", str(clid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX23ranking") @@ -154,37 +156,39 @@ class IIDXCopulaClient(BaseClient): call = self.call_node() # Construct node - IIDX23pc = Node.void('IIDX23shop') + IIDX23pc = Node.void("IIDX23shop") call.add_child(IIDX23pc) - IIDX23pc.set_attribute('method', 'sentinfo') - IIDX23pc.set_attribute('lid', lid) - IIDX23pc.set_attribute('bflg', '1') - IIDX23pc.set_attribute('bnum', '2') - IIDX23pc.set_attribute('ioid', '0') - IIDX23pc.set_attribute('tax_phase', '0') + IIDX23pc.set_attribute("method", "sentinfo") + IIDX23pc.set_attribute("lid", lid) + IIDX23pc.set_attribute("bflg", "1") + IIDX23pc.set_attribute("bnum", "2") + IIDX23pc.set_attribute("ioid", "0") + IIDX23pc.set_attribute("tax_phase", "0") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX23shop") - def verify_iidx23pc_get(self, ref_id: str, card_id: str, lid: str) -> Dict[str, Any]: + def verify_iidx23pc_get( + self, ref_id: str, card_id: str, lid: str + ) -> Dict[str, Any]: call = self.call_node() # Construct node - IIDX23pc = Node.void('IIDX23pc') + IIDX23pc = Node.void("IIDX23pc") call.add_child(IIDX23pc) - IIDX23pc.set_attribute('rid', ref_id) - IIDX23pc.set_attribute('did', ref_id) - IIDX23pc.set_attribute('pid', '51') - IIDX23pc.set_attribute('lid', lid) - IIDX23pc.set_attribute('cid', card_id) - IIDX23pc.set_attribute('method', 'get') - IIDX23pc.set_attribute('ctype', '1') + IIDX23pc.set_attribute("rid", ref_id) + IIDX23pc.set_attribute("did", ref_id) + IIDX23pc.set_attribute("pid", "51") + IIDX23pc.set_attribute("lid", lid) + IIDX23pc.set_attribute("cid", card_id) + IIDX23pc.set_attribute("method", "get") + IIDX23pc.set_attribute("ctype", "1") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that the response is correct self.assert_path(resp, "response/IIDX23pc/pcdata/@name") @@ -207,14 +211,14 @@ class IIDXCopulaClient(BaseClient): self.assert_path(resp, "response/IIDX23pc/favorite/dp_mlist") self.assert_path(resp, "response/IIDX23pc/favorite/dp_clist") - name = resp.child('IIDX23pc/pcdata').attribute('name') + name = resp.child("IIDX23pc/pcdata").attribute("name") if name != self.NAME: - raise Exception(f'Invalid name \'{name}\' returned for Ref ID \'{ref_id}\'') + raise Exception(f"Invalid name '{name}' returned for Ref ID '{ref_id}'") # Extract and return account data ir_data: Dict[int, Dict[int, Dict[str, int]]] = {} - for child in resp.child('IIDX23pc/ir_data').children: - if child.name == 'e': + for child in resp.child("IIDX23pc/ir_data").children: + if child.name == "e": course_id = child.value[0] course_chart = child.value[1] clear_status = child.value[2] @@ -224,14 +228,14 @@ class IIDXCopulaClient(BaseClient): if course_id not in ir_data: ir_data[course_id] = {} ir_data[course_id][course_chart] = { - 'clear_status': clear_status, - 'pgnum': pgnum, - 'gnum': gnum, + "clear_status": clear_status, + "pgnum": pgnum, + "gnum": gnum, } secret_course_data: Dict[int, Dict[int, Dict[str, int]]] = {} - for child in resp.child('IIDX23pc/secret_course_data').children: - if child.name == 'e': + for child in resp.child("IIDX23pc/secret_course_data").children: + if child.name == "e": course_id = child.value[0] course_chart = child.value[1] clear_status = child.value[2] @@ -241,53 +245,57 @@ class IIDXCopulaClient(BaseClient): if course_id not in secret_course_data: secret_course_data[course_id] = {} secret_course_data[course_id][course_chart] = { - 'clear_status': clear_status, - 'pgnum': pgnum, - 'gnum': gnum, + "clear_status": clear_status, + "pgnum": pgnum, + "gnum": gnum, } expert_point: Dict[int, Dict[str, int]] = {} - for child in resp.child('IIDX23pc/expert_point').children: - if child.name == 'detail': - expert_point[int(child.attribute('course_id'))] = { - 'n_point': int(child.attribute('n_point')), - 'h_point': int(child.attribute('h_point')), - 'a_point': int(child.attribute('a_point')), + for child in resp.child("IIDX23pc/expert_point").children: + if child.name == "detail": + expert_point[int(child.attribute("course_id"))] = { + "n_point": int(child.attribute("n_point")), + "h_point": int(child.attribute("h_point")), + "a_point": int(child.attribute("a_point")), } return { - 'extid': int(resp.child('IIDX23pc/pcdata').attribute('id')), - 'sp_dan': int(resp.child('IIDX23pc/grade').attribute('sgid')), - 'dp_dan': int(resp.child('IIDX23pc/grade').attribute('dgid')), - 'deller': int(resp.child('IIDX23pc/deller').attribute('deller')), - 'ir_data': ir_data, - 'secret_course_data': secret_course_data, - 'expert_point': expert_point, + "extid": int(resp.child("IIDX23pc/pcdata").attribute("id")), + "sp_dan": int(resp.child("IIDX23pc/grade").attribute("sgid")), + "dp_dan": int(resp.child("IIDX23pc/grade").attribute("dgid")), + "deller": int(resp.child("IIDX23pc/deller").attribute("deller")), + "ir_data": ir_data, + "secret_course_data": secret_course_data, + "expert_point": expert_point, } - def verify_iidx23music_getrank(self, extid: int) -> Dict[int, Dict[int, Dict[str, int]]]: + def verify_iidx23music_getrank( + self, extid: int + ) -> Dict[int, Dict[int, Dict[str, int]]]: scores: Dict[int, Dict[int, Dict[str, int]]] = {} for cltype in [0, 1]: # singles, doubles call = self.call_node() # Construct node - IIDX23music = Node.void('IIDX23music') + IIDX23music = Node.void("IIDX23music") call.add_child(IIDX23music) - IIDX23music.set_attribute('method', 'getrank') - IIDX23music.set_attribute('iidxid', str(extid)) - IIDX23music.set_attribute('cltype', str(cltype)) + IIDX23music.set_attribute("method", "getrank") + IIDX23music.set_attribute("iidxid", str(extid)) + IIDX23music.set_attribute("cltype", str(cltype)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX23music/style") - if int(resp.child('IIDX23music/style').attribute('type')) != cltype: - raise Exception('Returned wrong clear type for IIDX23music.getrank!') + if int(resp.child("IIDX23music/style").attribute("type")) != cltype: + raise Exception("Returned wrong clear type for IIDX23music.getrank!") - for child in resp.child('IIDX23music').children: - if child.name == 'm': + for child in resp.child("IIDX23music").children: + if child.name == "m": if child.value[0] != -1: - raise Exception('Got non-self score back when requesting only our scores!') + raise Exception( + "Got non-self score back when requesting only our scores!" + ) music_id = child.value[1] normal_clear_status = child.value[2] @@ -313,197 +321,207 @@ class IIDXCopulaClient(BaseClient): scores[music_id] = {} scores[music_id][normal] = { - 'clear_status': normal_clear_status, - 'ex_score': normal_ex_score, - 'miss_count': normal_miss_count, + "clear_status": normal_clear_status, + "ex_score": normal_ex_score, + "miss_count": normal_miss_count, } scores[music_id][hyper] = { - 'clear_status': hyper_clear_status, - 'ex_score': hyper_ex_score, - 'miss_count': hyper_miss_count, + "clear_status": hyper_clear_status, + "ex_score": hyper_ex_score, + "miss_count": hyper_miss_count, } scores[music_id][another] = { - 'clear_status': another_clear_status, - 'ex_score': another_ex_score, - 'miss_count': another_miss_count, + "clear_status": another_clear_status, + "ex_score": another_ex_score, + "miss_count": another_miss_count, } - elif child.name == 'b': + elif child.name == "b": music_id = child.value[0] clear_status = child.value[1] scores[music_id][6] = { - 'clear_status': clear_status, - 'ex_score': -1, - 'miss_count': -1, + "clear_status": clear_status, + "ex_score": -1, + "miss_count": -1, } return scores - def verify_iidx23pc_save(self, extid: int, card: str, lid: str, expert_point: Optional[Dict[str, int]]=None) -> None: + def verify_iidx23pc_save( + self, + extid: int, + card: str, + lid: str, + expert_point: Optional[Dict[str, int]] = None, + ) -> None: call = self.call_node() # Construct node - IIDX23pc = Node.void('IIDX23pc') + IIDX23pc = Node.void("IIDX23pc") call.add_child(IIDX23pc) - IIDX23pc.set_attribute('s_disp_judge', '1') - IIDX23pc.set_attribute('mode', '6') - IIDX23pc.set_attribute('pmode', '0') - IIDX23pc.set_attribute('method', 'save') - IIDX23pc.set_attribute('s_sorttype', '0') - IIDX23pc.set_attribute('s_exscore', '0') - IIDX23pc.set_attribute('d_notes', '0.000000') - IIDX23pc.set_attribute('gpos', '0') - IIDX23pc.set_attribute('s_gno', '8') - IIDX23pc.set_attribute('s_hispeed', '5.771802') - IIDX23pc.set_attribute('s_judge', '0') - IIDX23pc.set_attribute('d_timing', '0') - IIDX23pc.set_attribute('rtype', '0') - IIDX23pc.set_attribute('d_largejudge', '0') - IIDX23pc.set_attribute('d_lift', '60') - IIDX23pc.set_attribute('s_pace', '0') - IIDX23pc.set_attribute('d_exscore', '0') - IIDX23pc.set_attribute('d_sdtype', '0') - IIDX23pc.set_attribute('s_opstyle', '1') - IIDX23pc.set_attribute('s_achi', '449') - IIDX23pc.set_attribute('s_largejudge', '0') - IIDX23pc.set_attribute('d_gno', '0') - IIDX23pc.set_attribute('s_lift', '60') - IIDX23pc.set_attribute('s_notes', '31.484070') - IIDX23pc.set_attribute('d_tune', '0') - IIDX23pc.set_attribute('d_sdlen', '0') - IIDX23pc.set_attribute('d_achi', '4') - IIDX23pc.set_attribute('d_opstyle', '0') - IIDX23pc.set_attribute('sp_opt', '8208') - IIDX23pc.set_attribute('iidxid', str(extid)) - IIDX23pc.set_attribute('lid', lid) - IIDX23pc.set_attribute('s_judgeAdj', '0') - IIDX23pc.set_attribute('s_tune', '3') - IIDX23pc.set_attribute('s_sdtype', '1') - IIDX23pc.set_attribute('s_gtype', '2') - IIDX23pc.set_attribute('d_judge', '0') - IIDX23pc.set_attribute('cid', card) - IIDX23pc.set_attribute('cltype', '0') - IIDX23pc.set_attribute('ctype', '1') - IIDX23pc.set_attribute('bookkeep', '0') - IIDX23pc.set_attribute('d_hispeed', '0.000000') - IIDX23pc.set_attribute('d_pace', '0') - IIDX23pc.set_attribute('d_judgeAdj', '0') - IIDX23pc.set_attribute('s_timing', '1') - IIDX23pc.set_attribute('d_disp_judge', '0') - IIDX23pc.set_attribute('s_sdlen', '121') - IIDX23pc.set_attribute('dp_opt2', '0') - IIDX23pc.set_attribute('d_gtype', '0') - IIDX23pc.set_attribute('d_sorttype', '0') - IIDX23pc.set_attribute('dp_opt', '0') - pyramid = Node.void('pyramid') + IIDX23pc.set_attribute("s_disp_judge", "1") + IIDX23pc.set_attribute("mode", "6") + IIDX23pc.set_attribute("pmode", "0") + IIDX23pc.set_attribute("method", "save") + IIDX23pc.set_attribute("s_sorttype", "0") + IIDX23pc.set_attribute("s_exscore", "0") + IIDX23pc.set_attribute("d_notes", "0.000000") + IIDX23pc.set_attribute("gpos", "0") + IIDX23pc.set_attribute("s_gno", "8") + IIDX23pc.set_attribute("s_hispeed", "5.771802") + IIDX23pc.set_attribute("s_judge", "0") + IIDX23pc.set_attribute("d_timing", "0") + IIDX23pc.set_attribute("rtype", "0") + IIDX23pc.set_attribute("d_largejudge", "0") + IIDX23pc.set_attribute("d_lift", "60") + IIDX23pc.set_attribute("s_pace", "0") + IIDX23pc.set_attribute("d_exscore", "0") + IIDX23pc.set_attribute("d_sdtype", "0") + IIDX23pc.set_attribute("s_opstyle", "1") + IIDX23pc.set_attribute("s_achi", "449") + IIDX23pc.set_attribute("s_largejudge", "0") + IIDX23pc.set_attribute("d_gno", "0") + IIDX23pc.set_attribute("s_lift", "60") + IIDX23pc.set_attribute("s_notes", "31.484070") + IIDX23pc.set_attribute("d_tune", "0") + IIDX23pc.set_attribute("d_sdlen", "0") + IIDX23pc.set_attribute("d_achi", "4") + IIDX23pc.set_attribute("d_opstyle", "0") + IIDX23pc.set_attribute("sp_opt", "8208") + IIDX23pc.set_attribute("iidxid", str(extid)) + IIDX23pc.set_attribute("lid", lid) + IIDX23pc.set_attribute("s_judgeAdj", "0") + IIDX23pc.set_attribute("s_tune", "3") + IIDX23pc.set_attribute("s_sdtype", "1") + IIDX23pc.set_attribute("s_gtype", "2") + IIDX23pc.set_attribute("d_judge", "0") + IIDX23pc.set_attribute("cid", card) + IIDX23pc.set_attribute("cltype", "0") + IIDX23pc.set_attribute("ctype", "1") + IIDX23pc.set_attribute("bookkeep", "0") + IIDX23pc.set_attribute("d_hispeed", "0.000000") + IIDX23pc.set_attribute("d_pace", "0") + IIDX23pc.set_attribute("d_judgeAdj", "0") + IIDX23pc.set_attribute("s_timing", "1") + IIDX23pc.set_attribute("d_disp_judge", "0") + IIDX23pc.set_attribute("s_sdlen", "121") + IIDX23pc.set_attribute("dp_opt2", "0") + IIDX23pc.set_attribute("d_gtype", "0") + IIDX23pc.set_attribute("d_sorttype", "0") + IIDX23pc.set_attribute("dp_opt", "0") + pyramid = Node.void("pyramid") IIDX23pc.add_child(pyramid) - pyramid.set_attribute('point', '290') - destiny_catharsis = Node.void('destiny_catharsis') + pyramid.set_attribute("point", "290") + destiny_catharsis = Node.void("destiny_catharsis") IIDX23pc.add_child(destiny_catharsis) - destiny_catharsis.set_attribute('point', '290') - bemani_summer_collabo = Node.void('bemani_summer_collabo') + destiny_catharsis.set_attribute("point", "290") + bemani_summer_collabo = Node.void("bemani_summer_collabo") IIDX23pc.add_child(bemani_summer_collabo) - bemani_summer_collabo.set_attribute('point', '290') - deller = Node.void('deller') + bemani_summer_collabo.set_attribute("point", "290") + deller = Node.void("deller") IIDX23pc.add_child(deller) - deller.set_attribute('deller', '150') + deller.set_attribute("deller", "150") if expert_point is not None: - epnode = Node.void('expert_point') - epnode.set_attribute('h_point', str(expert_point['h_point'])) - epnode.set_attribute('course_id', str(expert_point['course_id'])) - epnode.set_attribute('n_point', str(expert_point['n_point'])) - epnode.set_attribute('a_point', str(expert_point['a_point'])) + epnode = Node.void("expert_point") + epnode.set_attribute("h_point", str(expert_point["h_point"])) + epnode.set_attribute("course_id", str(expert_point["course_id"])) + epnode.set_attribute("n_point", str(expert_point["n_point"])) + epnode.set_attribute("a_point", str(expert_point["a_point"])) IIDX23pc.add_child(epnode) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX23pc") - def verify_iidx23music_reg(self, extid: int, lid: str, score: Dict[str, Any]) -> None: + def verify_iidx23music_reg( + self, extid: int, lid: str, score: Dict[str, Any] + ) -> None: call = self.call_node() # Construct node - IIDX23music = Node.void('IIDX23music') + IIDX23music = Node.void("IIDX23music") call.add_child(IIDX23music) - IIDX23music.set_attribute('convid', '-1') - IIDX23music.set_attribute('iidxid', str(extid)) - IIDX23music.set_attribute('pgnum', str(score['pgnum'])) - IIDX23music.set_attribute('pid', '51') - IIDX23music.set_attribute('rankside', '1') - IIDX23music.set_attribute('cflg', str(score['clear_status'])) - IIDX23music.set_attribute('method', 'reg') - IIDX23music.set_attribute('gnum', str(score['gnum'])) - IIDX23music.set_attribute('clid', str(score['chart'])) - IIDX23music.set_attribute('mnum', str(score['mnum'])) - IIDX23music.set_attribute('is_death', '0') - IIDX23music.set_attribute('theory', '0') - IIDX23music.set_attribute('shopconvid', lid) - IIDX23music.set_attribute('mid', str(score['id'])) - IIDX23music.set_attribute('shopflg', '1') - IIDX23music.add_child(Node.binary('ghost', bytes([1] * 64))) + IIDX23music.set_attribute("convid", "-1") + IIDX23music.set_attribute("iidxid", str(extid)) + IIDX23music.set_attribute("pgnum", str(score["pgnum"])) + IIDX23music.set_attribute("pid", "51") + IIDX23music.set_attribute("rankside", "1") + IIDX23music.set_attribute("cflg", str(score["clear_status"])) + IIDX23music.set_attribute("method", "reg") + IIDX23music.set_attribute("gnum", str(score["gnum"])) + IIDX23music.set_attribute("clid", str(score["chart"])) + IIDX23music.set_attribute("mnum", str(score["mnum"])) + IIDX23music.set_attribute("is_death", "0") + IIDX23music.set_attribute("theory", "0") + IIDX23music.set_attribute("shopconvid", lid) + IIDX23music.set_attribute("mid", str(score["id"])) + IIDX23music.set_attribute("shopflg", "1") + IIDX23music.add_child(Node.binary("ghost", bytes([1] * 64))) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX23music/shopdata/@rank") self.assert_path(resp, "response/IIDX23music/ranklist/data") - def verify_iidx23music_appoint(self, extid: int, musicid: int, chart: int) -> Tuple[int, bytes]: + def verify_iidx23music_appoint( + self, extid: int, musicid: int, chart: int + ) -> Tuple[int, bytes]: call = self.call_node() # Construct node - IIDX23music = Node.void('IIDX23music') + IIDX23music = Node.void("IIDX23music") call.add_child(IIDX23music) - IIDX23music.set_attribute('clid', str(chart)) - IIDX23music.set_attribute('method', 'appoint') - IIDX23music.set_attribute('ctype', '0') - IIDX23music.set_attribute('iidxid', str(extid)) - IIDX23music.set_attribute('subtype', '') - IIDX23music.set_attribute('mid', str(musicid)) + IIDX23music.set_attribute("clid", str(chart)) + IIDX23music.set_attribute("method", "appoint") + IIDX23music.set_attribute("ctype", "0") + IIDX23music.set_attribute("iidxid", str(extid)) + IIDX23music.set_attribute("subtype", "") + IIDX23music.set_attribute("mid", str(musicid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX23music/mydata/@score") return ( - int(resp.child('IIDX23music/mydata').attribute('score')), - resp.child_value('IIDX23music/mydata'), + int(resp.child("IIDX23music/mydata").attribute("score")), + resp.child_value("IIDX23music/mydata"), ) def verify_iidx23pc_reg(self, ref_id: str, card_id: str, lid: str) -> int: call = self.call_node() # Construct node - IIDX23pc = Node.void('IIDX23pc') + IIDX23pc = Node.void("IIDX23pc") call.add_child(IIDX23pc) - IIDX23pc.set_attribute('lid', lid) - IIDX23pc.set_attribute('pid', '51') - IIDX23pc.set_attribute('method', 'reg') - IIDX23pc.set_attribute('cid', card_id) - IIDX23pc.set_attribute('did', ref_id) - IIDX23pc.set_attribute('rid', ref_id) - IIDX23pc.set_attribute('name', self.NAME) + IIDX23pc.set_attribute("lid", lid) + IIDX23pc.set_attribute("pid", "51") + IIDX23pc.set_attribute("method", "reg") + IIDX23pc.set_attribute("cid", card_id) + IIDX23pc.set_attribute("did", ref_id) + IIDX23pc.set_attribute("rid", ref_id) + IIDX23pc.set_attribute("name", self.NAME) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX23pc/@id") self.assert_path(resp, "response/IIDX23pc/@id_str") - return int(resp.child('IIDX23pc').attribute('id')) + return int(resp.child("IIDX23pc").attribute("id")) def verify_iidx23pc_playstart(self) -> None: call = self.call_node() # Construct node - IIDX23pc = Node.void('IIDX23pc') - IIDX23pc.set_attribute('method', 'playstart') - IIDX23pc.set_attribute('side', '1') + IIDX23pc = Node.void("IIDX23pc") + IIDX23pc.set_attribute("method", "playstart") + IIDX23pc.set_attribute("side", "1") call.add_child(IIDX23pc) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX23pc") @@ -512,19 +530,19 @@ class IIDXCopulaClient(BaseClient): call = self.call_node() # Construct node - IIDX23music = Node.void('IIDX23music') - IIDX23music.set_attribute('opt', '64') - IIDX23music.set_attribute('clid', str(score['chart'])) - IIDX23music.set_attribute('mid', str(score['id'])) - IIDX23music.set_attribute('gnum', str(score['gnum'])) - IIDX23music.set_attribute('cflg', str(score['clear_status'])) - IIDX23music.set_attribute('pgnum', str(score['pgnum'])) - IIDX23music.set_attribute('pid', '51') - IIDX23music.set_attribute('method', 'play') + IIDX23music = Node.void("IIDX23music") + IIDX23music.set_attribute("opt", "64") + IIDX23music.set_attribute("clid", str(score["chart"])) + IIDX23music.set_attribute("mid", str(score["id"])) + IIDX23music.set_attribute("gnum", str(score["gnum"])) + IIDX23music.set_attribute("cflg", str(score["clear_status"])) + IIDX23music.set_attribute("pgnum", str(score["pgnum"])) + IIDX23music.set_attribute("pid", "51") + IIDX23music.set_attribute("method", "play") call.add_child(IIDX23music) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX23music/@clid") @@ -536,15 +554,15 @@ class IIDXCopulaClient(BaseClient): call = self.call_node() # Construct node - IIDX23pc = Node.void('IIDX23pc') - IIDX23pc.set_attribute('cltype', '0') - IIDX23pc.set_attribute('bookkeep', '0') - IIDX23pc.set_attribute('mode', '1') - IIDX23pc.set_attribute('method', 'playend') + IIDX23pc = Node.void("IIDX23pc") + IIDX23pc.set_attribute("cltype", "0") + IIDX23pc.set_attribute("bookkeep", "0") + IIDX23pc.set_attribute("mode", "1") + IIDX23pc.set_attribute("method", "playend") call.add_child(IIDX23pc) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX23pc") @@ -553,68 +571,72 @@ class IIDXCopulaClient(BaseClient): call = self.call_node() # Construct node - IIDX23music = Node.void('IIDX23music') - IIDX23music.set_attribute('gnum', str(score['gnum'])) - IIDX23music.set_attribute('iidxid', str(iidxid)) - IIDX23music.set_attribute('mid', str(score['id'])) - IIDX23music.set_attribute('method', 'breg') - IIDX23music.set_attribute('pgnum', str(score['pgnum'])) - IIDX23music.set_attribute('cflg', str(score['clear_status'])) + IIDX23music = Node.void("IIDX23music") + IIDX23music.set_attribute("gnum", str(score["gnum"])) + IIDX23music.set_attribute("iidxid", str(iidxid)) + IIDX23music.set_attribute("mid", str(score["id"])) + IIDX23music.set_attribute("method", "breg") + IIDX23music.set_attribute("pgnum", str(score["pgnum"])) + IIDX23music.set_attribute("cflg", str(score["clear_status"])) call.add_child(IIDX23music) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX23music") - def verify_iidx23grade_raised(self, iidxid: int, shop_name: str, dantype: str) -> None: + def verify_iidx23grade_raised( + self, iidxid: int, shop_name: str, dantype: str + ) -> None: call = self.call_node() # Construct node - IIDX23grade = Node.void('IIDX23grade') - IIDX23grade.set_attribute('opname', shop_name) - IIDX23grade.set_attribute('is_mirror', '0') - IIDX23grade.set_attribute('oppid', '51') - IIDX23grade.set_attribute('achi', '50') - IIDX23grade.set_attribute('cstage', '4') - IIDX23grade.set_attribute('gid', '5') - IIDX23grade.set_attribute('iidxid', str(iidxid)) - IIDX23grade.set_attribute('gtype', '0' if dantype == 'sp' else '1') - IIDX23grade.set_attribute('is_ex', '0') - IIDX23grade.set_attribute('pside', '0') - IIDX23grade.set_attribute('method', 'raised') + IIDX23grade = Node.void("IIDX23grade") + IIDX23grade.set_attribute("opname", shop_name) + IIDX23grade.set_attribute("is_mirror", "0") + IIDX23grade.set_attribute("oppid", "51") + IIDX23grade.set_attribute("achi", "50") + IIDX23grade.set_attribute("cstage", "4") + IIDX23grade.set_attribute("gid", "5") + IIDX23grade.set_attribute("iidxid", str(iidxid)) + IIDX23grade.set_attribute("gtype", "0" if dantype == "sp" else "1") + IIDX23grade.set_attribute("is_ex", "0") + IIDX23grade.set_attribute("pside", "0") + IIDX23grade.set_attribute("method", "raised") call.add_child(IIDX23grade) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX23grade/@pnum") - def verify_iidx23ranking_entry(self, iidxid: int, shop_name: str, coursetype: str) -> None: + def verify_iidx23ranking_entry( + self, iidxid: int, shop_name: str, coursetype: str + ) -> None: call = self.call_node() # Construct node - IIDX23ranking = Node.void('IIDX23ranking') - IIDX23ranking.set_attribute('opname', shop_name) - IIDX23ranking.set_attribute('clr', '4') - IIDX23ranking.set_attribute('pgnum', '1771') - IIDX23ranking.set_attribute('coid', '2') - IIDX23ranking.set_attribute('method', 'entry') - IIDX23ranking.set_attribute('opt', '8208') - IIDX23ranking.set_attribute('opt2', '0') - IIDX23ranking.set_attribute('oppid', '51') - IIDX23ranking.set_attribute('cstage', '4') - IIDX23ranking.set_attribute('gnum', '967') - IIDX23ranking.set_attribute('pside', '1') - IIDX23ranking.set_attribute('clid', '1') - IIDX23ranking.set_attribute('regist_type', '0' if coursetype == 'ir' else '1') - IIDX23ranking.set_attribute('iidxid', str(iidxid)) + IIDX23ranking = Node.void("IIDX23ranking") + IIDX23ranking.set_attribute("opname", shop_name) + IIDX23ranking.set_attribute("clr", "4") + IIDX23ranking.set_attribute("pgnum", "1771") + IIDX23ranking.set_attribute("coid", "2") + IIDX23ranking.set_attribute("method", "entry") + IIDX23ranking.set_attribute("opt", "8208") + IIDX23ranking.set_attribute("opt2", "0") + IIDX23ranking.set_attribute("oppid", "51") + IIDX23ranking.set_attribute("cstage", "4") + IIDX23ranking.set_attribute("gnum", "967") + IIDX23ranking.set_attribute("pside", "1") + IIDX23ranking.set_attribute("clid", "1") + IIDX23ranking.set_attribute("regist_type", "0" if coursetype == "ir" else "1") + IIDX23ranking.set_attribute("iidxid", str(iidxid)) call.add_child(IIDX23ranking) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX23ranking/@anum") @@ -624,20 +646,20 @@ class IIDXCopulaClient(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -660,226 +682,292 @@ class IIDXCopulaClient(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") self.verify_iidx23pc_reg(ref_id, card, lid) self.verify_iidx23pc_get(ref_id, card, lid) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") if cardid is None: # Verify score handling profile = self.verify_iidx23pc_get(ref_id, card, lid) - if profile['sp_dan'] != -1: - raise Exception('Somehow has SP DAN ranking on new profile!') - if profile['dp_dan'] != -1: - raise Exception('Somehow has DP DAN ranking on new profile!') - if profile['deller'] != 0: - raise Exception('Somehow has deller on new profile!') - if len(profile['ir_data'].keys()) > 0: - raise Exception('Somehow has internet ranking data on new profile!') - if len(profile['secret_course_data'].keys()) > 0: - raise Exception('Somehow has secret course data on new profile!') - if len(profile['expert_point'].keys()) > 0: - raise Exception('Somehow has expert point data on new profile!') - scores = self.verify_iidx23music_getrank(profile['extid']) + if profile["sp_dan"] != -1: + raise Exception("Somehow has SP DAN ranking on new profile!") + if profile["dp_dan"] != -1: + raise Exception("Somehow has DP DAN ranking on new profile!") + if profile["deller"] != 0: + raise Exception("Somehow has deller on new profile!") + if len(profile["ir_data"].keys()) > 0: + raise Exception("Somehow has internet ranking data on new profile!") + if len(profile["secret_course_data"].keys()) > 0: + raise Exception("Somehow has secret course data on new profile!") + if len(profile["expert_point"].keys()) > 0: + raise Exception("Somehow has expert point data on new profile!") + scores = self.verify_iidx23music_getrank(profile["extid"]) if len(scores.keys()) > 0: - raise Exception('Somehow have scores on a new profile!') + raise Exception("Somehow have scores on a new profile!") for phase in [1, 2]: if phase == 1: dummyscores = [ # An okay score on a chart { - 'id': 1000, - 'chart': 2, - 'clear_status': 4, - 'pgnum': 123, - 'gnum': 123, - 'mnum': 5, + "id": 1000, + "chart": 2, + "clear_status": 4, + "pgnum": 123, + "gnum": 123, + "mnum": 5, }, # A good score on an easier chart of the same song { - 'id': 1000, - 'chart': 0, - 'clear_status': 7, - 'pgnum': 246, - 'gnum': 0, - 'mnum': 0, + "id": 1000, + "chart": 0, + "clear_status": 7, + "pgnum": 246, + "gnum": 0, + "mnum": 0, }, # A bad score on a hard chart { - 'id': 1003, - 'chart': 2, - 'clear_status': 1, - 'pgnum': 10, - 'gnum': 20, - 'mnum': 50, + "id": 1003, + "chart": 2, + "clear_status": 1, + "pgnum": 10, + "gnum": 20, + "mnum": 50, }, # A terrible score on an easy chart { - 'id': 1003, - 'chart': 0, - 'clear_status': 1, - 'pgnum': 2, - 'gnum': 5, - 'mnum': 75, + "id": 1003, + "chart": 0, + "clear_status": 1, + "pgnum": 2, + "gnum": 5, + "mnum": 75, }, ] if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 1000, - 'chart': 2, - 'clear_status': 5, - 'pgnum': 234, - 'gnum': 234, - 'mnum': 3, + "id": 1000, + "chart": 2, + "clear_status": 5, + "pgnum": 234, + "gnum": 234, + "mnum": 3, }, # A worse score on another same chart { - 'id': 1000, - 'chart': 0, - 'clear_status': 4, - 'pgnum': 123, - 'gnum': 123, - 'mnum': 35, - 'expected_clear_status': 7, - 'expected_ex_score': 492, - 'expected_miss_count': 0, + "id": 1000, + "chart": 0, + "clear_status": 4, + "pgnum": 123, + "gnum": 123, + "mnum": 35, + "expected_clear_status": 7, + "expected_ex_score": 492, + "expected_miss_count": 0, }, ] for dummyscore in dummyscores: - self.verify_iidx23music_reg(profile['extid'], lid, dummyscore) - self.verify_iidx23pc_visit(profile['extid'], lid) - self.verify_iidx23pc_save(profile['extid'], card, lid) - scores = self.verify_iidx23music_getrank(profile['extid']) + self.verify_iidx23music_reg(profile["extid"], lid, dummyscore) + self.verify_iidx23pc_visit(profile["extid"], lid) + self.verify_iidx23pc_save(profile["extid"], card, lid) + scores = self.verify_iidx23music_getrank(profile["extid"]) for score in dummyscores: - data = scores.get(score['id'], {}).get(score['chart'], None) + data = scores.get(score["id"], {}).get(score["chart"], None) if data is None: - raise Exception(f'Expected to get score back for song {score["id"]} chart {score["chart"]}!') + raise Exception( + f'Expected to get score back for song {score["id"]} chart {score["chart"]}!' + ) - if 'expected_ex_score' in score: - expected_score = score['expected_ex_score'] + if "expected_ex_score" in score: + expected_score = score["expected_ex_score"] else: - expected_score = (score['pgnum'] * 2) + score['gnum'] - if 'expected_clear_status' in score: - expected_clear_status = score['expected_clear_status'] + expected_score = (score["pgnum"] * 2) + score["gnum"] + if "expected_clear_status" in score: + expected_clear_status = score["expected_clear_status"] else: - expected_clear_status = score['clear_status'] - if 'expected_miss_count' in score: - expected_miss_count = score['expected_miss_count'] + expected_clear_status = score["clear_status"] + if "expected_miss_count" in score: + expected_miss_count = score["expected_miss_count"] else: - expected_miss_count = score['mnum'] + expected_miss_count = score["mnum"] - if data['ex_score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\'') - if data['clear_status'] != expected_clear_status: - raise Exception(f'Expected a clear status of \'{expected_clear_status}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got clear status \'{data["clear_status"]}\'') - if data['miss_count'] != expected_miss_count: - raise Exception(f'Expected a miss count of \'{expected_miss_count}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got miss count \'{data["miss_count"]}\'') + if data["ex_score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\'' + ) + if data["clear_status"] != expected_clear_status: + raise Exception( + f'Expected a clear status of \'{expected_clear_status}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got clear status \'{data["clear_status"]}\'' + ) + if data["miss_count"] != expected_miss_count: + raise Exception( + f'Expected a miss count of \'{expected_miss_count}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got miss count \'{data["miss_count"]}\'' + ) # Verify we can fetch our own ghost - ex_score, ghost = self.verify_iidx23music_appoint(profile['extid'], score['id'], score['chart']) + ex_score, ghost = self.verify_iidx23music_appoint( + profile["extid"], score["id"], score["chart"] + ) if ex_score != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\'') + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\'' + ) if len(ghost) != 64: - raise Exception(f'Wrong ghost length {len(ghost)} for ghost!') + raise Exception(f"Wrong ghost length {len(ghost)} for ghost!") for g in ghost: if g != 0x01: - raise Exception(f'Got back wrong ghost data for song \'{score["id"]}\' chart \'{score["chart"]}\'') + raise Exception( + f'Got back wrong ghost data for song \'{score["id"]}\' chart \'{score["chart"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) # Verify that we can save/load expert points - self.verify_iidx23pc_save(profile['extid'], card, lid, {'course_id': 1, 'n_point': 0, 'h_point': 500, 'a_point': 0}) + self.verify_iidx23pc_save( + profile["extid"], + card, + lid, + {"course_id": 1, "n_point": 0, "h_point": 500, "a_point": 0}, + ) profile = self.verify_iidx23pc_get(ref_id, card, lid) - if sorted(profile['expert_point'].keys()) != [1]: - raise Exception('Got back wrong number of expert course points!') - if profile['expert_point'][1] != {'n_point': 0, 'h_point': 500, 'a_point': 0}: - raise Exception('Got back wrong expert points after saving!') - self.verify_iidx23pc_save(profile['extid'], card, lid, {'course_id': 1, 'n_point': 0, 'h_point': 1000, 'a_point': 0}) + if sorted(profile["expert_point"].keys()) != [1]: + raise Exception("Got back wrong number of expert course points!") + if profile["expert_point"][1] != { + "n_point": 0, + "h_point": 500, + "a_point": 0, + }: + raise Exception("Got back wrong expert points after saving!") + self.verify_iidx23pc_save( + profile["extid"], + card, + lid, + {"course_id": 1, "n_point": 0, "h_point": 1000, "a_point": 0}, + ) profile = self.verify_iidx23pc_get(ref_id, card, lid) - if sorted(profile['expert_point'].keys()) != [1]: - raise Exception('Got back wrong number of expert course points!') - if profile['expert_point'][1] != {'n_point': 0, 'h_point': 1000, 'a_point': 0}: - raise Exception('Got back wrong expert points after saving!') - self.verify_iidx23pc_save(profile['extid'], card, lid, {'course_id': 2, 'n_point': 0, 'h_point': 0, 'a_point': 500}) + if sorted(profile["expert_point"].keys()) != [1]: + raise Exception("Got back wrong number of expert course points!") + if profile["expert_point"][1] != { + "n_point": 0, + "h_point": 1000, + "a_point": 0, + }: + raise Exception("Got back wrong expert points after saving!") + self.verify_iidx23pc_save( + profile["extid"], + card, + lid, + {"course_id": 2, "n_point": 0, "h_point": 0, "a_point": 500}, + ) profile = self.verify_iidx23pc_get(ref_id, card, lid) - if sorted(profile['expert_point'].keys()) != [1, 2]: - raise Exception('Got back wrong number of expert course points!') - if profile['expert_point'][1] != {'n_point': 0, 'h_point': 1000, 'a_point': 0}: - raise Exception('Got back wrong expert points after saving!') - if profile['expert_point'][2] != {'n_point': 0, 'h_point': 0, 'a_point': 500}: - raise Exception('Got back wrong expert points after saving!') + if sorted(profile["expert_point"].keys()) != [1, 2]: + raise Exception("Got back wrong number of expert course points!") + if profile["expert_point"][1] != { + "n_point": 0, + "h_point": 1000, + "a_point": 0, + }: + raise Exception("Got back wrong expert points after saving!") + if profile["expert_point"][2] != { + "n_point": 0, + "h_point": 0, + "a_point": 500, + }: + raise Exception("Got back wrong expert points after saving!") # Verify that a player without a card can play self.verify_iidx23pc_playstart() - self.verify_iidx23music_play({ - 'id': 1000, - 'chart': 2, - 'clear_status': 4, - 'pgnum': 123, - 'gnum': 123, - }) + self.verify_iidx23music_play( + { + "id": 1000, + "chart": 2, + "clear_status": 4, + "pgnum": 123, + "gnum": 123, + } + ) self.verify_iidx23pc_playend() # Verify shop name change setting - self.verify_iidx23shop_savename(lid, 'newname1') + self.verify_iidx23shop_savename(lid, "newname1") newname = self.verify_iidx23shop_getname(lid) - if newname != 'newname1': - raise Exception('Invalid shop name returned after change!') - self.verify_iidx23shop_savename(lid, 'newname2') + if newname != "newname1": + raise Exception("Invalid shop name returned after change!") + self.verify_iidx23shop_savename(lid, "newname2") newname = self.verify_iidx23shop_getname(lid) - if newname != 'newname2': - raise Exception('Invalid shop name returned after change!') + if newname != "newname2": + raise Exception("Invalid shop name returned after change!") # Verify beginner score saving - self.verify_iidx23music_breg(profile['extid'], { - 'id': 1000, - 'clear_status': 4, - 'pgnum': 123, - 'gnum': 123, - }) - scores = self.verify_iidx23music_getrank(profile['extid']) + self.verify_iidx23music_breg( + profile["extid"], + { + "id": 1000, + "clear_status": 4, + "pgnum": 123, + "gnum": 123, + }, + ) + scores = self.verify_iidx23music_getrank(profile["extid"]) if 1000 not in scores: - raise Exception(f'Didn\'t get expected scores back for song {1000} beginner chart!') + raise Exception( + f"Didn't get expected scores back for song {1000} beginner chart!" + ) if 6 not in scores[1000]: - raise Exception(f'Didn\'t get beginner score back for song {1000}!') - if scores[1000][6] != {'clear_status': 4, 'ex_score': -1, 'miss_count': -1}: - raise Exception('Didn\'t get correct status back from beginner save!') + raise Exception(f"Didn't get beginner score back for song {1000}!") + if scores[1000][6] != {"clear_status": 4, "ex_score": -1, "miss_count": -1}: + raise Exception("Didn't get correct status back from beginner save!") # Verify DAN score saving and loading - self.verify_iidx23grade_raised(profile['extid'], newname, 'sp') - self.verify_iidx23grade_raised(profile['extid'], newname, 'dp') + self.verify_iidx23grade_raised(profile["extid"], newname, "sp") + self.verify_iidx23grade_raised(profile["extid"], newname, "dp") profile = self.verify_iidx23pc_get(ref_id, card, lid) - if profile['sp_dan'] != 5: - raise Exception('Got wrong DAN score back for SP!') - if profile['dp_dan'] != 5: - raise Exception('Got wrong DAN score back for DP!') + if profile["sp_dan"] != 5: + raise Exception("Got wrong DAN score back for SP!") + if profile["dp_dan"] != 5: + raise Exception("Got wrong DAN score back for DP!") # Verify secret course and internet ranking course saving - self.verify_iidx23ranking_entry(profile['extid'], newname, 'ir') - self.verify_iidx23ranking_entry(profile['extid'], newname, 'secret') + self.verify_iidx23ranking_entry(profile["extid"], newname, "ir") + self.verify_iidx23ranking_entry(profile["extid"], newname, "secret") profile = self.verify_iidx23pc_get(ref_id, card, lid) - for ptype in ['ir_data', 'secret_course_data']: - if profile[ptype] != {2: {1: {'clear_status': 4, 'pgnum': 1771, 'gnum': 967}}}: - raise Exception(f'Invalid data {profile[ptype]} returned on profile load for {ptype}!') + for ptype in ["ir_data", "secret_course_data"]: + if profile[ptype] != { + 2: {1: {"clear_status": 4, "pgnum": 1771, "gnum": 967}} + }: + raise Exception( + f"Invalid data {profile[ptype]} returned on profile load for {ptype}!" + ) else: print("Skipping score checks for existing card") diff --git a/bemani/client/iidx/pendual.py b/bemani/client/iidx/pendual.py index 80ed0e5..acb7600 100644 --- a/bemani/client/iidx/pendual.py +++ b/bemani/client/iidx/pendual.py @@ -7,44 +7,44 @@ from bemani.protocol import Node class IIDXPendualClient(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_iidx22shop_getname(self, lid: str) -> str: call = self.call_node() # Construct node - IIDX22shop = Node.void('IIDX22shop') + IIDX22shop = Node.void("IIDX22shop") call.add_child(IIDX22shop) - IIDX22shop.set_attribute('method', 'getname') - IIDX22shop.set_attribute('lid', lid) + IIDX22shop.set_attribute("method", "getname") + IIDX22shop.set_attribute("lid", lid) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX22shop/@opname") self.assert_path(resp, "response/IIDX22shop/@pid") self.assert_path(resp, "response/IIDX22shop/@cls_opt") - return resp.child('IIDX22shop').attribute('opname') + return resp.child("IIDX22shop").attribute("opname") def verify_iidx22shop_savename(self, lid: str, name: str) -> None: call = self.call_node() # Construct node - IIDX22shop = Node.void('IIDX22shop') - IIDX22shop.set_attribute('lid', lid) - IIDX22shop.set_attribute('pid', '51') - IIDX22shop.set_attribute('method', 'savename') - IIDX22shop.set_attribute('cls_opt', '0') - IIDX22shop.set_attribute('ccode', 'US') - IIDX22shop.set_attribute('opname', name) - IIDX22shop.set_attribute('rcode', '.') + IIDX22shop = Node.void("IIDX22shop") + IIDX22shop.set_attribute("lid", lid) + IIDX22shop.set_attribute("pid", "51") + IIDX22shop.set_attribute("method", "savename") + IIDX22shop.set_attribute("cls_opt", "0") + IIDX22shop.set_attribute("ccode", "US") + IIDX22shop.set_attribute("opname", name) + IIDX22shop.set_attribute("rcode", ".") call.add_child(IIDX22shop) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX22shop") @@ -53,12 +53,12 @@ class IIDXPendualClient(BaseClient): call = self.call_node() # Construct node - IIDX22pc = Node.void('IIDX22pc') + IIDX22pc = Node.void("IIDX22pc") call.add_child(IIDX22pc) - IIDX22pc.set_attribute('method', 'common') + IIDX22pc.set_attribute("method", "common") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX22pc/ir/@beat") @@ -75,34 +75,36 @@ class IIDXPendualClient(BaseClient): call = self.call_node() # Construct node - IIDX22pc = Node.void('IIDX22music') + IIDX22pc = Node.void("IIDX22music") call.add_child(IIDX22pc) - IIDX22pc.set_attribute('method', 'crate') + IIDX22pc.set_attribute("method", "crate") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX22music") for child in resp.child("IIDX22music").children: - if child.name != 'c': - raise Exception(f'Invalid node {child} in clear rate response!') + if child.name != "c": + raise Exception(f"Invalid node {child} in clear rate response!") if len(child.value) != 12: - raise Exception(f'Invalid node data {child} in clear rate response!') + raise Exception(f"Invalid node data {child} in clear rate response!") for v in child.value: if v < 0 or v > 101: - raise Exception(f'Invalid clear percent {child} in clear rate response!') + raise Exception( + f"Invalid clear percent {child} in clear rate response!" + ) def verify_iidx22shop_getconvention(self, lid: str) -> None: call = self.call_node() # Construct node - IIDX22pc = Node.void('IIDX22shop') + IIDX22pc = Node.void("IIDX22shop") call.add_child(IIDX22pc) - IIDX22pc.set_attribute('method', 'getconvention') - IIDX22pc.set_attribute('lid', lid) + IIDX22pc.set_attribute("method", "getconvention") + IIDX22pc.set_attribute("lid", lid) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX22shop/valid") @@ -115,15 +117,15 @@ class IIDXPendualClient(BaseClient): call = self.call_node() # Construct node - IIDX22pc = Node.void('IIDX22pc') + IIDX22pc = Node.void("IIDX22pc") call.add_child(IIDX22pc) - IIDX22pc.set_attribute('iidxid', str(extid)) - IIDX22pc.set_attribute('lid', lid) - IIDX22pc.set_attribute('method', 'visit') - IIDX22pc.set_attribute('pid', '51') + IIDX22pc.set_attribute("iidxid", str(extid)) + IIDX22pc.set_attribute("lid", lid) + IIDX22pc.set_attribute("method", "visit") + IIDX22pc.set_attribute("pid", "51") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX22pc/@aflg") @@ -138,14 +140,14 @@ class IIDXPendualClient(BaseClient): call = self.call_node() # Construct node - IIDX22pc = Node.void('IIDX22ranking') + IIDX22pc = Node.void("IIDX22ranking") call.add_child(IIDX22pc) - IIDX22pc.set_attribute('method', 'getranker') - IIDX22pc.set_attribute('lid', lid) - IIDX22pc.set_attribute('clid', str(clid)) + IIDX22pc.set_attribute("method", "getranker") + IIDX22pc.set_attribute("lid", lid) + IIDX22pc.set_attribute("clid", str(clid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX22ranking") @@ -154,37 +156,39 @@ class IIDXPendualClient(BaseClient): call = self.call_node() # Construct node - IIDX22pc = Node.void('IIDX22shop') + IIDX22pc = Node.void("IIDX22shop") call.add_child(IIDX22pc) - IIDX22pc.set_attribute('method', 'sentinfo') - IIDX22pc.set_attribute('lid', lid) - IIDX22pc.set_attribute('bflg', '1') - IIDX22pc.set_attribute('bnum', '2') - IIDX22pc.set_attribute('ioid', '0') - IIDX22pc.set_attribute('tax_phase', '0') + IIDX22pc.set_attribute("method", "sentinfo") + IIDX22pc.set_attribute("lid", lid) + IIDX22pc.set_attribute("bflg", "1") + IIDX22pc.set_attribute("bnum", "2") + IIDX22pc.set_attribute("ioid", "0") + IIDX22pc.set_attribute("tax_phase", "0") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX22shop") - def verify_iidx22pc_get(self, ref_id: str, card_id: str, lid: str) -> Dict[str, Any]: + def verify_iidx22pc_get( + self, ref_id: str, card_id: str, lid: str + ) -> Dict[str, Any]: call = self.call_node() # Construct node - IIDX22pc = Node.void('IIDX22pc') + IIDX22pc = Node.void("IIDX22pc") call.add_child(IIDX22pc) - IIDX22pc.set_attribute('rid', ref_id) - IIDX22pc.set_attribute('did', ref_id) - IIDX22pc.set_attribute('pid', '51') - IIDX22pc.set_attribute('lid', lid) - IIDX22pc.set_attribute('cid', card_id) - IIDX22pc.set_attribute('method', 'get') - IIDX22pc.set_attribute('ctype', '1') + IIDX22pc.set_attribute("rid", ref_id) + IIDX22pc.set_attribute("did", ref_id) + IIDX22pc.set_attribute("pid", "51") + IIDX22pc.set_attribute("lid", lid) + IIDX22pc.set_attribute("cid", card_id) + IIDX22pc.set_attribute("method", "get") + IIDX22pc.set_attribute("ctype", "1") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that the response is correct self.assert_path(resp, "response/IIDX22pc/pcdata/@name") @@ -208,14 +212,14 @@ class IIDXPendualClient(BaseClient): self.assert_path(resp, "response/IIDX22pc/favorite/dp_mlist") self.assert_path(resp, "response/IIDX22pc/favorite/dp_clist") - name = resp.child('IIDX22pc/pcdata').attribute('name') + name = resp.child("IIDX22pc/pcdata").attribute("name") if name != self.NAME: - raise Exception(f'Invalid name \'{name}\' returned for Ref ID \'{ref_id}\'') + raise Exception(f"Invalid name '{name}' returned for Ref ID '{ref_id}'") # Extract and return account data ir_data: Dict[int, Dict[int, Dict[str, int]]] = {} - for child in resp.child('IIDX22pc/ir_data').children: - if child.name == 'e': + for child in resp.child("IIDX22pc/ir_data").children: + if child.name == "e": course_id = child.value[0] course_chart = child.value[1] clear_status = child.value[2] @@ -225,14 +229,14 @@ class IIDXPendualClient(BaseClient): if course_id not in ir_data: ir_data[course_id] = {} ir_data[course_id][course_chart] = { - 'clear_status': clear_status, - 'pgnum': pgnum, - 'gnum': gnum, + "clear_status": clear_status, + "pgnum": pgnum, + "gnum": gnum, } secret_course_data: Dict[int, Dict[int, Dict[str, int]]] = {} - for child in resp.child('IIDX22pc/secret_course_data').children: - if child.name == 'e': + for child in resp.child("IIDX22pc/secret_course_data").children: + if child.name == "e": course_id = child.value[0] course_chart = child.value[1] clear_status = child.value[2] @@ -242,53 +246,57 @@ class IIDXPendualClient(BaseClient): if course_id not in secret_course_data: secret_course_data[course_id] = {} secret_course_data[course_id][course_chart] = { - 'clear_status': clear_status, - 'pgnum': pgnum, - 'gnum': gnum, + "clear_status": clear_status, + "pgnum": pgnum, + "gnum": gnum, } expert_point: Dict[int, Dict[str, int]] = {} - for child in resp.child('IIDX22pc/expert_point').children: - if child.name == 'detail': - expert_point[int(child.attribute('course_id'))] = { - 'n_point': int(child.attribute('n_point')), - 'h_point': int(child.attribute('h_point')), - 'a_point': int(child.attribute('a_point')), + for child in resp.child("IIDX22pc/expert_point").children: + if child.name == "detail": + expert_point[int(child.attribute("course_id"))] = { + "n_point": int(child.attribute("n_point")), + "h_point": int(child.attribute("h_point")), + "a_point": int(child.attribute("a_point")), } return { - 'extid': int(resp.child('IIDX22pc/pcdata').attribute('id')), - 'sp_dan': int(resp.child('IIDX22pc/grade').attribute('sgid')), - 'dp_dan': int(resp.child('IIDX22pc/grade').attribute('dgid')), - 'deller': int(resp.child('IIDX22pc/deller').attribute('deller')), - 'ir_data': ir_data, - 'secret_course_data': secret_course_data, - 'expert_point': expert_point, + "extid": int(resp.child("IIDX22pc/pcdata").attribute("id")), + "sp_dan": int(resp.child("IIDX22pc/grade").attribute("sgid")), + "dp_dan": int(resp.child("IIDX22pc/grade").attribute("dgid")), + "deller": int(resp.child("IIDX22pc/deller").attribute("deller")), + "ir_data": ir_data, + "secret_course_data": secret_course_data, + "expert_point": expert_point, } - def verify_iidx22music_getrank(self, extid: int) -> Dict[int, Dict[int, Dict[str, int]]]: + def verify_iidx22music_getrank( + self, extid: int + ) -> Dict[int, Dict[int, Dict[str, int]]]: scores: Dict[int, Dict[int, Dict[str, int]]] = {} for cltype in [0, 1]: # singles, doubles call = self.call_node() # Construct node - IIDX22music = Node.void('IIDX22music') + IIDX22music = Node.void("IIDX22music") call.add_child(IIDX22music) - IIDX22music.set_attribute('method', 'getrank') - IIDX22music.set_attribute('iidxid', str(extid)) - IIDX22music.set_attribute('cltype', str(cltype)) + IIDX22music.set_attribute("method", "getrank") + IIDX22music.set_attribute("iidxid", str(extid)) + IIDX22music.set_attribute("cltype", str(cltype)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX22music/style") - if int(resp.child('IIDX22music/style').attribute('type')) != cltype: - raise Exception('Returned wrong clear type for IIDX22music.getrank!') + if int(resp.child("IIDX22music/style").attribute("type")) != cltype: + raise Exception("Returned wrong clear type for IIDX22music.getrank!") - for child in resp.child('IIDX22music').children: - if child.name == 'm': + for child in resp.child("IIDX22music").children: + if child.name == "m": if child.value[0] != -1: - raise Exception('Got non-self score back when requesting only our scores!') + raise Exception( + "Got non-self score back when requesting only our scores!" + ) music_id = child.value[1] normal_clear_status = child.value[2] @@ -314,197 +322,207 @@ class IIDXPendualClient(BaseClient): scores[music_id] = {} scores[music_id][normal] = { - 'clear_status': normal_clear_status, - 'ex_score': normal_ex_score, - 'miss_count': normal_miss_count, + "clear_status": normal_clear_status, + "ex_score": normal_ex_score, + "miss_count": normal_miss_count, } scores[music_id][hyper] = { - 'clear_status': hyper_clear_status, - 'ex_score': hyper_ex_score, - 'miss_count': hyper_miss_count, + "clear_status": hyper_clear_status, + "ex_score": hyper_ex_score, + "miss_count": hyper_miss_count, } scores[music_id][another] = { - 'clear_status': another_clear_status, - 'ex_score': another_ex_score, - 'miss_count': another_miss_count, + "clear_status": another_clear_status, + "ex_score": another_ex_score, + "miss_count": another_miss_count, } - elif child.name == 'b': + elif child.name == "b": music_id = child.value[0] clear_status = child.value[1] scores[music_id][6] = { - 'clear_status': clear_status, - 'ex_score': -1, - 'miss_count': -1, + "clear_status": clear_status, + "ex_score": -1, + "miss_count": -1, } return scores - def verify_iidx22pc_save(self, extid: int, card: str, lid: str, expert_point: Optional[Dict[str, int]]=None) -> None: + def verify_iidx22pc_save( + self, + extid: int, + card: str, + lid: str, + expert_point: Optional[Dict[str, int]] = None, + ) -> None: call = self.call_node() # Construct node - IIDX22pc = Node.void('IIDX22pc') + IIDX22pc = Node.void("IIDX22pc") call.add_child(IIDX22pc) - IIDX22pc.set_attribute('s_disp_judge', '1') - IIDX22pc.set_attribute('mode', '6') - IIDX22pc.set_attribute('pmode', '0') - IIDX22pc.set_attribute('method', 'save') - IIDX22pc.set_attribute('s_sorttype', '0') - IIDX22pc.set_attribute('s_exscore', '0') - IIDX22pc.set_attribute('d_notes', '0.000000') - IIDX22pc.set_attribute('gpos', '0') - IIDX22pc.set_attribute('s_gno', '8') - IIDX22pc.set_attribute('s_hispeed', '5.771802') - IIDX22pc.set_attribute('s_judge', '0') - IIDX22pc.set_attribute('d_timing', '0') - IIDX22pc.set_attribute('rtype', '0') - IIDX22pc.set_attribute('d_largejudge', '0') - IIDX22pc.set_attribute('d_lift', '60') - IIDX22pc.set_attribute('s_pace', '0') - IIDX22pc.set_attribute('d_exscore', '0') - IIDX22pc.set_attribute('d_sdtype', '0') - IIDX22pc.set_attribute('s_opstyle', '1') - IIDX22pc.set_attribute('s_achi', '449') - IIDX22pc.set_attribute('s_largejudge', '0') - IIDX22pc.set_attribute('d_gno', '0') - IIDX22pc.set_attribute('s_lift', '60') - IIDX22pc.set_attribute('s_notes', '31.484070') - IIDX22pc.set_attribute('d_tune', '0') - IIDX22pc.set_attribute('d_sdlen', '0') - IIDX22pc.set_attribute('d_achi', '4') - IIDX22pc.set_attribute('d_opstyle', '0') - IIDX22pc.set_attribute('sp_opt', '8208') - IIDX22pc.set_attribute('iidxid', str(extid)) - IIDX22pc.set_attribute('lid', lid) - IIDX22pc.set_attribute('s_judgeAdj', '0') - IIDX22pc.set_attribute('s_tune', '3') - IIDX22pc.set_attribute('s_sdtype', '1') - IIDX22pc.set_attribute('s_gtype', '2') - IIDX22pc.set_attribute('d_judge', '0') - IIDX22pc.set_attribute('cid', card) - IIDX22pc.set_attribute('cltype', '0') - IIDX22pc.set_attribute('ctype', '1') - IIDX22pc.set_attribute('bookkeep', '0') - IIDX22pc.set_attribute('d_hispeed', '0.000000') - IIDX22pc.set_attribute('d_pace', '0') - IIDX22pc.set_attribute('d_judgeAdj', '0') - IIDX22pc.set_attribute('s_timing', '1') - IIDX22pc.set_attribute('d_disp_judge', '0') - IIDX22pc.set_attribute('s_sdlen', '121') - IIDX22pc.set_attribute('dp_opt2', '0') - IIDX22pc.set_attribute('d_gtype', '0') - IIDX22pc.set_attribute('d_sorttype', '0') - IIDX22pc.set_attribute('dp_opt', '0') - pyramid = Node.void('pyramid') + IIDX22pc.set_attribute("s_disp_judge", "1") + IIDX22pc.set_attribute("mode", "6") + IIDX22pc.set_attribute("pmode", "0") + IIDX22pc.set_attribute("method", "save") + IIDX22pc.set_attribute("s_sorttype", "0") + IIDX22pc.set_attribute("s_exscore", "0") + IIDX22pc.set_attribute("d_notes", "0.000000") + IIDX22pc.set_attribute("gpos", "0") + IIDX22pc.set_attribute("s_gno", "8") + IIDX22pc.set_attribute("s_hispeed", "5.771802") + IIDX22pc.set_attribute("s_judge", "0") + IIDX22pc.set_attribute("d_timing", "0") + IIDX22pc.set_attribute("rtype", "0") + IIDX22pc.set_attribute("d_largejudge", "0") + IIDX22pc.set_attribute("d_lift", "60") + IIDX22pc.set_attribute("s_pace", "0") + IIDX22pc.set_attribute("d_exscore", "0") + IIDX22pc.set_attribute("d_sdtype", "0") + IIDX22pc.set_attribute("s_opstyle", "1") + IIDX22pc.set_attribute("s_achi", "449") + IIDX22pc.set_attribute("s_largejudge", "0") + IIDX22pc.set_attribute("d_gno", "0") + IIDX22pc.set_attribute("s_lift", "60") + IIDX22pc.set_attribute("s_notes", "31.484070") + IIDX22pc.set_attribute("d_tune", "0") + IIDX22pc.set_attribute("d_sdlen", "0") + IIDX22pc.set_attribute("d_achi", "4") + IIDX22pc.set_attribute("d_opstyle", "0") + IIDX22pc.set_attribute("sp_opt", "8208") + IIDX22pc.set_attribute("iidxid", str(extid)) + IIDX22pc.set_attribute("lid", lid) + IIDX22pc.set_attribute("s_judgeAdj", "0") + IIDX22pc.set_attribute("s_tune", "3") + IIDX22pc.set_attribute("s_sdtype", "1") + IIDX22pc.set_attribute("s_gtype", "2") + IIDX22pc.set_attribute("d_judge", "0") + IIDX22pc.set_attribute("cid", card) + IIDX22pc.set_attribute("cltype", "0") + IIDX22pc.set_attribute("ctype", "1") + IIDX22pc.set_attribute("bookkeep", "0") + IIDX22pc.set_attribute("d_hispeed", "0.000000") + IIDX22pc.set_attribute("d_pace", "0") + IIDX22pc.set_attribute("d_judgeAdj", "0") + IIDX22pc.set_attribute("s_timing", "1") + IIDX22pc.set_attribute("d_disp_judge", "0") + IIDX22pc.set_attribute("s_sdlen", "121") + IIDX22pc.set_attribute("dp_opt2", "0") + IIDX22pc.set_attribute("d_gtype", "0") + IIDX22pc.set_attribute("d_sorttype", "0") + IIDX22pc.set_attribute("dp_opt", "0") + pyramid = Node.void("pyramid") IIDX22pc.add_child(pyramid) - pyramid.set_attribute('point', '290') - destiny_catharsis = Node.void('destiny_catharsis') + pyramid.set_attribute("point", "290") + destiny_catharsis = Node.void("destiny_catharsis") IIDX22pc.add_child(destiny_catharsis) - destiny_catharsis.set_attribute('point', '290') - bemani_summer_collabo = Node.void('bemani_summer_collabo') + destiny_catharsis.set_attribute("point", "290") + bemani_summer_collabo = Node.void("bemani_summer_collabo") IIDX22pc.add_child(bemani_summer_collabo) - bemani_summer_collabo.set_attribute('point', '290') - deller = Node.void('deller') + bemani_summer_collabo.set_attribute("point", "290") + deller = Node.void("deller") IIDX22pc.add_child(deller) - deller.set_attribute('deller', '150') + deller.set_attribute("deller", "150") if expert_point is not None: - epnode = Node.void('expert_point') - epnode.set_attribute('h_point', str(expert_point['h_point'])) - epnode.set_attribute('course_id', str(expert_point['course_id'])) - epnode.set_attribute('n_point', str(expert_point['n_point'])) - epnode.set_attribute('a_point', str(expert_point['a_point'])) + epnode = Node.void("expert_point") + epnode.set_attribute("h_point", str(expert_point["h_point"])) + epnode.set_attribute("course_id", str(expert_point["course_id"])) + epnode.set_attribute("n_point", str(expert_point["n_point"])) + epnode.set_attribute("a_point", str(expert_point["a_point"])) IIDX22pc.add_child(epnode) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX22pc") - def verify_iidx22music_reg(self, extid: int, lid: str, score: Dict[str, Any]) -> None: + def verify_iidx22music_reg( + self, extid: int, lid: str, score: Dict[str, Any] + ) -> None: call = self.call_node() # Construct node - IIDX22music = Node.void('IIDX22music') + IIDX22music = Node.void("IIDX22music") call.add_child(IIDX22music) - IIDX22music.set_attribute('convid', '-1') - IIDX22music.set_attribute('iidxid', str(extid)) - IIDX22music.set_attribute('pgnum', str(score['pgnum'])) - IIDX22music.set_attribute('pid', '51') - IIDX22music.set_attribute('rankside', '1') - IIDX22music.set_attribute('cflg', str(score['clear_status'])) - IIDX22music.set_attribute('method', 'reg') - IIDX22music.set_attribute('gnum', str(score['gnum'])) - IIDX22music.set_attribute('clid', str(score['chart'])) - IIDX22music.set_attribute('mnum', str(score['mnum'])) - IIDX22music.set_attribute('is_death', '0') - IIDX22music.set_attribute('theory', '0') - IIDX22music.set_attribute('shopconvid', lid) - IIDX22music.set_attribute('mid', str(score['id'])) - IIDX22music.set_attribute('shopflg', '1') - IIDX22music.add_child(Node.binary('ghost', bytes([1] * 64))) + IIDX22music.set_attribute("convid", "-1") + IIDX22music.set_attribute("iidxid", str(extid)) + IIDX22music.set_attribute("pgnum", str(score["pgnum"])) + IIDX22music.set_attribute("pid", "51") + IIDX22music.set_attribute("rankside", "1") + IIDX22music.set_attribute("cflg", str(score["clear_status"])) + IIDX22music.set_attribute("method", "reg") + IIDX22music.set_attribute("gnum", str(score["gnum"])) + IIDX22music.set_attribute("clid", str(score["chart"])) + IIDX22music.set_attribute("mnum", str(score["mnum"])) + IIDX22music.set_attribute("is_death", "0") + IIDX22music.set_attribute("theory", "0") + IIDX22music.set_attribute("shopconvid", lid) + IIDX22music.set_attribute("mid", str(score["id"])) + IIDX22music.set_attribute("shopflg", "1") + IIDX22music.add_child(Node.binary("ghost", bytes([1] * 64))) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX22music/shopdata/@rank") self.assert_path(resp, "response/IIDX22music/ranklist/data") - def verify_iidx22music_appoint(self, extid: int, musicid: int, chart: int) -> Tuple[int, bytes]: + def verify_iidx22music_appoint( + self, extid: int, musicid: int, chart: int + ) -> Tuple[int, bytes]: call = self.call_node() # Construct node - IIDX22music = Node.void('IIDX22music') + IIDX22music = Node.void("IIDX22music") call.add_child(IIDX22music) - IIDX22music.set_attribute('clid', str(chart)) - IIDX22music.set_attribute('method', 'appoint') - IIDX22music.set_attribute('ctype', '0') - IIDX22music.set_attribute('iidxid', str(extid)) - IIDX22music.set_attribute('subtype', '') - IIDX22music.set_attribute('mid', str(musicid)) + IIDX22music.set_attribute("clid", str(chart)) + IIDX22music.set_attribute("method", "appoint") + IIDX22music.set_attribute("ctype", "0") + IIDX22music.set_attribute("iidxid", str(extid)) + IIDX22music.set_attribute("subtype", "") + IIDX22music.set_attribute("mid", str(musicid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX22music/mydata/@score") return ( - int(resp.child('IIDX22music/mydata').attribute('score')), - resp.child_value('IIDX22music/mydata'), + int(resp.child("IIDX22music/mydata").attribute("score")), + resp.child_value("IIDX22music/mydata"), ) def verify_iidx22pc_reg(self, ref_id: str, card_id: str, lid: str) -> int: call = self.call_node() # Construct node - IIDX22pc = Node.void('IIDX22pc') + IIDX22pc = Node.void("IIDX22pc") call.add_child(IIDX22pc) - IIDX22pc.set_attribute('lid', lid) - IIDX22pc.set_attribute('pid', '51') - IIDX22pc.set_attribute('method', 'reg') - IIDX22pc.set_attribute('cid', card_id) - IIDX22pc.set_attribute('did', ref_id) - IIDX22pc.set_attribute('rid', ref_id) - IIDX22pc.set_attribute('name', self.NAME) + IIDX22pc.set_attribute("lid", lid) + IIDX22pc.set_attribute("pid", "51") + IIDX22pc.set_attribute("method", "reg") + IIDX22pc.set_attribute("cid", card_id) + IIDX22pc.set_attribute("did", ref_id) + IIDX22pc.set_attribute("rid", ref_id) + IIDX22pc.set_attribute("name", self.NAME) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX22pc/@id") self.assert_path(resp, "response/IIDX22pc/@id_str") - return int(resp.child('IIDX22pc').attribute('id')) + return int(resp.child("IIDX22pc").attribute("id")) def verify_iidx22pc_playstart(self) -> None: call = self.call_node() # Construct node - IIDX22pc = Node.void('IIDX22pc') - IIDX22pc.set_attribute('method', 'playstart') - IIDX22pc.set_attribute('side', '1') + IIDX22pc = Node.void("IIDX22pc") + IIDX22pc.set_attribute("method", "playstart") + IIDX22pc.set_attribute("side", "1") call.add_child(IIDX22pc) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX22pc") @@ -513,19 +531,19 @@ class IIDXPendualClient(BaseClient): call = self.call_node() # Construct node - IIDX22music = Node.void('IIDX22music') - IIDX22music.set_attribute('opt', '64') - IIDX22music.set_attribute('clid', str(score['chart'])) - IIDX22music.set_attribute('mid', str(score['id'])) - IIDX22music.set_attribute('gnum', str(score['gnum'])) - IIDX22music.set_attribute('cflg', str(score['clear_status'])) - IIDX22music.set_attribute('pgnum', str(score['pgnum'])) - IIDX22music.set_attribute('pid', '51') - IIDX22music.set_attribute('method', 'play') + IIDX22music = Node.void("IIDX22music") + IIDX22music.set_attribute("opt", "64") + IIDX22music.set_attribute("clid", str(score["chart"])) + IIDX22music.set_attribute("mid", str(score["id"])) + IIDX22music.set_attribute("gnum", str(score["gnum"])) + IIDX22music.set_attribute("cflg", str(score["clear_status"])) + IIDX22music.set_attribute("pgnum", str(score["pgnum"])) + IIDX22music.set_attribute("pid", "51") + IIDX22music.set_attribute("method", "play") call.add_child(IIDX22music) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX22music/@clid") @@ -537,15 +555,15 @@ class IIDXPendualClient(BaseClient): call = self.call_node() # Construct node - IIDX22pc = Node.void('IIDX22pc') - IIDX22pc.set_attribute('cltype', '0') - IIDX22pc.set_attribute('bookkeep', '0') - IIDX22pc.set_attribute('mode', '1') - IIDX22pc.set_attribute('method', 'playend') + IIDX22pc = Node.void("IIDX22pc") + IIDX22pc.set_attribute("cltype", "0") + IIDX22pc.set_attribute("bookkeep", "0") + IIDX22pc.set_attribute("mode", "1") + IIDX22pc.set_attribute("method", "playend") call.add_child(IIDX22pc) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX22pc") @@ -554,68 +572,72 @@ class IIDXPendualClient(BaseClient): call = self.call_node() # Construct node - IIDX22music = Node.void('IIDX22music') - IIDX22music.set_attribute('gnum', str(score['gnum'])) - IIDX22music.set_attribute('iidxid', str(iidxid)) - IIDX22music.set_attribute('mid', str(score['id'])) - IIDX22music.set_attribute('method', 'breg') - IIDX22music.set_attribute('pgnum', str(score['pgnum'])) - IIDX22music.set_attribute('cflg', str(score['clear_status'])) + IIDX22music = Node.void("IIDX22music") + IIDX22music.set_attribute("gnum", str(score["gnum"])) + IIDX22music.set_attribute("iidxid", str(iidxid)) + IIDX22music.set_attribute("mid", str(score["id"])) + IIDX22music.set_attribute("method", "breg") + IIDX22music.set_attribute("pgnum", str(score["pgnum"])) + IIDX22music.set_attribute("cflg", str(score["clear_status"])) call.add_child(IIDX22music) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX22music") - def verify_iidx22grade_raised(self, iidxid: int, shop_name: str, dantype: str) -> None: + def verify_iidx22grade_raised( + self, iidxid: int, shop_name: str, dantype: str + ) -> None: call = self.call_node() # Construct node - IIDX22grade = Node.void('IIDX22grade') - IIDX22grade.set_attribute('opname', shop_name) - IIDX22grade.set_attribute('is_mirror', '0') - IIDX22grade.set_attribute('oppid', '51') - IIDX22grade.set_attribute('achi', '50') - IIDX22grade.set_attribute('cflg', '4' if dantype == 'sp' else '3') - IIDX22grade.set_attribute('gid', '5') - IIDX22grade.set_attribute('iidxid', str(iidxid)) - IIDX22grade.set_attribute('gtype', '0' if dantype == 'sp' else '1') - IIDX22grade.set_attribute('is_ex', '0') - IIDX22grade.set_attribute('pside', '0') - IIDX22grade.set_attribute('method', 'raised') + IIDX22grade = Node.void("IIDX22grade") + IIDX22grade.set_attribute("opname", shop_name) + IIDX22grade.set_attribute("is_mirror", "0") + IIDX22grade.set_attribute("oppid", "51") + IIDX22grade.set_attribute("achi", "50") + IIDX22grade.set_attribute("cflg", "4" if dantype == "sp" else "3") + IIDX22grade.set_attribute("gid", "5") + IIDX22grade.set_attribute("iidxid", str(iidxid)) + IIDX22grade.set_attribute("gtype", "0" if dantype == "sp" else "1") + IIDX22grade.set_attribute("is_ex", "0") + IIDX22grade.set_attribute("pside", "0") + IIDX22grade.set_attribute("method", "raised") call.add_child(IIDX22grade) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX22grade/@pnum") - def verify_iidx22ranking_entry(self, iidxid: int, shop_name: str, coursetype: str) -> None: + def verify_iidx22ranking_entry( + self, iidxid: int, shop_name: str, coursetype: str + ) -> None: call = self.call_node() # Construct node - IIDX22ranking = Node.void('IIDX22ranking') - IIDX22ranking.set_attribute('opname', shop_name) - IIDX22ranking.set_attribute('clr', '4') - IIDX22ranking.set_attribute('pgnum', '1771') - IIDX22ranking.set_attribute('coid', '2') - IIDX22ranking.set_attribute('method', 'entry') - IIDX22ranking.set_attribute('opt', '8208') - IIDX22ranking.set_attribute('opt2', '0') - IIDX22ranking.set_attribute('oppid', '51') - IIDX22ranking.set_attribute('cstage', '4') - IIDX22ranking.set_attribute('gnum', '967') - IIDX22ranking.set_attribute('pside', '1') - IIDX22ranking.set_attribute('clid', '1') - IIDX22ranking.set_attribute('regist_type', '0' if coursetype == 'ir' else '1') - IIDX22ranking.set_attribute('iidxid', str(iidxid)) + IIDX22ranking = Node.void("IIDX22ranking") + IIDX22ranking.set_attribute("opname", shop_name) + IIDX22ranking.set_attribute("clr", "4") + IIDX22ranking.set_attribute("pgnum", "1771") + IIDX22ranking.set_attribute("coid", "2") + IIDX22ranking.set_attribute("method", "entry") + IIDX22ranking.set_attribute("opt", "8208") + IIDX22ranking.set_attribute("opt2", "0") + IIDX22ranking.set_attribute("oppid", "51") + IIDX22ranking.set_attribute("cstage", "4") + IIDX22ranking.set_attribute("gnum", "967") + IIDX22ranking.set_attribute("pside", "1") + IIDX22ranking.set_attribute("clid", "1") + IIDX22ranking.set_attribute("regist_type", "0" if coursetype == "ir" else "1") + IIDX22ranking.set_attribute("iidxid", str(iidxid)) call.add_child(IIDX22ranking) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX22ranking/@anum") @@ -625,20 +647,20 @@ class IIDXPendualClient(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -661,226 +683,292 @@ class IIDXPendualClient(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") self.verify_iidx22pc_reg(ref_id, card, lid) self.verify_iidx22pc_get(ref_id, card, lid) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") if cardid is None: # Verify score handling profile = self.verify_iidx22pc_get(ref_id, card, lid) - if profile['sp_dan'] != -1: - raise Exception('Somehow has SP DAN ranking on new profile!') - if profile['dp_dan'] != -1: - raise Exception('Somehow has DP DAN ranking on new profile!') - if profile['deller'] != 0: - raise Exception('Somehow has deller on new profile!') - if len(profile['ir_data'].keys()) > 0: - raise Exception('Somehow has internet ranking data on new profile!') - if len(profile['secret_course_data'].keys()) > 0: - raise Exception('Somehow has secret course data on new profile!') - if len(profile['expert_point'].keys()) > 0: - raise Exception('Somehow has expert point data on new profile!') - scores = self.verify_iidx22music_getrank(profile['extid']) + if profile["sp_dan"] != -1: + raise Exception("Somehow has SP DAN ranking on new profile!") + if profile["dp_dan"] != -1: + raise Exception("Somehow has DP DAN ranking on new profile!") + if profile["deller"] != 0: + raise Exception("Somehow has deller on new profile!") + if len(profile["ir_data"].keys()) > 0: + raise Exception("Somehow has internet ranking data on new profile!") + if len(profile["secret_course_data"].keys()) > 0: + raise Exception("Somehow has secret course data on new profile!") + if len(profile["expert_point"].keys()) > 0: + raise Exception("Somehow has expert point data on new profile!") + scores = self.verify_iidx22music_getrank(profile["extid"]) if len(scores.keys()) > 0: - raise Exception('Somehow have scores on a new profile!') + raise Exception("Somehow have scores on a new profile!") for phase in [1, 2]: if phase == 1: dummyscores = [ # An okay score on a chart { - 'id': 1000, - 'chart': 2, - 'clear_status': 4, - 'pgnum': 123, - 'gnum': 123, - 'mnum': 5, + "id": 1000, + "chart": 2, + "clear_status": 4, + "pgnum": 123, + "gnum": 123, + "mnum": 5, }, # A good score on an easier chart of the same song { - 'id': 1000, - 'chart': 0, - 'clear_status': 7, - 'pgnum': 246, - 'gnum': 0, - 'mnum': 0, + "id": 1000, + "chart": 0, + "clear_status": 7, + "pgnum": 246, + "gnum": 0, + "mnum": 0, }, # A bad score on a hard chart { - 'id': 1003, - 'chart': 2, - 'clear_status': 1, - 'pgnum': 10, - 'gnum': 20, - 'mnum': 50, + "id": 1003, + "chart": 2, + "clear_status": 1, + "pgnum": 10, + "gnum": 20, + "mnum": 50, }, # A terrible score on an easy chart { - 'id': 1003, - 'chart': 0, - 'clear_status': 1, - 'pgnum': 2, - 'gnum': 5, - 'mnum': 75, + "id": 1003, + "chart": 0, + "clear_status": 1, + "pgnum": 2, + "gnum": 5, + "mnum": 75, }, ] if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 1000, - 'chart': 2, - 'clear_status': 5, - 'pgnum': 234, - 'gnum': 234, - 'mnum': 3, + "id": 1000, + "chart": 2, + "clear_status": 5, + "pgnum": 234, + "gnum": 234, + "mnum": 3, }, # A worse score on another same chart { - 'id': 1000, - 'chart': 0, - 'clear_status': 4, - 'pgnum': 123, - 'gnum': 123, - 'mnum': 35, - 'expected_clear_status': 7, - 'expected_ex_score': 492, - 'expected_miss_count': 0, + "id": 1000, + "chart": 0, + "clear_status": 4, + "pgnum": 123, + "gnum": 123, + "mnum": 35, + "expected_clear_status": 7, + "expected_ex_score": 492, + "expected_miss_count": 0, }, ] for dummyscore in dummyscores: - self.verify_iidx22music_reg(profile['extid'], lid, dummyscore) - self.verify_iidx22pc_visit(profile['extid'], lid) - self.verify_iidx22pc_save(profile['extid'], card, lid) - scores = self.verify_iidx22music_getrank(profile['extid']) + self.verify_iidx22music_reg(profile["extid"], lid, dummyscore) + self.verify_iidx22pc_visit(profile["extid"], lid) + self.verify_iidx22pc_save(profile["extid"], card, lid) + scores = self.verify_iidx22music_getrank(profile["extid"]) for score in dummyscores: - data = scores.get(score['id'], {}).get(score['chart'], None) + data = scores.get(score["id"], {}).get(score["chart"], None) if data is None: - raise Exception(f'Expected to get score back for song {score["id"]} chart {score["chart"]}!') + raise Exception( + f'Expected to get score back for song {score["id"]} chart {score["chart"]}!' + ) - if 'expected_ex_score' in score: - expected_score = score['expected_ex_score'] + if "expected_ex_score" in score: + expected_score = score["expected_ex_score"] else: - expected_score = (score['pgnum'] * 2) + score['gnum'] - if 'expected_clear_status' in score: - expected_clear_status = score['expected_clear_status'] + expected_score = (score["pgnum"] * 2) + score["gnum"] + if "expected_clear_status" in score: + expected_clear_status = score["expected_clear_status"] else: - expected_clear_status = score['clear_status'] - if 'expected_miss_count' in score: - expected_miss_count = score['expected_miss_count'] + expected_clear_status = score["clear_status"] + if "expected_miss_count" in score: + expected_miss_count = score["expected_miss_count"] else: - expected_miss_count = score['mnum'] + expected_miss_count = score["mnum"] - if data['ex_score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\'') - if data['clear_status'] != expected_clear_status: - raise Exception(f'Expected a clear status of \'{expected_clear_status}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got clear status \'{data["clear_status"]}\'') - if data['miss_count'] != expected_miss_count: - raise Exception(f'Expected a miss count of \'{expected_miss_count}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got miss count \'{data["miss_count"]}\'') + if data["ex_score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\'' + ) + if data["clear_status"] != expected_clear_status: + raise Exception( + f'Expected a clear status of \'{expected_clear_status}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got clear status \'{data["clear_status"]}\'' + ) + if data["miss_count"] != expected_miss_count: + raise Exception( + f'Expected a miss count of \'{expected_miss_count}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got miss count \'{data["miss_count"]}\'' + ) # Verify we can fetch our own ghost - ex_score, ghost = self.verify_iidx22music_appoint(profile['extid'], score['id'], score['chart']) + ex_score, ghost = self.verify_iidx22music_appoint( + profile["extid"], score["id"], score["chart"] + ) if ex_score != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\'') + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\'' + ) if len(ghost) != 64: - raise Exception(f'Wrong ghost length {len(ghost)} for ghost!') + raise Exception(f"Wrong ghost length {len(ghost)} for ghost!") for g in ghost: if g != 0x01: - raise Exception(f'Got back wrong ghost data for song \'{score["id"]}\' chart \'{score["chart"]}\'') + raise Exception( + f'Got back wrong ghost data for song \'{score["id"]}\' chart \'{score["chart"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) # Verify that we can save/load expert points - self.verify_iidx22pc_save(profile['extid'], card, lid, {'course_id': 1, 'n_point': 0, 'h_point': 500, 'a_point': 0}) + self.verify_iidx22pc_save( + profile["extid"], + card, + lid, + {"course_id": 1, "n_point": 0, "h_point": 500, "a_point": 0}, + ) profile = self.verify_iidx22pc_get(ref_id, card, lid) - if sorted(profile['expert_point'].keys()) != [1]: - raise Exception('Got back wrong number of expert course points!') - if profile['expert_point'][1] != {'n_point': 0, 'h_point': 500, 'a_point': 0}: - raise Exception('Got back wrong expert points after saving!') - self.verify_iidx22pc_save(profile['extid'], card, lid, {'course_id': 1, 'n_point': 0, 'h_point': 1000, 'a_point': 0}) + if sorted(profile["expert_point"].keys()) != [1]: + raise Exception("Got back wrong number of expert course points!") + if profile["expert_point"][1] != { + "n_point": 0, + "h_point": 500, + "a_point": 0, + }: + raise Exception("Got back wrong expert points after saving!") + self.verify_iidx22pc_save( + profile["extid"], + card, + lid, + {"course_id": 1, "n_point": 0, "h_point": 1000, "a_point": 0}, + ) profile = self.verify_iidx22pc_get(ref_id, card, lid) - if sorted(profile['expert_point'].keys()) != [1]: - raise Exception('Got back wrong number of expert course points!') - if profile['expert_point'][1] != {'n_point': 0, 'h_point': 1000, 'a_point': 0}: - raise Exception('Got back wrong expert points after saving!') - self.verify_iidx22pc_save(profile['extid'], card, lid, {'course_id': 2, 'n_point': 0, 'h_point': 0, 'a_point': 500}) + if sorted(profile["expert_point"].keys()) != [1]: + raise Exception("Got back wrong number of expert course points!") + if profile["expert_point"][1] != { + "n_point": 0, + "h_point": 1000, + "a_point": 0, + }: + raise Exception("Got back wrong expert points after saving!") + self.verify_iidx22pc_save( + profile["extid"], + card, + lid, + {"course_id": 2, "n_point": 0, "h_point": 0, "a_point": 500}, + ) profile = self.verify_iidx22pc_get(ref_id, card, lid) - if sorted(profile['expert_point'].keys()) != [1, 2]: - raise Exception('Got back wrong number of expert course points!') - if profile['expert_point'][1] != {'n_point': 0, 'h_point': 1000, 'a_point': 0}: - raise Exception('Got back wrong expert points after saving!') - if profile['expert_point'][2] != {'n_point': 0, 'h_point': 0, 'a_point': 500}: - raise Exception('Got back wrong expert points after saving!') + if sorted(profile["expert_point"].keys()) != [1, 2]: + raise Exception("Got back wrong number of expert course points!") + if profile["expert_point"][1] != { + "n_point": 0, + "h_point": 1000, + "a_point": 0, + }: + raise Exception("Got back wrong expert points after saving!") + if profile["expert_point"][2] != { + "n_point": 0, + "h_point": 0, + "a_point": 500, + }: + raise Exception("Got back wrong expert points after saving!") # Verify that a player without a card can play self.verify_iidx22pc_playstart() - self.verify_iidx22music_play({ - 'id': 1000, - 'chart': 2, - 'clear_status': 4, - 'pgnum': 123, - 'gnum': 123, - }) + self.verify_iidx22music_play( + { + "id": 1000, + "chart": 2, + "clear_status": 4, + "pgnum": 123, + "gnum": 123, + } + ) self.verify_iidx22pc_playend() # Verify shop name change setting - self.verify_iidx22shop_savename(lid, 'newname1') + self.verify_iidx22shop_savename(lid, "newname1") newname = self.verify_iidx22shop_getname(lid) - if newname != 'newname1': - raise Exception('Invalid shop name returned after change!') - self.verify_iidx22shop_savename(lid, 'newname2') + if newname != "newname1": + raise Exception("Invalid shop name returned after change!") + self.verify_iidx22shop_savename(lid, "newname2") newname = self.verify_iidx22shop_getname(lid) - if newname != 'newname2': - raise Exception('Invalid shop name returned after change!') + if newname != "newname2": + raise Exception("Invalid shop name returned after change!") # Verify beginner score saving - self.verify_iidx22music_breg(profile['extid'], { - 'id': 1000, - 'clear_status': 4, - 'pgnum': 123, - 'gnum': 123, - }) - scores = self.verify_iidx22music_getrank(profile['extid']) + self.verify_iidx22music_breg( + profile["extid"], + { + "id": 1000, + "clear_status": 4, + "pgnum": 123, + "gnum": 123, + }, + ) + scores = self.verify_iidx22music_getrank(profile["extid"]) if 1000 not in scores: - raise Exception(f'Didn\'t get expected scores back for song {1000} beginner chart!') + raise Exception( + f"Didn't get expected scores back for song {1000} beginner chart!" + ) if 6 not in scores[1000]: - raise Exception(f'Didn\'t get beginner score back for song {1000}!') - if scores[1000][6] != {'clear_status': 4, 'ex_score': -1, 'miss_count': -1}: - raise Exception('Didn\'t get correct status back from beginner save!') + raise Exception(f"Didn't get beginner score back for song {1000}!") + if scores[1000][6] != {"clear_status": 4, "ex_score": -1, "miss_count": -1}: + raise Exception("Didn't get correct status back from beginner save!") # Verify DAN score saving and loading - self.verify_iidx22grade_raised(profile['extid'], newname, 'sp') - self.verify_iidx22grade_raised(profile['extid'], newname, 'dp') + self.verify_iidx22grade_raised(profile["extid"], newname, "sp") + self.verify_iidx22grade_raised(profile["extid"], newname, "dp") profile = self.verify_iidx22pc_get(ref_id, card, lid) - if profile['sp_dan'] != 5: - raise Exception('Got wrong DAN score back for SP!') - if profile['dp_dan'] != 5: - raise Exception('Got wrong DAN score back for DP!') + if profile["sp_dan"] != 5: + raise Exception("Got wrong DAN score back for SP!") + if profile["dp_dan"] != 5: + raise Exception("Got wrong DAN score back for DP!") # Verify secret course and internet ranking course saving - self.verify_iidx22ranking_entry(profile['extid'], newname, 'ir') - self.verify_iidx22ranking_entry(profile['extid'], newname, 'secret') + self.verify_iidx22ranking_entry(profile["extid"], newname, "ir") + self.verify_iidx22ranking_entry(profile["extid"], newname, "secret") profile = self.verify_iidx22pc_get(ref_id, card, lid) - for ptype in ['ir_data', 'secret_course_data']: - if profile[ptype] != {2: {1: {'clear_status': 4, 'pgnum': 1771, 'gnum': 967}}}: - raise Exception(f'Invalid data {profile[ptype]} returned on profile load for {ptype}!') + for ptype in ["ir_data", "secret_course_data"]: + if profile[ptype] != { + 2: {1: {"clear_status": 4, "pgnum": 1771, "gnum": 967}} + }: + raise Exception( + f"Invalid data {profile[ptype]} returned on profile load for {ptype}!" + ) else: print("Skipping score checks for existing card") diff --git a/bemani/client/iidx/rootage.py b/bemani/client/iidx/rootage.py index e29c674..e7c0405 100644 --- a/bemani/client/iidx/rootage.py +++ b/bemani/client/iidx/rootage.py @@ -7,19 +7,19 @@ from bemani.protocol import Node class IIDXRootageClient(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_iidx26shop_getname(self, lid: str) -> str: call = self.call_node() # Construct node - IIDX26shop = Node.void('IIDX26shop') + IIDX26shop = Node.void("IIDX26shop") call.add_child(IIDX26shop) - IIDX26shop.set_attribute('method', 'getname') - IIDX26shop.set_attribute('lid', lid) + IIDX26shop.set_attribute("method", "getname") + IIDX26shop.set_attribute("lid", lid) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX26shop/@opname") @@ -28,25 +28,25 @@ class IIDXRootageClient(BaseClient): self.assert_path(resp, "response/IIDX26shop/@hr") self.assert_path(resp, "response/IIDX26shop/@mi") - return resp.child('IIDX26shop').attribute('opname') + return resp.child("IIDX26shop").attribute("opname") def verify_iidx26shop_savename(self, lid: str, name: str) -> None: call = self.call_node() # Construct node - IIDX26shop = Node.void('IIDX26shop') - IIDX26shop.set_attribute('lid', lid) - IIDX26shop.set_attribute('pid', '51') - IIDX26shop.set_attribute('method', 'savename') - IIDX26shop.set_attribute('cls_opt', '0') - IIDX26shop.set_attribute('ccode', 'US') - IIDX26shop.set_attribute('opname', name) - IIDX26shop.set_attribute('rcode', '.') + IIDX26shop = Node.void("IIDX26shop") + IIDX26shop.set_attribute("lid", lid) + IIDX26shop.set_attribute("pid", "51") + IIDX26shop.set_attribute("method", "savename") + IIDX26shop.set_attribute("cls_opt", "0") + IIDX26shop.set_attribute("ccode", "US") + IIDX26shop.set_attribute("opname", name) + IIDX26shop.set_attribute("rcode", ".") call.add_child(IIDX26shop) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX26shop") @@ -55,12 +55,12 @@ class IIDXRootageClient(BaseClient): call = self.call_node() # Construct node - IIDX26pc = Node.void('IIDX26pc') + IIDX26pc = Node.void("IIDX26pc") call.add_child(IIDX26pc) - IIDX26pc.set_attribute('method', 'common') + IIDX26pc.set_attribute("method", "common") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX26pc/ir/@beat") @@ -75,34 +75,36 @@ class IIDXRootageClient(BaseClient): call = self.call_node() # Construct node - IIDX26pc = Node.void('IIDX26music') + IIDX26pc = Node.void("IIDX26music") call.add_child(IIDX26pc) - IIDX26pc.set_attribute('method', 'crate') + IIDX26pc.set_attribute("method", "crate") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX26music") for child in resp.child("IIDX26music").children: - if child.name != 'c': - raise Exception(f'Invalid node {child} in clear rate response!') + if child.name != "c": + raise Exception(f"Invalid node {child} in clear rate response!") if len(child.value) != 12: - raise Exception(f'Invalid node data {child} in clear rate response!') + raise Exception(f"Invalid node data {child} in clear rate response!") for v in child.value: if v < 0 or v > 1001: - raise Exception(f'Invalid clear percent {child} in clear rate response!') + raise Exception( + f"Invalid clear percent {child} in clear rate response!" + ) def verify_iidx26shop_getconvention(self, lid: str) -> None: call = self.call_node() # Construct node - IIDX26pc = Node.void('IIDX26shop') + IIDX26pc = Node.void("IIDX26shop") call.add_child(IIDX26pc) - IIDX26pc.set_attribute('method', 'getconvention') - IIDX26pc.set_attribute('lid', lid) + IIDX26pc.set_attribute("method", "getconvention") + IIDX26pc.set_attribute("lid", lid) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX26shop/valid") @@ -115,15 +117,15 @@ class IIDXRootageClient(BaseClient): call = self.call_node() # Construct node - IIDX26pc = Node.void('IIDX26pc') + IIDX26pc = Node.void("IIDX26pc") call.add_child(IIDX26pc) - IIDX26pc.set_attribute('iidxid', str(extid)) - IIDX26pc.set_attribute('lid', lid) - IIDX26pc.set_attribute('method', 'visit') - IIDX26pc.set_attribute('pid', '51') + IIDX26pc.set_attribute("iidxid", str(extid)) + IIDX26pc.set_attribute("lid", lid) + IIDX26pc.set_attribute("method", "visit") + IIDX26pc.set_attribute("pid", "51") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX26pc/@aflg") @@ -138,14 +140,14 @@ class IIDXRootageClient(BaseClient): call = self.call_node() # Construct node - IIDX26pc = Node.void('IIDX26ranking') + IIDX26pc = Node.void("IIDX26ranking") call.add_child(IIDX26pc) - IIDX26pc.set_attribute('method', 'getranker') - IIDX26pc.set_attribute('lid', lid) - IIDX26pc.set_attribute('clid', str(clid)) + IIDX26pc.set_attribute("method", "getranker") + IIDX26pc.set_attribute("lid", lid) + IIDX26pc.set_attribute("clid", str(clid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX26ranking") @@ -154,40 +156,42 @@ class IIDXRootageClient(BaseClient): call = self.call_node() # Construct node - IIDX26pc = Node.void('IIDX26shop') + IIDX26pc = Node.void("IIDX26shop") call.add_child(IIDX26pc) - IIDX26pc.set_attribute('method', 'sentinfo') - IIDX26pc.set_attribute('lid', lid) - IIDX26pc.set_attribute('bflg', '1') - IIDX26pc.set_attribute('bnum', '2') - IIDX26pc.set_attribute('ioid', '0') - IIDX26pc.set_attribute('company_code', '') - IIDX26pc.set_attribute('consumer_code', '') - IIDX26pc.set_attribute('location_name', shop_name) - IIDX26pc.set_attribute('tax_phase', '0') + IIDX26pc.set_attribute("method", "sentinfo") + IIDX26pc.set_attribute("lid", lid) + IIDX26pc.set_attribute("bflg", "1") + IIDX26pc.set_attribute("bnum", "2") + IIDX26pc.set_attribute("ioid", "0") + IIDX26pc.set_attribute("company_code", "") + IIDX26pc.set_attribute("consumer_code", "") + IIDX26pc.set_attribute("location_name", shop_name) + IIDX26pc.set_attribute("tax_phase", "0") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX26shop") - def verify_iidx26pc_get(self, ref_id: str, card_id: str, lid: str) -> Dict[str, Any]: + def verify_iidx26pc_get( + self, ref_id: str, card_id: str, lid: str + ) -> Dict[str, Any]: call = self.call_node() # Construct node - IIDX26pc = Node.void('IIDX26pc') + IIDX26pc = Node.void("IIDX26pc") call.add_child(IIDX26pc) - IIDX26pc.set_attribute('rid', ref_id) - IIDX26pc.set_attribute('did', ref_id) - IIDX26pc.set_attribute('pid', '51') - IIDX26pc.set_attribute('lid', lid) - IIDX26pc.set_attribute('cid', card_id) - IIDX26pc.set_attribute('method', 'get') - IIDX26pc.set_attribute('ctype', '1') + IIDX26pc.set_attribute("rid", ref_id) + IIDX26pc.set_attribute("did", ref_id) + IIDX26pc.set_attribute("pid", "51") + IIDX26pc.set_attribute("lid", lid) + IIDX26pc.set_attribute("cid", card_id) + IIDX26pc.set_attribute("method", "get") + IIDX26pc.set_attribute("ctype", "1") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that the response is correct self.assert_path(resp, "response/IIDX26pc/pcdata/@name") @@ -213,50 +217,54 @@ class IIDXRootageClient(BaseClient): self.assert_path(resp, "response/IIDX26pc/extra_favorite/dp_mlist") self.assert_path(resp, "response/IIDX26pc/extra_favorite/dp_clist") - name = resp.child('IIDX26pc/pcdata').attribute('name') + name = resp.child("IIDX26pc/pcdata").attribute("name") if name != self.NAME: - raise Exception(f'Invalid name \'{name}\' returned for Ref ID \'{ref_id}\'') + raise Exception(f"Invalid name '{name}' returned for Ref ID '{ref_id}'") expert_point: Dict[int, Dict[str, int]] = {} - for child in resp.child('IIDX26pc/expert_point').children: - if child.name == 'detail': - expert_point[int(child.attribute('course_id'))] = { - 'n_point': int(child.attribute('n_point')), - 'h_point': int(child.attribute('h_point')), - 'a_point': int(child.attribute('a_point')), + for child in resp.child("IIDX26pc/expert_point").children: + if child.name == "detail": + expert_point[int(child.attribute("course_id"))] = { + "n_point": int(child.attribute("n_point")), + "h_point": int(child.attribute("h_point")), + "a_point": int(child.attribute("a_point")), } return { - 'extid': int(resp.child('IIDX26pc/pcdata').attribute('id')), - 'sp_dan': int(resp.child('IIDX26pc/grade').attribute('sgid')), - 'dp_dan': int(resp.child('IIDX26pc/grade').attribute('dgid')), - 'deller': int(resp.child('IIDX26pc/deller').attribute('deller')), - 'expert_point': expert_point, + "extid": int(resp.child("IIDX26pc/pcdata").attribute("id")), + "sp_dan": int(resp.child("IIDX26pc/grade").attribute("sgid")), + "dp_dan": int(resp.child("IIDX26pc/grade").attribute("dgid")), + "deller": int(resp.child("IIDX26pc/deller").attribute("deller")), + "expert_point": expert_point, } - def verify_iidx26music_getrank(self, extid: int) -> Dict[int, Dict[int, Dict[str, int]]]: + def verify_iidx26music_getrank( + self, extid: int + ) -> Dict[int, Dict[int, Dict[str, int]]]: scores: Dict[int, Dict[int, Dict[str, int]]] = {} for cltype in [0, 1]: # singles, doubles call = self.call_node() # Construct node - IIDX26music = Node.void('IIDX26music') + IIDX26music = Node.void("IIDX26music") call.add_child(IIDX26music) - IIDX26music.set_attribute('method', 'getrank') - IIDX26music.set_attribute('iidxid', str(extid)) - IIDX26music.set_attribute('cltype', str(cltype)) + IIDX26music.set_attribute("method", "getrank") + IIDX26music.set_attribute("iidxid", str(extid)) + IIDX26music.set_attribute("cltype", str(cltype)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX26music/style") - if int(resp.child('IIDX26music/style').attribute('type')) != cltype: - raise Exception('Returned wrong clear type for IIDX26music.getrank!') + if int(resp.child("IIDX26music/style").attribute("type")) != cltype: + raise Exception("Returned wrong clear type for IIDX26music.getrank!") - for child in resp.child('IIDX26music').children: - if child.name == 'm': + for child in resp.child("IIDX26music").children: + if child.name == "m": if child.value[0] != -1: - raise Exception('Got non-self score back when requesting only our scores!') + raise Exception( + "Got non-self score back when requesting only our scores!" + ) music_id = child.value[1] normal_clear_status = child.value[2] @@ -282,200 +290,210 @@ class IIDXRootageClient(BaseClient): scores[music_id] = {} scores[music_id][normal] = { - 'clear_status': normal_clear_status, - 'ex_score': normal_ex_score, - 'miss_count': normal_miss_count, + "clear_status": normal_clear_status, + "ex_score": normal_ex_score, + "miss_count": normal_miss_count, } scores[music_id][hyper] = { - 'clear_status': hyper_clear_status, - 'ex_score': hyper_ex_score, - 'miss_count': hyper_miss_count, + "clear_status": hyper_clear_status, + "ex_score": hyper_ex_score, + "miss_count": hyper_miss_count, } scores[music_id][another] = { - 'clear_status': another_clear_status, - 'ex_score': another_ex_score, - 'miss_count': another_miss_count, + "clear_status": another_clear_status, + "ex_score": another_ex_score, + "miss_count": another_miss_count, } - elif child.name == 'b': + elif child.name == "b": music_id = child.value[0] clear_status = child.value[1] scores[music_id][6] = { - 'clear_status': clear_status, - 'ex_score': -1, - 'miss_count': -1, + "clear_status": clear_status, + "ex_score": -1, + "miss_count": -1, } return scores - def verify_iidx26pc_save(self, extid: int, card: str, lid: str, expert_point: Optional[Dict[str, int]]=None) -> None: + def verify_iidx26pc_save( + self, + extid: int, + card: str, + lid: str, + expert_point: Optional[Dict[str, int]] = None, + ) -> None: call = self.call_node() # Construct node - IIDX26pc = Node.void('IIDX26pc') + IIDX26pc = Node.void("IIDX26pc") call.add_child(IIDX26pc) - IIDX26pc.set_attribute('s_disp_judge', '1') - IIDX26pc.set_attribute('mode', '6') - IIDX26pc.set_attribute('pmode', '0') - IIDX26pc.set_attribute('method', 'save') - IIDX26pc.set_attribute('s_sorttype', '0') - IIDX26pc.set_attribute('s_exscore', '0') - IIDX26pc.set_attribute('d_notes', '0.000000') - IIDX26pc.set_attribute('gpos', '0') - IIDX26pc.set_attribute('s_gno', '8') - IIDX26pc.set_attribute('s_hispeed', '5.771802') - IIDX26pc.set_attribute('s_judge', '0') - IIDX26pc.set_attribute('d_timing', '0') - IIDX26pc.set_attribute('rtype', '0') - IIDX26pc.set_attribute('d_graph_score', '0') - IIDX26pc.set_attribute('d_lift', '60') - IIDX26pc.set_attribute('s_pace', '0') - IIDX26pc.set_attribute('d_exscore', '0') - IIDX26pc.set_attribute('d_sdtype', '0') - IIDX26pc.set_attribute('s_opstyle', '1') - IIDX26pc.set_attribute('s_achi', '449') - IIDX26pc.set_attribute('s_graph_score', '0') - IIDX26pc.set_attribute('d_gno', '0') - IIDX26pc.set_attribute('s_lift', '60') - IIDX26pc.set_attribute('s_notes', '31.484070') - IIDX26pc.set_attribute('d_tune', '0') - IIDX26pc.set_attribute('d_sdlen', '0') - IIDX26pc.set_attribute('d_achi', '4') - IIDX26pc.set_attribute('d_opstyle', '0') - IIDX26pc.set_attribute('sp_opt', '8208') - IIDX26pc.set_attribute('iidxid', str(extid)) - IIDX26pc.set_attribute('lid', lid) - IIDX26pc.set_attribute('s_judgeAdj', '0') - IIDX26pc.set_attribute('s_tune', '3') - IIDX26pc.set_attribute('s_sdtype', '1') - IIDX26pc.set_attribute('s_gtype', '2') - IIDX26pc.set_attribute('d_judge', '0') - IIDX26pc.set_attribute('cid', card) - IIDX26pc.set_attribute('cltype', '0') - IIDX26pc.set_attribute('ctype', '1') - IIDX26pc.set_attribute('bookkeep', '0') - IIDX26pc.set_attribute('d_hispeed', '0.000000') - IIDX26pc.set_attribute('d_pace', '0') - IIDX26pc.set_attribute('d_judgeAdj', '0') - IIDX26pc.set_attribute('s_timing', '1') - IIDX26pc.set_attribute('d_disp_judge', '0') - IIDX26pc.set_attribute('s_sdlen', '121') - IIDX26pc.set_attribute('dp_opt2', '0') - IIDX26pc.set_attribute('d_gtype', '0') - IIDX26pc.set_attribute('d_sorttype', '0') - IIDX26pc.set_attribute('dp_opt', '0') - IIDX26pc.set_attribute('s_auto_scrach', '0') - IIDX26pc.set_attribute('d_auto_scrach', '0') - IIDX26pc.set_attribute('s_gauge_disp', '0') - IIDX26pc.set_attribute('d_gauge_disp', '0') - IIDX26pc.set_attribute('s_lane_brignt', '0') - IIDX26pc.set_attribute('d_lane_brignt', '0') - IIDX26pc.set_attribute('s_camera_layout', '0') - IIDX26pc.set_attribute('d_camera_layout', '0') - IIDX26pc.set_attribute('s_ghost_score', '0') - IIDX26pc.set_attribute('d_ghost_score', '0') - IIDX26pc.set_attribute('s_tsujigiri_disp', '0') - IIDX26pc.set_attribute('d_tsujigiri_disp', '0') - deller = Node.void('deller') + IIDX26pc.set_attribute("s_disp_judge", "1") + IIDX26pc.set_attribute("mode", "6") + IIDX26pc.set_attribute("pmode", "0") + IIDX26pc.set_attribute("method", "save") + IIDX26pc.set_attribute("s_sorttype", "0") + IIDX26pc.set_attribute("s_exscore", "0") + IIDX26pc.set_attribute("d_notes", "0.000000") + IIDX26pc.set_attribute("gpos", "0") + IIDX26pc.set_attribute("s_gno", "8") + IIDX26pc.set_attribute("s_hispeed", "5.771802") + IIDX26pc.set_attribute("s_judge", "0") + IIDX26pc.set_attribute("d_timing", "0") + IIDX26pc.set_attribute("rtype", "0") + IIDX26pc.set_attribute("d_graph_score", "0") + IIDX26pc.set_attribute("d_lift", "60") + IIDX26pc.set_attribute("s_pace", "0") + IIDX26pc.set_attribute("d_exscore", "0") + IIDX26pc.set_attribute("d_sdtype", "0") + IIDX26pc.set_attribute("s_opstyle", "1") + IIDX26pc.set_attribute("s_achi", "449") + IIDX26pc.set_attribute("s_graph_score", "0") + IIDX26pc.set_attribute("d_gno", "0") + IIDX26pc.set_attribute("s_lift", "60") + IIDX26pc.set_attribute("s_notes", "31.484070") + IIDX26pc.set_attribute("d_tune", "0") + IIDX26pc.set_attribute("d_sdlen", "0") + IIDX26pc.set_attribute("d_achi", "4") + IIDX26pc.set_attribute("d_opstyle", "0") + IIDX26pc.set_attribute("sp_opt", "8208") + IIDX26pc.set_attribute("iidxid", str(extid)) + IIDX26pc.set_attribute("lid", lid) + IIDX26pc.set_attribute("s_judgeAdj", "0") + IIDX26pc.set_attribute("s_tune", "3") + IIDX26pc.set_attribute("s_sdtype", "1") + IIDX26pc.set_attribute("s_gtype", "2") + IIDX26pc.set_attribute("d_judge", "0") + IIDX26pc.set_attribute("cid", card) + IIDX26pc.set_attribute("cltype", "0") + IIDX26pc.set_attribute("ctype", "1") + IIDX26pc.set_attribute("bookkeep", "0") + IIDX26pc.set_attribute("d_hispeed", "0.000000") + IIDX26pc.set_attribute("d_pace", "0") + IIDX26pc.set_attribute("d_judgeAdj", "0") + IIDX26pc.set_attribute("s_timing", "1") + IIDX26pc.set_attribute("d_disp_judge", "0") + IIDX26pc.set_attribute("s_sdlen", "121") + IIDX26pc.set_attribute("dp_opt2", "0") + IIDX26pc.set_attribute("d_gtype", "0") + IIDX26pc.set_attribute("d_sorttype", "0") + IIDX26pc.set_attribute("dp_opt", "0") + IIDX26pc.set_attribute("s_auto_scrach", "0") + IIDX26pc.set_attribute("d_auto_scrach", "0") + IIDX26pc.set_attribute("s_gauge_disp", "0") + IIDX26pc.set_attribute("d_gauge_disp", "0") + IIDX26pc.set_attribute("s_lane_brignt", "0") + IIDX26pc.set_attribute("d_lane_brignt", "0") + IIDX26pc.set_attribute("s_camera_layout", "0") + IIDX26pc.set_attribute("d_camera_layout", "0") + IIDX26pc.set_attribute("s_ghost_score", "0") + IIDX26pc.set_attribute("d_ghost_score", "0") + IIDX26pc.set_attribute("s_tsujigiri_disp", "0") + IIDX26pc.set_attribute("d_tsujigiri_disp", "0") + deller = Node.void("deller") IIDX26pc.add_child(deller) - deller.set_attribute('deller', '150') + deller.set_attribute("deller", "150") if expert_point is not None: - epnode = Node.void('expert_point') - epnode.set_attribute('h_point', str(expert_point['h_point'])) - epnode.set_attribute('course_id', str(expert_point['course_id'])) - epnode.set_attribute('n_point', str(expert_point['n_point'])) - epnode.set_attribute('a_point', str(expert_point['a_point'])) + epnode = Node.void("expert_point") + epnode.set_attribute("h_point", str(expert_point["h_point"])) + epnode.set_attribute("course_id", str(expert_point["course_id"])) + epnode.set_attribute("n_point", str(expert_point["n_point"])) + epnode.set_attribute("a_point", str(expert_point["a_point"])) IIDX26pc.add_child(epnode) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX26pc") - def verify_iidx26music_reg(self, extid: int, lid: str, score: Dict[str, Any]) -> None: + def verify_iidx26music_reg( + self, extid: int, lid: str, score: Dict[str, Any] + ) -> None: call = self.call_node() # Construct node - IIDX26music = Node.void('IIDX26music') + IIDX26music = Node.void("IIDX26music") call.add_child(IIDX26music) - IIDX26music.set_attribute('convid', '-1') - IIDX26music.set_attribute('iidxid', str(extid)) - IIDX26music.set_attribute('pgnum', str(score['pgnum'])) - IIDX26music.set_attribute('pid', '51') - IIDX26music.set_attribute('rankside', '1') - IIDX26music.set_attribute('cflg', str(score['clear_status'])) - IIDX26music.set_attribute('method', 'reg') - IIDX26music.set_attribute('gnum', str(score['gnum'])) - IIDX26music.set_attribute('clid', str(score['chart'])) - IIDX26music.set_attribute('mnum', str(score['mnum'])) - IIDX26music.set_attribute('is_death', '0') - IIDX26music.set_attribute('theory', '0') - IIDX26music.set_attribute('dj_level', '1') - IIDX26music.set_attribute('location_id', lid) - IIDX26music.set_attribute('mid', str(score['id'])) - IIDX26music.add_child(Node.binary('ghost', bytes([1] * 64))) + IIDX26music.set_attribute("convid", "-1") + IIDX26music.set_attribute("iidxid", str(extid)) + IIDX26music.set_attribute("pgnum", str(score["pgnum"])) + IIDX26music.set_attribute("pid", "51") + IIDX26music.set_attribute("rankside", "1") + IIDX26music.set_attribute("cflg", str(score["clear_status"])) + IIDX26music.set_attribute("method", "reg") + IIDX26music.set_attribute("gnum", str(score["gnum"])) + IIDX26music.set_attribute("clid", str(score["chart"])) + IIDX26music.set_attribute("mnum", str(score["mnum"])) + IIDX26music.set_attribute("is_death", "0") + IIDX26music.set_attribute("theory", "0") + IIDX26music.set_attribute("dj_level", "1") + IIDX26music.set_attribute("location_id", lid) + IIDX26music.set_attribute("mid", str(score["id"])) + IIDX26music.add_child(Node.binary("ghost", bytes([1] * 64))) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX26music/shopdata/@rank") self.assert_path(resp, "response/IIDX26music/ranklist/data") - def verify_iidx26music_appoint(self, extid: int, musicid: int, chart: int) -> Tuple[int, bytes]: + def verify_iidx26music_appoint( + self, extid: int, musicid: int, chart: int + ) -> Tuple[int, bytes]: call = self.call_node() # Construct node - IIDX26music = Node.void('IIDX26music') + IIDX26music = Node.void("IIDX26music") call.add_child(IIDX26music) - IIDX26music.set_attribute('clid', str(chart)) - IIDX26music.set_attribute('method', 'appoint') - IIDX26music.set_attribute('ctype', '0') - IIDX26music.set_attribute('iidxid', str(extid)) - IIDX26music.set_attribute('subtype', '') - IIDX26music.set_attribute('mid', str(musicid)) + IIDX26music.set_attribute("clid", str(chart)) + IIDX26music.set_attribute("method", "appoint") + IIDX26music.set_attribute("ctype", "0") + IIDX26music.set_attribute("iidxid", str(extid)) + IIDX26music.set_attribute("subtype", "") + IIDX26music.set_attribute("mid", str(musicid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX26music/mydata/@score") return ( - int(resp.child('IIDX26music/mydata').attribute('score')), - resp.child_value('IIDX26music/mydata'), + int(resp.child("IIDX26music/mydata").attribute("score")), + resp.child_value("IIDX26music/mydata"), ) def verify_iidx26pc_reg(self, ref_id: str, card_id: str, lid: str) -> int: call = self.call_node() # Construct node - IIDX26pc = Node.void('IIDX26pc') + IIDX26pc = Node.void("IIDX26pc") call.add_child(IIDX26pc) - IIDX26pc.set_attribute('lid', lid) - IIDX26pc.set_attribute('pid', '51') - IIDX26pc.set_attribute('method', 'reg') - IIDX26pc.set_attribute('cid', card_id) - IIDX26pc.set_attribute('did', ref_id) - IIDX26pc.set_attribute('rid', ref_id) - IIDX26pc.set_attribute('name', self.NAME) + IIDX26pc.set_attribute("lid", lid) + IIDX26pc.set_attribute("pid", "51") + IIDX26pc.set_attribute("method", "reg") + IIDX26pc.set_attribute("cid", card_id) + IIDX26pc.set_attribute("did", ref_id) + IIDX26pc.set_attribute("rid", ref_id) + IIDX26pc.set_attribute("name", self.NAME) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX26pc/@id") self.assert_path(resp, "response/IIDX26pc/@id_str") - return int(resp.child('IIDX26pc').attribute('id')) + return int(resp.child("IIDX26pc").attribute("id")) def verify_iidx26pc_playstart(self) -> None: call = self.call_node() # Construct node - IIDX26pc = Node.void('IIDX26pc') - IIDX26pc.set_attribute('method', 'playstart') - IIDX26pc.set_attribute('side', '1') + IIDX26pc = Node.void("IIDX26pc") + IIDX26pc.set_attribute("method", "playstart") + IIDX26pc.set_attribute("side", "1") call.add_child(IIDX26pc) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX26pc") @@ -484,19 +502,19 @@ class IIDXRootageClient(BaseClient): call = self.call_node() # Construct node - IIDX26music = Node.void('IIDX26music') - IIDX26music.set_attribute('opt', '64') - IIDX26music.set_attribute('clid', str(score['chart'])) - IIDX26music.set_attribute('mid', str(score['id'])) - IIDX26music.set_attribute('gnum', str(score['gnum'])) - IIDX26music.set_attribute('cflg', str(score['clear_status'])) - IIDX26music.set_attribute('pgnum', str(score['pgnum'])) - IIDX26music.set_attribute('pid', '51') - IIDX26music.set_attribute('method', 'play') + IIDX26music = Node.void("IIDX26music") + IIDX26music.set_attribute("opt", "64") + IIDX26music.set_attribute("clid", str(score["chart"])) + IIDX26music.set_attribute("mid", str(score["id"])) + IIDX26music.set_attribute("gnum", str(score["gnum"])) + IIDX26music.set_attribute("cflg", str(score["clear_status"])) + IIDX26music.set_attribute("pgnum", str(score["pgnum"])) + IIDX26music.set_attribute("pid", "51") + IIDX26music.set_attribute("method", "play") call.add_child(IIDX26music) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX26music/@clid") @@ -508,20 +526,20 @@ class IIDXRootageClient(BaseClient): call = self.call_node() # Construct node - IIDX26pc = Node.void('IIDX26pc') - IIDX26pc.set_attribute('cltype', '0') - IIDX26pc.set_attribute('bookkeep', '0') - IIDX26pc.set_attribute('mode', '1') - IIDX26pc.set_attribute('pay_coin', '1') - IIDX26pc.set_attribute('method', 'playend') - IIDX26pc.set_attribute('company_code', '') - IIDX26pc.set_attribute('consumer_code', '') - IIDX26pc.set_attribute('location_name', shop_name) - IIDX26pc.set_attribute('lid', lid) + IIDX26pc = Node.void("IIDX26pc") + IIDX26pc.set_attribute("cltype", "0") + IIDX26pc.set_attribute("bookkeep", "0") + IIDX26pc.set_attribute("mode", "1") + IIDX26pc.set_attribute("pay_coin", "1") + IIDX26pc.set_attribute("method", "playend") + IIDX26pc.set_attribute("company_code", "") + IIDX26pc.set_attribute("consumer_code", "") + IIDX26pc.set_attribute("location_name", shop_name) + IIDX26pc.set_attribute("lid", lid) call.add_child(IIDX26pc) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX26pc") @@ -530,141 +548,147 @@ class IIDXRootageClient(BaseClient): call = self.call_node() # Construct node - IIDX26music = Node.void('IIDX26music') - IIDX26music.set_attribute('gnum', str(score['gnum'])) - IIDX26music.set_attribute('iidxid', str(iidxid)) - IIDX26music.set_attribute('mid', str(score['id'])) - IIDX26music.set_attribute('method', 'breg') - IIDX26music.set_attribute('pgnum', str(score['pgnum'])) - IIDX26music.set_attribute('cflg', str(score['clear_status'])) + IIDX26music = Node.void("IIDX26music") + IIDX26music.set_attribute("gnum", str(score["gnum"])) + IIDX26music.set_attribute("iidxid", str(iidxid)) + IIDX26music.set_attribute("mid", str(score["id"])) + IIDX26music.set_attribute("method", "breg") + IIDX26music.set_attribute("pgnum", str(score["pgnum"])) + IIDX26music.set_attribute("cflg", str(score["clear_status"])) call.add_child(IIDX26music) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX26music") - def verify_iidx26grade_raised(self, iidxid: int, shop_name: str, dantype: str) -> None: + def verify_iidx26grade_raised( + self, iidxid: int, shop_name: str, dantype: str + ) -> None: call = self.call_node() # Construct node - IIDX26grade = Node.void('IIDX26grade') - IIDX26grade.set_attribute('opname', shop_name) - IIDX26grade.set_attribute('is_mirror', '0') - IIDX26grade.set_attribute('oppid', '51') - IIDX26grade.set_attribute('achi', '50') - IIDX26grade.set_attribute('cstage', '4') - IIDX26grade.set_attribute('gid', '5') - IIDX26grade.set_attribute('iidxid', str(iidxid)) - IIDX26grade.set_attribute('gtype', '0' if dantype == 'sp' else '1') - IIDX26grade.set_attribute('is_ex', '0') - IIDX26grade.set_attribute('pside', '0') - IIDX26grade.set_attribute('method', 'raised') + IIDX26grade = Node.void("IIDX26grade") + IIDX26grade.set_attribute("opname", shop_name) + IIDX26grade.set_attribute("is_mirror", "0") + IIDX26grade.set_attribute("oppid", "51") + IIDX26grade.set_attribute("achi", "50") + IIDX26grade.set_attribute("cstage", "4") + IIDX26grade.set_attribute("gid", "5") + IIDX26grade.set_attribute("iidxid", str(iidxid)) + IIDX26grade.set_attribute("gtype", "0" if dantype == "sp" else "1") + IIDX26grade.set_attribute("is_ex", "0") + IIDX26grade.set_attribute("pside", "0") + IIDX26grade.set_attribute("method", "raised") call.add_child(IIDX26grade) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX26grade/@pnum") def verify_iidx26music_arenacpu(self) -> None: call = self.call_node() - IIDX26music = Node.void('IIDX26music') + IIDX26music = Node.void("IIDX26music") call.add_child(IIDX26music) - IIDX26music.set_attribute('method', 'arenaCPU') - music_list = Node.void('music_list') + IIDX26music.set_attribute("method", "arenaCPU") + music_list = Node.void("music_list") IIDX26music.add_child(music_list) - music_list.add_child(Node.s32('index', 0)) - music_list.add_child(Node.s32('music_id', 26072)) - music_list.add_child(Node.s32('note_style', 0)) - music_list.add_child(Node.s32('note_grade', 2)) - music_list.add_child(Node.s32('total_notes', 1857)) - music_list_1 = Node.void('music_list') + music_list.add_child(Node.s32("index", 0)) + music_list.add_child(Node.s32("music_id", 26072)) + music_list.add_child(Node.s32("note_style", 0)) + music_list.add_child(Node.s32("note_grade", 2)) + music_list.add_child(Node.s32("total_notes", 1857)) + music_list_1 = Node.void("music_list") IIDX26music.add_child(music_list_1) - music_list_1.add_child(Node.s32('index', 1)) - music_list_1.add_child(Node.s32('music_id', 25020)) - music_list_1.add_child(Node.s32('note_style', 0)) - music_list_1.add_child(Node.s32('note_grade', 0)) - music_list_1.add_child(Node.s32('total_notes', 483)) - music_list_2 = Node.void('music_list') + music_list_1.add_child(Node.s32("index", 1)) + music_list_1.add_child(Node.s32("music_id", 25020)) + music_list_1.add_child(Node.s32("note_style", 0)) + music_list_1.add_child(Node.s32("note_grade", 0)) + music_list_1.add_child(Node.s32("total_notes", 483)) + music_list_2 = Node.void("music_list") IIDX26music.add_child(music_list_2) - music_list_2.add_child(Node.s32('index', 2)) - music_list_2.add_child(Node.s32('music_id', 2000)) - music_list_2.add_child(Node.s32('note_style', 0)) - music_list_2.add_child(Node.s32('note_grade', 0)) - music_list_2.add_child(Node.s32('total_notes', 360)) - music_list_3 = Node.void('music_list') + music_list_2.add_child(Node.s32("index", 2)) + music_list_2.add_child(Node.s32("music_id", 2000)) + music_list_2.add_child(Node.s32("note_style", 0)) + music_list_2.add_child(Node.s32("note_grade", 0)) + music_list_2.add_child(Node.s32("total_notes", 360)) + music_list_3 = Node.void("music_list") IIDX26music.add_child(music_list_3) - music_list_3.add_child(Node.s32('index', 3)) - music_list_3.add_child(Node.s32('music_id', 19020)) - music_list_3.add_child(Node.s32('note_style', 0)) - music_list_3.add_child(Node.s32('note_grade', 0)) - music_list_3.add_child(Node.s32('total_notes', 404)) - cpu_list = Node.void('cpu_list') + music_list_3.add_child(Node.s32("index", 3)) + music_list_3.add_child(Node.s32("music_id", 19020)) + music_list_3.add_child(Node.s32("note_style", 0)) + music_list_3.add_child(Node.s32("note_grade", 0)) + music_list_3.add_child(Node.s32("total_notes", 404)) + cpu_list = Node.void("cpu_list") IIDX26music.add_child(cpu_list) - cpu_list.add_child(Node.s32('index', 0)) - cpu_list.add_child(Node.s32('play_style', 0)) - cpu_list.add_child(Node.s32('arena_class', 0)) - cpu_list.add_child(Node.s32('grade_id', -1)) - cpu_list_1 = Node.void('cpu_list') + cpu_list.add_child(Node.s32("index", 0)) + cpu_list.add_child(Node.s32("play_style", 0)) + cpu_list.add_child(Node.s32("arena_class", 0)) + cpu_list.add_child(Node.s32("grade_id", -1)) + cpu_list_1 = Node.void("cpu_list") IIDX26music.add_child(cpu_list_1) - cpu_list_1.add_child(Node.s32('index', 1)) - cpu_list_1.add_child(Node.s32('play_style', 0)) - cpu_list_1.add_child(Node.s32('arena_class', 0)) - cpu_list_1.add_child(Node.s32('grade_id', 6)) - cpu_list_2 = Node.void('cpu_list') + cpu_list_1.add_child(Node.s32("index", 1)) + cpu_list_1.add_child(Node.s32("play_style", 0)) + cpu_list_1.add_child(Node.s32("arena_class", 0)) + cpu_list_1.add_child(Node.s32("grade_id", 6)) + cpu_list_2 = Node.void("cpu_list") IIDX26music.add_child(cpu_list_2) - cpu_list_2.add_child(Node.s32('index', 2)) - cpu_list_2.add_child(Node.s32('play_style', 0)) - cpu_list_2.add_child(Node.s32('arena_class', 0)) - cpu_list_2.add_child(Node.s32('grade_id', 6)) - cpu_list_3 = Node.void('cpu_list') + cpu_list_2.add_child(Node.s32("index", 2)) + cpu_list_2.add_child(Node.s32("play_style", 0)) + cpu_list_2.add_child(Node.s32("arena_class", 0)) + cpu_list_2.add_child(Node.s32("grade_id", 6)) + cpu_list_3 = Node.void("cpu_list") IIDX26music.add_child(cpu_list_3) - cpu_list_3.add_child(Node.s32('index', 3)) - cpu_list_3.add_child(Node.s32('play_style', 0)) - cpu_list_3.add_child(Node.s32('arena_class', 0)) - cpu_list_3.add_child(Node.s32('grade_id', 6)) + cpu_list_3.add_child(Node.s32("index", 3)) + cpu_list_3.add_child(Node.s32("play_style", 0)) + cpu_list_3.add_child(Node.s32("arena_class", 0)) + cpu_list_3.add_child(Node.s32("grade_id", 6)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) - self.assert_path(resp, 'response/IIDX26music/cpu_score_list/score_list/score') - self.assert_path(resp, 'response/IIDX26music/cpu_score_list/score_list/enable_score') - self.assert_path(resp, 'response/IIDX26music/cpu_score_list/score_list/ghost') - self.assert_path(resp, 'response/IIDX26music/cpu_score_list/score_list/enable_ghost') + self.assert_path(resp, "response/IIDX26music/cpu_score_list/score_list/score") + self.assert_path( + resp, "response/IIDX26music/cpu_score_list/score_list/enable_score" + ) + self.assert_path(resp, "response/IIDX26music/cpu_score_list/score_list/ghost") + self.assert_path( + resp, "response/IIDX26music/cpu_score_list/score_list/enable_ghost" + ) def verify_iidx26gamesystem_systeminfo(self, lid: str) -> None: call = self.call_node() - IIDX26gameSystem = Node.void('IIDX26gameSystem') + IIDX26gameSystem = Node.void("IIDX26gameSystem") call.add_child(IIDX26gameSystem) - IIDX26gameSystem.set_attribute('method', 'systemInfo') - IIDX26gameSystem.add_child(Node.s32('ver', 15)) - IIDX26gameSystem.add_child(Node.string('location_id', lid)) + IIDX26gameSystem.set_attribute("method", "systemInfo") + IIDX26gameSystem.add_child(Node.s32("ver", 15)) + IIDX26gameSystem.add_child(Node.string("location_id", lid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) - self.assert_path(resp, 'response/IIDX26gameSystem/arena_schedule/phase') + self.assert_path(resp, "response/IIDX26gameSystem/arena_schedule/phase") def verify(self, cardid: Optional[str]) -> None: # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -677,7 +701,7 @@ class IIDXRootageClient(BaseClient): self.verify_iidx26music_crate() self.verify_iidx26shop_getconvention(lid) self.verify_iidx26ranking_getranker(lid) - self.verify_iidx26shop_sentinfo(lid, 'newname1') + self.verify_iidx26shop_sentinfo(lid, "newname1") self.verify_iidx26gamesystem_systeminfo(lid) self.verify_iidx26music_arenacpu() @@ -689,214 +713,276 @@ class IIDXRootageClient(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") self.verify_iidx26pc_reg(ref_id, card, lid) self.verify_iidx26pc_get(ref_id, card, lid) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") if cardid is None: # Verify score handling profile = self.verify_iidx26pc_get(ref_id, card, lid) - if profile['sp_dan'] != -1: - raise Exception('Somehow has SP DAN ranking on new profile!') - if profile['dp_dan'] != -1: - raise Exception('Somehow has DP DAN ranking on new profile!') - if profile['deller'] != 0: - raise Exception('Somehow has deller on new profile!') - if len(profile['expert_point'].keys()) > 0: - raise Exception('Somehow has expert point data on new profile!') - scores = self.verify_iidx26music_getrank(profile['extid']) + if profile["sp_dan"] != -1: + raise Exception("Somehow has SP DAN ranking on new profile!") + if profile["dp_dan"] != -1: + raise Exception("Somehow has DP DAN ranking on new profile!") + if profile["deller"] != 0: + raise Exception("Somehow has deller on new profile!") + if len(profile["expert_point"].keys()) > 0: + raise Exception("Somehow has expert point data on new profile!") + scores = self.verify_iidx26music_getrank(profile["extid"]) if len(scores.keys()) > 0: - raise Exception('Somehow have scores on a new profile!') + raise Exception("Somehow have scores on a new profile!") for phase in [1, 2]: if phase == 1: dummyscores = [ # An okay score on a chart { - 'id': 1000, - 'chart': 2, - 'clear_status': 4, - 'pgnum': 123, - 'gnum': 123, - 'mnum': 5, + "id": 1000, + "chart": 2, + "clear_status": 4, + "pgnum": 123, + "gnum": 123, + "mnum": 5, }, # A good score on an easier chart of the same song { - 'id': 1000, - 'chart': 0, - 'clear_status': 7, - 'pgnum': 246, - 'gnum': 0, - 'mnum': 0, + "id": 1000, + "chart": 0, + "clear_status": 7, + "pgnum": 246, + "gnum": 0, + "mnum": 0, }, # A bad score on a hard chart { - 'id': 1003, - 'chart': 2, - 'clear_status': 1, - 'pgnum': 10, - 'gnum': 20, - 'mnum': 50, + "id": 1003, + "chart": 2, + "clear_status": 1, + "pgnum": 10, + "gnum": 20, + "mnum": 50, }, # A terrible score on an easy chart { - 'id': 1003, - 'chart': 0, - 'clear_status': 1, - 'pgnum': 2, - 'gnum': 5, - 'mnum': 75, + "id": 1003, + "chart": 0, + "clear_status": 1, + "pgnum": 2, + "gnum": 5, + "mnum": 75, }, ] if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 1000, - 'chart': 2, - 'clear_status': 5, - 'pgnum': 234, - 'gnum': 234, - 'mnum': 3, + "id": 1000, + "chart": 2, + "clear_status": 5, + "pgnum": 234, + "gnum": 234, + "mnum": 3, }, # A worse score on another same chart { - 'id': 1000, - 'chart': 0, - 'clear_status': 4, - 'pgnum': 123, - 'gnum': 123, - 'mnum': 35, - 'expected_clear_status': 7, - 'expected_ex_score': 492, - 'expected_miss_count': 0, + "id": 1000, + "chart": 0, + "clear_status": 4, + "pgnum": 123, + "gnum": 123, + "mnum": 35, + "expected_clear_status": 7, + "expected_ex_score": 492, + "expected_miss_count": 0, }, ] for dummyscore in dummyscores: - self.verify_iidx26music_reg(profile['extid'], lid, dummyscore) - self.verify_iidx26pc_visit(profile['extid'], lid) - self.verify_iidx26pc_save(profile['extid'], card, lid) - scores = self.verify_iidx26music_getrank(profile['extid']) + self.verify_iidx26music_reg(profile["extid"], lid, dummyscore) + self.verify_iidx26pc_visit(profile["extid"], lid) + self.verify_iidx26pc_save(profile["extid"], card, lid) + scores = self.verify_iidx26music_getrank(profile["extid"]) for score in dummyscores: - data = scores.get(score['id'], {}).get(score['chart'], None) + data = scores.get(score["id"], {}).get(score["chart"], None) if data is None: - raise Exception(f'Expected to get score back for song {score["id"]} chart {score["chart"]}!') + raise Exception( + f'Expected to get score back for song {score["id"]} chart {score["chart"]}!' + ) - if 'expected_ex_score' in score: - expected_score = score['expected_ex_score'] + if "expected_ex_score" in score: + expected_score = score["expected_ex_score"] else: - expected_score = (score['pgnum'] * 2) + score['gnum'] - if 'expected_clear_status' in score: - expected_clear_status = score['expected_clear_status'] + expected_score = (score["pgnum"] * 2) + score["gnum"] + if "expected_clear_status" in score: + expected_clear_status = score["expected_clear_status"] else: - expected_clear_status = score['clear_status'] - if 'expected_miss_count' in score: - expected_miss_count = score['expected_miss_count'] + expected_clear_status = score["clear_status"] + if "expected_miss_count" in score: + expected_miss_count = score["expected_miss_count"] else: - expected_miss_count = score['mnum'] + expected_miss_count = score["mnum"] - if data['ex_score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\'') - if data['clear_status'] != expected_clear_status: - raise Exception(f'Expected a clear status of \'{expected_clear_status}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got clear status \'{data["clear_status"]}\'') - if data['miss_count'] != expected_miss_count: - raise Exception(f'Expected a miss count of \'{expected_miss_count}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got miss count \'{data["miss_count"]}\'') + if data["ex_score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\'' + ) + if data["clear_status"] != expected_clear_status: + raise Exception( + f'Expected a clear status of \'{expected_clear_status}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got clear status \'{data["clear_status"]}\'' + ) + if data["miss_count"] != expected_miss_count: + raise Exception( + f'Expected a miss count of \'{expected_miss_count}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got miss count \'{data["miss_count"]}\'' + ) # Verify we can fetch our own ghost - ex_score, ghost = self.verify_iidx26music_appoint(profile['extid'], score['id'], score['chart']) + ex_score, ghost = self.verify_iidx26music_appoint( + profile["extid"], score["id"], score["chart"] + ) if ex_score != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\'') + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\'' + ) if len(ghost) != 64: - raise Exception(f'Wrong ghost length {len(ghost)} for ghost!') + raise Exception(f"Wrong ghost length {len(ghost)} for ghost!") for g in ghost: if g != 0x01: - raise Exception(f'Got back wrong ghost data for song \'{score["id"]}\' chart \'{score["chart"]}\'') + raise Exception( + f'Got back wrong ghost data for song \'{score["id"]}\' chart \'{score["chart"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) # Verify that we can save/load expert points - self.verify_iidx26pc_save(profile['extid'], card, lid, {'course_id': 1, 'n_point': 0, 'h_point': 500, 'a_point': 0}) + self.verify_iidx26pc_save( + profile["extid"], + card, + lid, + {"course_id": 1, "n_point": 0, "h_point": 500, "a_point": 0}, + ) profile = self.verify_iidx26pc_get(ref_id, card, lid) - if sorted(profile['expert_point'].keys()) != [1]: - raise Exception('Got back wrong number of expert course points!') - if profile['expert_point'][1] != {'n_point': 0, 'h_point': 500, 'a_point': 0}: - raise Exception('Got back wrong expert points after saving!') - self.verify_iidx26pc_save(profile['extid'], card, lid, {'course_id': 1, 'n_point': 0, 'h_point': 1000, 'a_point': 0}) + if sorted(profile["expert_point"].keys()) != [1]: + raise Exception("Got back wrong number of expert course points!") + if profile["expert_point"][1] != { + "n_point": 0, + "h_point": 500, + "a_point": 0, + }: + raise Exception("Got back wrong expert points after saving!") + self.verify_iidx26pc_save( + profile["extid"], + card, + lid, + {"course_id": 1, "n_point": 0, "h_point": 1000, "a_point": 0}, + ) profile = self.verify_iidx26pc_get(ref_id, card, lid) - if sorted(profile['expert_point'].keys()) != [1]: - raise Exception('Got back wrong number of expert course points!') - if profile['expert_point'][1] != {'n_point': 0, 'h_point': 1000, 'a_point': 0}: - raise Exception('Got back wrong expert points after saving!') - self.verify_iidx26pc_save(profile['extid'], card, lid, {'course_id': 2, 'n_point': 0, 'h_point': 0, 'a_point': 500}) + if sorted(profile["expert_point"].keys()) != [1]: + raise Exception("Got back wrong number of expert course points!") + if profile["expert_point"][1] != { + "n_point": 0, + "h_point": 1000, + "a_point": 0, + }: + raise Exception("Got back wrong expert points after saving!") + self.verify_iidx26pc_save( + profile["extid"], + card, + lid, + {"course_id": 2, "n_point": 0, "h_point": 0, "a_point": 500}, + ) profile = self.verify_iidx26pc_get(ref_id, card, lid) - if sorted(profile['expert_point'].keys()) != [1, 2]: - raise Exception('Got back wrong number of expert course points!') - if profile['expert_point'][1] != {'n_point': 0, 'h_point': 1000, 'a_point': 0}: - raise Exception('Got back wrong expert points after saving!') - if profile['expert_point'][2] != {'n_point': 0, 'h_point': 0, 'a_point': 500}: - raise Exception('Got back wrong expert points after saving!') + if sorted(profile["expert_point"].keys()) != [1, 2]: + raise Exception("Got back wrong number of expert course points!") + if profile["expert_point"][1] != { + "n_point": 0, + "h_point": 1000, + "a_point": 0, + }: + raise Exception("Got back wrong expert points after saving!") + if profile["expert_point"][2] != { + "n_point": 0, + "h_point": 0, + "a_point": 500, + }: + raise Exception("Got back wrong expert points after saving!") # Verify that a player without a card can play self.verify_iidx26pc_playstart() - self.verify_iidx26music_play({ - 'id': 1000, - 'chart': 2, - 'clear_status': 4, - 'pgnum': 123, - 'gnum': 123, - }) - self.verify_iidx26pc_playend(lid, 'newname1') + self.verify_iidx26music_play( + { + "id": 1000, + "chart": 2, + "clear_status": 4, + "pgnum": 123, + "gnum": 123, + } + ) + self.verify_iidx26pc_playend(lid, "newname1") # Verify shop name change setting - self.verify_iidx26shop_savename(lid, 'newname1') + self.verify_iidx26shop_savename(lid, "newname1") newname = self.verify_iidx26shop_getname(lid) - if newname != 'newname1': - raise Exception('Invalid shop name returned after change!') - self.verify_iidx26shop_savename(lid, 'newname2') + if newname != "newname1": + raise Exception("Invalid shop name returned after change!") + self.verify_iidx26shop_savename(lid, "newname2") newname = self.verify_iidx26shop_getname(lid) - if newname != 'newname2': - raise Exception('Invalid shop name returned after change!') + if newname != "newname2": + raise Exception("Invalid shop name returned after change!") # Verify beginner score saving - self.verify_iidx26music_breg(profile['extid'], { - 'id': 1000, - 'clear_status': 4, - 'pgnum': 123, - 'gnum': 123, - }) - scores = self.verify_iidx26music_getrank(profile['extid']) + self.verify_iidx26music_breg( + profile["extid"], + { + "id": 1000, + "clear_status": 4, + "pgnum": 123, + "gnum": 123, + }, + ) + scores = self.verify_iidx26music_getrank(profile["extid"]) if 1000 not in scores: - raise Exception(f'Didn\'t get expected scores back for song {1000} beginner chart!') + raise Exception( + f"Didn't get expected scores back for song {1000} beginner chart!" + ) if 6 not in scores[1000]: - raise Exception(f'Didn\'t get beginner score back for song {1000}!') - if scores[1000][6] != {'clear_status': 4, 'ex_score': -1, 'miss_count': -1}: - raise Exception('Didn\'t get correct status back from beginner save!') + raise Exception(f"Didn't get beginner score back for song {1000}!") + if scores[1000][6] != {"clear_status": 4, "ex_score": -1, "miss_count": -1}: + raise Exception("Didn't get correct status back from beginner save!") # Verify DAN score saving and loading - self.verify_iidx26grade_raised(profile['extid'], newname, 'sp') - self.verify_iidx26grade_raised(profile['extid'], newname, 'dp') + self.verify_iidx26grade_raised(profile["extid"], newname, "sp") + self.verify_iidx26grade_raised(profile["extid"], newname, "dp") profile = self.verify_iidx26pc_get(ref_id, card, lid) - if profile['sp_dan'] != 5: - raise Exception('Got wrong DAN score back for SP!') - if profile['dp_dan'] != 5: - raise Exception('Got wrong DAN score back for DP!') + if profile["sp_dan"] != 5: + raise Exception("Got wrong DAN score back for SP!") + if profile["dp_dan"] != 5: + raise Exception("Got wrong DAN score back for DP!") else: print("Skipping score checks for existing card") diff --git a/bemani/client/iidx/sinobuz.py b/bemani/client/iidx/sinobuz.py index eae8705..117fa5a 100644 --- a/bemani/client/iidx/sinobuz.py +++ b/bemani/client/iidx/sinobuz.py @@ -7,19 +7,19 @@ from bemani.protocol import Node class IIDXSinobuzClient(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_iidx24shop_getname(self, lid: str) -> str: call = self.call_node() # Construct node - IIDX24shop = Node.void('IIDX24shop') + IIDX24shop = Node.void("IIDX24shop") call.add_child(IIDX24shop) - IIDX24shop.set_attribute('method', 'getname') - IIDX24shop.set_attribute('lid', lid) + IIDX24shop.set_attribute("method", "getname") + IIDX24shop.set_attribute("lid", lid) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX24shop/@opname") @@ -28,25 +28,25 @@ class IIDXSinobuzClient(BaseClient): self.assert_path(resp, "response/IIDX24shop/@hr") self.assert_path(resp, "response/IIDX24shop/@mi") - return resp.child('IIDX24shop').attribute('opname') + return resp.child("IIDX24shop").attribute("opname") def verify_iidx24shop_savename(self, lid: str, name: str) -> None: call = self.call_node() # Construct node - IIDX24shop = Node.void('IIDX24shop') - IIDX24shop.set_attribute('lid', lid) - IIDX24shop.set_attribute('pid', '51') - IIDX24shop.set_attribute('method', 'savename') - IIDX24shop.set_attribute('cls_opt', '0') - IIDX24shop.set_attribute('ccode', 'US') - IIDX24shop.set_attribute('opname', name) - IIDX24shop.set_attribute('rcode', '.') + IIDX24shop = Node.void("IIDX24shop") + IIDX24shop.set_attribute("lid", lid) + IIDX24shop.set_attribute("pid", "51") + IIDX24shop.set_attribute("method", "savename") + IIDX24shop.set_attribute("cls_opt", "0") + IIDX24shop.set_attribute("ccode", "US") + IIDX24shop.set_attribute("opname", name) + IIDX24shop.set_attribute("rcode", ".") call.add_child(IIDX24shop) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX24shop") @@ -55,12 +55,12 @@ class IIDXSinobuzClient(BaseClient): call = self.call_node() # Construct node - IIDX24pc = Node.void('IIDX24pc') + IIDX24pc = Node.void("IIDX24pc") call.add_child(IIDX24pc) - IIDX24pc.set_attribute('method', 'common') + IIDX24pc.set_attribute("method", "common") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX24pc/ir/@beat") @@ -77,34 +77,36 @@ class IIDXSinobuzClient(BaseClient): call = self.call_node() # Construct node - IIDX24pc = Node.void('IIDX24music') + IIDX24pc = Node.void("IIDX24music") call.add_child(IIDX24pc) - IIDX24pc.set_attribute('method', 'crate') + IIDX24pc.set_attribute("method", "crate") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX24music") for child in resp.child("IIDX24music").children: - if child.name != 'c': - raise Exception(f'Invalid node {child} in clear rate response!') + if child.name != "c": + raise Exception(f"Invalid node {child} in clear rate response!") if len(child.value) != 12: - raise Exception(f'Invalid node data {child} in clear rate response!') + raise Exception(f"Invalid node data {child} in clear rate response!") for v in child.value: if v < 0 or v > 1001: - raise Exception(f'Invalid clear percent {child} in clear rate response!') + raise Exception( + f"Invalid clear percent {child} in clear rate response!" + ) def verify_iidx24shop_getconvention(self, lid: str) -> None: call = self.call_node() # Construct node - IIDX24pc = Node.void('IIDX24shop') + IIDX24pc = Node.void("IIDX24shop") call.add_child(IIDX24pc) - IIDX24pc.set_attribute('method', 'getconvention') - IIDX24pc.set_attribute('lid', lid) + IIDX24pc.set_attribute("method", "getconvention") + IIDX24pc.set_attribute("lid", lid) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX24shop/valid") @@ -117,15 +119,15 @@ class IIDXSinobuzClient(BaseClient): call = self.call_node() # Construct node - IIDX24pc = Node.void('IIDX24pc') + IIDX24pc = Node.void("IIDX24pc") call.add_child(IIDX24pc) - IIDX24pc.set_attribute('iidxid', str(extid)) - IIDX24pc.set_attribute('lid', lid) - IIDX24pc.set_attribute('method', 'visit') - IIDX24pc.set_attribute('pid', '51') + IIDX24pc.set_attribute("iidxid", str(extid)) + IIDX24pc.set_attribute("lid", lid) + IIDX24pc.set_attribute("method", "visit") + IIDX24pc.set_attribute("pid", "51") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX24pc/@aflg") @@ -140,14 +142,14 @@ class IIDXSinobuzClient(BaseClient): call = self.call_node() # Construct node - IIDX24pc = Node.void('IIDX24ranking') + IIDX24pc = Node.void("IIDX24ranking") call.add_child(IIDX24pc) - IIDX24pc.set_attribute('method', 'getranker') - IIDX24pc.set_attribute('lid', lid) - IIDX24pc.set_attribute('clid', str(clid)) + IIDX24pc.set_attribute("method", "getranker") + IIDX24pc.set_attribute("lid", lid) + IIDX24pc.set_attribute("clid", str(clid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX24ranking") @@ -156,40 +158,42 @@ class IIDXSinobuzClient(BaseClient): call = self.call_node() # Construct node - IIDX24pc = Node.void('IIDX24shop') + IIDX24pc = Node.void("IIDX24shop") call.add_child(IIDX24pc) - IIDX24pc.set_attribute('method', 'sentinfo') - IIDX24pc.set_attribute('lid', lid) - IIDX24pc.set_attribute('bflg', '1') - IIDX24pc.set_attribute('bnum', '2') - IIDX24pc.set_attribute('ioid', '0') - IIDX24pc.set_attribute('company_code', '') - IIDX24pc.set_attribute('consumer_code', '') - IIDX24pc.set_attribute('location_name', shop_name) - IIDX24pc.set_attribute('tax_phase', '0') + IIDX24pc.set_attribute("method", "sentinfo") + IIDX24pc.set_attribute("lid", lid) + IIDX24pc.set_attribute("bflg", "1") + IIDX24pc.set_attribute("bnum", "2") + IIDX24pc.set_attribute("ioid", "0") + IIDX24pc.set_attribute("company_code", "") + IIDX24pc.set_attribute("consumer_code", "") + IIDX24pc.set_attribute("location_name", shop_name) + IIDX24pc.set_attribute("tax_phase", "0") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX24shop") - def verify_iidx24pc_get(self, ref_id: str, card_id: str, lid: str) -> Dict[str, Any]: + def verify_iidx24pc_get( + self, ref_id: str, card_id: str, lid: str + ) -> Dict[str, Any]: call = self.call_node() # Construct node - IIDX24pc = Node.void('IIDX24pc') + IIDX24pc = Node.void("IIDX24pc") call.add_child(IIDX24pc) - IIDX24pc.set_attribute('rid', ref_id) - IIDX24pc.set_attribute('did', ref_id) - IIDX24pc.set_attribute('pid', '51') - IIDX24pc.set_attribute('lid', lid) - IIDX24pc.set_attribute('cid', card_id) - IIDX24pc.set_attribute('method', 'get') - IIDX24pc.set_attribute('ctype', '1') + IIDX24pc.set_attribute("rid", ref_id) + IIDX24pc.set_attribute("did", ref_id) + IIDX24pc.set_attribute("pid", "51") + IIDX24pc.set_attribute("lid", lid) + IIDX24pc.set_attribute("cid", card_id) + IIDX24pc.set_attribute("method", "get") + IIDX24pc.set_attribute("ctype", "1") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that the response is correct self.assert_path(resp, "response/IIDX24pc/pcdata/@name") @@ -218,14 +222,14 @@ class IIDXSinobuzClient(BaseClient): self.assert_path(resp, "response/IIDX24pc/extra_favorite/dp_mlist") self.assert_path(resp, "response/IIDX24pc/extra_favorite/dp_clist") - name = resp.child('IIDX24pc/pcdata').attribute('name') + name = resp.child("IIDX24pc/pcdata").attribute("name") if name != self.NAME: - raise Exception(f'Invalid name \'{name}\' returned for Ref ID \'{ref_id}\'') + raise Exception(f"Invalid name '{name}' returned for Ref ID '{ref_id}'") # Extract and return account data ir_data: Dict[int, Dict[int, Dict[str, int]]] = {} - for child in resp.child('IIDX24pc/ir_data').children: - if child.name == 'e': + for child in resp.child("IIDX24pc/ir_data").children: + if child.name == "e": course_id = child.value[0] course_chart = child.value[1] clear_status = child.value[2] @@ -235,14 +239,14 @@ class IIDXSinobuzClient(BaseClient): if course_id not in ir_data: ir_data[course_id] = {} ir_data[course_id][course_chart] = { - 'clear_status': clear_status, - 'pgnum': pgnum, - 'gnum': gnum, + "clear_status": clear_status, + "pgnum": pgnum, + "gnum": gnum, } secret_course_data: Dict[int, Dict[int, Dict[str, int]]] = {} - for child in resp.child('IIDX24pc/secret_course_data').children: - if child.name == 'e': + for child in resp.child("IIDX24pc/secret_course_data").children: + if child.name == "e": course_id = child.value[0] course_chart = child.value[1] clear_status = child.value[2] @@ -252,71 +256,75 @@ class IIDXSinobuzClient(BaseClient): if course_id not in secret_course_data: secret_course_data[course_id] = {} secret_course_data[course_id][course_chart] = { - 'clear_status': clear_status, - 'pgnum': pgnum, - 'gnum': gnum, + "clear_status": clear_status, + "pgnum": pgnum, + "gnum": gnum, } classic_course_data: Dict[int, Dict[int, Dict[str, int]]] = {} # noqa: E701 - for child in resp.child('IIDX24pc/classic_course_data').children: - if child.name == 'score_data': - course_id = int(child.attribute('course_id')) - course_chart = int(child.attribute('play_style')) - clear_status = int(child.attribute('cflg')) - pgnum = int(child.attribute('pgnum')) - gnum = int(child.attribute('gnum')) + for child in resp.child("IIDX24pc/classic_course_data").children: + if child.name == "score_data": + course_id = int(child.attribute("course_id")) + course_chart = int(child.attribute("play_style")) + clear_status = int(child.attribute("cflg")) + pgnum = int(child.attribute("pgnum")) + gnum = int(child.attribute("gnum")) if course_id not in classic_course_data: classic_course_data[course_id] = {} classic_course_data[course_id][course_chart] = { - 'clear_status': clear_status, - 'pgnum': pgnum, - 'gnum': gnum, + "clear_status": clear_status, + "pgnum": pgnum, + "gnum": gnum, } expert_point: Dict[int, Dict[str, int]] = {} - for child in resp.child('IIDX24pc/expert_point').children: - if child.name == 'detail': - expert_point[int(child.attribute('course_id'))] = { - 'n_point': int(child.attribute('n_point')), - 'h_point': int(child.attribute('h_point')), - 'a_point': int(child.attribute('a_point')), + for child in resp.child("IIDX24pc/expert_point").children: + if child.name == "detail": + expert_point[int(child.attribute("course_id"))] = { + "n_point": int(child.attribute("n_point")), + "h_point": int(child.attribute("h_point")), + "a_point": int(child.attribute("a_point")), } return { - 'extid': int(resp.child('IIDX24pc/pcdata').attribute('id')), - 'sp_dan': int(resp.child('IIDX24pc/grade').attribute('sgid')), - 'dp_dan': int(resp.child('IIDX24pc/grade').attribute('dgid')), - 'deller': int(resp.child('IIDX24pc/deller').attribute('deller')), - 'ir_data': ir_data, - 'secret_course_data': secret_course_data, - 'classic_course_data': classic_course_data, - 'expert_point': expert_point, + "extid": int(resp.child("IIDX24pc/pcdata").attribute("id")), + "sp_dan": int(resp.child("IIDX24pc/grade").attribute("sgid")), + "dp_dan": int(resp.child("IIDX24pc/grade").attribute("dgid")), + "deller": int(resp.child("IIDX24pc/deller").attribute("deller")), + "ir_data": ir_data, + "secret_course_data": secret_course_data, + "classic_course_data": classic_course_data, + "expert_point": expert_point, } - def verify_iidx24music_getrank(self, extid: int) -> Dict[int, Dict[int, Dict[str, int]]]: + def verify_iidx24music_getrank( + self, extid: int + ) -> Dict[int, Dict[int, Dict[str, int]]]: scores: Dict[int, Dict[int, Dict[str, int]]] = {} for cltype in [0, 1]: # singles, doubles call = self.call_node() # Construct node - IIDX24music = Node.void('IIDX24music') + IIDX24music = Node.void("IIDX24music") call.add_child(IIDX24music) - IIDX24music.set_attribute('method', 'getrank') - IIDX24music.set_attribute('iidxid', str(extid)) - IIDX24music.set_attribute('cltype', str(cltype)) + IIDX24music.set_attribute("method", "getrank") + IIDX24music.set_attribute("iidxid", str(extid)) + IIDX24music.set_attribute("cltype", str(cltype)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX24music/style") - if int(resp.child('IIDX24music/style').attribute('type')) != cltype: - raise Exception('Returned wrong clear type for IIDX24music.getrank!') + if int(resp.child("IIDX24music/style").attribute("type")) != cltype: + raise Exception("Returned wrong clear type for IIDX24music.getrank!") - for child in resp.child('IIDX24music').children: - if child.name == 'm': + for child in resp.child("IIDX24music").children: + if child.name == "m": if child.value[0] != -1: - raise Exception('Got non-self score back when requesting only our scores!') + raise Exception( + "Got non-self score back when requesting only our scores!" + ) music_id = child.value[1] normal_clear_status = child.value[2] @@ -342,189 +350,199 @@ class IIDXSinobuzClient(BaseClient): scores[music_id] = {} scores[music_id][normal] = { - 'clear_status': normal_clear_status, - 'ex_score': normal_ex_score, - 'miss_count': normal_miss_count, + "clear_status": normal_clear_status, + "ex_score": normal_ex_score, + "miss_count": normal_miss_count, } scores[music_id][hyper] = { - 'clear_status': hyper_clear_status, - 'ex_score': hyper_ex_score, - 'miss_count': hyper_miss_count, + "clear_status": hyper_clear_status, + "ex_score": hyper_ex_score, + "miss_count": hyper_miss_count, } scores[music_id][another] = { - 'clear_status': another_clear_status, - 'ex_score': another_ex_score, - 'miss_count': another_miss_count, + "clear_status": another_clear_status, + "ex_score": another_ex_score, + "miss_count": another_miss_count, } - elif child.name == 'b': + elif child.name == "b": music_id = child.value[0] clear_status = child.value[1] scores[music_id][6] = { - 'clear_status': clear_status, - 'ex_score': -1, - 'miss_count': -1, + "clear_status": clear_status, + "ex_score": -1, + "miss_count": -1, } return scores - def verify_iidx24pc_save(self, extid: int, card: str, lid: str, expert_point: Optional[Dict[str, int]]=None) -> None: + def verify_iidx24pc_save( + self, + extid: int, + card: str, + lid: str, + expert_point: Optional[Dict[str, int]] = None, + ) -> None: call = self.call_node() # Construct node - IIDX24pc = Node.void('IIDX24pc') + IIDX24pc = Node.void("IIDX24pc") call.add_child(IIDX24pc) - IIDX24pc.set_attribute('s_disp_judge', '1') - IIDX24pc.set_attribute('mode', '6') - IIDX24pc.set_attribute('pmode', '0') - IIDX24pc.set_attribute('method', 'save') - IIDX24pc.set_attribute('s_sorttype', '0') - IIDX24pc.set_attribute('s_exscore', '0') - IIDX24pc.set_attribute('d_notes', '0.000000') - IIDX24pc.set_attribute('gpos', '0') - IIDX24pc.set_attribute('s_gno', '8') - IIDX24pc.set_attribute('s_hispeed', '5.771802') - IIDX24pc.set_attribute('s_judge', '0') - IIDX24pc.set_attribute('d_timing', '0') - IIDX24pc.set_attribute('rtype', '0') - IIDX24pc.set_attribute('d_graph_score', '0') - IIDX24pc.set_attribute('d_lift', '60') - IIDX24pc.set_attribute('s_pace', '0') - IIDX24pc.set_attribute('d_exscore', '0') - IIDX24pc.set_attribute('d_sdtype', '0') - IIDX24pc.set_attribute('s_opstyle', '1') - IIDX24pc.set_attribute('s_achi', '449') - IIDX24pc.set_attribute('s_graph_score', '0') - IIDX24pc.set_attribute('d_gno', '0') - IIDX24pc.set_attribute('s_lift', '60') - IIDX24pc.set_attribute('s_notes', '31.484070') - IIDX24pc.set_attribute('d_tune', '0') - IIDX24pc.set_attribute('d_sdlen', '0') - IIDX24pc.set_attribute('d_achi', '4') - IIDX24pc.set_attribute('d_opstyle', '0') - IIDX24pc.set_attribute('sp_opt', '8208') - IIDX24pc.set_attribute('iidxid', str(extid)) - IIDX24pc.set_attribute('lid', lid) - IIDX24pc.set_attribute('s_judgeAdj', '0') - IIDX24pc.set_attribute('s_tune', '3') - IIDX24pc.set_attribute('s_sdtype', '1') - IIDX24pc.set_attribute('s_gtype', '2') - IIDX24pc.set_attribute('d_judge', '0') - IIDX24pc.set_attribute('cid', card) - IIDX24pc.set_attribute('cltype', '0') - IIDX24pc.set_attribute('ctype', '1') - IIDX24pc.set_attribute('bookkeep', '0') - IIDX24pc.set_attribute('d_hispeed', '0.000000') - IIDX24pc.set_attribute('d_pace', '0') - IIDX24pc.set_attribute('d_judgeAdj', '0') - IIDX24pc.set_attribute('s_timing', '1') - IIDX24pc.set_attribute('d_disp_judge', '0') - IIDX24pc.set_attribute('s_sdlen', '121') - IIDX24pc.set_attribute('dp_opt2', '0') - IIDX24pc.set_attribute('d_gtype', '0') - IIDX24pc.set_attribute('d_sorttype', '0') - IIDX24pc.set_attribute('dp_opt', '0') - deller = Node.void('deller') + IIDX24pc.set_attribute("s_disp_judge", "1") + IIDX24pc.set_attribute("mode", "6") + IIDX24pc.set_attribute("pmode", "0") + IIDX24pc.set_attribute("method", "save") + IIDX24pc.set_attribute("s_sorttype", "0") + IIDX24pc.set_attribute("s_exscore", "0") + IIDX24pc.set_attribute("d_notes", "0.000000") + IIDX24pc.set_attribute("gpos", "0") + IIDX24pc.set_attribute("s_gno", "8") + IIDX24pc.set_attribute("s_hispeed", "5.771802") + IIDX24pc.set_attribute("s_judge", "0") + IIDX24pc.set_attribute("d_timing", "0") + IIDX24pc.set_attribute("rtype", "0") + IIDX24pc.set_attribute("d_graph_score", "0") + IIDX24pc.set_attribute("d_lift", "60") + IIDX24pc.set_attribute("s_pace", "0") + IIDX24pc.set_attribute("d_exscore", "0") + IIDX24pc.set_attribute("d_sdtype", "0") + IIDX24pc.set_attribute("s_opstyle", "1") + IIDX24pc.set_attribute("s_achi", "449") + IIDX24pc.set_attribute("s_graph_score", "0") + IIDX24pc.set_attribute("d_gno", "0") + IIDX24pc.set_attribute("s_lift", "60") + IIDX24pc.set_attribute("s_notes", "31.484070") + IIDX24pc.set_attribute("d_tune", "0") + IIDX24pc.set_attribute("d_sdlen", "0") + IIDX24pc.set_attribute("d_achi", "4") + IIDX24pc.set_attribute("d_opstyle", "0") + IIDX24pc.set_attribute("sp_opt", "8208") + IIDX24pc.set_attribute("iidxid", str(extid)) + IIDX24pc.set_attribute("lid", lid) + IIDX24pc.set_attribute("s_judgeAdj", "0") + IIDX24pc.set_attribute("s_tune", "3") + IIDX24pc.set_attribute("s_sdtype", "1") + IIDX24pc.set_attribute("s_gtype", "2") + IIDX24pc.set_attribute("d_judge", "0") + IIDX24pc.set_attribute("cid", card) + IIDX24pc.set_attribute("cltype", "0") + IIDX24pc.set_attribute("ctype", "1") + IIDX24pc.set_attribute("bookkeep", "0") + IIDX24pc.set_attribute("d_hispeed", "0.000000") + IIDX24pc.set_attribute("d_pace", "0") + IIDX24pc.set_attribute("d_judgeAdj", "0") + IIDX24pc.set_attribute("s_timing", "1") + IIDX24pc.set_attribute("d_disp_judge", "0") + IIDX24pc.set_attribute("s_sdlen", "121") + IIDX24pc.set_attribute("dp_opt2", "0") + IIDX24pc.set_attribute("d_gtype", "0") + IIDX24pc.set_attribute("d_sorttype", "0") + IIDX24pc.set_attribute("dp_opt", "0") + deller = Node.void("deller") IIDX24pc.add_child(deller) - deller.set_attribute('deller', '150') + deller.set_attribute("deller", "150") if expert_point is not None: - epnode = Node.void('expert_point') - epnode.set_attribute('h_point', str(expert_point['h_point'])) - epnode.set_attribute('course_id', str(expert_point['course_id'])) - epnode.set_attribute('n_point', str(expert_point['n_point'])) - epnode.set_attribute('a_point', str(expert_point['a_point'])) + epnode = Node.void("expert_point") + epnode.set_attribute("h_point", str(expert_point["h_point"])) + epnode.set_attribute("course_id", str(expert_point["course_id"])) + epnode.set_attribute("n_point", str(expert_point["n_point"])) + epnode.set_attribute("a_point", str(expert_point["a_point"])) IIDX24pc.add_child(epnode) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX24pc") - def verify_iidx24music_reg(self, extid: int, lid: str, score: Dict[str, Any]) -> None: + def verify_iidx24music_reg( + self, extid: int, lid: str, score: Dict[str, Any] + ) -> None: call = self.call_node() # Construct node - IIDX24music = Node.void('IIDX24music') + IIDX24music = Node.void("IIDX24music") call.add_child(IIDX24music) - IIDX24music.set_attribute('convid', '-1') - IIDX24music.set_attribute('iidxid', str(extid)) - IIDX24music.set_attribute('pgnum', str(score['pgnum'])) - IIDX24music.set_attribute('pid', '51') - IIDX24music.set_attribute('rankside', '1') - IIDX24music.set_attribute('cflg', str(score['clear_status'])) - IIDX24music.set_attribute('method', 'reg') - IIDX24music.set_attribute('gnum', str(score['gnum'])) - IIDX24music.set_attribute('clid', str(score['chart'])) - IIDX24music.set_attribute('mnum', str(score['mnum'])) - IIDX24music.set_attribute('is_death', '0') - IIDX24music.set_attribute('theory', '0') - IIDX24music.set_attribute('dj_level', '1') - IIDX24music.set_attribute('shopconvid', lid) - IIDX24music.set_attribute('mid', str(score['id'])) - IIDX24music.set_attribute('shopflg', '1') - IIDX24music.add_child(Node.binary('ghost', bytes([1] * 64))) + IIDX24music.set_attribute("convid", "-1") + IIDX24music.set_attribute("iidxid", str(extid)) + IIDX24music.set_attribute("pgnum", str(score["pgnum"])) + IIDX24music.set_attribute("pid", "51") + IIDX24music.set_attribute("rankside", "1") + IIDX24music.set_attribute("cflg", str(score["clear_status"])) + IIDX24music.set_attribute("method", "reg") + IIDX24music.set_attribute("gnum", str(score["gnum"])) + IIDX24music.set_attribute("clid", str(score["chart"])) + IIDX24music.set_attribute("mnum", str(score["mnum"])) + IIDX24music.set_attribute("is_death", "0") + IIDX24music.set_attribute("theory", "0") + IIDX24music.set_attribute("dj_level", "1") + IIDX24music.set_attribute("shopconvid", lid) + IIDX24music.set_attribute("mid", str(score["id"])) + IIDX24music.set_attribute("shopflg", "1") + IIDX24music.add_child(Node.binary("ghost", bytes([1] * 64))) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX24music/shopdata/@rank") self.assert_path(resp, "response/IIDX24music/ranklist/data") - def verify_iidx24music_appoint(self, extid: int, musicid: int, chart: int) -> Tuple[int, bytes]: + def verify_iidx24music_appoint( + self, extid: int, musicid: int, chart: int + ) -> Tuple[int, bytes]: call = self.call_node() # Construct node - IIDX24music = Node.void('IIDX24music') + IIDX24music = Node.void("IIDX24music") call.add_child(IIDX24music) - IIDX24music.set_attribute('clid', str(chart)) - IIDX24music.set_attribute('method', 'appoint') - IIDX24music.set_attribute('ctype', '0') - IIDX24music.set_attribute('iidxid', str(extid)) - IIDX24music.set_attribute('subtype', '') - IIDX24music.set_attribute('mid', str(musicid)) + IIDX24music.set_attribute("clid", str(chart)) + IIDX24music.set_attribute("method", "appoint") + IIDX24music.set_attribute("ctype", "0") + IIDX24music.set_attribute("iidxid", str(extid)) + IIDX24music.set_attribute("subtype", "") + IIDX24music.set_attribute("mid", str(musicid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX24music/mydata/@score") return ( - int(resp.child('IIDX24music/mydata').attribute('score')), - resp.child_value('IIDX24music/mydata'), + int(resp.child("IIDX24music/mydata").attribute("score")), + resp.child_value("IIDX24music/mydata"), ) def verify_iidx24pc_reg(self, ref_id: str, card_id: str, lid: str) -> int: call = self.call_node() # Construct node - IIDX24pc = Node.void('IIDX24pc') + IIDX24pc = Node.void("IIDX24pc") call.add_child(IIDX24pc) - IIDX24pc.set_attribute('lid', lid) - IIDX24pc.set_attribute('pid', '51') - IIDX24pc.set_attribute('method', 'reg') - IIDX24pc.set_attribute('cid', card_id) - IIDX24pc.set_attribute('did', ref_id) - IIDX24pc.set_attribute('rid', ref_id) - IIDX24pc.set_attribute('name', self.NAME) + IIDX24pc.set_attribute("lid", lid) + IIDX24pc.set_attribute("pid", "51") + IIDX24pc.set_attribute("method", "reg") + IIDX24pc.set_attribute("cid", card_id) + IIDX24pc.set_attribute("did", ref_id) + IIDX24pc.set_attribute("rid", ref_id) + IIDX24pc.set_attribute("name", self.NAME) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX24pc/@id") self.assert_path(resp, "response/IIDX24pc/@id_str") - return int(resp.child('IIDX24pc').attribute('id')) + return int(resp.child("IIDX24pc").attribute("id")) def verify_iidx24pc_playstart(self) -> None: call = self.call_node() # Construct node - IIDX24pc = Node.void('IIDX24pc') - IIDX24pc.set_attribute('method', 'playstart') - IIDX24pc.set_attribute('side', '1') + IIDX24pc = Node.void("IIDX24pc") + IIDX24pc.set_attribute("method", "playstart") + IIDX24pc.set_attribute("side", "1") call.add_child(IIDX24pc) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX24pc") @@ -533,19 +551,19 @@ class IIDXSinobuzClient(BaseClient): call = self.call_node() # Construct node - IIDX24music = Node.void('IIDX24music') - IIDX24music.set_attribute('opt', '64') - IIDX24music.set_attribute('clid', str(score['chart'])) - IIDX24music.set_attribute('mid', str(score['id'])) - IIDX24music.set_attribute('gnum', str(score['gnum'])) - IIDX24music.set_attribute('cflg', str(score['clear_status'])) - IIDX24music.set_attribute('pgnum', str(score['pgnum'])) - IIDX24music.set_attribute('pid', '51') - IIDX24music.set_attribute('method', 'play') + IIDX24music = Node.void("IIDX24music") + IIDX24music.set_attribute("opt", "64") + IIDX24music.set_attribute("clid", str(score["chart"])) + IIDX24music.set_attribute("mid", str(score["id"])) + IIDX24music.set_attribute("gnum", str(score["gnum"])) + IIDX24music.set_attribute("cflg", str(score["clear_status"])) + IIDX24music.set_attribute("pgnum", str(score["pgnum"])) + IIDX24music.set_attribute("pid", "51") + IIDX24music.set_attribute("method", "play") call.add_child(IIDX24music) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX24music/@clid") @@ -557,20 +575,20 @@ class IIDXSinobuzClient(BaseClient): call = self.call_node() # Construct node - IIDX24pc = Node.void('IIDX24pc') - IIDX24pc.set_attribute('cltype', '0') - IIDX24pc.set_attribute('bookkeep', '0') - IIDX24pc.set_attribute('mode', '1') - IIDX24pc.set_attribute('pay_coin', '1') - IIDX24pc.set_attribute('method', 'playend') - IIDX24pc.set_attribute('company_code', '') - IIDX24pc.set_attribute('consumer_code', '') - IIDX24pc.set_attribute('location_name', shop_name) - IIDX24pc.set_attribute('lid', lid) + IIDX24pc = Node.void("IIDX24pc") + IIDX24pc.set_attribute("cltype", "0") + IIDX24pc.set_attribute("bookkeep", "0") + IIDX24pc.set_attribute("mode", "1") + IIDX24pc.set_attribute("pay_coin", "1") + IIDX24pc.set_attribute("method", "playend") + IIDX24pc.set_attribute("company_code", "") + IIDX24pc.set_attribute("consumer_code", "") + IIDX24pc.set_attribute("location_name", shop_name) + IIDX24pc.set_attribute("lid", lid) call.add_child(IIDX24pc) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX24pc") @@ -579,68 +597,72 @@ class IIDXSinobuzClient(BaseClient): call = self.call_node() # Construct node - IIDX24music = Node.void('IIDX24music') - IIDX24music.set_attribute('gnum', str(score['gnum'])) - IIDX24music.set_attribute('iidxid', str(iidxid)) - IIDX24music.set_attribute('mid', str(score['id'])) - IIDX24music.set_attribute('method', 'breg') - IIDX24music.set_attribute('pgnum', str(score['pgnum'])) - IIDX24music.set_attribute('cflg', str(score['clear_status'])) + IIDX24music = Node.void("IIDX24music") + IIDX24music.set_attribute("gnum", str(score["gnum"])) + IIDX24music.set_attribute("iidxid", str(iidxid)) + IIDX24music.set_attribute("mid", str(score["id"])) + IIDX24music.set_attribute("method", "breg") + IIDX24music.set_attribute("pgnum", str(score["pgnum"])) + IIDX24music.set_attribute("cflg", str(score["clear_status"])) call.add_child(IIDX24music) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX24music") - def verify_iidx24grade_raised(self, iidxid: int, shop_name: str, dantype: str) -> None: + def verify_iidx24grade_raised( + self, iidxid: int, shop_name: str, dantype: str + ) -> None: call = self.call_node() # Construct node - IIDX24grade = Node.void('IIDX24grade') - IIDX24grade.set_attribute('opname', shop_name) - IIDX24grade.set_attribute('is_mirror', '0') - IIDX24grade.set_attribute('oppid', '51') - IIDX24grade.set_attribute('achi', '50') - IIDX24grade.set_attribute('cstage', '4') - IIDX24grade.set_attribute('gid', '5') - IIDX24grade.set_attribute('iidxid', str(iidxid)) - IIDX24grade.set_attribute('gtype', '0' if dantype == 'sp' else '1') - IIDX24grade.set_attribute('is_ex', '0') - IIDX24grade.set_attribute('pside', '0') - IIDX24grade.set_attribute('method', 'raised') + IIDX24grade = Node.void("IIDX24grade") + IIDX24grade.set_attribute("opname", shop_name) + IIDX24grade.set_attribute("is_mirror", "0") + IIDX24grade.set_attribute("oppid", "51") + IIDX24grade.set_attribute("achi", "50") + IIDX24grade.set_attribute("cstage", "4") + IIDX24grade.set_attribute("gid", "5") + IIDX24grade.set_attribute("iidxid", str(iidxid)) + IIDX24grade.set_attribute("gtype", "0" if dantype == "sp" else "1") + IIDX24grade.set_attribute("is_ex", "0") + IIDX24grade.set_attribute("pside", "0") + IIDX24grade.set_attribute("method", "raised") call.add_child(IIDX24grade) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX24grade/@pnum") - def verify_iidx24ranking_entry(self, iidxid: int, shop_name: str, coursetype: str) -> None: + def verify_iidx24ranking_entry( + self, iidxid: int, shop_name: str, coursetype: str + ) -> None: call = self.call_node() # Construct node - IIDX24ranking = Node.void('IIDX24ranking') - IIDX24ranking.set_attribute('opname', shop_name) - IIDX24ranking.set_attribute('clr', '4') - IIDX24ranking.set_attribute('pgnum', '1771') - IIDX24ranking.set_attribute('coid', '2') - IIDX24ranking.set_attribute('method', 'entry') - IIDX24ranking.set_attribute('opt', '8208') - IIDX24ranking.set_attribute('opt2', '0') - IIDX24ranking.set_attribute('oppid', '51') - IIDX24ranking.set_attribute('cstage', '4') - IIDX24ranking.set_attribute('gnum', '967') - IIDX24ranking.set_attribute('pside', '1') - IIDX24ranking.set_attribute('clid', '1') - IIDX24ranking.set_attribute('regist_type', '0' if coursetype == 'ir' else '1') - IIDX24ranking.set_attribute('iidxid', str(iidxid)) + IIDX24ranking = Node.void("IIDX24ranking") + IIDX24ranking.set_attribute("opname", shop_name) + IIDX24ranking.set_attribute("clr", "4") + IIDX24ranking.set_attribute("pgnum", "1771") + IIDX24ranking.set_attribute("coid", "2") + IIDX24ranking.set_attribute("method", "entry") + IIDX24ranking.set_attribute("opt", "8208") + IIDX24ranking.set_attribute("opt2", "0") + IIDX24ranking.set_attribute("oppid", "51") + IIDX24ranking.set_attribute("cstage", "4") + IIDX24ranking.set_attribute("gnum", "967") + IIDX24ranking.set_attribute("pside", "1") + IIDX24ranking.set_attribute("clid", "1") + IIDX24ranking.set_attribute("regist_type", "0" if coursetype == "ir" else "1") + IIDX24ranking.set_attribute("iidxid", str(iidxid)) call.add_child(IIDX24ranking) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX24ranking/@anum") @@ -650,20 +672,20 @@ class IIDXSinobuzClient(BaseClient): call = self.call_node() # Construct node - IIDX24ranking = Node.void('IIDX24ranking') - IIDX24ranking.set_attribute('clear_stage', '4') - IIDX24ranking.set_attribute('clear_flg', '4') - IIDX24ranking.set_attribute('course_id', '2') - IIDX24ranking.set_attribute('score', '4509') - IIDX24ranking.set_attribute('gnum', '967') - IIDX24ranking.set_attribute('iidx_id', str(iidxid)) - IIDX24ranking.set_attribute('method', 'classicentry') - IIDX24ranking.set_attribute('pgnum', '1771') - IIDX24ranking.set_attribute('play_style', '1') + IIDX24ranking = Node.void("IIDX24ranking") + IIDX24ranking.set_attribute("clear_stage", "4") + IIDX24ranking.set_attribute("clear_flg", "4") + IIDX24ranking.set_attribute("course_id", "2") + IIDX24ranking.set_attribute("score", "4509") + IIDX24ranking.set_attribute("gnum", "967") + IIDX24ranking.set_attribute("iidx_id", str(iidxid)) + IIDX24ranking.set_attribute("method", "classicentry") + IIDX24ranking.set_attribute("pgnum", "1771") + IIDX24ranking.set_attribute("play_style", "1") call.add_child(IIDX24ranking) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX24ranking/@status") @@ -672,20 +694,20 @@ class IIDXSinobuzClient(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -698,7 +720,7 @@ class IIDXSinobuzClient(BaseClient): self.verify_iidx24music_crate() self.verify_iidx24shop_getconvention(lid) self.verify_iidx24ranking_getranker(lid) - self.verify_iidx24shop_sentinfo(lid, 'newname1') + self.verify_iidx24shop_sentinfo(lid, "newname1") # Verify card registration and profile lookup if cardid is not None: @@ -708,227 +730,293 @@ class IIDXSinobuzClient(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") self.verify_iidx24pc_reg(ref_id, card, lid) self.verify_iidx24pc_get(ref_id, card, lid) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") if cardid is None: # Verify score handling profile = self.verify_iidx24pc_get(ref_id, card, lid) - if profile['sp_dan'] != -1: - raise Exception('Somehow has SP DAN ranking on new profile!') - if profile['dp_dan'] != -1: - raise Exception('Somehow has DP DAN ranking on new profile!') - if profile['deller'] != 0: - raise Exception('Somehow has deller on new profile!') - if len(profile['ir_data'].keys()) > 0: - raise Exception('Somehow has internet ranking data on new profile!') - if len(profile['secret_course_data'].keys()) > 0: - raise Exception('Somehow has secret course data on new profile!') - if len(profile['expert_point'].keys()) > 0: - raise Exception('Somehow has expert point data on new profile!') - scores = self.verify_iidx24music_getrank(profile['extid']) + if profile["sp_dan"] != -1: + raise Exception("Somehow has SP DAN ranking on new profile!") + if profile["dp_dan"] != -1: + raise Exception("Somehow has DP DAN ranking on new profile!") + if profile["deller"] != 0: + raise Exception("Somehow has deller on new profile!") + if len(profile["ir_data"].keys()) > 0: + raise Exception("Somehow has internet ranking data on new profile!") + if len(profile["secret_course_data"].keys()) > 0: + raise Exception("Somehow has secret course data on new profile!") + if len(profile["expert_point"].keys()) > 0: + raise Exception("Somehow has expert point data on new profile!") + scores = self.verify_iidx24music_getrank(profile["extid"]) if len(scores.keys()) > 0: - raise Exception('Somehow have scores on a new profile!') + raise Exception("Somehow have scores on a new profile!") for phase in [1, 2]: if phase == 1: dummyscores = [ # An okay score on a chart { - 'id': 1000, - 'chart': 2, - 'clear_status': 4, - 'pgnum': 123, - 'gnum': 123, - 'mnum': 5, + "id": 1000, + "chart": 2, + "clear_status": 4, + "pgnum": 123, + "gnum": 123, + "mnum": 5, }, # A good score on an easier chart of the same song { - 'id': 1000, - 'chart': 0, - 'clear_status': 7, - 'pgnum': 246, - 'gnum': 0, - 'mnum': 0, + "id": 1000, + "chart": 0, + "clear_status": 7, + "pgnum": 246, + "gnum": 0, + "mnum": 0, }, # A bad score on a hard chart { - 'id': 1003, - 'chart': 2, - 'clear_status': 1, - 'pgnum': 10, - 'gnum': 20, - 'mnum': 50, + "id": 1003, + "chart": 2, + "clear_status": 1, + "pgnum": 10, + "gnum": 20, + "mnum": 50, }, # A terrible score on an easy chart { - 'id': 1003, - 'chart': 0, - 'clear_status': 1, - 'pgnum': 2, - 'gnum': 5, - 'mnum': 75, + "id": 1003, + "chart": 0, + "clear_status": 1, + "pgnum": 2, + "gnum": 5, + "mnum": 75, }, ] if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 1000, - 'chart': 2, - 'clear_status': 5, - 'pgnum': 234, - 'gnum': 234, - 'mnum': 3, + "id": 1000, + "chart": 2, + "clear_status": 5, + "pgnum": 234, + "gnum": 234, + "mnum": 3, }, # A worse score on another same chart { - 'id': 1000, - 'chart': 0, - 'clear_status': 4, - 'pgnum': 123, - 'gnum': 123, - 'mnum': 35, - 'expected_clear_status': 7, - 'expected_ex_score': 492, - 'expected_miss_count': 0, + "id": 1000, + "chart": 0, + "clear_status": 4, + "pgnum": 123, + "gnum": 123, + "mnum": 35, + "expected_clear_status": 7, + "expected_ex_score": 492, + "expected_miss_count": 0, }, ] for dummyscore in dummyscores: - self.verify_iidx24music_reg(profile['extid'], lid, dummyscore) - self.verify_iidx24pc_visit(profile['extid'], lid) - self.verify_iidx24pc_save(profile['extid'], card, lid) - scores = self.verify_iidx24music_getrank(profile['extid']) + self.verify_iidx24music_reg(profile["extid"], lid, dummyscore) + self.verify_iidx24pc_visit(profile["extid"], lid) + self.verify_iidx24pc_save(profile["extid"], card, lid) + scores = self.verify_iidx24music_getrank(profile["extid"]) for score in dummyscores: - data = scores.get(score['id'], {}).get(score['chart'], None) + data = scores.get(score["id"], {}).get(score["chart"], None) if data is None: - raise Exception(f'Expected to get score back for song {score["id"]} chart {score["chart"]}!') + raise Exception( + f'Expected to get score back for song {score["id"]} chart {score["chart"]}!' + ) - if 'expected_ex_score' in score: - expected_score = score['expected_ex_score'] + if "expected_ex_score" in score: + expected_score = score["expected_ex_score"] else: - expected_score = (score['pgnum'] * 2) + score['gnum'] - if 'expected_clear_status' in score: - expected_clear_status = score['expected_clear_status'] + expected_score = (score["pgnum"] * 2) + score["gnum"] + if "expected_clear_status" in score: + expected_clear_status = score["expected_clear_status"] else: - expected_clear_status = score['clear_status'] - if 'expected_miss_count' in score: - expected_miss_count = score['expected_miss_count'] + expected_clear_status = score["clear_status"] + if "expected_miss_count" in score: + expected_miss_count = score["expected_miss_count"] else: - expected_miss_count = score['mnum'] + expected_miss_count = score["mnum"] - if data['ex_score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\'') - if data['clear_status'] != expected_clear_status: - raise Exception(f'Expected a clear status of \'{expected_clear_status}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got clear status \'{data["clear_status"]}\'') - if data['miss_count'] != expected_miss_count: - raise Exception(f'Expected a miss count of \'{expected_miss_count}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got miss count \'{data["miss_count"]}\'') + if data["ex_score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\'' + ) + if data["clear_status"] != expected_clear_status: + raise Exception( + f'Expected a clear status of \'{expected_clear_status}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got clear status \'{data["clear_status"]}\'' + ) + if data["miss_count"] != expected_miss_count: + raise Exception( + f'Expected a miss count of \'{expected_miss_count}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got miss count \'{data["miss_count"]}\'' + ) # Verify we can fetch our own ghost - ex_score, ghost = self.verify_iidx24music_appoint(profile['extid'], score['id'], score['chart']) + ex_score, ghost = self.verify_iidx24music_appoint( + profile["extid"], score["id"], score["chart"] + ) if ex_score != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\'') + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\'' + ) if len(ghost) != 64: - raise Exception(f'Wrong ghost length {len(ghost)} for ghost!') + raise Exception(f"Wrong ghost length {len(ghost)} for ghost!") for g in ghost: if g != 0x01: - raise Exception(f'Got back wrong ghost data for song \'{score["id"]}\' chart \'{score["chart"]}\'') + raise Exception( + f'Got back wrong ghost data for song \'{score["id"]}\' chart \'{score["chart"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) # Verify that we can save/load expert points - self.verify_iidx24pc_save(profile['extid'], card, lid, {'course_id': 1, 'n_point': 0, 'h_point': 500, 'a_point': 0}) + self.verify_iidx24pc_save( + profile["extid"], + card, + lid, + {"course_id": 1, "n_point": 0, "h_point": 500, "a_point": 0}, + ) profile = self.verify_iidx24pc_get(ref_id, card, lid) - if sorted(profile['expert_point'].keys()) != [1]: - raise Exception('Got back wrong number of expert course points!') - if profile['expert_point'][1] != {'n_point': 0, 'h_point': 500, 'a_point': 0}: - raise Exception('Got back wrong expert points after saving!') - self.verify_iidx24pc_save(profile['extid'], card, lid, {'course_id': 1, 'n_point': 0, 'h_point': 1000, 'a_point': 0}) + if sorted(profile["expert_point"].keys()) != [1]: + raise Exception("Got back wrong number of expert course points!") + if profile["expert_point"][1] != { + "n_point": 0, + "h_point": 500, + "a_point": 0, + }: + raise Exception("Got back wrong expert points after saving!") + self.verify_iidx24pc_save( + profile["extid"], + card, + lid, + {"course_id": 1, "n_point": 0, "h_point": 1000, "a_point": 0}, + ) profile = self.verify_iidx24pc_get(ref_id, card, lid) - if sorted(profile['expert_point'].keys()) != [1]: - raise Exception('Got back wrong number of expert course points!') - if profile['expert_point'][1] != {'n_point': 0, 'h_point': 1000, 'a_point': 0}: - raise Exception('Got back wrong expert points after saving!') - self.verify_iidx24pc_save(profile['extid'], card, lid, {'course_id': 2, 'n_point': 0, 'h_point': 0, 'a_point': 500}) + if sorted(profile["expert_point"].keys()) != [1]: + raise Exception("Got back wrong number of expert course points!") + if profile["expert_point"][1] != { + "n_point": 0, + "h_point": 1000, + "a_point": 0, + }: + raise Exception("Got back wrong expert points after saving!") + self.verify_iidx24pc_save( + profile["extid"], + card, + lid, + {"course_id": 2, "n_point": 0, "h_point": 0, "a_point": 500}, + ) profile = self.verify_iidx24pc_get(ref_id, card, lid) - if sorted(profile['expert_point'].keys()) != [1, 2]: - raise Exception('Got back wrong number of expert course points!') - if profile['expert_point'][1] != {'n_point': 0, 'h_point': 1000, 'a_point': 0}: - raise Exception('Got back wrong expert points after saving!') - if profile['expert_point'][2] != {'n_point': 0, 'h_point': 0, 'a_point': 500}: - raise Exception('Got back wrong expert points after saving!') + if sorted(profile["expert_point"].keys()) != [1, 2]: + raise Exception("Got back wrong number of expert course points!") + if profile["expert_point"][1] != { + "n_point": 0, + "h_point": 1000, + "a_point": 0, + }: + raise Exception("Got back wrong expert points after saving!") + if profile["expert_point"][2] != { + "n_point": 0, + "h_point": 0, + "a_point": 500, + }: + raise Exception("Got back wrong expert points after saving!") # Verify that a player without a card can play self.verify_iidx24pc_playstart() - self.verify_iidx24music_play({ - 'id': 1000, - 'chart': 2, - 'clear_status': 4, - 'pgnum': 123, - 'gnum': 123, - }) - self.verify_iidx24pc_playend(lid, 'newname1') + self.verify_iidx24music_play( + { + "id": 1000, + "chart": 2, + "clear_status": 4, + "pgnum": 123, + "gnum": 123, + } + ) + self.verify_iidx24pc_playend(lid, "newname1") # Verify shop name change setting - self.verify_iidx24shop_savename(lid, 'newname1') + self.verify_iidx24shop_savename(lid, "newname1") newname = self.verify_iidx24shop_getname(lid) - if newname != 'newname1': - raise Exception('Invalid shop name returned after change!') - self.verify_iidx24shop_savename(lid, 'newname2') + if newname != "newname1": + raise Exception("Invalid shop name returned after change!") + self.verify_iidx24shop_savename(lid, "newname2") newname = self.verify_iidx24shop_getname(lid) - if newname != 'newname2': - raise Exception('Invalid shop name returned after change!') + if newname != "newname2": + raise Exception("Invalid shop name returned after change!") # Verify beginner score saving - self.verify_iidx24music_breg(profile['extid'], { - 'id': 1000, - 'clear_status': 4, - 'pgnum': 123, - 'gnum': 123, - }) - scores = self.verify_iidx24music_getrank(profile['extid']) + self.verify_iidx24music_breg( + profile["extid"], + { + "id": 1000, + "clear_status": 4, + "pgnum": 123, + "gnum": 123, + }, + ) + scores = self.verify_iidx24music_getrank(profile["extid"]) if 1000 not in scores: - raise Exception(f'Didn\'t get expected scores back for song {1000} beginner chart!') + raise Exception( + f"Didn't get expected scores back for song {1000} beginner chart!" + ) if 6 not in scores[1000]: - raise Exception(f'Didn\'t get beginner score back for song {1000}!') - if scores[1000][6] != {'clear_status': 4, 'ex_score': -1, 'miss_count': -1}: - raise Exception('Didn\'t get correct status back from beginner save!') + raise Exception(f"Didn't get beginner score back for song {1000}!") + if scores[1000][6] != {"clear_status": 4, "ex_score": -1, "miss_count": -1}: + raise Exception("Didn't get correct status back from beginner save!") # Verify DAN score saving and loading - self.verify_iidx24grade_raised(profile['extid'], newname, 'sp') - self.verify_iidx24grade_raised(profile['extid'], newname, 'dp') + self.verify_iidx24grade_raised(profile["extid"], newname, "sp") + self.verify_iidx24grade_raised(profile["extid"], newname, "dp") profile = self.verify_iidx24pc_get(ref_id, card, lid) - if profile['sp_dan'] != 5: - raise Exception('Got wrong DAN score back for SP!') - if profile['dp_dan'] != 5: - raise Exception('Got wrong DAN score back for DP!') + if profile["sp_dan"] != 5: + raise Exception("Got wrong DAN score back for SP!") + if profile["dp_dan"] != 5: + raise Exception("Got wrong DAN score back for DP!") # Verify secret course and internet ranking course saving - self.verify_iidx24ranking_entry(profile['extid'], newname, 'ir') - self.verify_iidx24ranking_entry(profile['extid'], newname, 'secret') - self.verify_iidx24ranking_classicentry(profile['extid']) + self.verify_iidx24ranking_entry(profile["extid"], newname, "ir") + self.verify_iidx24ranking_entry(profile["extid"], newname, "secret") + self.verify_iidx24ranking_classicentry(profile["extid"]) profile = self.verify_iidx24pc_get(ref_id, card, lid) - for ptype in ['ir_data', 'secret_course_data', 'classic_course_data']: - if profile[ptype] != {2: {1: {'clear_status': 4, 'pgnum': 1771, 'gnum': 967}}}: - raise Exception(f'Invalid data {profile[ptype]} returned on profile load for {ptype}!') + for ptype in ["ir_data", "secret_course_data", "classic_course_data"]: + if profile[ptype] != { + 2: {1: {"clear_status": 4, "pgnum": 1771, "gnum": 967}} + }: + raise Exception( + f"Invalid data {profile[ptype]} returned on profile load for {ptype}!" + ) else: print("Skipping score checks for existing card") diff --git a/bemani/client/iidx/spada.py b/bemani/client/iidx/spada.py index 2a3c724..b75536f 100644 --- a/bemani/client/iidx/spada.py +++ b/bemani/client/iidx/spada.py @@ -7,44 +7,44 @@ from bemani.protocol import Node class IIDXSpadaClient(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_iidx21shop_getname(self, lid: str) -> str: call = self.call_node() # Construct node - IIDX21shop = Node.void('IIDX21shop') + IIDX21shop = Node.void("IIDX21shop") call.add_child(IIDX21shop) - IIDX21shop.set_attribute('method', 'getname') - IIDX21shop.set_attribute('lid', lid) + IIDX21shop.set_attribute("method", "getname") + IIDX21shop.set_attribute("lid", lid) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX21shop/@opname") self.assert_path(resp, "response/IIDX21shop/@pid") self.assert_path(resp, "response/IIDX21shop/@cls_opt") - return resp.child('IIDX21shop').attribute('opname') + return resp.child("IIDX21shop").attribute("opname") def verify_iidx21shop_savename(self, lid: str, name: str) -> None: call = self.call_node() # Construct node - IIDX21shop = Node.void('IIDX21shop') - IIDX21shop.set_attribute('lid', lid) - IIDX21shop.set_attribute('pid', '51') - IIDX21shop.set_attribute('method', 'savename') - IIDX21shop.set_attribute('cls_opt', '0') - IIDX21shop.set_attribute('ccode', 'US') - IIDX21shop.set_attribute('opname', name) - IIDX21shop.set_attribute('rcode', '.') + IIDX21shop = Node.void("IIDX21shop") + IIDX21shop.set_attribute("lid", lid) + IIDX21shop.set_attribute("pid", "51") + IIDX21shop.set_attribute("method", "savename") + IIDX21shop.set_attribute("cls_opt", "0") + IIDX21shop.set_attribute("ccode", "US") + IIDX21shop.set_attribute("opname", name) + IIDX21shop.set_attribute("rcode", ".") call.add_child(IIDX21shop) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX21shop") @@ -53,12 +53,12 @@ class IIDXSpadaClient(BaseClient): call = self.call_node() # Construct node - IIDX21pc = Node.void('IIDX21pc') + IIDX21pc = Node.void("IIDX21pc") call.add_child(IIDX21pc) - IIDX21pc.set_attribute('method', 'common') + IIDX21pc.set_attribute("method", "common") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX21pc/ir/@beat") @@ -77,34 +77,36 @@ class IIDXSpadaClient(BaseClient): call = self.call_node() # Construct node - IIDX21pc = Node.void('IIDX21music') + IIDX21pc = Node.void("IIDX21music") call.add_child(IIDX21pc) - IIDX21pc.set_attribute('method', 'crate') + IIDX21pc.set_attribute("method", "crate") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX21music") for child in resp.child("IIDX21music").children: - if child.name != 'c': - raise Exception(f'Invalid node {child} in clear rate response!') + if child.name != "c": + raise Exception(f"Invalid node {child} in clear rate response!") if len(child.value) != 12: - raise Exception(f'Invalid node data {child} in clear rate response!') + raise Exception(f"Invalid node data {child} in clear rate response!") for v in child.value: if v < 0 or v > 101: - raise Exception(f'Invalid clear percent {child} in clear rate response!') + raise Exception( + f"Invalid clear percent {child} in clear rate response!" + ) def verify_iidx21shop_getconvention(self, lid: str) -> None: call = self.call_node() # Construct node - IIDX21pc = Node.void('IIDX21shop') + IIDX21pc = Node.void("IIDX21shop") call.add_child(IIDX21pc) - IIDX21pc.set_attribute('method', 'getconvention') - IIDX21pc.set_attribute('lid', lid) + IIDX21pc.set_attribute("method", "getconvention") + IIDX21pc.set_attribute("lid", lid) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX21shop/valid") @@ -117,15 +119,15 @@ class IIDXSpadaClient(BaseClient): call = self.call_node() # Construct node - IIDX21pc = Node.void('IIDX21pc') + IIDX21pc = Node.void("IIDX21pc") call.add_child(IIDX21pc) - IIDX21pc.set_attribute('iidxid', str(extid)) - IIDX21pc.set_attribute('lid', lid) - IIDX21pc.set_attribute('method', 'visit') - IIDX21pc.set_attribute('pid', '51') + IIDX21pc.set_attribute("iidxid", str(extid)) + IIDX21pc.set_attribute("lid", lid) + IIDX21pc.set_attribute("method", "visit") + IIDX21pc.set_attribute("pid", "51") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX21pc/@aflg") @@ -140,14 +142,14 @@ class IIDXSpadaClient(BaseClient): call = self.call_node() # Construct node - IIDX21pc = Node.void('IIDX21ranking') + IIDX21pc = Node.void("IIDX21ranking") call.add_child(IIDX21pc) - IIDX21pc.set_attribute('method', 'getranker') - IIDX21pc.set_attribute('lid', lid) - IIDX21pc.set_attribute('clid', str(clid)) + IIDX21pc.set_attribute("method", "getranker") + IIDX21pc.set_attribute("lid", lid) + IIDX21pc.set_attribute("clid", str(clid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX21ranking") @@ -156,37 +158,39 @@ class IIDXSpadaClient(BaseClient): call = self.call_node() # Construct node - IIDX21pc = Node.void('IIDX21shop') + IIDX21pc = Node.void("IIDX21shop") call.add_child(IIDX21pc) - IIDX21pc.set_attribute('method', 'sentinfo') - IIDX21pc.set_attribute('lid', lid) - IIDX21pc.set_attribute('bflg', '1') - IIDX21pc.set_attribute('bnum', '2') - IIDX21pc.set_attribute('ioid', '0') - IIDX21pc.set_attribute('tax_phase', '0') + IIDX21pc.set_attribute("method", "sentinfo") + IIDX21pc.set_attribute("lid", lid) + IIDX21pc.set_attribute("bflg", "1") + IIDX21pc.set_attribute("bnum", "2") + IIDX21pc.set_attribute("ioid", "0") + IIDX21pc.set_attribute("tax_phase", "0") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/IIDX21shop") - def verify_iidx21pc_get(self, ref_id: str, card_id: str, lid: str) -> Dict[str, Any]: + def verify_iidx21pc_get( + self, ref_id: str, card_id: str, lid: str + ) -> Dict[str, Any]: call = self.call_node() # Construct node - IIDX21pc = Node.void('IIDX21pc') + IIDX21pc = Node.void("IIDX21pc") call.add_child(IIDX21pc) - IIDX21pc.set_attribute('rid', ref_id) - IIDX21pc.set_attribute('did', ref_id) - IIDX21pc.set_attribute('pid', '51') - IIDX21pc.set_attribute('lid', lid) - IIDX21pc.set_attribute('cid', card_id) - IIDX21pc.set_attribute('method', 'get') - IIDX21pc.set_attribute('ctype', '1') + IIDX21pc.set_attribute("rid", ref_id) + IIDX21pc.set_attribute("did", ref_id) + IIDX21pc.set_attribute("pid", "51") + IIDX21pc.set_attribute("lid", lid) + IIDX21pc.set_attribute("cid", card_id) + IIDX21pc.set_attribute("method", "get") + IIDX21pc.set_attribute("ctype", "1") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that the response is correct self.assert_path(resp, "response/IIDX21pc/pcdata/@name") @@ -208,40 +212,44 @@ class IIDXSpadaClient(BaseClient): self.assert_path(resp, "response/IIDX21pc/favorite/dp_mlist") self.assert_path(resp, "response/IIDX21pc/favorite/dp_clist") - name = resp.child('IIDX21pc/pcdata').attribute('name') + name = resp.child("IIDX21pc/pcdata").attribute("name") if name != self.NAME: - raise Exception(f'Invalid name \'{name}\' returned for Ref ID \'{ref_id}\'') + raise Exception(f"Invalid name '{name}' returned for Ref ID '{ref_id}'") return { - 'extid': int(resp.child('IIDX21pc/pcdata').attribute('id')), - 'sp_dan': int(resp.child('IIDX21pc/grade').attribute('sgid')), - 'dp_dan': int(resp.child('IIDX21pc/grade').attribute('dgid')), - 'deller': int(resp.child('IIDX21pc/deller').attribute('deller')), + "extid": int(resp.child("IIDX21pc/pcdata").attribute("id")), + "sp_dan": int(resp.child("IIDX21pc/grade").attribute("sgid")), + "dp_dan": int(resp.child("IIDX21pc/grade").attribute("dgid")), + "deller": int(resp.child("IIDX21pc/deller").attribute("deller")), } - def verify_iidx21music_getrank(self, extid: int) -> Dict[int, Dict[int, Dict[str, int]]]: + def verify_iidx21music_getrank( + self, extid: int + ) -> Dict[int, Dict[int, Dict[str, int]]]: scores: Dict[int, Dict[int, Dict[str, int]]] = {} for cltype in [0, 1]: # singles, doubles call = self.call_node() # Construct node - IIDX21music = Node.void('IIDX21music') + IIDX21music = Node.void("IIDX21music") call.add_child(IIDX21music) - IIDX21music.set_attribute('method', 'getrank') - IIDX21music.set_attribute('iidxid', str(extid)) - IIDX21music.set_attribute('cltype', str(cltype)) + IIDX21music.set_attribute("method", "getrank") + IIDX21music.set_attribute("iidxid", str(extid)) + IIDX21music.set_attribute("cltype", str(cltype)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX21music/style") - if int(resp.child('IIDX21music/style').attribute('type')) != cltype: - raise Exception('Returned wrong clear type for IIDX21music.getrank!') + if int(resp.child("IIDX21music/style").attribute("type")) != cltype: + raise Exception("Returned wrong clear type for IIDX21music.getrank!") - for child in resp.child('IIDX21music').children: - if child.name == 'm': + for child in resp.child("IIDX21music").children: + if child.name == "m": if child.value[0] != -1: - raise Exception('Got non-self score back when requesting only our scores!') + raise Exception( + "Got non-self score back when requesting only our scores!" + ) music_id = child.value[1] normal_clear_status = child.value[2] @@ -267,28 +275,28 @@ class IIDXSpadaClient(BaseClient): scores[music_id] = {} scores[music_id][normal] = { - 'clear_status': normal_clear_status, - 'ex_score': normal_ex_score, - 'miss_count': normal_miss_count, + "clear_status": normal_clear_status, + "ex_score": normal_ex_score, + "miss_count": normal_miss_count, } scores[music_id][hyper] = { - 'clear_status': hyper_clear_status, - 'ex_score': hyper_ex_score, - 'miss_count': hyper_miss_count, + "clear_status": hyper_clear_status, + "ex_score": hyper_ex_score, + "miss_count": hyper_miss_count, } scores[music_id][another] = { - 'clear_status': another_clear_status, - 'ex_score': another_ex_score, - 'miss_count': another_miss_count, + "clear_status": another_clear_status, + "ex_score": another_ex_score, + "miss_count": another_miss_count, } - elif child.name == 'b': + elif child.name == "b": music_id = child.value[0] clear_status = child.value[1] scores[music_id][6] = { - 'clear_status': clear_status, - 'ex_score': -1, - 'miss_count': -1, + "clear_status": clear_status, + "ex_score": -1, + "miss_count": -1, } return scores @@ -297,159 +305,163 @@ class IIDXSpadaClient(BaseClient): call = self.call_node() # Construct node - IIDX21pc = Node.void('IIDX21pc') + IIDX21pc = Node.void("IIDX21pc") call.add_child(IIDX21pc) - IIDX21pc.set_attribute('s_disp_judge', '1') - IIDX21pc.set_attribute('mode', '6') - IIDX21pc.set_attribute('pmode', '0') - IIDX21pc.set_attribute('method', 'save') - IIDX21pc.set_attribute('s_sorttype', '0') - IIDX21pc.set_attribute('s_exscore', '0') - IIDX21pc.set_attribute('d_notes', '0.000000') - IIDX21pc.set_attribute('gpos', '0') - IIDX21pc.set_attribute('s_gno', '8') - IIDX21pc.set_attribute('s_hispeed', '5.771802') - IIDX21pc.set_attribute('s_judge', '0') - IIDX21pc.set_attribute('d_timing', '0') - IIDX21pc.set_attribute('rtype', '0') - IIDX21pc.set_attribute('d_largejudge', '0') - IIDX21pc.set_attribute('d_lift', '60') - IIDX21pc.set_attribute('s_pace', '0') - IIDX21pc.set_attribute('d_exscore', '0') - IIDX21pc.set_attribute('d_sdtype', '0') - IIDX21pc.set_attribute('s_opstyle', '1') - IIDX21pc.set_attribute('s_achi', '449') - IIDX21pc.set_attribute('s_largejudge', '0') - IIDX21pc.set_attribute('d_gno', '0') - IIDX21pc.set_attribute('s_lift', '60') - IIDX21pc.set_attribute('s_notes', '31.484070') - IIDX21pc.set_attribute('d_tune', '0') - IIDX21pc.set_attribute('d_sdlen', '0') - IIDX21pc.set_attribute('d_achi', '4') - IIDX21pc.set_attribute('d_opstyle', '0') - IIDX21pc.set_attribute('sp_opt', '8208') - IIDX21pc.set_attribute('iidxid', str(extid)) - IIDX21pc.set_attribute('lid', lid) - IIDX21pc.set_attribute('s_judgeAdj', '0') - IIDX21pc.set_attribute('s_tune', '3') - IIDX21pc.set_attribute('s_sdtype', '1') - IIDX21pc.set_attribute('s_gtype', '2') - IIDX21pc.set_attribute('d_judge', '0') - IIDX21pc.set_attribute('cid', card) - IIDX21pc.set_attribute('cltype', '0') - IIDX21pc.set_attribute('ctype', '1') - IIDX21pc.set_attribute('bookkeep', '0') - IIDX21pc.set_attribute('d_hispeed', '0.000000') - IIDX21pc.set_attribute('d_pace', '0') - IIDX21pc.set_attribute('d_judgeAdj', '0') - IIDX21pc.set_attribute('s_timing', '1') - IIDX21pc.set_attribute('d_disp_judge', '0') - IIDX21pc.set_attribute('s_sdlen', '121') - IIDX21pc.set_attribute('dp_opt2', '0') - IIDX21pc.set_attribute('d_gtype', '0') - IIDX21pc.set_attribute('d_sorttype', '0') - IIDX21pc.set_attribute('dp_opt', '0') - pyramid = Node.void('pyramid') + IIDX21pc.set_attribute("s_disp_judge", "1") + IIDX21pc.set_attribute("mode", "6") + IIDX21pc.set_attribute("pmode", "0") + IIDX21pc.set_attribute("method", "save") + IIDX21pc.set_attribute("s_sorttype", "0") + IIDX21pc.set_attribute("s_exscore", "0") + IIDX21pc.set_attribute("d_notes", "0.000000") + IIDX21pc.set_attribute("gpos", "0") + IIDX21pc.set_attribute("s_gno", "8") + IIDX21pc.set_attribute("s_hispeed", "5.771802") + IIDX21pc.set_attribute("s_judge", "0") + IIDX21pc.set_attribute("d_timing", "0") + IIDX21pc.set_attribute("rtype", "0") + IIDX21pc.set_attribute("d_largejudge", "0") + IIDX21pc.set_attribute("d_lift", "60") + IIDX21pc.set_attribute("s_pace", "0") + IIDX21pc.set_attribute("d_exscore", "0") + IIDX21pc.set_attribute("d_sdtype", "0") + IIDX21pc.set_attribute("s_opstyle", "1") + IIDX21pc.set_attribute("s_achi", "449") + IIDX21pc.set_attribute("s_largejudge", "0") + IIDX21pc.set_attribute("d_gno", "0") + IIDX21pc.set_attribute("s_lift", "60") + IIDX21pc.set_attribute("s_notes", "31.484070") + IIDX21pc.set_attribute("d_tune", "0") + IIDX21pc.set_attribute("d_sdlen", "0") + IIDX21pc.set_attribute("d_achi", "4") + IIDX21pc.set_attribute("d_opstyle", "0") + IIDX21pc.set_attribute("sp_opt", "8208") + IIDX21pc.set_attribute("iidxid", str(extid)) + IIDX21pc.set_attribute("lid", lid) + IIDX21pc.set_attribute("s_judgeAdj", "0") + IIDX21pc.set_attribute("s_tune", "3") + IIDX21pc.set_attribute("s_sdtype", "1") + IIDX21pc.set_attribute("s_gtype", "2") + IIDX21pc.set_attribute("d_judge", "0") + IIDX21pc.set_attribute("cid", card) + IIDX21pc.set_attribute("cltype", "0") + IIDX21pc.set_attribute("ctype", "1") + IIDX21pc.set_attribute("bookkeep", "0") + IIDX21pc.set_attribute("d_hispeed", "0.000000") + IIDX21pc.set_attribute("d_pace", "0") + IIDX21pc.set_attribute("d_judgeAdj", "0") + IIDX21pc.set_attribute("s_timing", "1") + IIDX21pc.set_attribute("d_disp_judge", "0") + IIDX21pc.set_attribute("s_sdlen", "121") + IIDX21pc.set_attribute("dp_opt2", "0") + IIDX21pc.set_attribute("d_gtype", "0") + IIDX21pc.set_attribute("d_sorttype", "0") + IIDX21pc.set_attribute("dp_opt", "0") + pyramid = Node.void("pyramid") IIDX21pc.add_child(pyramid) - pyramid.set_attribute('point', '290') - destiny_catharsis = Node.void('destiny_catharsis') + pyramid.set_attribute("point", "290") + destiny_catharsis = Node.void("destiny_catharsis") IIDX21pc.add_child(destiny_catharsis) - destiny_catharsis.set_attribute('point', '290') - bemani_summer_collabo = Node.void('bemani_summer_collabo') + destiny_catharsis.set_attribute("point", "290") + bemani_summer_collabo = Node.void("bemani_summer_collabo") IIDX21pc.add_child(bemani_summer_collabo) - bemani_summer_collabo.set_attribute('point', '290') - deller = Node.void('deller') + bemani_summer_collabo.set_attribute("point", "290") + deller = Node.void("deller") IIDX21pc.add_child(deller) - deller.set_attribute('deller', '150') + deller.set_attribute("deller", "150") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX21pc") - def verify_iidx21music_reg(self, extid: int, lid: str, score: Dict[str, Any]) -> None: + def verify_iidx21music_reg( + self, extid: int, lid: str, score: Dict[str, Any] + ) -> None: call = self.call_node() # Construct node - IIDX21music = Node.void('IIDX21music') + IIDX21music = Node.void("IIDX21music") call.add_child(IIDX21music) - IIDX21music.set_attribute('convid', '-1') - IIDX21music.set_attribute('iidxid', str(extid)) - IIDX21music.set_attribute('pgnum', str(score['pgnum'])) - IIDX21music.set_attribute('pid', '51') - IIDX21music.set_attribute('rankside', '1') - IIDX21music.set_attribute('cflg', str(score['clear_status'])) - IIDX21music.set_attribute('method', 'reg') - IIDX21music.set_attribute('gnum', str(score['gnum'])) - IIDX21music.set_attribute('clid', str(score['chart'])) - IIDX21music.set_attribute('mnum', str(score['mnum'])) - IIDX21music.set_attribute('is_death', '0') - IIDX21music.set_attribute('theory', '0') - IIDX21music.set_attribute('shopconvid', lid) - IIDX21music.set_attribute('mid', str(score['id'])) - IIDX21music.set_attribute('shopflg', '1') - IIDX21music.add_child(Node.binary('ghost', bytes([1] * 64))) + IIDX21music.set_attribute("convid", "-1") + IIDX21music.set_attribute("iidxid", str(extid)) + IIDX21music.set_attribute("pgnum", str(score["pgnum"])) + IIDX21music.set_attribute("pid", "51") + IIDX21music.set_attribute("rankside", "1") + IIDX21music.set_attribute("cflg", str(score["clear_status"])) + IIDX21music.set_attribute("method", "reg") + IIDX21music.set_attribute("gnum", str(score["gnum"])) + IIDX21music.set_attribute("clid", str(score["chart"])) + IIDX21music.set_attribute("mnum", str(score["mnum"])) + IIDX21music.set_attribute("is_death", "0") + IIDX21music.set_attribute("theory", "0") + IIDX21music.set_attribute("shopconvid", lid) + IIDX21music.set_attribute("mid", str(score["id"])) + IIDX21music.set_attribute("shopflg", "1") + IIDX21music.add_child(Node.binary("ghost", bytes([1] * 64))) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX21music/shopdata/@rank") self.assert_path(resp, "response/IIDX21music/ranklist/data") - def verify_iidx21music_appoint(self, extid: int, musicid: int, chart: int) -> Tuple[int, bytes]: + def verify_iidx21music_appoint( + self, extid: int, musicid: int, chart: int + ) -> Tuple[int, bytes]: call = self.call_node() # Construct node - IIDX21music = Node.void('IIDX21music') + IIDX21music = Node.void("IIDX21music") call.add_child(IIDX21music) - IIDX21music.set_attribute('clid', str(chart)) - IIDX21music.set_attribute('method', 'appoint') - IIDX21music.set_attribute('ctype', '0') - IIDX21music.set_attribute('iidxid', str(extid)) - IIDX21music.set_attribute('subtype', '') - IIDX21music.set_attribute('mid', str(musicid)) + IIDX21music.set_attribute("clid", str(chart)) + IIDX21music.set_attribute("method", "appoint") + IIDX21music.set_attribute("ctype", "0") + IIDX21music.set_attribute("iidxid", str(extid)) + IIDX21music.set_attribute("subtype", "") + IIDX21music.set_attribute("mid", str(musicid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/IIDX21music/mydata/@score") return ( - int(resp.child('IIDX21music/mydata').attribute('score')), - resp.child_value('IIDX21music/mydata'), + int(resp.child("IIDX21music/mydata").attribute("score")), + resp.child_value("IIDX21music/mydata"), ) def verify_iidx21pc_reg(self, ref_id: str, card_id: str, lid: str) -> int: call = self.call_node() # Construct node - IIDX21pc = Node.void('IIDX21pc') + IIDX21pc = Node.void("IIDX21pc") call.add_child(IIDX21pc) - IIDX21pc.set_attribute('lid', lid) - IIDX21pc.set_attribute('pid', '51') - IIDX21pc.set_attribute('method', 'reg') - IIDX21pc.set_attribute('cid', card_id) - IIDX21pc.set_attribute('did', ref_id) - IIDX21pc.set_attribute('rid', ref_id) - IIDX21pc.set_attribute('name', self.NAME) + IIDX21pc.set_attribute("lid", lid) + IIDX21pc.set_attribute("pid", "51") + IIDX21pc.set_attribute("method", "reg") + IIDX21pc.set_attribute("cid", card_id) + IIDX21pc.set_attribute("did", ref_id) + IIDX21pc.set_attribute("rid", ref_id) + IIDX21pc.set_attribute("name", self.NAME) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX21pc/@id") self.assert_path(resp, "response/IIDX21pc/@id_str") - return int(resp.child('IIDX21pc').attribute('id')) + return int(resp.child("IIDX21pc").attribute("id")) def verify_iidx21pc_playstart(self) -> None: call = self.call_node() # Construct node - IIDX21pc = Node.void('IIDX21pc') - IIDX21pc.set_attribute('method', 'playstart') - IIDX21pc.set_attribute('side', '1') + IIDX21pc = Node.void("IIDX21pc") + IIDX21pc.set_attribute("method", "playstart") + IIDX21pc.set_attribute("side", "1") call.add_child(IIDX21pc) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX21pc") @@ -458,19 +470,19 @@ class IIDXSpadaClient(BaseClient): call = self.call_node() # Construct node - IIDX21music = Node.void('IIDX21music') - IIDX21music.set_attribute('opt', '64') - IIDX21music.set_attribute('clid', str(score['chart'])) - IIDX21music.set_attribute('mid', str(score['id'])) - IIDX21music.set_attribute('gnum', str(score['gnum'])) - IIDX21music.set_attribute('cflg', str(score['clear_status'])) - IIDX21music.set_attribute('pgnum', str(score['pgnum'])) - IIDX21music.set_attribute('pid', '51') - IIDX21music.set_attribute('method', 'play') + IIDX21music = Node.void("IIDX21music") + IIDX21music.set_attribute("opt", "64") + IIDX21music.set_attribute("clid", str(score["chart"])) + IIDX21music.set_attribute("mid", str(score["id"])) + IIDX21music.set_attribute("gnum", str(score["gnum"])) + IIDX21music.set_attribute("cflg", str(score["clear_status"])) + IIDX21music.set_attribute("pgnum", str(score["pgnum"])) + IIDX21music.set_attribute("pid", "51") + IIDX21music.set_attribute("method", "play") call.add_child(IIDX21music) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX21music/@clid") @@ -482,15 +494,15 @@ class IIDXSpadaClient(BaseClient): call = self.call_node() # Construct node - IIDX21pc = Node.void('IIDX21pc') - IIDX21pc.set_attribute('cltype', '0') - IIDX21pc.set_attribute('bookkeep', '0') - IIDX21pc.set_attribute('mode', '1') - IIDX21pc.set_attribute('method', 'playend') + IIDX21pc = Node.void("IIDX21pc") + IIDX21pc.set_attribute("cltype", "0") + IIDX21pc.set_attribute("bookkeep", "0") + IIDX21pc.set_attribute("mode", "1") + IIDX21pc.set_attribute("method", "playend") call.add_child(IIDX21pc) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX21pc") @@ -499,41 +511,43 @@ class IIDXSpadaClient(BaseClient): call = self.call_node() # Construct node - IIDX21music = Node.void('IIDX21music') - IIDX21music.set_attribute('gnum', str(score['gnum'])) - IIDX21music.set_attribute('iidxid', str(iidxid)) - IIDX21music.set_attribute('mid', str(score['id'])) - IIDX21music.set_attribute('method', 'breg') - IIDX21music.set_attribute('pgnum', str(score['pgnum'])) - IIDX21music.set_attribute('cflg', str(score['clear_status'])) + IIDX21music = Node.void("IIDX21music") + IIDX21music.set_attribute("gnum", str(score["gnum"])) + IIDX21music.set_attribute("iidxid", str(iidxid)) + IIDX21music.set_attribute("mid", str(score["id"])) + IIDX21music.set_attribute("method", "breg") + IIDX21music.set_attribute("pgnum", str(score["pgnum"])) + IIDX21music.set_attribute("cflg", str(score["clear_status"])) call.add_child(IIDX21music) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX21music") - def verify_iidx21grade_raised(self, iidxid: int, shop_name: str, dantype: str) -> None: + def verify_iidx21grade_raised( + self, iidxid: int, shop_name: str, dantype: str + ) -> None: call = self.call_node() # Construct node - IIDX21grade = Node.void('IIDX21grade') - IIDX21grade.set_attribute('opname', shop_name) - IIDX21grade.set_attribute('is_mirror', '0') - IIDX21grade.set_attribute('oppid', '51') - IIDX21grade.set_attribute('achi', '50') - IIDX21grade.set_attribute('cflg', '4' if dantype == 'sp' else '3') - IIDX21grade.set_attribute('gid', '5') - IIDX21grade.set_attribute('iidxid', str(iidxid)) - IIDX21grade.set_attribute('gtype', '0' if dantype == 'sp' else '1') - IIDX21grade.set_attribute('is_ex', '0') - IIDX21grade.set_attribute('pside', '0') - IIDX21grade.set_attribute('method', 'raised') + IIDX21grade = Node.void("IIDX21grade") + IIDX21grade.set_attribute("opname", shop_name) + IIDX21grade.set_attribute("is_mirror", "0") + IIDX21grade.set_attribute("oppid", "51") + IIDX21grade.set_attribute("achi", "50") + IIDX21grade.set_attribute("cflg", "4" if dantype == "sp" else "3") + IIDX21grade.set_attribute("gid", "5") + IIDX21grade.set_attribute("iidxid", str(iidxid)) + IIDX21grade.set_attribute("gtype", "0" if dantype == "sp" else "1") + IIDX21grade.set_attribute("is_ex", "0") + IIDX21grade.set_attribute("pside", "0") + IIDX21grade.set_attribute("method", "raised") call.add_child(IIDX21grade) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/IIDX21grade/@pnum") @@ -542,20 +556,20 @@ class IIDXSpadaClient(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -578,190 +592,221 @@ class IIDXSpadaClient(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") self.verify_iidx21pc_reg(ref_id, card, lid) self.verify_iidx21pc_get(ref_id, card, lid) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") if cardid is None: # Verify score handling profile = self.verify_iidx21pc_get(ref_id, card, lid) - if profile['sp_dan'] != -1: - raise Exception('Somehow has SP DAN ranking on new profile!') - if profile['dp_dan'] != -1: - raise Exception('Somehow has DP DAN ranking on new profile!') - if profile['deller'] != 0: - raise Exception('Somehow has deller on new profile!') - scores = self.verify_iidx21music_getrank(profile['extid']) + if profile["sp_dan"] != -1: + raise Exception("Somehow has SP DAN ranking on new profile!") + if profile["dp_dan"] != -1: + raise Exception("Somehow has DP DAN ranking on new profile!") + if profile["deller"] != 0: + raise Exception("Somehow has deller on new profile!") + scores = self.verify_iidx21music_getrank(profile["extid"]) if len(scores.keys()) > 0: - raise Exception('Somehow have scores on a new profile!') + raise Exception("Somehow have scores on a new profile!") for phase in [1, 2]: if phase == 1: dummyscores = [ # An okay score on a chart { - 'id': 1000, - 'chart': 2, - 'clear_status': 4, - 'pgnum': 123, - 'gnum': 123, - 'mnum': 5, + "id": 1000, + "chart": 2, + "clear_status": 4, + "pgnum": 123, + "gnum": 123, + "mnum": 5, }, # A good score on an easier chart of the same song { - 'id': 1000, - 'chart': 0, - 'clear_status': 7, - 'pgnum': 246, - 'gnum': 0, - 'mnum': 0, + "id": 1000, + "chart": 0, + "clear_status": 7, + "pgnum": 246, + "gnum": 0, + "mnum": 0, }, # A bad score on a hard chart { - 'id': 1003, - 'chart': 2, - 'clear_status': 1, - 'pgnum': 10, - 'gnum': 20, - 'mnum': 50, + "id": 1003, + "chart": 2, + "clear_status": 1, + "pgnum": 10, + "gnum": 20, + "mnum": 50, }, # A terrible score on an easy chart { - 'id': 1003, - 'chart': 0, - 'clear_status': 1, - 'pgnum': 2, - 'gnum': 5, - 'mnum': 75, + "id": 1003, + "chart": 0, + "clear_status": 1, + "pgnum": 2, + "gnum": 5, + "mnum": 75, }, ] if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 1000, - 'chart': 2, - 'clear_status': 5, - 'pgnum': 234, - 'gnum': 234, - 'mnum': 3, + "id": 1000, + "chart": 2, + "clear_status": 5, + "pgnum": 234, + "gnum": 234, + "mnum": 3, }, # A worse score on another same chart { - 'id': 1000, - 'chart': 0, - 'clear_status': 4, - 'pgnum': 123, - 'gnum': 123, - 'mnum': 35, - 'expected_clear_status': 7, - 'expected_ex_score': 492, - 'expected_miss_count': 0, + "id": 1000, + "chart": 0, + "clear_status": 4, + "pgnum": 123, + "gnum": 123, + "mnum": 35, + "expected_clear_status": 7, + "expected_ex_score": 492, + "expected_miss_count": 0, }, ] for dummyscore in dummyscores: - self.verify_iidx21music_reg(profile['extid'], lid, dummyscore) - self.verify_iidx21pc_visit(profile['extid'], lid) - self.verify_iidx21pc_save(profile['extid'], card, lid) - scores = self.verify_iidx21music_getrank(profile['extid']) + self.verify_iidx21music_reg(profile["extid"], lid, dummyscore) + self.verify_iidx21pc_visit(profile["extid"], lid) + self.verify_iidx21pc_save(profile["extid"], card, lid) + scores = self.verify_iidx21music_getrank(profile["extid"]) for score in dummyscores: - data = scores.get(score['id'], {}).get(score['chart'], None) + data = scores.get(score["id"], {}).get(score["chart"], None) if data is None: - raise Exception(f'Expected to get score back for song {score["id"]} chart {score["chart"]}!') + raise Exception( + f'Expected to get score back for song {score["id"]} chart {score["chart"]}!' + ) - if 'expected_ex_score' in score: - expected_score = score['expected_ex_score'] + if "expected_ex_score" in score: + expected_score = score["expected_ex_score"] else: - expected_score = (score['pgnum'] * 2) + score['gnum'] - if 'expected_clear_status' in score: - expected_clear_status = score['expected_clear_status'] + expected_score = (score["pgnum"] * 2) + score["gnum"] + if "expected_clear_status" in score: + expected_clear_status = score["expected_clear_status"] else: - expected_clear_status = score['clear_status'] - if 'expected_miss_count' in score: - expected_miss_count = score['expected_miss_count'] + expected_clear_status = score["clear_status"] + if "expected_miss_count" in score: + expected_miss_count = score["expected_miss_count"] else: - expected_miss_count = score['mnum'] + expected_miss_count = score["mnum"] - if data['ex_score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\'') - if data['clear_status'] != expected_clear_status: - raise Exception(f'Expected a clear status of \'{expected_clear_status}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got clear status \'{data["clear_status"]}\'') - if data['miss_count'] != expected_miss_count: - raise Exception(f'Expected a miss count of \'{expected_miss_count}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got miss count \'{data["miss_count"]}\'') + if data["ex_score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\'' + ) + if data["clear_status"] != expected_clear_status: + raise Exception( + f'Expected a clear status of \'{expected_clear_status}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got clear status \'{data["clear_status"]}\'' + ) + if data["miss_count"] != expected_miss_count: + raise Exception( + f'Expected a miss count of \'{expected_miss_count}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got miss count \'{data["miss_count"]}\'' + ) # Verify we can fetch our own ghost - ex_score, ghost = self.verify_iidx21music_appoint(profile['extid'], score['id'], score['chart']) + ex_score, ghost = self.verify_iidx21music_appoint( + profile["extid"], score["id"], score["chart"] + ) if ex_score != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\'') + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\'' + ) if len(ghost) != 64: - raise Exception(f'Wrong ghost length {len(ghost)} for ghost!') + raise Exception(f"Wrong ghost length {len(ghost)} for ghost!") for g in ghost: if g != 0x01: - raise Exception(f'Got back wrong ghost data for song \'{score["id"]}\' chart \'{score["chart"]}\'') + raise Exception( + f'Got back wrong ghost data for song \'{score["id"]}\' chart \'{score["chart"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) # Verify that a player without a card can play self.verify_iidx21pc_playstart() - self.verify_iidx21music_play({ - 'id': 1000, - 'chart': 2, - 'clear_status': 4, - 'pgnum': 123, - 'gnum': 123, - }) + self.verify_iidx21music_play( + { + "id": 1000, + "chart": 2, + "clear_status": 4, + "pgnum": 123, + "gnum": 123, + } + ) self.verify_iidx21pc_playend() # Verify shop name change setting - self.verify_iidx21shop_savename(lid, 'newname1') + self.verify_iidx21shop_savename(lid, "newname1") newname = self.verify_iidx21shop_getname(lid) - if newname != 'newname1': - raise Exception('Invalid shop name returned after change!') - self.verify_iidx21shop_savename(lid, 'newname2') + if newname != "newname1": + raise Exception("Invalid shop name returned after change!") + self.verify_iidx21shop_savename(lid, "newname2") newname = self.verify_iidx21shop_getname(lid) - if newname != 'newname2': - raise Exception('Invalid shop name returned after change!') + if newname != "newname2": + raise Exception("Invalid shop name returned after change!") # Verify beginner score saving - self.verify_iidx21music_breg(profile['extid'], { - 'id': 1000, - 'clear_status': 4, - 'pgnum': 123, - 'gnum': 123, - }) - scores = self.verify_iidx21music_getrank(profile['extid']) + self.verify_iidx21music_breg( + profile["extid"], + { + "id": 1000, + "clear_status": 4, + "pgnum": 123, + "gnum": 123, + }, + ) + scores = self.verify_iidx21music_getrank(profile["extid"]) if 1000 not in scores: - raise Exception(f'Didn\'t get expected scores back for song {1000} beginner chart!') + raise Exception( + f"Didn't get expected scores back for song {1000} beginner chart!" + ) if 6 not in scores[1000]: - raise Exception(f'Didn\'t get beginner score back for song {1000}!') - if scores[1000][6] != {'clear_status': 4, 'ex_score': -1, 'miss_count': -1}: - raise Exception('Didn\'t get correct status back from beginner save!') + raise Exception(f"Didn't get beginner score back for song {1000}!") + if scores[1000][6] != {"clear_status": 4, "ex_score": -1, "miss_count": -1}: + raise Exception("Didn't get correct status back from beginner save!") # Verify DAN score saving and loading - self.verify_iidx21grade_raised(profile['extid'], newname, 'sp') - self.verify_iidx21grade_raised(profile['extid'], newname, 'dp') + self.verify_iidx21grade_raised(profile["extid"], newname, "sp") + self.verify_iidx21grade_raised(profile["extid"], newname, "dp") profile = self.verify_iidx21pc_get(ref_id, card, lid) - if profile['sp_dan'] != 5: - raise Exception('Got wrong DAN score back for SP!') - if profile['dp_dan'] != 5: - raise Exception('Got wrong DAN score back for DP!') + if profile["sp_dan"] != 5: + raise Exception("Got wrong DAN score back for SP!") + if profile["dp_dan"] != 5: + raise Exception("Got wrong DAN score back for DP!") else: print("Skipping score checks for existing card") diff --git a/bemani/client/iidx/tricoro.py b/bemani/client/iidx/tricoro.py index e2ac191..2895334 100644 --- a/bemani/client/iidx/tricoro.py +++ b/bemani/client/iidx/tricoro.py @@ -7,44 +7,44 @@ from bemani.protocol import Node class IIDXTricoroClient(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_shop_getname(self, lid: str) -> str: call = self.call_node() # Construct node - IIDX21shop = Node.void('shop') + IIDX21shop = Node.void("shop") call.add_child(IIDX21shop) - IIDX21shop.set_attribute('method', 'getname') - IIDX21shop.set_attribute('lid', lid) + IIDX21shop.set_attribute("method", "getname") + IIDX21shop.set_attribute("lid", lid) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/shop/@opname") self.assert_path(resp, "response/shop/@pid") self.assert_path(resp, "response/shop/@cls_opt") - return resp.child('shop').attribute('opname') + return resp.child("shop").attribute("opname") def verify_shop_savename(self, lid: str, name: str) -> None: call = self.call_node() # Construct node - IIDX21shop = Node.void('shop') - IIDX21shop.set_attribute('lid', lid) - IIDX21shop.set_attribute('pid', '51') - IIDX21shop.set_attribute('method', 'savename') - IIDX21shop.set_attribute('cls_opt', '0') - IIDX21shop.set_attribute('ccode', 'US') - IIDX21shop.set_attribute('opname', name) - IIDX21shop.set_attribute('rcode', '.') + IIDX21shop = Node.void("shop") + IIDX21shop.set_attribute("lid", lid) + IIDX21shop.set_attribute("pid", "51") + IIDX21shop.set_attribute("method", "savename") + IIDX21shop.set_attribute("cls_opt", "0") + IIDX21shop.set_attribute("ccode", "US") + IIDX21shop.set_attribute("opname", name) + IIDX21shop.set_attribute("rcode", ".") call.add_child(IIDX21shop) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/shop") @@ -53,12 +53,12 @@ class IIDXTricoroClient(BaseClient): call = self.call_node() # Construct node - IIDX21pc = Node.void('pc') + IIDX21pc = Node.void("pc") call.add_child(IIDX21pc) - IIDX21pc.set_attribute('method', 'common') + IIDX21pc.set_attribute("method", "common") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/pc/ir/@beat") @@ -74,34 +74,36 @@ class IIDXTricoroClient(BaseClient): call = self.call_node() # Construct node - IIDX21pc = Node.void('music') + IIDX21pc = Node.void("music") call.add_child(IIDX21pc) - IIDX21pc.set_attribute('method', 'crate') + IIDX21pc.set_attribute("method", "crate") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/music") for child in resp.child("music").children: - if child.name != 'c': - raise Exception(f'Invalid node {child} in clear rate response!') + if child.name != "c": + raise Exception(f"Invalid node {child} in clear rate response!") if len(child.value) != 12: - raise Exception(f'Invalid node data {child} in clear rate response!') + raise Exception(f"Invalid node data {child} in clear rate response!") for v in child.value: if v < 0 or v > 101: - raise Exception(f'Invalid clear percent {child} in clear rate response!') + raise Exception( + f"Invalid clear percent {child} in clear rate response!" + ) def verify_shop_getconvention(self, lid: str) -> None: call = self.call_node() # Construct node - IIDX21pc = Node.void('shop') + IIDX21pc = Node.void("shop") call.add_child(IIDX21pc) - IIDX21pc.set_attribute('method', 'getconvention') - IIDX21pc.set_attribute('lid', lid) + IIDX21pc.set_attribute("method", "getconvention") + IIDX21pc.set_attribute("lid", lid) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/shop/valid") @@ -114,15 +116,15 @@ class IIDXTricoroClient(BaseClient): call = self.call_node() # Construct node - IIDX21pc = Node.void('pc') + IIDX21pc = Node.void("pc") call.add_child(IIDX21pc) - IIDX21pc.set_attribute('iidxid', str(extid)) - IIDX21pc.set_attribute('lid', lid) - IIDX21pc.set_attribute('method', 'visit') - IIDX21pc.set_attribute('pid', '51') + IIDX21pc.set_attribute("iidxid", str(extid)) + IIDX21pc.set_attribute("lid", lid) + IIDX21pc.set_attribute("method", "visit") + IIDX21pc.set_attribute("pid", "51") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/pc/@aflg") @@ -137,14 +139,14 @@ class IIDXTricoroClient(BaseClient): call = self.call_node() # Construct node - IIDX21pc = Node.void('ranking') + IIDX21pc = Node.void("ranking") call.add_child(IIDX21pc) - IIDX21pc.set_attribute('method', 'getranker') - IIDX21pc.set_attribute('lid', lid) - IIDX21pc.set_attribute('clid', str(clid)) + IIDX21pc.set_attribute("method", "getranker") + IIDX21pc.set_attribute("lid", lid) + IIDX21pc.set_attribute("clid", str(clid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/ranking") @@ -153,17 +155,17 @@ class IIDXTricoroClient(BaseClient): call = self.call_node() # Construct node - IIDX21pc = Node.void('shop') + IIDX21pc = Node.void("shop") call.add_child(IIDX21pc) - IIDX21pc.set_attribute('method', 'sentinfo') - IIDX21pc.set_attribute('lid', lid) - IIDX21pc.set_attribute('bflg', '1') - IIDX21pc.set_attribute('bnum', '2') - IIDX21pc.set_attribute('ioid', '0') - IIDX21pc.set_attribute('tax_phase', '0') + IIDX21pc.set_attribute("method", "sentinfo") + IIDX21pc.set_attribute("lid", lid) + IIDX21pc.set_attribute("bflg", "1") + IIDX21pc.set_attribute("bnum", "2") + IIDX21pc.set_attribute("ioid", "0") + IIDX21pc.set_attribute("tax_phase", "0") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/shop") @@ -172,18 +174,18 @@ class IIDXTricoroClient(BaseClient): call = self.call_node() # Construct node - IIDX21pc = Node.void('pc') + IIDX21pc = Node.void("pc") call.add_child(IIDX21pc) - IIDX21pc.set_attribute('rid', ref_id) - IIDX21pc.set_attribute('did', ref_id) - IIDX21pc.set_attribute('pid', '51') - IIDX21pc.set_attribute('lid', lid) - IIDX21pc.set_attribute('cid', card_id) - IIDX21pc.set_attribute('method', 'get') - IIDX21pc.set_attribute('ctype', '1') + IIDX21pc.set_attribute("rid", ref_id) + IIDX21pc.set_attribute("did", ref_id) + IIDX21pc.set_attribute("pid", "51") + IIDX21pc.set_attribute("lid", lid) + IIDX21pc.set_attribute("cid", card_id) + IIDX21pc.set_attribute("method", "get") + IIDX21pc.set_attribute("ctype", "1") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that the response is correct self.assert_path(resp, "response/pc/pcdata/@name") @@ -203,15 +205,15 @@ class IIDXTricoroClient(BaseClient): self.assert_path(resp, "response/pc/rlist") self.assert_path(resp, "response/pc/step") - name = resp.child('pc/pcdata').attribute('name') + name = resp.child("pc/pcdata").attribute("name") if name != self.NAME: - raise Exception(f'Invalid name \'{name}\' returned for Ref ID \'{ref_id}\'') + raise Exception(f"Invalid name '{name}' returned for Ref ID '{ref_id}'") return { - 'extid': int(resp.child('pc/pcdata').attribute('id')), - 'sp_dan': int(resp.child('pc/grade').attribute('sgid')), - 'dp_dan': int(resp.child('pc/grade').attribute('dgid')), - 'deller': int(resp.child('pc/commonboss').attribute('deller')), + "extid": int(resp.child("pc/pcdata").attribute("id")), + "sp_dan": int(resp.child("pc/grade").attribute("sgid")), + "dp_dan": int(resp.child("pc/grade").attribute("dgid")), + "deller": int(resp.child("pc/commonboss").attribute("deller")), } def verify_music_getrank(self, extid: int) -> Dict[int, Dict[int, Dict[str, int]]]: @@ -220,23 +222,25 @@ class IIDXTricoroClient(BaseClient): call = self.call_node() # Construct node - IIDX21music = Node.void('music') + IIDX21music = Node.void("music") call.add_child(IIDX21music) - IIDX21music.set_attribute('method', 'getrank') - IIDX21music.set_attribute('iidxid', str(extid)) - IIDX21music.set_attribute('cltype', str(cltype)) + IIDX21music.set_attribute("method", "getrank") + IIDX21music.set_attribute("iidxid", str(extid)) + IIDX21music.set_attribute("cltype", str(cltype)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/music/style") - if int(resp.child('music/style').attribute('type')) != cltype: - raise Exception('Returned wrong clear type for IIDX21music.getrank!') + if int(resp.child("music/style").attribute("type")) != cltype: + raise Exception("Returned wrong clear type for IIDX21music.getrank!") - for child in resp.child('music').children: - if child.name == 'm': + for child in resp.child("music").children: + if child.name == "m": if child.value[0] != -1: - raise Exception('Got non-self score back when requesting only our scores!') + raise Exception( + "Got non-self score back when requesting only our scores!" + ) music_id = child.value[1] normal_clear_status = child.value[2] @@ -262,28 +266,28 @@ class IIDXTricoroClient(BaseClient): scores[music_id] = {} scores[music_id][normal] = { - 'clear_status': normal_clear_status, - 'ex_score': normal_ex_score, - 'miss_count': normal_miss_count, + "clear_status": normal_clear_status, + "ex_score": normal_ex_score, + "miss_count": normal_miss_count, } scores[music_id][hyper] = { - 'clear_status': hyper_clear_status, - 'ex_score': hyper_ex_score, - 'miss_count': hyper_miss_count, + "clear_status": hyper_clear_status, + "ex_score": hyper_ex_score, + "miss_count": hyper_miss_count, } scores[music_id][another] = { - 'clear_status': another_clear_status, - 'ex_score': another_ex_score, - 'miss_count': another_miss_count, + "clear_status": another_clear_status, + "ex_score": another_ex_score, + "miss_count": another_miss_count, } - elif child.name == 'b': + elif child.name == "b": music_id = child.value[0] clear_status = child.value[1] scores[music_id][6] = { - 'clear_status': clear_status, - 'ex_score': -1, - 'miss_count': -1, + "clear_status": clear_status, + "ex_score": -1, + "miss_count": -1, } return scores @@ -292,134 +296,136 @@ class IIDXTricoroClient(BaseClient): call = self.call_node() # Construct node - IIDX21pc = Node.void('pc') + IIDX21pc = Node.void("pc") call.add_child(IIDX21pc) - IIDX21pc.set_attribute('achi', '449') - IIDX21pc.set_attribute('opt', '8208') - IIDX21pc.set_attribute('gpos', '0') - IIDX21pc.set_attribute('gno', '8') - IIDX21pc.set_attribute('timing', '0') - IIDX21pc.set_attribute('help', '0') - IIDX21pc.set_attribute('sdhd', '0') - IIDX21pc.set_attribute('sdtype', '0') - IIDX21pc.set_attribute('notes', '31.484070') - IIDX21pc.set_attribute('pase', '0') - IIDX21pc.set_attribute('judge', '0') - IIDX21pc.set_attribute('opstyle', '1') - IIDX21pc.set_attribute('hispeed', '5.771802') - IIDX21pc.set_attribute('mode', '6') - IIDX21pc.set_attribute('pmode', '0') - IIDX21pc.set_attribute('lift', '60') - IIDX21pc.set_attribute('judgeAdj', '0') + IIDX21pc.set_attribute("achi", "449") + IIDX21pc.set_attribute("opt", "8208") + IIDX21pc.set_attribute("gpos", "0") + IIDX21pc.set_attribute("gno", "8") + IIDX21pc.set_attribute("timing", "0") + IIDX21pc.set_attribute("help", "0") + IIDX21pc.set_attribute("sdhd", "0") + IIDX21pc.set_attribute("sdtype", "0") + IIDX21pc.set_attribute("notes", "31.484070") + IIDX21pc.set_attribute("pase", "0") + IIDX21pc.set_attribute("judge", "0") + IIDX21pc.set_attribute("opstyle", "1") + IIDX21pc.set_attribute("hispeed", "5.771802") + IIDX21pc.set_attribute("mode", "6") + IIDX21pc.set_attribute("pmode", "0") + IIDX21pc.set_attribute("lift", "60") + IIDX21pc.set_attribute("judgeAdj", "0") - IIDX21pc.set_attribute('method', 'save') - IIDX21pc.set_attribute('iidxid', str(extid)) - IIDX21pc.set_attribute('lid', lid) - IIDX21pc.set_attribute('cid', card) - IIDX21pc.set_attribute('cltype', '0') - IIDX21pc.set_attribute('ctype', '1') + IIDX21pc.set_attribute("method", "save") + IIDX21pc.set_attribute("iidxid", str(extid)) + IIDX21pc.set_attribute("lid", lid) + IIDX21pc.set_attribute("cid", card) + IIDX21pc.set_attribute("cltype", "0") + IIDX21pc.set_attribute("ctype", "1") - pyramid = Node.void('pyramid') + pyramid = Node.void("pyramid") IIDX21pc.add_child(pyramid) - pyramid.set_attribute('point', '290') - destiny_catharsis = Node.void('destiny_catharsis') + pyramid.set_attribute("point", "290") + destiny_catharsis = Node.void("destiny_catharsis") IIDX21pc.add_child(destiny_catharsis) - destiny_catharsis.set_attribute('point', '290') - bemani_summer_collabo = Node.void('bemani_summer_collabo') + destiny_catharsis.set_attribute("point", "290") + bemani_summer_collabo = Node.void("bemani_summer_collabo") IIDX21pc.add_child(bemani_summer_collabo) - bemani_summer_collabo.set_attribute('point', '290') - deller = Node.void('deller') + bemani_summer_collabo.set_attribute("point", "290") + deller = Node.void("deller") IIDX21pc.add_child(deller) - deller.set_attribute('deller', '150') + deller.set_attribute("deller", "150") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/pc") def verify_music_reg(self, extid: int, lid: str, score: Dict[str, Any]) -> None: call = self.call_node() # Construct node - IIDX21music = Node.void('music') + IIDX21music = Node.void("music") call.add_child(IIDX21music) - IIDX21music.set_attribute('convid', '-1') - IIDX21music.set_attribute('iidxid', str(extid)) - IIDX21music.set_attribute('pgnum', str(score['pgnum'])) - IIDX21music.set_attribute('pid', '51') - IIDX21music.set_attribute('rankside', '1') - IIDX21music.set_attribute('cflg', str(score['clear_status'])) - IIDX21music.set_attribute('method', 'reg') - IIDX21music.set_attribute('gnum', str(score['gnum'])) - IIDX21music.set_attribute('clid', str(score['chart'])) - IIDX21music.set_attribute('mnum', str(score['mnum'])) - IIDX21music.set_attribute('is_death', '0') - IIDX21music.set_attribute('theory', '0') - IIDX21music.set_attribute('shopconvid', lid) - IIDX21music.set_attribute('mid', str(score['id'])) - IIDX21music.set_attribute('shopflg', '1') - IIDX21music.add_child(Node.binary('ghost', bytes([1] * 64))) + IIDX21music.set_attribute("convid", "-1") + IIDX21music.set_attribute("iidxid", str(extid)) + IIDX21music.set_attribute("pgnum", str(score["pgnum"])) + IIDX21music.set_attribute("pid", "51") + IIDX21music.set_attribute("rankside", "1") + IIDX21music.set_attribute("cflg", str(score["clear_status"])) + IIDX21music.set_attribute("method", "reg") + IIDX21music.set_attribute("gnum", str(score["gnum"])) + IIDX21music.set_attribute("clid", str(score["chart"])) + IIDX21music.set_attribute("mnum", str(score["mnum"])) + IIDX21music.set_attribute("is_death", "0") + IIDX21music.set_attribute("theory", "0") + IIDX21music.set_attribute("shopconvid", lid) + IIDX21music.set_attribute("mid", str(score["id"])) + IIDX21music.set_attribute("shopflg", "1") + IIDX21music.add_child(Node.binary("ghost", bytes([1] * 64))) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/music/shopdata/@rank") self.assert_path(resp, "response/music/ranklist/data") - def verify_music_appoint(self, extid: int, musicid: int, chart: int) -> Tuple[int, bytes]: + def verify_music_appoint( + self, extid: int, musicid: int, chart: int + ) -> Tuple[int, bytes]: call = self.call_node() # Construct node - IIDX21music = Node.void('music') + IIDX21music = Node.void("music") call.add_child(IIDX21music) - IIDX21music.set_attribute('clid', str(chart)) - IIDX21music.set_attribute('method', 'appoint') - IIDX21music.set_attribute('ctype', '0') - IIDX21music.set_attribute('iidxid', str(extid)) - IIDX21music.set_attribute('subtype', '') - IIDX21music.set_attribute('mid', str(musicid)) + IIDX21music.set_attribute("clid", str(chart)) + IIDX21music.set_attribute("method", "appoint") + IIDX21music.set_attribute("ctype", "0") + IIDX21music.set_attribute("iidxid", str(extid)) + IIDX21music.set_attribute("subtype", "") + IIDX21music.set_attribute("mid", str(musicid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/music/mydata/@score") return ( - int(resp.child('music/mydata').attribute('score')), - resp.child_value('music/mydata'), + int(resp.child("music/mydata").attribute("score")), + resp.child_value("music/mydata"), ) def verify_pc_reg(self, ref_id: str, card_id: str, lid: str) -> int: call = self.call_node() # Construct node - IIDX21pc = Node.void('pc') + IIDX21pc = Node.void("pc") call.add_child(IIDX21pc) - IIDX21pc.set_attribute('lid', lid) - IIDX21pc.set_attribute('pid', '51') - IIDX21pc.set_attribute('method', 'reg') - IIDX21pc.set_attribute('cid', card_id) - IIDX21pc.set_attribute('did', ref_id) - IIDX21pc.set_attribute('rid', ref_id) - IIDX21pc.set_attribute('name', self.NAME) + IIDX21pc.set_attribute("lid", lid) + IIDX21pc.set_attribute("pid", "51") + IIDX21pc.set_attribute("method", "reg") + IIDX21pc.set_attribute("cid", card_id) + IIDX21pc.set_attribute("did", ref_id) + IIDX21pc.set_attribute("rid", ref_id) + IIDX21pc.set_attribute("name", self.NAME) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/pc/@id") self.assert_path(resp, "response/pc/@id_str") - return int(resp.child('pc').attribute('id')) + return int(resp.child("pc").attribute("id")) def verify_pc_playstart(self) -> None: call = self.call_node() # Construct node - IIDX21pc = Node.void('pc') - IIDX21pc.set_attribute('method', 'playstart') - IIDX21pc.set_attribute('side', '1') + IIDX21pc = Node.void("pc") + IIDX21pc.set_attribute("method", "playstart") + IIDX21pc.set_attribute("side", "1") call.add_child(IIDX21pc) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/pc") @@ -428,19 +434,19 @@ class IIDXTricoroClient(BaseClient): call = self.call_node() # Construct node - IIDX21music = Node.void('music') - IIDX21music.set_attribute('opt', '64') - IIDX21music.set_attribute('clid', str(score['chart'])) - IIDX21music.set_attribute('mid', str(score['id'])) - IIDX21music.set_attribute('gnum', str(score['gnum'])) - IIDX21music.set_attribute('cflg', str(score['clear_status'])) - IIDX21music.set_attribute('pgnum', str(score['pgnum'])) - IIDX21music.set_attribute('pid', '51') - IIDX21music.set_attribute('method', 'play') + IIDX21music = Node.void("music") + IIDX21music.set_attribute("opt", "64") + IIDX21music.set_attribute("clid", str(score["chart"])) + IIDX21music.set_attribute("mid", str(score["id"])) + IIDX21music.set_attribute("gnum", str(score["gnum"])) + IIDX21music.set_attribute("cflg", str(score["clear_status"])) + IIDX21music.set_attribute("pgnum", str(score["pgnum"])) + IIDX21music.set_attribute("pid", "51") + IIDX21music.set_attribute("method", "play") call.add_child(IIDX21music) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/music/@clid") @@ -452,15 +458,15 @@ class IIDXTricoroClient(BaseClient): call = self.call_node() # Construct node - IIDX21pc = Node.void('pc') - IIDX21pc.set_attribute('cltype', '0') - IIDX21pc.set_attribute('bookkeep', '0') - IIDX21pc.set_attribute('mode', '1') - IIDX21pc.set_attribute('method', 'playend') + IIDX21pc = Node.void("pc") + IIDX21pc.set_attribute("cltype", "0") + IIDX21pc.set_attribute("bookkeep", "0") + IIDX21pc.set_attribute("mode", "1") + IIDX21pc.set_attribute("method", "playend") call.add_child(IIDX21pc) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/pc") @@ -469,17 +475,17 @@ class IIDXTricoroClient(BaseClient): call = self.call_node() # Construct node - IIDX21music = Node.void('music') - IIDX21music.set_attribute('gnum', str(score['gnum'])) - IIDX21music.set_attribute('iidxid', str(iidxid)) - IIDX21music.set_attribute('mid', str(score['id'])) - IIDX21music.set_attribute('method', 'breg') - IIDX21music.set_attribute('pgnum', str(score['pgnum'])) - IIDX21music.set_attribute('cflg', str(score['clear_status'])) + IIDX21music = Node.void("music") + IIDX21music.set_attribute("gnum", str(score["gnum"])) + IIDX21music.set_attribute("iidxid", str(iidxid)) + IIDX21music.set_attribute("mid", str(score["id"])) + IIDX21music.set_attribute("method", "breg") + IIDX21music.set_attribute("pgnum", str(score["pgnum"])) + IIDX21music.set_attribute("cflg", str(score["clear_status"])) call.add_child(IIDX21music) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/music") @@ -488,22 +494,22 @@ class IIDXTricoroClient(BaseClient): call = self.call_node() # Construct node - IIDX21grade = Node.void('grade') - IIDX21grade.set_attribute('opname', shop_name) - IIDX21grade.set_attribute('is_mirror', '0') - IIDX21grade.set_attribute('oppid', '51') - IIDX21grade.set_attribute('achi', '50') - IIDX21grade.set_attribute('cflg', '4' if dantype == 'sp' else '3') - IIDX21grade.set_attribute('gid', '5') - IIDX21grade.set_attribute('iidxid', str(iidxid)) - IIDX21grade.set_attribute('gtype', '0' if dantype == 'sp' else '1') - IIDX21grade.set_attribute('is_ex', '0') - IIDX21grade.set_attribute('pside', '0') - IIDX21grade.set_attribute('method', 'raised') + IIDX21grade = Node.void("grade") + IIDX21grade.set_attribute("opname", shop_name) + IIDX21grade.set_attribute("is_mirror", "0") + IIDX21grade.set_attribute("oppid", "51") + IIDX21grade.set_attribute("achi", "50") + IIDX21grade.set_attribute("cflg", "4" if dantype == "sp" else "3") + IIDX21grade.set_attribute("gid", "5") + IIDX21grade.set_attribute("iidxid", str(iidxid)) + IIDX21grade.set_attribute("gtype", "0" if dantype == "sp" else "1") + IIDX21grade.set_attribute("is_ex", "0") + IIDX21grade.set_attribute("pside", "0") + IIDX21grade.set_attribute("method", "raised") call.add_child(IIDX21grade) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/grade/@pnum") @@ -512,20 +518,20 @@ class IIDXTricoroClient(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -548,190 +554,221 @@ class IIDXTricoroClient(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") self.verify_pc_reg(ref_id, card, lid) self.verify_pc_get(ref_id, card, lid) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") if cardid is None: # Verify score handling profile = self.verify_pc_get(ref_id, card, lid) - if profile['sp_dan'] != -1: - raise Exception('Somehow has SP DAN ranking on new profile!') - if profile['dp_dan'] != -1: - raise Exception('Somehow has DP DAN ranking on new profile!') - if profile['deller'] != 0: - raise Exception('Somehow has deller on new profile!') - scores = self.verify_music_getrank(profile['extid']) + if profile["sp_dan"] != -1: + raise Exception("Somehow has SP DAN ranking on new profile!") + if profile["dp_dan"] != -1: + raise Exception("Somehow has DP DAN ranking on new profile!") + if profile["deller"] != 0: + raise Exception("Somehow has deller on new profile!") + scores = self.verify_music_getrank(profile["extid"]) if len(scores.keys()) > 0: - raise Exception('Somehow have scores on a new profile!') + raise Exception("Somehow have scores on a new profile!") for phase in [1, 2]: if phase == 1: dummyscores = [ # An okay score on a chart { - 'id': 1000, - 'chart': 2, - 'clear_status': 4, - 'pgnum': 123, - 'gnum': 123, - 'mnum': 5, + "id": 1000, + "chart": 2, + "clear_status": 4, + "pgnum": 123, + "gnum": 123, + "mnum": 5, }, # A good score on an easier chart of the same song { - 'id': 1000, - 'chart': 0, - 'clear_status': 7, - 'pgnum': 246, - 'gnum': 0, - 'mnum': 0, + "id": 1000, + "chart": 0, + "clear_status": 7, + "pgnum": 246, + "gnum": 0, + "mnum": 0, }, # A bad score on a hard chart { - 'id': 1003, - 'chart': 2, - 'clear_status': 1, - 'pgnum': 10, - 'gnum': 20, - 'mnum': 50, + "id": 1003, + "chart": 2, + "clear_status": 1, + "pgnum": 10, + "gnum": 20, + "mnum": 50, }, # A terrible score on an easy chart { - 'id': 1003, - 'chart': 0, - 'clear_status': 1, - 'pgnum': 2, - 'gnum': 5, - 'mnum': 75, + "id": 1003, + "chart": 0, + "clear_status": 1, + "pgnum": 2, + "gnum": 5, + "mnum": 75, }, ] if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 1000, - 'chart': 2, - 'clear_status': 5, - 'pgnum': 234, - 'gnum': 234, - 'mnum': 3, + "id": 1000, + "chart": 2, + "clear_status": 5, + "pgnum": 234, + "gnum": 234, + "mnum": 3, }, # A worse score on another same chart { - 'id': 1000, - 'chart': 0, - 'clear_status': 4, - 'pgnum': 123, - 'gnum': 123, - 'mnum': 35, - 'expected_clear_status': 7, - 'expected_ex_score': 492, - 'expected_miss_count': 0, + "id": 1000, + "chart": 0, + "clear_status": 4, + "pgnum": 123, + "gnum": 123, + "mnum": 35, + "expected_clear_status": 7, + "expected_ex_score": 492, + "expected_miss_count": 0, }, ] for dummyscore in dummyscores: - self.verify_music_reg(profile['extid'], lid, dummyscore) - self.verify_pc_visit(profile['extid'], lid) - self.verify_pc_save(profile['extid'], card, lid) - scores = self.verify_music_getrank(profile['extid']) + self.verify_music_reg(profile["extid"], lid, dummyscore) + self.verify_pc_visit(profile["extid"], lid) + self.verify_pc_save(profile["extid"], card, lid) + scores = self.verify_music_getrank(profile["extid"]) for score in dummyscores: - data = scores.get(score['id'], {}).get(score['chart'], None) + data = scores.get(score["id"], {}).get(score["chart"], None) if data is None: - raise Exception(f'Expected to get score back for song {score["id"]} chart {score["chart"]}!') + raise Exception( + f'Expected to get score back for song {score["id"]} chart {score["chart"]}!' + ) - if 'expected_ex_score' in score: - expected_score = score['expected_ex_score'] + if "expected_ex_score" in score: + expected_score = score["expected_ex_score"] else: - expected_score = (score['pgnum'] * 2) + score['gnum'] - if 'expected_clear_status' in score: - expected_clear_status = score['expected_clear_status'] + expected_score = (score["pgnum"] * 2) + score["gnum"] + if "expected_clear_status" in score: + expected_clear_status = score["expected_clear_status"] else: - expected_clear_status = score['clear_status'] - if 'expected_miss_count' in score: - expected_miss_count = score['expected_miss_count'] + expected_clear_status = score["clear_status"] + if "expected_miss_count" in score: + expected_miss_count = score["expected_miss_count"] else: - expected_miss_count = score['mnum'] + expected_miss_count = score["mnum"] - if data['ex_score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\'') - if data['clear_status'] != expected_clear_status: - raise Exception(f'Expected a clear status of \'{expected_clear_status}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got clear status \'{data["clear_status"]}\'') - if data['miss_count'] != expected_miss_count: - raise Exception(f'Expected a miss count of \'{expected_miss_count}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got miss count \'{data["miss_count"]}\'') + if data["ex_score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\'' + ) + if data["clear_status"] != expected_clear_status: + raise Exception( + f'Expected a clear status of \'{expected_clear_status}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got clear status \'{data["clear_status"]}\'' + ) + if data["miss_count"] != expected_miss_count: + raise Exception( + f'Expected a miss count of \'{expected_miss_count}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got miss count \'{data["miss_count"]}\'' + ) # Verify we can fetch our own ghost - ex_score, ghost = self.verify_music_appoint(profile['extid'], score['id'], score['chart']) + ex_score, ghost = self.verify_music_appoint( + profile["extid"], score["id"], score["chart"] + ) if ex_score != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\'') + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\'' + ) if len(ghost) != 64: - raise Exception(f'Wrong ghost length {len(ghost)} for ghost!') + raise Exception(f"Wrong ghost length {len(ghost)} for ghost!") for g in ghost: if g != 0x01: - raise Exception(f'Got back wrong ghost data for song \'{score["id"]}\' chart \'{score["chart"]}\'') + raise Exception( + f'Got back wrong ghost data for song \'{score["id"]}\' chart \'{score["chart"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) # Verify that a player without a card can play self.verify_pc_playstart() - self.verify_music_play({ - 'id': 1000, - 'chart': 2, - 'clear_status': 4, - 'pgnum': 123, - 'gnum': 123, - }) + self.verify_music_play( + { + "id": 1000, + "chart": 2, + "clear_status": 4, + "pgnum": 123, + "gnum": 123, + } + ) self.verify_pc_playend() # Verify shop name change setting - self.verify_shop_savename(lid, 'newname1') + self.verify_shop_savename(lid, "newname1") newname = self.verify_shop_getname(lid) - if newname != 'newname1': - raise Exception('Invalid shop name returned after change!') - self.verify_shop_savename(lid, 'newname2') + if newname != "newname1": + raise Exception("Invalid shop name returned after change!") + self.verify_shop_savename(lid, "newname2") newname = self.verify_shop_getname(lid) - if newname != 'newname2': - raise Exception('Invalid shop name returned after change!') + if newname != "newname2": + raise Exception("Invalid shop name returned after change!") # Verify beginner score saving - self.verify_music_breg(profile['extid'], { - 'id': 1000, - 'clear_status': 4, - 'pgnum': 123, - 'gnum': 123, - }) - scores = self.verify_music_getrank(profile['extid']) + self.verify_music_breg( + profile["extid"], + { + "id": 1000, + "clear_status": 4, + "pgnum": 123, + "gnum": 123, + }, + ) + scores = self.verify_music_getrank(profile["extid"]) if 1000 not in scores: - raise Exception(f'Didn\'t get expected scores back for song {1000} beginner chart!') + raise Exception( + f"Didn't get expected scores back for song {1000} beginner chart!" + ) if 6 not in scores[1000]: - raise Exception(f'Didn\'t get beginner score back for song {1000}!') - if scores[1000][6] != {'clear_status': 4, 'ex_score': -1, 'miss_count': -1}: - raise Exception('Didn\'t get correct status back from beginner save!') + raise Exception(f"Didn't get beginner score back for song {1000}!") + if scores[1000][6] != {"clear_status": 4, "ex_score": -1, "miss_count": -1}: + raise Exception("Didn't get correct status back from beginner save!") # Verify DAN score saving and loading - self.verify_grade_raised(profile['extid'], newname, 'sp') - self.verify_grade_raised(profile['extid'], newname, 'dp') + self.verify_grade_raised(profile["extid"], newname, "sp") + self.verify_grade_raised(profile["extid"], newname, "dp") profile = self.verify_pc_get(ref_id, card, lid) - if profile['sp_dan'] != 5: - raise Exception('Got wrong DAN score back for SP!') - if profile['dp_dan'] != 5: - raise Exception('Got wrong DAN score back for DP!') + if profile["sp_dan"] != 5: + raise Exception("Got wrong DAN score back for SP!") + if profile["dp_dan"] != 5: + raise Exception("Got wrong DAN score back for DP!") else: print("Skipping score checks for existing card") diff --git a/bemani/client/jubeat/clan.py b/bemani/client/jubeat/clan.py index 8629374..e16cae8 100644 --- a/bemani/client/jubeat/clan.py +++ b/bemani/client/jubeat/clan.py @@ -8,29 +8,29 @@ from bemani.protocol import Node class JubeatClanClient(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_shopinfo_regist(self) -> None: call = self.call_node() # Construct node - shopinfo = Node.void('shopinfo') - shopinfo.set_attribute('method', 'regist') + shopinfo = Node.void("shopinfo") + shopinfo.set_attribute("method", "regist") call.add_child(shopinfo) - shop = Node.void('shop') + shop = Node.void("shop") shopinfo.add_child(shop) - shop.add_child(Node.string('name', '')) - shop.add_child(Node.string('pref', 'JP-14')) - shop.add_child(Node.string('softwareid', '')) - shop.add_child(Node.string('systemid', self.pcbid)) - shop.add_child(Node.string('hardwareid', '01020304050607080900')) - shop.add_child(Node.string('locationid', 'US-1')) - shop.add_child(Node.string('monitor', 'D26L155 6252 151')) - testmode = Node.void('testmode') + shop.add_child(Node.string("name", "")) + shop.add_child(Node.string("pref", "JP-14")) + shop.add_child(Node.string("softwareid", "")) + shop.add_child(Node.string("systemid", self.pcbid)) + shop.add_child(Node.string("hardwareid", "01020304050607080900")) + shop.add_child(Node.string("locationid", "US-1")) + shop.add_child(Node.string("monitor", "D26L155 6252 151")) + testmode = Node.void("testmode") shop.add_child(testmode) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/shopinfo/data/cabid") @@ -53,12 +53,24 @@ class JubeatClanClient(BaseClient): self.assert_path(resp, "response/shopinfo/data/info/born/year") self.assert_path(resp, "response/shopinfo/data/info/collection/rating_s") self.assert_path(resp, "response/shopinfo/data/info/expert_option/is_available") - self.assert_path(resp, "response/shopinfo/data/info/all_music_matching/is_available") - self.assert_path(resp, "response/shopinfo/data/info/all_music_matching/team/default_flag") - self.assert_path(resp, "response/shopinfo/data/info/all_music_matching/team/redbelk_flag") - self.assert_path(resp, "response/shopinfo/data/info/all_music_matching/team/cyanttle_flag") - self.assert_path(resp, "response/shopinfo/data/info/all_music_matching/team/greenesia_flag") - self.assert_path(resp, "response/shopinfo/data/info/all_music_matching/team/plumpark_flag") + self.assert_path( + resp, "response/shopinfo/data/info/all_music_matching/is_available" + ) + self.assert_path( + resp, "response/shopinfo/data/info/all_music_matching/team/default_flag" + ) + self.assert_path( + resp, "response/shopinfo/data/info/all_music_matching/team/redbelk_flag" + ) + self.assert_path( + resp, "response/shopinfo/data/info/all_music_matching/team/cyanttle_flag" + ) + self.assert_path( + resp, "response/shopinfo/data/info/all_music_matching/team/greenesia_flag" + ) + self.assert_path( + resp, "response/shopinfo/data/info/all_music_matching/team/plumpark_flag" + ) self.assert_path(resp, "response/shopinfo/data/info/question_list") self.assert_path(resp, "response/shopinfo/data/info/drop_list") self.assert_path(resp, "response/shopinfo/data/info/daily_bonus_list") @@ -68,15 +80,15 @@ class JubeatClanClient(BaseClient): call = self.call_node() # Construct node - demodata = Node.void('demodata') + demodata = Node.void("demodata") call.add_child(demodata) - demodata.set_attribute('method', 'get_info') - pcbinfo = Node.void('pcbinfo') + demodata.set_attribute("method", "get_info") + pcbinfo = Node.void("pcbinfo") demodata.add_child(pcbinfo) - pcbinfo.set_attribute('client_data_version', '0') + pcbinfo.set_attribute("client_data_version", "0") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/demodata/data/info/black_jacket_list") @@ -85,12 +97,12 @@ class JubeatClanClient(BaseClient): call = self.call_node() # Construct node - demodata = Node.void('demodata') + demodata = Node.void("demodata") call.add_child(demodata) - demodata.set_attribute('method', 'get_news') + demodata.set_attribute("method", "get_news") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/demodata/data/officialnews") @@ -99,12 +111,12 @@ class JubeatClanClient(BaseClient): call = self.call_node() # Construct node - demodata = Node.void('demodata') + demodata = Node.void("demodata") call.add_child(demodata) - demodata.set_attribute('method', 'get_jbox_list') + demodata.set_attribute("method", "get_jbox_list") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/demodata/@status") @@ -126,74 +138,86 @@ class JubeatClanClient(BaseClient): self.assert_path(resp, "response/gametop/data/info/born/year") self.assert_path(resp, "response/gametop/data/info/collection/rating_s") self.assert_path(resp, "response/gametop/data/info/expert_option/is_available") - self.assert_path(resp, "response/gametop/data/info/all_music_matching/is_available") - self.assert_path(resp, "response/gametop/data/info/all_music_matching/team/default_flag") - self.assert_path(resp, "response/gametop/data/info/all_music_matching/team/redbelk_flag") - self.assert_path(resp, "response/gametop/data/info/all_music_matching/team/cyanttle_flag") - self.assert_path(resp, "response/gametop/data/info/all_music_matching/team/greenesia_flag") - self.assert_path(resp, "response/gametop/data/info/all_music_matching/team/plumpark_flag") + self.assert_path( + resp, "response/gametop/data/info/all_music_matching/is_available" + ) + self.assert_path( + resp, "response/gametop/data/info/all_music_matching/team/default_flag" + ) + self.assert_path( + resp, "response/gametop/data/info/all_music_matching/team/redbelk_flag" + ) + self.assert_path( + resp, "response/gametop/data/info/all_music_matching/team/cyanttle_flag" + ) + self.assert_path( + resp, "response/gametop/data/info/all_music_matching/team/greenesia_flag" + ) + self.assert_path( + resp, "response/gametop/data/info/all_music_matching/team/plumpark_flag" + ) self.assert_path(resp, "response/gametop/data/info/question_list") self.assert_path(resp, "response/gametop/data/info/drop_list") self.assert_path(resp, "response/gametop/data/info/daily_bonus_list") self.assert_path(resp, "response/gametop/data/info/department/pack_list") for item in [ - 'tune_cnt', - 'save_cnt', - 'saved_cnt', - 'fc_cnt', - 'ex_cnt', - 'clear_cnt', - 'match_cnt', - 'beat_cnt', - 'mynews_cnt', - 'bonus_tune_points', - 'is_bonus_tune_played', - 'inherit', - 'mtg_entry_cnt', - 'mtg_hold_cnt', - 'mtg_result', + "tune_cnt", + "save_cnt", + "saved_cnt", + "fc_cnt", + "ex_cnt", + "clear_cnt", + "match_cnt", + "beat_cnt", + "mynews_cnt", + "bonus_tune_points", + "is_bonus_tune_played", + "inherit", + "mtg_entry_cnt", + "mtg_hold_cnt", + "mtg_result", ]: self.assert_path(resp, f"response/gametop/data/player/info/{item}") for item in [ - 'music_list', - 'secret_list', - 'theme_list', - 'marker_list', - 'title_list', - 'parts_list', - 'emblem_list', - 'commu_list', - 'new/secret_list', - 'new/theme_list', - 'new/marker_list', + "music_list", + "secret_list", + "theme_list", + "marker_list", + "title_list", + "parts_list", + "emblem_list", + "commu_list", + "new/secret_list", + "new/theme_list", + "new/marker_list", ]: self.assert_path(resp, f"response/gametop/data/player/item/{item}") for item in [ - 'play_time', - 'shopname', - 'areaname', - 'music_id', - 'seq_id', - 'sort', - 'category', - 'expert_option', + "play_time", + "shopname", + "areaname", + "music_id", + "seq_id", + "sort", + "category", + "expert_option", ]: self.assert_path(resp, f"response/gametop/data/player/last/{item}") for item in [ - 'marker', - 'theme', - 'title', - 'parts', - 'rank_sort', - 'combo_disp', - 'emblem', - 'matching', - 'hard', - 'hazard', + "marker", + "theme", + "title", + "parts", + "rank_sort", + "combo_disp", + "emblem", + "matching", + "hard", + "hazard", ]: self.assert_path(resp, f"response/gametop/data/player/last/settings/{item}") @@ -210,13 +234,19 @@ class JubeatClanClient(BaseClient): self.assert_path(resp, "response/gametop/data/player/lab_edit_seq") self.assert_path(resp, "response/gametop/data/player/event_info") self.assert_path(resp, "response/gametop/data/player/navi/flag") - self.assert_path(resp, "response/gametop/data/player/fc_challenge/today/music_id") + self.assert_path( + resp, "response/gametop/data/player/fc_challenge/today/music_id" + ) self.assert_path(resp, "response/gametop/data/player/fc_challenge/today/state") - self.assert_path(resp, "response/gametop/data/player/fc_challenge/whim/music_id") + self.assert_path( + resp, "response/gametop/data/player/fc_challenge/whim/music_id" + ) self.assert_path(resp, "response/gametop/data/player/fc_challenge/whim/state") self.assert_path(resp, "response/gametop/data/player/official_news/news_list") self.assert_path(resp, "response/gametop/data/player/rivallist") - self.assert_path(resp, "response/gametop/data/player/free_first_play/is_available") + self.assert_path( + resp, "response/gametop/data/player/free_first_play/is_available" + ) self.assert_path(resp, "response/gametop/data/player/jbox/point") self.assert_path(resp, "response/gametop/data/player/jbox/emblem/normal/index") self.assert_path(resp, "response/gametop/data/player/jbox/emblem/premium/index") @@ -225,7 +255,9 @@ class JubeatClanClient(BaseClient): self.assert_path(resp, "response/gametop/data/player/born/status") self.assert_path(resp, "response/gametop/data/player/question_list") self.assert_path(resp, "response/gametop/data/player/jubility/@param") - self.assert_path(resp, "response/gametop/data/player/jubility/target_music_list") + self.assert_path( + resp, "response/gametop/data/player/jubility/target_music_list" + ) self.assert_path(resp, "response/gametop/data/player/team/@id") self.assert_path(resp, "response/gametop/data/player/team/section") self.assert_path(resp, "response/gametop/data/player/team/street") @@ -245,17 +277,29 @@ class JubeatClanClient(BaseClient): self.assert_path(resp, "response/gametop/data/player/drop_list/drop/@id") self.assert_path(resp, "response/gametop/data/player/drop_list/drop/exp") self.assert_path(resp, "response/gametop/data/player/drop_list/drop/flag") - self.assert_path(resp, "response/gametop/data/player/drop_list/drop/item_list/item/@id") - self.assert_path(resp, "response/gametop/data/player/drop_list/drop/item_list/item/num") - self.assert_path(resp, "response/gametop/data/player/fill_in_category/no_gray_flag_list") - self.assert_path(resp, "response/gametop/data/player/fill_in_category/all_yellow_flag_list") - self.assert_path(resp, "response/gametop/data/player/fill_in_category/full_combo_flag_list") - self.assert_path(resp, "response/gametop/data/player/fill_in_category/excellent_flag_list") + self.assert_path( + resp, "response/gametop/data/player/drop_list/drop/item_list/item/@id" + ) + self.assert_path( + resp, "response/gametop/data/player/drop_list/drop/item_list/item/num" + ) + self.assert_path( + resp, "response/gametop/data/player/fill_in_category/no_gray_flag_list" + ) + self.assert_path( + resp, "response/gametop/data/player/fill_in_category/all_yellow_flag_list" + ) + self.assert_path( + resp, "response/gametop/data/player/fill_in_category/full_combo_flag_list" + ) + self.assert_path( + resp, "response/gametop/data/player/fill_in_category/excellent_flag_list" + ) self.assert_path(resp, "response/gametop/data/player/daily_bonus_list") self.assert_path(resp, "response/gametop/data/player/ticket_list") # Return the jid - return resp.child_value('gametop/data/player/jid') + return resp.child_value("gametop/data/player/jid") def verify_gameend_regist( self, @@ -266,55 +310,91 @@ class JubeatClanClient(BaseClient): call = self.call_node() # Construct node - gameend = Node.void('gameend') + gameend = Node.void("gameend") call.add_child(gameend) - gameend.set_attribute('method', 'regist') - gameend.add_child(Node.s32('retry', 0)) - pcbinfo = Node.void('pcbinfo') + gameend.set_attribute("method", "regist") + gameend.add_child(Node.s32("retry", 0)) + pcbinfo = Node.void("pcbinfo") gameend.add_child(pcbinfo) - pcbinfo.set_attribute('client_data_version', '0') - data = Node.void('data') + pcbinfo.set_attribute("client_data_version", "0") + data = Node.void("data") gameend.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.string('refid', ref_id)) - player.add_child(Node.s32('jid', jid)) - player.add_child(Node.string('name', self.NAME)) - result = Node.void('result') + player.add_child(Node.string("refid", ref_id)) + player.add_child(Node.s32("jid", jid)) + player.add_child(Node.string("name", self.NAME)) + result = Node.void("result") data.add_child(result) - result.set_attribute('count', str(len(scores))) + result.set_attribute("count", str(len(scores))) # Send scores scoreid = 0 for score in scores: # Always played bits = 0x1 - if score['clear']: + if score["clear"]: bits |= 0x2 - if score['fc']: + if score["fc"]: bits |= 0x4 - if score['ex']: + if score["ex"]: bits |= 0x8 # Intentionally starting at 1 because that's what the game does scoreid = scoreid + 1 - tune = Node.void('tune') + tune = Node.void("tune") result.add_child(tune) - tune.set_attribute('id', str(scoreid)) - tune.add_child(Node.s32('music', score['id'])) - tune.add_child(Node.s64('timestamp', Time.now() * 1000)) - player_1 = Node.void('player') + tune.set_attribute("id", str(scoreid)) + tune.add_child(Node.s32("music", score["id"])) + tune.add_child(Node.s64("timestamp", Time.now() * 1000)) + player_1 = Node.void("player") tune.add_child(player_1) - player_1.set_attribute('rank', '1') - scorenode = Node.s32('score', score['score']) + player_1.set_attribute("rank", "1") + scorenode = Node.s32("score", score["score"]) player_1.add_child(scorenode) - scorenode.set_attribute('seq', str(score['chart'])) - scorenode.set_attribute('clear', str(bits)) - scorenode.set_attribute('combo', '69') - player_1.add_child(Node.u8_array('mbar', [239, 175, 170, 170, 190, 234, 187, 158, 153, 230, 170, 90, 102, 170, 85, 150, 150, 102, 85, 234, 171, 169, 157, 150, 170, 101, 230, 90, 214, 255])) + scorenode.set_attribute("seq", str(score["chart"])) + scorenode.set_attribute("clear", str(bits)) + scorenode.set_attribute("combo", "69") + player_1.add_child( + Node.u8_array( + "mbar", + [ + 239, + 175, + 170, + 170, + 190, + 234, + 187, + 158, + 153, + 230, + 170, + 90, + 102, + 170, + 85, + 150, + 150, + 102, + 85, + 234, + 171, + 169, + 157, + 150, + 170, + 101, + 230, + 90, + 214, + 255, + ], + ) + ) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/gameend/data/player/session_id") def verify_gameend_final( @@ -325,50 +405,50 @@ class JubeatClanClient(BaseClient): call = self.call_node() # Construct node - gameend = Node.void('gameend') + gameend = Node.void("gameend") call.add_child(gameend) - gameend.set_attribute('method', 'final') - gameend.add_child(Node.s32('retry', 0)) - pcbinfo = Node.void('pcbinfo') + gameend.set_attribute("method", "final") + gameend.add_child(Node.s32("retry", 0)) + pcbinfo = Node.void("pcbinfo") gameend.add_child(pcbinfo) - pcbinfo.set_attribute('client_data_version', '0') - data = Node.void('data') + pcbinfo.set_attribute("client_data_version", "0") + data = Node.void("data") gameend.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.string('refid', ref_id)) - player.add_child(Node.s32('jid', jid)) - jbox = Node.void('jbox') + player.add_child(Node.string("refid", ref_id)) + player.add_child(Node.s32("jid", jid)) + jbox = Node.void("jbox") player.add_child(jbox) - jbox.add_child(Node.s32('point', 0)) - emblem = Node.void('emblem') + jbox.add_child(Node.s32("point", 0)) + emblem = Node.void("emblem") jbox.add_child(emblem) - emblem.add_child(Node.u8('type', 0)) - emblem.add_child(Node.s16('index', 0)) + emblem.add_child(Node.u8("type", 0)) + emblem.add_child(Node.s16("index", 0)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/gameend/@status") def verify_gametop_regist(self, card_id: str, ref_id: str) -> int: call = self.call_node() # Construct node - gametop = Node.void('gametop') + gametop = Node.void("gametop") call.add_child(gametop) - gametop.set_attribute('method', 'regist') - data = Node.void('data') + gametop.set_attribute("method", "regist") + data = Node.void("data") gametop.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.string('refid', ref_id)) - player.add_child(Node.string('datid', ref_id)) - player.add_child(Node.string('uid', card_id)) - player.add_child(Node.bool('inherit', True)) - player.add_child(Node.string('name', self.NAME)) + player.add_child(Node.string("refid", ref_id)) + player.add_child(Node.string("datid", ref_id)) + player.add_child(Node.string("uid", card_id)) + player.add_child(Node.bool("inherit", True)) + player.add_child(Node.string("name", self.NAME)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist return self.__verify_profile(resp) @@ -377,22 +457,22 @@ class JubeatClanClient(BaseClient): call = self.call_node() # Construct node - gametop = Node.void('gametop') + gametop = Node.void("gametop") call.add_child(gametop) - gametop.set_attribute('method', 'get_pdata') - retry = Node.s32('retry', 0) + gametop.set_attribute("method", "get_pdata") + retry = Node.s32("retry", 0) gametop.add_child(retry) - data = Node.void('data') + data = Node.void("data") gametop.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.string('refid', ref_id)) - player.add_child(Node.string('datid', ref_id)) - player.add_child(Node.string('uid', card_id)) - player.add_child(Node.string('card_no', CardCipher.encode(card_id))) + player.add_child(Node.string("refid", ref_id)) + player.add_child(Node.string("datid", ref_id)) + player.add_child(Node.string("uid", card_id)) + player.add_child(Node.string("card_no", CardCipher.encode(card_id))) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist return self.__verify_profile(resp) @@ -401,33 +481,33 @@ class JubeatClanClient(BaseClient): call = self.call_node() # Construct node - gametop = Node.void('gametop') + gametop = Node.void("gametop") call.add_child(gametop) - gametop.set_attribute('method', 'get_mdata') - retry = Node.s32('retry', 0) + gametop.set_attribute("method", "get_mdata") + retry = Node.s32("retry", 0) gametop.add_child(retry) - data = Node.void('data') + data = Node.void("data") gametop.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.s32('jid', jid)) + player.add_child(Node.s32("jid", jid)) # Technically the game sends this same packet 3 times, one with # each value 1, 2, 3 here. Unclear why, but we won't emulate it. - player.add_child(Node.s8('mdata_ver', 1)) - player.add_child(Node.bool('rival', False)) + player.add_child(Node.s8("mdata_ver", 1)) + player.add_child(Node.bool("rival", False)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Parse out scores self.assert_path(resp, "response/gametop/data/player/mdata_list") ret = {} - for musicdata in resp.child('gametop/data/player/mdata_list').children: - if musicdata.name != 'musicdata': - raise Exception('Unexpected node in playdata!') + for musicdata in resp.child("gametop/data/player/mdata_list").children: + if musicdata.name != "musicdata": + raise Exception("Unexpected node in playdata!") - music_id = musicdata.attribute('music_id') + music_id = musicdata.attribute("music_id") scores_by_chart: List[Dict[str, int]] = [{}, {}, {}] def extract_cnts(name: str, val: List[int]) -> None: @@ -435,12 +515,12 @@ class JubeatClanClient(BaseClient): scores_by_chart[1][name] = val[1] scores_by_chart[2][name] = val[2] - extract_cnts('plays', musicdata.child_value('play_cnt')) - extract_cnts('clears', musicdata.child_value('clear_cnt')) - extract_cnts('full_combos', musicdata.child_value('fc_cnt')) - extract_cnts('excellents', musicdata.child_value('ex_cnt')) - extract_cnts('score', musicdata.child_value('score')) - extract_cnts('medal', musicdata.child_value('clear')) + extract_cnts("plays", musicdata.child_value("play_cnt")) + extract_cnts("clears", musicdata.child_value("clear_cnt")) + extract_cnts("full_combos", musicdata.child_value("fc_cnt")) + extract_cnts("excellents", musicdata.child_value("ex_cnt")) + extract_cnts("score", musicdata.child_value("score")) + extract_cnts("medal", musicdata.child_value("clear")) ret[music_id] = scores_by_chart return ret @@ -449,21 +529,21 @@ class JubeatClanClient(BaseClient): call = self.call_node() # Construct node - gametop = Node.void('gametop') + gametop = Node.void("gametop") call.add_child(gametop) - gametop.set_attribute('method', 'get_meeting') - gametop.add_child(Node.s32('retry', 0)) - data = Node.void('data') + gametop.set_attribute("method", "get_meeting") + gametop.add_child(Node.s32("retry", 0)) + data = Node.void("data") gametop.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.s32('jid', jid)) - pcbinfo = Node.void('pcbinfo') + player.add_child(Node.s32("jid", jid)) + pcbinfo = Node.void("pcbinfo") gametop.add_child(pcbinfo) - pcbinfo.set_attribute('client_data_version', '0') + pcbinfo.set_attribute("client_data_version", "0") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify expected nodes self.assert_path(resp, "response/gametop/data/meeting/single/@count") @@ -475,17 +555,17 @@ class JubeatClanClient(BaseClient): call = self.call_node() # Construct node - recommend = Node.void('recommend') + recommend = Node.void("recommend") call.add_child(recommend) - recommend.set_attribute('method', 'get_recommend') - recommend.add_child(Node.s32('retry', 0)) - player = Node.void('player') + recommend.set_attribute("method", "get_recommend") + recommend.add_child(Node.s32("retry", 0)) + player = Node.void("player") recommend.add_child(player) - player.add_child(Node.s32('jid', jid)) - player.add_child(Node.void('music_list')) + player.add_child(Node.s32("jid", jid)) + player.add_child(Node.void("music_list")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify expected nodes self.assert_path(resp, "response/recommend/data/player/music_list") @@ -494,12 +574,12 @@ class JubeatClanClient(BaseClient): call = self.call_node() # Construct node - gametop = Node.void('demodata') + gametop = Node.void("demodata") call.add_child(gametop) - gametop.set_attribute('method', 'get_hitchart') + gametop.set_attribute("method", "get_hitchart") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify expected nodes self.assert_path(resp, "response/demodata/data/update") @@ -510,17 +590,17 @@ class JubeatClanClient(BaseClient): call = self.call_node() # Construct node - jbox = Node.void('jbox') + jbox = Node.void("jbox") call.add_child(jbox) - jbox.set_attribute('method', 'get_list') - data = Node.void('data') + jbox.set_attribute("method", "get_list") + data = Node.void("data") jbox.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.s32('jid', jid)) + player.add_child(Node.s32("jid", jid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/jbox/selection_list") @@ -529,17 +609,17 @@ class JubeatClanClient(BaseClient): call = self.call_node() # Construct node - jbox = Node.void('jbox') + jbox = Node.void("jbox") call.add_child(jbox) - jbox.set_attribute('method', 'get_agreement') - data = Node.void('data') + jbox.set_attribute("method", "get_agreement") + data = Node.void("data") jbox.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.s32('jid', jid)) + player.add_child(Node.s32("jid", jid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/jbox/is_agreement") @@ -548,26 +628,26 @@ class JubeatClanClient(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive(ecflag=3) self.verify_package_list() self.verify_message_get() - self.verify_facility_get(encoding='Shift-JIS') + self.verify_facility_get(encoding="Shift-JIS") self.verify_pcbevent_put() self.verify_shopinfo_regist() self.verify_demodata_get_info() @@ -583,22 +663,32 @@ class JubeatClanClient(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") self.verify_gametop_regist(card, ref_id) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") if cardid is None: # Verify score handling @@ -607,9 +697,9 @@ class JubeatClanClient(BaseClient): scores = self.verify_gametop_get_mdata(jid) self.verify_gametop_get_meeting(jid) if scores is None: - raise Exception('Expected to get scores back, didn\'t get anything!') + raise Exception("Expected to get scores back, didn't get anything!") if len(scores) > 0: - raise Exception('Got nonzero score count on a new card!') + raise Exception("Got nonzero score count on a new card!") # Verify end of game behavior self.verify_jbox_get_list(jid) @@ -621,77 +711,77 @@ class JubeatClanClient(BaseClient): dummyscores = [ # An okay score on a chart { - 'id': 40000059, - 'chart': 2, - 'clear': True, - 'fc': False, - 'ex': False, - 'score': 800000, - 'expected_medal': 0x3, + "id": 40000059, + "chart": 2, + "clear": True, + "fc": False, + "ex": False, + "score": 800000, + "expected_medal": 0x3, }, # A good score on an easier chart of the same song { - 'id': 40000059, - 'chart': 1, - 'clear': True, - 'fc': True, - 'ex': False, - 'score': 990000, - 'expected_medal': 0x5, + "id": 40000059, + "chart": 1, + "clear": True, + "fc": True, + "ex": False, + "score": 990000, + "expected_medal": 0x5, }, # A perfect score on an easiest chart of the same song { - 'id': 40000059, - 'chart': 0, - 'clear': True, - 'fc': True, - 'ex': True, - 'score': 1000000, - 'expected_medal': 0x9, + "id": 40000059, + "chart": 0, + "clear": True, + "fc": True, + "ex": True, + "score": 1000000, + "expected_medal": 0x9, }, # A bad score on a hard chart { - 'id': 30000024, - 'chart': 2, - 'clear': False, - 'fc': False, - 'ex': False, - 'score': 400000, - 'expected_medal': 0x1, + "id": 30000024, + "chart": 2, + "clear": False, + "fc": False, + "ex": False, + "score": 400000, + "expected_medal": 0x1, }, # A terrible score on an easy chart { - 'id': 50000045, - 'chart': 0, - 'clear': False, - 'fc': False, - 'ex': False, - 'score': 100000, - 'expected_medal': 0x1, + "id": 50000045, + "chart": 0, + "clear": False, + "fc": False, + "ex": False, + "score": 100000, + "expected_medal": 0x1, }, ] if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 50000045, - 'chart': 0, - 'clear': True, - 'fc': False, - 'ex': False, - 'score': 850000, - 'expected_medal': 0x3, + "id": 50000045, + "chart": 0, + "clear": True, + "fc": False, + "ex": False, + "score": 850000, + "expected_medal": 0x3, }, # A worse score on another same chart { - 'id': 40000059, - 'chart': 1, - 'clear': True, - 'fc': False, - 'ex': False, - 'score': 925000, - 'expected_score': 990000, - 'expected_medal': 0x7, + "id": 40000059, + "chart": 1, + "clear": True, + "fc": False, + "ex": False, + "score": 925000, + "expected_score": 990000, + "expected_medal": 0x7, }, ] @@ -700,18 +790,22 @@ class JubeatClanClient(BaseClient): scores = self.verify_gametop_get_mdata(jid) for score in dummyscores: - newscore = scores[str(score['id'])][score['chart']] + newscore = scores[str(score["id"])][score["chart"]] - if 'expected_score' in score: - expected_score = score['expected_score'] + if "expected_score" in score: + expected_score = score["expected_score"] else: - expected_score = score['score'] + expected_score = score["score"] - if newscore['score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{newscore["score"]}\'') + if newscore["score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{newscore["score"]}\'' + ) - if newscore['medal'] != score['expected_medal']: - raise Exception(f'Expected a medal of \'{score["expected_medal"]}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got medal \'{newscore["medal"]}\'') + if newscore["medal"] != score["expected_medal"]: + raise Exception( + f'Expected a medal of \'{score["expected_medal"]}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got medal \'{newscore["medal"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) diff --git a/bemani/client/jubeat/festo.py b/bemani/client/jubeat/festo.py index 7fe420c..9f5e20e 100644 --- a/bemani/client/jubeat/festo.py +++ b/bemani/client/jubeat/festo.py @@ -8,7 +8,7 @@ from bemani.protocol import Node class JubeatFestoClient(BaseClient): - NAME = 'TEST' + NAME = "TEST" def __verify_info(self, resp: Node, base: str) -> None: # Verify that response is correct @@ -26,9 +26,13 @@ class JubeatFestoClient(BaseClient): self.assert_path(resp, f"response/{base}/data/info/jbox/emblem/premium/index") self.assert_path(resp, f"response/{base}/data/info/born/status") self.assert_path(resp, f"response/{base}/data/info/born/year") - self.assert_path(resp, f"response/{base}/data/info/konami_logo_50th/is_available") + self.assert_path( + resp, f"response/{base}/data/info/konami_logo_50th/is_available" + ) self.assert_path(resp, f"response/{base}/data/info/expert_option/is_available") - self.assert_path(resp, f"response/{base}/data/info/all_music_matching/is_available") + self.assert_path( + resp, f"response/{base}/data/info/all_music_matching/is_available" + ) self.assert_path(resp, f"response/{base}/data/info/department/shop_list") self.assert_path(resp, f"response/{base}/data/info/question_list") # Don't bother asserting on actual courses, this is highly specific. @@ -50,103 +54,103 @@ class JubeatFestoClient(BaseClient): call = self.call_node() # Construct node - shopinfo = Node.void('shopinfo') - shopinfo.set_attribute('method', 'regist') + shopinfo = Node.void("shopinfo") + shopinfo.set_attribute("method", "regist") call.add_child(shopinfo) - shop = Node.void('shop') + shop = Node.void("shop") shopinfo.add_child(shop) - shop.add_child(Node.string('name', '')) - shop.add_child(Node.string('pref', 'JP-14')) - shop.add_child(Node.string('softwareid', '')) - shop.add_child(Node.string('systemid', self.pcbid)) - shop.add_child(Node.string('hardwareid', '01020304050607080900')) - shop.add_child(Node.string('locationid', 'US-1')) - shop.add_child(Node.string('monitor', 'D26L155 6252 151')) - testmode = Node.void('testmode') + shop.add_child(Node.string("name", "")) + shop.add_child(Node.string("pref", "JP-14")) + shop.add_child(Node.string("softwareid", "")) + shop.add_child(Node.string("systemid", self.pcbid)) + shop.add_child(Node.string("hardwareid", "01020304050607080900")) + shop.add_child(Node.string("locationid", "US-1")) + shop.add_child(Node.string("monitor", "D26L155 6252 151")) + testmode = Node.void("testmode") shop.add_child(testmode) - sound = Node.void('sound') + sound = Node.void("sound") testmode.add_child(sound) - sound.add_child(Node.u8('volume_in_attract', 0)) - game = Node.void('game') + sound.add_child(Node.u8("volume_in_attract", 0)) + game = Node.void("game") testmode.add_child(game) - play_settings = Node.void('play_settings') + play_settings = Node.void("play_settings") game.add_child(play_settings) - play_settings.add_child(Node.u8('max_member', 1)) - game_settings = Node.void('game_settings') + play_settings.add_child(Node.u8("max_member", 1)) + game_settings = Node.void("game_settings") game.add_child(game_settings) - game_settings.add_child(Node.u8('close_set', 0)) - game_settings.add_child(Node.s32('close_time', 0)) - display_type_settings = Node.void('display_type_settings') + game_settings.add_child(Node.u8("close_set", 0)) + game_settings.add_child(Node.s32("close_time", 0)) + display_type_settings = Node.void("display_type_settings") game.add_child(display_type_settings) - display_type_settings.add_child(Node.u8('display_type', 2)) - coin = Node.void('coin') + display_type_settings.add_child(Node.u8("display_type", 2)) + coin = Node.void("coin") testmode.add_child(coin) - coin.add_child(Node.u8('free_play', 0)) - coin.add_child(Node.u8('free_first_play', 1)) - coin.add_child(Node.u8('coin_slot', 8)) - coin.add_child(Node.u8('start', 1)) - network = Node.void('network') + coin.add_child(Node.u8("free_play", 0)) + coin.add_child(Node.u8("free_first_play", 1)) + coin.add_child(Node.u8("coin_slot", 8)) + coin.add_child(Node.u8("start", 1)) + network = Node.void("network") testmode.add_child(network) - network.add_child(Node.u8('cabinet_id', 1)) - bookkeeping = Node.void('bookkeeping') + network.add_child(Node.u8("cabinet_id", 1)) + bookkeeping = Node.void("bookkeeping") testmode.add_child(bookkeeping) - bookkeeping.add_child(Node.u8('enable', 0)) - clock = Node.void('clock') + bookkeeping.add_child(Node.u8("enable", 0)) + clock = Node.void("clock") testmode.add_child(clock) - clock.add_child(Node.u8('enable', 1)) - clock.add_child(Node.s32('offset', 0)) - virtual_coin = Node.void('virtual_coin') + clock.add_child(Node.u8("enable", 1)) + clock.add_child(Node.s32("offset", 0)) + virtual_coin = Node.void("virtual_coin") testmode.add_child(virtual_coin) - pattern1 = Node.void('pattern1') + pattern1 = Node.void("pattern1") virtual_coin.add_child(pattern1) - pattern1.add_child(Node.u16('basic_rate', 1000)) - pattern1.add_child(Node.u8('balance_of_credit', 0)) - pattern1.add_child(Node.u8('is_premium_start', 0)) - pattern1.add_child(Node.u8('service_value', 10)) - pattern1.add_child(Node.u8('service_limit', 10)) - pattern1.add_child(Node.u8('service_time_start_h', 7)) - pattern1.add_child(Node.u8('service_time_start_m', 0)) - pattern1.add_child(Node.u8('service_time_end_h', 11)) - pattern1.add_child(Node.u8('service_time_end_m', 0)) - pattern2 = Node.void('pattern2') + pattern1.add_child(Node.u16("basic_rate", 1000)) + pattern1.add_child(Node.u8("balance_of_credit", 0)) + pattern1.add_child(Node.u8("is_premium_start", 0)) + pattern1.add_child(Node.u8("service_value", 10)) + pattern1.add_child(Node.u8("service_limit", 10)) + pattern1.add_child(Node.u8("service_time_start_h", 7)) + pattern1.add_child(Node.u8("service_time_start_m", 0)) + pattern1.add_child(Node.u8("service_time_end_h", 11)) + pattern1.add_child(Node.u8("service_time_end_m", 0)) + pattern2 = Node.void("pattern2") virtual_coin.add_child(pattern2) - pattern2.add_child(Node.u16('basic_rate', 1000)) - pattern2.add_child(Node.u8('balance_of_credit', 0)) - pattern2.add_child(Node.u8('is_premium_start', 0)) - pattern2.add_child(Node.u8('service_value', 10)) - pattern2.add_child(Node.u8('service_limit', 10)) - pattern2.add_child(Node.u8('service_time_start_h', 7)) - pattern2.add_child(Node.u8('service_time_start_m', 0)) - pattern2.add_child(Node.u8('service_time_end_h', 11)) - pattern2.add_child(Node.u8('service_time_end_m', 0)) - pattern3 = Node.void('pattern3') + pattern2.add_child(Node.u16("basic_rate", 1000)) + pattern2.add_child(Node.u8("balance_of_credit", 0)) + pattern2.add_child(Node.u8("is_premium_start", 0)) + pattern2.add_child(Node.u8("service_value", 10)) + pattern2.add_child(Node.u8("service_limit", 10)) + pattern2.add_child(Node.u8("service_time_start_h", 7)) + pattern2.add_child(Node.u8("service_time_start_m", 0)) + pattern2.add_child(Node.u8("service_time_end_h", 11)) + pattern2.add_child(Node.u8("service_time_end_m", 0)) + pattern3 = Node.void("pattern3") virtual_coin.add_child(pattern3) - pattern3.add_child(Node.u16('basic_rate', 1000)) - pattern3.add_child(Node.u8('balance_of_credit', 0)) - pattern3.add_child(Node.u8('is_premium_start', 0)) - pattern3.add_child(Node.u8('service_value', 10)) - pattern3.add_child(Node.u8('service_limit', 10)) - pattern3.add_child(Node.u8('service_time_start_h', 7)) - pattern3.add_child(Node.u8('service_time_start_m', 0)) - pattern3.add_child(Node.u8('service_time_end_h', 11)) - pattern3.add_child(Node.u8('service_time_end_m', 0)) - schedule = Node.void('schedule') + pattern3.add_child(Node.u16("basic_rate", 1000)) + pattern3.add_child(Node.u8("balance_of_credit", 0)) + pattern3.add_child(Node.u8("is_premium_start", 0)) + pattern3.add_child(Node.u8("service_value", 10)) + pattern3.add_child(Node.u8("service_limit", 10)) + pattern3.add_child(Node.u8("service_time_start_h", 7)) + pattern3.add_child(Node.u8("service_time_start_m", 0)) + pattern3.add_child(Node.u8("service_time_end_h", 11)) + pattern3.add_child(Node.u8("service_time_end_m", 0)) + schedule = Node.void("schedule") virtual_coin.add_child(schedule) - schedule.add_child(Node.u8('mon', 0)) - schedule.add_child(Node.u8('tue', 0)) - schedule.add_child(Node.u8('wed', 0)) - schedule.add_child(Node.u8('thu', 0)) - schedule.add_child(Node.u8('fri', 0)) - schedule.add_child(Node.u8('sat', 0)) - schedule.add_child(Node.u8('sun', 0)) - schedule.add_child(Node.u8('holi', 0)) - tax = Node.void('tax') + schedule.add_child(Node.u8("mon", 0)) + schedule.add_child(Node.u8("tue", 0)) + schedule.add_child(Node.u8("wed", 0)) + schedule.add_child(Node.u8("thu", 0)) + schedule.add_child(Node.u8("fri", 0)) + schedule.add_child(Node.u8("sat", 0)) + schedule.add_child(Node.u8("sun", 0)) + schedule.add_child(Node.u8("holi", 0)) + tax = Node.void("tax") testmode.add_child(tax) - tax.add_child(Node.u8('tax_phase', 0)) - tax.add_child(Node.u8('tax_mode', 0)) + tax.add_child(Node.u8("tax_phase", 0)) + tax.add_child(Node.u8("tax_mode", 0)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/shopinfo/data/cabid") self.assert_path(resp, "response/shopinfo/data/locationid") @@ -154,23 +158,25 @@ class JubeatFestoClient(BaseClient): self.assert_path(resp, "response/shopinfo/data/facility/exist") # Verify server flags for events and stuff. - self.__verify_info(resp, 'shopinfo') + self.__verify_info(resp, "shopinfo") def verify_logger_report(self) -> None: call = self.call_node() # Construct node - logger = Node.void('logger') + logger = Node.void("logger") call.add_child(logger) - logger.set_attribute('method', 'report') - logger.add_child(Node.s32('retry', 0)) - data = Node.void('data') + logger.set_attribute("method", "report") + logger.add_child(Node.s32("retry", 0)) + data = Node.void("data") logger.add_child(data) - data.add_child(Node.string('code', 'pcbinfo_01')) - data.add_child(Node.string('information', 'u can literally put anything here lmao')) + data.add_child(Node.string("code", "pcbinfo_01")) + data.add_child( + Node.string("information", "u can literally put anything here lmao") + ) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/logger/@status") @@ -179,15 +185,15 @@ class JubeatFestoClient(BaseClient): call = self.call_node() # Construct node - demodata = Node.void('demodata') + demodata = Node.void("demodata") call.add_child(demodata) - demodata.set_attribute('method', 'get_info') - pcbinfo = Node.void('pcbinfo') + demodata.set_attribute("method", "get_info") + pcbinfo = Node.void("pcbinfo") demodata.add_child(pcbinfo) - pcbinfo.set_attribute('client_data_version', '0') + pcbinfo.set_attribute("client_data_version", "0") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/demodata/data/info/black_jacket_list") @@ -196,12 +202,12 @@ class JubeatFestoClient(BaseClient): call = self.call_node() # Construct node - demodata = Node.void('demodata') + demodata = Node.void("demodata") call.add_child(demodata) - demodata.set_attribute('method', 'get_news') + demodata.set_attribute("method", "get_news") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/demodata/data/officialnews/@count") @@ -210,12 +216,12 @@ class JubeatFestoClient(BaseClient): call = self.call_node() # Construct node - demodata = Node.void('demodata') + demodata = Node.void("demodata") call.add_child(demodata) - demodata.set_attribute('method', 'get_jbox_list') + demodata.set_attribute("method", "get_jbox_list") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/demodata/@status") @@ -224,14 +230,14 @@ class JubeatFestoClient(BaseClient): call = self.call_node() # Construct node - lab = Node.void('lab') + lab = Node.void("lab") call.add_child(lab) - lab.set_attribute('method', 'get_ranking') - lab.add_child(Node.s32('retry', 0)) - lab.add_child(Node.s8('category', 1)) + lab.set_attribute("method", "get_ranking") + lab.add_child(Node.s32("retry", 0)) + lab.add_child(Node.s8("category", 1)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/lab/category") @@ -243,21 +249,21 @@ class JubeatFestoClient(BaseClient): def __verify_profile(self, resp: Node, should_inherit: bool) -> int: for item in [ - 'tune_cnt', - 'save_cnt', - 'saved_cnt', - 'fc_cnt', - 'ex_cnt', - 'clear_cnt', - 'match_cnt', - 'beat_cnt', - 'mynews_cnt', - 'bonus_tune_points', - 'is_bonus_tune_played', - 'inherit', - 'mtg_entry_cnt', - 'mtg_hold_cnt', - 'mtg_result', + "tune_cnt", + "save_cnt", + "saved_cnt", + "fc_cnt", + "ex_cnt", + "clear_cnt", + "match_cnt", + "beat_cnt", + "mynews_cnt", + "bonus_tune_points", + "is_bonus_tune_played", + "inherit", + "mtg_entry_cnt", + "mtg_hold_cnt", + "mtg_result", ]: self.assert_path(resp, f"response/gametop/data/player/info/{item}") @@ -267,43 +273,43 @@ class JubeatFestoClient(BaseClient): raise Exception("Inherit flag wrong for profile!") for item in [ - 'music_list', - 'secret_list', - 'theme_list', - 'marker_list', - 'title_list', - 'parts_list', - 'emblem_list', - 'commu_list', - 'new/secret_list', - 'new/theme_list', - 'new/marker_list', + "music_list", + "secret_list", + "theme_list", + "marker_list", + "title_list", + "parts_list", + "emblem_list", + "commu_list", + "new/secret_list", + "new/theme_list", + "new/marker_list", ]: self.assert_path(resp, f"response/gametop/data/player/item/{item}") for item in [ - 'play_time', - 'shopname', - 'areaname', - 'music_id', - 'seq_id', - 'sort', - 'category', - 'expert_option', + "play_time", + "shopname", + "areaname", + "music_id", + "seq_id", + "sort", + "category", + "expert_option", ]: self.assert_path(resp, f"response/gametop/data/player/last/{item}") for item in [ - 'marker', - 'theme', - 'title', - 'parts', - 'rank_sort', - 'combo_disp', - 'emblem', - 'matching', - 'hard', - 'hazard', + "marker", + "theme", + "title", + "parts", + "rank_sort", + "combo_disp", + "emblem", + "matching", + "hard", + "hazard", ]: self.assert_path(resp, f"response/gametop/data/player/last/settings/{item}") @@ -314,19 +320,25 @@ class JubeatFestoClient(BaseClient): # Profile settings self.assert_path(resp, "response/gametop/data/player/name") self.assert_path(resp, "response/gametop/data/player/jid") - if resp.child_value('gametop/data/player/name') != self.NAME: + if resp.child_value("gametop/data/player/name") != self.NAME: raise Exception("Unexpected name received from server!") # Required nodes for events and stuff self.assert_path(resp, "response/gametop/data/player/rivallist") self.assert_path(resp, "response/gametop/data/player/lab_edit_seq") - self.assert_path(resp, "response/gametop/data/player/fc_challenge/today/music_id") + self.assert_path( + resp, "response/gametop/data/player/fc_challenge/today/music_id" + ) self.assert_path(resp, "response/gametop/data/player/fc_challenge/today/state") - self.assert_path(resp, "response/gametop/data/player/fc_challenge/whim/music_id") + self.assert_path( + resp, "response/gametop/data/player/fc_challenge/whim/music_id" + ) self.assert_path(resp, "response/gametop/data/player/fc_challenge/whim/state") self.assert_path(resp, "response/gametop/data/player/official_news/news_list") self.assert_path(resp, "response/gametop/data/player/history/@count") - self.assert_path(resp, "response/gametop/data/player/free_first_play/is_available") + self.assert_path( + resp, "response/gametop/data/player/free_first_play/is_available" + ) self.assert_path(resp, "response/gametop/data/player/event_info") self.assert_path(resp, "response/gametop/data/player/jbox/point") self.assert_path(resp, "response/gametop/data/player/jbox/emblem/normal/index") @@ -341,21 +353,44 @@ class JubeatFestoClient(BaseClient): self.assert_path(resp, "response/gametop/data/player/server") self.assert_path(resp, "response/gametop/data/player/course_list") self.assert_path(resp, "response/gametop/data/player/course_list/category_list") - self.assert_path(resp, "response/gametop/data/player/fill_in_category/normal/no_gray_flag_list") - self.assert_path(resp, "response/gametop/data/player/fill_in_category/normal/all_yellow_flag_list") - self.assert_path(resp, "response/gametop/data/player/fill_in_category/normal/full_combo_flag_list") - self.assert_path(resp, "response/gametop/data/player/fill_in_category/normal/excellent_flag_list") - self.assert_path(resp, "response/gametop/data/player/fill_in_category/hard/no_gray_flag_list") - self.assert_path(resp, "response/gametop/data/player/fill_in_category/hard/all_yellow_flag_list") - self.assert_path(resp, "response/gametop/data/player/fill_in_category/hard/full_combo_flag_list") - self.assert_path(resp, "response/gametop/data/player/fill_in_category/hard/excellent_flag_list") + self.assert_path( + resp, + "response/gametop/data/player/fill_in_category/normal/no_gray_flag_list", + ) + self.assert_path( + resp, + "response/gametop/data/player/fill_in_category/normal/all_yellow_flag_list", + ) + self.assert_path( + resp, + "response/gametop/data/player/fill_in_category/normal/full_combo_flag_list", + ) + self.assert_path( + resp, + "response/gametop/data/player/fill_in_category/normal/excellent_flag_list", + ) + self.assert_path( + resp, "response/gametop/data/player/fill_in_category/hard/no_gray_flag_list" + ) + self.assert_path( + resp, + "response/gametop/data/player/fill_in_category/hard/all_yellow_flag_list", + ) + self.assert_path( + resp, + "response/gametop/data/player/fill_in_category/hard/full_combo_flag_list", + ) + self.assert_path( + resp, + "response/gametop/data/player/fill_in_category/hard/excellent_flag_list", + ) self.assert_path(resp, "response/gametop/data/player/department/shop_list") self.assert_path(resp, "response/gametop/data/player/stamp/sheet_list") self.assert_path(resp, "response/gametop/data/player/festo_dungeon/phase") self.assert_path(resp, "response/gametop/data/player/festo_dungeon/clear_flag") # Return the jid - return resp.child_value('gametop/data/player/jid') + return resp.child_value("gametop/data/player/jid") def verify_gameend_regist( self, @@ -366,57 +401,93 @@ class JubeatFestoClient(BaseClient): call = self.call_node() # Construct node - gameend = Node.void('gameend') + gameend = Node.void("gameend") call.add_child(gameend) - gameend.set_attribute('method', 'regist') - gameend.add_child(Node.s32('retry', 0)) - pcbinfo = Node.void('pcbinfo') + gameend.set_attribute("method", "regist") + gameend.add_child(Node.s32("retry", 0)) + pcbinfo = Node.void("pcbinfo") gameend.add_child(pcbinfo) - pcbinfo.set_attribute('client_data_version', '0') - data = Node.void('data') + pcbinfo.set_attribute("client_data_version", "0") + data = Node.void("data") gameend.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.string('refid', ref_id)) - player.add_child(Node.s32('jid', jid)) - player.add_child(Node.string('name', self.NAME)) - result = Node.void('result') + player.add_child(Node.string("refid", ref_id)) + player.add_child(Node.s32("jid", jid)) + player.add_child(Node.string("name", self.NAME)) + result = Node.void("result") data.add_child(result) - result.set_attribute('count', str(len(scores))) + result.set_attribute("count", str(len(scores))) # Send scores scoreid = 0 for score in scores: # Always played bits = 0x1 - if score['clear']: + if score["clear"]: bits |= 0x2 - if score['fc']: + if score["fc"]: bits |= 0x4 - if score['ex']: + if score["ex"]: bits |= 0x8 # Intentionally starting at 1 because that's what the game does scoreid = scoreid + 1 - tune = Node.void('tune') + tune = Node.void("tune") result.add_child(tune) - tune.set_attribute('id', str(scoreid)) - tune.add_child(Node.s32('music', score['id'])) - tune.add_child(Node.s64('timestamp', Time.now() * 1000)) - player_1 = Node.void('player') + tune.set_attribute("id", str(scoreid)) + tune.add_child(Node.s32("music", score["id"])) + tune.add_child(Node.s64("timestamp", Time.now() * 1000)) + player_1 = Node.void("player") tune.add_child(player_1) - player_1.set_attribute('rank', '1') - scorenode = Node.s32('score', score['score']) + player_1.set_attribute("rank", "1") + scorenode = Node.s32("score", score["score"]) player_1.add_child(scorenode) - scorenode.set_attribute('seq', str(score['chart'])) - scorenode.set_attribute('clear', str(bits)) - scorenode.set_attribute('combo', '69') - player_1.add_child(Node.u8_array('mbar', [239, 175, 170, 170, 190, 234, 187, 158, 153, 230, 170, 90, 102, 170, 85, 150, 150, 102, 85, 234, 171, 169, 157, 150, 170, 101, 230, 90, 214, 255])) - player_1.add_child(Node.bool('is_hard_mode', score['hard'])) - player_1.add_child(Node.s32('music_rate', score['rate'])) + scorenode.set_attribute("seq", str(score["chart"])) + scorenode.set_attribute("clear", str(bits)) + scorenode.set_attribute("combo", "69") + player_1.add_child( + Node.u8_array( + "mbar", + [ + 239, + 175, + 170, + 170, + 190, + 234, + 187, + 158, + 153, + 230, + 170, + 90, + 102, + 170, + 85, + 150, + 150, + 102, + 85, + 234, + 171, + 169, + 157, + 150, + 170, + 101, + 230, + 90, + 214, + 255, + ], + ) + ) + player_1.add_child(Node.bool("is_hard_mode", score["hard"])) + player_1.add_child(Node.s32("music_rate", score["rate"])) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/gameend/data/player/session_id") self.assert_path(resp, "response/gameend/data/player/end_final_session_id") @@ -428,60 +499,60 @@ class JubeatFestoClient(BaseClient): call = self.call_node() # Construct node - gameend = Node.void('gameend') + gameend = Node.void("gameend") call.add_child(gameend) - gameend.set_attribute('method', 'final') - gameend.add_child(Node.s32('retry', 0)) - pcbinfo = Node.void('pcbinfo') + gameend.set_attribute("method", "final") + gameend.add_child(Node.s32("retry", 0)) + pcbinfo = Node.void("pcbinfo") gameend.add_child(pcbinfo) - pcbinfo.set_attribute('client_data_version', '0') - data = Node.void('data') + pcbinfo.set_attribute("client_data_version", "0") + data = Node.void("data") gameend.add_child(data) - info = Node.void('info') + info = Node.void("info") data.add_child(info) - born = Node.void('born') + born = Node.void("born") info.add_child(born) - born.add_child(Node.s8('status', 3)) - born.add_child(Node.s16('year', 0)) - info.add_child(Node.void('question_list')) - player = Node.void('player') + born.add_child(Node.s8("status", 3)) + born.add_child(Node.s16("year", 0)) + info.add_child(Node.void("question_list")) + player = Node.void("player") data.add_child(player) - player.add_child(Node.string('refid', ref_id)) - player.add_child(Node.s32('jid', jid)) - jbox = Node.void('jbox') + player.add_child(Node.string("refid", ref_id)) + player.add_child(Node.s32("jid", jid)) + jbox = Node.void("jbox") player.add_child(jbox) - jbox.add_child(Node.s32('point', 0)) - emblem = Node.void('emblem') + jbox.add_child(Node.s32("point", 0)) + emblem = Node.void("emblem") jbox.add_child(emblem) - emblem.add_child(Node.u8('type', 0)) - emblem.add_child(Node.s16('index', 0)) + emblem.add_child(Node.u8("type", 0)) + emblem.add_child(Node.s16("index", 0)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/gameend/@status") def verify_gametop_regist(self, card_id: str, ref_id: str) -> int: call = self.call_node() # Construct node - gametop = Node.void('gametop') + gametop = Node.void("gametop") call.add_child(gametop) - gametop.set_attribute('method', 'regist') - data = Node.void('data') + gametop.set_attribute("method", "regist") + data = Node.void("data") gametop.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.string('refid', ref_id)) - player.add_child(Node.string('datid', ref_id)) - player.add_child(Node.string('uid', card_id)) - player.add_child(Node.bool('inherit', True)) - player.add_child(Node.string('name', self.NAME)) + player.add_child(Node.string("refid", ref_id)) + player.add_child(Node.string("datid", ref_id)) + player.add_child(Node.string("uid", card_id)) + player.add_child(Node.bool("inherit", True)) + player.add_child(Node.string("name", self.NAME)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify server flags for events and stuff. - self.__verify_info(resp, 'gametop') + self.__verify_info(resp, "gametop") # Verify nodes that cause crashes or failed card-ins if they don't exist return self.__verify_profile(resp, False) @@ -490,22 +561,22 @@ class JubeatFestoClient(BaseClient): call = self.call_node() # Construct node - gametop = Node.void('gametop') + gametop = Node.void("gametop") call.add_child(gametop) - gametop.set_attribute('method', 'get_pdata') - retry = Node.s32('retry', 0) + gametop.set_attribute("method", "get_pdata") + retry = Node.s32("retry", 0) gametop.add_child(retry) - data = Node.void('data') + data = Node.void("data") gametop.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.string('refid', ref_id)) - player.add_child(Node.string('datid', ref_id)) - player.add_child(Node.string('uid', card_id)) - player.add_child(Node.string('card_no', CardCipher.encode(card_id))) + player.add_child(Node.string("refid", ref_id)) + player.add_child(Node.string("datid", ref_id)) + player.add_child(Node.string("uid", card_id)) + player.add_child(Node.string("card_no", CardCipher.encode(card_id))) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist return self.__verify_profile(resp, False) @@ -514,26 +585,26 @@ class JubeatFestoClient(BaseClient): call = self.call_node() # Construct node - gametop = Node.void('gametop') + gametop = Node.void("gametop") call.add_child(gametop) - gametop.set_attribute('method', 'get_mdata') - retry = Node.s32('retry', 0) + gametop.set_attribute("method", "get_mdata") + retry = Node.s32("retry", 0) gametop.add_child(retry) - data = Node.void('data') + data = Node.void("data") gametop.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.s32('jid', jid)) + player.add_child(Node.s32("jid", jid)) # Technically the game sends this same packet 3 times, one with # each value 1, 2, 3 here. This is for sharding across 3 requests, # and the game will combine all 3 responses. Its up to the server to # handle this the way it wants, and we just send everything back in the # first request and ignore the rest. - player.add_child(Node.s8('mdata_ver', 1)) - player.add_child(Node.bool('rival', False)) + player.add_child(Node.s8("mdata_ver", 1)) + player.add_child(Node.bool("rival", False)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Parse out scores self.assert_path(resp, "response/gametop/data/player/jid") @@ -542,11 +613,11 @@ class JubeatFestoClient(BaseClient): raise Exception("Unexpected jid received from server!") ret = {} - for musicdata in resp.child('gametop/data/player/mdata_list').children: - if musicdata.name != 'musicdata': - raise Exception('Unexpected node in playdata!') + for musicdata in resp.child("gametop/data/player/mdata_list").children: + if musicdata.name != "musicdata": + raise Exception("Unexpected node in playdata!") - music_id = musicdata.attribute('music_id') + music_id = musicdata.attribute("music_id") scores_by_chart: List[Dict[str, int]] = [{}, {}, {}, {}, {}, {}] def extract_cnts(name: str, offset: int, val: List[int]) -> None: @@ -562,13 +633,13 @@ class JubeatFestoClient(BaseClient): else: raise Exception(f"Unexpected chart type {subdata.name}!") - extract_cnts('plays', offset, subdata.child_value('play_cnt')) - extract_cnts('clears', offset, subdata.child_value('clear_cnt')) - extract_cnts('full_combos', offset, subdata.child_value('fc_cnt')) - extract_cnts('excellents', offset, subdata.child_value('ex_cnt')) - extract_cnts('score', offset, subdata.child_value('score')) - extract_cnts('medal', offset, subdata.child_value('clear')) - extract_cnts('rate', offset, subdata.child_value('music_rate')) + extract_cnts("plays", offset, subdata.child_value("play_cnt")) + extract_cnts("clears", offset, subdata.child_value("clear_cnt")) + extract_cnts("full_combos", offset, subdata.child_value("fc_cnt")) + extract_cnts("excellents", offset, subdata.child_value("ex_cnt")) + extract_cnts("score", offset, subdata.child_value("score")) + extract_cnts("medal", offset, subdata.child_value("clear")) + extract_cnts("rate", offset, subdata.child_value("music_rate")) ret[music_id] = scores_by_chart @@ -578,21 +649,21 @@ class JubeatFestoClient(BaseClient): call = self.call_node() # Construct node - gametop = Node.void('gametop') + gametop = Node.void("gametop") call.add_child(gametop) - gametop.set_attribute('method', 'get_meeting') - gametop.add_child(Node.s32('retry', 0)) - data = Node.void('data') + gametop.set_attribute("method", "get_meeting") + gametop.add_child(Node.s32("retry", 0)) + data = Node.void("data") gametop.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.s32('jid', jid)) - pcbinfo = Node.void('pcbinfo') + player.add_child(Node.s32("jid", jid)) + pcbinfo = Node.void("pcbinfo") gametop.add_child(pcbinfo) - pcbinfo.set_attribute('client_data_version', '0') + pcbinfo.set_attribute("client_data_version", "0") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify expected nodes self.assert_path(resp, "response/gametop/data/meeting/single/@count") @@ -604,17 +675,17 @@ class JubeatFestoClient(BaseClient): call = self.call_node() # Construct node - recommend = Node.void('recommend') + recommend = Node.void("recommend") call.add_child(recommend) - recommend.set_attribute('method', 'get_recommend') - recommend.add_child(Node.s32('retry', 0)) - player = Node.void('player') + recommend.set_attribute("method", "get_recommend") + recommend.add_child(Node.s32("retry", 0)) + player = Node.void("player") recommend.add_child(player) - player.add_child(Node.s32('jid', jid)) - player.add_child(Node.void('music_list')) + player.add_child(Node.s32("jid", jid)) + player.add_child(Node.void("music_list")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify expected nodes self.assert_path(resp, "response/recommend/data/player/music_list") @@ -623,12 +694,12 @@ class JubeatFestoClient(BaseClient): call = self.call_node() # Construct node - gametop = Node.void('demodata') + gametop = Node.void("demodata") call.add_child(gametop) - gametop.set_attribute('method', 'get_hitchart') + gametop.set_attribute("method", "get_hitchart") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify expected nodes self.assert_path(resp, "response/demodata/data/update") @@ -639,17 +710,17 @@ class JubeatFestoClient(BaseClient): call = self.call_node() # Construct node - jbox = Node.void('jbox') + jbox = Node.void("jbox") call.add_child(jbox) - jbox.set_attribute('method', 'get_list') - data = Node.void('data') + jbox.set_attribute("method", "get_list") + data = Node.void("data") jbox.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.s32('jid', jid)) + player.add_child(Node.s32("jid", jid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/jbox/selection_list") @@ -658,17 +729,17 @@ class JubeatFestoClient(BaseClient): call = self.call_node() # Construct node - jbox = Node.void('jbox') + jbox = Node.void("jbox") call.add_child(jbox) - jbox.set_attribute('method', 'get_agreement') - data = Node.void('data') + jbox.set_attribute("method", "get_agreement") + data = Node.void("data") jbox.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.s32('jid', jid)) + player.add_child(Node.s32("jid", jid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/jbox/is_agreement") @@ -677,29 +748,29 @@ class JubeatFestoClient(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'netlog', - 'slocal', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "netlog", + "slocal", + "ntp", + "keepalive", ], include_net=True, ) paseli_enabled = self.verify_pcbtracker_alive(ecflag=3) self.verify_package_list() self.verify_message_get() - self.verify_facility_get(encoding='Shift-JIS') + self.verify_facility_get(encoding="Shift-JIS") self.verify_pcbevent_put() self.verify_logger_report() self.verify_shopinfo_regist() @@ -717,22 +788,32 @@ class JubeatFestoClient(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") self.verify_gametop_regist(card, ref_id) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") if cardid is None: # Verify score handling @@ -741,9 +822,9 @@ class JubeatFestoClient(BaseClient): scores = self.verify_gametop_get_mdata(jid) self.verify_gametop_get_meeting(jid) if scores is None: - raise Exception('Expected to get scores back, didn\'t get anything!') + raise Exception("Expected to get scores back, didn't get anything!") if len(scores) > 0: - raise Exception('Got nonzero score count on a new card!') + raise Exception("Got nonzero score count on a new card!") # Verify end of game behavior self.verify_jbox_get_list(jid) @@ -755,105 +836,105 @@ class JubeatFestoClient(BaseClient): dummyscores = [ # An okay score on a chart { - 'id': 40000059, - 'chart': 2, - 'hard': False, - 'clear': True, - 'fc': False, - 'ex': False, - 'score': 800000, - 'rate': 567, - 'expected_medal': 0x3, + "id": 40000059, + "chart": 2, + "hard": False, + "clear": True, + "fc": False, + "ex": False, + "score": 800000, + "rate": 567, + "expected_medal": 0x3, }, # A good score on an easier chart of the same song { - 'id': 40000059, - 'chart': 1, - 'hard': False, - 'clear': True, - 'fc': True, - 'ex': False, - 'score': 990000, - 'rate': 456, - 'expected_medal': 0x5, + "id": 40000059, + "chart": 1, + "hard": False, + "clear": True, + "fc": True, + "ex": False, + "score": 990000, + "rate": 456, + "expected_medal": 0x5, }, # A perfect score on an easiest chart of the same song { - 'id': 40000059, - 'chart': 0, - 'hard': False, - 'clear': True, - 'fc': True, - 'ex': True, - 'score': 1000000, - 'rate': 678, - 'expected_medal': 0x9, + "id": 40000059, + "chart": 0, + "hard": False, + "clear": True, + "fc": True, + "ex": True, + "score": 1000000, + "rate": 678, + "expected_medal": 0x9, }, # A bad score on a hard chart { - 'id': 30000027, - 'chart': 2, - 'hard': False, - 'clear': False, - 'fc': False, - 'ex': False, - 'score': 400000, - 'rate': 123, - 'expected_medal': 0x1, + "id": 30000027, + "chart": 2, + "hard": False, + "clear": False, + "fc": False, + "ex": False, + "score": 400000, + "rate": 123, + "expected_medal": 0x1, }, # A terrible score on an easy chart { - 'id': 50000045, - 'chart': 0, - 'hard': False, - 'clear': False, - 'fc': False, - 'ex': False, - 'score': 100000, - 'rate': 69, - 'expected_medal': 0x1, + "id": 50000045, + "chart": 0, + "hard": False, + "clear": False, + "fc": False, + "ex": False, + "score": 100000, + "rate": 69, + "expected_medal": 0x1, }, # A good score on a hard chart to make sure # it doesn't pollute regular charts. { - 'id': 40000059, - 'chart': 2, - 'hard': True, - 'clear': True, - 'fc': False, - 'ex': False, - 'score': 812300, - 'rate': 666, - 'expected_medal': 0x3, + "id": 40000059, + "chart": 2, + "hard": True, + "clear": True, + "fc": False, + "ex": False, + "score": 812300, + "rate": 666, + "expected_medal": 0x3, }, ] if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 50000045, - 'chart': 0, - 'hard': False, - 'clear': True, - 'fc': False, - 'ex': False, - 'score': 850000, - 'rate': 555, - 'expected_medal': 0x3, + "id": 50000045, + "chart": 0, + "hard": False, + "clear": True, + "fc": False, + "ex": False, + "score": 850000, + "rate": 555, + "expected_medal": 0x3, }, # A worse score on another same chart { - 'id': 40000059, - 'chart': 1, - 'hard': False, - 'clear': True, - 'fc': False, - 'ex': False, - 'score': 925000, - 'rate': 432, - 'expected_score': 990000, - 'expected_rate': 456, - 'expected_medal': 0x7, + "id": 40000059, + "chart": 1, + "hard": False, + "clear": True, + "fc": False, + "ex": False, + "score": 925000, + "rate": 432, + "expected_score": 990000, + "expected_rate": 456, + "expected_medal": 0x7, }, ] @@ -862,27 +943,33 @@ class JubeatFestoClient(BaseClient): scores = self.verify_gametop_get_mdata(jid) for score in dummyscores: - chart = score['chart'] + (3 if score['hard'] else 0) - newscore = scores[str(score['id'])][chart] + chart = score["chart"] + (3 if score["hard"] else 0) + newscore = scores[str(score["id"])][chart] - if 'expected_score' in score: - expected_score = score['expected_score'] + if "expected_score" in score: + expected_score = score["expected_score"] else: - expected_score = score['score'] + expected_score = score["score"] - if 'expected_rate' in score: - expected_rate = score['expected_rate'] + if "expected_rate" in score: + expected_rate = score["expected_rate"] else: - expected_rate = score['rate'] + expected_rate = score["rate"] - if newscore['score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{chart}\' but got score \'{newscore["score"]}\'') + if newscore["score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{chart}\' but got score \'{newscore["score"]}\'' + ) - if newscore['rate'] != expected_rate: - raise Exception(f'Expected a rate of \'{expected_rate}\' for song \'{score["id"]}\' chart \'{chart}\' but got rate \'{newscore["rate"]}\'') + if newscore["rate"] != expected_rate: + raise Exception( + f'Expected a rate of \'{expected_rate}\' for song \'{score["id"]}\' chart \'{chart}\' but got rate \'{newscore["rate"]}\'' + ) - if newscore['medal'] != score['expected_medal']: - raise Exception(f'Expected a medal of \'{score["expected_medal"]}\' for song \'{score["id"]}\' chart \'{chart}\' but got medal \'{newscore["medal"]}\'') + if newscore["medal"] != score["expected_medal"]: + raise Exception( + f'Expected a medal of \'{score["expected_medal"]}\' for song \'{score["id"]}\' chart \'{chart}\' but got medal \'{newscore["medal"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) diff --git a/bemani/client/jubeat/prop.py b/bemani/client/jubeat/prop.py index 8febec3..e9caa29 100644 --- a/bemani/client/jubeat/prop.py +++ b/bemani/client/jubeat/prop.py @@ -8,29 +8,29 @@ from bemani.protocol import Node class JubeatPropClient(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_shopinfo_regist(self) -> None: call = self.call_node() # Construct node - shopinfo = Node.void('shopinfo') - shopinfo.set_attribute('method', 'regist') + shopinfo = Node.void("shopinfo") + shopinfo.set_attribute("method", "regist") call.add_child(shopinfo) - shop = Node.void('shop') + shop = Node.void("shop") shopinfo.add_child(shop) - shop.add_child(Node.string('name', '')) - shop.add_child(Node.string('pref', 'JP-14')) - shop.add_child(Node.string('softwareid', '')) - shop.add_child(Node.string('systemid', self.pcbid)) - shop.add_child(Node.string('hardwareid', '01020304050607080900')) - shop.add_child(Node.string('locationid', 'US-1')) - shop.add_child(Node.string('monitor', 'D26L155 6252 151')) - testmode = Node.void('testmode') + shop.add_child(Node.string("name", "")) + shop.add_child(Node.string("pref", "JP-14")) + shop.add_child(Node.string("softwareid", "")) + shop.add_child(Node.string("systemid", self.pcbid)) + shop.add_child(Node.string("hardwareid", "01020304050607080900")) + shop.add_child(Node.string("locationid", "US-1")) + shop.add_child(Node.string("monitor", "D26L155 6252 151")) + testmode = Node.void("testmode") shop.add_child(testmode) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/shopinfo/data/cabid") @@ -41,12 +41,16 @@ class JubeatPropClient(BaseClient): self.assert_path(resp, "response/shopinfo/data/info/share_music") self.assert_path(resp, "response/shopinfo/data/info/bonus_music") self.assert_path(resp, "response/shopinfo/data/info/only_now_music") - self.assert_path(resp, "response/shopinfo/data/info/fc_challenge/today/music_id") + self.assert_path( + resp, "response/shopinfo/data/info/fc_challenge/today/music_id" + ) self.assert_path(resp, "response/shopinfo/data/info/white_music_list") self.assert_path(resp, "response/shopinfo/data/info/open_music_list") self.assert_path(resp, "response/shopinfo/data/info/cabinet_survey/id") self.assert_path(resp, "response/shopinfo/data/info/cabinet_survey/status") - self.assert_path(resp, "response/shopinfo/data/info/kaitou_bisco/remaining_days") + self.assert_path( + resp, "response/shopinfo/data/info/kaitou_bisco/remaining_days" + ) self.assert_path(resp, "response/shopinfo/data/info/league/status") self.assert_path(resp, "response/shopinfo/data/info/bistro/bistro_id") self.assert_path(resp, "response/shopinfo/data/info/jbox/point") @@ -57,12 +61,12 @@ class JubeatPropClient(BaseClient): call = self.call_node() # Construct node - demodata = Node.void('demodata') + demodata = Node.void("demodata") call.add_child(demodata) - demodata.set_attribute('method', 'get_news') + demodata.set_attribute("method", "get_news") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/demodata/data/officialnews") @@ -85,64 +89,64 @@ class JubeatPropClient(BaseClient): self.assert_path(resp, "response/gametop/data/info/jbox/emblem/premium/index") for item in [ - 'jubility', - 'jubility_yday', - 'tune_cnt', - 'save_cnt', - 'saved_cnt', - 'fc_cnt', - 'ex_cnt', - 'clear_cnt', - 'pf_cnt', - 'match_cnt', - 'beat_cnt', - 'mynews_cnt', - 'bonus_tune_points', - 'is_bonus_tune_played', - 'inherit', - 'mtg_entry_cnt', - 'mtg_hold_cnt', - 'mtg_result', + "jubility", + "jubility_yday", + "tune_cnt", + "save_cnt", + "saved_cnt", + "fc_cnt", + "ex_cnt", + "clear_cnt", + "pf_cnt", + "match_cnt", + "beat_cnt", + "mynews_cnt", + "bonus_tune_points", + "is_bonus_tune_played", + "inherit", + "mtg_entry_cnt", + "mtg_hold_cnt", + "mtg_result", ]: self.assert_path(resp, f"response/gametop/data/player/info/{item}") for item in [ - 'music_list', - 'secret_list', - 'theme_list', - 'marker_list', - 'title_list', - 'parts_list', - 'emblem_list', - 'new/secret_list', - 'new/theme_list', - 'new/marker_list', + "music_list", + "secret_list", + "theme_list", + "marker_list", + "title_list", + "parts_list", + "emblem_list", + "new/secret_list", + "new/theme_list", + "new/marker_list", ]: self.assert_path(resp, f"response/gametop/data/player/item/{item}") for item in [ - 'play_time', - 'shopname', - 'areaname', - 'expert_option', - 'category', - 'sort', - 'music_id', - 'seq_id', + "play_time", + "shopname", + "areaname", + "expert_option", + "category", + "sort", + "music_id", + "seq_id", ]: self.assert_path(resp, f"response/gametop/data/player/last/{item}") for item in [ - 'marker', - 'theme', - 'title', - 'parts', - 'rank_sort', - 'combo_disp', - 'emblem', - 'matching', - 'hazard', - 'hard', + "marker", + "theme", + "title", + "parts", + "rank_sort", + "combo_disp", + "emblem", + "matching", + "hazard", + "hard", ]: self.assert_path(resp, f"response/gametop/data/player/last/settings/{item}") @@ -161,18 +165,31 @@ class JubeatPropClient(BaseClient): self.assert_path(resp, "response/gametop/data/player/cabinet_survey/read_flag") self.assert_path(resp, "response/gametop/data/player/kaitou_bisco/read_flag") self.assert_path(resp, "response/gametop/data/player/navi/flag") - self.assert_path(resp, "response/gametop/data/player/fc_challenge/today/music_id") + self.assert_path( + resp, "response/gametop/data/player/fc_challenge/today/music_id" + ) self.assert_path(resp, "response/gametop/data/player/fc_challenge/today/state") - self.assert_path(resp, "response/gametop/data/player/fc_challenge/whim/music_id") + self.assert_path( + resp, "response/gametop/data/player/fc_challenge/whim/music_id" + ) self.assert_path(resp, "response/gametop/data/player/fc_challenge/whim/state") self.assert_path(resp, "response/gametop/data/player/news/checked") self.assert_path(resp, "response/gametop/data/player/news/checked_flag") self.assert_path(resp, "response/gametop/data/player/rivallist") - self.assert_path(resp, "response/gametop/data/player/free_first_play/is_available") + self.assert_path( + resp, "response/gametop/data/player/free_first_play/is_available" + ) self.assert_path(resp, "response/gametop/data/player/free_first_play/point") - self.assert_path(resp, "response/gametop/data/player/free_first_play/point_used") - self.assert_path(resp, "response/gametop/data/player/free_first_play/come_come_jbox/is_valid") - self.assert_path(resp, "response/gametop/data/player/free_first_play/come_come_jbox/end_time_if_paired") + self.assert_path( + resp, "response/gametop/data/player/free_first_play/point_used" + ) + self.assert_path( + resp, "response/gametop/data/player/free_first_play/come_come_jbox/is_valid" + ) + self.assert_path( + resp, + "response/gametop/data/player/free_first_play/come_come_jbox/end_time_if_paired", + ) self.assert_path(resp, "response/gametop/data/player/jbox/point") self.assert_path(resp, "response/gametop/data/player/jbox/emblem/normal/index") self.assert_path(resp, "response/gametop/data/player/jbox/emblem/premium/index") @@ -184,18 +201,22 @@ class JubeatPropClient(BaseClient): self.assert_path(resp, "response/gametop/data/player/league/class") self.assert_path(resp, "response/gametop/data/player/league/subclass") self.assert_path(resp, "response/gametop/data/player/new_music") - self.assert_path(resp, "response/gametop/data/player/eapass_privilege/emblem_list") + self.assert_path( + resp, "response/gametop/data/player/eapass_privilege/emblem_list" + ) self.assert_path(resp, "response/gametop/data/player/bonus_music/music") self.assert_path(resp, "response/gametop/data/player/bonus_music/event_id") self.assert_path(resp, "response/gametop/data/player/bonus_music/till_time") self.assert_path(resp, "response/gametop/data/player/bistro/chef/id") self.assert_path(resp, "response/gametop/data/player/bistro/carry_over") - self.assert_path(resp, "response/gametop/data/player/bistro/route_list/route_count") + self.assert_path( + resp, "response/gametop/data/player/bistro/route_list/route_count" + ) self.assert_path(resp, "response/gametop/data/player/bistro/extension") self.assert_path(resp, "response/gametop/data/player/gift_list") # Return the jid - return resp.child_value('gametop/data/player/jid') + return resp.child_value("gametop/data/player/jid") def verify_gameend_regist( self, @@ -208,104 +229,140 @@ class JubeatPropClient(BaseClient): call = self.call_node() # Construct node - gameend = Node.void('gameend') + gameend = Node.void("gameend") call.add_child(gameend) - gameend.set_attribute('method', 'regist') - gameend.add_child(Node.s32('retry', 0)) - data = Node.void('data') + gameend.set_attribute("method", "regist") + gameend.add_child(Node.s32("retry", 0)) + data = Node.void("data") gameend.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.string('refid', ref_id)) - player.add_child(Node.s32('jid', jid)) - player.add_child(Node.string('name', self.NAME)) - result = Node.void('result') + player.add_child(Node.string("refid", ref_id)) + player.add_child(Node.s32("jid", jid)) + player.add_child(Node.string("name", self.NAME)) + result = Node.void("result") data.add_child(result) - result.set_attribute('count', str(len(scores))) + result.set_attribute("count", str(len(scores))) # Send scores scoreid = 0 for score in scores: # Always played bits = 0x1 - if score['clear']: + if score["clear"]: bits |= 0x2 - if score['fc']: + if score["fc"]: bits |= 0x4 - if score['ex']: + if score["ex"]: bits |= 0x8 # Intentionally starting at 1 because that's what the game does scoreid = scoreid + 1 - tune = Node.void('tune') + tune = Node.void("tune") result.add_child(tune) - tune.set_attribute('id', str(scoreid)) - tune.set_attribute('count', '0') - tune.add_child(Node.s32('music', score['id'])) - player_1 = Node.void('player') + tune.set_attribute("id", str(scoreid)) + tune.set_attribute("count", "0") + tune.add_child(Node.s32("music", score["id"])) + player_1 = Node.void("player") tune.add_child(player_1) - player_1.set_attribute('rank', '1') - scorenode = Node.s32('score', score['score']) + player_1.set_attribute("rank", "1") + scorenode = Node.s32("score", score["score"]) player_1.add_child(scorenode) - scorenode.set_attribute('seq', str(score['chart'])) - scorenode.set_attribute('clear', str(bits)) - scorenode.set_attribute('combo', '69') - player_1.add_child(Node.u8_array('mbar', [239, 175, 170, 170, 190, 234, 187, 158, 153, 230, 170, 90, 102, 170, 85, 150, 150, 102, 85, 234, 171, 169, 157, 150, 170, 101, 230, 90, 214, 255])) + scorenode.set_attribute("seq", str(score["chart"])) + scorenode.set_attribute("clear", str(bits)) + scorenode.set_attribute("combo", "69") + player_1.add_child( + Node.u8_array( + "mbar", + [ + 239, + 175, + 170, + 170, + 190, + 234, + 187, + 158, + 153, + 230, + 170, + 90, + 102, + 170, + 85, + 150, + 150, + 102, + 85, + 234, + 171, + 169, + 157, + 150, + 170, + 101, + 230, + 90, + 214, + 255, + ], + ) + ) if len(course) > 0: - coursenode = Node.void('course') + coursenode = Node.void("course") player.add_child(coursenode) - coursenode.add_child(Node.s32('course_id', course['course_id'])) - coursenode.add_child(Node.u8('rating', course['rating'])) + coursenode.add_child(Node.s32("course_id", course["course_id"])) + coursenode.add_child(Node.u8("rating", course["rating"])) index = 0 - for coursescore in course['scores']: - music = Node.void('music') + for coursescore in course["scores"]: + music = Node.void("music") coursenode.add_child(music) - music.set_attribute('index', str(index)) - music.add_child(Node.s32('score', coursescore)) + music.set_attribute("index", str(index)) + music.add_child(Node.s32("score", coursescore)) index = index + 1 if league is not None: - leaguenode = Node.void('league') + leaguenode = Node.void("league") player.add_child(leaguenode) - leaguenode.add_child(Node.s32('league_id', league[0])) - leaguenode.add_child(Node.bool('is_first_play', False)) - leaguenode.add_child(Node.bool('is_checked', True)) + leaguenode.add_child(Node.s32("league_id", league[0])) + leaguenode.add_child(Node.bool("is_first_play", False)) + leaguenode.add_child(Node.bool("is_checked", True)) index = 0 for leaguescore in league[1]: - musicnode = Node.void('music') + musicnode = Node.void("music") leaguenode.add_child(musicnode) - musicnode.set_attribute('index', str(index)) + musicnode.set_attribute("index", str(index)) index = index + 1 - scorenode = Node.s32('score', leaguescore) + scorenode = Node.s32("score", leaguescore) musicnode.add_child(scorenode) - scorenode.set_attribute('clear', '3') + scorenode.set_attribute("clear", "3") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/gameend/data/player/session_id") def verify_gametop_regist(self, card_id: str, ref_id: str) -> int: call = self.call_node() # Construct node - gametop = Node.void('gametop') + gametop = Node.void("gametop") call.add_child(gametop) - gametop.set_attribute('method', 'regist') - data = Node.void('data') + gametop.set_attribute("method", "regist") + data = Node.void("data") gametop.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.string('refid', ref_id)) - player.add_child(Node.string('datid', ref_id)) - player.add_child(Node.string('uid', card_id)) - player.add_child(Node.bool('inherit', True)) - player.add_child(Node.string('name', self.NAME)) + player.add_child(Node.string("refid", ref_id)) + player.add_child(Node.string("datid", ref_id)) + player.add_child(Node.string("uid", card_id)) + player.add_child(Node.bool("inherit", True)) + player.add_child(Node.string("name", self.NAME)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist return self.__verify_profile(resp) @@ -314,22 +371,22 @@ class JubeatPropClient(BaseClient): call = self.call_node() # Construct node - gametop = Node.void('gametop') + gametop = Node.void("gametop") call.add_child(gametop) - gametop.set_attribute('method', 'get_pdata') - retry = Node.s32('retry', 0) + gametop.set_attribute("method", "get_pdata") + retry = Node.s32("retry", 0) gametop.add_child(retry) - data = Node.void('data') + data = Node.void("data") gametop.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.string('refid', ref_id)) - player.add_child(Node.string('datid', ref_id)) - player.add_child(Node.string('uid', card_id)) - player.add_child(Node.string('card_no', CardCipher.encode(card_id))) + player.add_child(Node.string("refid", ref_id)) + player.add_child(Node.string("datid", ref_id)) + player.add_child(Node.string("uid", card_id)) + player.add_child(Node.string("card_no", CardCipher.encode(card_id))) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist return self.__verify_profile(resp) @@ -338,33 +395,33 @@ class JubeatPropClient(BaseClient): call = self.call_node() # Construct node - gametop = Node.void('gametop') + gametop = Node.void("gametop") call.add_child(gametop) - gametop.set_attribute('method', 'get_mdata') - retry = Node.s32('retry', 0) + gametop.set_attribute("method", "get_mdata") + retry = Node.s32("retry", 0) gametop.add_child(retry) - data = Node.void('data') + data = Node.void("data") gametop.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.s32('jid', jid)) + player.add_child(Node.s32("jid", jid)) # Technically the game sends this same packet 3 times, one with # each value 1, 2, 3 here. Unclear why, but we won't emulate it. - player.add_child(Node.s8('mdata_ver', 1)) - player.add_child(Node.bool('rival', False)) + player.add_child(Node.s8("mdata_ver", 1)) + player.add_child(Node.bool("rival", False)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Parse out scores self.assert_path(resp, "response/gametop/data/player/mdata_list") ret = {} - for musicdata in resp.child('gametop/data/player/mdata_list').children: - if musicdata.name != 'musicdata': - raise Exception('Unexpected node in playdata!') + for musicdata in resp.child("gametop/data/player/mdata_list").children: + if musicdata.name != "musicdata": + raise Exception("Unexpected node in playdata!") - music_id = musicdata.attribute('music_id') + music_id = musicdata.attribute("music_id") scores_by_chart: List[Dict[str, int]] = [{}, {}, {}] def extract_cnts(name: str, val: List[int]) -> None: @@ -372,12 +429,12 @@ class JubeatPropClient(BaseClient): scores_by_chart[1][name] = val[1] scores_by_chart[2][name] = val[2] - extract_cnts('plays', musicdata.child_value('play_cnt')) - extract_cnts('clears', musicdata.child_value('clear_cnt')) - extract_cnts('full_combos', musicdata.child_value('fc_cnt')) - extract_cnts('excellents', musicdata.child_value('ex_cnt')) - extract_cnts('score', musicdata.child_value('score')) - extract_cnts('medal', musicdata.child_value('clear')) + extract_cnts("plays", musicdata.child_value("play_cnt")) + extract_cnts("clears", musicdata.child_value("clear_cnt")) + extract_cnts("full_combos", musicdata.child_value("fc_cnt")) + extract_cnts("excellents", musicdata.child_value("ex_cnt")) + extract_cnts("score", musicdata.child_value("score")) + extract_cnts("medal", musicdata.child_value("clear")) ret[music_id] = scores_by_chart return ret @@ -386,18 +443,18 @@ class JubeatPropClient(BaseClient): call = self.call_node() # Construct node - gametop = Node.void('gametop') + gametop = Node.void("gametop") call.add_child(gametop) - gametop.set_attribute('method', 'get_course') - gametop.add_child(Node.s32('retry', 0)) - data = Node.void('data') + gametop.set_attribute("method", "get_course") + gametop.add_child(Node.s32("retry", 0)) + data = Node.void("data") gametop.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.s32('jid', jid)) + player.add_child(Node.s32("jid", jid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify expected nodes self.assert_path(resp, "response/gametop/data/course_list") @@ -405,8 +462,8 @@ class JubeatPropClient(BaseClient): self.assert_path(resp, "response/gametop/data/last_course_id") playernode = None - for player in resp.child('gametop/data/player_list').children: - if player.child_value('jid') == jid: + for player in resp.child("gametop/data/player_list").children: + if player.child_value("jid") == jid: playernode = player break @@ -414,15 +471,15 @@ class JubeatPropClient(BaseClient): raise Exception(f"Didn't find any scores for ExtID {jid}") ret = [] - for result in playernode.child('result_list').children: - if result.name != 'result': - raise Exception('Unexpected node in result_list!') + for result in playernode.child("result_list").children: + if result.name != "result": + raise Exception("Unexpected node in result_list!") - course_id = result.child_value('id') - rating = result.child_value('rating') - scores = result.child_value('score') + course_id = result.child_value("id") + rating = result.child_value("rating") + scores = result.child_value("score") - ret.append({'course_id': course_id, 'rating': rating, 'scores': scores}) + ret.append({"course_id": course_id, "rating": rating, "scores": scores}) return ret @@ -430,19 +487,19 @@ class JubeatPropClient(BaseClient): call = self.call_node() # Construct node - gametop = Node.void('gametop') + gametop = Node.void("gametop") call.add_child(gametop) - gametop.set_attribute('method', 'get_league') - gametop.add_child(Node.s32('retry', 0)) - data = Node.void('data') + gametop.set_attribute("method", "get_league") + gametop.add_child(Node.s32("retry", 0)) + data = Node.void("data") gametop.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.s32('jid', jid)) - player.add_child(Node.s32_array('rival_jid', [0] * 3)) + player.add_child(Node.s32("jid", jid)) + player.add_child(Node.s32_array("rival_jid", [0] * 3)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify expected nodes self.assert_path(resp, "response/gametop/data/league_list/league/player_list") @@ -450,17 +507,19 @@ class JubeatPropClient(BaseClient): self.assert_path(resp, "response/gametop/data/last_subclass") self.assert_path(resp, "response/gametop/data/is_checked") - leagueid = resp.child_value('gametop/data/league_list/league/id') + leagueid = resp.child_value("gametop/data/league_list/league/id") playernode = None - for player in resp.child('gametop/data/league_list/league/player_list').children: - if player.child_value('jid') == jid: + for player in resp.child( + "gametop/data/league_list/league/player_list" + ).children: + if player.child_value("jid") == jid: playernode = player break if playernode is None: raise Exception(f"Didn't find any scores for ExtID {jid}") - result = playernode.child_value('result/score') + result = playernode.child_value("result/score") if result is not None: return (leagueid, (result[0], result[1], result[2])) else: @@ -470,18 +529,18 @@ class JubeatPropClient(BaseClient): call = self.call_node() # Construct node - gametop = Node.void('gametop') + gametop = Node.void("gametop") call.add_child(gametop) - gametop.set_attribute('method', 'get_meeting') - gametop.add_child(Node.s32('retry', 0)) - data = Node.void('data') + gametop.set_attribute("method", "get_meeting") + gametop.add_child(Node.s32("retry", 0)) + data = Node.void("data") gametop.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.s32('jid', jid)) + player.add_child(Node.s32("jid", jid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify expected nodes self.assert_path(resp, "response/gametop/data/meeting/single") @@ -493,12 +552,12 @@ class JubeatPropClient(BaseClient): call = self.call_node() # Construct node - gametop = Node.void('demodata') + gametop = Node.void("demodata") call.add_child(gametop) - gametop.set_attribute('method', 'get_hitchart') + gametop.set_attribute("method", "get_hitchart") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify expected nodes self.assert_path(resp, "response/demodata/data/update") @@ -509,20 +568,20 @@ class JubeatPropClient(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -542,22 +601,32 @@ class JubeatPropClient(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") self.verify_gametop_regist(card, ref_id) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") if cardid is None: # Verify score handling @@ -567,94 +636,94 @@ class JubeatPropClient(BaseClient): league = self.verify_gametop_get_league(jid) self.verify_gametop_get_meeting(jid) if scores is None: - raise Exception('Expected to get scores back, didn\'t get anything!') + raise Exception("Expected to get scores back, didn't get anything!") if courses is None: - raise Exception('Expected to get courses back, didn\'t get anything!') + raise Exception("Expected to get courses back, didn't get anything!") if league is None: - raise Exception('Expected to get league back, didn\'t get anything!') + raise Exception("Expected to get league back, didn't get anything!") if len(scores) > 0: - raise Exception('Got nonzero score count on a new card!') + raise Exception("Got nonzero score count on a new card!") if len(courses) > 0: - raise Exception('Got nonzero course count on a new card!') + raise Exception("Got nonzero course count on a new card!") if league[1][0] != 0 or league[1][1] != 0 or league[1][2] != 0: - raise Exception('Got nonzero league score on a new card!') + raise Exception("Got nonzero league score on a new card!") for phase in [1, 2]: if phase == 1: dummyscores = [ # An okay score on a chart { - 'id': 40000059, - 'chart': 2, - 'clear': True, - 'fc': False, - 'ex': False, - 'score': 800000, - 'expected_medal': 0x3, + "id": 40000059, + "chart": 2, + "clear": True, + "fc": False, + "ex": False, + "score": 800000, + "expected_medal": 0x3, }, # A good score on an easier chart of the same song { - 'id': 40000059, - 'chart': 1, - 'clear': True, - 'fc': True, - 'ex': False, - 'score': 990000, - 'expected_medal': 0x5, + "id": 40000059, + "chart": 1, + "clear": True, + "fc": True, + "ex": False, + "score": 990000, + "expected_medal": 0x5, }, # A perfect score on an easiest chart of the same song { - 'id': 40000059, - 'chart': 0, - 'clear': True, - 'fc': True, - 'ex': True, - 'score': 1000000, - 'expected_medal': 0x9, + "id": 40000059, + "chart": 0, + "clear": True, + "fc": True, + "ex": True, + "score": 1000000, + "expected_medal": 0x9, }, # A bad score on a hard chart { - 'id': 30000024, - 'chart': 2, - 'clear': False, - 'fc': False, - 'ex': False, - 'score': 400000, - 'expected_medal': 0x1, + "id": 30000024, + "chart": 2, + "clear": False, + "fc": False, + "ex": False, + "score": 400000, + "expected_medal": 0x1, }, # A terrible score on an easy chart { - 'id': 50000045, - 'chart': 0, - 'clear': False, - 'fc': False, - 'ex': False, - 'score': 100000, - 'expected_medal': 0x1, + "id": 50000045, + "chart": 0, + "clear": False, + "fc": False, + "ex": False, + "score": 100000, + "expected_medal": 0x1, }, ] if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 50000045, - 'chart': 0, - 'clear': True, - 'fc': False, - 'ex': False, - 'score': 850000, - 'expected_medal': 0x3, + "id": 50000045, + "chart": 0, + "clear": True, + "fc": False, + "ex": False, + "score": 850000, + "expected_medal": 0x3, }, # A worse score on another same chart { - 'id': 40000059, - 'chart': 1, - 'clear': True, - 'fc': False, - 'ex': False, - 'score': 925000, - 'expected_score': 990000, - 'expected_medal': 0x7, + "id": 40000059, + "chart": 1, + "clear": True, + "fc": False, + "ex": False, + "score": 925000, + "expected_score": 990000, + "expected_medal": 0x7, }, ] @@ -664,23 +733,31 @@ class JubeatPropClient(BaseClient): courses = self.verify_gametop_get_course(jid) league = self.verify_gametop_get_league(jid) if len(courses) > 0: - raise Exception('Got nonzero course count without playing any courses!') + raise Exception( + "Got nonzero course count without playing any courses!" + ) if league[1][0] != 0 or league[1][1] != 0 or league[1][2] != 0: - raise Exception('Got nonzero league count without playing any league!') + raise Exception( + "Got nonzero league count without playing any league!" + ) for score in dummyscores: - newscore = scores[str(score['id'])][score['chart']] + newscore = scores[str(score["id"])][score["chart"]] - if 'expected_score' in score: - expected_score = score['expected_score'] + if "expected_score" in score: + expected_score = score["expected_score"] else: - expected_score = score['score'] + expected_score = score["score"] - if newscore['score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{newscore["score"]}\'') + if newscore["score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{newscore["score"]}\'' + ) - if newscore['medal'] != score['expected_medal']: - raise Exception(f'Expected a medal of \'{score["expected_medal"]}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got medal \'{newscore["medal"]}\'') + if newscore["medal"] != score["expected_medal"]: + raise Exception( + f'Expected a medal of \'{score["expected_medal"]}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got medal \'{newscore["medal"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) @@ -688,33 +765,43 @@ class JubeatPropClient(BaseClient): for phase in [1, 2]: dummycourses: List[Dict[str, Any]] = [] if phase == 1: - dummycourses.extend([ - { - 'course_id': 1, - 'rating': 1, - 'scores': [123456, 123457, 123458, 123459, 123460], - }, - { - 'course_id': 2, - 'rating': 2, - 'scores': [123456, 123457, 123458, 123459, 123460], - }, - ]) + dummycourses.extend( + [ + { + "course_id": 1, + "rating": 1, + "scores": [123456, 123457, 123458, 123459, 123460], + }, + { + "course_id": 2, + "rating": 2, + "scores": [123456, 123457, 123458, 123459, 123460], + }, + ] + ) else: - dummycourses.extend([ - { - 'course_id': 1, - 'rating': 2, - 'scores': [223456, 223457, 223458, 223459, 223460], - }, - { - 'course_id': 2, - 'rating': 1, - 'expected_rating': 2, - 'scores': [23456, 23457, 23458, 23459, 23460], - 'expected_scores': [123456, 123457, 123458, 123459, 123460], - }, - ]) + dummycourses.extend( + [ + { + "course_id": 1, + "rating": 2, + "scores": [223456, 223457, 223458, 223459, 223460], + }, + { + "course_id": 2, + "rating": 1, + "expected_rating": 2, + "scores": [23456, 23457, 23458, 23459, 23460], + "expected_scores": [ + 123456, + 123457, + 123458, + 123459, + 123460, + ], + }, + ] + ) for course in dummycourses: self.verify_gameend_regist(ref_id, jid, [], course, None) @@ -723,50 +810,74 @@ class JubeatPropClient(BaseClient): for course in dummycourses: # Find the course - foundcourses = [c for c in courses if c['course_id'] == course['course_id']] + foundcourses = [ + c for c in courses if c["course_id"] == course["course_id"] + ] if len(foundcourses) == 0: - raise Exception(f"Didn't find course by ID {course['course_id']}") + raise Exception( + f"Didn't find course by ID {course['course_id']}" + ) foundcourse = foundcourses[0] - if 'expected_rating' in course: - expected_rating = course['expected_rating'] + if "expected_rating" in course: + expected_rating = course["expected_rating"] else: - expected_rating = course['rating'] + expected_rating = course["rating"] - if 'expected_scores' in course: - expected_scores = course['expected_scores'] + if "expected_scores" in course: + expected_scores = course["expected_scores"] else: - expected_scores = course['scores'] + expected_scores = course["scores"] - if foundcourse['course_id'] != course['course_id']: + if foundcourse["course_id"] != course["course_id"]: raise Exception("Logic error!") - if foundcourse['rating'] != expected_rating: - raise Exception(f'Expected a rating of \'{expected_rating}\' for course \'{course["course_id"]}\' but got rating \'{foundcourse["rating"]}\'') + if foundcourse["rating"] != expected_rating: + raise Exception( + f'Expected a rating of \'{expected_rating}\' for course \'{course["course_id"]}\' but got rating \'{foundcourse["rating"]}\'' + ) for i in range(len(expected_scores)): - if foundcourse['scores'][i] != expected_scores[i]: - raise Exception(f'Expected a score of \'{expected_scores[i]}\' for course \'{course["course_id"]}\' song number \'{i}\' but got score \'{foundcourse["scores"][i]}\'') + if foundcourse["scores"][i] != expected_scores[i]: + raise Exception( + f'Expected a score of \'{expected_scores[i]}\' for course \'{course["course_id"]}\' song number \'{i}\' but got score \'{foundcourse["scores"][i]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) # Play a league course, save the score - self.verify_gameend_regist(ref_id, jid, [], {}, (league[0], (123456, 234567, 345678))) + self.verify_gameend_regist( + ref_id, jid, [], {}, (league[0], (123456, 234567, 345678)) + ) jid = self.verify_gametop_get_pdata(card, ref_id) league = self.verify_gametop_get_league(jid) - if league[1][0] != 123456 or league[1][1] != 234567 or league[1][2] != 345678: - raise Exception(f'League score didn\t save! Got wrong values {league[1][0]}, {league[1][1]}, {league[1][2]} back!') + if ( + league[1][0] != 123456 + or league[1][1] != 234567 + or league[1][2] != 345678 + ): + raise Exception( + f"League score didn\t save! Got wrong values {league[1][0]}, {league[1][1]}, {league[1][2]} back!" + ) # Play a league course, do worse, make sure it doesn't overwrite - self.verify_gameend_regist(ref_id, jid, [], {}, (league[0], (12345, 23456, 34567))) + self.verify_gameend_regist( + ref_id, jid, [], {}, (league[0], (12345, 23456, 34567)) + ) jid = self.verify_gametop_get_pdata(card, ref_id) league = self.verify_gametop_get_league(jid) - if league[1][0] != 123456 or league[1][1] != 234567 or league[1][2] != 345678: - raise Exception(f'League score got overwritten! Got wrong values {league[1][0]}, {league[1][1]}, {league[1][2]} back!') + if ( + league[1][0] != 123456 + or league[1][1] != 234567 + or league[1][2] != 345678 + ): + raise Exception( + f"League score got overwritten! Got wrong values {league[1][0]}, {league[1][1]}, {league[1][2]} back!" + ) else: print("Skipping score checks for existing card") diff --git a/bemani/client/jubeat/qubell.py b/bemani/client/jubeat/qubell.py index 3039d0f..8a9484e 100644 --- a/bemani/client/jubeat/qubell.py +++ b/bemani/client/jubeat/qubell.py @@ -8,29 +8,29 @@ from bemani.protocol import Node class JubeatQubellClient(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_shopinfo_regist(self) -> None: call = self.call_node() # Construct node - shopinfo = Node.void('shopinfo') - shopinfo.set_attribute('method', 'regist') + shopinfo = Node.void("shopinfo") + shopinfo.set_attribute("method", "regist") call.add_child(shopinfo) - shop = Node.void('shop') + shop = Node.void("shop") shopinfo.add_child(shop) - shop.add_child(Node.string('name', '')) - shop.add_child(Node.string('pref', 'JP-14')) - shop.add_child(Node.string('softwareid', '')) - shop.add_child(Node.string('systemid', self.pcbid)) - shop.add_child(Node.string('hardwareid', '01020304050607080900')) - shop.add_child(Node.string('locationid', 'US-1')) - shop.add_child(Node.string('monitor', 'D26L155 6252 151')) - testmode = Node.void('testmode') + shop.add_child(Node.string("name", "")) + shop.add_child(Node.string("pref", "JP-14")) + shop.add_child(Node.string("softwareid", "")) + shop.add_child(Node.string("systemid", self.pcbid)) + shop.add_child(Node.string("hardwareid", "01020304050607080900")) + shop.add_child(Node.string("locationid", "US-1")) + shop.add_child(Node.string("monitor", "D26L155 6252 151")) + testmode = Node.void("testmode") shop.add_child(testmode) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/shopinfo/data/cabid") @@ -58,12 +58,12 @@ class JubeatQubellClient(BaseClient): call = self.call_node() # Construct node - demodata = Node.void('demodata') + demodata = Node.void("demodata") call.add_child(demodata) - demodata.set_attribute('method', 'get_news') + demodata.set_attribute("method", "get_news") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/demodata/data/officialnews") @@ -87,63 +87,63 @@ class JubeatQubellClient(BaseClient): self.assert_path(resp, "response/gametop/data/info/generic_dig/map_list") for item in [ - 'jubility', - 'jubility_yday', - 'tune_cnt', - 'save_cnt', - 'saved_cnt', - 'fc_cnt', - 'ex_cnt', - 'clear_cnt', - 'match_cnt', - 'beat_cnt', - 'mynews_cnt', - 'bonus_tune_points', - 'is_bonus_tune_played', - 'inherit', - 'mtg_entry_cnt', - 'mtg_hold_cnt', - 'mtg_result', + "jubility", + "jubility_yday", + "tune_cnt", + "save_cnt", + "saved_cnt", + "fc_cnt", + "ex_cnt", + "clear_cnt", + "match_cnt", + "beat_cnt", + "mynews_cnt", + "bonus_tune_points", + "is_bonus_tune_played", + "inherit", + "mtg_entry_cnt", + "mtg_hold_cnt", + "mtg_result", ]: self.assert_path(resp, f"response/gametop/data/player/info/{item}") for item in [ - 'music_list', - 'secret_list', - 'theme_list', - 'marker_list', - 'title_list', - 'parts_list', - 'emblem_list', - 'new/secret_list', - 'new/theme_list', - 'new/marker_list', + "music_list", + "secret_list", + "theme_list", + "marker_list", + "title_list", + "parts_list", + "emblem_list", + "new/secret_list", + "new/theme_list", + "new/marker_list", ]: self.assert_path(resp, f"response/gametop/data/player/item/{item}") for item in [ - 'play_time', - 'shopname', - 'areaname', - 'expert_option', - 'category', - 'sort', - 'music_id', - 'seq_id', + "play_time", + "shopname", + "areaname", + "expert_option", + "category", + "sort", + "music_id", + "seq_id", ]: self.assert_path(resp, f"response/gametop/data/player/last/{item}") for item in [ - 'marker', - 'theme', - 'title', - 'parts', - 'rank_sort', - 'combo_disp', - 'emblem', - 'matching', - 'hazard', - 'hard', + "marker", + "theme", + "title", + "parts", + "rank_sort", + "combo_disp", + "emblem", + "matching", + "hazard", + "hard", ]: self.assert_path(resp, f"response/gametop/data/player/last/settings/{item}") @@ -160,14 +160,20 @@ class JubeatQubellClient(BaseClient): self.assert_path(resp, "response/gametop/data/player/lab_edit_seq") self.assert_path(resp, "response/gametop/data/player/event_info") self.assert_path(resp, "response/gametop/data/player/navi/flag") - self.assert_path(resp, "response/gametop/data/player/fc_challenge/today/music_id") + self.assert_path( + resp, "response/gametop/data/player/fc_challenge/today/music_id" + ) self.assert_path(resp, "response/gametop/data/player/fc_challenge/today/state") - self.assert_path(resp, "response/gametop/data/player/fc_challenge/whim/music_id") + self.assert_path( + resp, "response/gametop/data/player/fc_challenge/whim/music_id" + ) self.assert_path(resp, "response/gametop/data/player/fc_challenge/whim/state") self.assert_path(resp, "response/gametop/data/player/news/checked") self.assert_path(resp, "response/gametop/data/player/news/checked_flag") self.assert_path(resp, "response/gametop/data/player/rivallist") - self.assert_path(resp, "response/gametop/data/player/free_first_play/is_available") + self.assert_path( + resp, "response/gametop/data/player/free_first_play/is_available" + ) self.assert_path(resp, "response/gametop/data/player/jbox/point") self.assert_path(resp, "response/gametop/data/player/jbox/emblem/normal/index") self.assert_path(resp, "response/gametop/data/player/jbox/emblem/premium/index") @@ -183,17 +189,31 @@ class JubeatQubellClient(BaseClient): self.assert_path(resp, "response/gametop/data/player/digdig/eternal/ratio") self.assert_path(resp, "response/gametop/data/player/digdig/eternal/used_point") self.assert_path(resp, "response/gametop/data/player/digdig/eternal/point") - self.assert_path(resp, "response/gametop/data/player/digdig/eternal/excavated_point") + self.assert_path( + resp, "response/gametop/data/player/digdig/eternal/excavated_point" + ) self.assert_path(resp, "response/gametop/data/player/digdig/eternal/cube/state") - self.assert_path(resp, "response/gametop/data/player/digdig/eternal/cube/item/kind") - self.assert_path(resp, "response/gametop/data/player/digdig/eternal/cube/item/value") - self.assert_path(resp, "response/gametop/data/player/digdig/eternal/cube/norma/till_time") - self.assert_path(resp, "response/gametop/data/player/digdig/eternal/cube/norma/kind") - self.assert_path(resp, "response/gametop/data/player/digdig/eternal/cube/norma/value") - self.assert_path(resp, "response/gametop/data/player/digdig/eternal/cube/norma/param") + self.assert_path( + resp, "response/gametop/data/player/digdig/eternal/cube/item/kind" + ) + self.assert_path( + resp, "response/gametop/data/player/digdig/eternal/cube/item/value" + ) + self.assert_path( + resp, "response/gametop/data/player/digdig/eternal/cube/norma/till_time" + ) + self.assert_path( + resp, "response/gametop/data/player/digdig/eternal/cube/norma/kind" + ) + self.assert_path( + resp, "response/gametop/data/player/digdig/eternal/cube/norma/value" + ) + self.assert_path( + resp, "response/gametop/data/player/digdig/eternal/cube/norma/param" + ) # Return the jid - return resp.child_value('gametop/data/player/jid') + return resp.child_value("gametop/data/player/jid") def verify_gameend_regist( self, @@ -204,74 +224,110 @@ class JubeatQubellClient(BaseClient): call = self.call_node() # Construct node - gameend = Node.void('gameend') + gameend = Node.void("gameend") call.add_child(gameend) - gameend.set_attribute('method', 'regist') - gameend.add_child(Node.s32('retry', 0)) - data = Node.void('data') + gameend.set_attribute("method", "regist") + gameend.add_child(Node.s32("retry", 0)) + data = Node.void("data") gameend.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.string('refid', ref_id)) - player.add_child(Node.s32('jid', jid)) - player.add_child(Node.string('name', self.NAME)) - result = Node.void('result') + player.add_child(Node.string("refid", ref_id)) + player.add_child(Node.s32("jid", jid)) + player.add_child(Node.string("name", self.NAME)) + result = Node.void("result") data.add_child(result) - result.set_attribute('count', str(len(scores))) + result.set_attribute("count", str(len(scores))) # Send scores scoreid = 0 for score in scores: # Always played bits = 0x1 - if score['clear']: + if score["clear"]: bits |= 0x2 - if score['fc']: + if score["fc"]: bits |= 0x4 - if score['ex']: + if score["ex"]: bits |= 0x8 # Intentionally starting at 1 because that's what the game does scoreid = scoreid + 1 - tune = Node.void('tune') + tune = Node.void("tune") result.add_child(tune) - tune.set_attribute('id', str(scoreid)) - tune.set_attribute('count', '0') - tune.add_child(Node.s32('music', score['id'])) - tune.add_child(Node.s64('timestamp', Time.now() * 1000)) - player_1 = Node.void('player') + tune.set_attribute("id", str(scoreid)) + tune.set_attribute("count", "0") + tune.add_child(Node.s32("music", score["id"])) + tune.add_child(Node.s64("timestamp", Time.now() * 1000)) + player_1 = Node.void("player") tune.add_child(player_1) - player_1.set_attribute('rank', '1') - scorenode = Node.s32('score', score['score']) + player_1.set_attribute("rank", "1") + scorenode = Node.s32("score", score["score"]) player_1.add_child(scorenode) - scorenode.set_attribute('seq', str(score['chart'])) - scorenode.set_attribute('clear', str(bits)) - scorenode.set_attribute('combo', '69') - player_1.add_child(Node.u8_array('mbar', [239, 175, 170, 170, 190, 234, 187, 158, 153, 230, 170, 90, 102, 170, 85, 150, 150, 102, 85, 234, 171, 169, 157, 150, 170, 101, 230, 90, 214, 255])) + scorenode.set_attribute("seq", str(score["chart"])) + scorenode.set_attribute("clear", str(bits)) + scorenode.set_attribute("combo", "69") + player_1.add_child( + Node.u8_array( + "mbar", + [ + 239, + 175, + 170, + 170, + 190, + 234, + 187, + 158, + 153, + 230, + 170, + 90, + 102, + 170, + 85, + 150, + 150, + 102, + 85, + 234, + 171, + 169, + 157, + 150, + 170, + 101, + 230, + 90, + 214, + 255, + ], + ) + ) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/gameend/data/player/session_id") def verify_gametop_regist(self, card_id: str, ref_id: str) -> int: call = self.call_node() # Construct node - gametop = Node.void('gametop') + gametop = Node.void("gametop") call.add_child(gametop) - gametop.set_attribute('method', 'regist') - data = Node.void('data') + gametop.set_attribute("method", "regist") + data = Node.void("data") gametop.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.string('refid', ref_id)) - player.add_child(Node.string('datid', ref_id)) - player.add_child(Node.string('uid', card_id)) - player.add_child(Node.bool('inherit', True)) - player.add_child(Node.string('name', self.NAME)) + player.add_child(Node.string("refid", ref_id)) + player.add_child(Node.string("datid", ref_id)) + player.add_child(Node.string("uid", card_id)) + player.add_child(Node.bool("inherit", True)) + player.add_child(Node.string("name", self.NAME)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist return self.__verify_profile(resp) @@ -280,22 +336,22 @@ class JubeatQubellClient(BaseClient): call = self.call_node() # Construct node - gametop = Node.void('gametop') + gametop = Node.void("gametop") call.add_child(gametop) - gametop.set_attribute('method', 'get_pdata') - retry = Node.s32('retry', 0) + gametop.set_attribute("method", "get_pdata") + retry = Node.s32("retry", 0) gametop.add_child(retry) - data = Node.void('data') + data = Node.void("data") gametop.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.string('refid', ref_id)) - player.add_child(Node.string('datid', ref_id)) - player.add_child(Node.string('uid', card_id)) - player.add_child(Node.string('card_no', CardCipher.encode(card_id))) + player.add_child(Node.string("refid", ref_id)) + player.add_child(Node.string("datid", ref_id)) + player.add_child(Node.string("uid", card_id)) + player.add_child(Node.string("card_no", CardCipher.encode(card_id))) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist return self.__verify_profile(resp) @@ -304,33 +360,33 @@ class JubeatQubellClient(BaseClient): call = self.call_node() # Construct node - gametop = Node.void('gametop') + gametop = Node.void("gametop") call.add_child(gametop) - gametop.set_attribute('method', 'get_mdata') - retry = Node.s32('retry', 0) + gametop.set_attribute("method", "get_mdata") + retry = Node.s32("retry", 0) gametop.add_child(retry) - data = Node.void('data') + data = Node.void("data") gametop.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.s32('jid', jid)) + player.add_child(Node.s32("jid", jid)) # Technically the game sends this same packet 3 times, one with # each value 1, 2, 3 here. Unclear why, but we won't emulate it. - player.add_child(Node.s8('mdata_ver', 1)) - player.add_child(Node.bool('rival', False)) + player.add_child(Node.s8("mdata_ver", 1)) + player.add_child(Node.bool("rival", False)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Parse out scores self.assert_path(resp, "response/gametop/data/player/mdata_list") ret = {} - for musicdata in resp.child('gametop/data/player/mdata_list').children: - if musicdata.name != 'musicdata': - raise Exception('Unexpected node in playdata!') + for musicdata in resp.child("gametop/data/player/mdata_list").children: + if musicdata.name != "musicdata": + raise Exception("Unexpected node in playdata!") - music_id = musicdata.attribute('music_id') + music_id = musicdata.attribute("music_id") scores_by_chart: List[Dict[str, int]] = [{}, {}, {}] def extract_cnts(name: str, val: List[int]) -> None: @@ -338,12 +394,12 @@ class JubeatQubellClient(BaseClient): scores_by_chart[1][name] = val[1] scores_by_chart[2][name] = val[2] - extract_cnts('plays', musicdata.child_value('play_cnt')) - extract_cnts('clears', musicdata.child_value('clear_cnt')) - extract_cnts('full_combos', musicdata.child_value('fc_cnt')) - extract_cnts('excellents', musicdata.child_value('ex_cnt')) - extract_cnts('score', musicdata.child_value('score')) - extract_cnts('medal', musicdata.child_value('clear')) + extract_cnts("plays", musicdata.child_value("play_cnt")) + extract_cnts("clears", musicdata.child_value("clear_cnt")) + extract_cnts("full_combos", musicdata.child_value("fc_cnt")) + extract_cnts("excellents", musicdata.child_value("ex_cnt")) + extract_cnts("score", musicdata.child_value("score")) + extract_cnts("medal", musicdata.child_value("clear")) ret[music_id] = scores_by_chart return ret @@ -352,18 +408,18 @@ class JubeatQubellClient(BaseClient): call = self.call_node() # Construct node - gametop = Node.void('gametop') + gametop = Node.void("gametop") call.add_child(gametop) - gametop.set_attribute('method', 'get_meeting') - gametop.add_child(Node.s32('retry', 0)) - data = Node.void('data') + gametop.set_attribute("method", "get_meeting") + gametop.add_child(Node.s32("retry", 0)) + data = Node.void("data") gametop.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.s32('jid', jid)) + player.add_child(Node.s32("jid", jid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify expected nodes self.assert_path(resp, "response/gametop/data/meeting/single") @@ -375,17 +431,17 @@ class JubeatQubellClient(BaseClient): call = self.call_node() # Construct node - recommend = Node.void('recommend') + recommend = Node.void("recommend") call.add_child(recommend) - recommend.set_attribute('method', 'get_recommend') - recommend.add_child(Node.s32('retry', 0)) - player = Node.void('player') + recommend.set_attribute("method", "get_recommend") + recommend.add_child(Node.s32("retry", 0)) + player = Node.void("player") recommend.add_child(player) - player.add_child(Node.s32('jid', jid)) - player.add_child(Node.void('music_list')) + player.add_child(Node.s32("jid", jid)) + player.add_child(Node.void("music_list")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify expected nodes self.assert_path(resp, "response/recommend/data/player/music_list") @@ -394,12 +450,12 @@ class JubeatQubellClient(BaseClient): call = self.call_node() # Construct node - gametop = Node.void('demodata') + gametop = Node.void("demodata") call.add_child(gametop) - gametop.set_attribute('method', 'get_hitchart') + gametop.set_attribute("method", "get_hitchart") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify expected nodes self.assert_path(resp, "response/demodata/data/update") @@ -410,20 +466,20 @@ class JubeatQubellClient(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -443,22 +499,32 @@ class JubeatQubellClient(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") self.verify_gametop_regist(card, ref_id) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") if cardid is None: # Verify score handling @@ -467,86 +533,86 @@ class JubeatQubellClient(BaseClient): scores = self.verify_gametop_get_mdata(jid) self.verify_gametop_get_meeting(jid) if scores is None: - raise Exception('Expected to get scores back, didn\'t get anything!') + raise Exception("Expected to get scores back, didn't get anything!") if len(scores) > 0: - raise Exception('Got nonzero score count on a new card!') + raise Exception("Got nonzero score count on a new card!") for phase in [1, 2]: if phase == 1: dummyscores = [ # An okay score on a chart { - 'id': 40000059, - 'chart': 2, - 'clear': True, - 'fc': False, - 'ex': False, - 'score': 800000, - 'expected_medal': 0x3, + "id": 40000059, + "chart": 2, + "clear": True, + "fc": False, + "ex": False, + "score": 800000, + "expected_medal": 0x3, }, # A good score on an easier chart of the same song { - 'id': 40000059, - 'chart': 1, - 'clear': True, - 'fc': True, - 'ex': False, - 'score': 990000, - 'expected_medal': 0x5, + "id": 40000059, + "chart": 1, + "clear": True, + "fc": True, + "ex": False, + "score": 990000, + "expected_medal": 0x5, }, # A perfect score on an easiest chart of the same song { - 'id': 40000059, - 'chart': 0, - 'clear': True, - 'fc': True, - 'ex': True, - 'score': 1000000, - 'expected_medal': 0x9, + "id": 40000059, + "chart": 0, + "clear": True, + "fc": True, + "ex": True, + "score": 1000000, + "expected_medal": 0x9, }, # A bad score on a hard chart { - 'id': 30000024, - 'chart': 2, - 'clear': False, - 'fc': False, - 'ex': False, - 'score': 400000, - 'expected_medal': 0x1, + "id": 30000024, + "chart": 2, + "clear": False, + "fc": False, + "ex": False, + "score": 400000, + "expected_medal": 0x1, }, # A terrible score on an easy chart { - 'id': 50000045, - 'chart': 0, - 'clear': False, - 'fc': False, - 'ex': False, - 'score': 100000, - 'expected_medal': 0x1, + "id": 50000045, + "chart": 0, + "clear": False, + "fc": False, + "ex": False, + "score": 100000, + "expected_medal": 0x1, }, ] if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 50000045, - 'chart': 0, - 'clear': True, - 'fc': False, - 'ex': False, - 'score': 850000, - 'expected_medal': 0x3, + "id": 50000045, + "chart": 0, + "clear": True, + "fc": False, + "ex": False, + "score": 850000, + "expected_medal": 0x3, }, # A worse score on another same chart { - 'id': 40000059, - 'chart': 1, - 'clear': True, - 'fc': False, - 'ex': False, - 'score': 925000, - 'expected_score': 990000, - 'expected_medal': 0x7, + "id": 40000059, + "chart": 1, + "clear": True, + "fc": False, + "ex": False, + "score": 925000, + "expected_score": 990000, + "expected_medal": 0x7, }, ] @@ -555,18 +621,22 @@ class JubeatQubellClient(BaseClient): scores = self.verify_gametop_get_mdata(jid) for score in dummyscores: - newscore = scores[str(score['id'])][score['chart']] + newscore = scores[str(score["id"])][score["chart"]] - if 'expected_score' in score: - expected_score = score['expected_score'] + if "expected_score" in score: + expected_score = score["expected_score"] else: - expected_score = score['score'] + expected_score = score["score"] - if newscore['score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{newscore["score"]}\'') + if newscore["score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{newscore["score"]}\'' + ) - if newscore['medal'] != score['expected_medal']: - raise Exception(f'Expected a medal of \'{score["expected_medal"]}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got medal \'{newscore["medal"]}\'') + if newscore["medal"] != score["expected_medal"]: + raise Exception( + f'Expected a medal of \'{score["expected_medal"]}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got medal \'{newscore["medal"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) diff --git a/bemani/client/jubeat/saucer.py b/bemani/client/jubeat/saucer.py index 7ca6b10..090795a 100644 --- a/bemani/client/jubeat/saucer.py +++ b/bemani/client/jubeat/saucer.py @@ -7,30 +7,30 @@ from bemani.protocol import Node class JubeatSaucerClient(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_shopinfo_regist(self) -> None: call = self.call_node() # Construct node - shopinfo = Node.void('shopinfo') - shopinfo.set_attribute('method', 'regist') + shopinfo = Node.void("shopinfo") + shopinfo.set_attribute("method", "regist") call.add_child(shopinfo) - shop = Node.void('shop') + shop = Node.void("shop") shopinfo.add_child(shop) - shop.add_child(Node.string('name', '')) - shop.add_child(Node.string('pref', 'JP-14')) - shop.add_child(Node.string('softwareid', '')) - shop.add_child(Node.string('systemid', self.pcbid)) - shop.add_child(Node.string('hardwareid', '01020304050607080900')) - shop.add_child(Node.string('locationid', 'US-1')) - shop.add_child(Node.string('monitor', 'D26L155 6252 151')) - testmode = Node.void('testmode') + shop.add_child(Node.string("name", "")) + shop.add_child(Node.string("pref", "JP-14")) + shop.add_child(Node.string("softwareid", "")) + shop.add_child(Node.string("systemid", self.pcbid)) + shop.add_child(Node.string("hardwareid", "01020304050607080900")) + shop.add_child(Node.string("locationid", "US-1")) + shop.add_child(Node.string("monitor", "D26L155 6252 151")) + testmode = Node.void("testmode") shop.add_child(testmode) - testmode.set_attribute('send', '0') + testmode.set_attribute("send", "0") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/shopinfo/data/cabid") @@ -47,64 +47,64 @@ class JubeatSaucerClient(BaseClient): call = self.call_node() # Construct node - demodata = Node.void('demodata') + demodata = Node.void("demodata") call.add_child(demodata) - demodata.set_attribute('method', 'get_news') + demodata.set_attribute("method", "get_news") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/demodata/data/officialnews") def __verify_profile(self, resp: Node) -> int: for item in [ - 'jubility', - 'jubility_yday', - 'tune_cnt', - 'save_cnt', - 'saved_cnt', - 'fc_cnt', - 'ex_cnt', - 'pf_cnt', - 'clear_cnt', - 'match_cnt', - 'beat_cnt', - 'mynews_cnt', - 'inherit', - 'mtg_entry_cnt', - 'mtg_hold_cnt', - 'mtg_result', + "jubility", + "jubility_yday", + "tune_cnt", + "save_cnt", + "saved_cnt", + "fc_cnt", + "ex_cnt", + "pf_cnt", + "clear_cnt", + "match_cnt", + "beat_cnt", + "mynews_cnt", + "inherit", + "mtg_entry_cnt", + "mtg_hold_cnt", + "mtg_result", ]: self.assert_path(resp, f"response/gametop/data/player/info/{item}") for item in [ - 'secret_list', - 'title_list', - 'theme_list', - 'marker_list', - 'parts_list', - 'new/secret_list', - 'new/title_list', - 'new/theme_list', - 'new/marker_list', + "secret_list", + "title_list", + "theme_list", + "marker_list", + "parts_list", + "new/secret_list", + "new/title_list", + "new/theme_list", + "new/marker_list", ]: self.assert_path(resp, f"response/gametop/data/player/item/{item}") for item in [ - 'music_id', - 'marker', - 'title', - 'theme', - 'sort', - 'rank_sort', - 'combo_disp', - 'seq_id', - 'parts', - 'category', - 'play_time', - 'shopname', - 'areaname', + "music_id", + "marker", + "title", + "theme", + "sort", + "rank_sort", + "combo_disp", + "seq_id", + "parts", + "category", + "play_time", + "shopname", + "areaname", ]: self.assert_path(resp, f"response/gametop/data/player/last/{item}") @@ -132,81 +132,119 @@ class JubeatSaucerClient(BaseClient): self.assert_path(resp, "response/gametop/data/open_music_list") # Return the jid - return resp.child_value('gametop/data/player/jid') + return resp.child_value("gametop/data/player/jid") - def verify_gameend_regist(self, ref_id: str, jid: int, scores: List[Dict[str, Any]]) -> None: + def verify_gameend_regist( + self, ref_id: str, jid: int, scores: List[Dict[str, Any]] + ) -> None: call = self.call_node() # Construct node - gameend = Node.void('gameend') + gameend = Node.void("gameend") call.add_child(gameend) - gameend.set_attribute('method', 'regist') - gameend.add_child(Node.s32('retry', 0)) - data = Node.void('data') + gameend.set_attribute("method", "regist") + gameend.add_child(Node.s32("retry", 0)) + data = Node.void("data") gameend.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.string('refid', ref_id)) - player.add_child(Node.s32('jid', jid)) - player.add_child(Node.string('name', self.NAME)) - result = Node.void('result') + player.add_child(Node.string("refid", ref_id)) + player.add_child(Node.s32("jid", jid)) + player.add_child(Node.string("name", self.NAME)) + result = Node.void("result") data.add_child(result) - result.set_attribute('count', str(len(scores))) + result.set_attribute("count", str(len(scores))) # Send scores scoreid = 0 for score in scores: # Always played bits = 0x1 - if score['clear']: + if score["clear"]: bits |= 0x2 - if score['fc']: + if score["fc"]: bits |= 0x4 - if score['ex']: + if score["ex"]: bits |= 0x8 # Intentionally starting at 1 because that's what the game does scoreid = scoreid + 1 - tune = Node.void('tune') + tune = Node.void("tune") result.add_child(tune) - tune.set_attribute('id', str(scoreid)) - tune.set_attribute('count', '0') - tune.add_child(Node.s32('music', score['id'])) - player_1 = Node.void('player') + tune.set_attribute("id", str(scoreid)) + tune.set_attribute("count", "0") + tune.add_child(Node.s32("music", score["id"])) + player_1 = Node.void("player") tune.add_child(player_1) - player_1.set_attribute('rank', '1') - scorenode = Node.s32('score', score['score']) + player_1.set_attribute("rank", "1") + scorenode = Node.s32("score", score["score"]) player_1.add_child(scorenode) - scorenode.set_attribute('seq', str(score['chart'])) - scorenode.set_attribute('clear', str(bits)) - scorenode.set_attribute('combo', '69') - player_1.add_child(Node.u8_array('mbar', [239, 175, 170, 170, 190, 234, 187, 158, 153, 230, 170, 90, 102, 170, 85, 150, 150, 102, 85, 234, 171, 169, 157, 150, 170, 101, 230, 90, 214, 255])) + scorenode.set_attribute("seq", str(score["chart"])) + scorenode.set_attribute("clear", str(bits)) + scorenode.set_attribute("combo", "69") + player_1.add_child( + Node.u8_array( + "mbar", + [ + 239, + 175, + 170, + 170, + 190, + 234, + 187, + 158, + 153, + 230, + 170, + 90, + 102, + 170, + 85, + 150, + 150, + 102, + 85, + 234, + 171, + 169, + 157, + 150, + 170, + 101, + 230, + 90, + 214, + 255, + ], + ) + ) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/gameend/data/player/session_id") def verify_gametop_regist(self, card_id: str, ref_id: str) -> int: call = self.call_node() # Construct node - gametop = Node.void('gametop') + gametop = Node.void("gametop") call.add_child(gametop) - gametop.set_attribute('method', 'regist') - data = Node.void('data') + gametop.set_attribute("method", "regist") + data = Node.void("data") gametop.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - passnode = Node.void('pass') + passnode = Node.void("pass") player.add_child(passnode) - passnode.add_child(Node.string('refid', ref_id)) - passnode.add_child(Node.string('datid', ref_id)) - passnode.add_child(Node.string('uid', card_id)) - passnode.add_child(Node.bool('inherit', True)) - player.add_child(Node.string('name', self.NAME)) + passnode.add_child(Node.string("refid", ref_id)) + passnode.add_child(Node.string("datid", ref_id)) + passnode.add_child(Node.string("uid", card_id)) + passnode.add_child(Node.bool("inherit", True)) + player.add_child(Node.string("name", self.NAME)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist return self.__verify_profile(resp) @@ -215,23 +253,23 @@ class JubeatSaucerClient(BaseClient): call = self.call_node() # Construct node - gametop = Node.void('gametop') + gametop = Node.void("gametop") call.add_child(gametop) - gametop.set_attribute('method', 'get_pdata') - retry = Node.s32('retry', 0) + gametop.set_attribute("method", "get_pdata") + retry = Node.s32("retry", 0) gametop.add_child(retry) - data = Node.void('data') + data = Node.void("data") gametop.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - passnode = Node.void('pass') + passnode = Node.void("pass") player.add_child(passnode) - passnode.add_child(Node.string('refid', ref_id)) - passnode.add_child(Node.string('datid', ref_id)) - passnode.add_child(Node.string('uid', card_id)) + passnode.add_child(Node.string("refid", ref_id)) + passnode.add_child(Node.string("datid", ref_id)) + passnode.add_child(Node.string("uid", card_id)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist return self.__verify_profile(resp) @@ -240,32 +278,32 @@ class JubeatSaucerClient(BaseClient): call = self.call_node() # Construct node - gametop = Node.void('gametop') + gametop = Node.void("gametop") call.add_child(gametop) - gametop.set_attribute('method', 'get_mdata') - retry = Node.s32('retry', 0) + gametop.set_attribute("method", "get_mdata") + retry = Node.s32("retry", 0) gametop.add_child(retry) - data = Node.void('data') + data = Node.void("data") gametop.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.s32('jid', jid)) + player.add_child(Node.s32("jid", jid)) # Technically the game sends this same packet 3 times, one with # each value 1, 2, 3 here. Unclear why, but we won't emulate it. - player.add_child(Node.s8('mdata_ver', 1)) + player.add_child(Node.s8("mdata_ver", 1)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Parse out scores self.assert_path(resp, "response/gametop/data/player/playdata") ret = {} - for musicdata in resp.child('gametop/data/player/playdata').children: - if musicdata.name != 'musicdata': - raise Exception('Unexpected node in playdata!') + for musicdata in resp.child("gametop/data/player/playdata").children: + if musicdata.name != "musicdata": + raise Exception("Unexpected node in playdata!") - music_id = musicdata.attribute('music_id') + music_id = musicdata.attribute("music_id") scores_by_chart: List[Dict[str, int]] = [{}, {}, {}] def extract_cnts(name: str, val: List[int]) -> None: @@ -273,12 +311,12 @@ class JubeatSaucerClient(BaseClient): scores_by_chart[1][name] = val[1] scores_by_chart[2][name] = val[2] - extract_cnts('plays', musicdata.child_value('play_cnt')) - extract_cnts('clears', musicdata.child_value('clear_cnt')) - extract_cnts('full_combos', musicdata.child_value('fc_cnt')) - extract_cnts('excellents', musicdata.child_value('ex_cnt')) - extract_cnts('score', musicdata.child_value('score')) - extract_cnts('medal', musicdata.child_value('clear')) + extract_cnts("plays", musicdata.child_value("play_cnt")) + extract_cnts("clears", musicdata.child_value("clear_cnt")) + extract_cnts("full_combos", musicdata.child_value("fc_cnt")) + extract_cnts("excellents", musicdata.child_value("ex_cnt")) + extract_cnts("score", musicdata.child_value("score")) + extract_cnts("medal", musicdata.child_value("clear")) ret[music_id] = scores_by_chart return ret @@ -287,18 +325,18 @@ class JubeatSaucerClient(BaseClient): call = self.call_node() # Construct node - gametop = Node.void('gametop') + gametop = Node.void("gametop") call.add_child(gametop) - gametop.set_attribute('method', 'get_meeting') - gametop.add_child(Node.s32('retry', 0)) - data = Node.void('data') + gametop.set_attribute("method", "get_meeting") + gametop.add_child(Node.s32("retry", 0)) + data = Node.void("data") gametop.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.s32('jid', jid)) + player.add_child(Node.s32("jid", jid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify expected nodes self.assert_path(resp, "response/gametop/data/meeting/single") @@ -310,20 +348,20 @@ class JubeatSaucerClient(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -342,22 +380,32 @@ class JubeatSaucerClient(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") self.verify_gametop_regist(card, ref_id) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") if cardid is None: # Verify score handling @@ -365,86 +413,86 @@ class JubeatSaucerClient(BaseClient): scores = self.verify_gametop_get_mdata(jid) self.verify_gametop_get_meeting(jid) if scores is None: - raise Exception('Expected to get scores back, didn\'t get anything!') + raise Exception("Expected to get scores back, didn't get anything!") if len(scores) > 0: - raise Exception('Got nonzero score count on a new card!') + raise Exception("Got nonzero score count on a new card!") for phase in [1, 2]: if phase == 1: dummyscores = [ # An okay score on a chart { - 'id': 40000059, - 'chart': 2, - 'clear': True, - 'fc': False, - 'ex': False, - 'score': 800000, - 'expected_medal': 0x3, + "id": 40000059, + "chart": 2, + "clear": True, + "fc": False, + "ex": False, + "score": 800000, + "expected_medal": 0x3, }, # A good score on an easier chart of the same song { - 'id': 40000059, - 'chart': 1, - 'clear': True, - 'fc': True, - 'ex': False, - 'score': 990000, - 'expected_medal': 0x5, + "id": 40000059, + "chart": 1, + "clear": True, + "fc": True, + "ex": False, + "score": 990000, + "expected_medal": 0x5, }, # A perfect score on an easiest chart of the same song { - 'id': 40000059, - 'chart': 0, - 'clear': True, - 'fc': True, - 'ex': True, - 'score': 1000000, - 'expected_medal': 0x9, + "id": 40000059, + "chart": 0, + "clear": True, + "fc": True, + "ex": True, + "score": 1000000, + "expected_medal": 0x9, }, # A bad score on a hard chart { - 'id': 30000024, - 'chart': 2, - 'clear': False, - 'fc': False, - 'ex': False, - 'score': 400000, - 'expected_medal': 0x1, + "id": 30000024, + "chart": 2, + "clear": False, + "fc": False, + "ex": False, + "score": 400000, + "expected_medal": 0x1, }, # A terrible score on an easy chart { - 'id': 50000045, - 'chart': 0, - 'clear': False, - 'fc': False, - 'ex': False, - 'score': 100000, - 'expected_medal': 0x1, + "id": 50000045, + "chart": 0, + "clear": False, + "fc": False, + "ex": False, + "score": 100000, + "expected_medal": 0x1, }, ] if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 50000045, - 'chart': 0, - 'clear': True, - 'fc': False, - 'ex': False, - 'score': 850000, - 'expected_medal': 0x3, + "id": 50000045, + "chart": 0, + "clear": True, + "fc": False, + "ex": False, + "score": 850000, + "expected_medal": 0x3, }, # A worse score on another same chart { - 'id': 40000059, - 'chart': 1, - 'clear': True, - 'fc': False, - 'ex': False, - 'score': 925000, - 'expected_score': 990000, - 'expected_medal': 0x7, + "id": 40000059, + "chart": 1, + "clear": True, + "fc": False, + "ex": False, + "score": 925000, + "expected_score": 990000, + "expected_medal": 0x7, }, ] @@ -452,18 +500,22 @@ class JubeatSaucerClient(BaseClient): jid = self.verify_gametop_get_pdata(card, ref_id) scores = self.verify_gametop_get_mdata(jid) for score in dummyscores: - newscore = scores[str(score['id'])][score['chart']] + newscore = scores[str(score["id"])][score["chart"]] - if 'expected_score' in score: - expected_score = score['expected_score'] + if "expected_score" in score: + expected_score = score["expected_score"] else: - expected_score = score['score'] + expected_score = score["score"] - if newscore['score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{newscore["score"]}\'') + if newscore["score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{newscore["score"]}\'' + ) - if newscore['medal'] != score['expected_medal']: - raise Exception(f'Expected a medal of \'{score["expected_medal"]}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got medal \'{newscore["medal"]}\'') + if newscore["medal"] != score["expected_medal"]: + raise Exception( + f'Expected a medal of \'{score["expected_medal"]}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got medal \'{newscore["medal"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) diff --git a/bemani/client/jubeat/saucerfulfill.py b/bemani/client/jubeat/saucerfulfill.py index 737e0d3..e3e37df 100644 --- a/bemani/client/jubeat/saucerfulfill.py +++ b/bemani/client/jubeat/saucerfulfill.py @@ -7,30 +7,30 @@ from bemani.protocol import Node class JubeatSaucerFulfillClient(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_shopinfo_regist(self) -> None: call = self.call_node() # Construct node - shopinfo = Node.void('shopinfo') - shopinfo.set_attribute('method', 'regist') + shopinfo = Node.void("shopinfo") + shopinfo.set_attribute("method", "regist") call.add_child(shopinfo) - shop = Node.void('shop') + shop = Node.void("shop") shopinfo.add_child(shop) - shop.add_child(Node.string('name', '')) - shop.add_child(Node.string('pref', 'JP-14')) - shop.add_child(Node.string('softwareid', '')) - shop.add_child(Node.string('systemid', self.pcbid)) - shop.add_child(Node.string('hardwareid', '01020304050607080900')) - shop.add_child(Node.string('locationid', 'US-1')) - shop.add_child(Node.string('monitor', 'D26L155 6252 151')) - testmode = Node.void('testmode') + shop.add_child(Node.string("name", "")) + shop.add_child(Node.string("pref", "JP-14")) + shop.add_child(Node.string("softwareid", "")) + shop.add_child(Node.string("systemid", self.pcbid)) + shop.add_child(Node.string("hardwareid", "01020304050607080900")) + shop.add_child(Node.string("locationid", "US-1")) + shop.add_child(Node.string("monitor", "D26L155 6252 151")) + testmode = Node.void("testmode") shop.add_child(testmode) - testmode.set_attribute('send', '0') + testmode.set_attribute("send", "0") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/shopinfo/data/cabid") @@ -50,70 +50,70 @@ class JubeatSaucerFulfillClient(BaseClient): call = self.call_node() # Construct node - demodata = Node.void('demodata') + demodata = Node.void("demodata") call.add_child(demodata) - demodata.set_attribute('method', 'get_news') + demodata.set_attribute("method", "get_news") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/demodata/data/officialnews") def __verify_profile(self, resp: Node) -> int: for item in [ - 'jubility', - 'jubility_yday', - 'tune_cnt', - 'save_cnt', - 'saved_cnt', - 'fc_cnt', - 'ex_cnt', - 'pf_cnt', - 'clear_cnt', - 'match_cnt', - 'beat_cnt', - 'mynews_cnt', - 'extra_point', - 'is_extra_played', - 'inherit', - 'mtg_entry_cnt', - 'mtg_hold_cnt', - 'mtg_result', + "jubility", + "jubility_yday", + "tune_cnt", + "save_cnt", + "saved_cnt", + "fc_cnt", + "ex_cnt", + "pf_cnt", + "clear_cnt", + "match_cnt", + "beat_cnt", + "mynews_cnt", + "extra_point", + "is_extra_played", + "inherit", + "mtg_entry_cnt", + "mtg_hold_cnt", + "mtg_result", ]: self.assert_path(resp, f"response/gametop/data/player/info/{item}") for item in [ - 'secret_list', - 'title_list', - 'theme_list', - 'marker_list', - 'parts_list', - 'new/secret_list', - 'new/title_list', - 'new/theme_list', - 'new/marker_list', + "secret_list", + "title_list", + "theme_list", + "marker_list", + "parts_list", + "new/secret_list", + "new/title_list", + "new/theme_list", + "new/marker_list", ]: self.assert_path(resp, f"response/gametop/data/player/item/{item}") for item in [ - 'music_id', - 'marker', - 'title', - 'theme', - 'sort', - 'rank_sort', - 'combo_disp', - 'seq_id', - 'parts', - 'category', - 'play_time', - 'expert_option', - 'matching', - 'hazard', - 'hard', - 'shopname', - 'areaname', + "music_id", + "marker", + "title", + "theme", + "sort", + "rank_sort", + "combo_disp", + "seq_id", + "parts", + "category", + "play_time", + "expert_option", + "matching", + "hazard", + "hard", + "shopname", + "areaname", ]: self.assert_path(resp, f"response/gametop/data/player/last/{item}") @@ -148,95 +148,138 @@ class JubeatSaucerFulfillClient(BaseClient): self.assert_path(resp, "response/gametop/data/open_music_list") # Return the jid - return resp.child_value('gametop/data/player/jid') + return resp.child_value("gametop/data/player/jid") - def verify_gameend_regist(self, ref_id: str, jid: int, mode: int, scores: List[Dict[str, Any]], course: Dict[str, Any]) -> None: + def verify_gameend_regist( + self, + ref_id: str, + jid: int, + mode: int, + scores: List[Dict[str, Any]], + course: Dict[str, Any], + ) -> None: call = self.call_node() # Construct node - gameend = Node.void('gameend') + gameend = Node.void("gameend") call.add_child(gameend) - gameend.set_attribute('method', 'regist') - gameend.add_child(Node.s32('retry', 0)) - data = Node.void('data') + gameend.set_attribute("method", "regist") + gameend.add_child(Node.s32("retry", 0)) + data = Node.void("data") gameend.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.s8('mode', mode)) - player.add_child(Node.string('refid', ref_id)) - player.add_child(Node.s32('jid', jid)) - player.add_child(Node.string('name', self.NAME)) - result = Node.void('result') + player.add_child(Node.s8("mode", mode)) + player.add_child(Node.string("refid", ref_id)) + player.add_child(Node.s32("jid", jid)) + player.add_child(Node.string("name", self.NAME)) + result = Node.void("result") data.add_child(result) - result.set_attribute('count', str(len(scores))) + result.set_attribute("count", str(len(scores))) # Send scores scoreid = 0 for score in scores: # Always played bits = 0x1 - if score['clear']: + if score["clear"]: bits |= 0x2 - if score['fc']: + if score["fc"]: bits |= 0x4 - if score['ex']: + if score["ex"]: bits |= 0x8 # Intentionally starting at 1 because that's what the game does scoreid = scoreid + 1 - tune = Node.void('tune') + tune = Node.void("tune") result.add_child(tune) - tune.set_attribute('id', str(scoreid)) - tune.set_attribute('count', '0') - tune.add_child(Node.s32('music', score['id'])) - player_1 = Node.void('player') + tune.set_attribute("id", str(scoreid)) + tune.set_attribute("count", "0") + tune.add_child(Node.s32("music", score["id"])) + player_1 = Node.void("player") tune.add_child(player_1) - player_1.set_attribute('rank', '1') - scorenode = Node.s32('score', score['score']) + player_1.set_attribute("rank", "1") + scorenode = Node.s32("score", score["score"]) player_1.add_child(scorenode) - scorenode.set_attribute('seq', str(score['chart'])) - scorenode.set_attribute('clear', str(bits)) - scorenode.set_attribute('combo', '69') - player_1.add_child(Node.u8_array('mbar', [239, 175, 170, 170, 190, 234, 187, 158, 153, 230, 170, 90, 102, 170, 85, 150, 150, 102, 85, 234, 171, 169, 157, 150, 170, 101, 230, 90, 214, 255])) + scorenode.set_attribute("seq", str(score["chart"])) + scorenode.set_attribute("clear", str(bits)) + scorenode.set_attribute("combo", "69") + player_1.add_child( + Node.u8_array( + "mbar", + [ + 239, + 175, + 170, + 170, + 190, + 234, + 187, + 158, + 153, + 230, + 170, + 90, + 102, + 170, + 85, + 150, + 150, + 102, + 85, + 234, + 171, + 169, + 157, + 150, + 170, + 101, + 230, + 90, + 214, + 255, + ], + ) + ) if len(course) > 0: - coursenode = Node.void('course') + coursenode = Node.void("course") data.add_child(coursenode) - coursenode.add_child(Node.s32('course_id', course['course_id'])) - coursenode.add_child(Node.u8('rating', course['rating'])) + coursenode.add_child(Node.s32("course_id", course["course_id"])) + coursenode.add_child(Node.u8("rating", course["rating"])) index = 0 - for coursescore in course['scores']: - music = Node.void('music') + for coursescore in course["scores"]: + music = Node.void("music") coursenode.add_child(music) - music.set_attribute('index', str(index)) - music.add_child(Node.s32('score', coursescore)) + music.set_attribute("index", str(index)) + music.add_child(Node.s32("score", coursescore)) index = index + 1 # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/gameend/data/player/session_id") def verify_gametop_regist(self, card_id: str, ref_id: str) -> int: call = self.call_node() # Construct node - gametop = Node.void('gametop') + gametop = Node.void("gametop") call.add_child(gametop) - gametop.set_attribute('method', 'regist') - data = Node.void('data') + gametop.set_attribute("method", "regist") + data = Node.void("data") gametop.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - passnode = Node.void('pass') + passnode = Node.void("pass") player.add_child(passnode) - passnode.add_child(Node.string('refid', ref_id)) - passnode.add_child(Node.string('datid', ref_id)) - passnode.add_child(Node.string('uid', card_id)) - passnode.add_child(Node.bool('inherit', True)) - player.add_child(Node.string('name', self.NAME)) + passnode.add_child(Node.string("refid", ref_id)) + passnode.add_child(Node.string("datid", ref_id)) + passnode.add_child(Node.string("uid", card_id)) + passnode.add_child(Node.bool("inherit", True)) + player.add_child(Node.string("name", self.NAME)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist return self.__verify_profile(resp) @@ -245,23 +288,23 @@ class JubeatSaucerFulfillClient(BaseClient): call = self.call_node() # Construct node - gametop = Node.void('gametop') + gametop = Node.void("gametop") call.add_child(gametop) - gametop.set_attribute('method', 'get_pdata') - retry = Node.s32('retry', 0) + gametop.set_attribute("method", "get_pdata") + retry = Node.s32("retry", 0) gametop.add_child(retry) - data = Node.void('data') + data = Node.void("data") gametop.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - passnode = Node.void('pass') + passnode = Node.void("pass") player.add_child(passnode) - passnode.add_child(Node.string('refid', ref_id)) - passnode.add_child(Node.string('datid', ref_id)) - passnode.add_child(Node.string('uid', card_id)) + passnode.add_child(Node.string("refid", ref_id)) + passnode.add_child(Node.string("datid", ref_id)) + passnode.add_child(Node.string("uid", card_id)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist return self.__verify_profile(resp) @@ -270,32 +313,32 @@ class JubeatSaucerFulfillClient(BaseClient): call = self.call_node() # Construct node - gametop = Node.void('gametop') + gametop = Node.void("gametop") call.add_child(gametop) - gametop.set_attribute('method', 'get_mdata') - retry = Node.s32('retry', 0) + gametop.set_attribute("method", "get_mdata") + retry = Node.s32("retry", 0) gametop.add_child(retry) - data = Node.void('data') + data = Node.void("data") gametop.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.s32('jid', jid)) + player.add_child(Node.s32("jid", jid)) # Technically the game sends this same packet 3 times, one with # each value 1, 2, 3 here. Unclear why, but we won't emulate it. - player.add_child(Node.s8('mdata_ver', 1)) + player.add_child(Node.s8("mdata_ver", 1)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Parse out scores self.assert_path(resp, "response/gametop/data/player/playdata") ret = {} - for musicdata in resp.child('gametop/data/player/playdata').children: - if musicdata.name != 'musicdata': - raise Exception('Unexpected node in playdata!') + for musicdata in resp.child("gametop/data/player/playdata").children: + if musicdata.name != "musicdata": + raise Exception("Unexpected node in playdata!") - music_id = musicdata.attribute('music_id') + music_id = musicdata.attribute("music_id") scores_by_chart: List[Dict[str, int]] = [{}, {}, {}] def extract_cnts(name: str, val: List[int]) -> None: @@ -303,12 +346,12 @@ class JubeatSaucerFulfillClient(BaseClient): scores_by_chart[1][name] = val[1] scores_by_chart[2][name] = val[2] - extract_cnts('plays', musicdata.child_value('play_cnt')) - extract_cnts('clears', musicdata.child_value('clear_cnt')) - extract_cnts('full_combos', musicdata.child_value('fc_cnt')) - extract_cnts('excellents', musicdata.child_value('ex_cnt')) - extract_cnts('score', musicdata.child_value('score')) - extract_cnts('medal', musicdata.child_value('clear')) + extract_cnts("plays", musicdata.child_value("play_cnt")) + extract_cnts("clears", musicdata.child_value("clear_cnt")) + extract_cnts("full_combos", musicdata.child_value("fc_cnt")) + extract_cnts("excellents", musicdata.child_value("ex_cnt")) + extract_cnts("score", musicdata.child_value("score")) + extract_cnts("medal", musicdata.child_value("clear")) ret[music_id] = scores_by_chart return ret @@ -317,18 +360,18 @@ class JubeatSaucerFulfillClient(BaseClient): call = self.call_node() # Construct node - gametop = Node.void('gametop') + gametop = Node.void("gametop") call.add_child(gametop) - gametop.set_attribute('method', 'get_course') - gametop.add_child(Node.s32('retry', 0)) - data = Node.void('data') + gametop.set_attribute("method", "get_course") + gametop.add_child(Node.s32("retry", 0)) + data = Node.void("data") gametop.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.s32('jid', jid)) + player.add_child(Node.s32("jid", jid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify expected nodes self.assert_path(resp, "response/gametop/data/course_list") @@ -336,8 +379,8 @@ class JubeatSaucerFulfillClient(BaseClient): self.assert_path(resp, "response/gametop/data/last_course_id") playernode = None - for player in resp.child('gametop/data/player_list').children: - if player.child_value('jid') == jid: + for player in resp.child("gametop/data/player_list").children: + if player.child_value("jid") == jid: playernode = player break @@ -345,15 +388,15 @@ class JubeatSaucerFulfillClient(BaseClient): raise Exception(f"Didn't find any scores for ExtID {jid}") ret = [] - for result in playernode.child('result_list').children: - if result.name != 'result': - raise Exception('Unexpected node in result_list!') + for result in playernode.child("result_list").children: + if result.name != "result": + raise Exception("Unexpected node in result_list!") - course_id = result.child_value('id') - rating = result.child_value('rating') - scores = result.child_value('score') + course_id = result.child_value("id") + rating = result.child_value("rating") + scores = result.child_value("score") - ret.append({'course_id': course_id, 'rating': rating, 'scores': scores}) + ret.append({"course_id": course_id, "rating": rating, "scores": scores}) return ret @@ -361,18 +404,18 @@ class JubeatSaucerFulfillClient(BaseClient): call = self.call_node() # Construct node - gametop = Node.void('gametop') + gametop = Node.void("gametop") call.add_child(gametop) - gametop.set_attribute('method', 'get_meeting') - gametop.add_child(Node.s32('retry', 0)) - data = Node.void('data') + gametop.set_attribute("method", "get_meeting") + gametop.add_child(Node.s32("retry", 0)) + data = Node.void("data") gametop.add_child(data) - player = Node.void('player') + player = Node.void("player") data.add_child(player) - player.add_child(Node.s32('jid', jid)) + player.add_child(Node.s32("jid", jid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify expected nodes self.assert_path(resp, "response/gametop/data/meeting/single") @@ -384,20 +427,20 @@ class JubeatSaucerFulfillClient(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -416,22 +459,32 @@ class JubeatSaucerFulfillClient(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") self.verify_gametop_regist(card, ref_id) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") if cardid is None: # Verify score handling @@ -440,90 +493,90 @@ class JubeatSaucerFulfillClient(BaseClient): courses = self.verify_gametop_get_course(jid) self.verify_gametop_get_meeting(jid) if scores is None: - raise Exception('Expected to get scores back, didn\'t get anything!') + raise Exception("Expected to get scores back, didn't get anything!") if courses is None: - raise Exception('Expected to get courses back, didn\'t get anything!') + raise Exception("Expected to get courses back, didn't get anything!") if len(scores) > 0: - raise Exception('Got nonzero score count on a new card!') + raise Exception("Got nonzero score count on a new card!") if len(courses) > 0: - raise Exception('Got nonzero course count on a new card!') + raise Exception("Got nonzero course count on a new card!") for phase in [1, 2]: if phase == 1: dummyscores = [ # An okay score on a chart { - 'id': 40000059, - 'chart': 2, - 'clear': True, - 'fc': False, - 'ex': False, - 'score': 800000, - 'expected_medal': 0x3, + "id": 40000059, + "chart": 2, + "clear": True, + "fc": False, + "ex": False, + "score": 800000, + "expected_medal": 0x3, }, # A good score on an easier chart of the same song { - 'id': 40000059, - 'chart': 1, - 'clear': True, - 'fc': True, - 'ex': False, - 'score': 990000, - 'expected_medal': 0x5, + "id": 40000059, + "chart": 1, + "clear": True, + "fc": True, + "ex": False, + "score": 990000, + "expected_medal": 0x5, }, # A perfect score on an easiest chart of the same song { - 'id': 40000059, - 'chart': 0, - 'clear': True, - 'fc': True, - 'ex': True, - 'score': 1000000, - 'expected_medal': 0x9, + "id": 40000059, + "chart": 0, + "clear": True, + "fc": True, + "ex": True, + "score": 1000000, + "expected_medal": 0x9, }, # A bad score on a hard chart { - 'id': 30000024, - 'chart': 2, - 'clear': False, - 'fc': False, - 'ex': False, - 'score': 400000, - 'expected_medal': 0x1, + "id": 30000024, + "chart": 2, + "clear": False, + "fc": False, + "ex": False, + "score": 400000, + "expected_medal": 0x1, }, # A terrible score on an easy chart { - 'id': 50000045, - 'chart': 0, - 'clear': False, - 'fc': False, - 'ex': False, - 'score': 100000, - 'expected_medal': 0x1, + "id": 50000045, + "chart": 0, + "clear": False, + "fc": False, + "ex": False, + "score": 100000, + "expected_medal": 0x1, }, ] if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 50000045, - 'chart': 0, - 'clear': True, - 'fc': False, - 'ex': False, - 'score': 850000, - 'expected_medal': 0x3, + "id": 50000045, + "chart": 0, + "clear": True, + "fc": False, + "ex": False, + "score": 850000, + "expected_medal": 0x3, }, # A worse score on another same chart { - 'id': 40000059, - 'chart': 1, - 'clear': True, - 'fc': False, - 'ex': False, - 'score': 925000, - 'expected_score': 990000, - 'expected_medal': 0x7, + "id": 40000059, + "chart": 1, + "clear": True, + "fc": False, + "ex": False, + "score": 925000, + "expected_score": 990000, + "expected_medal": 0x7, }, ] @@ -532,21 +585,27 @@ class JubeatSaucerFulfillClient(BaseClient): scores = self.verify_gametop_get_mdata(jid) courses = self.verify_gametop_get_course(jid) if len(courses) > 0: - raise Exception('Got nonzero course count without playing any courses!') + raise Exception( + "Got nonzero course count without playing any courses!" + ) for score in dummyscores: - newscore = scores[str(score['id'])][score['chart']] + newscore = scores[str(score["id"])][score["chart"]] - if 'expected_score' in score: - expected_score = score['expected_score'] + if "expected_score" in score: + expected_score = score["expected_score"] else: - expected_score = score['score'] + expected_score = score["score"] - if newscore['score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{newscore["score"]}\'') + if newscore["score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{newscore["score"]}\'' + ) - if newscore['medal'] != score['expected_medal']: - raise Exception(f'Expected a medal of \'{score["expected_medal"]}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got medal \'{newscore["medal"]}\'') + if newscore["medal"] != score["expected_medal"]: + raise Exception( + f'Expected a medal of \'{score["expected_medal"]}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got medal \'{newscore["medal"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) @@ -554,33 +613,43 @@ class JubeatSaucerFulfillClient(BaseClient): for phase in [1, 2]: dummycourses: List[Dict[str, Any]] = [] if phase == 1: - dummycourses.extend([ - { - 'course_id': 1, - 'rating': 1, - 'scores': [123456, 123457, 123458, 123459, 123460], - }, - { - 'course_id': 2, - 'rating': 2, - 'scores': [123456, 123457, 123458, 123459, 123460], - }, - ]) + dummycourses.extend( + [ + { + "course_id": 1, + "rating": 1, + "scores": [123456, 123457, 123458, 123459, 123460], + }, + { + "course_id": 2, + "rating": 2, + "scores": [123456, 123457, 123458, 123459, 123460], + }, + ] + ) else: - dummycourses.extend([ - { - 'course_id': 1, - 'rating': 2, - 'scores': [223456, 223457, 223458, 223459, 223460], - }, - { - 'course_id': 2, - 'rating': 1, - 'expected_rating': 2, - 'scores': [23456, 23457, 23458, 23459, 23460], - 'expected_scores': [123456, 123457, 123458, 123459, 123460], - }, - ]) + dummycourses.extend( + [ + { + "course_id": 1, + "rating": 2, + "scores": [223456, 223457, 223458, 223459, 223460], + }, + { + "course_id": 2, + "rating": 1, + "expected_rating": 2, + "scores": [23456, 23457, 23458, 23459, 23460], + "expected_scores": [ + 123456, + 123457, + 123458, + 123459, + 123460, + ], + }, + ] + ) for course in dummycourses: self.verify_gameend_regist(ref_id, jid, 6, [], course) @@ -589,31 +658,39 @@ class JubeatSaucerFulfillClient(BaseClient): for course in dummycourses: # Find the course - foundcourses = [c for c in courses if c['course_id'] == course['course_id']] + foundcourses = [ + c for c in courses if c["course_id"] == course["course_id"] + ] if len(foundcourses) == 0: - raise Exception(f"Didn't find course by ID {course['course_id']}") + raise Exception( + f"Didn't find course by ID {course['course_id']}" + ) foundcourse = foundcourses[0] - if 'expected_rating' in course: - expected_rating = course['expected_rating'] + if "expected_rating" in course: + expected_rating = course["expected_rating"] else: - expected_rating = course['rating'] + expected_rating = course["rating"] - if 'expected_scores' in course: - expected_scores = course['expected_scores'] + if "expected_scores" in course: + expected_scores = course["expected_scores"] else: - expected_scores = course['scores'] + expected_scores = course["scores"] - if foundcourse['course_id'] != course['course_id']: + if foundcourse["course_id"] != course["course_id"]: raise Exception("Logic error!") - if foundcourse['rating'] != expected_rating: - raise Exception(f'Expected a rating of \'{expected_rating}\' for course \'{course["course_id"]}\' but got rating \'{foundcourse["rating"]}\'') + if foundcourse["rating"] != expected_rating: + raise Exception( + f'Expected a rating of \'{expected_rating}\' for course \'{course["course_id"]}\' but got rating \'{foundcourse["rating"]}\'' + ) for i in range(len(expected_scores)): - if foundcourse['scores'][i] != expected_scores[i]: - raise Exception(f'Expected a score of \'{expected_scores[i]}\' for course \'{course["course_id"]}\' song number \'{i}\' but got score \'{foundcourse["scores"][i]}\'') + if foundcourse["scores"][i] != expected_scores[i]: + raise Exception( + f'Expected a score of \'{expected_scores[i]}\' for course \'{course["course_id"]}\' song number \'{i}\' but got score \'{foundcourse["scores"][i]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) diff --git a/bemani/client/mga/mga.py b/bemani/client/mga/mga.py index b709e75..01fc20c 100644 --- a/bemani/client/mga/mga.py +++ b/bemani/client/mga/mga.py @@ -7,30 +7,30 @@ from bemani.protocol import Node class MetalGearArcadeClient(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_eventlog_write(self, location: str) -> None: call = self.call_node() # Construct node - eventlog = Node.void('eventlog') + eventlog = Node.void("eventlog") call.add_child(eventlog) - eventlog.set_attribute('method', 'write') - eventlog.add_child(Node.u32('retrycnt', 0)) - data = Node.void('data') + eventlog.set_attribute("method", "write") + eventlog.add_child(Node.u32("retrycnt", 0)) + data = Node.void("data") eventlog.add_child(data) - data.add_child(Node.string('eventid', 'S_PWRON')) - data.add_child(Node.s32('eventorder', 0)) - data.add_child(Node.u64('pcbtime', int(time.time() * 1000))) - data.add_child(Node.s64('gamesession', -1)) - data.add_child(Node.string('strdata1', '1.9.1')) - data.add_child(Node.string('strdata2', '')) - data.add_child(Node.s64('numdata1', 1)) - data.add_child(Node.s64('numdata2', 0)) - data.add_child(Node.string('locationid', location)) + data.add_child(Node.string("eventid", "S_PWRON")) + data.add_child(Node.s32("eventorder", 0)) + data.add_child(Node.u64("pcbtime", int(time.time() * 1000))) + data.add_child(Node.s64("gamesession", -1)) + data.add_child(Node.string("strdata1", "1.9.1")) + data.add_child(Node.string("strdata2", "")) + data.add_child(Node.s64("numdata1", 1)) + data.add_child(Node.s64("numdata2", 0)) + data.add_child(Node.string("locationid", location)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/eventlog/gamesession") @@ -42,17 +42,17 @@ class MetalGearArcadeClient(BaseClient): call = self.call_node() # Construct node - system = Node.void('system') + system = Node.void("system") call.add_child(system) - system.set_attribute('method', 'getmaster') - data = Node.void('data') + system.set_attribute("method", "getmaster") + data = Node.void("data") system.add_child(data) - data.add_child(Node.string('gamekind', 'I36')) - data.add_child(Node.string('datatype', 'S_SRVMSG')) - data.add_child(Node.string('datakey', 'INFO')) + data.add_child(Node.string("gamekind", "I36")) + data.add_child(Node.string("datatype", "S_SRVMSG")) + data.add_child(Node.string("datakey", "INFO")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/system/result") @@ -62,239 +62,244 @@ class MetalGearArcadeClient(BaseClient): # Set up profile write profiledata = [ - b'ffffffff', - b'PLAYDATA', - b'8', - b'1', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'ffffffffffffa928', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'PLAYER', - b'JP-13', - b'ea', - b'', - b'JP-13', - b'', - b'', - b'' + b"ffffffff", + b"PLAYDATA", + b"8", + b"1", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"ffffffffffffa928", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"PLAYER", + b"JP-13", + b"ea", + b"", + b"JP-13", + b"", + b"", + b"", ] outfitdata = [ - b'ffffffff', - b'OUTFIT', - b'8', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'202000020400800', - b'1000100', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', - b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', - b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', - b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', - b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', - b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAABwACxEWEUgRWBEuADkRZgBmAAAcAAsRFhFIEVgRLgA5EWYAZgAAHAALERYRSBFYES4AORFmAGYA', - b'AAAAAA==', - b'', + b"ffffffff", + b"OUTFIT", + b"8", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"202000020400800", + b"1000100", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAABwACxEWEUgRWBEuADkRZgBmAAAcAAsRFhFIEVgRLgA5EWYAZgAAHAALERYRSBFYES4AORFmAGYA", + b"AAAAAA==", + b"", ] weapondata = [ - b'ffffffff', - b'WEAPON', - b'8', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'201000000003', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', - b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', - b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', - b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', - b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', - b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', - b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', - b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', + b"ffffffff", + b"WEAPON", + b"8", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"201000000003", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", ] mainruledata = [ - b'ffffffff', - b'MAINRULE', - b'8', - b'6', - b'800000', - b'1', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'0', - b'1.000000', - b'0.000000', - b'10.000000', - b'4.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'0.000000', - b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', - b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', - b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', - b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAA=', - b'', - b'', - b'', - b'', + b"ffffffff", + b"MAINRULE", + b"8", + b"6", + b"800000", + b"1", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"0", + b"1.000000", + b"0.000000", + b"10.000000", + b"4.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"0.000000", + b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAA=", + b"", + b"", + b"", + b"", ] - if msg_type == 'new': + if msg_type == "new": # New profile gets blank name, because we save over it at the end of the round. - profiledata[27] = b'' - elif msg_type == 'existing': + profiledata[27] = b"" + elif msg_type == "existing": # Exiting profile gets our hardcoded name saved. - profiledata[27] = self.NAME.encode('shift-jis') + profiledata[27] = self.NAME.encode("shift-jis") # Construct node - playerdata = Node.void('playerdata') + playerdata = Node.void("playerdata") call.add_child(playerdata) - playerdata.set_attribute('method', 'usergamedata_send') - playerdata.add_child(Node.u32('retrycnt', 0)) + playerdata.set_attribute("method", "usergamedata_send") + playerdata.add_child(Node.u32("retrycnt", 0)) - data = Node.void('data') + data = Node.void("data") playerdata.add_child(data) - data.add_child(Node.string('eaid', ref_id)) - data.add_child(Node.string('gamekind', 'I36')) - data.add_child(Node.u32('datanum', 4)) - record = Node.void('record') + data.add_child(Node.string("eaid", ref_id)) + data.add_child(Node.string("gamekind", "I36")) + data.add_child(Node.u32("datanum", 4)) + record = Node.void("record") data.add_child(record) - d = Node.string('d', base64.b64encode(b','.join(profiledata)).decode('ascii')) + d = Node.string("d", base64.b64encode(b",".join(profiledata)).decode("ascii")) record.add_child(d) - d.add_child(Node.string('bin1', '')) - d = Node.string('d', base64.b64encode(b','.join(outfitdata)).decode('ascii')) + d.add_child(Node.string("bin1", "")) + d = Node.string("d", base64.b64encode(b",".join(outfitdata)).decode("ascii")) record.add_child(d) - d.add_child(Node.string('bin1', '')) - d = Node.string('d', base64.b64encode(b','.join(weapondata)).decode('ascii')) + d.add_child(Node.string("bin1", "")) + d = Node.string("d", base64.b64encode(b",".join(weapondata)).decode("ascii")) record.add_child(d) - d.add_child(Node.string('bin1', '')) - d = Node.string('d', base64.b64encode(b','.join(mainruledata)).decode('ascii')) + d.add_child(Node.string("bin1", "")) + d = Node.string("d", base64.b64encode(b",".join(mainruledata)).decode("ascii")) record.add_child(d) - d.add_child(Node.string('bin1', '')) + d.add_child(Node.string("bin1", "")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/playerdata/result") def verify_usergamedata_recv(self, ref_id: str) -> str: call = self.call_node() # Construct node - playerdata = Node.void('playerdata') + playerdata = Node.void("playerdata") call.add_child(playerdata) - playerdata.set_attribute('method', 'usergamedata_recv') - data = Node.void('data') + playerdata.set_attribute("method", "usergamedata_recv") + data = Node.void("data") playerdata.add_child(data) - data.add_child(Node.string('eaid', ref_id)) - data.add_child(Node.string('gamekind', 'I36')) - data.add_child(Node.u32('recv_num', 4)) - data.add_child(Node.string('recv_csv', 'PLAYDATA,3fffffffff,OUTFIT,3fffffffff,WEAPON,3fffffffff,MAINRULE,3fffffffff')) + data.add_child(Node.string("eaid", ref_id)) + data.add_child(Node.string("gamekind", "I36")) + data.add_child(Node.u32("recv_num", 4)) + data.add_child( + Node.string( + "recv_csv", + "PLAYDATA,3fffffffff,OUTFIT,3fffffffff,WEAPON,3fffffffff,MAINRULE,3fffffffff", + ) + ) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/playerdata/result") self.assert_path(resp, "response/playerdata/player/record/d/bin1") self.assert_path(resp, "response/playerdata/player/record_num") # Grab binary data, parse out name - bindata = resp.child_value('playerdata/player/record/d') - profiledata = base64.b64decode(bindata).split(b',') + bindata = resp.child_value("playerdata/player/record/d") + profiledata = base64.b64decode(bindata).split(b",") # We lob off the first two values in returning profile, so the name is offset by two - return profiledata[25].decode('shift-jis') + return profiledata[25].decode("shift-jis") def verify(self, cardid: Optional[str]) -> None: # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -313,33 +318,43 @@ class MetalGearArcadeClient(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # MGA doesn't read a new profile, it just writes out CSV for a blank one - self.verify_usergamedata_send(ref_id, msg_type='new') + self.verify_usergamedata_send(ref_id, msg_type="new") else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") if cardid is None: # Verify profile saving name = self.verify_usergamedata_recv(ref_id) - if name != '': - raise Exception('New profile has a name associated with it!') + if name != "": + raise Exception("New profile has a name associated with it!") - self.verify_usergamedata_send(ref_id, msg_type='existing') + self.verify_usergamedata_send(ref_id, msg_type="existing") name = self.verify_usergamedata_recv(ref_id) if name != self.NAME: - raise Exception('Existing profile has no name associated with it!') + raise Exception("Existing profile has no name associated with it!") else: print("Skipping score checks for existing card") diff --git a/bemani/client/museca/museca1.py b/bemani/client/museca/museca1.py index ed94d57..1dab0db 100644 --- a/bemani/client/museca/museca1.py +++ b/bemani/client/museca/museca1.py @@ -7,30 +7,30 @@ from bemani.protocol import Node class Museca1Client(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_eventlog_write(self, location: str) -> None: call = self.call_node() # Construct node - eventlog = Node.void('eventlog') + eventlog = Node.void("eventlog") call.add_child(eventlog) - eventlog.set_attribute('method', 'write') - eventlog.add_child(Node.u32('retrycnt', 0)) - data = Node.void('data') + eventlog.set_attribute("method", "write") + eventlog.add_child(Node.u32("retrycnt", 0)) + data = Node.void("data") eventlog.add_child(data) - data.add_child(Node.string('eventid', 'S_PWRON')) - data.add_child(Node.s32('eventorder', 0)) - data.add_child(Node.u64('pcbtime', int(time.time() * 1000))) - data.add_child(Node.s64('gamesession', -1)) - data.add_child(Node.string('strdata1', '2.4.0')) - data.add_child(Node.string('strdata2', '')) - data.add_child(Node.s64('numdata1', 1)) - data.add_child(Node.s64('numdata2', 0)) - data.add_child(Node.string('locationid', location)) + data.add_child(Node.string("eventid", "S_PWRON")) + data.add_child(Node.s32("eventorder", 0)) + data.add_child(Node.u64("pcbtime", int(time.time() * 1000))) + data.add_child(Node.s64("gamesession", -1)) + data.add_child(Node.string("strdata1", "2.4.0")) + data.add_child(Node.string("strdata2", "")) + data.add_child(Node.s64("numdata1", 1)) + data.add_child(Node.s64("numdata2", 0)) + data.add_child(Node.string("locationid", location)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/eventlog/gamesession") @@ -41,14 +41,14 @@ class Museca1Client(BaseClient): def verify_game_hiscore(self, location: str) -> None: call = self.call_node() - game = Node.void('game_3') - game.set_attribute('ver', '0') - game.set_attribute('method', 'hiscore') - game.add_child(Node.string('locid', location)) + game = Node.void("game_3") + game.set_attribute("ver", "0") + game.set_attribute("method", "hiscore") + game.add_child(Node.string("locid", location)) call.add_child(game) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/hitchart/info/id") @@ -70,48 +70,48 @@ class Museca1Client(BaseClient): def verify_game_shop(self, location: str) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'shop') - game.set_attribute('ver', '0') - game.add_child(Node.string('locid', location)) - game.add_child(Node.string('regcode', '.')) - game.add_child(Node.string('locname', '')) - game.add_child(Node.u8('loctype', 0)) - game.add_child(Node.string('cstcode', '')) - game.add_child(Node.string('cpycode', '')) - game.add_child(Node.s32('latde', 0)) - game.add_child(Node.s32('londe', 0)) - game.add_child(Node.u8('accu', 0)) - game.add_child(Node.string('linid', '.')) - game.add_child(Node.u8('linclass', 0)) - game.add_child(Node.ipv4('ipaddr', '0.0.0.0')) - game.add_child(Node.string('hadid', '00010203040506070809')) - game.add_child(Node.string('licid', '00010203040506070809')) - game.add_child(Node.string('actid', self.pcbid)) - game.add_child(Node.s8('appstate', 0)) - game.add_child(Node.s8('c_need', 1)) - game.add_child(Node.s8('c_credit', 2)) - game.add_child(Node.s8('s_credit', 2)) - game.add_child(Node.bool('free_p', True)) - game.add_child(Node.bool('close', False)) - game.add_child(Node.s32('close_t', 1380)) - game.add_child(Node.u32('playc', 0)) - game.add_child(Node.u32('playn', 0)) - game.add_child(Node.u32('playe', 0)) - game.add_child(Node.u32('test_m', 0)) - game.add_child(Node.u32('service', 0)) - game.add_child(Node.bool('paseli', True)) - game.add_child(Node.u32('update', 0)) - game.add_child(Node.string('shopname', '')) - game.add_child(Node.bool('newpc', False)) - game.add_child(Node.s32('s_paseli', 206)) - game.add_child(Node.s32('monitor', 1)) - game.add_child(Node.string('romnumber', '-')) - game.add_child(Node.string('etc', 'TaxMode:1,BasicRate:100/1,FirstFree:0')) + game.set_attribute("method", "shop") + game.set_attribute("ver", "0") + game.add_child(Node.string("locid", location)) + game.add_child(Node.string("regcode", ".")) + game.add_child(Node.string("locname", "")) + game.add_child(Node.u8("loctype", 0)) + game.add_child(Node.string("cstcode", "")) + game.add_child(Node.string("cpycode", "")) + game.add_child(Node.s32("latde", 0)) + game.add_child(Node.s32("londe", 0)) + game.add_child(Node.u8("accu", 0)) + game.add_child(Node.string("linid", ".")) + game.add_child(Node.u8("linclass", 0)) + game.add_child(Node.ipv4("ipaddr", "0.0.0.0")) + game.add_child(Node.string("hadid", "00010203040506070809")) + game.add_child(Node.string("licid", "00010203040506070809")) + game.add_child(Node.string("actid", self.pcbid)) + game.add_child(Node.s8("appstate", 0)) + game.add_child(Node.s8("c_need", 1)) + game.add_child(Node.s8("c_credit", 2)) + game.add_child(Node.s8("s_credit", 2)) + game.add_child(Node.bool("free_p", True)) + game.add_child(Node.bool("close", False)) + game.add_child(Node.s32("close_t", 1380)) + game.add_child(Node.u32("playc", 0)) + game.add_child(Node.u32("playn", 0)) + game.add_child(Node.u32("playe", 0)) + game.add_child(Node.u32("test_m", 0)) + game.add_child(Node.u32("service", 0)) + game.add_child(Node.bool("paseli", True)) + game.add_child(Node.u32("update", 0)) + game.add_child(Node.string("shopname", "")) + game.add_child(Node.bool("newpc", False)) + game.add_child(Node.s32("s_paseli", 206)) + game.add_child(Node.s32("monitor", 1)) + game.add_child(Node.string("romnumber", "-")) + game.add_child(Node.string("etc", "TaxMode:1,BasicRate:100/1,FirstFree:0")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/nxt_time") @@ -119,14 +119,14 @@ class Museca1Client(BaseClient): def verify_game_exception(self, location: str) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'exception') - game.add_child(Node.string('text', '')) - game.add_child(Node.string('lid', location)) + game.set_attribute("method", "exception") + game.add_child(Node.string("text", "")) + game.add_child(Node.string("lid", location)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/@status") @@ -134,17 +134,17 @@ class Museca1Client(BaseClient): def verify_game_new(self, location: str, refid: str) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'new') - game.set_attribute('ver', '0') - game.add_child(Node.string('dataid', refid)) - game.add_child(Node.string('refid', refid)) - game.add_child(Node.string('name', self.NAME)) - game.add_child(Node.string('locid', location)) + game.set_attribute("method", "new") + game.set_attribute("ver", "0") + game.add_child(Node.string("dataid", refid)) + game.add_child(Node.string("refid", refid)) + game.add_child(Node.string("name", self.NAME)) + game.add_child(Node.string("locid", location)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3") @@ -152,58 +152,65 @@ class Museca1Client(BaseClient): def verify_game_frozen(self, refid: str, time: int) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'frozen') - game.add_child(Node.string('refid', refid)) - game.add_child(Node.u32('sec', time)) + game.set_attribute("ver", "0") + game.set_attribute("method", "frozen") + game.add_child(Node.string("refid", refid)) + game.add_child(Node.u32("sec", time)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/result") - def verify_game_save(self, location: str, refid: str, packet: int, block: int, blaster_energy: int) -> None: + def verify_game_save( + self, location: str, refid: str, packet: int, block: int, blaster_energy: int + ) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'save') - game.set_attribute('ver', '0') - game.add_child(Node.string('refid', refid)) - game.add_child(Node.string('locid', location)) - game.add_child(Node.u8('headphone', 0)) - game.add_child(Node.u16('appeal_id', 1001)) - game.add_child(Node.u16('comment_id', 0)) - game.add_child(Node.s32('music_id', 29)) - game.add_child(Node.u8('music_type', 1)) - game.add_child(Node.u8('sort_type', 1)) - game.add_child(Node.u8('narrow_down', 0)) - game.add_child(Node.u8('gauge_option', 0)) - game.add_child(Node.u32('earned_gamecoin_packet', packet)) - game.add_child(Node.u32('earned_gamecoin_block', block)) - item = Node.void('item') + game.set_attribute("method", "save") + game.set_attribute("ver", "0") + game.add_child(Node.string("refid", refid)) + game.add_child(Node.string("locid", location)) + game.add_child(Node.u8("headphone", 0)) + game.add_child(Node.u16("appeal_id", 1001)) + game.add_child(Node.u16("comment_id", 0)) + game.add_child(Node.s32("music_id", 29)) + game.add_child(Node.u8("music_type", 1)) + game.add_child(Node.u8("sort_type", 1)) + game.add_child(Node.u8("narrow_down", 0)) + game.add_child(Node.u8("gauge_option", 0)) + game.add_child(Node.u32("earned_gamecoin_packet", packet)) + game.add_child(Node.u32("earned_gamecoin_block", block)) + item = Node.void("item") game.add_child(item) - info = Node.void('info') + info = Node.void("info") item.add_child(info) - info.add_child(Node.u32('id', 1)) - info.add_child(Node.u32('type', 5)) - info.add_child(Node.u32('param', 333333376)) - info = Node.void('info') + info.add_child(Node.u32("id", 1)) + info.add_child(Node.u32("type", 5)) + info.add_child(Node.u32("param", 333333376)) + info = Node.void("info") item.add_child(info) - info.add_child(Node.u32('id', 1)) - info.add_child(Node.u32('type', 7)) - info.add_child(Node.u32('param', 1)) - info.add_child(Node.s32('diff_param', 1)) - game.add_child(Node.s32_array('hidden_param', [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])) - game.add_child(Node.s16('skill_name_id', -1)) - game.add_child(Node.s32('earned_blaster_energy', blaster_energy)) - game.add_child(Node.u32('blaster_count', 0)) + info.add_child(Node.u32("id", 1)) + info.add_child(Node.u32("type", 7)) + info.add_child(Node.u32("param", 1)) + info.add_child(Node.s32("diff_param", 1)) + game.add_child( + Node.s32_array( + "hidden_param", + [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ) + ) + game.add_child(Node.s16("skill_name_id", -1)) + game.add_child(Node.s32("earned_blaster_energy", blaster_energy)) + game.add_child(Node.u32("blaster_count", 0)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3") @@ -211,36 +218,38 @@ class Museca1Client(BaseClient): def verify_game_common(self) -> None: call = self.call_node() - game = Node.void('game_3') - game.set_attribute('ver', '0') - game.set_attribute('method', 'common') + game = Node.void("game_3") + game.set_attribute("ver", "0") + game.set_attribute("method", "common") call.add_child(game) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/music_limited") self.assert_path(resp, "response/game_3/event") - def verify_game_load(self, cardid: str, refid: str, msg_type: str) -> Dict[str, Any]: + def verify_game_load( + self, cardid: str, refid: str, msg_type: str + ) -> Dict[str, Any]: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'load') - game.set_attribute('ver', '0') - game.add_child(Node.string('dataid', refid)) - game.add_child(Node.string('cardid', cardid)) - game.add_child(Node.string('refid', refid)) + game.set_attribute("method", "load") + game.set_attribute("ver", "0") + game.add_child(Node.string("dataid", refid)) + game.add_child(Node.string("cardid", cardid)) + game.add_child(Node.string("refid", refid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct if msg_type == "new": self.assert_path(resp, "response/game_3/result") - if resp.child_value('game_3/result') != 1: + if resp.child_value("game_3/result") != 1: raise Exception("Invalid result for new profile!") return None @@ -259,24 +268,24 @@ class Museca1Client(BaseClient): self.assert_path(resp, "response/game_3/item") items: Dict[int, Dict[int, int]] = {} - for child in resp.child('game_3/item').children: - if child.name != 'info': + for child in resp.child("game_3/item").children: + if child.name != "info": continue - itype = child.child_value('type') - iid = child.child_value('id') - param = child.child_value('param') + itype = child.child_value("type") + iid = child.child_value("id") + param = child.child_value("param") if itype not in items: items[itype] = {} items[itype][iid] = param return { - 'name': resp.child_value('game_3/name'), - 'packet': resp.child_value('game_3/gamecoin_packet'), - 'block': resp.child_value('game_3/gamecoin_block'), - 'blaster_energy': resp.child_value('game_3/blaster_energy'), - 'items': items, + "name": resp.child_value("game_3/name"), + "packet": resp.child_value("game_3/gamecoin_packet"), + "block": resp.child_value("game_3/gamecoin_block"), + "blaster_energy": resp.child_value("game_3/blaster_energy"), + "items": items, } else: raise Exception(f"Invalid game load type {msg_type}") @@ -284,25 +293,25 @@ class Museca1Client(BaseClient): def verify_game_play_e(self, location: str, refid: str) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'play_e') - game.add_child(Node.string('dataid', refid)) - game.add_child(Node.s8('mode', 0)) - game.add_child(Node.s16('track_num', 3)) - game.add_child(Node.s32('s_coin', 0)) - game.add_child(Node.s32('s_paseli', 0)) - game.add_child(Node.s16('blaster_count', 0)) - game.add_child(Node.s16('blaster_cartridge', 0)) - game.add_child(Node.string('locid', location)) - game.add_child(Node.u16('drop_frame', 396)) - game.add_child(Node.u16('drop_frame_max', 396)) - game.add_child(Node.u16('drop_count', 1)) - game.add_child(Node.string('etc', 'StoryID:0,StoryPrg:0,PrgPrm:0')) + game.set_attribute("ver", "0") + game.set_attribute("method", "play_e") + game.add_child(Node.string("dataid", refid)) + game.add_child(Node.s8("mode", 0)) + game.add_child(Node.s16("track_num", 3)) + game.add_child(Node.s32("s_coin", 0)) + game.add_child(Node.s32("s_paseli", 0)) + game.add_child(Node.s16("blaster_count", 0)) + game.add_child(Node.s16("blaster_cartridge", 0)) + game.add_child(Node.string("locid", location)) + game.add_child(Node.u16("drop_frame", 396)) + game.add_child(Node.u16("drop_frame_max", 396)) + game.add_child(Node.u16("drop_count", 1)) + game.add_child(Node.string("etc", "StoryID:0,StoryPrg:0,PrgPrm:0")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3") @@ -310,69 +319,73 @@ class Museca1Client(BaseClient): def verify_game_load_m(self, refid: str) -> List[Dict[str, int]]: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'load_m') - game.set_attribute('ver', '0') - game.add_child(Node.string('dataid', refid)) + game.set_attribute("method", "load_m") + game.set_attribute("ver", "0") + game.add_child(Node.string("dataid", refid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/new") scores = [] - for child in resp.child('game_3/new').children: - if child.name != 'music': + for child in resp.child("game_3/new").children: + if child.name != "music": continue - musicid = child.child_value('music_id') - chart = child.child_value('music_type') - clear_type = child.child_value('clear_type') - score = child.child_value('score') - grade = child.child_value('score_grade') + musicid = child.child_value("music_id") + chart = child.child_value("music_type") + clear_type = child.child_value("clear_type") + score = child.child_value("score") + grade = child.child_value("score_grade") - scores.append({ - 'id': musicid, - 'chart': chart, - 'clear_type': clear_type, - 'score': score, - 'grade': grade, - }) + scores.append( + { + "id": musicid, + "chart": chart, + "clear_type": clear_type, + "score": score, + "grade": grade, + } + ) return scores - def verify_game_save_m(self, location: str, refid: str, score: Dict[str, int]) -> None: + def verify_game_save_m( + self, location: str, refid: str, score: Dict[str, int] + ) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'save_m') - game.add_child(Node.string('refid', refid)) - game.add_child(Node.string('dataid', refid)) - game.add_child(Node.u32('music_id', score['id'])) - game.add_child(Node.u32('music_type', score['chart'])) - game.add_child(Node.u32('score', score['score'])) - game.add_child(Node.u32('clear_type', score['clear_type'])) - game.add_child(Node.u32('score_grade', score['grade'])) - game.add_child(Node.u32('max_chain', 0)) - game.add_child(Node.u32('critical', 0)) - game.add_child(Node.u32('near', 0)) - game.add_child(Node.u32('error', 0)) - game.add_child(Node.u32('effective_rate', 100)) - game.add_child(Node.u32('btn_rate', 0)) - game.add_child(Node.u32('long_rate', 0)) - game.add_child(Node.u32('vol_rate', 0)) - game.add_child(Node.u8('mode', 0)) - game.add_child(Node.u8('gauge_type', 0)) - game.add_child(Node.u16('online_num', 0)) - game.add_child(Node.u16('local_num', 0)) - game.add_child(Node.string('locid', location)) + game.set_attribute("ver", "0") + game.set_attribute("method", "save_m") + game.add_child(Node.string("refid", refid)) + game.add_child(Node.string("dataid", refid)) + game.add_child(Node.u32("music_id", score["id"])) + game.add_child(Node.u32("music_type", score["chart"])) + game.add_child(Node.u32("score", score["score"])) + game.add_child(Node.u32("clear_type", score["clear_type"])) + game.add_child(Node.u32("score_grade", score["grade"])) + game.add_child(Node.u32("max_chain", 0)) + game.add_child(Node.u32("critical", 0)) + game.add_child(Node.u32("near", 0)) + game.add_child(Node.u32("error", 0)) + game.add_child(Node.u32("effective_rate", 100)) + game.add_child(Node.u32("btn_rate", 0)) + game.add_child(Node.u32("long_rate", 0)) + game.add_child(Node.u32("vol_rate", 0)) + game.add_child(Node.u8("mode", 0)) + game.add_child(Node.u8("gauge_type", 0)) + game.add_child(Node.u16("online_num", 0)) + game.add_child(Node.u16("local_num", 0)) + game.add_child(Node.string("locid", location)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3") @@ -381,20 +394,20 @@ class Museca1Client(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -415,25 +428,35 @@ class Museca1Client(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Museca doesn't read the new profile, it asks for the profile itself after calling new - self.verify_game_load(card, ref_id, msg_type='new') + self.verify_game_load(card, ref_id, msg_type="new") self.verify_game_new(location, ref_id) - self.verify_game_load(card, ref_id, msg_type='existing') + self.verify_game_load(card, ref_id, msg_type="existing") else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Verify account freezing self.verify_game_frozen(ref_id, 900) @@ -442,31 +465,41 @@ class Museca1Client(BaseClient): if cardid is None: # Verify profile loading and saving - profile = self.verify_game_load(card, ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception(f'Profile has incorrect name {profile["name"]} associated with it!') - if profile['packet'] != 0: - raise Exception('Profile has nonzero blocks associated with it!') - if profile['block'] != 0: - raise Exception('Profile has nonzero packets associated with it!') - if profile['blaster_energy'] != 0: - raise Exception('Profile has nonzero blaster energy associated with it!') + profile = self.verify_game_load(card, ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception( + f'Profile has incorrect name {profile["name"]} associated with it!' + ) + if profile["packet"] != 0: + raise Exception("Profile has nonzero blocks associated with it!") + if profile["block"] != 0: + raise Exception("Profile has nonzero packets associated with it!") + if profile["blaster_energy"] != 0: + raise Exception( + "Profile has nonzero blaster energy associated with it!" + ) - self.verify_game_save(location, ref_id, packet=123, block=234, blaster_energy=42) - profile = self.verify_game_load(card, ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception(f'Profile has incorrect name {profile["name"]} associated with it!') - if profile['packet'] != 123: - raise Exception('Profile has invalid blocks associated with it!') - if profile['block'] != 234: - raise Exception('Profile has invalid packets associated with it!') - if profile['blaster_energy'] != 42: - raise Exception('Profile has invalid blaster energy associated with it!') + self.verify_game_save( + location, ref_id, packet=123, block=234, blaster_energy=42 + ) + profile = self.verify_game_load(card, ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception( + f'Profile has incorrect name {profile["name"]} associated with it!' + ) + if profile["packet"] != 123: + raise Exception("Profile has invalid blocks associated with it!") + if profile["block"] != 234: + raise Exception("Profile has invalid packets associated with it!") + if profile["blaster_energy"] != 42: + raise Exception( + "Profile has invalid blaster energy associated with it!" + ) # Verify empty profile has no scores on it scores = self.verify_game_load_m(ref_id) if len(scores) > 0: - raise Exception('Score on an empty profile!') + raise Exception("Score on an empty profile!") # Verify score saving and updating for phase in [1, 2]: @@ -474,57 +507,57 @@ class Museca1Client(BaseClient): dummyscores = [ # An okay score on a chart { - 'id': 1, - 'chart': 1, - 'grade': 3, - 'clear_type': 2, - 'score': 765432, + "id": 1, + "chart": 1, + "grade": 3, + "clear_type": 2, + "score": 765432, }, # A good score on an easier chart of the same song { - 'id': 1, - 'chart': 0, - 'grade': 6, - 'clear_type': 4, - 'score': 7654321, + "id": 1, + "chart": 0, + "grade": 6, + "clear_type": 4, + "score": 7654321, }, # A bad score on a hard chart { - 'id': 2, - 'chart': 2, - 'grade': 1, - 'clear_type': 1, - 'score': 12345, + "id": 2, + "chart": 2, + "grade": 1, + "clear_type": 1, + "score": 12345, }, # A terrible score on an easy chart { - 'id': 3, - 'chart': 0, - 'grade': 1, - 'clear_type': 1, - 'score': 123, + "id": 3, + "chart": 0, + "grade": 1, + "clear_type": 1, + "score": 123, }, ] if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 1, - 'chart': 1, - 'grade': 5, - 'clear_type': 4, - 'score': 8765432, + "id": 1, + "chart": 1, + "grade": 5, + "clear_type": 4, + "score": 8765432, }, # A worse score on another same chart { - 'id': 1, - 'chart': 0, - 'grade': 4, - 'clear_type': 2, - 'score': 6543210, - 'expected_score': 7654321, - 'expected_clear_type': 4, - 'expected_grade': 6, + "id": 1, + "chart": 0, + "grade": 4, + "clear_type": 2, + "score": 6543210, + "expected_score": 7654321, + "expected_clear_type": 4, + "expected_grade": 6, }, ] for dummyscore in dummyscores: @@ -534,32 +567,43 @@ class Museca1Client(BaseClient): for expected in dummyscores: actual = None for received in scores: - if received['id'] == expected['id'] and received['chart'] == expected['chart']: + if ( + received["id"] == expected["id"] + and received["chart"] == expected["chart"] + ): actual = received break if actual is None: - raise Exception(f"Didn't find song {expected['id']} chart {expected['chart']} in response!") + raise Exception( + f"Didn't find song {expected['id']} chart {expected['chart']} in response!" + ) - if 'expected_score' in expected: - expected_score = expected['expected_score'] + if "expected_score" in expected: + expected_score = expected["expected_score"] else: - expected_score = expected['score'] - if 'expected_grade' in expected: - expected_grade = expected['expected_grade'] + expected_score = expected["score"] + if "expected_grade" in expected: + expected_grade = expected["expected_grade"] else: - expected_grade = expected['grade'] - if 'expected_clear_type' in expected: - expected_clear_type = expected['expected_clear_type'] + expected_grade = expected["grade"] + if "expected_clear_type" in expected: + expected_clear_type = expected["expected_clear_type"] else: - expected_clear_type = expected['clear_type'] + expected_clear_type = expected["clear_type"] - if actual['score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'') - if actual['grade'] != expected_grade: - raise Exception(f'Expected a grade of \'{expected_grade}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got grade \'{actual["grade"]}\'') - if actual['clear_type'] != expected_clear_type: - raise Exception(f'Expected a clear_type of \'{expected_clear_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got clear_type \'{actual["clear_type"]}\'') + if actual["score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'' + ) + if actual["grade"] != expected_grade: + raise Exception( + f'Expected a grade of \'{expected_grade}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got grade \'{actual["grade"]}\'' + ) + if actual["clear_type"] != expected_clear_type: + raise Exception( + f'Expected a clear_type of \'{expected_clear_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got clear_type \'{actual["clear_type"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) diff --git a/bemani/client/museca/museca1plus.py b/bemani/client/museca/museca1plus.py index 0a3ac54..bd1b4a1 100644 --- a/bemani/client/museca/museca1plus.py +++ b/bemani/client/museca/museca1plus.py @@ -7,30 +7,30 @@ from bemani.protocol import Node class Museca1PlusClient(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_eventlog_write(self, location: str) -> None: call = self.call_node() # Construct node - eventlog = Node.void('eventlog') + eventlog = Node.void("eventlog") call.add_child(eventlog) - eventlog.set_attribute('method', 'write') - eventlog.add_child(Node.u32('retrycnt', 0)) - data = Node.void('data') + eventlog.set_attribute("method", "write") + eventlog.add_child(Node.u32("retrycnt", 0)) + data = Node.void("data") eventlog.add_child(data) - data.add_child(Node.string('eventid', 'S_PWRON')) - data.add_child(Node.s32('eventorder', 0)) - data.add_child(Node.u64('pcbtime', int(time.time() * 1000))) - data.add_child(Node.s64('gamesession', -1)) - data.add_child(Node.string('strdata1', '2.4.0')) - data.add_child(Node.string('strdata2', '')) - data.add_child(Node.s64('numdata1', 1)) - data.add_child(Node.s64('numdata2', 0)) - data.add_child(Node.string('locationid', location)) + data.add_child(Node.string("eventid", "S_PWRON")) + data.add_child(Node.s32("eventorder", 0)) + data.add_child(Node.u64("pcbtime", int(time.time() * 1000))) + data.add_child(Node.s64("gamesession", -1)) + data.add_child(Node.string("strdata1", "2.4.0")) + data.add_child(Node.string("strdata2", "")) + data.add_child(Node.s64("numdata1", 1)) + data.add_child(Node.s64("numdata2", 0)) + data.add_child(Node.string("locationid", location)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/eventlog/gamesession") @@ -41,14 +41,14 @@ class Museca1PlusClient(BaseClient): def verify_game_hiscore(self, location: str) -> None: call = self.call_node() - game = Node.void('game_3') - game.set_attribute('ver', '0') - game.set_attribute('method', 'hiscore') - game.add_child(Node.string('locid', location)) + game = Node.void("game_3") + game.set_attribute("ver", "0") + game.set_attribute("method", "hiscore") + game.add_child(Node.string("locid", location)) call.add_child(game) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/hitchart/info/id") @@ -70,14 +70,14 @@ class Museca1PlusClient(BaseClient): def verify_game_exception(self, location: str) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'exception') - game.add_child(Node.string('text', '')) - game.add_child(Node.string('lid', location)) + game.set_attribute("method", "exception") + game.add_child(Node.string("text", "")) + game.add_child(Node.string("lid", location)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/@status") @@ -85,48 +85,48 @@ class Museca1PlusClient(BaseClient): def verify_game_shop(self, location: str) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'shop') - game.set_attribute('ver', '0') - game.add_child(Node.string('locid', location)) - game.add_child(Node.string('regcode', '.')) - game.add_child(Node.string('locname', '')) - game.add_child(Node.u8('loctype', 0)) - game.add_child(Node.string('cstcode', '')) - game.add_child(Node.string('cpycode', '')) - game.add_child(Node.s32('latde', 0)) - game.add_child(Node.s32('londe', 0)) - game.add_child(Node.u8('accu', 0)) - game.add_child(Node.string('linid', '.')) - game.add_child(Node.u8('linclass', 0)) - game.add_child(Node.ipv4('ipaddr', '0.0.0.0')) - game.add_child(Node.string('hadid', '00010203040506070809')) - game.add_child(Node.string('licid', '00010203040506070809')) - game.add_child(Node.string('actid', self.pcbid)) - game.add_child(Node.s8('appstate', 0)) - game.add_child(Node.s8('c_need', 1)) - game.add_child(Node.s8('c_credit', 2)) - game.add_child(Node.s8('s_credit', 2)) - game.add_child(Node.bool('free_p', True)) - game.add_child(Node.bool('close', False)) - game.add_child(Node.s32('close_t', 1380)) - game.add_child(Node.u32('playc', 0)) - game.add_child(Node.u32('playn', 0)) - game.add_child(Node.u32('playe', 0)) - game.add_child(Node.u32('test_m', 0)) - game.add_child(Node.u32('service', 0)) - game.add_child(Node.bool('paseli', True)) - game.add_child(Node.u32('update', 0)) - game.add_child(Node.string('shopname', '')) - game.add_child(Node.bool('newpc', False)) - game.add_child(Node.s32('s_paseli', 206)) - game.add_child(Node.s32('monitor', 1)) - game.add_child(Node.string('romnumber', '-')) - game.add_child(Node.string('etc', 'TaxMode:1,BasicRate:100/1,FirstFree:0')) + game.set_attribute("method", "shop") + game.set_attribute("ver", "0") + game.add_child(Node.string("locid", location)) + game.add_child(Node.string("regcode", ".")) + game.add_child(Node.string("locname", "")) + game.add_child(Node.u8("loctype", 0)) + game.add_child(Node.string("cstcode", "")) + game.add_child(Node.string("cpycode", "")) + game.add_child(Node.s32("latde", 0)) + game.add_child(Node.s32("londe", 0)) + game.add_child(Node.u8("accu", 0)) + game.add_child(Node.string("linid", ".")) + game.add_child(Node.u8("linclass", 0)) + game.add_child(Node.ipv4("ipaddr", "0.0.0.0")) + game.add_child(Node.string("hadid", "00010203040506070809")) + game.add_child(Node.string("licid", "00010203040506070809")) + game.add_child(Node.string("actid", self.pcbid)) + game.add_child(Node.s8("appstate", 0)) + game.add_child(Node.s8("c_need", 1)) + game.add_child(Node.s8("c_credit", 2)) + game.add_child(Node.s8("s_credit", 2)) + game.add_child(Node.bool("free_p", True)) + game.add_child(Node.bool("close", False)) + game.add_child(Node.s32("close_t", 1380)) + game.add_child(Node.u32("playc", 0)) + game.add_child(Node.u32("playn", 0)) + game.add_child(Node.u32("playe", 0)) + game.add_child(Node.u32("test_m", 0)) + game.add_child(Node.u32("service", 0)) + game.add_child(Node.bool("paseli", True)) + game.add_child(Node.u32("update", 0)) + game.add_child(Node.string("shopname", "")) + game.add_child(Node.bool("newpc", False)) + game.add_child(Node.s32("s_paseli", 206)) + game.add_child(Node.s32("monitor", 1)) + game.add_child(Node.string("romnumber", "-")) + game.add_child(Node.string("etc", "TaxMode:1,BasicRate:100/1,FirstFree:0")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/nxt_time") @@ -134,17 +134,17 @@ class Museca1PlusClient(BaseClient): def verify_game_new(self, location: str, refid: str) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'new') - game.set_attribute('ver', '0') - game.add_child(Node.string('dataid', refid)) - game.add_child(Node.string('refid', refid)) - game.add_child(Node.string('name', self.NAME)) - game.add_child(Node.string('locid', location)) + game.set_attribute("method", "new") + game.set_attribute("ver", "0") + game.add_child(Node.string("dataid", refid)) + game.add_child(Node.string("refid", refid)) + game.add_child(Node.string("name", self.NAME)) + game.add_child(Node.string("locid", location)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3") @@ -152,58 +152,65 @@ class Museca1PlusClient(BaseClient): def verify_game_frozen(self, refid: str, time: int) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'frozen') - game.add_child(Node.string('refid', refid)) - game.add_child(Node.u32('sec', time)) + game.set_attribute("ver", "0") + game.set_attribute("method", "frozen") + game.add_child(Node.string("refid", refid)) + game.add_child(Node.u32("sec", time)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/result") - def verify_game_save(self, location: str, refid: str, packet: int, block: int, blaster_energy: int) -> None: + def verify_game_save( + self, location: str, refid: str, packet: int, block: int, blaster_energy: int + ) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'save') - game.set_attribute('ver', '0') - game.add_child(Node.string('refid', refid)) - game.add_child(Node.string('locid', location)) - game.add_child(Node.u8('headphone', 0)) - game.add_child(Node.u16('appeal_id', 1001)) - game.add_child(Node.u16('comment_id', 0)) - game.add_child(Node.s32('music_id', 29)) - game.add_child(Node.u8('music_type', 1)) - game.add_child(Node.u8('sort_type', 1)) - game.add_child(Node.u8('narrow_down', 0)) - game.add_child(Node.u8('gauge_option', 0)) - game.add_child(Node.u32('earned_gamecoin_packet', packet)) - game.add_child(Node.u32('earned_gamecoin_block', block)) - item = Node.void('item') + game.set_attribute("method", "save") + game.set_attribute("ver", "0") + game.add_child(Node.string("refid", refid)) + game.add_child(Node.string("locid", location)) + game.add_child(Node.u8("headphone", 0)) + game.add_child(Node.u16("appeal_id", 1001)) + game.add_child(Node.u16("comment_id", 0)) + game.add_child(Node.s32("music_id", 29)) + game.add_child(Node.u8("music_type", 1)) + game.add_child(Node.u8("sort_type", 1)) + game.add_child(Node.u8("narrow_down", 0)) + game.add_child(Node.u8("gauge_option", 0)) + game.add_child(Node.u32("earned_gamecoin_packet", packet)) + game.add_child(Node.u32("earned_gamecoin_block", block)) + item = Node.void("item") game.add_child(item) - info = Node.void('info') + info = Node.void("info") item.add_child(info) - info.add_child(Node.u32('id', 1)) - info.add_child(Node.u32('type', 5)) - info.add_child(Node.u32('param', 333333376)) - info = Node.void('info') + info.add_child(Node.u32("id", 1)) + info.add_child(Node.u32("type", 5)) + info.add_child(Node.u32("param", 333333376)) + info = Node.void("info") item.add_child(info) - info.add_child(Node.u32('id', 1)) - info.add_child(Node.u32('type', 7)) - info.add_child(Node.u32('param', 1)) - info.add_child(Node.s32('diff_param', 1)) - game.add_child(Node.s32_array('hidden_param', [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])) - game.add_child(Node.s16('skill_name_id', -1)) - game.add_child(Node.s32('earned_blaster_energy', blaster_energy)) - game.add_child(Node.u32('blaster_count', 0)) + info.add_child(Node.u32("id", 1)) + info.add_child(Node.u32("type", 7)) + info.add_child(Node.u32("param", 1)) + info.add_child(Node.s32("diff_param", 1)) + game.add_child( + Node.s32_array( + "hidden_param", + [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ) + ) + game.add_child(Node.s16("skill_name_id", -1)) + game.add_child(Node.s32("earned_blaster_energy", blaster_energy)) + game.add_child(Node.u32("blaster_count", 0)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3") @@ -211,36 +218,38 @@ class Museca1PlusClient(BaseClient): def verify_game_common(self) -> None: call = self.call_node() - game = Node.void('game_3') - game.set_attribute('ver', '0') - game.set_attribute('method', 'common') + game = Node.void("game_3") + game.set_attribute("ver", "0") + game.set_attribute("method", "common") call.add_child(game) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/music_limited") self.assert_path(resp, "response/game_3/event") - def verify_game_load(self, cardid: str, refid: str, msg_type: str) -> Dict[str, Any]: + def verify_game_load( + self, cardid: str, refid: str, msg_type: str + ) -> Dict[str, Any]: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'load') - game.set_attribute('ver', '0') - game.add_child(Node.string('dataid', refid)) - game.add_child(Node.string('cardid', cardid)) - game.add_child(Node.string('refid', refid)) + game.set_attribute("method", "load") + game.set_attribute("ver", "0") + game.add_child(Node.string("dataid", refid)) + game.add_child(Node.string("cardid", cardid)) + game.add_child(Node.string("refid", refid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct if msg_type == "new": self.assert_path(resp, "response/game_3/result") - if resp.child_value('game_3/result') != 1: + if resp.child_value("game_3/result") != 1: raise Exception("Invalid result for new profile!") return None @@ -260,24 +269,24 @@ class Museca1PlusClient(BaseClient): self.assert_path(resp, "response/game_3/item") items: Dict[int, Dict[int, int]] = {} - for child in resp.child('game_3/item').children: - if child.name != 'info': + for child in resp.child("game_3/item").children: + if child.name != "info": continue - itype = child.child_value('type') - iid = child.child_value('id') - param = child.child_value('param') + itype = child.child_value("type") + iid = child.child_value("id") + param = child.child_value("param") if itype not in items: items[itype] = {} items[itype][iid] = param return { - 'name': resp.child_value('game_3/name'), - 'packet': resp.child_value('game_3/gamecoin_packet'), - 'block': resp.child_value('game_3/gamecoin_block'), - 'blaster_energy': resp.child_value('game_3/blaster_energy'), - 'items': items, + "name": resp.child_value("game_3/name"), + "packet": resp.child_value("game_3/gamecoin_packet"), + "block": resp.child_value("game_3/gamecoin_block"), + "blaster_energy": resp.child_value("game_3/blaster_energy"), + "items": items, } else: raise Exception(f"Invalid game load type {msg_type}") @@ -285,25 +294,25 @@ class Museca1PlusClient(BaseClient): def verify_game_play_e(self, location: str, refid: str) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'play_e') - game.add_child(Node.string('dataid', refid)) - game.add_child(Node.s8('mode', 0)) - game.add_child(Node.s16('track_num', 3)) - game.add_child(Node.s32('s_coin', 0)) - game.add_child(Node.s32('s_paseli', 0)) - game.add_child(Node.s16('blaster_count', 0)) - game.add_child(Node.s16('blaster_cartridge', 0)) - game.add_child(Node.string('locid', location)) - game.add_child(Node.u16('drop_frame', 396)) - game.add_child(Node.u16('drop_frame_max', 396)) - game.add_child(Node.u16('drop_count', 1)) - game.add_child(Node.string('etc', 'StoryID:0,StoryPrg:0,PrgPrm:0')) + game.set_attribute("ver", "0") + game.set_attribute("method", "play_e") + game.add_child(Node.string("dataid", refid)) + game.add_child(Node.s8("mode", 0)) + game.add_child(Node.s16("track_num", 3)) + game.add_child(Node.s32("s_coin", 0)) + game.add_child(Node.s32("s_paseli", 0)) + game.add_child(Node.s16("blaster_count", 0)) + game.add_child(Node.s16("blaster_cartridge", 0)) + game.add_child(Node.string("locid", location)) + game.add_child(Node.u16("drop_frame", 396)) + game.add_child(Node.u16("drop_frame_max", 396)) + game.add_child(Node.u16("drop_count", 1)) + game.add_child(Node.string("etc", "StoryID:0,StoryPrg:0,PrgPrm:0")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3") @@ -311,13 +320,13 @@ class Museca1PlusClient(BaseClient): def verify_game_lounge(self) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'lounge') - game.set_attribute('ver', '0') + game.set_attribute("method", "lounge") + game.set_attribute("ver", "0") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/interval") @@ -325,69 +334,73 @@ class Museca1PlusClient(BaseClient): def verify_game_load_m(self, refid: str) -> List[Dict[str, int]]: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'load_m') - game.set_attribute('ver', '0') - game.add_child(Node.string('dataid', refid)) + game.set_attribute("method", "load_m") + game.set_attribute("ver", "0") + game.add_child(Node.string("dataid", refid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/new") scores = [] - for child in resp.child('game_3/new').children: - if child.name != 'music': + for child in resp.child("game_3/new").children: + if child.name != "music": continue - musicid = child.child_value('music_id') - chart = child.child_value('music_type') - clear_type = child.child_value('clear_type') - score = child.child_value('score') - grade = child.child_value('score_grade') + musicid = child.child_value("music_id") + chart = child.child_value("music_type") + clear_type = child.child_value("clear_type") + score = child.child_value("score") + grade = child.child_value("score_grade") - scores.append({ - 'id': musicid, - 'chart': chart, - 'clear_type': clear_type, - 'score': score, - 'grade': grade, - }) + scores.append( + { + "id": musicid, + "chart": chart, + "clear_type": clear_type, + "score": score, + "grade": grade, + } + ) return scores - def verify_game_save_m(self, location: str, refid: str, score: Dict[str, int]) -> None: + def verify_game_save_m( + self, location: str, refid: str, score: Dict[str, int] + ) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'save_m') - game.add_child(Node.string('refid', refid)) - game.add_child(Node.string('dataid', refid)) - game.add_child(Node.u32('music_id', score['id'])) - game.add_child(Node.u32('music_type', score['chart'])) - game.add_child(Node.u32('score', score['score'])) - game.add_child(Node.u32('clear_type', score['clear_type'])) - game.add_child(Node.u32('score_grade', score['grade'])) - game.add_child(Node.u32('max_chain', 0)) - game.add_child(Node.u32('critical', 0)) - game.add_child(Node.u32('near', 0)) - game.add_child(Node.u32('error', 0)) - game.add_child(Node.u32('effective_rate', 100)) - game.add_child(Node.u32('btn_rate', 0)) - game.add_child(Node.u32('long_rate', 0)) - game.add_child(Node.u32('vol_rate', 0)) - game.add_child(Node.u8('mode', 0)) - game.add_child(Node.u8('gauge_type', 0)) - game.add_child(Node.u16('online_num', 0)) - game.add_child(Node.u16('local_num', 0)) - game.add_child(Node.string('locid', location)) + game.set_attribute("ver", "0") + game.set_attribute("method", "save_m") + game.add_child(Node.string("refid", refid)) + game.add_child(Node.string("dataid", refid)) + game.add_child(Node.u32("music_id", score["id"])) + game.add_child(Node.u32("music_type", score["chart"])) + game.add_child(Node.u32("score", score["score"])) + game.add_child(Node.u32("clear_type", score["clear_type"])) + game.add_child(Node.u32("score_grade", score["grade"])) + game.add_child(Node.u32("max_chain", 0)) + game.add_child(Node.u32("critical", 0)) + game.add_child(Node.u32("near", 0)) + game.add_child(Node.u32("error", 0)) + game.add_child(Node.u32("effective_rate", 100)) + game.add_child(Node.u32("btn_rate", 0)) + game.add_child(Node.u32("long_rate", 0)) + game.add_child(Node.u32("vol_rate", 0)) + game.add_child(Node.u8("mode", 0)) + game.add_child(Node.u8("gauge_type", 0)) + game.add_child(Node.u16("online_num", 0)) + game.add_child(Node.u16("local_num", 0)) + game.add_child(Node.string("locid", location)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3") @@ -396,20 +409,20 @@ class Museca1PlusClient(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -430,25 +443,35 @@ class Museca1PlusClient(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Museca doesn't read the new profile, it asks for the profile itself after calling new - self.verify_game_load(card, ref_id, msg_type='new') + self.verify_game_load(card, ref_id, msg_type="new") self.verify_game_new(location, ref_id) - self.verify_game_load(card, ref_id, msg_type='existing') + self.verify_game_load(card, ref_id, msg_type="existing") else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Verify account freezing self.verify_game_frozen(ref_id, 900) @@ -460,31 +483,41 @@ class Museca1PlusClient(BaseClient): if cardid is None: # Verify profile loading and saving - profile = self.verify_game_load(card, ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception(f'Profile has incorrect name {profile["name"]} associated with it!') - if profile['packet'] != 0: - raise Exception('Profile has nonzero blocks associated with it!') - if profile['block'] != 0: - raise Exception('Profile has nonzero packets associated with it!') - if profile['blaster_energy'] != 0: - raise Exception('Profile has nonzero blaster energy associated with it!') + profile = self.verify_game_load(card, ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception( + f'Profile has incorrect name {profile["name"]} associated with it!' + ) + if profile["packet"] != 0: + raise Exception("Profile has nonzero blocks associated with it!") + if profile["block"] != 0: + raise Exception("Profile has nonzero packets associated with it!") + if profile["blaster_energy"] != 0: + raise Exception( + "Profile has nonzero blaster energy associated with it!" + ) - self.verify_game_save(location, ref_id, packet=123, block=234, blaster_energy=42) - profile = self.verify_game_load(card, ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception(f'Profile has incorrect name {profile["name"]} associated with it!') - if profile['packet'] != 123: - raise Exception('Profile has invalid blocks associated with it!') - if profile['block'] != 234: - raise Exception('Profile has invalid packets associated with it!') - if profile['blaster_energy'] != 42: - raise Exception('Profile has invalid blaster energy associated with it!') + self.verify_game_save( + location, ref_id, packet=123, block=234, blaster_energy=42 + ) + profile = self.verify_game_load(card, ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception( + f'Profile has incorrect name {profile["name"]} associated with it!' + ) + if profile["packet"] != 123: + raise Exception("Profile has invalid blocks associated with it!") + if profile["block"] != 234: + raise Exception("Profile has invalid packets associated with it!") + if profile["blaster_energy"] != 42: + raise Exception( + "Profile has invalid blaster energy associated with it!" + ) # Verify empty profile has no scores on it scores = self.verify_game_load_m(ref_id) if len(scores) > 0: - raise Exception('Score on an empty profile!') + raise Exception("Score on an empty profile!") # Verify score saving and updating for phase in [1, 2]: @@ -492,57 +525,57 @@ class Museca1PlusClient(BaseClient): dummyscores = [ # An okay score on a chart { - 'id': 1, - 'chart': 1, - 'grade': 3, - 'clear_type': 2, - 'score': 765432, + "id": 1, + "chart": 1, + "grade": 3, + "clear_type": 2, + "score": 765432, }, # A good score on an easier chart of the same song { - 'id': 1, - 'chart': 0, - 'grade': 6, - 'clear_type': 4, - 'score': 7654321, + "id": 1, + "chart": 0, + "grade": 6, + "clear_type": 4, + "score": 7654321, }, # A bad score on a hard chart { - 'id': 2, - 'chart': 2, - 'grade': 1, - 'clear_type': 1, - 'score': 12345, + "id": 2, + "chart": 2, + "grade": 1, + "clear_type": 1, + "score": 12345, }, # A terrible score on an easy chart { - 'id': 3, - 'chart': 0, - 'grade': 1, - 'clear_type': 1, - 'score': 123, + "id": 3, + "chart": 0, + "grade": 1, + "clear_type": 1, + "score": 123, }, ] if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 1, - 'chart': 1, - 'grade': 5, - 'clear_type': 4, - 'score': 8765432, + "id": 1, + "chart": 1, + "grade": 5, + "clear_type": 4, + "score": 8765432, }, # A worse score on another same chart { - 'id': 1, - 'chart': 0, - 'grade': 4, - 'clear_type': 2, - 'score': 6543210, - 'expected_score': 7654321, - 'expected_clear_type': 4, - 'expected_grade': 6, + "id": 1, + "chart": 0, + "grade": 4, + "clear_type": 2, + "score": 6543210, + "expected_score": 7654321, + "expected_clear_type": 4, + "expected_grade": 6, }, ] for dummyscore in dummyscores: @@ -552,32 +585,43 @@ class Museca1PlusClient(BaseClient): for expected in dummyscores: actual = None for received in scores: - if received['id'] == expected['id'] and received['chart'] == expected['chart']: + if ( + received["id"] == expected["id"] + and received["chart"] == expected["chart"] + ): actual = received break if actual is None: - raise Exception(f"Didn't find song {expected['id']} chart {expected['chart']} in response!") + raise Exception( + f"Didn't find song {expected['id']} chart {expected['chart']} in response!" + ) - if 'expected_score' in expected: - expected_score = expected['expected_score'] + if "expected_score" in expected: + expected_score = expected["expected_score"] else: - expected_score = expected['score'] - if 'expected_grade' in expected: - expected_grade = expected['expected_grade'] + expected_score = expected["score"] + if "expected_grade" in expected: + expected_grade = expected["expected_grade"] else: - expected_grade = expected['grade'] - if 'expected_clear_type' in expected: - expected_clear_type = expected['expected_clear_type'] + expected_grade = expected["grade"] + if "expected_clear_type" in expected: + expected_clear_type = expected["expected_clear_type"] else: - expected_clear_type = expected['clear_type'] + expected_clear_type = expected["clear_type"] - if actual['score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'') - if actual['grade'] != expected_grade: - raise Exception(f'Expected a grade of \'{expected_grade}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got grade \'{actual["grade"]}\'') - if actual['clear_type'] != expected_clear_type: - raise Exception(f'Expected a clear_type of \'{expected_clear_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got clear_type \'{actual["clear_type"]}\'') + if actual["score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'' + ) + if actual["grade"] != expected_grade: + raise Exception( + f'Expected a grade of \'{expected_grade}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got grade \'{actual["grade"]}\'' + ) + if actual["clear_type"] != expected_clear_type: + raise Exception( + f'Expected a clear_type of \'{expected_clear_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got clear_type \'{actual["clear_type"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) diff --git a/bemani/client/popn/eclale.py b/bemani/client/popn/eclale.py index 2aa187b..0cca4f2 100644 --- a/bemani/client/popn/eclale.py +++ b/bemani/client/popn/eclale.py @@ -7,33 +7,33 @@ from bemani.protocol import Node class PopnMusicEclaleClient(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_pcb23_boot(self, loc: str) -> None: call = self.call_node() # Construct node - pcb23 = Node.void('pcb23') + pcb23 = Node.void("pcb23") call.add_child(pcb23) - pcb23.set_attribute('method', 'boot') - pcb23.add_child(Node.string('loc_id', loc)) - pcb23.add_child(Node.u8('loc_type', 0)) - pcb23.add_child(Node.string('loc_name', '')) - pcb23.add_child(Node.string('country', 'US')) - pcb23.add_child(Node.string('region', '.')) - pcb23.add_child(Node.s16('pref', 51)) - pcb23.add_child(Node.string('customer', '')) - pcb23.add_child(Node.string('company', '')) - pcb23.add_child(Node.ipv4('gip', '127.0.0.1')) - pcb23.add_child(Node.u16('gp', 10011)) - pcb23.add_child(Node.string('rom_number', 'M39-JB-G01')) - pcb23.add_child(Node.u64('c_drive', 10028228608)) - pcb23.add_child(Node.u64('d_drive', 47945170944)) - pcb23.add_child(Node.u64('e_drive', 10394677248)) - pcb23.add_child(Node.string('etc', '')) + pcb23.set_attribute("method", "boot") + pcb23.add_child(Node.string("loc_id", loc)) + pcb23.add_child(Node.u8("loc_type", 0)) + pcb23.add_child(Node.string("loc_name", "")) + pcb23.add_child(Node.string("country", "US")) + pcb23.add_child(Node.string("region", ".")) + pcb23.add_child(Node.s16("pref", 51)) + pcb23.add_child(Node.string("customer", "")) + pcb23.add_child(Node.string("company", "")) + pcb23.add_child(Node.ipv4("gip", "127.0.0.1")) + pcb23.add_child(Node.u16("gp", 10011)) + pcb23.add_child(Node.string("rom_number", "M39-JB-G01")) + pcb23.add_child(Node.u64("c_drive", 10028228608)) + pcb23.add_child(Node.u64("d_drive", 47945170944)) + pcb23.add_child(Node.u64("e_drive", 10394677248)) + pcb23.add_child(Node.string("etc", "")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/pcb23/@status") @@ -52,29 +52,29 @@ class PopnMusicEclaleClient(BaseClient): call = self.call_node() # Construct node - info23 = Node.void('info23') + info23 = Node.void("info23") call.add_child(info23) - info23.set_attribute('loc_id', loc) - info23.set_attribute('method', 'common') + info23.set_attribute("loc_id", loc) + info23.set_attribute("method", "common") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct - self.__verify_common('info23', resp) + self.__verify_common("info23", resp) def verify_lobby22_getlist(self, loc: str) -> None: call = self.call_node() # Construct node - lobby22 = Node.void('lobby22') + lobby22 = Node.void("lobby22") call.add_child(lobby22) - lobby22.set_attribute('method', 'getList') - lobby22.add_child(Node.string('location_id', loc)) - lobby22.add_child(Node.u8('net_version', 53)) + lobby22.set_attribute("method", "getList") + lobby22.add_child(Node.string("location_id", loc)) + lobby22.add_child(Node.u8("net_version", 53)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/lobby22/@status") @@ -118,87 +118,95 @@ class PopnMusicEclaleClient(BaseClient): self.assert_path(resp, "response/player23/stamp/stamp_id") self.assert_path(resp, "response/player23/stamp/cnt") - def verify_player23_read(self, ref_id: str, msg_type: str) -> Dict[str, Dict[int, Dict[str, int]]]: + def verify_player23_read( + self, ref_id: str, msg_type: str + ) -> Dict[str, Dict[int, Dict[str, int]]]: call = self.call_node() # Construct node - player23 = Node.void('player23') + player23 = Node.void("player23") call.add_child(player23) - player23.set_attribute('method', 'read') + player23.set_attribute("method", "read") - player23.add_child(Node.string('ref_id', ref_id)) - player23.add_child(Node.s8('pref', 51)) + player23.add_child(Node.string("ref_id", ref_id)) + player23.add_child(Node.s8("pref", 51)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) - if msg_type == 'new': + if msg_type == "new": # Verify that response is correct self.assert_path(resp, "response/player23/result") - status = resp.child_value('player23/result') + status = resp.child_value("player23/result") if status != 2: - raise Exception(f'Reference ID \'{ref_id}\' returned invalid status \'{status}\'') + raise Exception( + f"Reference ID '{ref_id}' returned invalid status '{status}'" + ) return { - 'medals': {}, - 'items': {}, - 'characters': {}, - 'lumina': {}, + "medals": {}, + "items": {}, + "characters": {}, + "lumina": {}, } - elif msg_type == 'query': + elif msg_type == "query": # Verify that the response is correct self.__verify_profile(resp) self.assert_path(resp, "response/player23/result") - status = resp.child_value('player23/result') + status = resp.child_value("player23/result") if status != 0: - raise Exception(f'Reference ID \'{ref_id}\' returned invalid status \'{status}\'') - name = resp.child_value('player23/account/name') + raise Exception( + f"Reference ID '{ref_id}' returned invalid status '{status}'" + ) + name = resp.child_value("player23/account/name") if name != self.NAME: - raise Exception(f'Invalid name \'{name}\' returned for Ref ID \'{ref_id}\'') + raise Exception(f"Invalid name '{name}' returned for Ref ID '{ref_id}'") # Medals and items items: Dict[int, Dict[str, int]] = {} medals: Dict[int, Dict[str, int]] = {} charas: Dict[int, Dict[str, int]] = {} - for obj in resp.child('player23').children: - if obj.name == 'medal': - medals[obj.child_value('medal_id')] = { - 'level': obj.child_value('level'), - 'exp': obj.child_value('exp'), + for obj in resp.child("player23").children: + if obj.name == "medal": + medals[obj.child_value("medal_id")] = { + "level": obj.child_value("level"), + "exp": obj.child_value("exp"), } - elif obj.name == 'item': - items[obj.child_value('id')] = { - 'type': obj.child_value('type'), - 'param': obj.child_value('param'), + elif obj.name == "item": + items[obj.child_value("id")] = { + "type": obj.child_value("type"), + "param": obj.child_value("param"), } - elif obj.name == 'chara_param': - charas[obj.child_value('chara_id')] = { - 'friendship': obj.child_value('friendship'), + elif obj.name == "chara_param": + charas[obj.child_value("chara_id")] = { + "friendship": obj.child_value("friendship"), } return { - 'medals': medals, - 'items': items, - 'characters': charas, - 'lumina': {0: {'lumina': resp.child_value('player23/account/lumina')}}, + "medals": medals, + "items": items, + "characters": charas, + "lumina": {0: {"lumina": resp.child_value("player23/account/lumina")}}, } else: - raise Exception(f'Unrecognized message type \'{msg_type}\'') + raise Exception(f"Unrecognized message type '{msg_type}'") - def verify_player23_read_score(self, ref_id: str) -> Dict[str, Dict[int, Dict[int, int]]]: + def verify_player23_read_score( + self, ref_id: str + ) -> Dict[str, Dict[int, Dict[int, int]]]: call = self.call_node() # Construct node - player23 = Node.void('player23') + player23 = Node.void("player23") call.add_child(player23) - player23.set_attribute('method', 'read_score') + player23.set_attribute("method", "read_score") - player23.add_child(Node.string('ref_id', ref_id)) - player23.add_child(Node.s8('pref', 51)) + player23.add_child(Node.string("ref_id", ref_id)) + player23.add_child(Node.s8("pref", 51)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify defaults self.assert_path(resp, "response/player23/@status") @@ -206,14 +214,14 @@ class PopnMusicEclaleClient(BaseClient): # Grab scores scores: Dict[int, Dict[int, int]] = {} medals: Dict[int, Dict[int, int]] = {} - for child in resp.child('player23').children: - if child.name != 'music': + for child in resp.child("player23").children: + if child.name != "music": continue - musicid = child.child_value('music_num') - chart = child.child_value('sheet_num') - score = child.child_value('score') - medal = child.child_value('clear_type') + musicid = child.child_value("music_num") + chart = child.child_value("sheet_num") + score = child.child_value("score") + medal = child.child_value("clear_type") if musicid not in scores: scores[musicid] = {} @@ -224,42 +232,42 @@ class PopnMusicEclaleClient(BaseClient): medals[musicid][chart] = medal return { - 'scores': scores, - 'medals': medals, + "scores": scores, + "medals": medals, } def verify_player23_start(self, ref_id: str, loc: str) -> None: call = self.call_node() # Construct node - player23 = Node.void('player23') + player23 = Node.void("player23") call.add_child(player23) - player23.set_attribute('loc_id', loc) - player23.set_attribute('ref_id', ref_id) - player23.set_attribute('method', 'start') - player23.set_attribute('start_type', '0') - pcb_card = Node.void('pcb_card') + player23.set_attribute("loc_id", loc) + player23.set_attribute("ref_id", ref_id) + player23.set_attribute("method", "start") + player23.set_attribute("start_type", "0") + pcb_card = Node.void("pcb_card") player23.add_child(pcb_card) - pcb_card.add_child(Node.s8('card_enable', 0)) - pcb_card.add_child(Node.s8('card_soldout', 0)) + pcb_card.add_child(Node.s8("card_enable", 0)) + pcb_card.add_child(Node.s8("card_soldout", 0)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct - self.__verify_common('player23', resp) + self.__verify_common("player23", resp) def verify_player23_logout(self, ref_id: str) -> None: call = self.call_node() # Construct node - player23 = Node.void('player23') + player23 = Node.void("player23") call.add_child(player23) - player23.set_attribute('ref_id', ref_id) - player23.set_attribute('method', 'logout') + player23.set_attribute("ref_id", ref_id) + player23.set_attribute("method", "logout") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player23/@status") @@ -267,108 +275,108 @@ class PopnMusicEclaleClient(BaseClient): def verify_player23_write( self, ref_id: str, - medal: Optional[Dict[str, int]]=None, - item: Optional[Dict[str, int]]=None, - character: Optional[Dict[str, int]]=None, + medal: Optional[Dict[str, int]] = None, + item: Optional[Dict[str, int]] = None, + character: Optional[Dict[str, int]] = None, ) -> None: call = self.call_node() # Construct node - player23 = Node.void('player23') + player23 = Node.void("player23") call.add_child(player23) - player23.set_attribute('method', 'write') - player23.add_child(Node.string('ref_id', ref_id)) + player23.set_attribute("method", "write") + player23.add_child(Node.string("ref_id", ref_id)) # Add required children - config = Node.void('config') + config = Node.void("config") player23.add_child(config) - config.add_child(Node.s16('chara', 1543)) + config.add_child(Node.s16("chara", 1543)) if medal is not None: - medalnode = Node.void('medal') + medalnode = Node.void("medal") player23.add_child(medalnode) - medalnode.add_child(Node.s16('medal_id', medal['id'])) - medalnode.add_child(Node.s16('level', medal['level'])) - medalnode.add_child(Node.s32('exp', medal['exp'])) - medalnode.add_child(Node.s32('set_count', 0)) - medalnode.add_child(Node.s32('get_count', 0)) + medalnode.add_child(Node.s16("medal_id", medal["id"])) + medalnode.add_child(Node.s16("level", medal["level"])) + medalnode.add_child(Node.s32("exp", medal["exp"])) + medalnode.add_child(Node.s32("set_count", 0)) + medalnode.add_child(Node.s32("get_count", 0)) if item is not None: - itemnode = Node.void('item') + itemnode = Node.void("item") player23.add_child(itemnode) - itemnode.add_child(Node.u8('type', item['type'])) - itemnode.add_child(Node.u16('id', item['id'])) - itemnode.add_child(Node.u16('param', item['param'])) - itemnode.add_child(Node.bool('is_new', False)) + itemnode.add_child(Node.u8("type", item["type"])) + itemnode.add_child(Node.u16("id", item["id"])) + itemnode.add_child(Node.u16("param", item["param"])) + itemnode.add_child(Node.bool("is_new", False)) if character is not None: - chara_param = Node.void('chara_param') + chara_param = Node.void("chara_param") player23.add_child(chara_param) - chara_param.add_child(Node.u16('chara_id', character['id'])) - chara_param.add_child(Node.u16('friendship', character['friendship'])) + chara_param.add_child(Node.u16("chara_id", character["id"])) + chara_param.add_child(Node.u16("friendship", character["friendship"])) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/player23/@status") def verify_player23_buy(self, ref_id: str, item: Dict[str, int]) -> None: call = self.call_node() # Construct node - player23 = Node.void('player23') + player23 = Node.void("player23") call.add_child(player23) - player23.set_attribute('method', 'buy') - player23.add_child(Node.s32('play_id', 0)) - player23.add_child(Node.string('ref_id', ref_id)) - player23.add_child(Node.u16('id', item['id'])) - player23.add_child(Node.u8('type', item['type'])) - player23.add_child(Node.u16('param', item['param'])) - player23.add_child(Node.s32('lumina', item['lumina'])) - player23.add_child(Node.u16('price', item['price'])) + player23.set_attribute("method", "buy") + player23.add_child(Node.s32("play_id", 0)) + player23.add_child(Node.string("ref_id", ref_id)) + player23.add_child(Node.u16("id", item["id"])) + player23.add_child(Node.u8("type", item["type"])) + player23.add_child(Node.u16("param", item["param"])) + player23.add_child(Node.s32("lumina", item["lumina"])) + player23.add_child(Node.u16("price", item["price"])) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/player23/@status") def verify_player23_write_music(self, ref_id: str, score: Dict[str, Any]) -> None: call = self.call_node() # Construct node - player23 = Node.void('player23') + player23 = Node.void("player23") call.add_child(player23) - player23.set_attribute('method', 'write_music') - player23.add_child(Node.string('ref_id', ref_id)) - player23.add_child(Node.string('data_id', ref_id)) - player23.add_child(Node.string('name', self.NAME)) - player23.add_child(Node.u8('stage', 0)) - player23.add_child(Node.s16('music_num', score['id'])) - player23.add_child(Node.u8('sheet_num', score['chart'])) - player23.add_child(Node.u8('clearmedal', score['medal'])) - player23.add_child(Node.s32('score', score['score'])) - player23.add_child(Node.s16('combo', 0)) - player23.add_child(Node.s16('cool', 0)) - player23.add_child(Node.s16('great', 0)) - player23.add_child(Node.s16('good', 0)) - player23.add_child(Node.s16('bad', 0)) + player23.set_attribute("method", "write_music") + player23.add_child(Node.string("ref_id", ref_id)) + player23.add_child(Node.string("data_id", ref_id)) + player23.add_child(Node.string("name", self.NAME)) + player23.add_child(Node.u8("stage", 0)) + player23.add_child(Node.s16("music_num", score["id"])) + player23.add_child(Node.u8("sheet_num", score["chart"])) + player23.add_child(Node.u8("clearmedal", score["medal"])) + player23.add_child(Node.s32("score", score["score"])) + player23.add_child(Node.s16("combo", 0)) + player23.add_child(Node.s16("cool", 0)) + player23.add_child(Node.s16("great", 0)) + player23.add_child(Node.s16("good", 0)) + player23.add_child(Node.s16("bad", 0)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/player23/@status") def verify_player23_new(self, ref_id: str) -> None: call = self.call_node() # Construct node - player23 = Node.void('player23') + player23 = Node.void("player23") call.add_child(player23) - player23.set_attribute('method', 'new') + player23.set_attribute("method", "new") - player23.add_child(Node.string('ref_id', ref_id)) - player23.add_child(Node.string('name', self.NAME)) - player23.add_child(Node.s8('pref', 51)) + player23.add_child(Node.string("ref_id", ref_id)) + player23.add_child(Node.string("name", self.NAME)) + player23.add_child(Node.s8("pref", 51)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes self.__verify_profile(resp) @@ -377,20 +385,20 @@ class PopnMusicEclaleClient(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -410,116 +418,130 @@ class PopnMusicEclaleClient(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') - self.verify_player23_read(ref_id, msg_type='new') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") + self.verify_player23_read(ref_id, msg_type="new") self.verify_player23_new(ref_id) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Verify proper handling of basic stuff - self.verify_player23_read(ref_id, msg_type='query') + self.verify_player23_read(ref_id, msg_type="query") self.verify_player23_start(ref_id, location) self.verify_player23_write(ref_id) self.verify_player23_logout(ref_id) # Verify unlocks/story mode work - unlocks = self.verify_player23_read(ref_id, msg_type='query') - for _ in unlocks['items']: - raise Exception('Got nonzero items count on a new card!') - for _ in unlocks['medals']: - raise Exception('Got nonzero medals count on a new card!') - for _ in unlocks['characters']: - raise Exception('Got nonzero characters count on a new card!') - if unlocks['lumina'][0]['lumina'] != 300: - raise Exception('Got wrong default value for lumina on a new card!') + unlocks = self.verify_player23_read(ref_id, msg_type="query") + for _ in unlocks["items"]: + raise Exception("Got nonzero items count on a new card!") + for _ in unlocks["medals"]: + raise Exception("Got nonzero medals count on a new card!") + for _ in unlocks["characters"]: + raise Exception("Got nonzero characters count on a new card!") + if unlocks["lumina"][0]["lumina"] != 300: + raise Exception("Got wrong default value for lumina on a new card!") - self.verify_player23_write(ref_id, medal={'id': 1, 'level': 3, 'exp': 42}) - unlocks = self.verify_player23_read(ref_id, msg_type='query') - if 1 not in unlocks['medals']: - raise Exception('Expecting to see medal ID 1 in medals!') - if unlocks['medals'][1]['level'] != 3: - raise Exception('Expecting to see medal ID 1 to have level 3 in medals!') - if unlocks['medals'][1]['exp'] != 42: - raise Exception('Expecting to see medal ID 1 to have exp 42 in medals!') + self.verify_player23_write(ref_id, medal={"id": 1, "level": 3, "exp": 42}) + unlocks = self.verify_player23_read(ref_id, msg_type="query") + if 1 not in unlocks["medals"]: + raise Exception("Expecting to see medal ID 1 in medals!") + if unlocks["medals"][1]["level"] != 3: + raise Exception("Expecting to see medal ID 1 to have level 3 in medals!") + if unlocks["medals"][1]["exp"] != 42: + raise Exception("Expecting to see medal ID 1 to have exp 42 in medals!") - self.verify_player23_write(ref_id, item={'id': 4, 'type': 2, 'param': 69}) - unlocks = self.verify_player23_read(ref_id, msg_type='query') - if 4 not in unlocks['items']: - raise Exception('Expecting to see item ID 4 in items!') - if unlocks['items'][4]['type'] != 2: - raise Exception('Expecting to see item ID 4 to have type 2 in items!') - if unlocks['items'][4]['param'] != 69: - raise Exception('Expecting to see item ID 4 to have param 69 in items!') + self.verify_player23_write(ref_id, item={"id": 4, "type": 2, "param": 69}) + unlocks = self.verify_player23_read(ref_id, msg_type="query") + if 4 not in unlocks["items"]: + raise Exception("Expecting to see item ID 4 in items!") + if unlocks["items"][4]["type"] != 2: + raise Exception("Expecting to see item ID 4 to have type 2 in items!") + if unlocks["items"][4]["param"] != 69: + raise Exception("Expecting to see item ID 4 to have param 69 in items!") - self.verify_player23_write(ref_id, character={'id': 5, 'friendship': 420}) - unlocks = self.verify_player23_read(ref_id, msg_type='query') - if 5 not in unlocks['characters']: - raise Exception('Expecting to see chara ID 5 in characters!') - if unlocks['characters'][5]['friendship'] != 420: - raise Exception('Expecting to see chara ID 5 to have type 2 in characters!') + self.verify_player23_write(ref_id, character={"id": 5, "friendship": 420}) + unlocks = self.verify_player23_read(ref_id, msg_type="query") + if 5 not in unlocks["characters"]: + raise Exception("Expecting to see chara ID 5 in characters!") + if unlocks["characters"][5]["friendship"] != 420: + raise Exception("Expecting to see chara ID 5 to have type 2 in characters!") # Verify purchases work - self.verify_player23_buy(ref_id, item={'id': 6, 'type': 7, 'param': 8, 'lumina': 400, 'price': 250}) - unlocks = self.verify_player23_read(ref_id, msg_type='query') - if 6 not in unlocks['items']: - raise Exception('Expecting to see item ID 6 in items!') - if unlocks['items'][6]['type'] != 7: - raise Exception('Expecting to see item ID 6 to have type 7 in items!') - if unlocks['items'][6]['param'] != 8: - raise Exception('Expecting to see item ID 6 to have param 8 in items!') - if unlocks['lumina'][0]['lumina'] != 150: - raise Exception(f'Got wrong value for lumina {unlocks["lumina"][0]["lumina"]} after purchase!') + self.verify_player23_buy( + ref_id, item={"id": 6, "type": 7, "param": 8, "lumina": 400, "price": 250} + ) + unlocks = self.verify_player23_read(ref_id, msg_type="query") + if 6 not in unlocks["items"]: + raise Exception("Expecting to see item ID 6 in items!") + if unlocks["items"][6]["type"] != 7: + raise Exception("Expecting to see item ID 6 to have type 7 in items!") + if unlocks["items"][6]["param"] != 8: + raise Exception("Expecting to see item ID 6 to have param 8 in items!") + if unlocks["lumina"][0]["lumina"] != 150: + raise Exception( + f'Got wrong value for lumina {unlocks["lumina"][0]["lumina"]} after purchase!' + ) if cardid is None: # Verify score handling scores = self.verify_player23_read_score(ref_id) - for _ in scores['medals']: - raise Exception('Got nonzero medals count on a new card!') - for _ in scores['scores']: - raise Exception('Got nonzero scores count on a new card!') + for _ in scores["medals"]: + raise Exception("Got nonzero medals count on a new card!") + for _ in scores["scores"]: + raise Exception("Got nonzero scores count on a new card!") for phase in [1, 2]: if phase == 1: dummyscores = [ # An okay score on a chart { - 'id': 987, - 'chart': 2, - 'medal': 5, - 'score': 76543, + "id": 987, + "chart": 2, + "medal": 5, + "score": 76543, }, # A good score on an easier chart of the same song { - 'id': 987, - 'chart': 0, - 'medal': 6, - 'score': 99999, + "id": 987, + "chart": 0, + "medal": 6, + "score": 99999, }, # A bad score on a hard chart { - 'id': 741, - 'chart': 3, - 'medal': 2, - 'score': 45000, + "id": 741, + "chart": 3, + "medal": 2, + "score": 45000, }, # A terrible score on an easy chart { - 'id': 742, - 'chart': 1, - 'medal': 2, - 'score': 1, + "id": 742, + "chart": 1, + "medal": 2, + "score": 1, }, ] # Random score to add in @@ -527,29 +549,31 @@ class PopnMusicEclaleClient(BaseClient): chartid = random.randint(0, 3) score = random.randint(0, 100000) medal = random.randint(1, 11) - dummyscores.append({ - 'id': songid, - 'chart': chartid, - 'medal': medal, - 'score': score, - }) + dummyscores.append( + { + "id": songid, + "chart": chartid, + "medal": medal, + "score": score, + } + ) if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 987, - 'chart': 2, - 'medal': 5, - 'score': 98765, + "id": 987, + "chart": 2, + "medal": 5, + "score": 98765, }, # A worse score on another same chart { - 'id': 987, - 'chart': 0, - 'medal': 3, - 'score': 12345, - 'expected_score': 99999, - 'expected_medal': 6, + "id": 987, + "chart": 0, + "medal": 3, + "score": 12345, + "expected_score": 99999, + "expected_medal": 6, }, ] @@ -557,22 +581,26 @@ class PopnMusicEclaleClient(BaseClient): self.verify_player23_write_music(ref_id, dummyscore) scores = self.verify_player23_read_score(ref_id) for expected in dummyscores: - newscore = scores['scores'][expected['id']][expected['chart']] - newmedal = scores['medals'][expected['id']][expected['chart']] + newscore = scores["scores"][expected["id"]][expected["chart"]] + newmedal = scores["medals"][expected["id"]][expected["chart"]] - if 'expected_score' in expected: - expected_score = expected['expected_score'] + if "expected_score" in expected: + expected_score = expected["expected_score"] else: - expected_score = expected['score'] - if 'expected_medal' in expected: - expected_medal = expected['expected_medal'] + expected_score = expected["score"] + if "expected_medal" in expected: + expected_medal = expected["expected_medal"] else: - expected_medal = expected['medal'] + expected_medal = expected["medal"] if newscore != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{newscore}\'') + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{newscore}\'' + ) if newmedal != expected_medal: - raise Exception(f'Expected a medal of \'{expected_medal}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got medal \'{newmedal}\'') + raise Exception( + f'Expected a medal of \'{expected_medal}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got medal \'{newmedal}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) diff --git a/bemani/client/popn/fantasia.py b/bemani/client/popn/fantasia.py index 1daf3c5..197a531 100644 --- a/bemani/client/popn/fantasia.py +++ b/bemani/client/popn/fantasia.py @@ -7,51 +7,51 @@ from bemani.protocol import Node class PopnMusicFantasiaClient(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_game_active(self) -> None: call = self.call_node() # Construct node - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'active') + game.set_attribute("method", "active") # Add what Pop'n 20 would add after full unlock - game.set_attribute('method', 'active') - game.add_child(Node.s8('event', 0)) - game.add_child(Node.s8('card_use', 0)) - game.add_child(Node.string('name', '')) - game.add_child(Node.string('location_id', 'JP-1')) - game.add_child(Node.string('shop_name_facility', '.')) - game.add_child(Node.s8('pref', 50)) - game.add_child(Node.string('shop_addr', '127.0.0.1 10000')) - game.add_child(Node.string('shop_name', '')) - game.add_child(Node.string('eacoin_price', '200,260,200,200')) - game.add_child(Node.s8('eacoin_available', 1)) - game.add_child(Node.s8('dipswitch', 0)) - game.add_child(Node.u8('max_stage', 1)) - game.add_child(Node.u8('difficult', 4)) - game.add_child(Node.s8('free_play', 1)) - game.add_child(Node.s8('event_mode', 0)) - game.add_child(Node.s8('popn_card', 0)) - game.add_child(Node.s16('close_time', -1)) - game.add_child(Node.s32('game_phase', 2)) - game.add_child(Node.s32('net_phase', 0)) - game.add_child(Node.s32('event_phase', 0)) - game.add_child(Node.s32('card_phase', 3)) - game.add_child(Node.s32('ir_phase', 0)) - game.add_child(Node.u8('coin_to_credit', 1)) - game.add_child(Node.u8('credit_to_start', 2)) - game.add_child(Node.s32('sound_attract', 100)) - game.add_child(Node.s8('bookkeeping', 0)) - game.add_child(Node.s8('set_clock', 0)) - game.add_child(Node.s32('local_clock_sec', 80011)) - game.add_child(Node.s32('revision_sec', 0)) - game.add_child(Node.string('crash_log', '')) + game.set_attribute("method", "active") + game.add_child(Node.s8("event", 0)) + game.add_child(Node.s8("card_use", 0)) + game.add_child(Node.string("name", "")) + game.add_child(Node.string("location_id", "JP-1")) + game.add_child(Node.string("shop_name_facility", ".")) + game.add_child(Node.s8("pref", 50)) + game.add_child(Node.string("shop_addr", "127.0.0.1 10000")) + game.add_child(Node.string("shop_name", "")) + game.add_child(Node.string("eacoin_price", "200,260,200,200")) + game.add_child(Node.s8("eacoin_available", 1)) + game.add_child(Node.s8("dipswitch", 0)) + game.add_child(Node.u8("max_stage", 1)) + game.add_child(Node.u8("difficult", 4)) + game.add_child(Node.s8("free_play", 1)) + game.add_child(Node.s8("event_mode", 0)) + game.add_child(Node.s8("popn_card", 0)) + game.add_child(Node.s16("close_time", -1)) + game.add_child(Node.s32("game_phase", 2)) + game.add_child(Node.s32("net_phase", 0)) + game.add_child(Node.s32("event_phase", 0)) + game.add_child(Node.s32("card_phase", 3)) + game.add_child(Node.s32("ir_phase", 0)) + game.add_child(Node.u8("coin_to_credit", 1)) + game.add_child(Node.u8("credit_to_start", 2)) + game.add_child(Node.s32("sound_attract", 100)) + game.add_child(Node.s8("bookkeeping", 0)) + game.add_child(Node.s8("set_clock", 0)) + game.add_child(Node.s32("local_clock_sec", 80011)) + game.add_child(Node.s32("revision_sec", 0)) + game.add_child(Node.string("crash_log", "")) # Swap with server - resp = self.exchange('pnm20/game', call) + resp = self.exchange("pnm20/game", call) # Verify that response is correct self.assert_path(resp, "response/game/@status") @@ -60,90 +60,96 @@ class PopnMusicFantasiaClient(BaseClient): call = self.call_node() # Construct node - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('location_id', 'JP-1') - game.set_attribute('method', 'get') - game.add_child(Node.s8('card_enable', 0)) - game.add_child(Node.s8('card_soldout', 1)) + game.set_attribute("location_id", "JP-1") + game.set_attribute("method", "get") + game.add_child(Node.s8("card_enable", 0)) + game.add_child(Node.s8("card_soldout", 1)) # Swap with server - resp = self.exchange('pnm20/game', call) + resp = self.exchange("pnm20/game", call) # Verify that response is correct self.assert_path(resp, "response/game") for name in [ - 'game_phase', - 'ir_phase', - 'event_phase', - 'netvs_phase', - 'illust_phase', - 'psp_phase', - 'other_phase', - 'jubeat_phase', - 'public_phase', - 'kac_phase', - 'local_matching_enable', - 'n_matching_sec', - 'l_matching_sec', - 'is_check_cpu', - 'week_no', + "game_phase", + "ir_phase", + "event_phase", + "netvs_phase", + "illust_phase", + "psp_phase", + "other_phase", + "jubeat_phase", + "public_phase", + "kac_phase", + "local_matching_enable", + "n_matching_sec", + "l_matching_sec", + "is_check_cpu", + "week_no", ]: - node = resp.child('game').child(name) + node = resp.child("game").child(name) if node is None: - raise Exception(f'Missing node \'{name}\' in response!') - if node.data_type != 's32': - raise Exception(f'Node \'{name}\' has wrong data type!') + raise Exception(f"Missing node '{name}' in response!") + if node.data_type != "s32": + raise Exception(f"Node '{name}' has wrong data type!") - sel_ranking = resp.child('game').child('sel_ranking') - up_ranking = resp.child('game').child('up_ranking') - ng_illust = resp.child('game').child('ng_illust') + sel_ranking = resp.child("game").child("sel_ranking") + up_ranking = resp.child("game").child("up_ranking") + ng_illust = resp.child("game").child("ng_illust") for name, node, dtype, length in [ - ('sel_ranking', sel_ranking, 's16', 10), - ('up_ranking', up_ranking, 's16', 10), - ('ng_illust', ng_illust, 's32', 64), + ("sel_ranking", sel_ranking, "s16", 10), + ("up_ranking", up_ranking, "s16", 10), + ("ng_illust", ng_illust, "s32", 64), ]: if node is None: - raise Exception(f'Missing node \'{name}\' in response!') + raise Exception(f"Missing node '{name}' in response!") if node.data_type != dtype: - raise Exception(f'Node \'{name}\' has wrong data type!') + raise Exception(f"Node '{name}' has wrong data type!") if not node.is_array: - raise Exception(f'Node \'{name}\' is not array!') + raise Exception(f"Node '{name}' is not array!") if len(node.value) != length: - raise Exception(f'Node \'{name}\' is wrong array length!') + raise Exception(f"Node '{name}' is wrong array length!") - def verify_playerdata_get(self, ref_id: str, msg_type: str) -> Optional[Dict[str, Any]]: + def verify_playerdata_get( + self, ref_id: str, msg_type: str + ) -> Optional[Dict[str, Any]]: call = self.call_node() # Construct node - playerdata = Node.void('playerdata') + playerdata = Node.void("playerdata") call.add_child(playerdata) - playerdata.set_attribute('method', 'get') - if msg_type == 'new': - playerdata.set_attribute('model', self.config['old_profile_model'].split(':')[0]) + playerdata.set_attribute("method", "get") + if msg_type == "new": + playerdata.set_attribute( + "model", self.config["old_profile_model"].split(":")[0] + ) - playerdata.add_child(Node.string('ref_id', ref_id)) - playerdata.add_child(Node.string('shop_name', '')) - playerdata.add_child(Node.s8('pref', 50)) - playerdata.add_child(Node.s32('navigate', 1)) + playerdata.add_child(Node.string("ref_id", ref_id)) + playerdata.add_child(Node.string("shop_name", "")) + playerdata.add_child(Node.s8("pref", 50)) + playerdata.add_child(Node.s32("navigate", 1)) # Swap with server - resp = self.exchange('pnm20/playerdata', call) + resp = self.exchange("pnm20/playerdata", call) - if msg_type == 'new': + if msg_type == "new": # Verify that response is correct self.assert_path(resp, "response/playerdata/@status") - status = int(resp.child('playerdata').attribute('status')) + status = int(resp.child("playerdata").attribute("status")) if status != 109: - raise Exception(f'Reference ID \'{ref_id}\' returned invalid status \'{status}\'') + raise Exception( + f"Reference ID '{ref_id}' returned invalid status '{status}'" + ) # No score data return None - elif msg_type == 'query': + elif msg_type == "query": # Verify that the response is correct self.assert_path(resp, "response/playerdata/base/g_pm_id") self.assert_path(resp, "response/playerdata/base/name") @@ -159,9 +165,9 @@ class PopnMusicFantasiaClient(BaseClient): self.assert_path(resp, "response/playerdata/reflec_data") self.assert_path(resp, "response/playerdata/navigate") - name = resp.child('playerdata').child('base').child('name').value + name = resp.child("playerdata").child("base").child("name").value if name != self.NAME: - raise Exception(f'Invalid name \'{name}\' returned for Ref ID \'{ref_id}\'') + raise Exception(f"Invalid name '{name}' returned for Ref ID '{ref_id}'") # Extract and return score data self.assert_path(resp, "response/playerdata/base/clear_medal") @@ -174,9 +180,15 @@ class PopnMusicFantasiaClient(BaseClient): (medal >> 12) & 0xF, ) - medals = [transform_medals(medal) for medal in resp.child('playerdata').child('base').child('clear_medal').value] + medals = [ + transform_medals(medal) + for medal in resp.child("playerdata") + .child("base") + .child("clear_medal") + .value + ] - hiscore = resp.child('playerdata').child('hiscore').value + hiscore = resp.child("playerdata").child("hiscore").value hiscores = [] for i in range(0, len(hiscore) * 8, 17): byte_offset = int(i / 8) @@ -189,68 +201,78 @@ class PopnMusicFantasiaClient(BaseClient): value = value >> bit_offset hiscores.append(value & 0x1FFFF) - scores = [(hiscores[x], hiscores[x + 1], hiscores[x + 2], hiscores[x + 3]) for x in range(0, len(hiscores), 4)] + scores = [ + (hiscores[x], hiscores[x + 1], hiscores[x + 2], hiscores[x + 3]) + for x in range(0, len(hiscores), 4) + ] - return {'medals': medals, 'scores': scores} + return {"medals": medals, "scores": scores} else: - raise Exception(f'Unrecognized message type \'{msg_type}\'') + raise Exception(f"Unrecognized message type '{msg_type}'") def verify_playerdata_set(self, ref_id: str, scores: List[Dict[str, Any]]) -> None: call = self.call_node() # Construct node - playerdata = Node.void('playerdata') + playerdata = Node.void("playerdata") call.add_child(playerdata) - playerdata.set_attribute('method', 'set') - playerdata.set_attribute('ref_id', ref_id) - playerdata.set_attribute('shop_name', '') + playerdata.set_attribute("method", "set") + playerdata.set_attribute("ref_id", ref_id) + playerdata.set_attribute("shop_name", "") # Add required children - playerdata.add_child(Node.s16('chara', 1543)) + playerdata.add_child(Node.s16("chara", 1543)) # Add requested scores for score in scores: - stage = Node.void('stage') + stage = Node.void("stage") playerdata.add_child(stage) - stage.add_child(Node.s16('no', score['id'])) - stage.add_child(Node.u8('sheet', { - 0: 2, - 1: 0, - 2: 1, - 3: 3, - }[score['chart']])) - stage.add_child(Node.u16('n_data', (score['medal'] << (4 * score['chart'])))) - stage.add_child(Node.u8('e_data', 0)) - stage.add_child(Node.s32('score', score['score'])) - stage.add_child(Node.u8('ojama_1', 0)) - stage.add_child(Node.u8('ojama_2', 0)) + stage.add_child(Node.s16("no", score["id"])) + stage.add_child( + Node.u8( + "sheet", + { + 0: 2, + 1: 0, + 2: 1, + 3: 3, + }[score["chart"]], + ) + ) + stage.add_child( + Node.u16("n_data", (score["medal"] << (4 * score["chart"]))) + ) + stage.add_child(Node.u8("e_data", 0)) + stage.add_child(Node.s32("score", score["score"])) + stage.add_child(Node.u8("ojama_1", 0)) + stage.add_child(Node.u8("ojama_2", 0)) # Swap with server - resp = self.exchange('pnm20/playerdata', call) + resp = self.exchange("pnm20/playerdata", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/playerdata/name") - name = resp.child('playerdata').child('name').value + name = resp.child("playerdata").child("name").value if name != self.NAME: - raise Exception(f'Invalid name \'{name}\' returned for Ref ID \'{ref_id}\'') + raise Exception(f"Invalid name '{name}' returned for Ref ID '{ref_id}'") def verify_playerdata_new(self, ref_id: str) -> None: call = self.call_node() # Construct node - playerdata = Node.void('playerdata') + playerdata = Node.void("playerdata") call.add_child(playerdata) - playerdata.set_attribute('method', 'new') + playerdata.set_attribute("method", "new") - playerdata.add_child(Node.string('ref_id', ref_id)) - playerdata.add_child(Node.string('name', self.NAME)) - playerdata.add_child(Node.string('shop_name', '')) - playerdata.add_child(Node.s8('pref', 51)) + playerdata.add_child(Node.string("ref_id", ref_id)) + playerdata.add_child(Node.string("name", self.NAME)) + playerdata.add_child(Node.string("shop_name", "")) + playerdata.add_child(Node.s8("pref", 51)) # Swap with server - resp = self.exchange('pnm20/playerdata', call) + resp = self.exchange("pnm20/playerdata", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/playerdata/base/g_pm_id") @@ -271,20 +293,20 @@ class PopnMusicFantasiaClient(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -303,68 +325,78 @@ class PopnMusicFantasiaClient(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') - self.verify_playerdata_get(ref_id, msg_type='new') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") + self.verify_playerdata_get(ref_id, msg_type="new") self.verify_playerdata_new(ref_id) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") if cardid is None: # Verify score handling - scores = self.verify_playerdata_get(ref_id, msg_type='query') + scores = self.verify_playerdata_get(ref_id, msg_type="query") if scores is None: - raise Exception('Expected to get scores back, didn\'t get anything!') - for medal in scores['medals']: + raise Exception("Expected to get scores back, didn't get anything!") + for medal in scores["medals"]: for i in range(4): if medal[i] != 0: - raise Exception('Got nonzero medals count on a new card!') - for score in scores['scores']: + raise Exception("Got nonzero medals count on a new card!") + for score in scores["scores"]: for i in range(4): if score[i] != 0: - raise Exception('Got nonzero scores count on a new card!') + raise Exception("Got nonzero scores count on a new card!") for phase in [1, 2]: if phase == 1: dummyscores = [ # An okay score on a chart { - 'id': 987, - 'chart': 2, - 'medal': 5, - 'score': 76543, + "id": 987, + "chart": 2, + "medal": 5, + "score": 76543, }, # A good score on an easier chart of the same song { - 'id': 987, - 'chart': 0, - 'medal': 6, - 'score': 99999, + "id": 987, + "chart": 0, + "medal": 6, + "score": 99999, }, # A bad score on a hard chart { - 'id': 741, - 'chart': 3, - 'medal': 2, - 'score': 45000, + "id": 741, + "chart": 3, + "medal": 2, + "score": 45000, }, # A terrible score on an easy chart { - 'id': 742, - 'chart': 1, - 'medal': 2, - 'score': 1, + "id": 742, + "chart": 1, + "medal": 2, + "score": 1, }, ] # Random score to add in @@ -372,51 +404,57 @@ class PopnMusicFantasiaClient(BaseClient): chartid = random.randint(0, 3) score = random.randint(0, 100000) medal = random.choice([1, 2, 3, 5, 6, 7, 9, 10, 11, 15]) - dummyscores.append({ - 'id': songid, - 'chart': chartid, - 'medal': medal, - 'score': score, - }) + dummyscores.append( + { + "id": songid, + "chart": chartid, + "medal": medal, + "score": score, + } + ) if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 987, - 'chart': 2, - 'medal': 5, - 'score': 98765, + "id": 987, + "chart": 2, + "medal": 5, + "score": 98765, }, # A worse score on another same chart { - 'id': 987, - 'chart': 0, - 'medal': 3, - 'score': 12345, - 'expected_score': 99999, - 'expected_medal': 6, + "id": 987, + "chart": 0, + "medal": 3, + "score": 12345, + "expected_score": 99999, + "expected_medal": 6, }, ] self.verify_playerdata_set(ref_id, dummyscores) - scores = self.verify_playerdata_get(ref_id, msg_type='query') + scores = self.verify_playerdata_get(ref_id, msg_type="query") for score in dummyscores: - newscore = scores['scores'][score['id']][score['chart']] - newmedal = scores['medals'][score['id']][score['chart']] + newscore = scores["scores"][score["id"]][score["chart"]] + newmedal = scores["medals"][score["id"]][score["chart"]] - if 'expected_score' in score: - expected_score = score['expected_score'] + if "expected_score" in score: + expected_score = score["expected_score"] else: - expected_score = score['score'] - if 'expected_medal' in score: - expected_medal = score['expected_medal'] + expected_score = score["score"] + if "expected_medal" in score: + expected_medal = score["expected_medal"] else: - expected_medal = score['medal'] + expected_medal = score["medal"] if newscore != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{newscore}\'') + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{newscore}\'' + ) if newmedal != expected_medal: - raise Exception(f'Expected a medal of \'{expected_medal}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got medal \'{newmedal}\'') + raise Exception( + f'Expected a medal of \'{expected_medal}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got medal \'{newmedal}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) diff --git a/bemani/client/popn/kaimei.py b/bemani/client/popn/kaimei.py index 9f1470f..7c7ae5b 100644 --- a/bemani/client/popn/kaimei.py +++ b/bemani/client/popn/kaimei.py @@ -7,33 +7,33 @@ from bemani.protocol import Node class PopnMusicKaimeiClient(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_pcb24_boot(self, loc: str) -> None: call = self.call_node() # Construct node - pcb24 = Node.void('pcb24') + pcb24 = Node.void("pcb24") call.add_child(pcb24) - pcb24.set_attribute('method', 'boot') - pcb24.add_child(Node.string('loc_id', loc)) - pcb24.add_child(Node.u8('loc_type', 0)) - pcb24.add_child(Node.string('loc_name', '')) - pcb24.add_child(Node.string('country', 'US')) - pcb24.add_child(Node.string('region', '.')) - pcb24.add_child(Node.s16('pref', 51)) - pcb24.add_child(Node.string('customer', '')) - pcb24.add_child(Node.string('company', '')) - pcb24.add_child(Node.ipv4('gip', '127.0.0.1')) - pcb24.add_child(Node.u16('gp', 10011)) - pcb24.add_child(Node.string('rom_number', 'M39-JB-G01')) - pcb24.add_child(Node.u64('c_drive', 10028228608)) - pcb24.add_child(Node.u64('d_drive', 47945170944)) - pcb24.add_child(Node.u64('e_drive', 10394677248)) - pcb24.add_child(Node.string('etc', '')) + pcb24.set_attribute("method", "boot") + pcb24.add_child(Node.string("loc_id", loc)) + pcb24.add_child(Node.u8("loc_type", 0)) + pcb24.add_child(Node.string("loc_name", "")) + pcb24.add_child(Node.string("country", "US")) + pcb24.add_child(Node.string("region", ".")) + pcb24.add_child(Node.s16("pref", 51)) + pcb24.add_child(Node.string("customer", "")) + pcb24.add_child(Node.string("company", "")) + pcb24.add_child(Node.ipv4("gip", "127.0.0.1")) + pcb24.add_child(Node.u16("gp", 10011)) + pcb24.add_child(Node.string("rom_number", "M39-JB-G01")) + pcb24.add_child(Node.u64("c_drive", 10028228608)) + pcb24.add_child(Node.u64("d_drive", 47945170944)) + pcb24.add_child(Node.u64("e_drive", 10394677248)) + pcb24.add_child(Node.string("etc", "")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/pcb24/@status") @@ -59,29 +59,29 @@ class PopnMusicKaimeiClient(BaseClient): call = self.call_node() # Construct node - info24 = Node.void('info24') + info24 = Node.void("info24") call.add_child(info24) - info24.set_attribute('loc_id', loc) - info24.set_attribute('method', 'common') + info24.set_attribute("loc_id", loc) + info24.set_attribute("method", "common") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct - self.__verify_common('info24', resp) + self.__verify_common("info24", resp) def verify_lobby24_getlist(self, loc: str) -> None: call = self.call_node() # Construct node - lobby24 = Node.void('lobby24') + lobby24 = Node.void("lobby24") call.add_child(lobby24) - lobby24.set_attribute('method', 'getList') - lobby24.add_child(Node.string('location_id', loc)) - lobby24.add_child(Node.u8('net_version', 63)) + lobby24.set_attribute("method", "getList") + lobby24.add_child(Node.string("location_id", loc)) + lobby24.add_child(Node.u8("net_version", 63)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/lobby24/@status") @@ -136,89 +136,99 @@ class PopnMusicKaimeiClient(BaseClient): self.assert_path(resp, "response/player24/stamp/stamp_id") self.assert_path(resp, "response/player24/stamp/cnt") - def verify_player24_read(self, ref_id: str, msg_type: str) -> Dict[str, Dict[int, Dict[str, int]]]: + def verify_player24_read( + self, ref_id: str, msg_type: str + ) -> Dict[str, Dict[int, Dict[str, int]]]: call = self.call_node() # Construct node - player24 = Node.void('player24') + player24 = Node.void("player24") call.add_child(player24) - player24.set_attribute('method', 'read') + player24.set_attribute("method", "read") - player24.add_child(Node.string('ref_id', ref_id)) - player24.add_child(Node.s8('pref', 51)) + player24.add_child(Node.string("ref_id", ref_id)) + player24.add_child(Node.s8("pref", 51)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) - if msg_type == 'new': + if msg_type == "new": # Verify that response is correct self.assert_path(resp, "response/player24/result") - status = resp.child_value('player24/result') + status = resp.child_value("player24/result") if status != 2: - raise Exception(f'Reference ID \'{ref_id}\' returned invalid status \'{status}\'') + raise Exception( + f"Reference ID '{ref_id}' returned invalid status '{status}'" + ) return { - 'items': {}, - 'characters': {}, - 'points': {}, + "items": {}, + "characters": {}, + "points": {}, } - elif msg_type == 'query': + elif msg_type == "query": # Verify that the response is correct self.__verify_profile(resp) self.assert_path(resp, "response/player24/result") - status = resp.child_value('player24/result') + status = resp.child_value("player24/result") if status != 0: - raise Exception(f'Reference ID \'{ref_id}\' returned invalid status \'{status}\'') - name = resp.child_value('player24/account/name') + raise Exception( + f"Reference ID '{ref_id}' returned invalid status '{status}'" + ) + name = resp.child_value("player24/account/name") if name != self.NAME: - raise Exception(f'Invalid name \'{name}\' returned for Ref ID \'{ref_id}\'') + raise Exception(f"Invalid name '{name}' returned for Ref ID '{ref_id}'") # Medals and items items: Dict[int, Dict[str, int]] = {} charas: Dict[int, Dict[str, int]] = {} courses: Dict[int, Dict[str, int]] = {} - for obj in resp.child('player24').children: - if obj.name == 'item': - items[obj.child_value('id')] = { - 'type': obj.child_value('type'), - 'param': obj.child_value('param'), + for obj in resp.child("player24").children: + if obj.name == "item": + items[obj.child_value("id")] = { + "type": obj.child_value("type"), + "param": obj.child_value("param"), } - elif obj.name == 'chara_param': - charas[obj.child_value('chara_id')] = { - 'friendship': obj.child_value('friendship'), + elif obj.name == "chara_param": + charas[obj.child_value("chara_id")] = { + "friendship": obj.child_value("friendship"), } - elif obj.name == 'course_data': - courses[obj.child_value('course_id')] = { - 'clear_type': obj.child_value('clear_type'), - 'clear_rank': obj.child_value('clear_rank'), - 'total_score': obj.child_value('total_score'), - 'count': obj.child_value('update_count'), - 'sheet_num': obj.child_value('sheet_num'), + elif obj.name == "course_data": + courses[obj.child_value("course_id")] = { + "clear_type": obj.child_value("clear_type"), + "clear_rank": obj.child_value("clear_rank"), + "total_score": obj.child_value("total_score"), + "count": obj.child_value("update_count"), + "sheet_num": obj.child_value("sheet_num"), } return { - 'items': items, - 'characters': charas, - 'courses': courses, - 'points': {0: {'points': resp.child_value('player24/account/player_point')}}, + "items": items, + "characters": charas, + "courses": courses, + "points": { + 0: {"points": resp.child_value("player24/account/player_point")} + }, } else: - raise Exception(f'Unrecognized message type \'{msg_type}\'') + raise Exception(f"Unrecognized message type '{msg_type}'") - def verify_player24_read_score(self, ref_id: str) -> Dict[str, Dict[int, Dict[int, int]]]: + def verify_player24_read_score( + self, ref_id: str + ) -> Dict[str, Dict[int, Dict[int, int]]]: call = self.call_node() # Construct node - player24 = Node.void('player24') + player24 = Node.void("player24") call.add_child(player24) - player24.set_attribute('method', 'read_score') + player24.set_attribute("method", "read_score") - player24.add_child(Node.string('ref_id', ref_id)) - player24.add_child(Node.s8('pref', 51)) + player24.add_child(Node.string("ref_id", ref_id)) + player24.add_child(Node.s8("pref", 51)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify defaults self.assert_path(resp, "response/player24/@status") @@ -227,15 +237,15 @@ class PopnMusicKaimeiClient(BaseClient): scores: Dict[int, Dict[int, int]] = {} medals: Dict[int, Dict[int, int]] = {} ranks: Dict[int, Dict[int, int]] = {} - for child in resp.child('player24').children: - if child.name != 'music': + for child in resp.child("player24").children: + if child.name != "music": continue - musicid = child.child_value('music_num') - chart = child.child_value('sheet_num') - score = child.child_value('score') - medal = child.child_value('clear_type') - rank = child.child_value('clear_rank') + musicid = child.child_value("music_num") + chart = child.child_value("sheet_num") + score = child.child_value("score") + medal = child.child_value("clear_type") + rank = child.child_value("clear_rank") if musicid not in scores: scores[musicid] = {} @@ -249,53 +259,53 @@ class PopnMusicKaimeiClient(BaseClient): ranks[musicid][chart] = rank return { - 'scores': scores, - 'medals': medals, - 'ranks': ranks, + "scores": scores, + "medals": medals, + "ranks": ranks, } def verify_player24_start(self, ref_id: str, loc: str) -> None: call = self.call_node() # Construct node - player24 = Node.void('player24') + player24 = Node.void("player24") call.add_child(player24) - player24.set_attribute('loc_id', loc) - player24.set_attribute('ref_id', ref_id) - player24.set_attribute('method', 'start') - player24.set_attribute('start_type', '0') - pcb_card = Node.void('pcb_card') + player24.set_attribute("loc_id", loc) + player24.set_attribute("ref_id", ref_id) + player24.set_attribute("method", "start") + player24.set_attribute("start_type", "0") + pcb_card = Node.void("pcb_card") player24.add_child(pcb_card) - pcb_card.add_child(Node.s8('card_enable', 1)) - pcb_card.add_child(Node.s8('card_soldout', 0)) + pcb_card.add_child(Node.s8("card_enable", 1)) + pcb_card.add_child(Node.s8("card_soldout", 0)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct - self.__verify_common('player24', resp) + self.__verify_common("player24", resp) def verify_player24_update_ranking(self, ref_id: str, loc: str) -> None: call = self.call_node() # Construct node - player24 = Node.void('player24') + player24 = Node.void("player24") call.add_child(player24) - player24.set_attribute('method', 'update_ranking') - player24.add_child(Node.s16('pref', 51)) - player24.add_child(Node.string('location_id', loc)) - player24.add_child(Node.string('ref_id', ref_id)) - player24.add_child(Node.string('name', self.NAME)) - player24.add_child(Node.s16('chara_num', 1)) - player24.add_child(Node.s16('course_id', 12345)) - player24.add_child(Node.s32('total_score', 86000)) - player24.add_child(Node.s16('music_num', 1375)) - player24.add_child(Node.u8('sheet_num', 2)) - player24.add_child(Node.u8('clear_type', 7)) - player24.add_child(Node.u8('clear_rank', 5)) + player24.set_attribute("method", "update_ranking") + player24.add_child(Node.s16("pref", 51)) + player24.add_child(Node.string("location_id", loc)) + player24.add_child(Node.string("ref_id", ref_id)) + player24.add_child(Node.string("name", self.NAME)) + player24.add_child(Node.s16("chara_num", 1)) + player24.add_child(Node.s16("course_id", 12345)) + player24.add_child(Node.s32("total_score", 86000)) + player24.add_child(Node.s16("music_num", 1375)) + player24.add_child(Node.u8("sheet_num", 2)) + player24.add_child(Node.u8("clear_type", 7)) + player24.add_child(Node.u8("clear_rank", 5)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player24/all_ranking/name") @@ -310,13 +320,13 @@ class PopnMusicKaimeiClient(BaseClient): call = self.call_node() # Construct node - player24 = Node.void('player24') + player24 = Node.void("player24") call.add_child(player24) - player24.set_attribute('ref_id', ref_id) - player24.set_attribute('method', 'logout') + player24.set_attribute("ref_id", ref_id) + player24.set_attribute("method", "logout") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player24/@status") @@ -324,99 +334,99 @@ class PopnMusicKaimeiClient(BaseClient): def verify_player24_write( self, ref_id: str, - item: Optional[Dict[str, int]]=None, - character: Optional[Dict[str, int]]=None, + item: Optional[Dict[str, int]] = None, + character: Optional[Dict[str, int]] = None, ) -> None: call = self.call_node() # Construct node - player24 = Node.void('player24') + player24 = Node.void("player24") call.add_child(player24) - player24.set_attribute('method', 'write') - player24.add_child(Node.string('ref_id', ref_id)) + player24.set_attribute("method", "write") + player24.add_child(Node.string("ref_id", ref_id)) # Add required children - config = Node.void('config') + config = Node.void("config") player24.add_child(config) - config.add_child(Node.s16('chara', 1543)) + config.add_child(Node.s16("chara", 1543)) if item is not None: - itemnode = Node.void('item') + itemnode = Node.void("item") player24.add_child(itemnode) - itemnode.add_child(Node.u8('type', item['type'])) - itemnode.add_child(Node.u16('id', item['id'])) - itemnode.add_child(Node.u16('param', item['param'])) - itemnode.add_child(Node.bool('is_new', False)) - itemnode.add_child(Node.u64('get_time', 0)) + itemnode.add_child(Node.u8("type", item["type"])) + itemnode.add_child(Node.u16("id", item["id"])) + itemnode.add_child(Node.u16("param", item["param"])) + itemnode.add_child(Node.bool("is_new", False)) + itemnode.add_child(Node.u64("get_time", 0)) if character is not None: - chara_param = Node.void('chara_param') + chara_param = Node.void("chara_param") player24.add_child(chara_param) - chara_param.add_child(Node.u16('chara_id', character['id'])) - chara_param.add_child(Node.u16('friendship', character['friendship'])) + chara_param.add_child(Node.u16("chara_id", character["id"])) + chara_param.add_child(Node.u16("friendship", character["friendship"])) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/player24/@status") def verify_player24_buy(self, ref_id: str, item: Dict[str, int]) -> None: call = self.call_node() # Construct node - player24 = Node.void('player24') + player24 = Node.void("player24") call.add_child(player24) - player24.set_attribute('method', 'buy') - player24.add_child(Node.s32('play_id', 0)) - player24.add_child(Node.string('ref_id', ref_id)) - player24.add_child(Node.u16('id', item['id'])) - player24.add_child(Node.u8('type', item['type'])) - player24.add_child(Node.u16('param', item['param'])) - player24.add_child(Node.s32('lumina', item['points'])) - player24.add_child(Node.u16('price', item['price'])) + player24.set_attribute("method", "buy") + player24.add_child(Node.s32("play_id", 0)) + player24.add_child(Node.string("ref_id", ref_id)) + player24.add_child(Node.u16("id", item["id"])) + player24.add_child(Node.u8("type", item["type"])) + player24.add_child(Node.u16("param", item["param"])) + player24.add_child(Node.s32("lumina", item["points"])) + player24.add_child(Node.u16("price", item["price"])) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/player24/@status") def verify_player24_write_music(self, ref_id: str, score: Dict[str, Any]) -> None: call = self.call_node() # Construct node - player24 = Node.void('player24') + player24 = Node.void("player24") call.add_child(player24) - player24.set_attribute('method', 'write_music') - player24.add_child(Node.string('ref_id', ref_id)) - player24.add_child(Node.string('data_id', ref_id)) - player24.add_child(Node.string('name', self.NAME)) - player24.add_child(Node.u8('stage', 0)) - player24.add_child(Node.s16('music_num', score['id'])) - player24.add_child(Node.u8('sheet_num', score['chart'])) - player24.add_child(Node.u8('clear_type', score['medal'])) - player24.add_child(Node.s32('score', score['score'])) - player24.add_child(Node.s16('combo', 0)) - player24.add_child(Node.s16('cool', 0)) - player24.add_child(Node.s16('great', 0)) - player24.add_child(Node.s16('good', 0)) - player24.add_child(Node.s16('bad', 0)) + player24.set_attribute("method", "write_music") + player24.add_child(Node.string("ref_id", ref_id)) + player24.add_child(Node.string("data_id", ref_id)) + player24.add_child(Node.string("name", self.NAME)) + player24.add_child(Node.u8("stage", 0)) + player24.add_child(Node.s16("music_num", score["id"])) + player24.add_child(Node.u8("sheet_num", score["chart"])) + player24.add_child(Node.u8("clear_type", score["medal"])) + player24.add_child(Node.s32("score", score["score"])) + player24.add_child(Node.s16("combo", 0)) + player24.add_child(Node.s16("cool", 0)) + player24.add_child(Node.s16("great", 0)) + player24.add_child(Node.s16("good", 0)) + player24.add_child(Node.s16("bad", 0)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/player24/@status") def verify_player24_new(self, ref_id: str) -> None: call = self.call_node() # Construct node - player24 = Node.void('player24') + player24 = Node.void("player24") call.add_child(player24) - player24.set_attribute('method', 'new') + player24.set_attribute("method", "new") - player24.add_child(Node.string('ref_id', ref_id)) - player24.add_child(Node.string('name', self.NAME)) - player24.add_child(Node.s8('pref', 51)) + player24.add_child(Node.string("ref_id", ref_id)) + player24.add_child(Node.string("name", self.NAME)) + player24.add_child(Node.s8("pref", 51)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes self.__verify_profile(resp) @@ -425,20 +435,20 @@ class PopnMusicKaimeiClient(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -458,126 +468,153 @@ class PopnMusicKaimeiClient(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') - self.verify_player24_read(ref_id, msg_type='new') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") + self.verify_player24_read(ref_id, msg_type="new") self.verify_player24_new(ref_id) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Verify proper handling of basic stuff - self.verify_player24_read(ref_id, msg_type='query') + self.verify_player24_read(ref_id, msg_type="query") self.verify_player24_start(ref_id, location) self.verify_player24_write(ref_id) self.verify_player24_logout(ref_id) if cardid is None: # Verify unlocks/story mode work - unlocks = self.verify_player24_read(ref_id, msg_type='query') - for item in unlocks['items']: + unlocks = self.verify_player24_read(ref_id, msg_type="query") + for item in unlocks["items"]: if item in [1592, 1608]: # Song unlocks after one play continue - raise Exception('Got nonzero items count on a new card!') - for _ in unlocks['characters']: - raise Exception('Got nonzero characters count on a new card!') - for _ in unlocks['courses']: - raise Exception('Got nonzero course count on a new card!') - if unlocks['points'][0]['points'] != 300: - raise Exception('Got wrong default value for points on a new card!') + raise Exception("Got nonzero items count on a new card!") + for _ in unlocks["characters"]: + raise Exception("Got nonzero characters count on a new card!") + for _ in unlocks["courses"]: + raise Exception("Got nonzero course count on a new card!") + if unlocks["points"][0]["points"] != 300: + raise Exception("Got wrong default value for points on a new card!") - self.verify_player24_write(ref_id, item={'id': 4, 'type': 2, 'param': 69}) - unlocks = self.verify_player24_read(ref_id, msg_type='query') - if 4 not in unlocks['items']: - raise Exception('Expecting to see item ID 4 in items!') - if unlocks['items'][4]['type'] != 2: - raise Exception('Expecting to see item ID 4 to have type 2 in items!') - if unlocks['items'][4]['param'] != 69: - raise Exception('Expecting to see item ID 4 to have param 69 in items!') + self.verify_player24_write(ref_id, item={"id": 4, "type": 2, "param": 69}) + unlocks = self.verify_player24_read(ref_id, msg_type="query") + if 4 not in unlocks["items"]: + raise Exception("Expecting to see item ID 4 in items!") + if unlocks["items"][4]["type"] != 2: + raise Exception("Expecting to see item ID 4 to have type 2 in items!") + if unlocks["items"][4]["param"] != 69: + raise Exception("Expecting to see item ID 4 to have param 69 in items!") - self.verify_player24_write(ref_id, character={'id': 5, 'friendship': 420}) - unlocks = self.verify_player24_read(ref_id, msg_type='query') - if 5 not in unlocks['characters']: - raise Exception('Expecting to see chara ID 5 in characters!') - if unlocks['characters'][5]['friendship'] != 420: - raise Exception('Expecting to see chara ID 5 to have type 2 in characters!') + self.verify_player24_write(ref_id, character={"id": 5, "friendship": 420}) + unlocks = self.verify_player24_read(ref_id, msg_type="query") + if 5 not in unlocks["characters"]: + raise Exception("Expecting to see chara ID 5 in characters!") + if unlocks["characters"][5]["friendship"] != 420: + raise Exception( + "Expecting to see chara ID 5 to have type 2 in characters!" + ) # Verify purchases work - self.verify_player24_buy(ref_id, item={'id': 6, 'type': 3, 'param': 8, 'points': 400, 'price': 250}) - unlocks = self.verify_player24_read(ref_id, msg_type='query') - if 6 not in unlocks['items']: - raise Exception('Expecting to see item ID 6 in items!') - if unlocks['items'][6]['type'] != 3: - raise Exception('Expecting to see item ID 6 to have type 3 in items!') - if unlocks['items'][6]['param'] != 8: - raise Exception('Expecting to see item ID 6 to have param 8 in items!') - if unlocks['points'][0]['points'] != 150: - raise Exception(f'Got wrong value for points {unlocks["points"][0]["points"]} after purchase!') + self.verify_player24_buy( + ref_id, + item={"id": 6, "type": 3, "param": 8, "points": 400, "price": 250}, + ) + unlocks = self.verify_player24_read(ref_id, msg_type="query") + if 6 not in unlocks["items"]: + raise Exception("Expecting to see item ID 6 in items!") + if unlocks["items"][6]["type"] != 3: + raise Exception("Expecting to see item ID 6 to have type 3 in items!") + if unlocks["items"][6]["param"] != 8: + raise Exception("Expecting to see item ID 6 to have param 8 in items!") + if unlocks["points"][0]["points"] != 150: + raise Exception( + f'Got wrong value for points {unlocks["points"][0]["points"]} after purchase!' + ) # Verify course handling self.verify_player24_update_ranking(ref_id, location) - unlocks = self.verify_player24_read(ref_id, msg_type='query') - if 12345 not in unlocks['courses']: - raise Exception('Expecting to see course ID 12345 in courses!') - if unlocks['courses'][12345]['clear_type'] != 7: - raise Exception('Expecting to see item ID 12345 to have clear_type 7 in courses!') - if unlocks['courses'][12345]['clear_rank'] != 5: - raise Exception('Expecting to see item ID 12345 to have clear_rank 5 in courses!') - if unlocks['courses'][12345]['total_score'] != 86000: - raise Exception('Expecting to see item ID 12345 to have total_score 86000 in courses!') - if unlocks['courses'][12345]['count'] != 1: - raise Exception('Expecting to see item ID 12345 to have count 1 in courses!') - if unlocks['courses'][12345]['sheet_num'] != 2: - raise Exception('Expecting to see item ID 12345 to have sheet_num 2 in courses!') + unlocks = self.verify_player24_read(ref_id, msg_type="query") + if 12345 not in unlocks["courses"]: + raise Exception("Expecting to see course ID 12345 in courses!") + if unlocks["courses"][12345]["clear_type"] != 7: + raise Exception( + "Expecting to see item ID 12345 to have clear_type 7 in courses!" + ) + if unlocks["courses"][12345]["clear_rank"] != 5: + raise Exception( + "Expecting to see item ID 12345 to have clear_rank 5 in courses!" + ) + if unlocks["courses"][12345]["total_score"] != 86000: + raise Exception( + "Expecting to see item ID 12345 to have total_score 86000 in courses!" + ) + if unlocks["courses"][12345]["count"] != 1: + raise Exception( + "Expecting to see item ID 12345 to have count 1 in courses!" + ) + if unlocks["courses"][12345]["sheet_num"] != 2: + raise Exception( + "Expecting to see item ID 12345 to have sheet_num 2 in courses!" + ) # Verify score handling scores = self.verify_player24_read_score(ref_id) - for _ in scores['medals']: - raise Exception('Got nonzero medals count on a new card!') - for _ in scores['scores']: - raise Exception('Got nonzero scores count on a new card!') + for _ in scores["medals"]: + raise Exception("Got nonzero medals count on a new card!") + for _ in scores["scores"]: + raise Exception("Got nonzero scores count on a new card!") for phase in [1, 2]: if phase == 1: dummyscores = [ # An okay score on a chart { - 'id': 987, - 'chart': 2, - 'medal': 5, - 'score': 76543, + "id": 987, + "chart": 2, + "medal": 5, + "score": 76543, }, # A good score on an easier chart of the same song { - 'id': 987, - 'chart': 0, - 'medal': 6, - 'score': 99999, + "id": 987, + "chart": 0, + "medal": 6, + "score": 99999, }, # A bad score on a hard chart { - 'id': 741, - 'chart': 3, - 'medal': 2, - 'score': 45000, + "id": 741, + "chart": 3, + "medal": 2, + "score": 45000, }, # A terrible score on an easy chart { - 'id': 742, - 'chart': 1, - 'medal': 2, - 'score': 1, + "id": 742, + "chart": 1, + "medal": 2, + "score": 1, }, ] # Random score to add in @@ -585,29 +622,31 @@ class PopnMusicKaimeiClient(BaseClient): chartid = random.randint(0, 3) score = random.randint(0, 100000) medal = random.randint(1, 11) - dummyscores.append({ - 'id': songid, - 'chart': chartid, - 'medal': medal, - 'score': score, - }) + dummyscores.append( + { + "id": songid, + "chart": chartid, + "medal": medal, + "score": score, + } + ) if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 987, - 'chart': 2, - 'medal': 6, - 'score': 98765, + "id": 987, + "chart": 2, + "medal": 6, + "score": 98765, }, # A worse score on another same chart { - 'id': 987, - 'chart': 0, - 'medal': 3, - 'score': 12345, - 'expected_score': 99999, - 'expected_medal': 6, + "id": 987, + "chart": 0, + "medal": 3, + "score": 12345, + "expected_score": 99999, + "expected_medal": 6, }, ] @@ -615,18 +654,18 @@ class PopnMusicKaimeiClient(BaseClient): self.verify_player24_write_music(ref_id, dummyscore) scores = self.verify_player24_read_score(ref_id) for expected in dummyscores: - newscore = scores['scores'][expected['id']][expected['chart']] - newmedal = scores['medals'][expected['id']][expected['chart']] - newrank = scores['ranks'][expected['id']][expected['chart']] + newscore = scores["scores"][expected["id"]][expected["chart"]] + newmedal = scores["medals"][expected["id"]][expected["chart"]] + newrank = scores["ranks"][expected["id"]][expected["chart"]] - if 'expected_score' in expected: - expected_score = expected['expected_score'] + if "expected_score" in expected: + expected_score = expected["expected_score"] else: - expected_score = expected['score'] - if 'expected_medal' in expected: - expected_medal = expected['expected_medal'] + expected_score = expected["score"] + if "expected_medal" in expected: + expected_medal = expected["expected_medal"] else: - expected_medal = expected['medal'] + expected_medal = expected["medal"] if newscore < 50000: expected_rank = 1 @@ -646,11 +685,17 @@ class PopnMusicKaimeiClient(BaseClient): expected_rank = 8 if newscore != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{newscore}\'') + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{newscore}\'' + ) if newmedal != expected_medal: - raise Exception(f'Expected a medal of \'{expected_medal}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got medal \'{newmedal}\'') + raise Exception( + f'Expected a medal of \'{expected_medal}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got medal \'{newmedal}\'' + ) if newrank != expected_rank: - raise Exception(f'Expected a rank of \'{expected_rank}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got rank \'{newrank}\'') + raise Exception( + f'Expected a rank of \'{expected_rank}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got rank \'{newrank}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) diff --git a/bemani/client/popn/lapistoria.py b/bemani/client/popn/lapistoria.py index 185e321..94343eb 100644 --- a/bemani/client/popn/lapistoria.py +++ b/bemani/client/popn/lapistoria.py @@ -7,18 +7,18 @@ from bemani.protocol import Node class PopnMusicLapistoriaClient(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_pcb22_boot(self) -> None: call = self.call_node() # Construct node - pcb22 = Node.void('pcb22') + pcb22 = Node.void("pcb22") call.add_child(pcb22) - pcb22.set_attribute('method', 'boot') + pcb22.set_attribute("method", "boot") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/pcb22/@status") @@ -27,54 +27,58 @@ class PopnMusicLapistoriaClient(BaseClient): call = self.call_node() # Construct node - info22 = Node.void('info22') + info22 = Node.void("info22") call.add_child(info22) - info22.set_attribute('loc_id', 'JP-1') - info22.set_attribute('method', 'common') + info22.set_attribute("loc_id", "JP-1") + info22.set_attribute("method", "common") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/info22") for name in [ - 'phase', - 'story', + "phase", + "story", ]: - node = resp.child('info22').child(name) + node = resp.child("info22").child(name) if node is None: - raise Exception(f'Missing node \'{name}\' in response!') - if node.data_type != 'void': - raise Exception(f'Node \'{name}\' has wrong data type!') + raise Exception(f"Missing node '{name}' in response!") + if node.data_type != "void": + raise Exception(f"Node '{name}' has wrong data type!") - def verify_player22_read(self, ref_id: str, msg_type: str) -> Optional[Dict[str, Any]]: + def verify_player22_read( + self, ref_id: str, msg_type: str + ) -> Optional[Dict[str, Any]]: call = self.call_node() # Construct node - player22 = Node.void('player22') + player22 = Node.void("player22") call.add_child(player22) - player22.set_attribute('method', 'read') + player22.set_attribute("method", "read") - player22.add_child(Node.string('ref_id', value=ref_id)) - player22.add_child(Node.string('shop_name', '')) - player22.add_child(Node.s8('pref', 51)) + player22.add_child(Node.string("ref_id", value=ref_id)) + player22.add_child(Node.string("shop_name", "")) + player22.add_child(Node.s8("pref", 51)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) - if msg_type == 'new': + if msg_type == "new": # Verify that response is correct self.assert_path(resp, "response/player22/@status") - status = int(resp.child('player22').attribute('status')) + status = int(resp.child("player22").attribute("status")) if status != 109: - raise Exception(f'Reference ID \'{ref_id}\' returned invalid status \'{status}\'') + raise Exception( + f"Reference ID '{ref_id}' returned invalid status '{status}'" + ) # No score data return None - elif msg_type == 'query': + elif msg_type == "query": # Verify that the response is correct self.assert_path(resp, "response/player22/account/name") self.assert_path(resp, "response/player22/account/g_pm_id") @@ -87,20 +91,20 @@ class PopnMusicLapistoriaClient(BaseClient): self.assert_path(resp, "response/player22/custom_cate") self.assert_path(resp, "response/player22/customize") - name = resp.child('player22').child('account').child('name').value + name = resp.child("player22").child("account").child("name").value if name != self.NAME: - raise Exception(f'Invalid name \'{name}\' returned for Ref ID \'{ref_id}\'') + raise Exception(f"Invalid name '{name}' returned for Ref ID '{ref_id}'") # Extract and return score data medals: Dict[int, List[int]] = {} scores: Dict[int, List[int]] = {} courses: Dict[int, Dict[str, int]] = {} - for child in resp.child('player22').children: - if child.name == 'music': - songid = child.child_value('music_num') - chart = child.child_value('sheet_num') - medal = child.child_value('clear_type') - points = child.child_value('score') + for child in resp.child("player22").children: + if child.name == "music": + songid = child.child_value("music_num") + chart = child.child_value("sheet_num") + medal = child.child_value("clear_type") + points = child.child_value("score") if songid not in medals: medals[songid] = [0, 0, 0, 0] @@ -110,139 +114,139 @@ class PopnMusicLapistoriaClient(BaseClient): scores[songid][chart] = points if child.name == "course": - courseid = child.child_value('course_id') - medal = child.child_value('clear_medal') - combo = child.child_value('max_cmbo') - stage1 = child.child_value('stage1_score') - stage2 = child.child_value('stage2_score') - stage3 = child.child_value('stage3_score') - stage4 = child.child_value('stage4_score') - total = child.child_value('total_score') + courseid = child.child_value("course_id") + medal = child.child_value("clear_medal") + combo = child.child_value("max_cmbo") + stage1 = child.child_value("stage1_score") + stage2 = child.child_value("stage2_score") + stage3 = child.child_value("stage3_score") + stage4 = child.child_value("stage4_score") + total = child.child_value("total_score") courses[courseid] = { - 'id': courseid, - 'medal': medal, - 'combo': combo, - 'stage1': stage1, - 'stage2': stage2, - 'stage3': stage3, - 'stage4': stage4, - 'total': total, + "id": courseid, + "medal": medal, + "combo": combo, + "stage1": stage1, + "stage2": stage2, + "stage3": stage3, + "stage4": stage4, + "total": total, } - return {'medals': medals, 'scores': scores, 'courses': courses} + return {"medals": medals, "scores": scores, "courses": courses} else: - raise Exception(f'Unrecognized message type \'{msg_type}\'') + raise Exception(f"Unrecognized message type '{msg_type}'") def verify_player22_write(self, ref_id: str, scores: List[Dict[str, Any]]) -> None: call = self.call_node() # Construct node - player22 = Node.void('player22') + player22 = Node.void("player22") call.add_child(player22) - player22.set_attribute('method', 'write') - player22.add_child(Node.string('ref_id', value=ref_id)) + player22.set_attribute("method", "write") + player22.add_child(Node.string("ref_id", value=ref_id)) # Add required children - config = Node.void('config') + config = Node.void("config") player22.add_child(config) - config.add_child(Node.s16('chara', value=1543)) + config.add_child(Node.s16("chara", value=1543)) # Add requested scores for score in scores: - stage = Node.void('stage') + stage = Node.void("stage") player22.add_child(stage) - stage.add_child(Node.s16('no', score['id'])) - stage.add_child(Node.u8('sheet', score['chart'])) - stage.add_child(Node.u16('clearmedal', score['medal'])) - stage.add_child(Node.s32('nscore', score['score'])) + stage.add_child(Node.s16("no", score["id"])) + stage.add_child(Node.u8("sheet", score["chart"])) + stage.add_child(Node.u16("clearmedal", score["medal"])) + stage.add_child(Node.s32("nscore", score["score"])) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/player22/@status") def verify_player22_write_music(self, ref_id: str, score: Dict[str, Any]) -> None: call = self.call_node() # Construct node - player22 = Node.void('player22') + player22 = Node.void("player22") call.add_child(player22) - player22.set_attribute('method', 'write_music') - player22.add_child(Node.string('ref_id', ref_id)) - player22.add_child(Node.string('name', self.NAME)) - player22.add_child(Node.u8('stage', 0)) - player22.add_child(Node.s16('music_num', score['id'])) - player22.add_child(Node.u8('sheet_num', score['chart'])) - player22.add_child(Node.u8('clearmedal', score['medal'])) - player22.add_child(Node.s32('score', score['score'])) - player22.add_child(Node.s16('combo', 0)) - player22.add_child(Node.s16('cool', 0)) - player22.add_child(Node.s16('great', 0)) - player22.add_child(Node.s16('good', 0)) - player22.add_child(Node.s16('bad', 0)) + player22.set_attribute("method", "write_music") + player22.add_child(Node.string("ref_id", ref_id)) + player22.add_child(Node.string("name", self.NAME)) + player22.add_child(Node.u8("stage", 0)) + player22.add_child(Node.s16("music_num", score["id"])) + player22.add_child(Node.u8("sheet_num", score["chart"])) + player22.add_child(Node.u8("clearmedal", score["medal"])) + player22.add_child(Node.s32("score", score["score"])) + player22.add_child(Node.s16("combo", 0)) + player22.add_child(Node.s16("cool", 0)) + player22.add_child(Node.s16("great", 0)) + player22.add_child(Node.s16("good", 0)) + player22.add_child(Node.s16("bad", 0)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/player22/@status") def verify_player22_write_course(self, ref_id: str, course: Dict[str, int]) -> None: call = self.call_node() # Construct node - player22 = Node.void('player22') + player22 = Node.void("player22") call.add_child(player22) - player22.set_attribute('method', 'write_course') - player22.add_child(Node.s16('pref', 51)) - player22.add_child(Node.string('location_id', 'JP-1')) - player22.add_child(Node.string('ref_id', ref_id)) - player22.add_child(Node.string('data_id', ref_id)) - player22.add_child(Node.string('name', self.NAME)) - player22.add_child(Node.s16('chara_num', 1543)) - player22.add_child(Node.s32('play_id', 0)) - player22.add_child(Node.s16('course_id', course['id'])) - player22.add_child(Node.s16('stage1_music_num', 148)) - player22.add_child(Node.u8('stage1_sheet_num', 1)) - player22.add_child(Node.s16('stage2_music_num', 550)) - player22.add_child(Node.u8('stage2_sheet_num', 1)) - player22.add_child(Node.s16('stage3_music_num', 1113)) - player22.add_child(Node.u8('stage3_sheet_num', 1)) - player22.add_child(Node.s16('stage4_music_num', 341)) - player22.add_child(Node.u8('stage4_sheet_num', 1)) - player22.add_child(Node.u8('norma_type', 2)) - player22.add_child(Node.s32('norma_1_num', 5)) - player22.add_child(Node.s32('norma_2_num', 0)) - player22.add_child(Node.u8('clear_medal', course['medal'])) - player22.add_child(Node.u8('clear_norma', 2)) - player22.add_child(Node.s32('total_score', course['total'])) - player22.add_child(Node.s16('max_combo', course['combo'])) + player22.set_attribute("method", "write_course") + player22.add_child(Node.s16("pref", 51)) + player22.add_child(Node.string("location_id", "JP-1")) + player22.add_child(Node.string("ref_id", ref_id)) + player22.add_child(Node.string("data_id", ref_id)) + player22.add_child(Node.string("name", self.NAME)) + player22.add_child(Node.s16("chara_num", 1543)) + player22.add_child(Node.s32("play_id", 0)) + player22.add_child(Node.s16("course_id", course["id"])) + player22.add_child(Node.s16("stage1_music_num", 148)) + player22.add_child(Node.u8("stage1_sheet_num", 1)) + player22.add_child(Node.s16("stage2_music_num", 550)) + player22.add_child(Node.u8("stage2_sheet_num", 1)) + player22.add_child(Node.s16("stage3_music_num", 1113)) + player22.add_child(Node.u8("stage3_sheet_num", 1)) + player22.add_child(Node.s16("stage4_music_num", 341)) + player22.add_child(Node.u8("stage4_sheet_num", 1)) + player22.add_child(Node.u8("norma_type", 2)) + player22.add_child(Node.s32("norma_1_num", 5)) + player22.add_child(Node.s32("norma_2_num", 0)) + player22.add_child(Node.u8("clear_medal", course["medal"])) + player22.add_child(Node.u8("clear_norma", 2)) + player22.add_child(Node.s32("total_score", course["total"])) + player22.add_child(Node.s16("max_combo", course["combo"])) for stage, music in enumerate([148, 550, 1113, 341]): - stagenode = Node.void('stage') + stagenode = Node.void("stage") player22.add_child(stagenode) - stagenode.add_child(Node.u8('stage', stage)) - stagenode.add_child(Node.s16('music_num', music)) - stagenode.add_child(Node.u8('sheet_num', 1)) - stagenode.add_child(Node.s32('score', course[f'stage{stage + 1}'])) + stagenode.add_child(Node.u8("stage", stage)) + stagenode.add_child(Node.s16("music_num", music)) + stagenode.add_child(Node.u8("sheet_num", 1)) + stagenode.add_child(Node.s32("score", course[f"stage{stage + 1}"])) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/player22/@status") def verify_player22_new(self, ref_id: str) -> None: call = self.call_node() # Construct node - player22 = Node.void('player22') + player22 = Node.void("player22") call.add_child(player22) - player22.set_attribute('method', 'new') + player22.set_attribute("method", "new") - player22.add_child(Node.string('ref_id', ref_id)) - player22.add_child(Node.string('name', self.NAME)) - player22.add_child(Node.string('shop_name', '')) - player22.add_child(Node.s8('pref', 51)) + player22.add_child(Node.string("ref_id", ref_id)) + player22.add_child(Node.string("name", self.NAME)) + player22.add_child(Node.string("shop_name", "")) + player22.add_child(Node.s8("pref", 51)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/player22/account") @@ -251,20 +255,20 @@ class PopnMusicLapistoriaClient(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -283,70 +287,80 @@ class PopnMusicLapistoriaClient(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') - self.verify_player22_read(ref_id, msg_type='new') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") + self.verify_player22_read(ref_id, msg_type="new") self.verify_player22_new(ref_id) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") if cardid is None: # Verify score handling - scores = self.verify_player22_read(ref_id, msg_type='query') + scores = self.verify_player22_read(ref_id, msg_type="query") if scores is None: - raise Exception('Expected to get scores back, didn\'t get anything!') - for medal in scores['medals']: + raise Exception("Expected to get scores back, didn't get anything!") + for medal in scores["medals"]: for i in range(4): if medal[i] != 0: - raise Exception('Got nonzero medals count on a new card!') - for score in scores['scores']: + raise Exception("Got nonzero medals count on a new card!") + for score in scores["scores"]: for i in range(4): if score[i] != 0: - raise Exception('Got nonzero scores count on a new card!') - for _ in scores['courses']: - raise Exception('Got nonzero courses count on a new card!') + raise Exception("Got nonzero scores count on a new card!") + for _ in scores["courses"]: + raise Exception("Got nonzero courses count on a new card!") for phase in [1, 2]: if phase == 1: dummyscores = [ # An okay score on a chart { - 'id': 987, - 'chart': 2, - 'medal': 5, - 'score': 76543, + "id": 987, + "chart": 2, + "medal": 5, + "score": 76543, }, # A good score on an easier chart of the same song { - 'id': 987, - 'chart': 0, - 'medal': 6, - 'score': 99999, + "id": 987, + "chart": 0, + "medal": 6, + "score": 99999, }, # A bad score on a hard chart { - 'id': 741, - 'chart': 3, - 'medal': 2, - 'score': 45000, + "id": 741, + "chart": 3, + "medal": 2, + "score": 45000, }, # A terrible score on an easy chart { - 'id': 742, - 'chart': 1, - 'medal': 2, - 'score': 1, + "id": 742, + "chart": 1, + "medal": 2, + "score": 1, }, ] # Random score to add in @@ -354,79 +368,95 @@ class PopnMusicLapistoriaClient(BaseClient): chartid = random.randint(0, 3) score = random.randint(0, 100000) medal = random.randint(1, 11) - dummyscores.append({ - 'id': songid, - 'chart': chartid, - 'medal': medal, - 'score': score, - }) + dummyscores.append( + { + "id": songid, + "chart": chartid, + "medal": medal, + "score": score, + } + ) if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 987, - 'chart': 2, - 'medal': 5, - 'score': 98765, + "id": 987, + "chart": 2, + "medal": 5, + "score": 98765, }, # A worse score on another same chart { - 'id': 987, - 'chart': 0, - 'medal': 3, - 'score': 12345, - 'expected_score': 99999, - 'expected_medal': 6, + "id": 987, + "chart": 0, + "medal": 3, + "score": 12345, + "expected_score": 99999, + "expected_medal": 6, }, ] for dummyscore in dummyscores: self.verify_player22_write_music(ref_id, dummyscore) self.verify_player22_write(ref_id, dummyscores) - scores = self.verify_player22_read(ref_id, msg_type='query') + scores = self.verify_player22_read(ref_id, msg_type="query") for score in dummyscores: - newscore = scores['scores'][score['id']][score['chart']] - newmedal = scores['medals'][score['id']][score['chart']] + newscore = scores["scores"][score["id"]][score["chart"]] + newmedal = scores["medals"][score["id"]][score["chart"]] - if 'expected_score' in score: - expected_score = score['expected_score'] + if "expected_score" in score: + expected_score = score["expected_score"] else: - expected_score = score['score'] - if 'expected_medal' in score: - expected_medal = score['expected_medal'] + expected_score = score["score"] + if "expected_medal" in score: + expected_medal = score["expected_medal"] else: - expected_medal = score['medal'] + expected_medal = score["medal"] if newscore != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{newscore}\'') + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{newscore}\'' + ) if newmedal != expected_medal: - raise Exception(f'Expected a medal of \'{expected_medal}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got medal \'{newmedal}\'') + raise Exception( + f'Expected a medal of \'{expected_medal}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got medal \'{newmedal}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) # Write a random course so we know we can retrieve them. course = { - 'id': random.randint(1, 100), - 'medal': 2, - 'combo': random.randint(10, 100), - 'stage1': random.randint(70000, 100000), - 'stage2': random.randint(70000, 100000), - 'stage3': random.randint(70000, 100000), - 'stage4': random.randint(70000, 100000), + "id": random.randint(1, 100), + "medal": 2, + "combo": random.randint(10, 100), + "stage1": random.randint(70000, 100000), + "stage2": random.randint(70000, 100000), + "stage3": random.randint(70000, 100000), + "stage4": random.randint(70000, 100000), } - course['total'] = sum(course[f'stage{i + 1}'] for i in range(4)) + course["total"] = sum(course[f"stage{i + 1}"] for i in range(4)) self.verify_player22_write_course(ref_id, course) # Now, grab the profile one more time and see that it is there. - scores = self.verify_player22_read(ref_id, msg_type='query') - if len(scores['courses']) != 1: + scores = self.verify_player22_read(ref_id, msg_type="query") + if len(scores["courses"]) != 1: raise Exception("Did not get a course back after saving!") - if course['id'] not in scores['courses']: + if course["id"] not in scores["courses"]: raise Exception("Did not get expected course back after saving!") - for key in ['medal', 'combo', 'stage1', 'stage2', 'stage3', 'stage4', 'total']: - if course[key] != scores['courses'][course['id']][key]: - raise Exception(f'Expected a {key} of \'{course[key]}\' but got \'{scores["courses"][course["id"]][key]}\'') + for key in [ + "medal", + "combo", + "stage1", + "stage2", + "stage3", + "stage4", + "total", + ]: + if course[key] != scores["courses"][course["id"]][key]: + raise Exception( + f'Expected a {key} of \'{course[key]}\' but got \'{scores["courses"][course["id"]][key]}\'' + ) else: print("Skipping score checks for existing card") diff --git a/bemani/client/popn/peace.py b/bemani/client/popn/peace.py index fca9ff0..7fcdd86 100644 --- a/bemani/client/popn/peace.py +++ b/bemani/client/popn/peace.py @@ -7,33 +7,33 @@ from bemani.protocol import Node class PopnMusicPeaceClient(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_pcb24_boot(self, loc: str) -> None: call = self.call_node() # Construct node - pcb24 = Node.void('pcb24') + pcb24 = Node.void("pcb24") call.add_child(pcb24) - pcb24.set_attribute('method', 'boot') - pcb24.add_child(Node.string('loc_id', loc)) - pcb24.add_child(Node.u8('loc_type', 0)) - pcb24.add_child(Node.string('loc_name', '')) - pcb24.add_child(Node.string('country', 'US')) - pcb24.add_child(Node.string('region', '.')) - pcb24.add_child(Node.s16('pref', 51)) - pcb24.add_child(Node.string('customer', '')) - pcb24.add_child(Node.string('company', '')) - pcb24.add_child(Node.ipv4('gip', '127.0.0.1')) - pcb24.add_child(Node.u16('gp', 10011)) - pcb24.add_child(Node.string('rom_number', 'M39-JB-G01')) - pcb24.add_child(Node.u64('c_drive', 10028228608)) - pcb24.add_child(Node.u64('d_drive', 47945170944)) - pcb24.add_child(Node.u64('e_drive', 10394677248)) - pcb24.add_child(Node.string('etc', '')) + pcb24.set_attribute("method", "boot") + pcb24.add_child(Node.string("loc_id", loc)) + pcb24.add_child(Node.u8("loc_type", 0)) + pcb24.add_child(Node.string("loc_name", "")) + pcb24.add_child(Node.string("country", "US")) + pcb24.add_child(Node.string("region", ".")) + pcb24.add_child(Node.s16("pref", 51)) + pcb24.add_child(Node.string("customer", "")) + pcb24.add_child(Node.string("company", "")) + pcb24.add_child(Node.ipv4("gip", "127.0.0.1")) + pcb24.add_child(Node.u16("gp", 10011)) + pcb24.add_child(Node.string("rom_number", "M39-JB-G01")) + pcb24.add_child(Node.u64("c_drive", 10028228608)) + pcb24.add_child(Node.u64("d_drive", 47945170944)) + pcb24.add_child(Node.u64("e_drive", 10394677248)) + pcb24.add_child(Node.string("etc", "")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/pcb24/@status") @@ -59,29 +59,29 @@ class PopnMusicPeaceClient(BaseClient): call = self.call_node() # Construct node - info24 = Node.void('info24') + info24 = Node.void("info24") call.add_child(info24) - info24.set_attribute('loc_id', loc) - info24.set_attribute('method', 'common') + info24.set_attribute("loc_id", loc) + info24.set_attribute("method", "common") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct - self.__verify_common('info24', resp) + self.__verify_common("info24", resp) def verify_lobby24_getlist(self, loc: str) -> None: call = self.call_node() # Construct node - lobby24 = Node.void('lobby24') + lobby24 = Node.void("lobby24") call.add_child(lobby24) - lobby24.set_attribute('method', 'getList') - lobby24.add_child(Node.string('location_id', loc)) - lobby24.add_child(Node.u8('net_version', 63)) + lobby24.set_attribute("method", "getList") + lobby24.add_child(Node.string("location_id", loc)) + lobby24.add_child(Node.u8("net_version", 63)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/lobby24/@status") @@ -136,89 +136,99 @@ class PopnMusicPeaceClient(BaseClient): self.assert_path(resp, "response/player24/stamp/stamp_id") self.assert_path(resp, "response/player24/stamp/cnt") - def verify_player24_read(self, ref_id: str, msg_type: str) -> Dict[str, Dict[int, Dict[str, int]]]: + def verify_player24_read( + self, ref_id: str, msg_type: str + ) -> Dict[str, Dict[int, Dict[str, int]]]: call = self.call_node() # Construct node - player24 = Node.void('player24') + player24 = Node.void("player24") call.add_child(player24) - player24.set_attribute('method', 'read') + player24.set_attribute("method", "read") - player24.add_child(Node.string('ref_id', ref_id)) - player24.add_child(Node.s8('pref', 51)) + player24.add_child(Node.string("ref_id", ref_id)) + player24.add_child(Node.s8("pref", 51)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) - if msg_type == 'new': + if msg_type == "new": # Verify that response is correct self.assert_path(resp, "response/player24/result") - status = resp.child_value('player24/result') + status = resp.child_value("player24/result") if status != 2: - raise Exception(f'Reference ID \'{ref_id}\' returned invalid status \'{status}\'') + raise Exception( + f"Reference ID '{ref_id}' returned invalid status '{status}'" + ) return { - 'items': {}, - 'characters': {}, - 'points': {}, + "items": {}, + "characters": {}, + "points": {}, } - elif msg_type == 'query': + elif msg_type == "query": # Verify that the response is correct self.__verify_profile(resp) self.assert_path(resp, "response/player24/result") - status = resp.child_value('player24/result') + status = resp.child_value("player24/result") if status != 0: - raise Exception(f'Reference ID \'{ref_id}\' returned invalid status \'{status}\'') - name = resp.child_value('player24/account/name') + raise Exception( + f"Reference ID '{ref_id}' returned invalid status '{status}'" + ) + name = resp.child_value("player24/account/name") if name != self.NAME: - raise Exception(f'Invalid name \'{name}\' returned for Ref ID \'{ref_id}\'') + raise Exception(f"Invalid name '{name}' returned for Ref ID '{ref_id}'") # Medals and items items: Dict[int, Dict[str, int]] = {} charas: Dict[int, Dict[str, int]] = {} courses: Dict[int, Dict[str, int]] = {} - for obj in resp.child('player24').children: - if obj.name == 'item': - items[obj.child_value('id')] = { - 'type': obj.child_value('type'), - 'param': obj.child_value('param'), + for obj in resp.child("player24").children: + if obj.name == "item": + items[obj.child_value("id")] = { + "type": obj.child_value("type"), + "param": obj.child_value("param"), } - elif obj.name == 'chara_param': - charas[obj.child_value('chara_id')] = { - 'friendship': obj.child_value('friendship'), + elif obj.name == "chara_param": + charas[obj.child_value("chara_id")] = { + "friendship": obj.child_value("friendship"), } - elif obj.name == 'course_data': - courses[obj.child_value('course_id')] = { - 'clear_type': obj.child_value('clear_type'), - 'clear_rank': obj.child_value('clear_rank'), - 'total_score': obj.child_value('total_score'), - 'count': obj.child_value('update_count'), - 'sheet_num': obj.child_value('sheet_num'), + elif obj.name == "course_data": + courses[obj.child_value("course_id")] = { + "clear_type": obj.child_value("clear_type"), + "clear_rank": obj.child_value("clear_rank"), + "total_score": obj.child_value("total_score"), + "count": obj.child_value("update_count"), + "sheet_num": obj.child_value("sheet_num"), } return { - 'items': items, - 'characters': charas, - 'courses': courses, - 'points': {0: {'points': resp.child_value('player24/account/player_point')}}, + "items": items, + "characters": charas, + "courses": courses, + "points": { + 0: {"points": resp.child_value("player24/account/player_point")} + }, } else: - raise Exception(f'Unrecognized message type \'{msg_type}\'') + raise Exception(f"Unrecognized message type '{msg_type}'") - def verify_player24_read_score(self, ref_id: str) -> Dict[str, Dict[int, Dict[int, int]]]: + def verify_player24_read_score( + self, ref_id: str + ) -> Dict[str, Dict[int, Dict[int, int]]]: call = self.call_node() # Construct node - player24 = Node.void('player24') + player24 = Node.void("player24") call.add_child(player24) - player24.set_attribute('method', 'read_score') + player24.set_attribute("method", "read_score") - player24.add_child(Node.string('ref_id', ref_id)) - player24.add_child(Node.s8('pref', 51)) + player24.add_child(Node.string("ref_id", ref_id)) + player24.add_child(Node.s8("pref", 51)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify defaults self.assert_path(resp, "response/player24/@status") @@ -227,15 +237,15 @@ class PopnMusicPeaceClient(BaseClient): scores: Dict[int, Dict[int, int]] = {} medals: Dict[int, Dict[int, int]] = {} ranks: Dict[int, Dict[int, int]] = {} - for child in resp.child('player24').children: - if child.name != 'music': + for child in resp.child("player24").children: + if child.name != "music": continue - musicid = child.child_value('music_num') - chart = child.child_value('sheet_num') - score = child.child_value('score') - medal = child.child_value('clear_type') - rank = child.child_value('clear_rank') + musicid = child.child_value("music_num") + chart = child.child_value("sheet_num") + score = child.child_value("score") + medal = child.child_value("clear_type") + rank = child.child_value("clear_rank") if musicid not in scores: scores[musicid] = {} @@ -249,53 +259,53 @@ class PopnMusicPeaceClient(BaseClient): ranks[musicid][chart] = rank return { - 'scores': scores, - 'medals': medals, - 'ranks': ranks, + "scores": scores, + "medals": medals, + "ranks": ranks, } def verify_player24_start(self, ref_id: str, loc: str) -> None: call = self.call_node() # Construct node - player24 = Node.void('player24') + player24 = Node.void("player24") call.add_child(player24) - player24.set_attribute('loc_id', loc) - player24.set_attribute('ref_id', ref_id) - player24.set_attribute('method', 'start') - player24.set_attribute('start_type', '0') - pcb_card = Node.void('pcb_card') + player24.set_attribute("loc_id", loc) + player24.set_attribute("ref_id", ref_id) + player24.set_attribute("method", "start") + player24.set_attribute("start_type", "0") + pcb_card = Node.void("pcb_card") player24.add_child(pcb_card) - pcb_card.add_child(Node.s8('card_enable', 1)) - pcb_card.add_child(Node.s8('card_soldout', 0)) + pcb_card.add_child(Node.s8("card_enable", 1)) + pcb_card.add_child(Node.s8("card_soldout", 0)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct - self.__verify_common('player24', resp) + self.__verify_common("player24", resp) def verify_player24_update_ranking(self, ref_id: str, loc: str) -> None: call = self.call_node() # Construct node - player24 = Node.void('player24') + player24 = Node.void("player24") call.add_child(player24) - player24.set_attribute('method', 'update_ranking') - player24.add_child(Node.s16('pref', 51)) - player24.add_child(Node.string('location_id', loc)) - player24.add_child(Node.string('ref_id', ref_id)) - player24.add_child(Node.string('name', self.NAME)) - player24.add_child(Node.s16('chara_num', 1)) - player24.add_child(Node.s16('course_id', 12345)) - player24.add_child(Node.s32('total_score', 86000)) - player24.add_child(Node.s16('music_num', 1375)) - player24.add_child(Node.u8('sheet_num', 2)) - player24.add_child(Node.u8('clear_type', 7)) - player24.add_child(Node.u8('clear_rank', 5)) + player24.set_attribute("method", "update_ranking") + player24.add_child(Node.s16("pref", 51)) + player24.add_child(Node.string("location_id", loc)) + player24.add_child(Node.string("ref_id", ref_id)) + player24.add_child(Node.string("name", self.NAME)) + player24.add_child(Node.s16("chara_num", 1)) + player24.add_child(Node.s16("course_id", 12345)) + player24.add_child(Node.s32("total_score", 86000)) + player24.add_child(Node.s16("music_num", 1375)) + player24.add_child(Node.u8("sheet_num", 2)) + player24.add_child(Node.u8("clear_type", 7)) + player24.add_child(Node.u8("clear_rank", 5)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player24/all_ranking/name") @@ -310,13 +320,13 @@ class PopnMusicPeaceClient(BaseClient): call = self.call_node() # Construct node - player24 = Node.void('player24') + player24 = Node.void("player24") call.add_child(player24) - player24.set_attribute('ref_id', ref_id) - player24.set_attribute('method', 'logout') + player24.set_attribute("ref_id", ref_id) + player24.set_attribute("method", "logout") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player24/@status") @@ -324,99 +334,99 @@ class PopnMusicPeaceClient(BaseClient): def verify_player24_write( self, ref_id: str, - item: Optional[Dict[str, int]]=None, - character: Optional[Dict[str, int]]=None, + item: Optional[Dict[str, int]] = None, + character: Optional[Dict[str, int]] = None, ) -> None: call = self.call_node() # Construct node - player24 = Node.void('player24') + player24 = Node.void("player24") call.add_child(player24) - player24.set_attribute('method', 'write') - player24.add_child(Node.string('ref_id', ref_id)) + player24.set_attribute("method", "write") + player24.add_child(Node.string("ref_id", ref_id)) # Add required children - config = Node.void('config') + config = Node.void("config") player24.add_child(config) - config.add_child(Node.s16('chara', 1543)) + config.add_child(Node.s16("chara", 1543)) if item is not None: - itemnode = Node.void('item') + itemnode = Node.void("item") player24.add_child(itemnode) - itemnode.add_child(Node.u8('type', item['type'])) - itemnode.add_child(Node.u16('id', item['id'])) - itemnode.add_child(Node.u16('param', item['param'])) - itemnode.add_child(Node.bool('is_new', False)) - itemnode.add_child(Node.u64('get_time', 0)) + itemnode.add_child(Node.u8("type", item["type"])) + itemnode.add_child(Node.u16("id", item["id"])) + itemnode.add_child(Node.u16("param", item["param"])) + itemnode.add_child(Node.bool("is_new", False)) + itemnode.add_child(Node.u64("get_time", 0)) if character is not None: - chara_param = Node.void('chara_param') + chara_param = Node.void("chara_param") player24.add_child(chara_param) - chara_param.add_child(Node.u16('chara_id', character['id'])) - chara_param.add_child(Node.u16('friendship', character['friendship'])) + chara_param.add_child(Node.u16("chara_id", character["id"])) + chara_param.add_child(Node.u16("friendship", character["friendship"])) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/player24/@status") def verify_player24_buy(self, ref_id: str, item: Dict[str, int]) -> None: call = self.call_node() # Construct node - player24 = Node.void('player24') + player24 = Node.void("player24") call.add_child(player24) - player24.set_attribute('method', 'buy') - player24.add_child(Node.s32('play_id', 0)) - player24.add_child(Node.string('ref_id', ref_id)) - player24.add_child(Node.u16('id', item['id'])) - player24.add_child(Node.u8('type', item['type'])) - player24.add_child(Node.u16('param', item['param'])) - player24.add_child(Node.s32('lumina', item['points'])) - player24.add_child(Node.u16('price', item['price'])) + player24.set_attribute("method", "buy") + player24.add_child(Node.s32("play_id", 0)) + player24.add_child(Node.string("ref_id", ref_id)) + player24.add_child(Node.u16("id", item["id"])) + player24.add_child(Node.u8("type", item["type"])) + player24.add_child(Node.u16("param", item["param"])) + player24.add_child(Node.s32("lumina", item["points"])) + player24.add_child(Node.u16("price", item["price"])) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/player24/@status") def verify_player24_write_music(self, ref_id: str, score: Dict[str, Any]) -> None: call = self.call_node() # Construct node - player24 = Node.void('player24') + player24 = Node.void("player24") call.add_child(player24) - player24.set_attribute('method', 'write_music') - player24.add_child(Node.string('ref_id', ref_id)) - player24.add_child(Node.string('data_id', ref_id)) - player24.add_child(Node.string('name', self.NAME)) - player24.add_child(Node.u8('stage', 0)) - player24.add_child(Node.s16('music_num', score['id'])) - player24.add_child(Node.u8('sheet_num', score['chart'])) - player24.add_child(Node.u8('clear_type', score['medal'])) - player24.add_child(Node.s32('score', score['score'])) - player24.add_child(Node.s16('combo', 0)) - player24.add_child(Node.s16('cool', 0)) - player24.add_child(Node.s16('great', 0)) - player24.add_child(Node.s16('good', 0)) - player24.add_child(Node.s16('bad', 0)) + player24.set_attribute("method", "write_music") + player24.add_child(Node.string("ref_id", ref_id)) + player24.add_child(Node.string("data_id", ref_id)) + player24.add_child(Node.string("name", self.NAME)) + player24.add_child(Node.u8("stage", 0)) + player24.add_child(Node.s16("music_num", score["id"])) + player24.add_child(Node.u8("sheet_num", score["chart"])) + player24.add_child(Node.u8("clear_type", score["medal"])) + player24.add_child(Node.s32("score", score["score"])) + player24.add_child(Node.s16("combo", 0)) + player24.add_child(Node.s16("cool", 0)) + player24.add_child(Node.s16("great", 0)) + player24.add_child(Node.s16("good", 0)) + player24.add_child(Node.s16("bad", 0)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/player24/@status") def verify_player24_new(self, ref_id: str) -> None: call = self.call_node() # Construct node - player24 = Node.void('player24') + player24 = Node.void("player24") call.add_child(player24) - player24.set_attribute('method', 'new') + player24.set_attribute("method", "new") - player24.add_child(Node.string('ref_id', ref_id)) - player24.add_child(Node.string('name', self.NAME)) - player24.add_child(Node.s8('pref', 51)) + player24.add_child(Node.string("ref_id", ref_id)) + player24.add_child(Node.string("name", self.NAME)) + player24.add_child(Node.s8("pref", 51)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes self.__verify_profile(resp) @@ -425,20 +435,20 @@ class PopnMusicPeaceClient(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -458,126 +468,153 @@ class PopnMusicPeaceClient(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') - self.verify_player24_read(ref_id, msg_type='new') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") + self.verify_player24_read(ref_id, msg_type="new") self.verify_player24_new(ref_id) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Verify proper handling of basic stuff - self.verify_player24_read(ref_id, msg_type='query') + self.verify_player24_read(ref_id, msg_type="query") self.verify_player24_start(ref_id, location) self.verify_player24_write(ref_id) self.verify_player24_logout(ref_id) if cardid is None: # Verify unlocks/story mode work - unlocks = self.verify_player24_read(ref_id, msg_type='query') - for item in unlocks['items']: + unlocks = self.verify_player24_read(ref_id, msg_type="query") + for item in unlocks["items"]: if item in [1592, 1608]: # Song unlocks after one play continue - raise Exception('Got nonzero items count on a new card!') - for _ in unlocks['characters']: - raise Exception('Got nonzero characters count on a new card!') - for _ in unlocks['courses']: - raise Exception('Got nonzero course count on a new card!') - if unlocks['points'][0]['points'] != 300: - raise Exception('Got wrong default value for points on a new card!') + raise Exception("Got nonzero items count on a new card!") + for _ in unlocks["characters"]: + raise Exception("Got nonzero characters count on a new card!") + for _ in unlocks["courses"]: + raise Exception("Got nonzero course count on a new card!") + if unlocks["points"][0]["points"] != 300: + raise Exception("Got wrong default value for points on a new card!") - self.verify_player24_write(ref_id, item={'id': 4, 'type': 2, 'param': 69}) - unlocks = self.verify_player24_read(ref_id, msg_type='query') - if 4 not in unlocks['items']: - raise Exception('Expecting to see item ID 4 in items!') - if unlocks['items'][4]['type'] != 2: - raise Exception('Expecting to see item ID 4 to have type 2 in items!') - if unlocks['items'][4]['param'] != 69: - raise Exception('Expecting to see item ID 4 to have param 69 in items!') + self.verify_player24_write(ref_id, item={"id": 4, "type": 2, "param": 69}) + unlocks = self.verify_player24_read(ref_id, msg_type="query") + if 4 not in unlocks["items"]: + raise Exception("Expecting to see item ID 4 in items!") + if unlocks["items"][4]["type"] != 2: + raise Exception("Expecting to see item ID 4 to have type 2 in items!") + if unlocks["items"][4]["param"] != 69: + raise Exception("Expecting to see item ID 4 to have param 69 in items!") - self.verify_player24_write(ref_id, character={'id': 5, 'friendship': 420}) - unlocks = self.verify_player24_read(ref_id, msg_type='query') - if 5 not in unlocks['characters']: - raise Exception('Expecting to see chara ID 5 in characters!') - if unlocks['characters'][5]['friendship'] != 420: - raise Exception('Expecting to see chara ID 5 to have type 2 in characters!') + self.verify_player24_write(ref_id, character={"id": 5, "friendship": 420}) + unlocks = self.verify_player24_read(ref_id, msg_type="query") + if 5 not in unlocks["characters"]: + raise Exception("Expecting to see chara ID 5 in characters!") + if unlocks["characters"][5]["friendship"] != 420: + raise Exception( + "Expecting to see chara ID 5 to have type 2 in characters!" + ) # Verify purchases work - self.verify_player24_buy(ref_id, item={'id': 6, 'type': 3, 'param': 8, 'points': 400, 'price': 250}) - unlocks = self.verify_player24_read(ref_id, msg_type='query') - if 6 not in unlocks['items']: - raise Exception('Expecting to see item ID 6 in items!') - if unlocks['items'][6]['type'] != 3: - raise Exception('Expecting to see item ID 6 to have type 3 in items!') - if unlocks['items'][6]['param'] != 8: - raise Exception('Expecting to see item ID 6 to have param 8 in items!') - if unlocks['points'][0]['points'] != 150: - raise Exception(f'Got wrong value for points {unlocks["points"][0]["points"]} after purchase!') + self.verify_player24_buy( + ref_id, + item={"id": 6, "type": 3, "param": 8, "points": 400, "price": 250}, + ) + unlocks = self.verify_player24_read(ref_id, msg_type="query") + if 6 not in unlocks["items"]: + raise Exception("Expecting to see item ID 6 in items!") + if unlocks["items"][6]["type"] != 3: + raise Exception("Expecting to see item ID 6 to have type 3 in items!") + if unlocks["items"][6]["param"] != 8: + raise Exception("Expecting to see item ID 6 to have param 8 in items!") + if unlocks["points"][0]["points"] != 150: + raise Exception( + f'Got wrong value for points {unlocks["points"][0]["points"]} after purchase!' + ) # Verify course handling self.verify_player24_update_ranking(ref_id, location) - unlocks = self.verify_player24_read(ref_id, msg_type='query') - if 12345 not in unlocks['courses']: - raise Exception('Expecting to see course ID 12345 in courses!') - if unlocks['courses'][12345]['clear_type'] != 7: - raise Exception('Expecting to see item ID 12345 to have clear_type 7 in courses!') - if unlocks['courses'][12345]['clear_rank'] != 5: - raise Exception('Expecting to see item ID 12345 to have clear_rank 5 in courses!') - if unlocks['courses'][12345]['total_score'] != 86000: - raise Exception('Expecting to see item ID 12345 to have total_score 86000 in courses!') - if unlocks['courses'][12345]['count'] != 1: - raise Exception('Expecting to see item ID 12345 to have count 1 in courses!') - if unlocks['courses'][12345]['sheet_num'] != 2: - raise Exception('Expecting to see item ID 12345 to have sheet_num 2 in courses!') + unlocks = self.verify_player24_read(ref_id, msg_type="query") + if 12345 not in unlocks["courses"]: + raise Exception("Expecting to see course ID 12345 in courses!") + if unlocks["courses"][12345]["clear_type"] != 7: + raise Exception( + "Expecting to see item ID 12345 to have clear_type 7 in courses!" + ) + if unlocks["courses"][12345]["clear_rank"] != 5: + raise Exception( + "Expecting to see item ID 12345 to have clear_rank 5 in courses!" + ) + if unlocks["courses"][12345]["total_score"] != 86000: + raise Exception( + "Expecting to see item ID 12345 to have total_score 86000 in courses!" + ) + if unlocks["courses"][12345]["count"] != 1: + raise Exception( + "Expecting to see item ID 12345 to have count 1 in courses!" + ) + if unlocks["courses"][12345]["sheet_num"] != 2: + raise Exception( + "Expecting to see item ID 12345 to have sheet_num 2 in courses!" + ) # Verify score handling scores = self.verify_player24_read_score(ref_id) - for _ in scores['medals']: - raise Exception('Got nonzero medals count on a new card!') - for _ in scores['scores']: - raise Exception('Got nonzero scores count on a new card!') + for _ in scores["medals"]: + raise Exception("Got nonzero medals count on a new card!") + for _ in scores["scores"]: + raise Exception("Got nonzero scores count on a new card!") for phase in [1, 2]: if phase == 1: dummyscores = [ # An okay score on a chart { - 'id': 987, - 'chart': 2, - 'medal': 5, - 'score': 76543, + "id": 987, + "chart": 2, + "medal": 5, + "score": 76543, }, # A good score on an easier chart of the same song { - 'id': 987, - 'chart': 0, - 'medal': 6, - 'score': 99999, + "id": 987, + "chart": 0, + "medal": 6, + "score": 99999, }, # A bad score on a hard chart { - 'id': 741, - 'chart': 3, - 'medal': 2, - 'score': 45000, + "id": 741, + "chart": 3, + "medal": 2, + "score": 45000, }, # A terrible score on an easy chart { - 'id': 742, - 'chart': 1, - 'medal': 2, - 'score': 1, + "id": 742, + "chart": 1, + "medal": 2, + "score": 1, }, ] # Random score to add in @@ -585,29 +622,31 @@ class PopnMusicPeaceClient(BaseClient): chartid = random.randint(0, 3) score = random.randint(0, 100000) medal = random.randint(1, 11) - dummyscores.append({ - 'id': songid, - 'chart': chartid, - 'medal': medal, - 'score': score, - }) + dummyscores.append( + { + "id": songid, + "chart": chartid, + "medal": medal, + "score": score, + } + ) if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 987, - 'chart': 2, - 'medal': 6, - 'score': 98765, + "id": 987, + "chart": 2, + "medal": 6, + "score": 98765, }, # A worse score on another same chart { - 'id': 987, - 'chart': 0, - 'medal': 3, - 'score': 12345, - 'expected_score': 99999, - 'expected_medal': 6, + "id": 987, + "chart": 0, + "medal": 3, + "score": 12345, + "expected_score": 99999, + "expected_medal": 6, }, ] @@ -615,18 +654,18 @@ class PopnMusicPeaceClient(BaseClient): self.verify_player24_write_music(ref_id, dummyscore) scores = self.verify_player24_read_score(ref_id) for expected in dummyscores: - newscore = scores['scores'][expected['id']][expected['chart']] - newmedal = scores['medals'][expected['id']][expected['chart']] - newrank = scores['ranks'][expected['id']][expected['chart']] + newscore = scores["scores"][expected["id"]][expected["chart"]] + newmedal = scores["medals"][expected["id"]][expected["chart"]] + newrank = scores["ranks"][expected["id"]][expected["chart"]] - if 'expected_score' in expected: - expected_score = expected['expected_score'] + if "expected_score" in expected: + expected_score = expected["expected_score"] else: - expected_score = expected['score'] - if 'expected_medal' in expected: - expected_medal = expected['expected_medal'] + expected_score = expected["score"] + if "expected_medal" in expected: + expected_medal = expected["expected_medal"] else: - expected_medal = expected['medal'] + expected_medal = expected["medal"] if newscore < 50000: expected_rank = 1 @@ -646,11 +685,17 @@ class PopnMusicPeaceClient(BaseClient): expected_rank = 8 if newscore != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{newscore}\'') + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{newscore}\'' + ) if newmedal != expected_medal: - raise Exception(f'Expected a medal of \'{expected_medal}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got medal \'{newmedal}\'') + raise Exception( + f'Expected a medal of \'{expected_medal}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got medal \'{newmedal}\'' + ) if newrank != expected_rank: - raise Exception(f'Expected a rank of \'{expected_rank}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got rank \'{newrank}\'') + raise Exception( + f'Expected a rank of \'{expected_rank}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got rank \'{newrank}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) diff --git a/bemani/client/popn/sunnypark.py b/bemani/client/popn/sunnypark.py index 6702350..dee82c9 100644 --- a/bemani/client/popn/sunnypark.py +++ b/bemani/client/popn/sunnypark.py @@ -7,21 +7,21 @@ from bemani.protocol import Node class PopnMusicSunnyParkClient(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_game_active(self) -> None: call = self.call_node() # Construct node - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'active') + game.set_attribute("method", "active") # Add minimum amount of stuff so server accepts - game.add_child(Node.s8('event', 0)) + game.add_child(Node.s8("event", 0)) # Swap with server - resp = self.exchange('pnm20/game', call) + resp = self.exchange("pnm20/game", call) # Verify that response is correct self.assert_path(resp, "response/game/@status") @@ -30,92 +30,98 @@ class PopnMusicSunnyParkClient(BaseClient): call = self.call_node() # Construct node - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('location_id', 'JP-1') - game.set_attribute('method', 'get') - game.add_child(Node.s8('event', 0)) + game.set_attribute("location_id", "JP-1") + game.set_attribute("method", "get") + game.add_child(Node.s8("event", 0)) # Swap with server - resp = self.exchange('pnm20/game', call) + resp = self.exchange("pnm20/game", call) # Verify that response is correct self.assert_path(resp, "response/game") for name in [ - 'ir_phase', - 'music_open_phase', - 'collabo_phase', - 'personal_event_phase', - 'shop_event_phase', - 'netvs_phase', - 'card_phase', - 'other_phase', - 'local_matching_enable', - 'n_matching_sec', - 'l_matching_sec', - 'is_check_cpu', - 'week_no', + "ir_phase", + "music_open_phase", + "collabo_phase", + "personal_event_phase", + "shop_event_phase", + "netvs_phase", + "card_phase", + "other_phase", + "local_matching_enable", + "n_matching_sec", + "l_matching_sec", + "is_check_cpu", + "week_no", ]: - node = resp.child('game').child(name) + node = resp.child("game").child(name) if node is None: - raise Exception(f'Missing node \'{name}\' in response!') - if node.data_type != 's32': - raise Exception(f'Node \'{name}\' has wrong data type!') + raise Exception(f"Missing node '{name}' in response!") + if node.data_type != "s32": + raise Exception(f"Node '{name}' has wrong data type!") - sel_ranking = resp.child('game').child('sel_ranking') - up_ranking = resp.child('game').child('up_ranking') + sel_ranking = resp.child("game").child("sel_ranking") + up_ranking = resp.child("game").child("up_ranking") - for nodepair in [('sel_ranking', sel_ranking), ('up_ranking', up_ranking)]: + for nodepair in [("sel_ranking", sel_ranking), ("up_ranking", up_ranking)]: name = nodepair[0] node = nodepair[1] if node is None: - raise Exception(f'Missing node \'{name}\' in response!') - if node.data_type != 's16': - raise Exception(f'Node \'{name}\' has wrong data type!') + raise Exception(f"Missing node '{name}' in response!") + if node.data_type != "s16": + raise Exception(f"Node '{name}' has wrong data type!") if not node.is_array: - raise Exception(f'Node \'{name}\' is not array!') + raise Exception(f"Node '{name}' is not array!") if len(node.value) != 5: - raise Exception(f'Node \'{name}\' is wrong array length!') + raise Exception(f"Node '{name}' is wrong array length!") - def verify_playerdata_get(self, ref_id: str, msg_type: str) -> Optional[Dict[str, Any]]: + def verify_playerdata_get( + self, ref_id: str, msg_type: str + ) -> Optional[Dict[str, Any]]: call = self.call_node() # Construct node - playerdata = Node.void('playerdata') + playerdata = Node.void("playerdata") call.add_child(playerdata) - playerdata.set_attribute('method', 'get') - if msg_type == 'new': - playerdata.set_attribute('model', self.config['old_profile_model'].split(':')[0]) + playerdata.set_attribute("method", "get") + if msg_type == "new": + playerdata.set_attribute( + "model", self.config["old_profile_model"].split(":")[0] + ) - playerdata.add_child(Node.string('ref_id', ref_id)) - playerdata.add_child(Node.string('shop_name', '')) - playerdata.add_child(Node.s8('pref', 51)) - if msg_type == 'new': - playerdata.add_child(Node.s32('ir_num', 0)) - elif msg_type == 'query': - playerdata.add_child(Node.s32('gakuen', 2)) - playerdata.add_child(Node.s32('zoo', 1)) - playerdata.add_child(Node.s32('floor_infection', 1)) - playerdata.add_child(Node.s32('triple_journey', 1)) - playerdata.add_child(Node.s32('baseball', 1)) + playerdata.add_child(Node.string("ref_id", ref_id)) + playerdata.add_child(Node.string("shop_name", "")) + playerdata.add_child(Node.s8("pref", 51)) + if msg_type == "new": + playerdata.add_child(Node.s32("ir_num", 0)) + elif msg_type == "query": + playerdata.add_child(Node.s32("gakuen", 2)) + playerdata.add_child(Node.s32("zoo", 1)) + playerdata.add_child(Node.s32("floor_infection", 1)) + playerdata.add_child(Node.s32("triple_journey", 1)) + playerdata.add_child(Node.s32("baseball", 1)) # Swap with server - resp = self.exchange('pnm20/playerdata', call) + resp = self.exchange("pnm20/playerdata", call) - if msg_type == 'new': + if msg_type == "new": # Verify that response is correct self.assert_path(resp, "response/playerdata/@status") - status = int(resp.child('playerdata').attribute('status')) + status = int(resp.child("playerdata").attribute("status")) if status != 109: - raise Exception(f'Reference ID \'{ref_id}\' returned invalid status \'{status}\'') + raise Exception( + f"Reference ID '{ref_id}' returned invalid status '{status}'" + ) # No score data return None - elif msg_type == 'query': + elif msg_type == "query": # Verify that the response is correct self.assert_path(resp, "response/playerdata/base/name") self.assert_path(resp, "response/playerdata/base/g_pm_id") @@ -127,9 +133,9 @@ class PopnMusicSunnyParkClient(BaseClient): self.assert_path(resp, "response/playerdata/sp_data") self.assert_path(resp, "response/playerdata/hiscore") - name = resp.child('playerdata').child('base').child('name').value + name = resp.child("playerdata").child("base").child("name").value if name != self.NAME: - raise Exception(f'Invalid name \'{name}\' returned for Ref ID \'{ref_id}\'') + raise Exception(f"Invalid name '{name}' returned for Ref ID '{ref_id}'") # Extract and return score data self.assert_path(resp, "response/playerdata/base/clear_medal") @@ -142,9 +148,15 @@ class PopnMusicSunnyParkClient(BaseClient): (medal >> 12) & 0xF, ) - medals = [transform_medals(medal) for medal in resp.child('playerdata').child('base').child('clear_medal').value] + medals = [ + transform_medals(medal) + for medal in resp.child("playerdata") + .child("base") + .child("clear_medal") + .value + ] - hiscore = resp.child('playerdata').child('hiscore').value + hiscore = resp.child("playerdata").child("hiscore").value hiscores = [] for i in range(0, len(hiscore) * 8, 17): byte_offset = int(i / 8) @@ -157,65 +169,70 @@ class PopnMusicSunnyParkClient(BaseClient): value = value >> bit_offset hiscores.append(value & 0x1FFFF) - scores = [(hiscores[x], hiscores[x + 1], hiscores[x + 2], hiscores[x + 3]) for x in range(0, len(hiscores), 4)] + scores = [ + (hiscores[x], hiscores[x + 1], hiscores[x + 2], hiscores[x + 3]) + for x in range(0, len(hiscores), 4) + ] - return {'medals': medals, 'scores': scores} + return {"medals": medals, "scores": scores} else: - raise Exception(f'Unrecognized message type \'{msg_type}\'') + raise Exception(f"Unrecognized message type '{msg_type}'") def verify_playerdata_set(self, ref_id: str, scores: List[Dict[str, Any]]) -> None: call = self.call_node() # Construct node - playerdata = Node.void('playerdata') + playerdata = Node.void("playerdata") call.add_child(playerdata) - playerdata.set_attribute('method', 'set') - playerdata.set_attribute('ref_id', ref_id) - playerdata.set_attribute('shop_name', '') + playerdata.set_attribute("method", "set") + playerdata.set_attribute("ref_id", ref_id) + playerdata.set_attribute("shop_name", "") # Add required children - playerdata.add_child(Node.s16('chara', 1543)) + playerdata.add_child(Node.s16("chara", 1543)) # Add requested scores for score in scores: - stage = Node.void('stage') + stage = Node.void("stage") playerdata.add_child(stage) - stage.add_child(Node.s16('no', score['id'])) - stage.add_child(Node.u8('sheet', score['chart'])) - stage.add_child(Node.u16('n_data', (score['medal'] << (4 * score['chart'])))) - stage.add_child(Node.s32('score', score['score'])) + stage.add_child(Node.s16("no", score["id"])) + stage.add_child(Node.u8("sheet", score["chart"])) + stage.add_child( + Node.u16("n_data", (score["medal"] << (4 * score["chart"]))) + ) + stage.add_child(Node.s32("score", score["score"])) # Swap with server - resp = self.exchange('pnm20/playerdata', call) + resp = self.exchange("pnm20/playerdata", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/playerdata/name") - name = resp.child('playerdata').child('name').value + name = resp.child("playerdata").child("name").value if name != self.NAME: - raise Exception(f'Invalid name \'{name}\' returned for Ref ID \'{ref_id}\'') + raise Exception(f"Invalid name '{name}' returned for Ref ID '{ref_id}'") def verify_playerdata_new(self, ref_id: str) -> None: call = self.call_node() # Construct node - playerdata = Node.void('playerdata') + playerdata = Node.void("playerdata") call.add_child(playerdata) - playerdata.set_attribute('method', 'new') + playerdata.set_attribute("method", "new") - playerdata.add_child(Node.string('ref_id', ref_id)) - playerdata.add_child(Node.string('name', self.NAME)) - playerdata.add_child(Node.string('shop_name', '')) - playerdata.add_child(Node.s8('pref', 51)) - playerdata.add_child(Node.s8('gakuen', 2)) - playerdata.add_child(Node.s8('zoo', 1)) - playerdata.add_child(Node.s8('floor_infection', 1)) - playerdata.add_child(Node.s8('triple_journey', 1)) - playerdata.add_child(Node.s8('baseball', 1)) + playerdata.add_child(Node.string("ref_id", ref_id)) + playerdata.add_child(Node.string("name", self.NAME)) + playerdata.add_child(Node.string("shop_name", "")) + playerdata.add_child(Node.s8("pref", 51)) + playerdata.add_child(Node.s8("gakuen", 2)) + playerdata.add_child(Node.s8("zoo", 1)) + playerdata.add_child(Node.s8("floor_infection", 1)) + playerdata.add_child(Node.s8("triple_journey", 1)) + playerdata.add_child(Node.s8("baseball", 1)) # Swap with server - resp = self.exchange('pnm20/playerdata', call) + resp = self.exchange("pnm20/playerdata", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/playerdata/base") @@ -224,20 +241,20 @@ class PopnMusicSunnyParkClient(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -256,68 +273,78 @@ class PopnMusicSunnyParkClient(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') - self.verify_playerdata_get(ref_id, msg_type='new') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") + self.verify_playerdata_get(ref_id, msg_type="new") self.verify_playerdata_new(ref_id) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") if cardid is None: # Verify score handling - scores = self.verify_playerdata_get(ref_id, msg_type='query') + scores = self.verify_playerdata_get(ref_id, msg_type="query") if scores is None: - raise Exception('Expected to get scores back, didn\'t get anything!') - for medal in scores['medals']: + raise Exception("Expected to get scores back, didn't get anything!") + for medal in scores["medals"]: for i in range(4): if medal[i] != 0: - raise Exception('Got nonzero medals count on a new card!') - for score in scores['scores']: + raise Exception("Got nonzero medals count on a new card!") + for score in scores["scores"]: for i in range(4): if score[i] != 0: - raise Exception('Got nonzero scores count on a new card!') + raise Exception("Got nonzero scores count on a new card!") for phase in [1, 2]: if phase == 1: dummyscores = [ # An okay score on a chart { - 'id': 987, - 'chart': 2, - 'medal': 5, - 'score': 76543, + "id": 987, + "chart": 2, + "medal": 5, + "score": 76543, }, # A good score on an easier chart of the same song { - 'id': 987, - 'chart': 0, - 'medal': 6, - 'score': 99999, + "id": 987, + "chart": 0, + "medal": 6, + "score": 99999, }, # A bad score on a hard chart { - 'id': 741, - 'chart': 3, - 'medal': 2, - 'score': 45000, + "id": 741, + "chart": 3, + "medal": 2, + "score": 45000, }, # A terrible score on an easy chart { - 'id': 742, - 'chart': 1, - 'medal': 2, - 'score': 1, + "id": 742, + "chart": 1, + "medal": 2, + "score": 1, }, ] # Random score to add in @@ -325,51 +352,57 @@ class PopnMusicSunnyParkClient(BaseClient): chartid = random.randint(0, 3) score = random.randint(0, 100000) medal = random.choice([1, 2, 3, 5, 6, 7, 9, 10, 11, 15]) - dummyscores.append({ - 'id': songid, - 'chart': chartid, - 'medal': medal, - 'score': score, - }) + dummyscores.append( + { + "id": songid, + "chart": chartid, + "medal": medal, + "score": score, + } + ) if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 987, - 'chart': 2, - 'medal': 5, - 'score': 98765, + "id": 987, + "chart": 2, + "medal": 5, + "score": 98765, }, # A worse score on another same chart { - 'id': 987, - 'chart': 0, - 'medal': 3, - 'score': 12345, - 'expected_score': 99999, - 'expected_medal': 6, + "id": 987, + "chart": 0, + "medal": 3, + "score": 12345, + "expected_score": 99999, + "expected_medal": 6, }, ] self.verify_playerdata_set(ref_id, dummyscores) - scores = self.verify_playerdata_get(ref_id, msg_type='query') + scores = self.verify_playerdata_get(ref_id, msg_type="query") for score in dummyscores: - newscore = scores['scores'][score['id']][score['chart']] - newmedal = scores['medals'][score['id']][score['chart']] + newscore = scores["scores"][score["id"]][score["chart"]] + newmedal = scores["medals"][score["id"]][score["chart"]] - if 'expected_score' in score: - expected_score = score['expected_score'] + if "expected_score" in score: + expected_score = score["expected_score"] else: - expected_score = score['score'] - if 'expected_medal' in score: - expected_medal = score['expected_medal'] + expected_score = score["score"] + if "expected_medal" in score: + expected_medal = score["expected_medal"] else: - expected_medal = score['medal'] + expected_medal = score["medal"] if newscore != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{newscore}\'') + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{newscore}\'' + ) if newmedal != expected_medal: - raise Exception(f'Expected a medal of \'{expected_medal}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got medal \'{newmedal}\'') + raise Exception( + f'Expected a medal of \'{expected_medal}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got medal \'{newmedal}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) diff --git a/bemani/client/popn/tunestreet.py b/bemani/client/popn/tunestreet.py index ab7fd90..b3c03c7 100644 --- a/bemani/client/popn/tunestreet.py +++ b/bemani/client/popn/tunestreet.py @@ -7,31 +7,31 @@ from bemani.protocol import Node class PopnMusicTuneStreetClient(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_game_active(self) -> None: call = self.call_node() # Construct node - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'active') + game.set_attribute("method", "active") # Add what Pop'n 19 would add after full unlock - game.set_attribute('eacoin_price', '200,260,200,200,10') - game.set_attribute('event', '0') - game.set_attribute('shop_name_facility', '.') - game.set_attribute('name', '') - game.set_attribute('location_id', 'JP-1') - game.set_attribute('shop_addr', '127.0.0.1 10000') - game.set_attribute('card_use', '0') - game.set_attribute('testmode', '0,1,1,4,0,-1,2,1,2,100,0,0,80513,0,92510336') - game.set_attribute('eacoin_available', '1') - game.set_attribute('pref', '0') - game.set_attribute('shop_name', '') + game.set_attribute("eacoin_price", "200,260,200,200,10") + game.set_attribute("event", "0") + game.set_attribute("shop_name_facility", ".") + game.set_attribute("name", "") + game.set_attribute("location_id", "JP-1") + game.set_attribute("shop_addr", "127.0.0.1 10000") + game.set_attribute("card_use", "0") + game.set_attribute("testmode", "0,1,1,4,0,-1,2,1,2,100,0,0,80513,0,92510336") + game.set_attribute("eacoin_available", "1") + game.set_attribute("pref", "0") + game.set_attribute("shop_name", "") # Swap with server - resp = self.exchange('pnm/game', call) + resp = self.exchange("pnm/game", call) # Verify that response is correct self.assert_path(resp, "response/game/@status") @@ -40,71 +40,85 @@ class PopnMusicTuneStreetClient(BaseClient): call = self.call_node() # Construct node - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'get') + game.set_attribute("method", "get") # Swap with server - resp = self.exchange('pnm/game', call) + resp = self.exchange("pnm/game", call) # Verify that response is correct self.assert_path(resp, "response/game") for name in [ - 'game_phase', - 'boss_battle_point', - 'boss_diff', - 'card_phase', - 'event_phase', - 'gfdm_phase', - 'ir_phase', - 'jubeat_phase', - 'local_matching_enable', - 'matching_sec', - 'netvs_phase', + "game_phase", + "boss_battle_point", + "boss_diff", + "card_phase", + "event_phase", + "gfdm_phase", + "ir_phase", + "jubeat_phase", + "local_matching_enable", + "matching_sec", + "netvs_phase", ]: - if name not in resp.child('game').attributes: - raise Exception(f'Missing attribute \'{name}\' in response!') + if name not in resp.child("game").attributes: + raise Exception(f"Missing attribute '{name}' in response!") - def verify_playerdata_get(self, ref_id: str, msg_type: str) -> Optional[Dict[str, Any]]: + def verify_playerdata_get( + self, ref_id: str, msg_type: str + ) -> Optional[Dict[str, Any]]: call = self.call_node() # Construct node - playerdata = Node.void('playerdata') + playerdata = Node.void("playerdata") call.add_child(playerdata) - playerdata.set_attribute('method', 'get') - playerdata.set_attribute('pref', '50') - playerdata.set_attribute('shop_name', '') - playerdata.set_attribute('ref_id', ref_id) + playerdata.set_attribute("method", "get") + playerdata.set_attribute("pref", "50") + playerdata.set_attribute("shop_name", "") + playerdata.set_attribute("ref_id", ref_id) - if msg_type == 'new': - playerdata.set_attribute('model', self.config['old_profile_model'].split(':')[0]) + if msg_type == "new": + playerdata.set_attribute( + "model", self.config["old_profile_model"].split(":")[0] + ) # Swap with server - resp = self.exchange('pnm/playerdata', call) + resp = self.exchange("pnm/playerdata", call) - if msg_type == 'new': + if msg_type == "new": # Verify that response is correct self.assert_path(resp, "response/playerdata/@status") - status = int(resp.child('playerdata').attribute('status')) + status = int(resp.child("playerdata").attribute("status")) if status != 109: - raise Exception(f'Reference ID \'{ref_id}\' returned invalid status \'{status}\'') + raise Exception( + f"Reference ID '{ref_id}' returned invalid status '{status}'" + ) # No score data return None - elif msg_type == 'query': + elif msg_type == "query": # Verify that the response is correct self.assert_path(resp, "response/playerdata/b") self.assert_path(resp, "response/playerdata/hiscore") self.assert_path(resp, "response/playerdata/town") - name = resp.child('playerdata').child('b').value[0:12].decode('SHIFT_JIS').replace("\x00", "") + name = ( + resp.child("playerdata") + .child("b") + .value[0:12] + .decode("SHIFT_JIS") + .replace("\x00", "") + ) if name != self.NAME: - raise Exception(f'Invalid name \'{name}\' returned for Ref ID \'{ref_id}\'') + raise Exception(f"Invalid name '{name}' returned for Ref ID '{ref_id}'") - medals = resp.child('playerdata').child('b').value[108:] - medals = [(medals[x] + (medals[x + 1] << 8)) for x in range(0, len(medals), 2)] + medals = resp.child("playerdata").child("b").value[108:] + medals = [ + (medals[x] + (medals[x + 1] << 8)) for x in range(0, len(medals), 2) + ] # Extract and return score data def transform_medals(medal: int) -> Tuple[int, int, int, int]: @@ -117,7 +131,7 @@ class PopnMusicTuneStreetClient(BaseClient): medals = [transform_medals(medal) for medal in medals] - hiscore = resp.child('playerdata').child('hiscore').value + hiscore = resp.child("playerdata").child("hiscore").value hiscores = [] for i in range(0, len(hiscore) * 8, 17): byte_offset = int(i / 8) @@ -140,77 +154,83 @@ class PopnMusicTuneStreetClient(BaseClient): hiscores[x + 2], hiscores[x + 0], hiscores[x + 3], - ) for x in range(0, len(hiscores), 7) + ) + for x in range(0, len(hiscores), 7) ] - return {'medals': medals, 'scores': scores} + return {"medals": medals, "scores": scores} else: - raise Exception(f'Unrecognized message type \'{msg_type}\'') + raise Exception(f"Unrecognized message type '{msg_type}'") def verify_playerdata_set(self, ref_id: str, scores: List[Dict[str, Any]]) -> None: call = self.call_node() # Construct node - playerdata = Node.void('playerdata') + playerdata = Node.void("playerdata") call.add_child(playerdata) - playerdata.set_attribute('method', 'set') - playerdata.set_attribute('last_play_flag', '0') - playerdata.set_attribute('play_mode', '3') - playerdata.set_attribute('music_num', '550') - playerdata.set_attribute('category_num', '14') - playerdata.set_attribute('norma_point', '0') - playerdata.set_attribute('medal_and_friend', '0') - playerdata.set_attribute('option', '131072') - playerdata.set_attribute('color_3p_flg', '0,0') - playerdata.set_attribute('sheet_num', '1') - playerdata.set_attribute('skin_sd_bgm', '0') - playerdata.set_attribute('read_news_no_max', '0') - playerdata.set_attribute('shop_name', '') - playerdata.set_attribute('skin_sd_se', '0') - playerdata.set_attribute('start_type', '2') - playerdata.set_attribute('skin_tex_note', '0') - playerdata.set_attribute('ref_id', ref_id) - playerdata.set_attribute('chara_num', '12') - playerdata.set_attribute('jubeat_collabo', '0') - playerdata.set_attribute('pref', '50') - playerdata.set_attribute('skin_tex_cmn', '0') + playerdata.set_attribute("method", "set") + playerdata.set_attribute("last_play_flag", "0") + playerdata.set_attribute("play_mode", "3") + playerdata.set_attribute("music_num", "550") + playerdata.set_attribute("category_num", "14") + playerdata.set_attribute("norma_point", "0") + playerdata.set_attribute("medal_and_friend", "0") + playerdata.set_attribute("option", "131072") + playerdata.set_attribute("color_3p_flg", "0,0") + playerdata.set_attribute("sheet_num", "1") + playerdata.set_attribute("skin_sd_bgm", "0") + playerdata.set_attribute("read_news_no_max", "0") + playerdata.set_attribute("shop_name", "") + playerdata.set_attribute("skin_sd_se", "0") + playerdata.set_attribute("start_type", "2") + playerdata.set_attribute("skin_tex_note", "0") + playerdata.set_attribute("ref_id", ref_id) + playerdata.set_attribute("chara_num", "12") + playerdata.set_attribute("jubeat_collabo", "0") + playerdata.set_attribute("pref", "50") + playerdata.set_attribute("skin_tex_cmn", "0") # Add requested scores for score in scores: - music = Node.void('music') + music = Node.void("music") playerdata.add_child(music) - music.set_attribute('norma_r', '0') - music.set_attribute('data', str({ - 0: ((score['medal'] & 0x3) << 0) | 0x0800, - 1: ((score['medal'] & 0x3) << 2) | 0x1000, - 2: ((score['medal'] & 0x3) << 4) | 0x2000, - 3: ((score['medal'] & 0x3) << 6) | 0x4000, - }[score['chart']])) - music.set_attribute('select_count', '1') - music.set_attribute('music_num', str(score['id'])) - music.set_attribute('norma_l', '0') - music.set_attribute('score', str(score['score'])) - music.set_attribute('sheet_num', str(score['chart'])) + music.set_attribute("norma_r", "0") + music.set_attribute( + "data", + str( + { + 0: ((score["medal"] & 0x3) << 0) | 0x0800, + 1: ((score["medal"] & 0x3) << 2) | 0x1000, + 2: ((score["medal"] & 0x3) << 4) | 0x2000, + 3: ((score["medal"] & 0x3) << 6) | 0x4000, + }[score["chart"]] + ), + ) + music.set_attribute("select_count", "1") + music.set_attribute("music_num", str(score["id"])) + music.set_attribute("norma_l", "0") + music.set_attribute("score", str(score["score"])) + music.set_attribute("sheet_num", str(score["chart"])) # Swap with server - self.exchange('pnm/playerdata', call) + self.exchange("pnm/playerdata", call) def verify_playerdata_new(self, card_id: str, ref_id: str) -> None: call = self.call_node() # Construct node - playerdata = Node.void('playerdata') + playerdata = Node.void("playerdata") call.add_child(playerdata) - playerdata.set_attribute('method', 'new') - playerdata.set_attribute('ref_id', ref_id) - playerdata.set_attribute('card_id', card_id) - playerdata.set_attribute('name', self.NAME) - playerdata.set_attribute('shop_name', '') - playerdata.set_attribute('pref', '50') + playerdata.set_attribute("method", "new") + playerdata.set_attribute("ref_id", ref_id) + playerdata.set_attribute("card_id", card_id) + playerdata.set_attribute("name", self.NAME) + playerdata.set_attribute("shop_name", "") + playerdata.set_attribute("pref", "50") # Swap with server - resp = self.exchange('pnm/playerdata', call) + resp = self.exchange("pnm/playerdata", call) # Verify nodes that cause crashes if they don't exist self.assert_path(resp, "response/playerdata/b") @@ -221,20 +241,20 @@ class PopnMusicTuneStreetClient(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -253,68 +273,78 @@ class PopnMusicTuneStreetClient(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') - self.verify_playerdata_get(ref_id, msg_type='new') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") + self.verify_playerdata_get(ref_id, msg_type="new") self.verify_playerdata_new(card, ref_id) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") if cardid is None: # Verify score handling - scores = self.verify_playerdata_get(ref_id, msg_type='query') + scores = self.verify_playerdata_get(ref_id, msg_type="query") if scores is None: - raise Exception('Expected to get scores back, didn\'t get anything!') - for medal in scores['medals']: + raise Exception("Expected to get scores back, didn't get anything!") + for medal in scores["medals"]: for i in range(4): if medal[i] != 0: - raise Exception('Got nonzero medals count on a new card!') - for score in scores['scores']: + raise Exception("Got nonzero medals count on a new card!") + for score in scores["scores"]: for i in range(4): if score[i] != 0: - raise Exception('Got nonzero scores count on a new card!') + raise Exception("Got nonzero scores count on a new card!") for phase in [1, 2]: if phase == 1: dummyscores = [ # An okay score on a chart { - 'id': 987, - 'chart': 2, - 'medal': 2, - 'score': 76543, + "id": 987, + "chart": 2, + "medal": 2, + "score": 76543, }, # A good score on an easier chart of the same song { - 'id': 987, - 'chart': 0, - 'medal': 3, - 'score': 99999, + "id": 987, + "chart": 0, + "medal": 3, + "score": 99999, }, # A bad score on a hard chart { - 'id': 741, - 'chart': 3, - 'medal': 1, - 'score': 45000, + "id": 741, + "chart": 3, + "medal": 1, + "score": 45000, }, # A terrible score on an easy chart { - 'id': 742, - 'chart': 1, - 'medal': 0, - 'score': 1, + "id": 742, + "chart": 1, + "medal": 0, + "score": 1, }, ] # Random score to add in @@ -322,51 +352,57 @@ class PopnMusicTuneStreetClient(BaseClient): chartid = random.randint(0, 3) score = random.randint(0, 100000) medal = random.choice([0, 1, 2, 3]) - dummyscores.append({ - 'id': songid, - 'chart': chartid, - 'medal': medal, - 'score': score, - }) + dummyscores.append( + { + "id": songid, + "chart": chartid, + "medal": medal, + "score": score, + } + ) if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 987, - 'chart': 2, - 'medal': 3, - 'score': 98765, + "id": 987, + "chart": 2, + "medal": 3, + "score": 98765, }, # A worse score on another same chart { - 'id': 987, - 'chart': 0, - 'medal': 2, - 'score': 12345, - 'expected_score': 99999, - 'expected_medal': 3, + "id": 987, + "chart": 0, + "medal": 2, + "score": 12345, + "expected_score": 99999, + "expected_medal": 3, }, ] self.verify_playerdata_set(ref_id, dummyscores) - scores = self.verify_playerdata_get(ref_id, msg_type='query') + scores = self.verify_playerdata_get(ref_id, msg_type="query") for score in dummyscores: - newscore = scores['scores'][score['id']][score['chart']] - newmedal = scores['medals'][score['id']][score['chart']] + newscore = scores["scores"][score["id"]][score["chart"]] + newmedal = scores["medals"][score["id"]][score["chart"]] - if 'expected_score' in score: - expected_score = score['expected_score'] + if "expected_score" in score: + expected_score = score["expected_score"] else: - expected_score = score['score'] - if 'expected_medal' in score: - expected_medal = score['expected_medal'] + expected_score = score["score"] + if "expected_medal" in score: + expected_medal = score["expected_medal"] else: - expected_medal = score['medal'] + expected_medal = score["medal"] if newscore != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{newscore}\'') + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{newscore}\'' + ) if newmedal != expected_medal: - raise Exception(f'Expected a medal of \'{expected_medal}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got medal \'{newmedal}\'') + raise Exception( + f'Expected a medal of \'{expected_medal}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got medal \'{newmedal}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) diff --git a/bemani/client/popn/usaneko.py b/bemani/client/popn/usaneko.py index 86d2def..5be00db 100644 --- a/bemani/client/popn/usaneko.py +++ b/bemani/client/popn/usaneko.py @@ -7,33 +7,33 @@ from bemani.protocol import Node class PopnMusicUsaNekoClient(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_pcb24_boot(self, loc: str) -> None: call = self.call_node() # Construct node - pcb24 = Node.void('pcb24') + pcb24 = Node.void("pcb24") call.add_child(pcb24) - pcb24.set_attribute('method', 'boot') - pcb24.add_child(Node.string('loc_id', loc)) - pcb24.add_child(Node.u8('loc_type', 0)) - pcb24.add_child(Node.string('loc_name', '')) - pcb24.add_child(Node.string('country', 'US')) - pcb24.add_child(Node.string('region', '.')) - pcb24.add_child(Node.s16('pref', 51)) - pcb24.add_child(Node.string('customer', '')) - pcb24.add_child(Node.string('company', '')) - pcb24.add_child(Node.ipv4('gip', '127.0.0.1')) - pcb24.add_child(Node.u16('gp', 10011)) - pcb24.add_child(Node.string('rom_number', 'M39-JB-G01')) - pcb24.add_child(Node.u64('c_drive', 10028228608)) - pcb24.add_child(Node.u64('d_drive', 47945170944)) - pcb24.add_child(Node.u64('e_drive', 10394677248)) - pcb24.add_child(Node.string('etc', '')) + pcb24.set_attribute("method", "boot") + pcb24.add_child(Node.string("loc_id", loc)) + pcb24.add_child(Node.u8("loc_type", 0)) + pcb24.add_child(Node.string("loc_name", "")) + pcb24.add_child(Node.string("country", "US")) + pcb24.add_child(Node.string("region", ".")) + pcb24.add_child(Node.s16("pref", 51)) + pcb24.add_child(Node.string("customer", "")) + pcb24.add_child(Node.string("company", "")) + pcb24.add_child(Node.ipv4("gip", "127.0.0.1")) + pcb24.add_child(Node.u16("gp", 10011)) + pcb24.add_child(Node.string("rom_number", "M39-JB-G01")) + pcb24.add_child(Node.u64("c_drive", 10028228608)) + pcb24.add_child(Node.u64("d_drive", 47945170944)) + pcb24.add_child(Node.u64("e_drive", 10394677248)) + pcb24.add_child(Node.string("etc", "")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/pcb24/@status") @@ -59,29 +59,29 @@ class PopnMusicUsaNekoClient(BaseClient): call = self.call_node() # Construct node - info24 = Node.void('info24') + info24 = Node.void("info24") call.add_child(info24) - info24.set_attribute('loc_id', loc) - info24.set_attribute('method', 'common') + info24.set_attribute("loc_id", loc) + info24.set_attribute("method", "common") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct - self.__verify_common('info24', resp) + self.__verify_common("info24", resp) def verify_lobby24_getlist(self, loc: str) -> None: call = self.call_node() # Construct node - lobby24 = Node.void('lobby24') + lobby24 = Node.void("lobby24") call.add_child(lobby24) - lobby24.set_attribute('method', 'getList') - lobby24.add_child(Node.string('location_id', loc)) - lobby24.add_child(Node.u8('net_version', 63)) + lobby24.set_attribute("method", "getList") + lobby24.add_child(Node.string("location_id", loc)) + lobby24.add_child(Node.u8("net_version", 63)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/lobby24/@status") @@ -136,89 +136,99 @@ class PopnMusicUsaNekoClient(BaseClient): self.assert_path(resp, "response/player24/stamp/stamp_id") self.assert_path(resp, "response/player24/stamp/cnt") - def verify_player24_read(self, ref_id: str, msg_type: str) -> Dict[str, Dict[int, Dict[str, int]]]: + def verify_player24_read( + self, ref_id: str, msg_type: str + ) -> Dict[str, Dict[int, Dict[str, int]]]: call = self.call_node() # Construct node - player24 = Node.void('player24') + player24 = Node.void("player24") call.add_child(player24) - player24.set_attribute('method', 'read') + player24.set_attribute("method", "read") - player24.add_child(Node.string('ref_id', ref_id)) - player24.add_child(Node.s8('pref', 51)) + player24.add_child(Node.string("ref_id", ref_id)) + player24.add_child(Node.s8("pref", 51)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) - if msg_type == 'new': + if msg_type == "new": # Verify that response is correct self.assert_path(resp, "response/player24/result") - status = resp.child_value('player24/result') + status = resp.child_value("player24/result") if status != 2: - raise Exception(f'Reference ID \'{ref_id}\' returned invalid status \'{status}\'') + raise Exception( + f"Reference ID '{ref_id}' returned invalid status '{status}'" + ) return { - 'items': {}, - 'characters': {}, - 'points': {}, + "items": {}, + "characters": {}, + "points": {}, } - elif msg_type == 'query': + elif msg_type == "query": # Verify that the response is correct self.__verify_profile(resp) self.assert_path(resp, "response/player24/result") - status = resp.child_value('player24/result') + status = resp.child_value("player24/result") if status != 0: - raise Exception(f'Reference ID \'{ref_id}\' returned invalid status \'{status}\'') - name = resp.child_value('player24/account/name') + raise Exception( + f"Reference ID '{ref_id}' returned invalid status '{status}'" + ) + name = resp.child_value("player24/account/name") if name != self.NAME: - raise Exception(f'Invalid name \'{name}\' returned for Ref ID \'{ref_id}\'') + raise Exception(f"Invalid name '{name}' returned for Ref ID '{ref_id}'") # Medals and items items: Dict[int, Dict[str, int]] = {} charas: Dict[int, Dict[str, int]] = {} courses: Dict[int, Dict[str, int]] = {} - for obj in resp.child('player24').children: - if obj.name == 'item': - items[obj.child_value('id')] = { - 'type': obj.child_value('type'), - 'param': obj.child_value('param'), + for obj in resp.child("player24").children: + if obj.name == "item": + items[obj.child_value("id")] = { + "type": obj.child_value("type"), + "param": obj.child_value("param"), } - elif obj.name == 'chara_param': - charas[obj.child_value('chara_id')] = { - 'friendship': obj.child_value('friendship'), + elif obj.name == "chara_param": + charas[obj.child_value("chara_id")] = { + "friendship": obj.child_value("friendship"), } - elif obj.name == 'course_data': - courses[obj.child_value('course_id')] = { - 'clear_type': obj.child_value('clear_type'), - 'clear_rank': obj.child_value('clear_rank'), - 'total_score': obj.child_value('total_score'), - 'count': obj.child_value('update_count'), - 'sheet_num': obj.child_value('sheet_num'), + elif obj.name == "course_data": + courses[obj.child_value("course_id")] = { + "clear_type": obj.child_value("clear_type"), + "clear_rank": obj.child_value("clear_rank"), + "total_score": obj.child_value("total_score"), + "count": obj.child_value("update_count"), + "sheet_num": obj.child_value("sheet_num"), } return { - 'items': items, - 'characters': charas, - 'courses': courses, - 'points': {0: {'points': resp.child_value('player24/account/player_point')}}, + "items": items, + "characters": charas, + "courses": courses, + "points": { + 0: {"points": resp.child_value("player24/account/player_point")} + }, } else: - raise Exception(f'Unrecognized message type \'{msg_type}\'') + raise Exception(f"Unrecognized message type '{msg_type}'") - def verify_player24_read_score(self, ref_id: str) -> Dict[str, Dict[int, Dict[int, int]]]: + def verify_player24_read_score( + self, ref_id: str + ) -> Dict[str, Dict[int, Dict[int, int]]]: call = self.call_node() # Construct node - player24 = Node.void('player24') + player24 = Node.void("player24") call.add_child(player24) - player24.set_attribute('method', 'read_score') + player24.set_attribute("method", "read_score") - player24.add_child(Node.string('ref_id', ref_id)) - player24.add_child(Node.s8('pref', 51)) + player24.add_child(Node.string("ref_id", ref_id)) + player24.add_child(Node.s8("pref", 51)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify defaults self.assert_path(resp, "response/player24/@status") @@ -227,15 +237,15 @@ class PopnMusicUsaNekoClient(BaseClient): scores: Dict[int, Dict[int, int]] = {} medals: Dict[int, Dict[int, int]] = {} ranks: Dict[int, Dict[int, int]] = {} - for child in resp.child('player24').children: - if child.name != 'music': + for child in resp.child("player24").children: + if child.name != "music": continue - musicid = child.child_value('music_num') - chart = child.child_value('sheet_num') - score = child.child_value('score') - medal = child.child_value('clear_type') - rank = child.child_value('clear_rank') + musicid = child.child_value("music_num") + chart = child.child_value("sheet_num") + score = child.child_value("score") + medal = child.child_value("clear_type") + rank = child.child_value("clear_rank") if musicid not in scores: scores[musicid] = {} @@ -249,53 +259,53 @@ class PopnMusicUsaNekoClient(BaseClient): ranks[musicid][chart] = rank return { - 'scores': scores, - 'medals': medals, - 'ranks': ranks, + "scores": scores, + "medals": medals, + "ranks": ranks, } def verify_player24_start(self, ref_id: str, loc: str) -> None: call = self.call_node() # Construct node - player24 = Node.void('player24') + player24 = Node.void("player24") call.add_child(player24) - player24.set_attribute('loc_id', loc) - player24.set_attribute('ref_id', ref_id) - player24.set_attribute('method', 'start') - player24.set_attribute('start_type', '0') - pcb_card = Node.void('pcb_card') + player24.set_attribute("loc_id", loc) + player24.set_attribute("ref_id", ref_id) + player24.set_attribute("method", "start") + player24.set_attribute("start_type", "0") + pcb_card = Node.void("pcb_card") player24.add_child(pcb_card) - pcb_card.add_child(Node.s8('card_enable', 1)) - pcb_card.add_child(Node.s8('card_soldout', 0)) + pcb_card.add_child(Node.s8("card_enable", 1)) + pcb_card.add_child(Node.s8("card_soldout", 0)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct - self.__verify_common('player24', resp) + self.__verify_common("player24", resp) def verify_player24_update_ranking(self, ref_id: str, loc: str) -> None: call = self.call_node() # Construct node - player24 = Node.void('player24') + player24 = Node.void("player24") call.add_child(player24) - player24.set_attribute('method', 'update_ranking') - player24.add_child(Node.s16('pref', 51)) - player24.add_child(Node.string('location_id', loc)) - player24.add_child(Node.string('ref_id', ref_id)) - player24.add_child(Node.string('name', self.NAME)) - player24.add_child(Node.s16('chara_num', 1)) - player24.add_child(Node.s16('course_id', 12345)) - player24.add_child(Node.s32('total_score', 86000)) - player24.add_child(Node.s16('music_num', 1375)) - player24.add_child(Node.u8('sheet_num', 2)) - player24.add_child(Node.u8('clear_type', 7)) - player24.add_child(Node.u8('clear_rank', 5)) + player24.set_attribute("method", "update_ranking") + player24.add_child(Node.s16("pref", 51)) + player24.add_child(Node.string("location_id", loc)) + player24.add_child(Node.string("ref_id", ref_id)) + player24.add_child(Node.string("name", self.NAME)) + player24.add_child(Node.s16("chara_num", 1)) + player24.add_child(Node.s16("course_id", 12345)) + player24.add_child(Node.s32("total_score", 86000)) + player24.add_child(Node.s16("music_num", 1375)) + player24.add_child(Node.u8("sheet_num", 2)) + player24.add_child(Node.u8("clear_type", 7)) + player24.add_child(Node.u8("clear_rank", 5)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player24/all_ranking/name") @@ -310,13 +320,13 @@ class PopnMusicUsaNekoClient(BaseClient): call = self.call_node() # Construct node - player24 = Node.void('player24') + player24 = Node.void("player24") call.add_child(player24) - player24.set_attribute('ref_id', ref_id) - player24.set_attribute('method', 'logout') + player24.set_attribute("ref_id", ref_id) + player24.set_attribute("method", "logout") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player24/@status") @@ -324,99 +334,99 @@ class PopnMusicUsaNekoClient(BaseClient): def verify_player24_write( self, ref_id: str, - item: Optional[Dict[str, int]]=None, - character: Optional[Dict[str, int]]=None, + item: Optional[Dict[str, int]] = None, + character: Optional[Dict[str, int]] = None, ) -> None: call = self.call_node() # Construct node - player24 = Node.void('player24') + player24 = Node.void("player24") call.add_child(player24) - player24.set_attribute('method', 'write') - player24.add_child(Node.string('ref_id', ref_id)) + player24.set_attribute("method", "write") + player24.add_child(Node.string("ref_id", ref_id)) # Add required children - config = Node.void('config') + config = Node.void("config") player24.add_child(config) - config.add_child(Node.s16('chara', 1543)) + config.add_child(Node.s16("chara", 1543)) if item is not None: - itemnode = Node.void('item') + itemnode = Node.void("item") player24.add_child(itemnode) - itemnode.add_child(Node.u8('type', item['type'])) - itemnode.add_child(Node.u16('id', item['id'])) - itemnode.add_child(Node.u16('param', item['param'])) - itemnode.add_child(Node.bool('is_new', False)) - itemnode.add_child(Node.u64('get_time', 0)) + itemnode.add_child(Node.u8("type", item["type"])) + itemnode.add_child(Node.u16("id", item["id"])) + itemnode.add_child(Node.u16("param", item["param"])) + itemnode.add_child(Node.bool("is_new", False)) + itemnode.add_child(Node.u64("get_time", 0)) if character is not None: - chara_param = Node.void('chara_param') + chara_param = Node.void("chara_param") player24.add_child(chara_param) - chara_param.add_child(Node.u16('chara_id', character['id'])) - chara_param.add_child(Node.u16('friendship', character['friendship'])) + chara_param.add_child(Node.u16("chara_id", character["id"])) + chara_param.add_child(Node.u16("friendship", character["friendship"])) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/player24/@status") def verify_player24_buy(self, ref_id: str, item: Dict[str, int]) -> None: call = self.call_node() # Construct node - player24 = Node.void('player24') + player24 = Node.void("player24") call.add_child(player24) - player24.set_attribute('method', 'buy') - player24.add_child(Node.s32('play_id', 0)) - player24.add_child(Node.string('ref_id', ref_id)) - player24.add_child(Node.u16('id', item['id'])) - player24.add_child(Node.u8('type', item['type'])) - player24.add_child(Node.u16('param', item['param'])) - player24.add_child(Node.s32('lumina', item['points'])) - player24.add_child(Node.u16('price', item['price'])) + player24.set_attribute("method", "buy") + player24.add_child(Node.s32("play_id", 0)) + player24.add_child(Node.string("ref_id", ref_id)) + player24.add_child(Node.u16("id", item["id"])) + player24.add_child(Node.u8("type", item["type"])) + player24.add_child(Node.u16("param", item["param"])) + player24.add_child(Node.s32("lumina", item["points"])) + player24.add_child(Node.u16("price", item["price"])) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/player24/@status") def verify_player24_write_music(self, ref_id: str, score: Dict[str, Any]) -> None: call = self.call_node() # Construct node - player24 = Node.void('player24') + player24 = Node.void("player24") call.add_child(player24) - player24.set_attribute('method', 'write_music') - player24.add_child(Node.string('ref_id', ref_id)) - player24.add_child(Node.string('data_id', ref_id)) - player24.add_child(Node.string('name', self.NAME)) - player24.add_child(Node.u8('stage', 0)) - player24.add_child(Node.s16('music_num', score['id'])) - player24.add_child(Node.u8('sheet_num', score['chart'])) - player24.add_child(Node.u8('clear_type', score['medal'])) - player24.add_child(Node.s32('score', score['score'])) - player24.add_child(Node.s16('combo', 0)) - player24.add_child(Node.s16('cool', 0)) - player24.add_child(Node.s16('great', 0)) - player24.add_child(Node.s16('good', 0)) - player24.add_child(Node.s16('bad', 0)) + player24.set_attribute("method", "write_music") + player24.add_child(Node.string("ref_id", ref_id)) + player24.add_child(Node.string("data_id", ref_id)) + player24.add_child(Node.string("name", self.NAME)) + player24.add_child(Node.u8("stage", 0)) + player24.add_child(Node.s16("music_num", score["id"])) + player24.add_child(Node.u8("sheet_num", score["chart"])) + player24.add_child(Node.u8("clear_type", score["medal"])) + player24.add_child(Node.s32("score", score["score"])) + player24.add_child(Node.s16("combo", 0)) + player24.add_child(Node.s16("cool", 0)) + player24.add_child(Node.s16("great", 0)) + player24.add_child(Node.s16("good", 0)) + player24.add_child(Node.s16("bad", 0)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) self.assert_path(resp, "response/player24/@status") def verify_player24_new(self, ref_id: str) -> None: call = self.call_node() # Construct node - player24 = Node.void('player24') + player24 = Node.void("player24") call.add_child(player24) - player24.set_attribute('method', 'new') + player24.set_attribute("method", "new") - player24.add_child(Node.string('ref_id', ref_id)) - player24.add_child(Node.string('name', self.NAME)) - player24.add_child(Node.s8('pref', 51)) + player24.add_child(Node.string("ref_id", ref_id)) + player24.add_child(Node.string("name", self.NAME)) + player24.add_child(Node.s8("pref", 51)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify nodes self.__verify_profile(resp) @@ -425,20 +435,20 @@ class PopnMusicUsaNekoClient(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -458,126 +468,153 @@ class PopnMusicUsaNekoClient(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') - self.verify_player24_read(ref_id, msg_type='new') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") + self.verify_player24_read(ref_id, msg_type="new") self.verify_player24_new(ref_id) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Verify proper handling of basic stuff - self.verify_player24_read(ref_id, msg_type='query') + self.verify_player24_read(ref_id, msg_type="query") self.verify_player24_start(ref_id, location) self.verify_player24_write(ref_id) self.verify_player24_logout(ref_id) if cardid is None: # Verify unlocks/story mode work - unlocks = self.verify_player24_read(ref_id, msg_type='query') - for item in unlocks['items']: + unlocks = self.verify_player24_read(ref_id, msg_type="query") + for item in unlocks["items"]: if item in [1592, 1608]: # Song unlocks after one play continue - raise Exception('Got nonzero items count on a new card!') - for _ in unlocks['characters']: - raise Exception('Got nonzero characters count on a new card!') - for _ in unlocks['courses']: - raise Exception('Got nonzero course count on a new card!') - if unlocks['points'][0]['points'] != 300: - raise Exception('Got wrong default value for points on a new card!') + raise Exception("Got nonzero items count on a new card!") + for _ in unlocks["characters"]: + raise Exception("Got nonzero characters count on a new card!") + for _ in unlocks["courses"]: + raise Exception("Got nonzero course count on a new card!") + if unlocks["points"][0]["points"] != 300: + raise Exception("Got wrong default value for points on a new card!") - self.verify_player24_write(ref_id, item={'id': 4, 'type': 2, 'param': 69}) - unlocks = self.verify_player24_read(ref_id, msg_type='query') - if 4 not in unlocks['items']: - raise Exception('Expecting to see item ID 4 in items!') - if unlocks['items'][4]['type'] != 2: - raise Exception('Expecting to see item ID 4 to have type 2 in items!') - if unlocks['items'][4]['param'] != 69: - raise Exception('Expecting to see item ID 4 to have param 69 in items!') + self.verify_player24_write(ref_id, item={"id": 4, "type": 2, "param": 69}) + unlocks = self.verify_player24_read(ref_id, msg_type="query") + if 4 not in unlocks["items"]: + raise Exception("Expecting to see item ID 4 in items!") + if unlocks["items"][4]["type"] != 2: + raise Exception("Expecting to see item ID 4 to have type 2 in items!") + if unlocks["items"][4]["param"] != 69: + raise Exception("Expecting to see item ID 4 to have param 69 in items!") - self.verify_player24_write(ref_id, character={'id': 5, 'friendship': 420}) - unlocks = self.verify_player24_read(ref_id, msg_type='query') - if 5 not in unlocks['characters']: - raise Exception('Expecting to see chara ID 5 in characters!') - if unlocks['characters'][5]['friendship'] != 420: - raise Exception('Expecting to see chara ID 5 to have type 2 in characters!') + self.verify_player24_write(ref_id, character={"id": 5, "friendship": 420}) + unlocks = self.verify_player24_read(ref_id, msg_type="query") + if 5 not in unlocks["characters"]: + raise Exception("Expecting to see chara ID 5 in characters!") + if unlocks["characters"][5]["friendship"] != 420: + raise Exception( + "Expecting to see chara ID 5 to have type 2 in characters!" + ) # Verify purchases work - self.verify_player24_buy(ref_id, item={'id': 6, 'type': 3, 'param': 8, 'points': 400, 'price': 250}) - unlocks = self.verify_player24_read(ref_id, msg_type='query') - if 6 not in unlocks['items']: - raise Exception('Expecting to see item ID 6 in items!') - if unlocks['items'][6]['type'] != 3: - raise Exception('Expecting to see item ID 6 to have type 3 in items!') - if unlocks['items'][6]['param'] != 8: - raise Exception('Expecting to see item ID 6 to have param 8 in items!') - if unlocks['points'][0]['points'] != 150: - raise Exception(f'Got wrong value for points {unlocks["points"][0]["points"]} after purchase!') + self.verify_player24_buy( + ref_id, + item={"id": 6, "type": 3, "param": 8, "points": 400, "price": 250}, + ) + unlocks = self.verify_player24_read(ref_id, msg_type="query") + if 6 not in unlocks["items"]: + raise Exception("Expecting to see item ID 6 in items!") + if unlocks["items"][6]["type"] != 3: + raise Exception("Expecting to see item ID 6 to have type 3 in items!") + if unlocks["items"][6]["param"] != 8: + raise Exception("Expecting to see item ID 6 to have param 8 in items!") + if unlocks["points"][0]["points"] != 150: + raise Exception( + f'Got wrong value for points {unlocks["points"][0]["points"]} after purchase!' + ) # Verify course handling self.verify_player24_update_ranking(ref_id, location) - unlocks = self.verify_player24_read(ref_id, msg_type='query') - if 12345 not in unlocks['courses']: - raise Exception('Expecting to see course ID 12345 in courses!') - if unlocks['courses'][12345]['clear_type'] != 7: - raise Exception('Expecting to see item ID 12345 to have clear_type 7 in courses!') - if unlocks['courses'][12345]['clear_rank'] != 5: - raise Exception('Expecting to see item ID 12345 to have clear_rank 5 in courses!') - if unlocks['courses'][12345]['total_score'] != 86000: - raise Exception('Expecting to see item ID 12345 to have total_score 86000 in courses!') - if unlocks['courses'][12345]['count'] != 1: - raise Exception('Expecting to see item ID 12345 to have count 1 in courses!') - if unlocks['courses'][12345]['sheet_num'] != 2: - raise Exception('Expecting to see item ID 12345 to have sheet_num 2 in courses!') + unlocks = self.verify_player24_read(ref_id, msg_type="query") + if 12345 not in unlocks["courses"]: + raise Exception("Expecting to see course ID 12345 in courses!") + if unlocks["courses"][12345]["clear_type"] != 7: + raise Exception( + "Expecting to see item ID 12345 to have clear_type 7 in courses!" + ) + if unlocks["courses"][12345]["clear_rank"] != 5: + raise Exception( + "Expecting to see item ID 12345 to have clear_rank 5 in courses!" + ) + if unlocks["courses"][12345]["total_score"] != 86000: + raise Exception( + "Expecting to see item ID 12345 to have total_score 86000 in courses!" + ) + if unlocks["courses"][12345]["count"] != 1: + raise Exception( + "Expecting to see item ID 12345 to have count 1 in courses!" + ) + if unlocks["courses"][12345]["sheet_num"] != 2: + raise Exception( + "Expecting to see item ID 12345 to have sheet_num 2 in courses!" + ) # Verify score handling scores = self.verify_player24_read_score(ref_id) - for _ in scores['medals']: - raise Exception('Got nonzero medals count on a new card!') - for _ in scores['scores']: - raise Exception('Got nonzero scores count on a new card!') + for _ in scores["medals"]: + raise Exception("Got nonzero medals count on a new card!") + for _ in scores["scores"]: + raise Exception("Got nonzero scores count on a new card!") for phase in [1, 2]: if phase == 1: dummyscores = [ # An okay score on a chart { - 'id': 987, - 'chart': 2, - 'medal': 5, - 'score': 76543, + "id": 987, + "chart": 2, + "medal": 5, + "score": 76543, }, # A good score on an easier chart of the same song { - 'id': 987, - 'chart': 0, - 'medal': 6, - 'score': 99999, + "id": 987, + "chart": 0, + "medal": 6, + "score": 99999, }, # A bad score on a hard chart { - 'id': 741, - 'chart': 3, - 'medal': 2, - 'score': 45000, + "id": 741, + "chart": 3, + "medal": 2, + "score": 45000, }, # A terrible score on an easy chart { - 'id': 742, - 'chart': 1, - 'medal': 2, - 'score': 1, + "id": 742, + "chart": 1, + "medal": 2, + "score": 1, }, ] # Random score to add in @@ -585,29 +622,31 @@ class PopnMusicUsaNekoClient(BaseClient): chartid = random.randint(0, 3) score = random.randint(0, 100000) medal = random.randint(1, 11) - dummyscores.append({ - 'id': songid, - 'chart': chartid, - 'medal': medal, - 'score': score, - }) + dummyscores.append( + { + "id": songid, + "chart": chartid, + "medal": medal, + "score": score, + } + ) if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 987, - 'chart': 2, - 'medal': 6, - 'score': 98765, + "id": 987, + "chart": 2, + "medal": 6, + "score": 98765, }, # A worse score on another same chart { - 'id': 987, - 'chart': 0, - 'medal': 3, - 'score': 12345, - 'expected_score': 99999, - 'expected_medal': 6, + "id": 987, + "chart": 0, + "medal": 3, + "score": 12345, + "expected_score": 99999, + "expected_medal": 6, }, ] @@ -615,18 +654,18 @@ class PopnMusicUsaNekoClient(BaseClient): self.verify_player24_write_music(ref_id, dummyscore) scores = self.verify_player24_read_score(ref_id) for expected in dummyscores: - newscore = scores['scores'][expected['id']][expected['chart']] - newmedal = scores['medals'][expected['id']][expected['chart']] - newrank = scores['ranks'][expected['id']][expected['chart']] + newscore = scores["scores"][expected["id"]][expected["chart"]] + newmedal = scores["medals"][expected["id"]][expected["chart"]] + newrank = scores["ranks"][expected["id"]][expected["chart"]] - if 'expected_score' in expected: - expected_score = expected['expected_score'] + if "expected_score" in expected: + expected_score = expected["expected_score"] else: - expected_score = expected['score'] - if 'expected_medal' in expected: - expected_medal = expected['expected_medal'] + expected_score = expected["score"] + if "expected_medal" in expected: + expected_medal = expected["expected_medal"] else: - expected_medal = expected['medal'] + expected_medal = expected["medal"] if newscore < 50000: expected_rank = 1 @@ -646,11 +685,17 @@ class PopnMusicUsaNekoClient(BaseClient): expected_rank = 8 if newscore != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{newscore}\'') + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{newscore}\'' + ) if newmedal != expected_medal: - raise Exception(f'Expected a medal of \'{expected_medal}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got medal \'{newmedal}\'') + raise Exception( + f'Expected a medal of \'{expected_medal}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got medal \'{newmedal}\'' + ) if newrank != expected_rank: - raise Exception(f'Expected a rank of \'{expected_rank}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got rank \'{newrank}\'') + raise Exception( + f'Expected a rank of \'{expected_rank}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got rank \'{newrank}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) diff --git a/bemani/client/protocol.py b/bemani/client/protocol.py index b498ca8..8a107ab 100644 --- a/bemani/client/protocol.py +++ b/bemani/client/protocol.py @@ -5,18 +5,31 @@ from bemani.protocol import EAmuseProtocol, Node class ClientProtocol: - def __init__(self, address: str, port: int, encryption: bool, compression: bool, verbose: bool) -> None: + def __init__( + self, + address: str, + port: int, + encryption: bool, + compression: bool, + verbose: bool, + ) -> None: self.__address = address self.__port = port self.__encryption = encryption self.__compression = compression self.__verbose = verbose - def exchange(self, uri: str, tree: Node, text_encoding: str="shift-jis", packet_encoding: str="binary") -> Node: + def exchange( + self, + uri: str, + tree: Node, + text_encoding: str = "shift-jis", + packet_encoding: str = "binary", + ) -> Node: headers = {} if self.__verbose: - print('Outgoing request:') + print("Outgoing request:") print(tree) # Handle encoding @@ -29,17 +42,17 @@ class ClientProtocol: # Handle encryption if self.__encryption: - encryption = f'1-{random_hex_string(8)}-{random_hex_string(4)}' - headers['X-Eamuse-Info'] = encryption + encryption = f"1-{random_hex_string(8)}-{random_hex_string(4)}" + headers["X-Eamuse-Info"] = encryption else: encryption = None # Handle compression if self.__compression: - compression = 'lz77' + compression = "lz77" else: compression = None - headers['X-Compress'] = compression + headers["X-Compress"] = compression # Convert it proto = EAmuseProtocol() @@ -53,14 +66,14 @@ class ClientProtocol: # Send the request, get the response r = requests.post( - f'http://{self.__address}:{self.__port}/{uri}', + f"http://{self.__address}:{self.__port}/{uri}", headers=headers, data=req, ) # Get the compression and encryption - encryption = headers.get('X-Eamuse-Info') - compression = headers.get('X-Compress') + encryption = headers.get("X-Eamuse-Info") + compression = headers.get("X-Compress") # Decode it packet = proto.decode( @@ -69,6 +82,6 @@ class ClientProtocol: r.content, ) if self.__verbose: - print('Incoming response:') + print("Incoming response:") print(packet) return packet diff --git a/bemani/client/reflec/colette.py b/bemani/client/reflec/colette.py index 5b4c180..f5a28f4 100644 --- a/bemani/client/reflec/colette.py +++ b/bemani/client/reflec/colette.py @@ -7,18 +7,18 @@ from bemani.protocol import Node class ReflecBeatColette(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_pcb_boot(self, loc: str) -> None: call = self.call_node() - pcb = Node.void('pcb') - pcb.set_attribute('method', 'boot') - pcb.add_child(Node.string('lid', loc)) + pcb = Node.void("pcb") + pcb.set_attribute("method", "boot") + pcb.add_child(Node.string("lid", loc)) call.add_child(pcb) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/pcb/sinfo/nm") @@ -29,12 +29,12 @@ class ReflecBeatColette(BaseClient): def verify_info_common(self) -> None: call = self.call_node() - info = Node.void('info') - info.set_attribute('method', 'common') + info = Node.void("info") + info.set_attribute("method", "common") call.add_child(info) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/info/event_ctrl") @@ -43,13 +43,13 @@ class ReflecBeatColette(BaseClient): def verify_info_ranking(self) -> None: call = self.call_node() - info = Node.void('info') - info.set_attribute('method', 'ranking') - info.add_child(Node.s32('ver', 0)) + info = Node.void("info") + info.set_attribute("method", "ranking") + info.add_child(Node.s32("ver", 0)) call.add_child(info) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/info/ver") @@ -69,16 +69,16 @@ class ReflecBeatColette(BaseClient): def verify_player_start(self, refid: str) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'start') - player.add_child(Node.string('rid', refid)) - player.add_child(Node.u8_array('ga', [127, 0, 0, 1])) - player.add_child(Node.u16('gp', 10573)) - player.add_child(Node.u8_array('la', [16, 0, 0, 0])) + player = Node.void("player") + player.set_attribute("method", "start") + player.add_child(Node.string("rid", refid)) + player.add_child(Node.u8_array("ga", [127, 0, 0, 1])) + player.add_child(Node.u16("gp", 10573)) + player.add_child(Node.u8_array("la", [16, 0, 0, 0])) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/plyid") @@ -92,13 +92,13 @@ class ReflecBeatColette(BaseClient): def verify_player_delete(self, refid: str) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'delete') - player.add_child(Node.string('rid', refid)) + player = Node.void("player") + player.set_attribute("method", "delete") + player.add_child(Node.string("rid", refid)) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player") @@ -106,13 +106,13 @@ class ReflecBeatColette(BaseClient): def verify_player_end(self, refid: str) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'end') - player.add_child(Node.string('rid', refid)) + player = Node.void("player") + player.set_attribute("method", "end") + player.add_child(Node.string("rid", refid)) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player") @@ -120,13 +120,13 @@ class ReflecBeatColette(BaseClient): def verify_player_succeed(self, refid: str) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'succeed') - player.add_child(Node.string('rid', refid)) + player = Node.void("player") + player.set_attribute("method", "succeed") + player.add_child(Node.string("rid", refid)) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/name") @@ -140,15 +140,15 @@ class ReflecBeatColette(BaseClient): def verify_player_read(self, refid: str, location: str) -> List[Dict[str, int]]: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'read') - player.add_child(Node.string('rid', refid)) - player.add_child(Node.string('lid', location)) - player.add_child(Node.s16('ver', 5)) + player = Node.void("player") + player.set_attribute("method", "read") + player.add_child(Node.string("rid", refid)) + player.add_child(Node.string("lid", location)) + player.add_child(Node.s16("ver", 5)) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/pdata/account/usrid") @@ -181,118 +181,178 @@ class ReflecBeatColette(BaseClient): self.assert_path(resp, "response/player/pdata/released") self.assert_path(resp, "response/player/pdata/record") - if resp.child_value('player/pdata/base/name') != self.NAME: - raise Exception(f'Invalid name {resp.child_value("player/pdata/base/name")} returned on profile read!') + if resp.child_value("player/pdata/base/name") != self.NAME: + raise Exception( + f'Invalid name {resp.child_value("player/pdata/base/name")} returned on profile read!' + ) scores = [] - for child in resp.child('player/pdata/record').children: - if child.name != 'rec': + for child in resp.child("player/pdata/record").children: + if child.name != "rec": continue score = { - 'id': child.child_value('mid'), - 'chart': child.child_value('ntgrd'), - 'clear_type': child.child_value('ct'), - 'achievement_rate': child.child_value('ar'), - 'score': child.child_value('scr'), - 'combo': child.child_value('cmb'), - 'miss_count': child.child_value('ms'), + "id": child.child_value("mid"), + "chart": child.child_value("ntgrd"), + "clear_type": child.child_value("ct"), + "achievement_rate": child.child_value("ar"), + "score": child.child_value("scr"), + "combo": child.child_value("cmb"), + "miss_count": child.child_value("ms"), } scores.append(score) return scores - def verify_player_write(self, refid: str, loc: str, scores: List[Dict[str, int]]) -> int: + def verify_player_write( + self, refid: str, loc: str, scores: List[Dict[str, int]] + ) -> int: call = self.call_node() - player = Node.void('player') + player = Node.void("player") call.add_child(player) - player.set_attribute('method', 'write') - pdata = Node.void('pdata') + player.set_attribute("method", "write") + pdata = Node.void("pdata") player.add_child(pdata) - account = Node.void('account') + account = Node.void("account") pdata.add_child(account) - account.add_child(Node.s32('usrid', 0)) - account.add_child(Node.s32('plyid', 0)) - account.add_child(Node.s32('tpc', 1)) - account.add_child(Node.s32('dpc', 1)) - account.add_child(Node.s32('crd', 1)) - account.add_child(Node.s32('brd', 1)) - account.add_child(Node.s32('tdc', 1)) - account.add_child(Node.string('rid', refid)) - account.add_child(Node.string('lid', loc)) - account.add_child(Node.u8('mode', 0)) - account.add_child(Node.s16('ver', 5)) - account.add_child(Node.bool('pp', True)) - account.add_child(Node.bool('ps', True)) - account.add_child(Node.s16('pay', 0)) - account.add_child(Node.s16('pay_pc', 0)) - account.add_child(Node.u64('st', int(time.time() * 1000))) - base = Node.void('base') + account.add_child(Node.s32("usrid", 0)) + account.add_child(Node.s32("plyid", 0)) + account.add_child(Node.s32("tpc", 1)) + account.add_child(Node.s32("dpc", 1)) + account.add_child(Node.s32("crd", 1)) + account.add_child(Node.s32("brd", 1)) + account.add_child(Node.s32("tdc", 1)) + account.add_child(Node.string("rid", refid)) + account.add_child(Node.string("lid", loc)) + account.add_child(Node.u8("mode", 0)) + account.add_child(Node.s16("ver", 5)) + account.add_child(Node.bool("pp", True)) + account.add_child(Node.bool("ps", True)) + account.add_child(Node.s16("pay", 0)) + account.add_child(Node.s16("pay_pc", 0)) + account.add_child(Node.u64("st", int(time.time() * 1000))) + base = Node.void("base") pdata.add_child(base) - base.add_child(Node.string('name', self.NAME)) - base.add_child(Node.s32('exp', 0)) - base.add_child(Node.s32('lv', 1)) - base.add_child(Node.s32('mg', -1)) - base.add_child(Node.s32('ap', -1)) - base.add_child(Node.s32_array('hidden_param', [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])) - base.add_child(Node.bool('is_tut', True)) - stglog = Node.void('stglog') + base.add_child(Node.string("name", self.NAME)) + base.add_child(Node.s32("exp", 0)) + base.add_child(Node.s32("lv", 1)) + base.add_child(Node.s32("mg", -1)) + base.add_child(Node.s32("ap", -1)) + base.add_child( + Node.s32_array( + "hidden_param", + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 2, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + ) + ) + base.add_child(Node.bool("is_tut", True)) + stglog = Node.void("stglog") pdata.add_child(stglog) index = 0 for score in scores: - log = Node.void('log') + log = Node.void("log") stglog.add_child(log) - log.add_child(Node.s8('stg', index)) - log.add_child(Node.s16('mid', score['id'])) - log.add_child(Node.s8('ng', score['chart'])) - log.add_child(Node.s8('col', 0)) - log.add_child(Node.s8('mt', 7)) - log.add_child(Node.s8('rt', 0)) - log.add_child(Node.s8('ct', score['clear_type'])) - log.add_child(Node.s16('grd', 0)) - log.add_child(Node.s16('ar', score['achievement_rate'])) - log.add_child(Node.s16('sc', score['score'])) - log.add_child(Node.s16('jt_jst', 0)) - log.add_child(Node.s16('jt_grt', 0)) - log.add_child(Node.s16('jt_gd', 0)) - log.add_child(Node.s16('jt_ms', score['miss_count'])) - log.add_child(Node.s16('jt_jr', 0)) - log.add_child(Node.s16('cmb', score['combo'])) - log.add_child(Node.s16('exp', 0)) - log.add_child(Node.s32('r_uid', 0)) - log.add_child(Node.s32('r_plyid', 0)) - log.add_child(Node.s8('r_stg', 0)) - log.add_child(Node.s8('r_ct', -1)) - log.add_child(Node.s16('r_sc', 0)) - log.add_child(Node.s16('r_grd', 0)) - log.add_child(Node.s16('r_ar', 0)) - log.add_child(Node.s8('r_cpuid', -1)) - log.add_child(Node.s32('time', int(time.time()))) - log.add_child(Node.s8('decide', 0)) + log.add_child(Node.s8("stg", index)) + log.add_child(Node.s16("mid", score["id"])) + log.add_child(Node.s8("ng", score["chart"])) + log.add_child(Node.s8("col", 0)) + log.add_child(Node.s8("mt", 7)) + log.add_child(Node.s8("rt", 0)) + log.add_child(Node.s8("ct", score["clear_type"])) + log.add_child(Node.s16("grd", 0)) + log.add_child(Node.s16("ar", score["achievement_rate"])) + log.add_child(Node.s16("sc", score["score"])) + log.add_child(Node.s16("jt_jst", 0)) + log.add_child(Node.s16("jt_grt", 0)) + log.add_child(Node.s16("jt_gd", 0)) + log.add_child(Node.s16("jt_ms", score["miss_count"])) + log.add_child(Node.s16("jt_jr", 0)) + log.add_child(Node.s16("cmb", score["combo"])) + log.add_child(Node.s16("exp", 0)) + log.add_child(Node.s32("r_uid", 0)) + log.add_child(Node.s32("r_plyid", 0)) + log.add_child(Node.s8("r_stg", 0)) + log.add_child(Node.s8("r_ct", -1)) + log.add_child(Node.s16("r_sc", 0)) + log.add_child(Node.s16("r_grd", 0)) + log.add_child(Node.s16("r_ar", 0)) + log.add_child(Node.s8("r_cpuid", -1)) + log.add_child(Node.s32("time", int(time.time()))) + log.add_child(Node.s8("decide", 0)) index = index + 1 # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/uid") - return resp.child_value('player/uid') + return resp.child_value("player/uid") def verify_lobby_read(self, location: str, extid: int) -> None: call = self.call_node() - lobby = Node.void('lobby') - lobby.set_attribute('method', 'read') - lobby.add_child(Node.s32('uid', extid)) - lobby.add_child(Node.u8('m_grade', 255)) - lobby.add_child(Node.string('lid', location)) - lobby.add_child(Node.s32('max', 128)) - lobby.add_child(Node.s32_array('friend', [])) - lobby.add_child(Node.u8('var', 5)) + lobby = Node.void("lobby") + lobby.set_attribute("method", "read") + lobby.add_child(Node.s32("uid", extid)) + lobby.add_child(Node.u8("m_grade", 255)) + lobby.add_child(Node.string("lid", location)) + lobby.add_child(Node.s32("max", 128)) + lobby.add_child(Node.s32_array("friend", [])) + lobby.add_child(Node.u8("var", 5)) call.add_child(lobby) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/lobby/interval") @@ -301,36 +361,36 @@ class ReflecBeatColette(BaseClient): def verify_lobby_entry(self, location: str, extid: int) -> int: call = self.call_node() - lobby = Node.void('lobby') - lobby.set_attribute('method', 'entry') - e = Node.void('e') + lobby = Node.void("lobby") + lobby.set_attribute("method", "entry") + e = Node.void("e") lobby.add_child(e) - e.add_child(Node.s32('eid', 0)) - e.add_child(Node.u16('mid', 79)) - e.add_child(Node.u8('ng', 0)) - e.add_child(Node.s32('uid', extid)) - e.add_child(Node.s32('uattr', 0)) - e.add_child(Node.string('pn', self.NAME)) - e.add_child(Node.s16('mg', 255)) - e.add_child(Node.s32('mopt', 0)) - e.add_child(Node.s32('tid', 0)) - e.add_child(Node.string('tn', '')) - e.add_child(Node.s32('topt', 0)) - e.add_child(Node.string('lid', location)) - e.add_child(Node.string('sn', '')) - e.add_child(Node.u8('pref', 51)) - e.add_child(Node.s8('stg', 4)) - e.add_child(Node.s8('pside', 0)) - e.add_child(Node.s16('eatime', 30)) - e.add_child(Node.u8_array('ga', [127, 0, 0, 1])) - e.add_child(Node.u16('gp', 10007)) - e.add_child(Node.u8_array('la', [16, 0, 0, 0])) - e.add_child(Node.u8('ver', 5)) - lobby.add_child(Node.s32_array('friend', [])) + e.add_child(Node.s32("eid", 0)) + e.add_child(Node.u16("mid", 79)) + e.add_child(Node.u8("ng", 0)) + e.add_child(Node.s32("uid", extid)) + e.add_child(Node.s32("uattr", 0)) + e.add_child(Node.string("pn", self.NAME)) + e.add_child(Node.s16("mg", 255)) + e.add_child(Node.s32("mopt", 0)) + e.add_child(Node.s32("tid", 0)) + e.add_child(Node.string("tn", "")) + e.add_child(Node.s32("topt", 0)) + e.add_child(Node.string("lid", location)) + e.add_child(Node.string("sn", "")) + e.add_child(Node.u8("pref", 51)) + e.add_child(Node.s8("stg", 4)) + e.add_child(Node.s8("pside", 0)) + e.add_child(Node.s16("eatime", 30)) + e.add_child(Node.u8_array("ga", [127, 0, 0, 1])) + e.add_child(Node.u16("gp", 10007)) + e.add_child(Node.u8_array("la", [16, 0, 0, 0])) + e.add_child(Node.u8("ver", 5)) + lobby.add_child(Node.s32_array("friend", [])) call.add_child(lobby) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/lobby/interval") @@ -357,18 +417,18 @@ class ReflecBeatColette(BaseClient): self.assert_path(resp, "response/lobby/e/gp") self.assert_path(resp, "response/lobby/e/la") self.assert_path(resp, "response/lobby/e/ver") - return resp.child_value('lobby/eid') + return resp.child_value("lobby/eid") def verify_lobby_delete(self, eid: int) -> None: call = self.call_node() - lobby = Node.void('lobby') - lobby.set_attribute('method', 'delete') - lobby.add_child(Node.s32('eid', eid)) + lobby = Node.void("lobby") + lobby.set_attribute("method", "delete") + lobby.add_child(Node.s32("eid", eid)) call.add_child(lobby) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/lobby") @@ -376,16 +436,16 @@ class ReflecBeatColette(BaseClient): def verify_pzlcmt_read(self, extid: int) -> None: call = self.call_node() - info = Node.void('info') - info.set_attribute('method', 'pzlcmt_read') - info.add_child(Node.s32('uid', extid)) - info.add_child(Node.s32('tid', 0)) - info.add_child(Node.s32('time', 0)) - info.add_child(Node.s32('limit', 30)) + info = Node.void("info") + info.set_attribute("method", "pzlcmt_read") + info.add_child(Node.s32("uid", extid)) + info.add_child(Node.s32("tid", 0)) + info.add_child(Node.s32("time", 0)) + info.add_child(Node.s32("limit", 30)) call.add_child(info) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/info/comment/time") @@ -402,40 +462,42 @@ class ReflecBeatColette(BaseClient): # Verify we posted our comment earlier found = False - for child in resp.child('info').children: - if child.name != 'c': + for child in resp.child("info").children: + if child.name != "c": continue - if child.child_value('uid') == extid: - name = child.child_value('name') - comment = child.child_value('comment') + if child.child_value("uid") == extid: + name = child.child_value("name") + comment = child.child_value("comment") if name != self.NAME: - raise Exception(f'Invalid name \'{name}\' returned for comment!') - if comment != 'アメ〜〜!': - raise Exception(f'Invalid comment \'{comment}\' returned for comment!') + raise Exception(f"Invalid name '{name}' returned for comment!") + if comment != "アメ〜〜!": + raise Exception( + f"Invalid comment '{comment}' returned for comment!" + ) found = True if not found: - raise Exception('Comment we posted was not found!') + raise Exception("Comment we posted was not found!") def verify_pzlcmt_write(self, extid: int) -> None: call = self.call_node() - info = Node.void('info') - info.set_attribute('method', 'pzlcmt_write') - info.add_child(Node.s32('uid', extid)) - info.add_child(Node.string('name', self.NAME)) - info.add_child(Node.s16('icon', 0)) - info.add_child(Node.s8('bln', 0)) - info.add_child(Node.s32('tid', 0)) - info.add_child(Node.string('t_name', '')) - info.add_child(Node.s8('pref', 51)) - info.add_child(Node.s32('time', int(time.time()))) - info.add_child(Node.string('comment', 'アメ〜〜!')) - info.add_child(Node.bool('is_tweet', True)) + info = Node.void("info") + info.set_attribute("method", "pzlcmt_write") + info.add_child(Node.s32("uid", extid)) + info.add_child(Node.string("name", self.NAME)) + info.add_child(Node.s16("icon", 0)) + info.add_child(Node.s8("bln", 0)) + info.add_child(Node.s32("tid", 0)) + info.add_child(Node.string("t_name", "")) + info.add_child(Node.s8("pref", 51)) + info.add_child(Node.s32("time", int(time.time()))) + info.add_child(Node.string("comment", "アメ〜〜!")) + info.add_child(Node.bool("is_tweet", True)) call.add_child(info) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/info") @@ -443,14 +505,14 @@ class ReflecBeatColette(BaseClient): def verify_jbrbcollabo_save(self, refid: str) -> None: call = self.call_node() - jbrbcollabo = Node.void('jbrbcollabo') - jbrbcollabo.set_attribute('method', 'save') - jbrbcollabo.add_child(Node.string('ref_id', refid)) - jbrbcollabo.add_child(Node.u16('cre_count', 0)) + jbrbcollabo = Node.void("jbrbcollabo") + jbrbcollabo.set_attribute("method", "save") + jbrbcollabo.add_child(Node.string("ref_id", refid)) + jbrbcollabo.add_child(Node.u16("cre_count", 0)) call.add_child(jbrbcollabo) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/jbrbcollabo") @@ -459,20 +521,20 @@ class ReflecBeatColette(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -491,12 +553,18 @@ class ReflecBeatColette(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Always get a player start, regardless of new profile or not self.verify_player_start(ref_id) self.verify_player_delete(ref_id) @@ -504,25 +572,31 @@ class ReflecBeatColette(BaseClient): extid = self.verify_player_write( ref_id, location, - [{ - 'id': 0, - 'chart': 0, - 'clear_type': -1, - 'achievement_rate': 0, - 'score': 0, - 'combo': 0, - 'miss_count': 0, - }] + [ + { + "id": 0, + "chart": 0, + "clear_type": -1, + "achievement_rate": 0, + "score": 0, + "combo": 0, + "miss_count": 0, + } + ], ) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Verify lobby functionality self.verify_lobby_read(location, extid) @@ -543,71 +617,71 @@ class ReflecBeatColette(BaseClient): dummyscores = [ # An okay score on a chart { - 'id': 1, - 'chart': 1, - 'clear_type': 2, - 'achievement_rate': 7543, - 'score': 432, - 'combo': 123, - 'miss_count': 5, + "id": 1, + "chart": 1, + "clear_type": 2, + "achievement_rate": 7543, + "score": 432, + "combo": 123, + "miss_count": 5, }, # A good score on an easier chart of the same song { - 'id': 1, - 'chart': 0, - 'clear_type': 4, - 'achievement_rate': 9876, - 'score': 543, - 'combo': 543, - 'miss_count': 0, + "id": 1, + "chart": 0, + "clear_type": 4, + "achievement_rate": 9876, + "score": 543, + "combo": 543, + "miss_count": 0, }, # A bad score on a hard chart { - 'id': 3, - 'chart': 2, - 'clear_type': 2, - 'achievement_rate': 1234, - 'score': 123, - 'combo': 42, - 'miss_count': 54, + "id": 3, + "chart": 2, + "clear_type": 2, + "achievement_rate": 1234, + "score": 123, + "combo": 42, + "miss_count": 54, }, # A terrible score on an easy chart { - 'id': 3, - 'chart': 0, - 'clear_type': 2, - 'achievement_rate': 1024, - 'score': 50, - 'combo': 12, - 'miss_count': 90, + "id": 3, + "chart": 0, + "clear_type": 2, + "achievement_rate": 1024, + "score": 50, + "combo": 12, + "miss_count": 90, }, ] if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 1, - 'chart': 1, - 'clear_type': 3, - 'achievement_rate': 8765, - 'score': 469, - 'combo': 468, - 'miss_count': 1, + "id": 1, + "chart": 1, + "clear_type": 3, + "achievement_rate": 8765, + "score": 469, + "combo": 468, + "miss_count": 1, }, # A worse score on another same chart { - 'id': 1, - 'chart': 0, - 'clear_type': 2, - 'achievement_rate': 8765, - 'score': 432, - 'combo': 321, - 'miss_count': 15, - 'expected_score': 543, - 'expected_clear_type': 4, - 'expected_achievement_rate': 9876, - 'expected_combo': 543, - 'expected_miss_count': 0, + "id": 1, + "chart": 0, + "clear_type": 2, + "achievement_rate": 8765, + "score": 432, + "combo": 321, + "miss_count": 15, + "expected_score": 543, + "expected_clear_type": 4, + "expected_achievement_rate": 9876, + "expected_combo": 543, + "expected_miss_count": 0, }, ] self.verify_player_write(ref_id, location, dummyscores) @@ -616,44 +690,61 @@ class ReflecBeatColette(BaseClient): for expected in dummyscores: actual = None for received in scores: - if received['id'] == expected['id'] and received['chart'] == expected['chart']: + if ( + received["id"] == expected["id"] + and received["chart"] == expected["chart"] + ): actual = received break if actual is None: - raise Exception(f"Didn't find song {expected['id']} chart {expected['chart']} in response!") + raise Exception( + f"Didn't find song {expected['id']} chart {expected['chart']} in response!" + ) - if 'expected_score' in expected: - expected_score = expected['expected_score'] + if "expected_score" in expected: + expected_score = expected["expected_score"] else: - expected_score = expected['score'] - if 'expected_achievement_rate' in expected: - expected_achievement_rate = expected['expected_achievement_rate'] + expected_score = expected["score"] + if "expected_achievement_rate" in expected: + expected_achievement_rate = expected[ + "expected_achievement_rate" + ] else: - expected_achievement_rate = expected['achievement_rate'] - if 'expected_clear_type' in expected: - expected_clear_type = expected['expected_clear_type'] + expected_achievement_rate = expected["achievement_rate"] + if "expected_clear_type" in expected: + expected_clear_type = expected["expected_clear_type"] else: - expected_clear_type = expected['clear_type'] - if 'expected_combo' in expected: - expected_combo = expected['expected_combo'] + expected_clear_type = expected["clear_type"] + if "expected_combo" in expected: + expected_combo = expected["expected_combo"] else: - expected_combo = expected['combo'] - if 'expected_miss_count' in expected: - expected_miss_count = expected['expected_miss_count'] + expected_combo = expected["combo"] + if "expected_miss_count" in expected: + expected_miss_count = expected["expected_miss_count"] else: - expected_miss_count = expected['miss_count'] + expected_miss_count = expected["miss_count"] - if actual['score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'') - if actual['achievement_rate'] != expected_achievement_rate: - raise Exception(f'Expected an achievement rate of \'{expected_achievement_rate}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got achievement rate \'{actual["achievement_rate"]}\'') - if actual['clear_type'] != expected_clear_type: - raise Exception(f'Expected a clear_type of \'{expected_clear_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got clear_type \'{actual["clear_type"]}\'') - if actual['combo'] != expected_combo: - raise Exception(f'Expected a combo of \'{expected_combo}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got combo \'{actual["combo"]}\'') - if actual['miss_count'] != expected_miss_count: - raise Exception(f'Expected a miss count of \'{expected_miss_count}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got miss count \'{actual["miss_count"]}\'') + if actual["score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'' + ) + if actual["achievement_rate"] != expected_achievement_rate: + raise Exception( + f'Expected an achievement rate of \'{expected_achievement_rate}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got achievement rate \'{actual["achievement_rate"]}\'' + ) + if actual["clear_type"] != expected_clear_type: + raise Exception( + f'Expected a clear_type of \'{expected_clear_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got clear_type \'{actual["clear_type"]}\'' + ) + if actual["combo"] != expected_combo: + raise Exception( + f'Expected a combo of \'{expected_combo}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got combo \'{actual["combo"]}\'' + ) + if actual["miss_count"] != expected_miss_count: + raise Exception( + f'Expected a miss count of \'{expected_miss_count}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got miss count \'{actual["miss_count"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) diff --git a/bemani/client/reflec/groovin.py b/bemani/client/reflec/groovin.py index 5f99f93..604906d 100644 --- a/bemani/client/reflec/groovin.py +++ b/bemani/client/reflec/groovin.py @@ -7,19 +7,19 @@ from bemani.protocol import Node class ReflecBeatGroovinUpper(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_pcb_rb4boot(self, loc: str) -> None: call = self.call_node() - pcb = Node.void('pcb') - pcb.set_attribute('method', 'rb4boot') - pcb.add_child(Node.string('lid', loc)) - pcb.add_child(Node.string('rno', 'Unknown')) + pcb = Node.void("pcb") + pcb.set_attribute("method", "rb4boot") + pcb.add_child(Node.string("lid", loc)) + pcb.add_child(Node.string("rno", "Unknown")) call.add_child(pcb) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/pcb/sinfo/nm") @@ -31,15 +31,15 @@ class ReflecBeatGroovinUpper(BaseClient): def verify_pcb_rb4error(self, loc: str) -> None: call = self.call_node() - pcb = Node.void('pcb') + pcb = Node.void("pcb") call.add_child(pcb) - pcb.set_attribute('method', 'rb4error') - pcb.add_child(Node.string('lid', loc)) - pcb.add_child(Node.string('code', 'exception')) - pcb.add_child(Node.string('msg', 'exceptionstring')) + pcb.set_attribute("method", "rb4error") + pcb.add_child(Node.string("lid", loc)) + pcb.add_child(Node.string("code", "exception")) + pcb.add_child(Node.string("msg", "exceptionstring")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/pcb/@status") @@ -47,13 +47,13 @@ class ReflecBeatGroovinUpper(BaseClient): def verify_info_rb4common(self, loc: str) -> None: call = self.call_node() - info = Node.void('info') + info = Node.void("info") call.add_child(info) - info.set_attribute('method', 'rb4common') - info.add_child(Node.string('lid', loc)) + info.set_attribute("method", "rb4common") + info.add_child(Node.string("lid", loc)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/info/event_ctrl") @@ -64,17 +64,17 @@ class ReflecBeatGroovinUpper(BaseClient): def verify_info_rb4shop_score_ranking(self, loc: str) -> None: call = self.call_node() - info = Node.void('info') + info = Node.void("info") call.add_child(info) - info.set_attribute('method', 'rb4shop_score_ranking') + info.set_attribute("method", "rb4shop_score_ranking") # Arbitrarily chosen based on the song IDs we send in the # score section below. - info.add_child(Node.s16('min', 1)) - info.add_child(Node.s16('max', 3)) - info.add_child(Node.string('lid', loc)) + info.add_child(Node.s16("min", 1)) + info.add_child(Node.s16("max", 3)) + info.add_child(Node.string("lid", loc)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/info/shop_score/time") @@ -91,13 +91,13 @@ class ReflecBeatGroovinUpper(BaseClient): def verify_info_rb4ranking(self) -> None: call = self.call_node() - info = Node.void('info') - info.set_attribute('method', 'rb4ranking') - info.add_child(Node.s32('ver', 0)) + info = Node.void("info") + info.set_attribute("method", "rb4ranking") + info.add_child(Node.s32("ver", 0)) call.add_child(info) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/info/ver") @@ -117,18 +117,22 @@ class ReflecBeatGroovinUpper(BaseClient): def verify_player_rb4start(self, refid: str) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'rb4start') - player.add_child(Node.string('rid', refid)) - player.add_child(Node.u8_array('ga', [127, 0, 0, 1])) - player.add_child(Node.u16('gp', 10573)) - player.add_child(Node.u8_array('la', [16, 0, 0, 0])) - player.add_child(Node.u8_array('pnid', [39, 16, 0, 0, 0, 23, 62, 60, 39, 127, 0, 0, 1, 23, 62, 60])) + player = Node.void("player") + player.set_attribute("method", "rb4start") + player.add_child(Node.string("rid", refid)) + player.add_child(Node.u8_array("ga", [127, 0, 0, 1])) + player.add_child(Node.u16("gp", 10573)) + player.add_child(Node.u8_array("la", [16, 0, 0, 0])) + player.add_child( + Node.u8_array( + "pnid", [39, 16, 0, 0, 0, 23, 62, 60, 39, 127, 0, 0, 1, 23, 62, 60] + ) + ) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/plyid") @@ -139,13 +143,13 @@ class ReflecBeatGroovinUpper(BaseClient): def verify_player_rb4end(self, refid: str) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'rb4end') - player.add_child(Node.string('rid', refid)) + player = Node.void("player") + player.set_attribute("method", "rb4end") + player.add_child(Node.string("rid", refid)) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player") @@ -153,14 +157,14 @@ class ReflecBeatGroovinUpper(BaseClient): def verify_player_rb4total_bestallrank_read(self) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'rb4total_bestallrank_read') - player.add_child(Node.s32('uid', 0)) - player.add_child(Node.s32_array('score', [897, 897, 0, 0, 0, 284])) + player = Node.void("player") + player.set_attribute("method", "rb4total_bestallrank_read") + player.add_child(Node.s32("uid", 0)) + player.add_child(Node.s32_array("score", [897, 897, 0, 0, 0, 284])) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/score/rank") @@ -170,15 +174,15 @@ class ReflecBeatGroovinUpper(BaseClient): def verify_player_rb4selectscore(self, extid: int) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'rb4selectscore') - player.add_child(Node.s32('uid', extid)) - player.add_child(Node.s32('music_id', 1)) - player.add_child(Node.s32('note_grade', 0)) + player = Node.void("player") + player.set_attribute("method", "rb4selectscore") + player.add_child(Node.s32("uid", extid)) + player.add_child(Node.s32("music_id", 1)) + player.add_child(Node.s32("note_grade", 0)) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/@status") @@ -191,11 +195,11 @@ class ReflecBeatGroovinUpper(BaseClient): self.assert_path(resp, "response/player/player_select_score/m_scoreTime") self.assert_path(resp, "response/player/player_select_score/m_iconID") - if resp.child_value('player/player_select_score/name') != self.NAME: + if resp.child_value("player/player_select_score/name") != self.NAME: raise Exception( f'Invalid name {resp.child_value("player/player_select_score/name")} returned on score read!' ) - if resp.child_value('player/player_select_score/user_id') != extid: + if resp.child_value("player/player_select_score/user_id") != extid: raise Exception( f'Invalid name {resp.child_value("player/player_select_score/user_id")} returned on score read!' ) @@ -203,13 +207,13 @@ class ReflecBeatGroovinUpper(BaseClient): def verify_player_rb4succeed(self, refid: str) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'rb4succeed') - player.add_child(Node.string('rid', refid)) + player = Node.void("player") + player.set_attribute("method", "rb4succeed") + player.add_child(Node.string("rid", refid)) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/name") @@ -224,17 +228,17 @@ class ReflecBeatGroovinUpper(BaseClient): def verify_player_rb4read(self, refid: str, cardid: str, location: str) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'rb4read') - player.add_child(Node.string('rid', refid)) - player.add_child(Node.string('lid', location)) - player.add_child(Node.s16('ver', 1)) - player.add_child(Node.string('card_id', cardid)) - player.add_child(Node.s16('card_type', 1)) + player = Node.void("player") + player.set_attribute("method", "rb4read") + player.add_child(Node.string("rid", refid)) + player.add_child(Node.string("lid", location)) + player.add_child(Node.s16("ver", 1)) + player.add_child(Node.string("card_id", cardid)) + player.add_child(Node.s16("card_type", 1)) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/pdata/account/usrid") @@ -288,35 +292,39 @@ class ReflecBeatGroovinUpper(BaseClient): self.assert_path(resp, "response/player/pdata/iidx_linkage") self.assert_path(resp, "response/player/pdata/pue") - if resp.child_value('player/pdata/base/name') != self.NAME: - raise Exception(f'Invalid name {resp.child_value("player/pdata/base/name")} returned on profile read!') + if resp.child_value("player/pdata/base/name") != self.NAME: + raise Exception( + f'Invalid name {resp.child_value("player/pdata/base/name")} returned on profile read!' + ) - def verify_player_rb4readscore(self, refid: str, location: str) -> List[Dict[str, int]]: + def verify_player_rb4readscore( + self, refid: str, location: str + ) -> List[Dict[str, int]]: call = self.call_node() - player = Node.void('player') + player = Node.void("player") call.add_child(player) - player.set_attribute('method', 'rb4readscore') - player.add_child(Node.string('rid', refid)) - player.add_child(Node.string('lid', location)) - player.add_child(Node.s16('ver', 1)) + player.set_attribute("method", "rb4readscore") + player.add_child(Node.string("rid", refid)) + player.add_child(Node.string("lid", location)) + player.add_child(Node.s16("ver", 1)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) scores = [] - for child in resp.child('player/pdata/record').children: - if child.name != 'rec': + for child in resp.child("player/pdata/record").children: + if child.name != "rec": continue score = { - 'id': child.child_value('mid'), - 'chart': child.child_value('ntgrd'), - 'clear_type': child.child_value('ct'), - 'combo_type': child.child_value('param'), - 'achievement_rate': child.child_value('ar'), - 'score': child.child_value('scr'), - 'miss_count': child.child_value('ms'), + "id": child.child_value("mid"), + "chart": child.child_value("ntgrd"), + "clear_type": child.child_value("ct"), + "combo_type": child.child_value("param"), + "achievement_rate": child.child_value("ar"), + "score": child.child_value("scr"), + "miss_count": child.child_value("ms"), } scores.append(score) return scores @@ -324,32 +332,34 @@ class ReflecBeatGroovinUpper(BaseClient): def verify_player_rb4readepisode(self, extid: int) -> List[Dict[str, int]]: call = self.call_node() - player = Node.void('player') + player = Node.void("player") call.add_child(player) - player.set_attribute('method', 'rb4readepisode') - player.add_child(Node.s32('user_id', extid)) - player.add_child(Node.s32('limit', 20)) + player.set_attribute("method", "rb4readepisode") + player.add_child(Node.s32("user_id", extid)) + player.add_child(Node.s32("limit", 20)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) episodes = [] - for child in resp.child('player/pdata/episode').children: - if child.name != 'info': + for child in resp.child("player/pdata/episode").children: + if child.name != "info": continue - if child.child_value('user_id') != extid: - raise Exception(f'Invalid user ID returned {child.child_value("user_id")}') + if child.child_value("user_id") != extid: + raise Exception( + f'Invalid user ID returned {child.child_value("user_id")}' + ) episode = { - 'id': child.child_value('type'), - 'user': child.child_value('user_id'), - 'values': [ - child.child_value('value0'), - child.child_value('value1'), + "id": child.child_value("type"), + "user": child.child_value("user_id"), + "values": [ + child.child_value("value0"), + child.child_value("value1"), ], - 'text': child.child_value('text'), - 'time': child.child_value('time'), + "text": child.child_value("text"), + "time": child.child_value("time"), } episodes.append(episode) return episodes @@ -358,121 +368,121 @@ class ReflecBeatGroovinUpper(BaseClient): self, refid: str, loc: str, - scores: List[Dict[str, int]]=[], - episodes: List[Dict[str, Any]]=[], + scores: List[Dict[str, int]] = [], + episodes: List[Dict[str, Any]] = [], ) -> int: call = self.call_node() - player = Node.void('player') + player = Node.void("player") call.add_child(player) - player.set_attribute('method', 'rb4write') - pdata = Node.void('pdata') + player.set_attribute("method", "rb4write") + pdata = Node.void("pdata") player.add_child(pdata) - account = Node.void('account') + account = Node.void("account") pdata.add_child(account) - account.add_child(Node.s32('usrid', 0)) - account.add_child(Node.s32('plyid', 0)) - account.add_child(Node.s32('tpc', 1)) - account.add_child(Node.s32('dpc', 1)) - account.add_child(Node.s32('crd', 1)) - account.add_child(Node.s32('brd', 1)) - account.add_child(Node.s32('tdc', 1)) - account.add_child(Node.string('rid', refid)) - account.add_child(Node.string('lid', loc)) - account.add_child(Node.u8('wmode', 0)) - account.add_child(Node.u8('gmode', 0)) - account.add_child(Node.s16('ver', 1)) - account.add_child(Node.bool('pp', False)) - account.add_child(Node.bool('ps', False)) - account.add_child(Node.s16('pay', 0)) - account.add_child(Node.s16('pay_pc', 0)) - account.add_child(Node.u64('st', int(time.time() * 1000))) - account.add_child(Node.u8('debutVer', 3)) - account.add_child(Node.s32('upper_pt', 0)) - account.add_child(Node.s32('upper_op', -1)) - base = Node.void('base') + account.add_child(Node.s32("usrid", 0)) + account.add_child(Node.s32("plyid", 0)) + account.add_child(Node.s32("tpc", 1)) + account.add_child(Node.s32("dpc", 1)) + account.add_child(Node.s32("crd", 1)) + account.add_child(Node.s32("brd", 1)) + account.add_child(Node.s32("tdc", 1)) + account.add_child(Node.string("rid", refid)) + account.add_child(Node.string("lid", loc)) + account.add_child(Node.u8("wmode", 0)) + account.add_child(Node.u8("gmode", 0)) + account.add_child(Node.s16("ver", 1)) + account.add_child(Node.bool("pp", False)) + account.add_child(Node.bool("ps", False)) + account.add_child(Node.s16("pay", 0)) + account.add_child(Node.s16("pay_pc", 0)) + account.add_child(Node.u64("st", int(time.time() * 1000))) + account.add_child(Node.u8("debutVer", 3)) + account.add_child(Node.s32("upper_pt", 0)) + account.add_child(Node.s32("upper_op", -1)) + base = Node.void("base") pdata.add_child(base) - base.add_child(Node.string('name', self.NAME)) - base.add_child(Node.s32('exp', 0)) - base.add_child(Node.s32('lv', 1)) - base.add_child(Node.s32('mg', -1)) - base.add_child(Node.s32('ap', -1)) - base.add_child(Node.s32('money', 0)) - base.add_child(Node.bool('is_tut', False)) - base.add_child(Node.s32('class', -1)) - base.add_child(Node.s32('class_ar', 0)) - base.add_child(Node.s32('upper_pt', 0)) - stglog = Node.void('stglog') + base.add_child(Node.string("name", self.NAME)) + base.add_child(Node.s32("exp", 0)) + base.add_child(Node.s32("lv", 1)) + base.add_child(Node.s32("mg", -1)) + base.add_child(Node.s32("ap", -1)) + base.add_child(Node.s32("money", 0)) + base.add_child(Node.bool("is_tut", False)) + base.add_child(Node.s32("class", -1)) + base.add_child(Node.s32("class_ar", 0)) + base.add_child(Node.s32("upper_pt", 0)) + stglog = Node.void("stglog") pdata.add_child(stglog) index = 0 for score in scores: - log = Node.void('log') + log = Node.void("log") stglog.add_child(log) - log.add_child(Node.s8('stg', index)) - log.add_child(Node.s16('mid', score['id'])) - log.add_child(Node.s8('ng', score['chart'])) - log.add_child(Node.s8('col', 1)) - log.add_child(Node.s8('mt', 0)) - log.add_child(Node.s8('rt', 0)) - log.add_child(Node.s8('ct', score['clear_type'])) - log.add_child(Node.s16('param', score['combo_type'])) - log.add_child(Node.s16('grd', 0)) - log.add_child(Node.s16('ar', score['achievement_rate'])) - log.add_child(Node.s16('sc', score['score'])) - log.add_child(Node.s16('jt_jst', 0)) - log.add_child(Node.s16('jt_grt', 0)) - log.add_child(Node.s16('jt_gd', 0)) - log.add_child(Node.s16('jt_ms', score['miss_count'])) - log.add_child(Node.s16('jt_jr', 0)) - log.add_child(Node.s32('r_uid', 0)) - log.add_child(Node.s32('r_plyid', 0)) - log.add_child(Node.s8('r_stg', 0)) - log.add_child(Node.s8('r_ct', -1)) - log.add_child(Node.s16('r_sc', 0)) - log.add_child(Node.s16('r_grd', 0)) - log.add_child(Node.s16('r_ar', 0)) - log.add_child(Node.s8('r_cpuid', -1)) - log.add_child(Node.s32('time', int(time.time()))) - log.add_child(Node.s8('decide', 0)) - log.add_child(Node.s8('hazard', 0)) + log.add_child(Node.s8("stg", index)) + log.add_child(Node.s16("mid", score["id"])) + log.add_child(Node.s8("ng", score["chart"])) + log.add_child(Node.s8("col", 1)) + log.add_child(Node.s8("mt", 0)) + log.add_child(Node.s8("rt", 0)) + log.add_child(Node.s8("ct", score["clear_type"])) + log.add_child(Node.s16("param", score["combo_type"])) + log.add_child(Node.s16("grd", 0)) + log.add_child(Node.s16("ar", score["achievement_rate"])) + log.add_child(Node.s16("sc", score["score"])) + log.add_child(Node.s16("jt_jst", 0)) + log.add_child(Node.s16("jt_grt", 0)) + log.add_child(Node.s16("jt_gd", 0)) + log.add_child(Node.s16("jt_ms", score["miss_count"])) + log.add_child(Node.s16("jt_jr", 0)) + log.add_child(Node.s32("r_uid", 0)) + log.add_child(Node.s32("r_plyid", 0)) + log.add_child(Node.s8("r_stg", 0)) + log.add_child(Node.s8("r_ct", -1)) + log.add_child(Node.s16("r_sc", 0)) + log.add_child(Node.s16("r_grd", 0)) + log.add_child(Node.s16("r_ar", 0)) + log.add_child(Node.s8("r_cpuid", -1)) + log.add_child(Node.s32("time", int(time.time()))) + log.add_child(Node.s8("decide", 0)) + log.add_child(Node.s8("hazard", 0)) index = index + 1 - episode = Node.void('episode') + episode = Node.void("episode") pdata.add_child(episode) for ep in episodes: - info = Node.void('info') + info = Node.void("info") episode.add_child(info) - info.add_child(Node.s32('user_id', ep['user'])) - info.add_child(Node.u8('type', ep['id'])) - info.add_child(Node.u16('value0', ep['values'][0])) - info.add_child(Node.u16('value1', ep['values'][1])) - info.add_child(Node.string('text', ep['text'])) - info.add_child(Node.s32('time', ep['time'])) + info.add_child(Node.s32("user_id", ep["user"])) + info.add_child(Node.u8("type", ep["id"])) + info.add_child(Node.u16("value0", ep["values"][0])) + info.add_child(Node.u16("value1", ep["values"][1])) + info.add_child(Node.string("text", ep["text"])) + info.add_child(Node.s32("time", ep["time"])) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/uid") - return resp.child_value('player/uid') + return resp.child_value("player/uid") def verify_lobby_rb4read(self, location: str, extid: int) -> None: call = self.call_node() - lobby = Node.void('lobby') - lobby.set_attribute('method', 'rb4read') - lobby.add_child(Node.s32('uid', extid)) - lobby.add_child(Node.s32('plyid', 0)) - lobby.add_child(Node.u8('m_grade', 255)) - lobby.add_child(Node.string('lid', location)) - lobby.add_child(Node.s32('max', 128)) - lobby.add_child(Node.s32_array('friend', [])) - lobby.add_child(Node.u8('var', 2)) + lobby = Node.void("lobby") + lobby.set_attribute("method", "rb4read") + lobby.add_child(Node.s32("uid", extid)) + lobby.add_child(Node.s32("plyid", 0)) + lobby.add_child(Node.u8("m_grade", 255)) + lobby.add_child(Node.string("lid", location)) + lobby.add_child(Node.s32("max", 128)) + lobby.add_child(Node.s32_array("friend", [])) + lobby.add_child(Node.u8("var", 2)) call.add_child(lobby) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/lobby/interval") @@ -481,35 +491,35 @@ class ReflecBeatGroovinUpper(BaseClient): def verify_lobby_rb4entry(self, location: str, extid: int) -> int: call = self.call_node() - lobby = Node.void('lobby') - lobby.set_attribute('method', 'rb4entry') - e = Node.void('e') + lobby = Node.void("lobby") + lobby.set_attribute("method", "rb4entry") + e = Node.void("e") lobby.add_child(e) - e.add_child(Node.s32('eid', 0)) - e.add_child(Node.u16('mid', 79)) - e.add_child(Node.u8('ng', 0)) - e.add_child(Node.s32('uid', extid)) - e.add_child(Node.s32('uattr', 0)) - e.add_child(Node.string('pn', self.NAME)) - e.add_child(Node.s32('plyid', 0)) - e.add_child(Node.s16('mg', 255)) - e.add_child(Node.s32('mopt', 0)) - e.add_child(Node.string('lid', location)) - e.add_child(Node.string('sn', '')) - e.add_child(Node.u8('pref', 51)) - e.add_child(Node.s8('stg', 4)) - e.add_child(Node.s8('pside', 0)) - e.add_child(Node.s16('eatime', 30)) - e.add_child(Node.u8_array('ga', [127, 0, 0, 1])) - e.add_child(Node.u16('gp', 10007)) - e.add_child(Node.u8_array('la', [16, 0, 0, 0])) - e.add_child(Node.u8('ver', 2)) - e.add_child(Node.s8('tension', 0)) - lobby.add_child(Node.s32_array('friend', [])) + e.add_child(Node.s32("eid", 0)) + e.add_child(Node.u16("mid", 79)) + e.add_child(Node.u8("ng", 0)) + e.add_child(Node.s32("uid", extid)) + e.add_child(Node.s32("uattr", 0)) + e.add_child(Node.string("pn", self.NAME)) + e.add_child(Node.s32("plyid", 0)) + e.add_child(Node.s16("mg", 255)) + e.add_child(Node.s32("mopt", 0)) + e.add_child(Node.string("lid", location)) + e.add_child(Node.string("sn", "")) + e.add_child(Node.u8("pref", 51)) + e.add_child(Node.s8("stg", 4)) + e.add_child(Node.s8("pside", 0)) + e.add_child(Node.s16("eatime", 30)) + e.add_child(Node.u8_array("ga", [127, 0, 0, 1])) + e.add_child(Node.u16("gp", 10007)) + e.add_child(Node.u8_array("la", [16, 0, 0, 0])) + e.add_child(Node.u8("ver", 2)) + e.add_child(Node.s8("tension", 0)) + lobby.add_child(Node.s32_array("friend", [])) call.add_child(lobby) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/lobby/interval") @@ -535,18 +545,18 @@ class ReflecBeatGroovinUpper(BaseClient): self.assert_path(resp, "response/lobby/e/la") self.assert_path(resp, "response/lobby/e/ver") self.assert_path(resp, "response/lobby/e/tension") - return resp.child_value('lobby/eid') + return resp.child_value("lobby/eid") def verify_lobby_rb4delete(self, eid: int) -> None: call = self.call_node() - lobby = Node.void('lobby') - lobby.set_attribute('method', 'rb4delete') - lobby.add_child(Node.s32('eid', eid)) + lobby = Node.void("lobby") + lobby.set_attribute("method", "rb4delete") + lobby.add_child(Node.s32("eid", eid)) call.add_child(lobby) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/lobby") @@ -554,16 +564,16 @@ class ReflecBeatGroovinUpper(BaseClient): def verify_rb4pzlcmt_read(self, loc: str, extid: int) -> None: call = self.call_node() - info = Node.void('info') - info.set_attribute('method', 'rb4pzlcmt_read') - info.add_child(Node.s32('uid', extid)) - info.add_child(Node.string('lid', loc)) - info.add_child(Node.s32('time', 0)) - info.add_child(Node.s32('limit', 30)) + info = Node.void("info") + info.set_attribute("method", "rb4pzlcmt_read") + info.add_child(Node.s32("uid", extid)) + info.add_child(Node.string("lid", loc)) + info.add_child(Node.s32("time", 0)) + info.add_child(Node.s32("limit", 30)) call.add_child(info) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/info/comment/time") @@ -579,39 +589,41 @@ class ReflecBeatGroovinUpper(BaseClient): # Verify we posted our comment earlier found = False - for child in resp.child('info').children: - if child.name != 'c': + for child in resp.child("info").children: + if child.name != "c": continue - if child.child_value('uid') == extid: - name = child.child_value('name') - comment = child.child_value('comment') + if child.child_value("uid") == extid: + name = child.child_value("name") + comment = child.child_value("comment") if name != self.NAME: - raise Exception(f'Invalid name \'{name}\' returned for comment!') - if comment != 'アメ〜〜!': - raise Exception(f'Invalid comment \'{comment}\' returned for comment!') + raise Exception(f"Invalid name '{name}' returned for comment!") + if comment != "アメ〜〜!": + raise Exception( + f"Invalid comment '{comment}' returned for comment!" + ) found = True if not found: - raise Exception('Comment we posted was not found!') + raise Exception("Comment we posted was not found!") def verify_rb4pzlcmt_write(self, loc: str, extid: int) -> None: call = self.call_node() - info = Node.void('info') - info.set_attribute('method', 'rb4pzlcmt_write') - info.add_child(Node.s32('uid', extid)) - info.add_child(Node.string('name', self.NAME)) - info.add_child(Node.s16('icon', 0)) - info.add_child(Node.s8('bln', 0)) - info.add_child(Node.string('lid', loc)) - info.add_child(Node.s8('pref', 51)) - info.add_child(Node.s32('time', int(time.time()))) - info.add_child(Node.string('comment', 'アメ〜〜!')) - info.add_child(Node.bool('is_tweet', False)) + info = Node.void("info") + info.set_attribute("method", "rb4pzlcmt_write") + info.add_child(Node.s32("uid", extid)) + info.add_child(Node.string("name", self.NAME)) + info.add_child(Node.s16("icon", 0)) + info.add_child(Node.s8("bln", 0)) + info.add_child(Node.string("lid", loc)) + info.add_child(Node.s8("pref", 51)) + info.add_child(Node.s32("time", int(time.time()))) + info.add_child(Node.string("comment", "アメ〜〜!")) + info.add_child(Node.bool("is_tweet", False)) call.add_child(info) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/info/@status") @@ -619,13 +631,13 @@ class ReflecBeatGroovinUpper(BaseClient): def verify_player_rbsvLinkageSave(self, refid: str) -> None: call = self.call_node() - player = Node.void('player') + player = Node.void("player") call.add_child(player) - player.set_attribute('method', 'rbsvLinkageSave') - player.add_child(Node.string('rid', refid)) + player.set_attribute("method", "rbsvLinkageSave") + player.add_child(Node.string("rid", refid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/before_pk_value") @@ -637,20 +649,20 @@ class ReflecBeatGroovinUpper(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -671,12 +683,18 @@ class ReflecBeatGroovinUpper(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Always get a player start, regardless of new profile or not self.verify_player_rb4start(ref_id) @@ -688,13 +706,17 @@ class ReflecBeatGroovinUpper(BaseClient): ) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Verify lobby functionality self.verify_lobby_rb4read(location, extid) @@ -711,42 +733,44 @@ class ReflecBeatGroovinUpper(BaseClient): # Verify user episode functionalty episodes = self.verify_player_rb4readepisode(extid) if len(episodes) > 0: - raise Exception('Existing episodes returned on new card?') + raise Exception("Existing episodes returned on new card?") dummyepisodes = sorted( [ { - 'id': 1, - 'user': extid, - 'values': [5, 10], - 'text': 'test1', - 'time': 12345, + "id": 1, + "user": extid, + "values": [5, 10], + "text": "test1", + "time": 12345, }, { - 'id': 2, - 'user': extid, - 'values': [6, 11], - 'text': 'test2', - 'time': 54321, + "id": 2, + "user": extid, + "values": [6, 11], + "text": "test2", + "time": 54321, }, ], - key=lambda ep: cast(int, ep['id']), + key=lambda ep: cast(int, ep["id"]), ) self.verify_player_rb4write(ref_id, location, episodes=dummyepisodes) episodes = sorted( self.verify_player_rb4readepisode(extid), - key=lambda ep: ep['id'], + key=lambda ep: ep["id"], ) if len(episodes) != len(dummyepisodes): - raise Exception('Unexpected number of episodes returned!') + raise Exception("Unexpected number of episodes returned!") for i in range(len(dummyepisodes)): for key in dummyepisodes[i]: if dummyepisodes[i][key] != episodes[i][key]: - raise Exception(f'Invalid value {episodes[i][key]} returned for episode {dummyepisodes[i]["id"]} key {key}') + raise Exception( + f'Invalid value {episodes[i][key]} returned for episode {dummyepisodes[i]["id"]} key {key}' + ) # Verify we start with empty scores scores = self.verify_player_rb4readscore(ref_id, location) if len(scores) > 0: - raise Exception('Existing scores returned on new card?') + raise Exception("Existing scores returned on new card?") if cardid is None: # Verify score saving and updating @@ -755,71 +779,71 @@ class ReflecBeatGroovinUpper(BaseClient): dummyscores = [ # An okay score on a chart { - 'id': 1, - 'chart': 1, - 'clear_type': 9, - 'combo_type': 0, - 'achievement_rate': 7543, - 'score': 432, - 'miss_count': 5, + "id": 1, + "chart": 1, + "clear_type": 9, + "combo_type": 0, + "achievement_rate": 7543, + "score": 432, + "miss_count": 5, }, # A good score on an easier chart of the same song { - 'id': 1, - 'chart': 0, - 'clear_type': 9, - 'combo_type': 1, - 'achievement_rate': 9876, - 'score': 543, - 'miss_count': 0, + "id": 1, + "chart": 0, + "clear_type": 9, + "combo_type": 1, + "achievement_rate": 9876, + "score": 543, + "miss_count": 0, }, # A bad score on a hard chart { - 'id': 3, - 'chart': 2, - 'clear_type': 9, - 'combo_type': 0, - 'achievement_rate': 1234, - 'score': 123, - 'miss_count': 54, + "id": 3, + "chart": 2, + "clear_type": 9, + "combo_type": 0, + "achievement_rate": 1234, + "score": 123, + "miss_count": 54, }, # A terrible score on an easy chart { - 'id': 3, - 'chart': 0, - 'clear_type': 9, - 'combo_type': 0, - 'achievement_rate': 1024, - 'score': 50, - 'miss_count': 90, + "id": 3, + "chart": 0, + "clear_type": 9, + "combo_type": 0, + "achievement_rate": 1024, + "score": 50, + "miss_count": 90, }, ] if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 1, - 'chart': 1, - 'clear_type': 9, - 'combo_type': 0, - 'achievement_rate': 8765, - 'score': 469, - 'miss_count': 1, + "id": 1, + "chart": 1, + "clear_type": 9, + "combo_type": 0, + "achievement_rate": 8765, + "score": 469, + "miss_count": 1, }, # A worse score on another same chart { - 'id': 1, - 'chart': 0, - 'clear_type': 9, - 'combo_type': 0, - 'achievement_rate': 8765, - 'score': 432, - 'miss_count': 15, - 'expected_score': 543, - 'expected_clear_type': 9, - 'expected_combo_type': 1, - 'expected_achievement_rate': 9876, - 'expected_miss_count': 0, + "id": 1, + "chart": 0, + "clear_type": 9, + "combo_type": 0, + "achievement_rate": 8765, + "score": 432, + "miss_count": 15, + "expected_score": 543, + "expected_clear_type": 9, + "expected_combo_type": 1, + "expected_achievement_rate": 9876, + "expected_miss_count": 0, }, ] self.verify_player_rb4write(ref_id, location, scores=dummyscores) @@ -829,44 +853,61 @@ class ReflecBeatGroovinUpper(BaseClient): for expected in dummyscores: actual = None for received in scores: - if received['id'] == expected['id'] and received['chart'] == expected['chart']: + if ( + received["id"] == expected["id"] + and received["chart"] == expected["chart"] + ): actual = received break if actual is None: - raise Exception(f"Didn't find song {expected['id']} chart {expected['chart']} in response!") + raise Exception( + f"Didn't find song {expected['id']} chart {expected['chart']} in response!" + ) - if 'expected_score' in expected: - expected_score = expected['expected_score'] + if "expected_score" in expected: + expected_score = expected["expected_score"] else: - expected_score = expected['score'] - if 'expected_achievement_rate' in expected: - expected_achievement_rate = expected['expected_achievement_rate'] + expected_score = expected["score"] + if "expected_achievement_rate" in expected: + expected_achievement_rate = expected[ + "expected_achievement_rate" + ] else: - expected_achievement_rate = expected['achievement_rate'] - if 'expected_clear_type' in expected: - expected_clear_type = expected['expected_clear_type'] + expected_achievement_rate = expected["achievement_rate"] + if "expected_clear_type" in expected: + expected_clear_type = expected["expected_clear_type"] else: - expected_clear_type = expected['clear_type'] - if 'expected_combo_type' in expected: - expected_combo_type = expected['expected_combo_type'] + expected_clear_type = expected["clear_type"] + if "expected_combo_type" in expected: + expected_combo_type = expected["expected_combo_type"] else: - expected_combo_type = expected['combo_type'] - if 'expected_miss_count' in expected: - expected_miss_count = expected['expected_miss_count'] + expected_combo_type = expected["combo_type"] + if "expected_miss_count" in expected: + expected_miss_count = expected["expected_miss_count"] else: - expected_miss_count = expected['miss_count'] + expected_miss_count = expected["miss_count"] - if actual['score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'') - if actual['achievement_rate'] != expected_achievement_rate: - raise Exception(f'Expected an achievement rate of \'{expected_achievement_rate}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got achievement rate \'{actual["achievement_rate"]}\'') - if actual['clear_type'] != expected_clear_type: - raise Exception(f'Expected a clear_type of \'{expected_clear_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got clear_type \'{actual["clear_type"]}\'') - if actual['combo_type'] != expected_combo_type: - raise Exception(f'Expected a combo_type of \'{expected_combo_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got combo_type \'{actual["combo_type"]}\'') - if actual['miss_count'] != expected_miss_count: - raise Exception(f'Expected a miss count of \'{expected_miss_count}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got miss count \'{actual["miss_count"]}\'') + if actual["score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'' + ) + if actual["achievement_rate"] != expected_achievement_rate: + raise Exception( + f'Expected an achievement rate of \'{expected_achievement_rate}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got achievement rate \'{actual["achievement_rate"]}\'' + ) + if actual["clear_type"] != expected_clear_type: + raise Exception( + f'Expected a clear_type of \'{expected_clear_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got clear_type \'{actual["clear_type"]}\'' + ) + if actual["combo_type"] != expected_combo_type: + raise Exception( + f'Expected a combo_type of \'{expected_combo_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got combo_type \'{actual["combo_type"]}\'' + ) + if actual["miss_count"] != expected_miss_count: + raise Exception( + f'Expected a miss count of \'{expected_miss_count}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got miss count \'{actual["miss_count"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) diff --git a/bemani/client/reflec/limelight.py b/bemani/client/reflec/limelight.py index 7104e62..24b3944 100644 --- a/bemani/client/reflec/limelight.py +++ b/bemani/client/reflec/limelight.py @@ -8,19 +8,19 @@ from bemani.protocol import Node class ReflecBeatLimelight(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_log_pcb_status(self, loc: str) -> None: call = self.call_node() - pcb = Node.void('log') - pcb.set_attribute('method', 'pcb_status') - pcb.add_child(Node.string('lid', loc)) - pcb.add_child(Node.s32('cnt', 0)) + pcb = Node.void("log") + pcb.set_attribute("method", "pcb_status") + pcb.add_child(Node.string("lid", loc)) + pcb.add_child(Node.s32("cnt", 0)) call.add_child(pcb) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/log/@status") @@ -28,13 +28,13 @@ class ReflecBeatLimelight(BaseClient): def verify_pcbinfo_get(self, loc: str) -> None: call = self.call_node() - pcb = Node.void('pcbinfo') - pcb.set_attribute('method', 'get') - pcb.add_child(Node.string('lid', loc)) + pcb = Node.void("pcbinfo") + pcb.set_attribute("method", "get") + pcb.add_child(Node.string("lid", loc)) call.add_child(pcb) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/pcbinfo/info/name") @@ -46,12 +46,12 @@ class ReflecBeatLimelight(BaseClient): def verify_sysinfo_get(self) -> None: call = self.call_node() - info = Node.void('sysinfo') - info.set_attribute('method', 'get') + info = Node.void("sysinfo") + info.set_attribute("method", "get") call.add_child(info) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/sysinfo/trd") @@ -59,12 +59,12 @@ class ReflecBeatLimelight(BaseClient): def verify_ranking_read(self) -> None: call = self.call_node() - ranking = Node.void('ranking') - ranking.set_attribute('method', 'read') + ranking = Node.void("ranking") + ranking.set_attribute("method", "read") call.add_child(ranking) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/ranking/lic_10/time") @@ -73,14 +73,14 @@ class ReflecBeatLimelight(BaseClient): def verify_player_start(self, refid: str) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'start') - player.add_child(Node.string('rid', refid)) - player.add_child(Node.s32('ver', 0)) + player = Node.void("player") + player.set_attribute("method", "start") + player.add_child(Node.string("rid", refid)) + player.add_child(Node.s32("ver", 0)) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/is_suc") @@ -121,13 +121,13 @@ class ReflecBeatLimelight(BaseClient): def verify_player_delete(self, refid: str) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'delete') - player.add_child(Node.string('rid', refid)) + player = Node.void("player") + player.set_attribute("method", "delete") + player.add_child(Node.string("rid", refid)) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/@status") @@ -135,14 +135,14 @@ class ReflecBeatLimelight(BaseClient): def verify_player_end(self, refid: str) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'end') - player.add_child(Node.string('rid', refid)) - player.add_child(Node.s32('status', 4)) + player = Node.void("player") + player.set_attribute("method", "end") + player.add_child(Node.string("rid", refid)) + player.add_child(Node.s32("status", 4)) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player") @@ -150,15 +150,15 @@ class ReflecBeatLimelight(BaseClient): def verify_player_read(self, refid: str, location: str) -> List[Dict[str, int]]: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'read') - player.add_child(Node.string('rid', refid)) - player.add_child(Node.string('lid', location)) - player.add_child(Node.s32('ver', 2)) + player = Node.void("player") + player.set_attribute("method", "read") + player.add_child(Node.string("rid", refid)) + player.add_child(Node.string("lid", location)) + player.add_child(Node.s32("ver", 2)) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/pdata/base/uid") @@ -212,124 +212,140 @@ class ReflecBeatLimelight(BaseClient): self.assert_path(resp, "response/player/pdata/fav_music_slot") self.assert_path(resp, "response/player/pdata/narrow_down/adv_param") - if resp.child_value('player/pdata/base/name') != self.NAME: - raise Exception(f'Invalid name {resp.child_value("player/pdata/base/name")} returned on profile read!') + if resp.child_value("player/pdata/base/name") != self.NAME: + raise Exception( + f'Invalid name {resp.child_value("player/pdata/base/name")} returned on profile read!' + ) scores = [] - for child in resp.child('player/pdata/record').children: - if child.name != 'rec': + for child in resp.child("player/pdata/record").children: + if child.name != "rec": continue score = { - 'id': child.child_value('mid'), - 'chart': child.child_value('ng'), - 'clear_type': child.child_value('mrec_0/ct'), - 'achievement_rate': child.child_value('mrec_0/ar'), - 'score': child.child_value('mrec_0/bs'), - 'combo': child.child_value('mrec_0/mc'), - 'miss_count': child.child_value('mrec_0/bmc'), + "id": child.child_value("mid"), + "chart": child.child_value("ng"), + "clear_type": child.child_value("mrec_0/ct"), + "achievement_rate": child.child_value("mrec_0/ar"), + "score": child.child_value("mrec_0/bs"), + "combo": child.child_value("mrec_0/mc"), + "miss_count": child.child_value("mrec_0/bmc"), } scores.append(score) return scores - def verify_player_write(self, refid: str, extid: int, loc: str, records: List[Dict[str, int]], scores: List[Dict[str, int]]) -> int: + def verify_player_write( + self, + refid: str, + extid: int, + loc: str, + records: List[Dict[str, int]], + scores: List[Dict[str, int]], + ) -> int: call = self.call_node() - player = Node.void('player') + player = Node.void("player") call.add_child(player) - player.set_attribute('method', 'write') - player.add_child(Node.string('rid', refid)) - player.add_child(Node.string('lid', loc)) - player.add_child(Node.u64('begin_time', Time.now() * 1000)) - player.add_child(Node.u64('end_time', Time.now() * 1000)) - pdata = Node.void('pdata') + player.set_attribute("method", "write") + player.add_child(Node.string("rid", refid)) + player.add_child(Node.string("lid", loc)) + player.add_child(Node.u64("begin_time", Time.now() * 1000)) + player.add_child(Node.u64("end_time", Time.now() * 1000)) + pdata = Node.void("pdata") player.add_child(pdata) - base = Node.void('base') + base = Node.void("base") pdata.add_child(base) - base.add_child(Node.s32('uid', extid)) - base.add_child(Node.string('name', self.NAME)) - base.add_child(Node.s16('icon_id', 0)) - base.add_child(Node.s16('lv', 1)) - base.add_child(Node.s32('exp', 0)) - base.add_child(Node.s16('mg', 0)) - base.add_child(Node.s16('ap', 0)) - base.add_child(Node.s32('pc', 0)) - base.add_child(Node.s32('uattr', 0)) - con = Node.void('con') + base.add_child(Node.s32("uid", extid)) + base.add_child(Node.string("name", self.NAME)) + base.add_child(Node.s16("icon_id", 0)) + base.add_child(Node.s16("lv", 1)) + base.add_child(Node.s32("exp", 0)) + base.add_child(Node.s16("mg", 0)) + base.add_child(Node.s16("ap", 0)) + base.add_child(Node.s32("pc", 0)) + base.add_child(Node.s32("uattr", 0)) + con = Node.void("con") pdata.add_child(con) - con.add_child(Node.s32('day', 0)) - con.add_child(Node.s32('cnt', 0)) - con.add_child(Node.s32('total_cnt', 0)) - con.add_child(Node.s32('last', 0)) - con.add_child(Node.s32('now', 0)) - custom = Node.void('custom') + con.add_child(Node.s32("day", 0)) + con.add_child(Node.s32("cnt", 0)) + con.add_child(Node.s32("total_cnt", 0)) + con.add_child(Node.s32("last", 0)) + con.add_child(Node.s32("now", 0)) + custom = Node.void("custom") pdata.add_child(custom) - custom.add_child(Node.u8('s_gls', 0)) - custom.add_child(Node.u8('bgm_m', 0)) - custom.add_child(Node.u8('st_f', 0)) - custom.add_child(Node.u8('st_bg', 0)) - custom.add_child(Node.u8('st_bg_b', 100)) - custom.add_child(Node.u8('eff_e', 0)) - custom.add_child(Node.u8('se_s', 0)) - custom.add_child(Node.u8('se_s_v', 100)) - custom.add_child(Node.s16('last_music_id', 85)) - custom.add_child(Node.u8('last_note_grade', 0)) - custom.add_child(Node.u8('sort_type', 0)) - custom.add_child(Node.u8('narrowdown_type', 0)) - custom.add_child(Node.bool('is_begginer', False)) - custom.add_child(Node.bool('is_tut', False)) - custom.add_child(Node.s16_array('symbol_chat_0', [0, 1, 2, 3, 4, 5])) - custom.add_child(Node.s16_array('symbol_chat_1', [0, 1, 2, 3, 4, 5])) - custom.add_child(Node.u8('gauge_style', 0)) - custom.add_child(Node.u8('obj_shade', 0)) - custom.add_child(Node.u8('obj_size', 0)) - custom.add_child(Node.s16_array('byword', [0, 0])) - custom.add_child(Node.bool_array('is_auto_byword', [True, True])) - custom.add_child(Node.bool('is_tweet', False)) - custom.add_child(Node.bool('is_link_twitter', False)) - custom.add_child(Node.s16('mrec_type', 0)) - custom.add_child(Node.s16('card_disp_type', 0)) - custom.add_child(Node.s16('tab_sel', 0)) - custom.add_child(Node.s32_array('hidden_param', [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0])) - pdata.add_child(Node.void('released')) - pdata.add_child(Node.void('rival')) - pdata.add_child(Node.void('glass')) - pdata.add_child(Node.void('fav_music_slot')) - lincle_link_4 = Node.void('lincle_link_4') + custom.add_child(Node.u8("s_gls", 0)) + custom.add_child(Node.u8("bgm_m", 0)) + custom.add_child(Node.u8("st_f", 0)) + custom.add_child(Node.u8("st_bg", 0)) + custom.add_child(Node.u8("st_bg_b", 100)) + custom.add_child(Node.u8("eff_e", 0)) + custom.add_child(Node.u8("se_s", 0)) + custom.add_child(Node.u8("se_s_v", 100)) + custom.add_child(Node.s16("last_music_id", 85)) + custom.add_child(Node.u8("last_note_grade", 0)) + custom.add_child(Node.u8("sort_type", 0)) + custom.add_child(Node.u8("narrowdown_type", 0)) + custom.add_child(Node.bool("is_begginer", False)) + custom.add_child(Node.bool("is_tut", False)) + custom.add_child(Node.s16_array("symbol_chat_0", [0, 1, 2, 3, 4, 5])) + custom.add_child(Node.s16_array("symbol_chat_1", [0, 1, 2, 3, 4, 5])) + custom.add_child(Node.u8("gauge_style", 0)) + custom.add_child(Node.u8("obj_shade", 0)) + custom.add_child(Node.u8("obj_size", 0)) + custom.add_child(Node.s16_array("byword", [0, 0])) + custom.add_child(Node.bool_array("is_auto_byword", [True, True])) + custom.add_child(Node.bool("is_tweet", False)) + custom.add_child(Node.bool("is_link_twitter", False)) + custom.add_child(Node.s16("mrec_type", 0)) + custom.add_child(Node.s16("card_disp_type", 0)) + custom.add_child(Node.s16("tab_sel", 0)) + custom.add_child( + Node.s32_array( + "hidden_param", + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0], + ) + ) + pdata.add_child(Node.void("released")) + pdata.add_child(Node.void("rival")) + pdata.add_child(Node.void("glass")) + pdata.add_child(Node.void("fav_music_slot")) + lincle_link_4 = Node.void("lincle_link_4") pdata.add_child(lincle_link_4) - lincle_link_4.add_child(Node.u32('qpro_add', 0)) - lincle_link_4.add_child(Node.u32('glass_add', 0)) - lincle_link_4.add_child(Node.bool('for_iidx_0_0', False)) - lincle_link_4.add_child(Node.bool('for_iidx_0_1', False)) - lincle_link_4.add_child(Node.bool('for_iidx_0_2', False)) - lincle_link_4.add_child(Node.bool('for_iidx_0_3', False)) - lincle_link_4.add_child(Node.bool('for_iidx_0_4', False)) - lincle_link_4.add_child(Node.bool('for_iidx_0_5', False)) - lincle_link_4.add_child(Node.bool('for_iidx_0_6', False)) - lincle_link_4.add_child(Node.bool('for_iidx_0', False)) - lincle_link_4.add_child(Node.bool('for_iidx_1', False)) - lincle_link_4.add_child(Node.bool('for_iidx_2', False)) - lincle_link_4.add_child(Node.bool('for_iidx_3', False)) - lincle_link_4.add_child(Node.bool('for_iidx_4', False)) - lincle_link_4.add_child(Node.bool('for_rb_0_0', False)) - lincle_link_4.add_child(Node.bool('for_rb_0_1', False)) - lincle_link_4.add_child(Node.bool('for_rb_0_2', False)) - lincle_link_4.add_child(Node.bool('for_rb_0_3', False)) - lincle_link_4.add_child(Node.bool('for_rb_0_4', False)) - lincle_link_4.add_child(Node.bool('for_rb_0_5', False)) - lincle_link_4.add_child(Node.bool('for_rb_0_6', False)) - lincle_link_4.add_child(Node.bool('for_rb_0', False)) - lincle_link_4.add_child(Node.bool('for_rb_1', False)) - lincle_link_4.add_child(Node.bool('for_rb_2', False)) - lincle_link_4.add_child(Node.bool('for_rb_3', False)) - lincle_link_4.add_child(Node.bool('for_rb_4', False)) + lincle_link_4.add_child(Node.u32("qpro_add", 0)) + lincle_link_4.add_child(Node.u32("glass_add", 0)) + lincle_link_4.add_child(Node.bool("for_iidx_0_0", False)) + lincle_link_4.add_child(Node.bool("for_iidx_0_1", False)) + lincle_link_4.add_child(Node.bool("for_iidx_0_2", False)) + lincle_link_4.add_child(Node.bool("for_iidx_0_3", False)) + lincle_link_4.add_child(Node.bool("for_iidx_0_4", False)) + lincle_link_4.add_child(Node.bool("for_iidx_0_5", False)) + lincle_link_4.add_child(Node.bool("for_iidx_0_6", False)) + lincle_link_4.add_child(Node.bool("for_iidx_0", False)) + lincle_link_4.add_child(Node.bool("for_iidx_1", False)) + lincle_link_4.add_child(Node.bool("for_iidx_2", False)) + lincle_link_4.add_child(Node.bool("for_iidx_3", False)) + lincle_link_4.add_child(Node.bool("for_iidx_4", False)) + lincle_link_4.add_child(Node.bool("for_rb_0_0", False)) + lincle_link_4.add_child(Node.bool("for_rb_0_1", False)) + lincle_link_4.add_child(Node.bool("for_rb_0_2", False)) + lincle_link_4.add_child(Node.bool("for_rb_0_3", False)) + lincle_link_4.add_child(Node.bool("for_rb_0_4", False)) + lincle_link_4.add_child(Node.bool("for_rb_0_5", False)) + lincle_link_4.add_child(Node.bool("for_rb_0_6", False)) + lincle_link_4.add_child(Node.bool("for_rb_0", False)) + lincle_link_4.add_child(Node.bool("for_rb_1", False)) + lincle_link_4.add_child(Node.bool("for_rb_2", False)) + lincle_link_4.add_child(Node.bool("for_rb_3", False)) + lincle_link_4.add_child(Node.bool("for_rb_4", False)) # First, filter down to only records that are also in the battle log def key(thing: Dict[str, int]) -> str: return f'{thing["id"]}-{thing["chart"]}' updates = [key(score) for score in scores] - sortedrecords = {key(record): record for record in records if key(record) in updates} + sortedrecords = { + key(record): record for record in records if key(record) in updates + } # Now, see what records need updating and update them for score in scores: @@ -339,127 +355,131 @@ class ReflecBeatLimelight(BaseClient): else: # First time playing record = { - 'clear_type': 0, - 'achievement_rate': 0, - 'score': 0, - 'combo': 0, - 'miss_count': 999999999, + "clear_type": 0, + "achievement_rate": 0, + "score": 0, + "combo": 0, + "miss_count": 999999999, } sortedrecords[key(score)] = { - 'id': score['id'], - 'chart': score['chart'], - 'clear_type': max(record['clear_type'], score['clear_type']), - 'achievement_rate': max(record['achievement_rate'], score['achievement_rate']), - 'score': max(record['score'], score['score']), - 'combo': max(record['combo'], score['combo']), - 'miss_count': min(record['miss_count'], score['miss_count']), + "id": score["id"], + "chart": score["chart"], + "clear_type": max(record["clear_type"], score["clear_type"]), + "achievement_rate": max( + record["achievement_rate"], score["achievement_rate"] + ), + "score": max(record["score"], score["score"]), + "combo": max(record["combo"], score["combo"]), + "miss_count": min(record["miss_count"], score["miss_count"]), } # Finally, send the records and battle logs - recordnode = Node.void('record') + recordnode = Node.void("record") pdata.add_child(recordnode) - blog = Node.void('blog') + blog = Node.void("blog") pdata.add_child(blog) for (_, record) in sortedrecords.items(): - rec = Node.void('rec') + rec = Node.void("rec") recordnode.add_child(rec) - rec.add_child(Node.u16('mid', record['id'])) - rec.add_child(Node.u8('ng', record['chart'])) - rec.add_child(Node.s32('point', 2)) - rec.add_child(Node.s32('played_time', Time.now())) - mrec_0 = Node.void('mrec_0') + rec.add_child(Node.u16("mid", record["id"])) + rec.add_child(Node.u8("ng", record["chart"])) + rec.add_child(Node.s32("point", 2)) + rec.add_child(Node.s32("played_time", Time.now())) + mrec_0 = Node.void("mrec_0") rec.add_child(mrec_0) - mrec_0.add_child(Node.s32('win', 1)) - mrec_0.add_child(Node.s32('lose', 0)) - mrec_0.add_child(Node.s32('draw', 0)) - mrec_0.add_child(Node.u8('ct', record['clear_type'])) - mrec_0.add_child(Node.s16('ar', record['achievement_rate'])) - mrec_0.add_child(Node.s32('bs', record['score'])) - mrec_0.add_child(Node.s16('mc', record['combo'])) - mrec_0.add_child(Node.s16('bmc', record['miss_count'])) - mrec_1 = Node.void('mrec_1') + mrec_0.add_child(Node.s32("win", 1)) + mrec_0.add_child(Node.s32("lose", 0)) + mrec_0.add_child(Node.s32("draw", 0)) + mrec_0.add_child(Node.u8("ct", record["clear_type"])) + mrec_0.add_child(Node.s16("ar", record["achievement_rate"])) + mrec_0.add_child(Node.s32("bs", record["score"])) + mrec_0.add_child(Node.s16("mc", record["combo"])) + mrec_0.add_child(Node.s16("bmc", record["miss_count"])) + mrec_1 = Node.void("mrec_1") rec.add_child(mrec_1) - mrec_1.add_child(Node.s32('win', 0)) - mrec_1.add_child(Node.s32('lose', 0)) - mrec_1.add_child(Node.s32('draw', 0)) - mrec_1.add_child(Node.u8('ct', 0)) - mrec_1.add_child(Node.s16('ar', 0)) - mrec_1.add_child(Node.s32('bs', 0)) - mrec_1.add_child(Node.s16('mc', 0)) - mrec_1.add_child(Node.s16('bmc', -1)) + mrec_1.add_child(Node.s32("win", 0)) + mrec_1.add_child(Node.s32("lose", 0)) + mrec_1.add_child(Node.s32("draw", 0)) + mrec_1.add_child(Node.u8("ct", 0)) + mrec_1.add_child(Node.s16("ar", 0)) + mrec_1.add_child(Node.s32("bs", 0)) + mrec_1.add_child(Node.s16("mc", 0)) + mrec_1.add_child(Node.s16("bmc", -1)) scoreid = 0 for score in scores: - log = Node.void('log') + log = Node.void("log") blog.add_child(log) - log.add_child(Node.u8('id', scoreid)) - log.add_child(Node.u16('mid', score['id'])) - log.add_child(Node.u8('ng', score['chart'])) - log.add_child(Node.u8('mt', 0)) - log.add_child(Node.u8('rt', 0)) - log.add_child(Node.s32('ruid', 0)) - myself = Node.void('myself') + log.add_child(Node.u8("id", scoreid)) + log.add_child(Node.u16("mid", score["id"])) + log.add_child(Node.u8("ng", score["chart"])) + log.add_child(Node.u8("mt", 0)) + log.add_child(Node.u8("rt", 0)) + log.add_child(Node.s32("ruid", 0)) + myself = Node.void("myself") log.add_child(myself) - myself.add_child(Node.s16('mg', 0)) - myself.add_child(Node.s16('ap', 0)) - myself.add_child(Node.u8('ct', score['clear_type'])) - myself.add_child(Node.s32('s', score['score'])) - myself.add_child(Node.s16('ar', score['achievement_rate'])) - rival = Node.void('rival') + myself.add_child(Node.s16("mg", 0)) + myself.add_child(Node.s16("ap", 0)) + myself.add_child(Node.u8("ct", score["clear_type"])) + myself.add_child(Node.s32("s", score["score"])) + myself.add_child(Node.s16("ar", score["achievement_rate"])) + rival = Node.void("rival") log.add_child(rival) - rival.add_child(Node.s16('mg', 0)) - rival.add_child(Node.s16('ap', 0)) - rival.add_child(Node.u8('ct', 2)) - rival.add_child(Node.s32('s', 177)) - rival.add_child(Node.s16('ar', 500)) - log.add_child(Node.s32('time', Time.now())) + rival.add_child(Node.s16("mg", 0)) + rival.add_child(Node.s16("ap", 0)) + rival.add_child(Node.u8("ct", 2)) + rival.add_child(Node.s32("s", 177)) + rival.add_child(Node.s16("ar", 500)) + log.add_child(Node.s32("time", Time.now())) scoreid = scoreid + 1 # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/uid") self.assert_path(resp, "response/player/time") - return resp.child_value('player/uid') + return resp.child_value("player/uid") - def verify_log_play(self, extid: int, loc: str, scores: List[Dict[str, int]]) -> None: + def verify_log_play( + self, extid: int, loc: str, scores: List[Dict[str, int]] + ) -> None: call = self.call_node() - log = Node.void('log') + log = Node.void("log") call.add_child(log) - log.set_attribute('method', 'play') - log.add_child(Node.s32('uid', extid)) - log.add_child(Node.string('lid', loc)) - play = Node.void('play') + log.set_attribute("method", "play") + log.add_child(Node.s32("uid", extid)) + log.add_child(Node.string("lid", loc)) + play = Node.void("play") log.add_child(play) - play.add_child(Node.s16('stage', len(scores))) - play.add_child(Node.s32('sec', 700)) + play.add_child(Node.s16("stage", len(scores))) + play.add_child(Node.s32("sec", 700)) scoreid = 0 for score in scores: - rec = Node.void('rec') + rec = Node.void("rec") log.add_child(rec) - rec.add_child(Node.s16('idx', scoreid)) - rec.add_child(Node.s16('mid', score['id'])) - rec.add_child(Node.s16('grade', score['chart'])) - rec.add_child(Node.s16('color', 0)) - rec.add_child(Node.s16('match', 0)) - rec.add_child(Node.s16('res', 0)) - rec.add_child(Node.s32('score', score['score'])) - rec.add_child(Node.s16('mc', score['combo'])) - rec.add_child(Node.s16('jt_jr', 0)) - rec.add_child(Node.s16('jt_ju', 0)) - rec.add_child(Node.s16('jt_gr', 0)) - rec.add_child(Node.s16('jt_gd', 0)) - rec.add_child(Node.s16('jt_ms', score['miss_count'])) - rec.add_child(Node.s32('sec', 200)) + rec.add_child(Node.s16("idx", scoreid)) + rec.add_child(Node.s16("mid", score["id"])) + rec.add_child(Node.s16("grade", score["chart"])) + rec.add_child(Node.s16("color", 0)) + rec.add_child(Node.s16("match", 0)) + rec.add_child(Node.s16("res", 0)) + rec.add_child(Node.s32("score", score["score"])) + rec.add_child(Node.s16("mc", score["combo"])) + rec.add_child(Node.s16("jt_jr", 0)) + rec.add_child(Node.s16("jt_ju", 0)) + rec.add_child(Node.s16("jt_gr", 0)) + rec.add_child(Node.s16("jt_gd", 0)) + rec.add_child(Node.s16("jt_ms", score["miss_count"])) + rec.add_child(Node.s32("sec", 200)) scoreid = scoreid + 1 # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/log/@status") @@ -467,17 +487,17 @@ class ReflecBeatLimelight(BaseClient): def verify_lobby_read(self, location: str, extid: int) -> None: call = self.call_node() - lobby = Node.void('lobby') - lobby.set_attribute('method', 'read') - lobby.add_child(Node.s32('uid', extid)) - lobby.add_child(Node.u8('m_grade', 255)) - lobby.add_child(Node.string('lid', location)) - lobby.add_child(Node.s32('max', 128)) - lobby.add_child(Node.s32_array('friend', [])) + lobby = Node.void("lobby") + lobby.set_attribute("method", "read") + lobby.add_child(Node.s32("uid", extid)) + lobby.add_child(Node.u8("m_grade", 255)) + lobby.add_child(Node.string("lid", location)) + lobby.add_child(Node.s32("max", 128)) + lobby.add_child(Node.s32_array("friend", [])) call.add_child(lobby) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/lobby/interval") @@ -486,35 +506,35 @@ class ReflecBeatLimelight(BaseClient): def verify_lobby_entry(self, location: str, extid: int) -> int: call = self.call_node() - lobby = Node.void('lobby') - lobby.set_attribute('method', 'entry') - e = Node.void('e') + lobby = Node.void("lobby") + lobby.set_attribute("method", "entry") + e = Node.void("e") lobby.add_child(e) - e.add_child(Node.s32('eid', 0)) - e.add_child(Node.u16('mid', 79)) - e.add_child(Node.u8('ng', 0)) - e.add_child(Node.s32('uid', extid)) - e.add_child(Node.s32('uattr', 0)) - e.add_child(Node.string('pn', self.NAME)) - e.add_child(Node.s16('mg', 0)) - e.add_child(Node.s32('mopt', 0)) - e.add_child(Node.s32('tid', 0)) - e.add_child(Node.string('tn', '')) - e.add_child(Node.s32('topt', 0)) - e.add_child(Node.string('lid', location)) - e.add_child(Node.string('sn', '')) - e.add_child(Node.u8('pref', 51)) - e.add_child(Node.s8('stg', 0)) - e.add_child(Node.s8('pside', 0)) - e.add_child(Node.s16('eatime', 30)) - e.add_child(Node.u8_array('ga', [127, 0, 0, 1])) - e.add_child(Node.u16('gp', 10007)) - e.add_child(Node.u8_array('la', [16, 0, 0, 0])) - lobby.add_child(Node.s32_array('friend', [])) + e.add_child(Node.s32("eid", 0)) + e.add_child(Node.u16("mid", 79)) + e.add_child(Node.u8("ng", 0)) + e.add_child(Node.s32("uid", extid)) + e.add_child(Node.s32("uattr", 0)) + e.add_child(Node.string("pn", self.NAME)) + e.add_child(Node.s16("mg", 0)) + e.add_child(Node.s32("mopt", 0)) + e.add_child(Node.s32("tid", 0)) + e.add_child(Node.string("tn", "")) + e.add_child(Node.s32("topt", 0)) + e.add_child(Node.string("lid", location)) + e.add_child(Node.string("sn", "")) + e.add_child(Node.u8("pref", 51)) + e.add_child(Node.s8("stg", 0)) + e.add_child(Node.s8("pside", 0)) + e.add_child(Node.s16("eatime", 30)) + e.add_child(Node.u8_array("ga", [127, 0, 0, 1])) + e.add_child(Node.u16("gp", 10007)) + e.add_child(Node.u8_array("la", [16, 0, 0, 0])) + lobby.add_child(Node.s32_array("friend", [])) call.add_child(lobby) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/lobby/eid") @@ -540,18 +560,18 @@ class ReflecBeatLimelight(BaseClient): self.assert_path(resp, "response/lobby/e/ga") self.assert_path(resp, "response/lobby/e/gp") self.assert_path(resp, "response/lobby/e/la") - return resp.child_value('lobby/eid') + return resp.child_value("lobby/eid") def verify_lobby_delete(self, eid: int) -> None: call = self.call_node() - lobby = Node.void('lobby') - lobby.set_attribute('method', 'delete') - lobby.add_child(Node.s32('eid', eid)) + lobby = Node.void("lobby") + lobby.set_attribute("method", "delete") + lobby.add_child(Node.s32("eid", eid)) call.add_child(lobby) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/lobby") @@ -559,26 +579,26 @@ class ReflecBeatLimelight(BaseClient): def verify_event_w_update_status(self, loc: str, extid: int) -> None: call = self.call_node() - event_w = Node.void('event_w') + event_w = Node.void("event_w") call.add_child(event_w) - event_w.set_attribute('method', 'update_status') - event_w.add_child(Node.s32('uid', extid)) - event_w.add_child(Node.string('p_name', self.NAME)) - event_w.add_child(Node.s32('exp', 0)) - event_w.add_child(Node.s32('customize', 0)) - event_w.add_child(Node.s32('tid', 0)) - event_w.add_child(Node.string('t_name', '')) - event_w.add_child(Node.string('lid', loc)) - event_w.add_child(Node.string('s_name', '')) - event_w.add_child(Node.s8('pref', 51)) - event_w.add_child(Node.s32('time', Time.now())) - event_w.add_child(Node.s8('status', 1)) - event_w.add_child(Node.s8('stage', 0)) - event_w.add_child(Node.s32('mid', -1)) - event_w.add_child(Node.s8('ng', -1)) + event_w.set_attribute("method", "update_status") + event_w.add_child(Node.s32("uid", extid)) + event_w.add_child(Node.string("p_name", self.NAME)) + event_w.add_child(Node.s32("exp", 0)) + event_w.add_child(Node.s32("customize", 0)) + event_w.add_child(Node.s32("tid", 0)) + event_w.add_child(Node.string("t_name", "")) + event_w.add_child(Node.string("lid", loc)) + event_w.add_child(Node.string("s_name", "")) + event_w.add_child(Node.s8("pref", 51)) + event_w.add_child(Node.s32("time", Time.now())) + event_w.add_child(Node.s8("status", 1)) + event_w.add_child(Node.s8("stage", 0)) + event_w.add_child(Node.s32("mid", -1)) + event_w.add_child(Node.s8("ng", -1)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/event_w/@status") @@ -586,24 +606,24 @@ class ReflecBeatLimelight(BaseClient): def verify_event_w_add_comment(self, loc: str, extid: int) -> None: call = self.call_node() - event_w = Node.void('event_w') + event_w = Node.void("event_w") call.add_child(event_w) - event_w.set_attribute('method', 'add_comment') - event_w.add_child(Node.s32('uid', extid)) - event_w.add_child(Node.string('p_name', self.NAME)) - event_w.add_child(Node.s32('exp', 0)) - event_w.add_child(Node.s32('customize', 0)) - event_w.add_child(Node.s32('tid', 0)) - event_w.add_child(Node.string('t_name', '')) - event_w.add_child(Node.string('lid', loc)) - event_w.add_child(Node.string('s_name', '')) - event_w.add_child(Node.s8('pref', 51)) - event_w.add_child(Node.s32('time', Time.now())) - event_w.add_child(Node.string('comment', 'アメ〜〜!')) - event_w.add_child(Node.bool('is_tweet', False)) + event_w.set_attribute("method", "add_comment") + event_w.add_child(Node.s32("uid", extid)) + event_w.add_child(Node.string("p_name", self.NAME)) + event_w.add_child(Node.s32("exp", 0)) + event_w.add_child(Node.s32("customize", 0)) + event_w.add_child(Node.s32("tid", 0)) + event_w.add_child(Node.string("t_name", "")) + event_w.add_child(Node.string("lid", loc)) + event_w.add_child(Node.string("s_name", "")) + event_w.add_child(Node.s8("pref", 51)) + event_w.add_child(Node.s32("time", Time.now())) + event_w.add_child(Node.string("comment", "アメ〜〜!")) + event_w.add_child(Node.bool("is_tweet", False)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/event_w/@status") @@ -611,15 +631,15 @@ class ReflecBeatLimelight(BaseClient): def verify_event_r_get_all(self, extid: int) -> None: call = self.call_node() - event_r = Node.void('event_r') + event_r = Node.void("event_r") call.add_child(event_r) - event_r.set_attribute('method', 'get_all') - event_r.add_child(Node.s32('uid', extid)) - event_r.add_child(Node.s32('time', 0)) - event_r.add_child(Node.s32('limit', 30)) + event_r.set_attribute("method", "get_all") + event_r.add_child(Node.s32("uid", extid)) + event_r.add_child(Node.s32("time", 0)) + event_r.add_child(Node.s32("limit", 30)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct. We should have at least one # comment (the one we just wrote) and one status (because we just @@ -654,53 +674,55 @@ class ReflecBeatLimelight(BaseClient): # Verify we posted our comment earlier found = False - for child in resp.child('event_r/comment').children: - if child.name != 'c': + for child in resp.child("event_r/comment").children: + if child.name != "c": continue - if child.child_value('uid') == extid: - name = child.child_value('p_name') - comment = child.child_value('comment') + if child.child_value("uid") == extid: + name = child.child_value("p_name") + comment = child.child_value("comment") if name != self.NAME: - raise Exception(f'Invalid name \'{name}\' returned for comment!') - if comment != 'アメ〜〜!': - raise Exception(f'Invalid comment \'{comment}\' returned for comment!') + raise Exception(f"Invalid name '{name}' returned for comment!") + if comment != "アメ〜〜!": + raise Exception( + f"Invalid comment '{comment}' returned for comment!" + ) found = True if not found: - raise Exception('Comment we posted was not found!') + raise Exception("Comment we posted was not found!") # Verify our status came through found = False - for child in resp.child('event_r/status').children: - if child.name != 's': + for child in resp.child("event_r/status").children: + if child.name != "s": continue - if child.child_value('uid') == extid: - name = child.child_value('p_name') + if child.child_value("uid") == extid: + name = child.child_value("p_name") if name != self.NAME: - raise Exception(f'Invalid name \'{name}\' returned for status!') + raise Exception(f"Invalid name '{name}' returned for status!") found = True if not found: - raise Exception('Status was not found!') + raise Exception("Status was not found!") def verify(self, cardid: Optional[str]) -> None: # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -723,12 +745,18 @@ class ReflecBeatLimelight(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Always get a player start, regardless of new profile or not self.verify_player_start(ref_id) self.verify_player_delete(ref_id) @@ -741,13 +769,17 @@ class ReflecBeatLimelight(BaseClient): ) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Verify lobby functionality self.verify_lobby_read(location, extid) @@ -769,71 +801,71 @@ class ReflecBeatLimelight(BaseClient): dummyscores = [ # An okay score on a chart { - 'id': 1, - 'chart': 1, - 'clear_type': 2, - 'achievement_rate': 7543, - 'score': 432, - 'combo': 123, - 'miss_count': 5, + "id": 1, + "chart": 1, + "clear_type": 2, + "achievement_rate": 7543, + "score": 432, + "combo": 123, + "miss_count": 5, }, # A good score on an easier chart of the same song { - 'id': 1, - 'chart': 0, - 'clear_type': 3, - 'achievement_rate': 9876, - 'score': 543, - 'combo': 543, - 'miss_count': 0, + "id": 1, + "chart": 0, + "clear_type": 3, + "achievement_rate": 9876, + "score": 543, + "combo": 543, + "miss_count": 0, }, # A bad score on a hard chart { - 'id': 3, - 'chart': 2, - 'clear_type': 2, - 'achievement_rate': 1234, - 'score': 123, - 'combo': 42, - 'miss_count': 54, + "id": 3, + "chart": 2, + "clear_type": 2, + "achievement_rate": 1234, + "score": 123, + "combo": 42, + "miss_count": 54, }, # A terrible score on an easy chart { - 'id': 3, - 'chart': 0, - 'clear_type': 2, - 'achievement_rate': 1024, - 'score': 50, - 'combo': 12, - 'miss_count': 90, + "id": 3, + "chart": 0, + "clear_type": 2, + "achievement_rate": 1024, + "score": 50, + "combo": 12, + "miss_count": 90, }, ] if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 1, - 'chart': 1, - 'clear_type': 3, - 'achievement_rate': 8765, - 'score': 469, - 'combo': 468, - 'miss_count': 1, + "id": 1, + "chart": 1, + "clear_type": 3, + "achievement_rate": 8765, + "score": 469, + "combo": 468, + "miss_count": 1, }, # A worse score on another same chart { - 'id': 1, - 'chart': 0, - 'clear_type': 2, - 'achievement_rate': 8765, - 'score': 432, - 'combo': 321, - 'miss_count': 15, - 'expected_score': 543, - 'expected_clear_type': 3, - 'expected_achievement_rate': 9876, - 'expected_combo': 543, - 'expected_miss_count': 0, + "id": 1, + "chart": 0, + "clear_type": 2, + "achievement_rate": 8765, + "score": 432, + "combo": 321, + "miss_count": 15, + "expected_score": 543, + "expected_clear_type": 3, + "expected_achievement_rate": 9876, + "expected_combo": 543, + "expected_miss_count": 0, }, ] self.verify_player_write(ref_id, extid, location, scores, dummyscores) @@ -843,44 +875,61 @@ class ReflecBeatLimelight(BaseClient): for expected in dummyscores: actual = None for received in scores: - if received['id'] == expected['id'] and received['chart'] == expected['chart']: + if ( + received["id"] == expected["id"] + and received["chart"] == expected["chart"] + ): actual = received break if actual is None: - raise Exception(f"Didn't find song {expected['id']} chart {expected['chart']} in response!") + raise Exception( + f"Didn't find song {expected['id']} chart {expected['chart']} in response!" + ) - if 'expected_score' in expected: - expected_score = expected['expected_score'] + if "expected_score" in expected: + expected_score = expected["expected_score"] else: - expected_score = expected['score'] - if 'expected_achievement_rate' in expected: - expected_achievement_rate = expected['expected_achievement_rate'] + expected_score = expected["score"] + if "expected_achievement_rate" in expected: + expected_achievement_rate = expected[ + "expected_achievement_rate" + ] else: - expected_achievement_rate = expected['achievement_rate'] - if 'expected_clear_type' in expected: - expected_clear_type = expected['expected_clear_type'] + expected_achievement_rate = expected["achievement_rate"] + if "expected_clear_type" in expected: + expected_clear_type = expected["expected_clear_type"] else: - expected_clear_type = expected['clear_type'] - if 'expected_combo' in expected: - expected_combo = expected['expected_combo'] + expected_clear_type = expected["clear_type"] + if "expected_combo" in expected: + expected_combo = expected["expected_combo"] else: - expected_combo = expected['combo'] - if 'expected_miss_count' in expected: - expected_miss_count = expected['expected_miss_count'] + expected_combo = expected["combo"] + if "expected_miss_count" in expected: + expected_miss_count = expected["expected_miss_count"] else: - expected_miss_count = expected['miss_count'] + expected_miss_count = expected["miss_count"] - if actual['score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'') - if actual['achievement_rate'] != expected_achievement_rate: - raise Exception(f'Expected an achievement rate of \'{expected_achievement_rate}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got achievement rate \'{actual["achievement_rate"]}\'') - if actual['clear_type'] != expected_clear_type: - raise Exception(f'Expected a clear_type of \'{expected_clear_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got clear_type \'{actual["clear_type"]}\'') - if actual['combo'] != expected_combo: - raise Exception(f'Expected a combo of \'{expected_combo}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got combo \'{actual["combo"]}\'') - if actual['miss_count'] != expected_miss_count: - raise Exception(f'Expected a miss count of \'{expected_miss_count}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got miss count \'{actual["miss_count"]}\'') + if actual["score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'' + ) + if actual["achievement_rate"] != expected_achievement_rate: + raise Exception( + f'Expected an achievement rate of \'{expected_achievement_rate}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got achievement rate \'{actual["achievement_rate"]}\'' + ) + if actual["clear_type"] != expected_clear_type: + raise Exception( + f'Expected a clear_type of \'{expected_clear_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got clear_type \'{actual["clear_type"]}\'' + ) + if actual["combo"] != expected_combo: + raise Exception( + f'Expected a combo of \'{expected_combo}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got combo \'{actual["combo"]}\'' + ) + if actual["miss_count"] != expected_miss_count: + raise Exception( + f'Expected a miss count of \'{expected_miss_count}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got miss count \'{actual["miss_count"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) diff --git a/bemani/client/reflec/reflec.py b/bemani/client/reflec/reflec.py index 896576e..c710e63 100644 --- a/bemani/client/reflec/reflec.py +++ b/bemani/client/reflec/reflec.py @@ -8,19 +8,19 @@ from bemani.protocol import Node class ReflecBeat(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_log_pcb_status(self, loc: str) -> None: call = self.call_node() - pcb = Node.void('log') - pcb.set_attribute('method', 'pcb_status') - pcb.add_child(Node.string('lid', loc)) - pcb.add_child(Node.u8('type', 0)) + pcb = Node.void("log") + pcb.set_attribute("method", "pcb_status") + pcb.add_child(Node.string("lid", loc)) + pcb.add_child(Node.u8("type", 0)) call.add_child(pcb) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/log/@status") @@ -28,13 +28,13 @@ class ReflecBeat(BaseClient): def verify_pcbinfo_get(self, loc: str) -> None: call = self.call_node() - pcb = Node.void('pcbinfo') - pcb.set_attribute('method', 'get') - pcb.add_child(Node.string('lid', loc)) + pcb = Node.void("pcbinfo") + pcb.set_attribute("method", "get") + pcb.add_child(Node.string("lid", loc)) call.add_child(pcb) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/pcbinfo/info/name") @@ -46,12 +46,12 @@ class ReflecBeat(BaseClient): def verify_sysinfo_get(self) -> None: call = self.call_node() - info = Node.void('sysinfo') - info.set_attribute('method', 'get') + info = Node.void("sysinfo") + info.set_attribute("method", "get") call.add_child(info) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/sysinfo/trd") @@ -59,14 +59,14 @@ class ReflecBeat(BaseClient): def verify_sysinfo_fan(self, loc: str) -> None: call = self.call_node() - info = Node.void('sysinfo') - info.set_attribute('method', 'fan') - info.add_child(Node.u8('pref', 0)) - info.add_child(Node.string('lid', loc)) + info = Node.void("sysinfo") + info.set_attribute("method", "fan") + info.add_child(Node.u8("pref", 0)) + info.add_child(Node.string("lid", loc)) call.add_child(info) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/sysinfo/pref") @@ -75,14 +75,14 @@ class ReflecBeat(BaseClient): def verify_player_start(self, refid: str) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'start') - player.add_child(Node.string('rid', refid)) - player.add_child(Node.s32('ver', 3)) + player = Node.void("player") + player.set_attribute("method", "start") + player.add_child(Node.string("rid", refid)) + player.add_child(Node.s32("ver", 3)) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/is_suc") @@ -90,13 +90,13 @@ class ReflecBeat(BaseClient): def verify_player_delete(self, refid: str) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'delete') - player.add_child(Node.string('rid', refid)) + player = Node.void("player") + player.set_attribute("method", "delete") + player.add_child(Node.string("rid", refid)) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/@status") @@ -104,13 +104,13 @@ class ReflecBeat(BaseClient): def verify_player_end(self, refid: str) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'end') - player.add_child(Node.string('rid', refid)) + player = Node.void("player") + player.set_attribute("method", "end") + player.add_child(Node.string("rid", refid)) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player") @@ -118,15 +118,15 @@ class ReflecBeat(BaseClient): def verify_player_read(self, refid: str, location: str) -> List[Dict[str, int]]: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'read') - player.add_child(Node.string('rid', refid)) - player.add_child(Node.string('lid', location)) - player.add_child(Node.s32('ver', 3)) + player = Node.void("player") + player.set_attribute("method", "read") + player.add_child(Node.string("rid", refid)) + player.add_child(Node.string("lid", location)) + player.add_child(Node.s32("ver", 3)) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/pdata/base/uid") @@ -154,68 +154,79 @@ class ReflecBeat(BaseClient): self.assert_path(resp, "response/player/pdata/blog") self.assert_path(resp, "response/player/pdata/cmnt") - if resp.child_value('player/pdata/base/name') != self.NAME: - raise Exception(f'Invalid name {resp.child_value("player/pdata/base/name")} returned on profile read!') + if resp.child_value("player/pdata/base/name") != self.NAME: + raise Exception( + f'Invalid name {resp.child_value("player/pdata/base/name")} returned on profile read!' + ) scores = [] - for child in resp.child('player/pdata/record').children: - if child.name != 'rec': + for child in resp.child("player/pdata/record").children: + if child.name != "rec": continue score = { - 'id': child.child_value('mid'), - 'chart': child.child_value('ng'), - 'clear_type': child.child_value('ct'), - 'achievement_rate': child.child_value('ar'), - 'score': child.child_value('bs'), - 'combo': child.child_value('mc'), - 'miss_count': child.child_value('bmc'), + "id": child.child_value("mid"), + "chart": child.child_value("ng"), + "clear_type": child.child_value("ct"), + "achievement_rate": child.child_value("ar"), + "score": child.child_value("bs"), + "combo": child.child_value("mc"), + "miss_count": child.child_value("bmc"), } scores.append(score) return scores - def verify_player_write(self, refid: str, extid: int, loc: str, records: List[Dict[str, int]], scores: List[Dict[str, int]]) -> int: + def verify_player_write( + self, + refid: str, + extid: int, + loc: str, + records: List[Dict[str, int]], + scores: List[Dict[str, int]], + ) -> int: call = self.call_node() - player = Node.void('player') + player = Node.void("player") call.add_child(player) - player.set_attribute('method', 'write') - player.add_child(Node.string('rid', refid)) - player.add_child(Node.string('lid', loc)) - pdata = Node.void('pdata') + player.set_attribute("method", "write") + player.add_child(Node.string("rid", refid)) + player.add_child(Node.string("lid", loc)) + pdata = Node.void("pdata") player.add_child(pdata) - base = Node.void('base') + base = Node.void("base") pdata.add_child(base) - base.add_child(Node.s32('uid', extid)) - base.add_child(Node.string('name', self.NAME)) - base.add_child(Node.s16('lv', 1)) - base.add_child(Node.s32('exp', 0)) - base.add_child(Node.s16('mg', 0)) - base.add_child(Node.s16('ap', 0)) - base.add_child(Node.s32('flag', 0)) - con = Node.void('con') + base.add_child(Node.s32("uid", extid)) + base.add_child(Node.string("name", self.NAME)) + base.add_child(Node.s16("lv", 1)) + base.add_child(Node.s32("exp", 0)) + base.add_child(Node.s16("mg", 0)) + base.add_child(Node.s16("ap", 0)) + base.add_child(Node.s32("flag", 0)) + con = Node.void("con") pdata.add_child(con) - con.add_child(Node.s32('day', 0)) - con.add_child(Node.s32('cnt', 0)) - con.add_child(Node.s32('last', 0)) - con.add_child(Node.s32('now', 0)) - custom = Node.void('custom') + con.add_child(Node.s32("day", 0)) + con.add_child(Node.s32("cnt", 0)) + con.add_child(Node.s32("last", 0)) + con.add_child(Node.s32("now", 0)) + custom = Node.void("custom") pdata.add_child(custom) - custom.add_child(Node.u8('bgm_m', 0)) - custom.add_child(Node.u8('st_f', 0)) - custom.add_child(Node.u8('st_bg', 0)) - custom.add_child(Node.u8('st_bg_b', 100)) - custom.add_child(Node.u8('eff_e', 0)) - custom.add_child(Node.u8('se_s', 0)) - custom.add_child(Node.u8('se_s_v', 100)) - pdata.add_child(Node.void('released')) + custom.add_child(Node.u8("bgm_m", 0)) + custom.add_child(Node.u8("st_f", 0)) + custom.add_child(Node.u8("st_bg", 0)) + custom.add_child(Node.u8("st_bg_b", 100)) + custom.add_child(Node.u8("eff_e", 0)) + custom.add_child(Node.u8("se_s", 0)) + custom.add_child(Node.u8("se_s_v", 100)) + pdata.add_child(Node.void("released")) # First, filter down to only records that are also in the battle log def key(thing: Dict[str, int]) -> str: return f'{thing["id"]}-{thing["chart"]}' updates = [key(score) for score in scores] - sortedrecords = {key(record): record for record in records if key(record) in updates} + sortedrecords = { + key(record): record for record in records if key(record) in updates + } # Now, see what records need updating and update them for score in scores: @@ -225,113 +236,117 @@ class ReflecBeat(BaseClient): else: # First time playing record = { - 'clear_type': 0, - 'achievement_rate': 0, - 'score': 0, - 'combo': 0, - 'miss_count': 999999999, + "clear_type": 0, + "achievement_rate": 0, + "score": 0, + "combo": 0, + "miss_count": 999999999, } sortedrecords[key(score)] = { - 'id': score['id'], - 'chart': score['chart'], - 'clear_type': max(record['clear_type'], score['clear_type']), - 'achievement_rate': max(record['achievement_rate'], score['achievement_rate']), - 'score': max(record['score'], score['score']), - 'combo': max(record['combo'], score['combo']), - 'miss_count': min(record['miss_count'], score['miss_count']), + "id": score["id"], + "chart": score["chart"], + "clear_type": max(record["clear_type"], score["clear_type"]), + "achievement_rate": max( + record["achievement_rate"], score["achievement_rate"] + ), + "score": max(record["score"], score["score"]), + "combo": max(record["combo"], score["combo"]), + "miss_count": min(record["miss_count"], score["miss_count"]), } # Finally, send the records and battle logs - recordnode = Node.void('record') + recordnode = Node.void("record") pdata.add_child(recordnode) - blog = Node.void('blog') + blog = Node.void("blog") pdata.add_child(blog) for (_, record) in sortedrecords.items(): - rec = Node.void('rec') + rec = Node.void("rec") recordnode.add_child(rec) - rec.add_child(Node.u16('mid', record['id'])) - rec.add_child(Node.u8('ng', record['chart'])) - rec.add_child(Node.s32('win', 1)) - rec.add_child(Node.s32('lose', 0)) - rec.add_child(Node.s32('draw', 0)) - rec.add_child(Node.u8('ct', record['clear_type'])) - rec.add_child(Node.s16('ar', record['achievement_rate'])) - rec.add_child(Node.s16('bs', record['score'])) - rec.add_child(Node.s16('mc', record['combo'])) - rec.add_child(Node.s16('bmc', record['miss_count'])) + rec.add_child(Node.u16("mid", record["id"])) + rec.add_child(Node.u8("ng", record["chart"])) + rec.add_child(Node.s32("win", 1)) + rec.add_child(Node.s32("lose", 0)) + rec.add_child(Node.s32("draw", 0)) + rec.add_child(Node.u8("ct", record["clear_type"])) + rec.add_child(Node.s16("ar", record["achievement_rate"])) + rec.add_child(Node.s16("bs", record["score"])) + rec.add_child(Node.s16("mc", record["combo"])) + rec.add_child(Node.s16("bmc", record["miss_count"])) scoreid = 0 for score in scores: - log = Node.void('log') + log = Node.void("log") blog.add_child(log) - log.add_child(Node.u8('id', scoreid)) - log.add_child(Node.u16('mid', score['id'])) - log.add_child(Node.u8('ng', score['chart'])) - log.add_child(Node.u8('mt', 0)) - log.add_child(Node.u8('rt', 0)) - log.add_child(Node.s32('ruid', 0)) - myself = Node.void('myself') + log.add_child(Node.u8("id", scoreid)) + log.add_child(Node.u16("mid", score["id"])) + log.add_child(Node.u8("ng", score["chart"])) + log.add_child(Node.u8("mt", 0)) + log.add_child(Node.u8("rt", 0)) + log.add_child(Node.s32("ruid", 0)) + myself = Node.void("myself") log.add_child(myself) - myself.add_child(Node.s16('mg', 0)) - myself.add_child(Node.s16('ap', 0)) - myself.add_child(Node.u8('ct', score['clear_type'])) - myself.add_child(Node.s16('s', score['score'])) - myself.add_child(Node.s16('ar', score['achievement_rate'])) - rival = Node.void('rival') + myself.add_child(Node.s16("mg", 0)) + myself.add_child(Node.s16("ap", 0)) + myself.add_child(Node.u8("ct", score["clear_type"])) + myself.add_child(Node.s16("s", score["score"])) + myself.add_child(Node.s16("ar", score["achievement_rate"])) + rival = Node.void("rival") log.add_child(rival) - rival.add_child(Node.s16('mg', 0)) - rival.add_child(Node.s16('ap', 0)) - rival.add_child(Node.u8('ct', 2)) - rival.add_child(Node.s16('s', 177)) - rival.add_child(Node.s16('ar', 500)) - log.add_child(Node.s32('time', Time.now())) + rival.add_child(Node.s16("mg", 0)) + rival.add_child(Node.s16("ap", 0)) + rival.add_child(Node.u8("ct", 2)) + rival.add_child(Node.s16("s", 177)) + rival.add_child(Node.s16("ar", 500)) + log.add_child(Node.s32("time", Time.now())) scoreid = scoreid + 1 # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/uid") self.assert_path(resp, "response/player/time") - return resp.child_value('player/uid') + return resp.child_value("player/uid") - def verify_log_play(self, extid: int, loc: str, scores: List[Dict[str, int]]) -> None: + def verify_log_play( + self, extid: int, loc: str, scores: List[Dict[str, int]] + ) -> None: call = self.call_node() - log = Node.void('log') + log = Node.void("log") call.add_child(log) - log.set_attribute('method', 'play') - log.add_child(Node.s32('uid', extid)) - log.add_child(Node.string('lid', loc)) - play = Node.void('play') + log.set_attribute("method", "play") + log.add_child(Node.s32("uid", extid)) + log.add_child(Node.string("lid", loc)) + play = Node.void("play") log.add_child(play) - play.add_child(Node.s16('stage', len(scores))) - play.add_child(Node.s32('sec', 700)) + play.add_child(Node.s16("stage", len(scores))) + play.add_child(Node.s32("sec", 700)) scoreid = 0 for score in scores: - rec = Node.void('rec') + rec = Node.void("rec") log.add_child(rec) - rec.add_child(Node.s16('idx', scoreid)) - rec.add_child(Node.s16('mid', score['id'])) - rec.add_child(Node.s16('grade', score['chart'])) - rec.add_child(Node.s16('color', 0)) - rec.add_child(Node.s16('match', 0)) - rec.add_child(Node.s16('res', 0)) - rec.add_child(Node.s16('score', score['score'])) - rec.add_child(Node.s16('mc', score['combo'])) - rec.add_child(Node.s16('jt_jr', 0)) - rec.add_child(Node.s16('jt_ju', 0)) - rec.add_child(Node.s16('jt_gr', 0)) - rec.add_child(Node.s16('jt_gd', 0)) - rec.add_child(Node.s16('jt_ms', score['miss_count'])) - rec.add_child(Node.s32('sec', 200)) + rec.add_child(Node.s16("idx", scoreid)) + rec.add_child(Node.s16("mid", score["id"])) + rec.add_child(Node.s16("grade", score["chart"])) + rec.add_child(Node.s16("color", 0)) + rec.add_child(Node.s16("match", 0)) + rec.add_child(Node.s16("res", 0)) + rec.add_child(Node.s16("score", score["score"])) + rec.add_child(Node.s16("mc", score["combo"])) + rec.add_child(Node.s16("jt_jr", 0)) + rec.add_child(Node.s16("jt_ju", 0)) + rec.add_child(Node.s16("jt_gr", 0)) + rec.add_child(Node.s16("jt_gd", 0)) + rec.add_child(Node.s16("jt_ms", score["miss_count"])) + rec.add_child(Node.s32("sec", 200)) scoreid = scoreid + 1 # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/log/@status") @@ -339,16 +354,16 @@ class ReflecBeat(BaseClient): def verify_lobby_read(self, location: str, extid: int) -> None: call = self.call_node() - lobby = Node.void('lobby') - lobby.set_attribute('method', 'read') - lobby.add_child(Node.s32('uid', extid)) - lobby.add_child(Node.u8('m_grade', 255)) - lobby.add_child(Node.string('lid', location)) - lobby.add_child(Node.s32('max', 128)) + lobby = Node.void("lobby") + lobby.set_attribute("method", "read") + lobby.add_child(Node.s32("uid", extid)) + lobby.add_child(Node.u8("m_grade", 255)) + lobby.add_child(Node.string("lid", location)) + lobby.add_child(Node.s32("max", 128)) call.add_child(lobby) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/lobby/@status") @@ -356,29 +371,29 @@ class ReflecBeat(BaseClient): def verify_lobby_entry(self, location: str, extid: int) -> int: call = self.call_node() - lobby = Node.void('lobby') - lobby.set_attribute('method', 'entry') - e = Node.void('e') + lobby = Node.void("lobby") + lobby.set_attribute("method", "entry") + e = Node.void("e") lobby.add_child(e) - e.add_child(Node.s32('eid', 0)) - e.add_child(Node.u16('mid', 79)) - e.add_child(Node.u8('ng', 0)) - e.add_child(Node.s32('uid', extid)) - e.add_child(Node.string('pn', self.NAME)) - e.add_child(Node.s32('exp', 0)) - e.add_child(Node.u8('mg', 0)) - e.add_child(Node.s32('tid', 0)) - e.add_child(Node.string('tn', '')) - e.add_child(Node.string('lid', location)) - e.add_child(Node.string('sn', '')) - e.add_child(Node.u8('pref', 51)) - e.add_child(Node.u8_array('ga', [127, 0, 0, 1])) - e.add_child(Node.u16('gp', 10007)) - e.add_child(Node.u8_array('la', [16, 0, 0, 0])) + e.add_child(Node.s32("eid", 0)) + e.add_child(Node.u16("mid", 79)) + e.add_child(Node.u8("ng", 0)) + e.add_child(Node.s32("uid", extid)) + e.add_child(Node.string("pn", self.NAME)) + e.add_child(Node.s32("exp", 0)) + e.add_child(Node.u8("mg", 0)) + e.add_child(Node.s32("tid", 0)) + e.add_child(Node.string("tn", "")) + e.add_child(Node.string("lid", location)) + e.add_child(Node.string("sn", "")) + e.add_child(Node.u8("pref", 51)) + e.add_child(Node.u8_array("ga", [127, 0, 0, 1])) + e.add_child(Node.u16("gp", 10007)) + e.add_child(Node.u8_array("la", [16, 0, 0, 0])) call.add_child(lobby) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/lobby/eid") @@ -397,18 +412,18 @@ class ReflecBeat(BaseClient): self.assert_path(resp, "response/lobby/e/ga") self.assert_path(resp, "response/lobby/e/gp") self.assert_path(resp, "response/lobby/e/la") - return resp.child_value('lobby/eid') + return resp.child_value("lobby/eid") def verify_lobby_delete(self, eid: int) -> None: call = self.call_node() - lobby = Node.void('lobby') - lobby.set_attribute('method', 'delete') - lobby.add_child(Node.s32('eid', eid)) + lobby = Node.void("lobby") + lobby.set_attribute("method", "delete") + lobby.add_child(Node.s32("eid", eid)) call.add_child(lobby) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/lobby") @@ -417,20 +432,20 @@ class ReflecBeat(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -453,12 +468,18 @@ class ReflecBeat(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Always get a player start, regardless of new profile or not self.verify_player_start(ref_id) self.verify_player_delete(ref_id) @@ -471,13 +492,17 @@ class ReflecBeat(BaseClient): ) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Verify lobby functionality self.verify_lobby_read(location, extid) @@ -494,71 +519,71 @@ class ReflecBeat(BaseClient): dummyscores = [ # An okay score on a chart { - 'id': 1, - 'chart': 1, - 'clear_type': 2, - 'achievement_rate': 7543, - 'score': 432, - 'combo': 123, - 'miss_count': 5, + "id": 1, + "chart": 1, + "clear_type": 2, + "achievement_rate": 7543, + "score": 432, + "combo": 123, + "miss_count": 5, }, # A good score on an easier chart of the same song { - 'id': 1, - 'chart': 0, - 'clear_type': 3, - 'achievement_rate': 9876, - 'score': 543, - 'combo': 543, - 'miss_count': 0, + "id": 1, + "chart": 0, + "clear_type": 3, + "achievement_rate": 9876, + "score": 543, + "combo": 543, + "miss_count": 0, }, # A bad score on a hard chart { - 'id': 3, - 'chart': 2, - 'clear_type': 2, - 'achievement_rate': 1234, - 'score': 123, - 'combo': 42, - 'miss_count': 54, + "id": 3, + "chart": 2, + "clear_type": 2, + "achievement_rate": 1234, + "score": 123, + "combo": 42, + "miss_count": 54, }, # A terrible score on an easy chart { - 'id': 3, - 'chart': 0, - 'clear_type': 2, - 'achievement_rate': 1024, - 'score': 50, - 'combo': 12, - 'miss_count': 90, + "id": 3, + "chart": 0, + "clear_type": 2, + "achievement_rate": 1024, + "score": 50, + "combo": 12, + "miss_count": 90, }, ] if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 1, - 'chart': 1, - 'clear_type': 3, - 'achievement_rate': 8765, - 'score': 469, - 'combo': 468, - 'miss_count': 1, + "id": 1, + "chart": 1, + "clear_type": 3, + "achievement_rate": 8765, + "score": 469, + "combo": 468, + "miss_count": 1, }, # A worse score on another same chart { - 'id': 1, - 'chart': 0, - 'clear_type': 2, - 'achievement_rate': 8765, - 'score': 432, - 'combo': 321, - 'miss_count': 15, - 'expected_score': 543, - 'expected_clear_type': 3, - 'expected_achievement_rate': 9876, - 'expected_combo': 543, - 'expected_miss_count': 0, + "id": 1, + "chart": 0, + "clear_type": 2, + "achievement_rate": 8765, + "score": 432, + "combo": 321, + "miss_count": 15, + "expected_score": 543, + "expected_clear_type": 3, + "expected_achievement_rate": 9876, + "expected_combo": 543, + "expected_miss_count": 0, }, ] self.verify_player_write(ref_id, extid, location, scores, dummyscores) @@ -568,44 +593,61 @@ class ReflecBeat(BaseClient): for expected in dummyscores: actual = None for received in scores: - if received['id'] == expected['id'] and received['chart'] == expected['chart']: + if ( + received["id"] == expected["id"] + and received["chart"] == expected["chart"] + ): actual = received break if actual is None: - raise Exception(f"Didn't find song {expected['id']} chart {expected['chart']} in response!") + raise Exception( + f"Didn't find song {expected['id']} chart {expected['chart']} in response!" + ) - if 'expected_score' in expected: - expected_score = expected['expected_score'] + if "expected_score" in expected: + expected_score = expected["expected_score"] else: - expected_score = expected['score'] - if 'expected_achievement_rate' in expected: - expected_achievement_rate = expected['expected_achievement_rate'] + expected_score = expected["score"] + if "expected_achievement_rate" in expected: + expected_achievement_rate = expected[ + "expected_achievement_rate" + ] else: - expected_achievement_rate = expected['achievement_rate'] - if 'expected_clear_type' in expected: - expected_clear_type = expected['expected_clear_type'] + expected_achievement_rate = expected["achievement_rate"] + if "expected_clear_type" in expected: + expected_clear_type = expected["expected_clear_type"] else: - expected_clear_type = expected['clear_type'] - if 'expected_combo' in expected: - expected_combo = expected['expected_combo'] + expected_clear_type = expected["clear_type"] + if "expected_combo" in expected: + expected_combo = expected["expected_combo"] else: - expected_combo = expected['combo'] - if 'expected_miss_count' in expected: - expected_miss_count = expected['expected_miss_count'] + expected_combo = expected["combo"] + if "expected_miss_count" in expected: + expected_miss_count = expected["expected_miss_count"] else: - expected_miss_count = expected['miss_count'] + expected_miss_count = expected["miss_count"] - if actual['score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'') - if actual['achievement_rate'] != expected_achievement_rate: - raise Exception(f'Expected an achievement rate of \'{expected_achievement_rate}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got achievement rate \'{actual["achievement_rate"]}\'') - if actual['clear_type'] != expected_clear_type: - raise Exception(f'Expected a clear_type of \'{expected_clear_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got clear_type \'{actual["clear_type"]}\'') - if actual['combo'] != expected_combo: - raise Exception(f'Expected a combo of \'{expected_combo}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got combo \'{actual["combo"]}\'') - if actual['miss_count'] != expected_miss_count: - raise Exception(f'Expected a miss count of \'{expected_miss_count}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got miss count \'{actual["miss_count"]}\'') + if actual["score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'' + ) + if actual["achievement_rate"] != expected_achievement_rate: + raise Exception( + f'Expected an achievement rate of \'{expected_achievement_rate}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got achievement rate \'{actual["achievement_rate"]}\'' + ) + if actual["clear_type"] != expected_clear_type: + raise Exception( + f'Expected a clear_type of \'{expected_clear_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got clear_type \'{actual["clear_type"]}\'' + ) + if actual["combo"] != expected_combo: + raise Exception( + f'Expected a combo of \'{expected_combo}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got combo \'{actual["combo"]}\'' + ) + if actual["miss_count"] != expected_miss_count: + raise Exception( + f'Expected a miss count of \'{expected_miss_count}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got miss count \'{actual["miss_count"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) diff --git a/bemani/client/reflec/volzza.py b/bemani/client/reflec/volzza.py index 5749924..812e16a 100644 --- a/bemani/client/reflec/volzza.py +++ b/bemani/client/reflec/volzza.py @@ -7,19 +7,19 @@ from bemani.protocol import Node class ReflecBeatVolzza(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_pcb_rb5_pcb_boot(self, loc: str) -> None: call = self.call_node() - pcb = Node.void('pcb') - pcb.set_attribute('method', 'rb5_pcb_boot') - pcb.add_child(Node.string('lid', loc)) - pcb.add_child(Node.string('rno', 'MBR-JA-C01')) + pcb = Node.void("pcb") + pcb.set_attribute("method", "rb5_pcb_boot") + pcb.add_child(Node.string("lid", loc)) + pcb.add_child(Node.string("rno", "MBR-JA-C01")) call.add_child(pcb) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/pcb/sinfo/nm") @@ -31,15 +31,15 @@ class ReflecBeatVolzza(BaseClient): def verify_pcb_rb5_pcb_error(self, loc: str) -> None: call = self.call_node() - pcb = Node.void('pcb') + pcb = Node.void("pcb") call.add_child(pcb) - pcb.set_attribute('method', 'rb5_pcb_error') - pcb.add_child(Node.string('lid', loc)) - pcb.add_child(Node.string('code', 'exception')) - pcb.add_child(Node.string('msg', 'exceptionstring')) + pcb.set_attribute("method", "rb5_pcb_error") + pcb.add_child(Node.string("lid", loc)) + pcb.add_child(Node.string("code", "exception")) + pcb.add_child(Node.string("msg", "exceptionstring")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/pcb/@status") @@ -47,13 +47,13 @@ class ReflecBeatVolzza(BaseClient): def verify_info_rb5_info_read(self, loc: str) -> None: call = self.call_node() - info = Node.void('info') + info = Node.void("info") call.add_child(info) - info.set_attribute('method', 'rb5_info_read') - info.add_child(Node.string('lid', loc)) + info.set_attribute("method", "rb5_info_read") + info.add_child(Node.string("lid", loc)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/info/event_ctrl") @@ -62,17 +62,17 @@ class ReflecBeatVolzza(BaseClient): def verify_info_rb5_info_read_shop_ranking(self, loc: str) -> None: call = self.call_node() - info = Node.void('info') + info = Node.void("info") call.add_child(info) - info.set_attribute('method', 'rb5_info_read_shop_ranking') + info.set_attribute("method", "rb5_info_read_shop_ranking") # Arbitrarily chosen based on the song IDs we send in the # score section below. - info.add_child(Node.s16('min', 1)) - info.add_child(Node.s16('max', 10)) - info.add_child(Node.string('lid', loc)) + info.add_child(Node.s16("min", 1)) + info.add_child(Node.s16("max", 10)) + info.add_child(Node.string("lid", loc)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/info/shop_score/time") @@ -89,13 +89,13 @@ class ReflecBeatVolzza(BaseClient): def verify_info_rb5_info_read_hit_chart(self) -> None: call = self.call_node() - info = Node.void('info') - info.set_attribute('method', 'rb5_info_read_hit_chart') - info.add_child(Node.s32('ver', 0)) + info = Node.void("info") + info.set_attribute("method", "rb5_info_read_hit_chart") + info.add_child(Node.s32("ver", 0)) call.add_child(info) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/info/ver") @@ -115,18 +115,22 @@ class ReflecBeatVolzza(BaseClient): def verify_player_rb5_player_start(self, refid: str) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'rb5_player_start') - player.add_child(Node.string('rid', refid)) - player.add_child(Node.u8_array('ga', [127, 0, 0, 1])) - player.add_child(Node.u16('gp', 10573)) - player.add_child(Node.u8_array('la', [16, 0, 0, 0])) - player.add_child(Node.u8_array('pnid', [39, 16, 0, 0, 0, 23, 62, 60, 39, 127, 0, 0, 1, 23, 62, 60])) + player = Node.void("player") + player.set_attribute("method", "rb5_player_start") + player.add_child(Node.string("rid", refid)) + player.add_child(Node.u8_array("ga", [127, 0, 0, 1])) + player.add_child(Node.u16("gp", 10573)) + player.add_child(Node.u8_array("la", [16, 0, 0, 0])) + player.add_child( + Node.u8_array( + "pnid", [39, 16, 0, 0, 0, 23, 62, 60, 39, 127, 0, 0, 1, 23, 62, 60] + ) + ) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/plyid") @@ -137,13 +141,13 @@ class ReflecBeatVolzza(BaseClient): def verify_player_rb5_player_end(self, refid: str) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'rb5_player_end') - player.add_child(Node.string('rid', refid)) + player = Node.void("player") + player.set_attribute("method", "rb5_player_end") + player.add_child(Node.string("rid", refid)) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player") @@ -151,16 +155,16 @@ class ReflecBeatVolzza(BaseClient): def verify_player_rb5_player_read_rank(self) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'rb5_player_read_rank') - player.add_child(Node.s32('uid', 0)) - player.add_child(Node.s32_array('sc', [897, 897, 0, 0, 0])) - player.add_child(Node.s8('mg_id', 0)) - player.add_child(Node.s32('mg_sc', 220)) + player = Node.void("player") + player.set_attribute("method", "rb5_player_read_rank") + player.add_child(Node.s32("uid", 0)) + player.add_child(Node.s32_array("sc", [897, 897, 0, 0, 0])) + player.add_child(Node.s8("mg_id", 0)) + player.add_child(Node.s32("mg_sc", 220)) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/tbs/new_rank") @@ -171,15 +175,15 @@ class ReflecBeatVolzza(BaseClient): def verify_player_rb5_player_read_rival_score(self, extid: int) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'rb5_player_read_rival_score') - player.add_child(Node.s32('uid', extid)) - player.add_child(Node.s32('music_id', 6)) - player.add_child(Node.s32('note_grade', 0)) + player = Node.void("player") + player.set_attribute("method", "rb5_player_read_rival_score") + player.add_child(Node.s32("uid", extid)) + player.add_child(Node.s32("music_id", 6)) + player.add_child(Node.s32("note_grade", 0)) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/@status") @@ -192,11 +196,11 @@ class ReflecBeatVolzza(BaseClient): self.assert_path(resp, "response/player/player_select_score/m_scoreTime") self.assert_path(resp, "response/player/player_select_score/m_iconID") - if resp.child_value('player/player_select_score/name') != self.NAME: + if resp.child_value("player/player_select_score/name") != self.NAME: raise Exception( f'Invalid name {resp.child_value("player/player_select_score/name")} returned on score read!' ) - if resp.child_value('player/player_select_score/user_id') != extid: + if resp.child_value("player/player_select_score/user_id") != extid: raise Exception( f'Invalid name {resp.child_value("player/player_select_score/user_id")} returned on score read!' ) @@ -204,14 +208,14 @@ class ReflecBeatVolzza(BaseClient): def verify_player_rb5_player_read_rival_ranking_data(self, extid: int) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'rb5_player_read_rival_ranking_data') - player.add_child(Node.s32('uid', extid)) - player.add_child(Node.s8('mgid', -1)) + player = Node.void("player") + player.set_attribute("method", "rb5_player_read_rival_ranking_data") + player.add_child(Node.s32("uid", extid)) + player.add_child(Node.s8("mgid", -1)) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/rival_data/rl/uid") @@ -220,7 +224,7 @@ class ReflecBeatVolzza(BaseClient): self.assert_path(resp, "response/player/rival_data/rl/sl/mid") self.assert_path(resp, "response/player/rival_data/rl/sl/m") self.assert_path(resp, "response/player/rival_data/rl/sl/t") - if resp.child_value('player/rival_data/rl/nm') != self.NAME: + if resp.child_value("player/rival_data/rl/nm") != self.NAME: raise Exception( f'Invalid name {resp.child_value("player/rival_data/rl/nm")} returned on rival ranking read!' ) @@ -228,13 +232,13 @@ class ReflecBeatVolzza(BaseClient): def verify_player_rb5_player_succeed(self, refid: str) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'rb5_player_succeed') - player.add_child(Node.string('rid', refid)) + player = Node.void("player") + player.set_attribute("method", "rb5_player_succeed") + player.add_child(Node.string("rid", refid)) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/name") @@ -242,20 +246,22 @@ class ReflecBeatVolzza(BaseClient): self.assert_path(resp, "response/player/ap") self.assert_path(resp, "response/player/uattr") - def verify_player_rb5_player_read(self, refid: str, cardid: str, location: str) -> None: + def verify_player_rb5_player_read( + self, refid: str, cardid: str, location: str + ) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'rb5_player_read') - player.add_child(Node.string('rid', refid)) - player.add_child(Node.string('lid', location)) - player.add_child(Node.s16('ver', 0)) - player.add_child(Node.string('card_id', cardid)) - player.add_child(Node.s16('card_type', 1)) + player = Node.void("player") + player.set_attribute("method", "rb5_player_read") + player.add_child(Node.string("rid", refid)) + player.add_child(Node.string("lid", location)) + player.add_child(Node.s16("ver", 0)) + player.add_child(Node.string("card_id", cardid)) + player.add_child(Node.s16("card_type", 1)) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/pdata/account/usrid") @@ -296,49 +302,53 @@ class ReflecBeatVolzza(BaseClient): self.assert_path(resp, "response/player/pdata/mylist/list/idx") self.assert_path(resp, "response/player/pdata/mylist/list/mlst") - if resp.child_value('player/pdata/base/name') != self.NAME: - raise Exception(f'Invalid name {resp.child_value("player/pdata/base/name")} returned on profile read!') + if resp.child_value("player/pdata/base/name") != self.NAME: + raise Exception( + f'Invalid name {resp.child_value("player/pdata/base/name")} returned on profile read!' + ) - def verify_player_rb5_player_read_score(self, refid: str, location: str) -> List[Dict[str, int]]: + def verify_player_rb5_player_read_score( + self, refid: str, location: str + ) -> List[Dict[str, int]]: call = self.call_node() - player = Node.void('player') + player = Node.void("player") call.add_child(player) - player.set_attribute('method', 'rb5_player_read_score') - player.add_child(Node.string('rid', refid)) - player.add_child(Node.s16('ver', 1)) + player.set_attribute("method", "rb5_player_read_score") + player.add_child(Node.string("rid", refid)) + player.add_child(Node.s16("ver", 1)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) scores = [] - for child in resp.child('player/pdata/record').children: - if child.name != 'rec': + for child in resp.child("player/pdata/record").children: + if child.name != "rec": continue - self.assert_path(child, 'rec/mid') - self.assert_path(child, 'rec/ntgrd') - self.assert_path(child, 'rec/pc') - self.assert_path(child, 'rec/ct') - self.assert_path(child, 'rec/ar') - self.assert_path(child, 'rec/scr') - self.assert_path(child, 'rec/ms') - self.assert_path(child, 'rec/param') - self.assert_path(child, 'rec/bscrt') - self.assert_path(child, 'rec/bart') - self.assert_path(child, 'rec/bctt') - self.assert_path(child, 'rec/bmst') - self.assert_path(child, 'rec/time') - self.assert_path(child, 'rec/k_flag') + self.assert_path(child, "rec/mid") + self.assert_path(child, "rec/ntgrd") + self.assert_path(child, "rec/pc") + self.assert_path(child, "rec/ct") + self.assert_path(child, "rec/ar") + self.assert_path(child, "rec/scr") + self.assert_path(child, "rec/ms") + self.assert_path(child, "rec/param") + self.assert_path(child, "rec/bscrt") + self.assert_path(child, "rec/bart") + self.assert_path(child, "rec/bctt") + self.assert_path(child, "rec/bmst") + self.assert_path(child, "rec/time") + self.assert_path(child, "rec/k_flag") score = { - 'id': child.child_value('mid'), - 'chart': child.child_value('ntgrd'), - 'clear_type': child.child_value('ct'), - 'combo_type': child.child_value('param'), - 'achievement_rate': child.child_value('ar'), - 'score': child.child_value('scr'), - 'miss_count': child.child_value('ms'), + "id": child.child_value("mid"), + "chart": child.child_value("ntgrd"), + "clear_type": child.child_value("ct"), + "combo_type": child.child_value("param"), + "achievement_rate": child.child_value("ar"), + "score": child.child_value("scr"), + "miss_count": child.child_value("ms"), } scores.append(score) return scores @@ -347,115 +357,115 @@ class ReflecBeatVolzza(BaseClient): self, refid: str, loc: str, - scores: List[Dict[str, int]]=[], - rivals: List[int]=[], + scores: List[Dict[str, int]] = [], + rivals: List[int] = [], ) -> int: call = self.call_node() - player = Node.void('player') + player = Node.void("player") call.add_child(player) - player.set_attribute('method', 'rb5_player_write') - pdata = Node.void('pdata') + player.set_attribute("method", "rb5_player_write") + pdata = Node.void("pdata") player.add_child(pdata) - account = Node.void('account') + account = Node.void("account") pdata.add_child(account) - account.add_child(Node.s32('usrid', 0)) - account.add_child(Node.s32('plyid', 0)) - account.add_child(Node.s32('tpc', 1)) - account.add_child(Node.s32('dpc', 1)) - account.add_child(Node.s32('crd', 1)) - account.add_child(Node.s32('brd', 1)) - account.add_child(Node.s32('tdc', 1)) - account.add_child(Node.string('rid', refid)) - account.add_child(Node.string('lid', loc)) - account.add_child(Node.u8('wmode', 0)) - account.add_child(Node.u8('gmode', 0)) - account.add_child(Node.s16('ver', 0)) - account.add_child(Node.bool('pp', False)) - account.add_child(Node.bool('ps', False)) - account.add_child(Node.bool('continue', False)) - account.add_child(Node.bool('firstfree', False)) - account.add_child(Node.s16('pay', 0)) - account.add_child(Node.s16('pay_pc', 0)) - account.add_child(Node.u64('st', int(time.time() * 1000))) - base = Node.void('base') + account.add_child(Node.s32("usrid", 0)) + account.add_child(Node.s32("plyid", 0)) + account.add_child(Node.s32("tpc", 1)) + account.add_child(Node.s32("dpc", 1)) + account.add_child(Node.s32("crd", 1)) + account.add_child(Node.s32("brd", 1)) + account.add_child(Node.s32("tdc", 1)) + account.add_child(Node.string("rid", refid)) + account.add_child(Node.string("lid", loc)) + account.add_child(Node.u8("wmode", 0)) + account.add_child(Node.u8("gmode", 0)) + account.add_child(Node.s16("ver", 0)) + account.add_child(Node.bool("pp", False)) + account.add_child(Node.bool("ps", False)) + account.add_child(Node.bool("continue", False)) + account.add_child(Node.bool("firstfree", False)) + account.add_child(Node.s16("pay", 0)) + account.add_child(Node.s16("pay_pc", 0)) + account.add_child(Node.u64("st", int(time.time() * 1000))) + base = Node.void("base") pdata.add_child(base) - base.add_child(Node.string('name', self.NAME)) - base.add_child(Node.s32('mg', 0)) - base.add_child(Node.s32('ap', 0)) - base.add_child(Node.s32('uattr', 0)) - base.add_child(Node.s32('money', 0)) - base.add_child(Node.bool('is_tut', False)) - base.add_child(Node.s32('class', -1)) - base.add_child(Node.s32('class_ar', 0)) - stglog = Node.void('stglog') + base.add_child(Node.string("name", self.NAME)) + base.add_child(Node.s32("mg", 0)) + base.add_child(Node.s32("ap", 0)) + base.add_child(Node.s32("uattr", 0)) + base.add_child(Node.s32("money", 0)) + base.add_child(Node.bool("is_tut", False)) + base.add_child(Node.s32("class", -1)) + base.add_child(Node.s32("class_ar", 0)) + stglog = Node.void("stglog") pdata.add_child(stglog) index = 0 for score in scores: - log = Node.void('log') + log = Node.void("log") stglog.add_child(log) - log.add_child(Node.s8('stg', index)) - log.add_child(Node.s16('mid', score['id'])) - log.add_child(Node.s8('ng', score['chart'])) - log.add_child(Node.s8('col', 1)) - log.add_child(Node.s8('mt', 0)) - log.add_child(Node.s8('rt', 0)) - log.add_child(Node.s8('ct', score['clear_type'])) - log.add_child(Node.s16('param', score['combo_type'])) - log.add_child(Node.s16('grd', 0)) - log.add_child(Node.s16('ar', score['achievement_rate'])) - log.add_child(Node.s16('sc', score['score'])) - log.add_child(Node.s16('jt_jst', 0)) - log.add_child(Node.s16('jt_grt', 0)) - log.add_child(Node.s16('jt_gd', 0)) - log.add_child(Node.s16('jt_ms', score['miss_count'])) - log.add_child(Node.s16('jt_jr', 0)) - log.add_child(Node.s32('r_uid', 0)) - log.add_child(Node.s32('r_plyid', 0)) - log.add_child(Node.s8('r_stg', 0)) - log.add_child(Node.s8('r_ct', -1)) - log.add_child(Node.s16('r_sc', 0)) - log.add_child(Node.s16('r_grd', 0)) - log.add_child(Node.s16('r_ar', 0)) - log.add_child(Node.s8('r_cpuid', -1)) - log.add_child(Node.s32('time', int(time.time()))) - log.add_child(Node.s8('decide', 0)) - log.add_child(Node.s16('g_gauge', 5000)) - log.add_child(Node.s32('k_flag', 0)) + log.add_child(Node.s8("stg", index)) + log.add_child(Node.s16("mid", score["id"])) + log.add_child(Node.s8("ng", score["chart"])) + log.add_child(Node.s8("col", 1)) + log.add_child(Node.s8("mt", 0)) + log.add_child(Node.s8("rt", 0)) + log.add_child(Node.s8("ct", score["clear_type"])) + log.add_child(Node.s16("param", score["combo_type"])) + log.add_child(Node.s16("grd", 0)) + log.add_child(Node.s16("ar", score["achievement_rate"])) + log.add_child(Node.s16("sc", score["score"])) + log.add_child(Node.s16("jt_jst", 0)) + log.add_child(Node.s16("jt_grt", 0)) + log.add_child(Node.s16("jt_gd", 0)) + log.add_child(Node.s16("jt_ms", score["miss_count"])) + log.add_child(Node.s16("jt_jr", 0)) + log.add_child(Node.s32("r_uid", 0)) + log.add_child(Node.s32("r_plyid", 0)) + log.add_child(Node.s8("r_stg", 0)) + log.add_child(Node.s8("r_ct", -1)) + log.add_child(Node.s16("r_sc", 0)) + log.add_child(Node.s16("r_grd", 0)) + log.add_child(Node.s16("r_ar", 0)) + log.add_child(Node.s8("r_cpuid", -1)) + log.add_child(Node.s32("time", int(time.time()))) + log.add_child(Node.s8("decide", 0)) + log.add_child(Node.s16("g_gauge", 5000)) + log.add_child(Node.s32("k_flag", 0)) index = index + 1 - rivalnode = Node.void('rival') + rivalnode = Node.void("rival") pdata.add_child(rivalnode) for rival in rivals: - r = Node.void('r') + r = Node.void("r") rivalnode.add_child(r) - r.add_child(Node.s32('id', rival)) - r.add_child(Node.s8('regist_type', 3)) + r.add_child(Node.s32("id", rival)) + r.add_child(Node.s8("regist_type", 3)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/uid") - return resp.child_value('player/uid') + return resp.child_value("player/uid") def verify_lobby_rb5_lobby_read(self, location: str, extid: int) -> None: call = self.call_node() - lobby = Node.void('lobby') - lobby.set_attribute('method', 'rb5_lobby_read') - lobby.add_child(Node.s32('uid', extid)) - lobby.add_child(Node.s32('plyid', 0)) - lobby.add_child(Node.u8('m_grade', 255)) - lobby.add_child(Node.string('lid', location)) - lobby.add_child(Node.s32('max', 128)) - lobby.add_child(Node.s32_array('friend', [])) - lobby.add_child(Node.u8('var', 3)) + lobby = Node.void("lobby") + lobby.set_attribute("method", "rb5_lobby_read") + lobby.add_child(Node.s32("uid", extid)) + lobby.add_child(Node.s32("plyid", 0)) + lobby.add_child(Node.u8("m_grade", 255)) + lobby.add_child(Node.string("lid", location)) + lobby.add_child(Node.s32("max", 128)) + lobby.add_child(Node.s32_array("friend", [])) + lobby.add_child(Node.u8("var", 3)) call.add_child(lobby) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/lobby/interval") @@ -464,34 +474,34 @@ class ReflecBeatVolzza(BaseClient): def verify_lobby_rb5_lobby_entry(self, location: str, extid: int) -> int: call = self.call_node() - lobby = Node.void('lobby') - lobby.set_attribute('method', 'rb5_lobby_entry') - e = Node.void('e') + lobby = Node.void("lobby") + lobby.set_attribute("method", "rb5_lobby_entry") + e = Node.void("e") lobby.add_child(e) - e.add_child(Node.s32('eid', 0)) - e.add_child(Node.u16('mid', 79)) - e.add_child(Node.u8('ng', 0)) - e.add_child(Node.s32('uid', extid)) - e.add_child(Node.s32('uattr', 0)) - e.add_child(Node.string('pn', self.NAME)) - e.add_child(Node.s32('plyid', 0)) - e.add_child(Node.s16('mg', 255)) - e.add_child(Node.s32('mopt', 0)) - e.add_child(Node.string('lid', location)) - e.add_child(Node.string('sn', '')) - e.add_child(Node.u8('pref', 51)) - e.add_child(Node.s8('stg', 4)) - e.add_child(Node.s8('pside', 0)) - e.add_child(Node.s16('eatime', 30)) - e.add_child(Node.u8_array('ga', [127, 0, 0, 1])) - e.add_child(Node.u16('gp', 10007)) - e.add_child(Node.u8_array('la', [16, 0, 0, 0])) - e.add_child(Node.u8('ver', 2)) - lobby.add_child(Node.s32_array('friend', [])) + e.add_child(Node.s32("eid", 0)) + e.add_child(Node.u16("mid", 79)) + e.add_child(Node.u8("ng", 0)) + e.add_child(Node.s32("uid", extid)) + e.add_child(Node.s32("uattr", 0)) + e.add_child(Node.string("pn", self.NAME)) + e.add_child(Node.s32("plyid", 0)) + e.add_child(Node.s16("mg", 255)) + e.add_child(Node.s32("mopt", 0)) + e.add_child(Node.string("lid", location)) + e.add_child(Node.string("sn", "")) + e.add_child(Node.u8("pref", 51)) + e.add_child(Node.s8("stg", 4)) + e.add_child(Node.s8("pside", 0)) + e.add_child(Node.s16("eatime", 30)) + e.add_child(Node.u8_array("ga", [127, 0, 0, 1])) + e.add_child(Node.u16("gp", 10007)) + e.add_child(Node.u8_array("la", [16, 0, 0, 0])) + e.add_child(Node.u8("ver", 2)) + lobby.add_child(Node.s32_array("friend", [])) call.add_child(lobby) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/lobby/interval") @@ -516,18 +526,18 @@ class ReflecBeatVolzza(BaseClient): self.assert_path(resp, "response/lobby/e/gp") self.assert_path(resp, "response/lobby/e/la") self.assert_path(resp, "response/lobby/e/ver") - return resp.child_value('lobby/eid') + return resp.child_value("lobby/eid") def verify_lobby_rb5_lobby_delete_entry(self, eid: int) -> None: call = self.call_node() - lobby = Node.void('lobby') - lobby.set_attribute('method', 'rb5_lobby_delete_entry') - lobby.add_child(Node.s32('eid', eid)) + lobby = Node.void("lobby") + lobby.set_attribute("method", "rb5_lobby_delete_entry") + lobby.add_child(Node.s32("eid", eid)) call.add_child(lobby) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/lobby/@status") @@ -536,21 +546,21 @@ class ReflecBeatVolzza(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'lobby2', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "lobby2", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -571,12 +581,18 @@ class ReflecBeatVolzza(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Always get a player start, regardless of new profile or not self.verify_player_rb5_player_start(ref_id) @@ -587,13 +603,17 @@ class ReflecBeatVolzza(BaseClient): ) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Verify lobby functionality self.verify_lobby_rb5_lobby_read(location, extid) @@ -603,7 +623,7 @@ class ReflecBeatVolzza(BaseClient): # Verify we start with empty scores scores = self.verify_player_rb5_player_read_score(ref_id, location) if len(scores) > 0: - raise Exception('Existing scores returned on new card?') + raise Exception("Existing scores returned on new card?") if cardid is None: # Verify score saving and updating @@ -612,118 +632,137 @@ class ReflecBeatVolzza(BaseClient): dummyscores = [ # An okay score on a chart { - 'id': 9, - 'chart': 1, - 'clear_type': 9, - 'combo_type': 0, - 'achievement_rate': 7543, - 'score': 432, - 'miss_count': 5, + "id": 9, + "chart": 1, + "clear_type": 9, + "combo_type": 0, + "achievement_rate": 7543, + "score": 432, + "miss_count": 5, }, # A good score on an easier chart of the same song { - 'id': 9, - 'chart': 0, - 'clear_type': 9, - 'combo_type': 1, - 'achievement_rate': 9876, - 'score': 543, - 'miss_count': 0, + "id": 9, + "chart": 0, + "clear_type": 9, + "combo_type": 1, + "achievement_rate": 9876, + "score": 543, + "miss_count": 0, }, # A bad score on a hard chart { - 'id': 6, - 'chart': 2, - 'clear_type': 9, - 'combo_type': 0, - 'achievement_rate': 1234, - 'score': 123, - 'miss_count': 54, + "id": 6, + "chart": 2, + "clear_type": 9, + "combo_type": 0, + "achievement_rate": 1234, + "score": 123, + "miss_count": 54, }, # A terrible score on an easy chart { - 'id': 6, - 'chart': 0, - 'clear_type': 9, - 'combo_type': 0, - 'achievement_rate': 1024, - 'score': 50, - 'miss_count': 90, + "id": 6, + "chart": 0, + "clear_type": 9, + "combo_type": 0, + "achievement_rate": 1024, + "score": 50, + "miss_count": 90, }, ] if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 9, - 'chart': 1, - 'clear_type': 9, - 'combo_type': 0, - 'achievement_rate': 8765, - 'score': 469, - 'miss_count': 1, + "id": 9, + "chart": 1, + "clear_type": 9, + "combo_type": 0, + "achievement_rate": 8765, + "score": 469, + "miss_count": 1, }, # A worse score on another same chart { - 'id': 9, - 'chart': 0, - 'clear_type': 9, - 'combo_type': 0, - 'achievement_rate': 8765, - 'score': 432, - 'miss_count': 15, - 'expected_score': 543, - 'expected_clear_type': 9, - 'expected_combo_type': 1, - 'expected_achievement_rate': 9876, - 'expected_miss_count': 0, + "id": 9, + "chart": 0, + "clear_type": 9, + "combo_type": 0, + "achievement_rate": 8765, + "score": 432, + "miss_count": 15, + "expected_score": 543, + "expected_clear_type": 9, + "expected_combo_type": 1, + "expected_achievement_rate": 9876, + "expected_miss_count": 0, }, ] - self.verify_player_rb5_player_write(ref_id, location, scores=dummyscores) + self.verify_player_rb5_player_write( + ref_id, location, scores=dummyscores + ) self.verify_player_rb5_player_read(ref_id, card, location) scores = self.verify_player_rb5_player_read_score(ref_id, location) for expected in dummyscores: actual = None for received in scores: - if received['id'] == expected['id'] and received['chart'] == expected['chart']: + if ( + received["id"] == expected["id"] + and received["chart"] == expected["chart"] + ): actual = received break if actual is None: - raise Exception(f"Didn't find song {expected['id']} chart {expected['chart']} in response!") + raise Exception( + f"Didn't find song {expected['id']} chart {expected['chart']} in response!" + ) - if 'expected_score' in expected: - expected_score = expected['expected_score'] + if "expected_score" in expected: + expected_score = expected["expected_score"] else: - expected_score = expected['score'] - if 'expected_achievement_rate' in expected: - expected_achievement_rate = expected['expected_achievement_rate'] + expected_score = expected["score"] + if "expected_achievement_rate" in expected: + expected_achievement_rate = expected[ + "expected_achievement_rate" + ] else: - expected_achievement_rate = expected['achievement_rate'] - if 'expected_clear_type' in expected: - expected_clear_type = expected['expected_clear_type'] + expected_achievement_rate = expected["achievement_rate"] + if "expected_clear_type" in expected: + expected_clear_type = expected["expected_clear_type"] else: - expected_clear_type = expected['clear_type'] - if 'expected_combo_type' in expected: - expected_combo_type = expected['expected_combo_type'] + expected_clear_type = expected["clear_type"] + if "expected_combo_type" in expected: + expected_combo_type = expected["expected_combo_type"] else: - expected_combo_type = expected['combo_type'] - if 'expected_miss_count' in expected: - expected_miss_count = expected['expected_miss_count'] + expected_combo_type = expected["combo_type"] + if "expected_miss_count" in expected: + expected_miss_count = expected["expected_miss_count"] else: - expected_miss_count = expected['miss_count'] + expected_miss_count = expected["miss_count"] - if actual['score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'') - if actual['achievement_rate'] != expected_achievement_rate: - raise Exception(f'Expected an achievement rate of \'{expected_achievement_rate}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got achievement rate \'{actual["achievement_rate"]}\'') - if actual['clear_type'] != expected_clear_type: - raise Exception(f'Expected a clear_type of \'{expected_clear_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got clear_type \'{actual["clear_type"]}\'') - if actual['combo_type'] != expected_combo_type: - raise Exception(f'Expected a combo_type of \'{expected_combo_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got combo_type \'{actual["combo_type"]}\'') - if actual['miss_count'] != expected_miss_count: - raise Exception(f'Expected a miss count of \'{expected_miss_count}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got miss count \'{actual["miss_count"]}\'') + if actual["score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'' + ) + if actual["achievement_rate"] != expected_achievement_rate: + raise Exception( + f'Expected an achievement rate of \'{expected_achievement_rate}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got achievement rate \'{actual["achievement_rate"]}\'' + ) + if actual["clear_type"] != expected_clear_type: + raise Exception( + f'Expected a clear_type of \'{expected_clear_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got clear_type \'{actual["clear_type"]}\'' + ) + if actual["combo_type"] != expected_combo_type: + raise Exception( + f'Expected a combo_type of \'{expected_combo_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got combo_type \'{actual["combo_type"]}\'' + ) + if actual["miss_count"] != expected_miss_count: + raise Exception( + f'Expected a miss count of \'{expected_miss_count}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got miss count \'{actual["miss_count"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) diff --git a/bemani/client/reflec/volzza2.py b/bemani/client/reflec/volzza2.py index b3c0579..8001c19 100644 --- a/bemani/client/reflec/volzza2.py +++ b/bemani/client/reflec/volzza2.py @@ -7,19 +7,19 @@ from bemani.protocol import Node class ReflecBeatVolzza2(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_pcb_rb5_pcb_boot(self, loc: str) -> None: call = self.call_node() - pcb = Node.void('pcb') - pcb.set_attribute('method', 'rb5_pcb_boot') - pcb.add_child(Node.string('lid', loc)) - pcb.add_child(Node.string('rno', 'MBR-JA-C01')) + pcb = Node.void("pcb") + pcb.set_attribute("method", "rb5_pcb_boot") + pcb.add_child(Node.string("lid", loc)) + pcb.add_child(Node.string("rno", "MBR-JA-C01")) call.add_child(pcb) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/pcb/sinfo/nm") @@ -31,15 +31,15 @@ class ReflecBeatVolzza2(BaseClient): def verify_pcb_rb5_pcb_error(self, loc: str) -> None: call = self.call_node() - pcb = Node.void('pcb') + pcb = Node.void("pcb") call.add_child(pcb) - pcb.set_attribute('method', 'rb5_pcb_error') - pcb.add_child(Node.string('lid', loc)) - pcb.add_child(Node.string('code', 'exception')) - pcb.add_child(Node.string('msg', 'exceptionstring')) + pcb.set_attribute("method", "rb5_pcb_error") + pcb.add_child(Node.string("lid", loc)) + pcb.add_child(Node.string("code", "exception")) + pcb.add_child(Node.string("msg", "exceptionstring")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/pcb/@status") @@ -47,13 +47,13 @@ class ReflecBeatVolzza2(BaseClient): def verify_info_rb5_info_read(self, loc: str) -> None: call = self.call_node() - info = Node.void('info') + info = Node.void("info") call.add_child(info) - info.set_attribute('method', 'rb5_info_read') - info.add_child(Node.string('lid', loc)) + info.set_attribute("method", "rb5_info_read") + info.add_child(Node.string("lid", loc)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/info/event_ctrl") @@ -63,17 +63,17 @@ class ReflecBeatVolzza2(BaseClient): def verify_info_rb5_info_read_shop_ranking(self, loc: str) -> None: call = self.call_node() - info = Node.void('info') + info = Node.void("info") call.add_child(info) - info.set_attribute('method', 'rb5_info_read_shop_ranking') + info.set_attribute("method", "rb5_info_read_shop_ranking") # Arbitrarily chosen based on the song IDs we send in the # score section below. - info.add_child(Node.s16('min', 1)) - info.add_child(Node.s16('max', 10)) - info.add_child(Node.string('lid', loc)) + info.add_child(Node.s16("min", 1)) + info.add_child(Node.s16("max", 10)) + info.add_child(Node.string("lid", loc)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/info/shop_score/time") @@ -90,13 +90,13 @@ class ReflecBeatVolzza2(BaseClient): def verify_info_rb5_info_read_hit_chart(self) -> None: call = self.call_node() - info = Node.void('info') - info.set_attribute('method', 'rb5_info_read_hit_chart') - info.add_child(Node.s32('ver', 0)) + info = Node.void("info") + info.set_attribute("method", "rb5_info_read_hit_chart") + info.add_child(Node.s32("ver", 0)) call.add_child(info) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/info/ver") @@ -116,18 +116,22 @@ class ReflecBeatVolzza2(BaseClient): def verify_player_rb5_player_start(self, refid: str) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'rb5_player_start') - player.add_child(Node.string('rid', refid)) - player.add_child(Node.u8_array('ga', [127, 0, 0, 1])) - player.add_child(Node.u16('gp', 10573)) - player.add_child(Node.u8_array('la', [16, 0, 0, 0])) - player.add_child(Node.u8_array('pnid', [39, 16, 0, 0, 0, 23, 62, 60, 39, 127, 0, 0, 1, 23, 62, 60])) + player = Node.void("player") + player.set_attribute("method", "rb5_player_start") + player.add_child(Node.string("rid", refid)) + player.add_child(Node.u8_array("ga", [127, 0, 0, 1])) + player.add_child(Node.u16("gp", 10573)) + player.add_child(Node.u8_array("la", [16, 0, 0, 0])) + player.add_child( + Node.u8_array( + "pnid", [39, 16, 0, 0, 0, 23, 62, 60, 39, 127, 0, 0, 1, 23, 62, 60] + ) + ) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/plyid") @@ -139,13 +143,13 @@ class ReflecBeatVolzza2(BaseClient): def verify_player_rb5_player_end(self, refid: str) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'rb5_player_end') - player.add_child(Node.string('rid', refid)) + player = Node.void("player") + player.set_attribute("method", "rb5_player_end") + player.add_child(Node.string("rid", refid)) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player") @@ -153,16 +157,16 @@ class ReflecBeatVolzza2(BaseClient): def verify_player_rb5_player_read_rank_5(self) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'rb5_player_read_rank_5') - player.add_child(Node.s32('uid', 0)) - player.add_child(Node.s32_array('sc', [897, 897, 0, 0, 0])) - player.add_child(Node.s8('mg_id', 0)) - player.add_child(Node.s32('mg_sc', 220)) + player = Node.void("player") + player.set_attribute("method", "rb5_player_read_rank_5") + player.add_child(Node.s32("uid", 0)) + player.add_child(Node.s32_array("sc", [897, 897, 0, 0, 0])) + player.add_child(Node.s8("mg_id", 0)) + player.add_child(Node.s32("mg_sc", 220)) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/tbs/new_rank") @@ -173,15 +177,15 @@ class ReflecBeatVolzza2(BaseClient): def verify_player_rb5_player_read_rival_score_5(self, extid: int) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'rb5_player_read_rival_score_5') - player.add_child(Node.s32('uid', extid)) - player.add_child(Node.s32('music_id', 6)) - player.add_child(Node.s8('note_grade', 0)) + player = Node.void("player") + player.set_attribute("method", "rb5_player_read_rival_score_5") + player.add_child(Node.s32("uid", extid)) + player.add_child(Node.s32("music_id", 6)) + player.add_child(Node.s8("note_grade", 0)) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/@status") @@ -194,11 +198,11 @@ class ReflecBeatVolzza2(BaseClient): self.assert_path(resp, "response/player/player_select_score/m_scoreTime") self.assert_path(resp, "response/player/player_select_score/m_iconID") - if resp.child_value('player/player_select_score/name') != self.NAME: + if resp.child_value("player/player_select_score/name") != self.NAME: raise Exception( f'Invalid name {resp.child_value("player/player_select_score/name")} returned on score read!' ) - if resp.child_value('player/player_select_score/user_id') != extid: + if resp.child_value("player/player_select_score/user_id") != extid: raise Exception( f'Invalid name {resp.child_value("player/player_select_score/user_id")} returned on score read!' ) @@ -206,14 +210,14 @@ class ReflecBeatVolzza2(BaseClient): def verify_player_rb5_player_read_rival_ranking_data_5(self, extid: int) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'rb5_player_read_rival_ranking_data_5') - player.add_child(Node.s32('uid', extid)) - player.add_child(Node.s8('mgid', -1)) + player = Node.void("player") + player.set_attribute("method", "rb5_player_read_rival_ranking_data_5") + player.add_child(Node.s32("uid", extid)) + player.add_child(Node.s8("mgid", -1)) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/rival_data/rl/uid") @@ -222,7 +226,7 @@ class ReflecBeatVolzza2(BaseClient): self.assert_path(resp, "response/player/rival_data/rl/sl/mid") self.assert_path(resp, "response/player/rival_data/rl/sl/m") self.assert_path(resp, "response/player/rival_data/rl/sl/t") - if resp.child_value('player/rival_data/rl/nm') != self.NAME: + if resp.child_value("player/rival_data/rl/nm") != self.NAME: raise Exception( f'Invalid name {resp.child_value("player/rival_data/rl/nm")} returned on rival ranking read!' ) @@ -230,13 +234,13 @@ class ReflecBeatVolzza2(BaseClient): def verify_player_rb5_player_succeed(self, refid: str) -> None: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'rb5_player_succeed') - player.add_child(Node.string('rid', refid)) + player = Node.void("player") + player.set_attribute("method", "rb5_player_succeed") + player.add_child(Node.string("rid", refid)) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/name") @@ -244,20 +248,22 @@ class ReflecBeatVolzza2(BaseClient): self.assert_path(resp, "response/player/ap") self.assert_path(resp, "response/player/uattr") - def verify_player_rb5_player_read(self, refid: str, cardid: str, location: str) -> Dict[str, Any]: + def verify_player_rb5_player_read( + self, refid: str, cardid: str, location: str + ) -> Dict[str, Any]: call = self.call_node() - player = Node.void('player') - player.set_attribute('method', 'rb5_player_read') - player.add_child(Node.string('rid', refid)) - player.add_child(Node.string('lid', location)) - player.add_child(Node.s16('ver', 0)) - player.add_child(Node.string('card_id', cardid)) - player.add_child(Node.s16('card_type', 1)) + player = Node.void("player") + player.set_attribute("method", "rb5_player_read") + player.add_child(Node.string("rid", refid)) + player.add_child(Node.string("lid", location)) + player.add_child(Node.s16("ver", 0)) + player.add_child(Node.string("card_id", cardid)) + player.add_child(Node.s16("card_type", 1)) call.add_child(player) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/pdata/account/usrid") @@ -303,119 +309,125 @@ class ReflecBeatVolzza2(BaseClient): self.assert_path(resp, "response/player/pdata/mycourse/mycourse_id") self.assert_path(resp, "response/player/pdata/mycourse_f") - if resp.child_value('player/pdata/base/name') != self.NAME: - raise Exception(f'Invalid name {resp.child_value("player/pdata/base/name")} returned on profile read!') + if resp.child_value("player/pdata/base/name") != self.NAME: + raise Exception( + f'Invalid name {resp.child_value("player/pdata/base/name")} returned on profile read!' + ) mycourse = [ { - 'music_id': resp.child_value('player/pdata/mycourse/music_id_1'), - 'note_grade': resp.child_value('player/pdata/mycourse/note_grade_1'), - 'score': resp.child_value('player/pdata/mycourse/score_1'), + "music_id": resp.child_value("player/pdata/mycourse/music_id_1"), + "note_grade": resp.child_value("player/pdata/mycourse/note_grade_1"), + "score": resp.child_value("player/pdata/mycourse/score_1"), }, { - 'music_id': resp.child_value('player/pdata/mycourse/music_id_2'), - 'note_grade': resp.child_value('player/pdata/mycourse/note_grade_2'), - 'score': resp.child_value('player/pdata/mycourse/score_2'), + "music_id": resp.child_value("player/pdata/mycourse/music_id_2"), + "note_grade": resp.child_value("player/pdata/mycourse/note_grade_2"), + "score": resp.child_value("player/pdata/mycourse/score_2"), }, { - 'music_id': resp.child_value('player/pdata/mycourse/music_id_3'), - 'note_grade': resp.child_value('player/pdata/mycourse/note_grade_3'), - 'score': resp.child_value('player/pdata/mycourse/score_3'), + "music_id": resp.child_value("player/pdata/mycourse/music_id_3"), + "note_grade": resp.child_value("player/pdata/mycourse/note_grade_3"), + "score": resp.child_value("player/pdata/mycourse/score_3"), }, { - 'music_id': resp.child_value('player/pdata/mycourse/music_id_4'), - 'note_grade': resp.child_value('player/pdata/mycourse/note_grade_4'), - 'score': resp.child_value('player/pdata/mycourse/score_4'), + "music_id": resp.child_value("player/pdata/mycourse/music_id_4"), + "note_grade": resp.child_value("player/pdata/mycourse/note_grade_4"), + "score": resp.child_value("player/pdata/mycourse/score_4"), }, ] return { - 'mycourse': mycourse, + "mycourse": mycourse, } - def verify_player_rb5_player_read_score_5(self, refid: str, location: str) -> List[Dict[str, int]]: + def verify_player_rb5_player_read_score_5( + self, refid: str, location: str + ) -> List[Dict[str, int]]: call = self.call_node() - player = Node.void('player') + player = Node.void("player") call.add_child(player) - player.set_attribute('method', 'rb5_player_read_score_5') - player.add_child(Node.string('rid', refid)) - player.add_child(Node.s16('ver', 1)) + player.set_attribute("method", "rb5_player_read_score_5") + player.add_child(Node.string("rid", refid)) + player.add_child(Node.s16("ver", 1)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) scores = [] - for child in resp.child('player/pdata/record').children: - if child.name != 'rec': + for child in resp.child("player/pdata/record").children: + if child.name != "rec": continue - self.assert_path(child, 'rec/mid') - self.assert_path(child, 'rec/ntgrd') - self.assert_path(child, 'rec/pc') - self.assert_path(child, 'rec/ct') - self.assert_path(child, 'rec/ar') - self.assert_path(child, 'rec/scr') - self.assert_path(child, 'rec/ms') - self.assert_path(child, 'rec/param') - self.assert_path(child, 'rec/bscrt') - self.assert_path(child, 'rec/bart') - self.assert_path(child, 'rec/bctt') - self.assert_path(child, 'rec/bmst') - self.assert_path(child, 'rec/time') - self.assert_path(child, 'rec/k_flag') + self.assert_path(child, "rec/mid") + self.assert_path(child, "rec/ntgrd") + self.assert_path(child, "rec/pc") + self.assert_path(child, "rec/ct") + self.assert_path(child, "rec/ar") + self.assert_path(child, "rec/scr") + self.assert_path(child, "rec/ms") + self.assert_path(child, "rec/param") + self.assert_path(child, "rec/bscrt") + self.assert_path(child, "rec/bart") + self.assert_path(child, "rec/bctt") + self.assert_path(child, "rec/bmst") + self.assert_path(child, "rec/time") + self.assert_path(child, "rec/k_flag") score = { - 'id': child.child_value('mid'), - 'chart': child.child_value('ntgrd'), - 'clear_type': child.child_value('ct'), - 'combo_type': child.child_value('param'), - 'achievement_rate': child.child_value('ar'), - 'score': child.child_value('scr'), - 'miss_count': child.child_value('ms'), + "id": child.child_value("mid"), + "chart": child.child_value("ntgrd"), + "clear_type": child.child_value("ct"), + "combo_type": child.child_value("param"), + "achievement_rate": child.child_value("ar"), + "score": child.child_value("scr"), + "miss_count": child.child_value("ms"), } scores.append(score) return scores - def verify_player_rb5_player_read_score_old_5(self, refid: str, location: str) -> List[Dict[str, int]]: + def verify_player_rb5_player_read_score_old_5( + self, refid: str, location: str + ) -> List[Dict[str, int]]: call = self.call_node() - player = Node.void('player') + player = Node.void("player") call.add_child(player) - player.set_attribute('method', 'rb5_player_read_score_old_5') - player.add_child(Node.string('rid', refid)) - player.add_child(Node.s16('ver', 1)) + player.set_attribute("method", "rb5_player_read_score_old_5") + player.add_child(Node.string("rid", refid)) + player.add_child(Node.s16("ver", 1)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) scores = [] - for child in resp.child('player/pdata/record_old').children: - if child.name != 'rec': + for child in resp.child("player/pdata/record_old").children: + if child.name != "rec": continue - self.assert_path(child, 'rec/mid') - self.assert_path(child, 'rec/ntgrd') - self.assert_path(child, 'rec/pc') - self.assert_path(child, 'rec/ct') - self.assert_path(child, 'rec/ar') - self.assert_path(child, 'rec/scr') - self.assert_path(child, 'rec/ms') - self.assert_path(child, 'rec/param') - self.assert_path(child, 'rec/bscrt') - self.assert_path(child, 'rec/bart') - self.assert_path(child, 'rec/bctt') - self.assert_path(child, 'rec/bmst') - self.assert_path(child, 'rec/time') - self.assert_path(child, 'rec/k_flag') + self.assert_path(child, "rec/mid") + self.assert_path(child, "rec/ntgrd") + self.assert_path(child, "rec/pc") + self.assert_path(child, "rec/ct") + self.assert_path(child, "rec/ar") + self.assert_path(child, "rec/scr") + self.assert_path(child, "rec/ms") + self.assert_path(child, "rec/param") + self.assert_path(child, "rec/bscrt") + self.assert_path(child, "rec/bart") + self.assert_path(child, "rec/bctt") + self.assert_path(child, "rec/bmst") + self.assert_path(child, "rec/time") + self.assert_path(child, "rec/k_flag") score = { - 'id': child.child_value('mid'), - 'chart': child.child_value('ntgrd'), - 'clear_type': child.child_value('ct'), - 'combo_type': child.child_value('param'), - 'achievement_rate': child.child_value('ar'), - 'score': child.child_value('scr'), - 'miss_count': child.child_value('ms'), + "id": child.child_value("mid"), + "chart": child.child_value("ntgrd"), + "clear_type": child.child_value("ct"), + "combo_type": child.child_value("param"), + "achievement_rate": child.child_value("ar"), + "score": child.child_value("scr"), + "miss_count": child.child_value("ms"), } scores.append(score) return scores @@ -424,143 +436,143 @@ class ReflecBeatVolzza2(BaseClient): self, refid: str, loc: str, - scores: List[Dict[str, int]]=[], - rivals: List[int]=[], - mycourse: Optional[List[Dict[str, int]]]=None, + scores: List[Dict[str, int]] = [], + rivals: List[int] = [], + mycourse: Optional[List[Dict[str, int]]] = None, ) -> int: call = self.call_node() - player = Node.void('player') + player = Node.void("player") call.add_child(player) - player.set_attribute('method', 'rb5_player_write_5') - pdata = Node.void('pdata') + player.set_attribute("method", "rb5_player_write_5") + pdata = Node.void("pdata") player.add_child(pdata) - account = Node.void('account') + account = Node.void("account") pdata.add_child(account) - account.add_child(Node.s32('usrid', 0)) - account.add_child(Node.s32('plyid', 0)) - account.add_child(Node.s32('tpc', 1)) - account.add_child(Node.s32('dpc', 1)) - account.add_child(Node.s32('crd', 1)) - account.add_child(Node.s32('brd', 1)) - account.add_child(Node.s32('tdc', 1)) - account.add_child(Node.string('rid', refid)) - account.add_child(Node.string('lid', loc)) - account.add_child(Node.u8('wmode', 0)) - account.add_child(Node.u8('gmode', 0)) - account.add_child(Node.s16('ver', 0)) - account.add_child(Node.bool('pp', False)) - account.add_child(Node.bool('ps', False)) - account.add_child(Node.bool('continue', False)) - account.add_child(Node.bool('firstfree', False)) - account.add_child(Node.s16('pay', 0)) - account.add_child(Node.s16('pay_pc', 0)) - account.add_child(Node.u64('st', int(time.time() * 1000))) - base = Node.void('base') + account.add_child(Node.s32("usrid", 0)) + account.add_child(Node.s32("plyid", 0)) + account.add_child(Node.s32("tpc", 1)) + account.add_child(Node.s32("dpc", 1)) + account.add_child(Node.s32("crd", 1)) + account.add_child(Node.s32("brd", 1)) + account.add_child(Node.s32("tdc", 1)) + account.add_child(Node.string("rid", refid)) + account.add_child(Node.string("lid", loc)) + account.add_child(Node.u8("wmode", 0)) + account.add_child(Node.u8("gmode", 0)) + account.add_child(Node.s16("ver", 0)) + account.add_child(Node.bool("pp", False)) + account.add_child(Node.bool("ps", False)) + account.add_child(Node.bool("continue", False)) + account.add_child(Node.bool("firstfree", False)) + account.add_child(Node.s16("pay", 0)) + account.add_child(Node.s16("pay_pc", 0)) + account.add_child(Node.u64("st", int(time.time() * 1000))) + base = Node.void("base") pdata.add_child(base) - base.add_child(Node.string('name', self.NAME)) - base.add_child(Node.s32('mg', 0)) - base.add_child(Node.s32('ap', 0)) - base.add_child(Node.s32('uattr', 0)) - base.add_child(Node.s32('money', 0)) - base.add_child(Node.bool('is_tut', False)) - base.add_child(Node.s32('class', -1)) - base.add_child(Node.s32('class_ar', 0)) - base.add_child(Node.s32('skill_point', 0)) - stglog = Node.void('stglog') + base.add_child(Node.string("name", self.NAME)) + base.add_child(Node.s32("mg", 0)) + base.add_child(Node.s32("ap", 0)) + base.add_child(Node.s32("uattr", 0)) + base.add_child(Node.s32("money", 0)) + base.add_child(Node.bool("is_tut", False)) + base.add_child(Node.s32("class", -1)) + base.add_child(Node.s32("class_ar", 0)) + base.add_child(Node.s32("skill_point", 0)) + stglog = Node.void("stglog") pdata.add_child(stglog) index = 0 for score in scores: - log = Node.void('log') + log = Node.void("log") stglog.add_child(log) - log.add_child(Node.s8('stg', index)) - log.add_child(Node.s16('mid', score['id'])) - log.add_child(Node.s8('ng', score['chart'])) - log.add_child(Node.s8('col', 1)) - log.add_child(Node.s8('mt', 0)) - log.add_child(Node.s8('rt', 0)) - log.add_child(Node.s8('ct', score['clear_type'])) - log.add_child(Node.s16('param', score['combo_type'])) - log.add_child(Node.s16('grd', 0)) - log.add_child(Node.s16('ar', score['achievement_rate'])) - log.add_child(Node.s16('sc', score['score'])) - log.add_child(Node.s16('jt_jst', 0)) - log.add_child(Node.s16('jt_grt', 0)) - log.add_child(Node.s16('jt_gd', 0)) - log.add_child(Node.s16('jt_ms', score['miss_count'])) - log.add_child(Node.s16('jt_jr', 0)) - log.add_child(Node.s32('r_uid', 0)) - log.add_child(Node.s32('r_plyid', 0)) - log.add_child(Node.s8('r_stg', 0)) - log.add_child(Node.s8('r_ct', -1)) - log.add_child(Node.s16('r_sc', 0)) - log.add_child(Node.s16('r_grd', 0)) - log.add_child(Node.s16('r_ar', 0)) - log.add_child(Node.s8('r_cpuid', -1)) - log.add_child(Node.s32('time', int(time.time()))) - log.add_child(Node.s8('decide', 0)) - log.add_child(Node.s16('g_gauge', 5000)) - log.add_child(Node.s32('k_flag', 0)) + log.add_child(Node.s8("stg", index)) + log.add_child(Node.s16("mid", score["id"])) + log.add_child(Node.s8("ng", score["chart"])) + log.add_child(Node.s8("col", 1)) + log.add_child(Node.s8("mt", 0)) + log.add_child(Node.s8("rt", 0)) + log.add_child(Node.s8("ct", score["clear_type"])) + log.add_child(Node.s16("param", score["combo_type"])) + log.add_child(Node.s16("grd", 0)) + log.add_child(Node.s16("ar", score["achievement_rate"])) + log.add_child(Node.s16("sc", score["score"])) + log.add_child(Node.s16("jt_jst", 0)) + log.add_child(Node.s16("jt_grt", 0)) + log.add_child(Node.s16("jt_gd", 0)) + log.add_child(Node.s16("jt_ms", score["miss_count"])) + log.add_child(Node.s16("jt_jr", 0)) + log.add_child(Node.s32("r_uid", 0)) + log.add_child(Node.s32("r_plyid", 0)) + log.add_child(Node.s8("r_stg", 0)) + log.add_child(Node.s8("r_ct", -1)) + log.add_child(Node.s16("r_sc", 0)) + log.add_child(Node.s16("r_grd", 0)) + log.add_child(Node.s16("r_ar", 0)) + log.add_child(Node.s8("r_cpuid", -1)) + log.add_child(Node.s32("time", int(time.time()))) + log.add_child(Node.s8("decide", 0)) + log.add_child(Node.s16("g_gauge", 5000)) + log.add_child(Node.s32("k_flag", 0)) index = index + 1 - rivalnode = Node.void('rival') + rivalnode = Node.void("rival") pdata.add_child(rivalnode) for rival in rivals: - r = Node.void('r') + r = Node.void("r") rivalnode.add_child(r) - r.add_child(Node.s32('id', rival)) - r.add_child(Node.s8('regist_type', 3)) + r.add_child(Node.s32("id", rival)) + r.add_child(Node.s8("regist_type", 3)) if mycourse is not None: - mycoursenode = Node.void('mycourse') + mycoursenode = Node.void("mycourse") pdata.add_child(mycoursenode) - mycoursenode.add_child(Node.s16('mycourse_id', 1)) - mycoursenode.add_child(Node.s32('music_id_1', mycourse[0]['music_id'])) - mycoursenode.add_child(Node.s16('note_grade_1', mycourse[0]['note_grade'])) - mycoursenode.add_child(Node.s32('score_1', mycourse[0]['score'])) - mycoursenode.add_child(Node.s32('music_id_2', mycourse[1]['music_id'])) - mycoursenode.add_child(Node.s16('note_grade_2', mycourse[1]['note_grade'])) - mycoursenode.add_child(Node.s32('score_2', mycourse[1]['score'])) - mycoursenode.add_child(Node.s32('music_id_3', mycourse[2]['music_id'])) - mycoursenode.add_child(Node.s16('note_grade_3', mycourse[2]['note_grade'])) - mycoursenode.add_child(Node.s32('score_3', mycourse[2]['score'])) - mycoursenode.add_child(Node.s32('music_id_4', mycourse[3]['music_id'])) - mycoursenode.add_child(Node.s16('note_grade_4', mycourse[3]['note_grade'])) - mycoursenode.add_child(Node.s32('score_4', mycourse[3]['score'])) - mycoursenode.add_child(Node.s32('insert_time', -1)) - mycoursenode.add_child(Node.s32('def_music_id_1', -1)) - mycoursenode.add_child(Node.s16('def_note_grade_1', -1)) - mycoursenode.add_child(Node.s32('def_music_id_2', -1)) - mycoursenode.add_child(Node.s16('def_note_grade_2', -1)) - mycoursenode.add_child(Node.s32('def_music_id_3', -1)) - mycoursenode.add_child(Node.s16('def_note_grade_3', -1)) - mycoursenode.add_child(Node.s32('def_music_id_4', -1)) - mycoursenode.add_child(Node.s16('def_note_grade_4', -1)) + mycoursenode.add_child(Node.s16("mycourse_id", 1)) + mycoursenode.add_child(Node.s32("music_id_1", mycourse[0]["music_id"])) + mycoursenode.add_child(Node.s16("note_grade_1", mycourse[0]["note_grade"])) + mycoursenode.add_child(Node.s32("score_1", mycourse[0]["score"])) + mycoursenode.add_child(Node.s32("music_id_2", mycourse[1]["music_id"])) + mycoursenode.add_child(Node.s16("note_grade_2", mycourse[1]["note_grade"])) + mycoursenode.add_child(Node.s32("score_2", mycourse[1]["score"])) + mycoursenode.add_child(Node.s32("music_id_3", mycourse[2]["music_id"])) + mycoursenode.add_child(Node.s16("note_grade_3", mycourse[2]["note_grade"])) + mycoursenode.add_child(Node.s32("score_3", mycourse[2]["score"])) + mycoursenode.add_child(Node.s32("music_id_4", mycourse[3]["music_id"])) + mycoursenode.add_child(Node.s16("note_grade_4", mycourse[3]["note_grade"])) + mycoursenode.add_child(Node.s32("score_4", mycourse[3]["score"])) + mycoursenode.add_child(Node.s32("insert_time", -1)) + mycoursenode.add_child(Node.s32("def_music_id_1", -1)) + mycoursenode.add_child(Node.s16("def_note_grade_1", -1)) + mycoursenode.add_child(Node.s32("def_music_id_2", -1)) + mycoursenode.add_child(Node.s16("def_note_grade_2", -1)) + mycoursenode.add_child(Node.s32("def_music_id_3", -1)) + mycoursenode.add_child(Node.s16("def_note_grade_3", -1)) + mycoursenode.add_child(Node.s32("def_music_id_4", -1)) + mycoursenode.add_child(Node.s16("def_note_grade_4", -1)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/player/uid") - return resp.child_value('player/uid') + return resp.child_value("player/uid") def verify_lobby_rb5_lobby_read(self, location: str, extid: int) -> None: call = self.call_node() - lobby = Node.void('lobby') - lobby.set_attribute('method', 'rb5_lobby_read') - lobby.add_child(Node.s32('uid', extid)) - lobby.add_child(Node.s32('plyid', 0)) - lobby.add_child(Node.u8('m_grade', 255)) - lobby.add_child(Node.string('lid', location)) - lobby.add_child(Node.s32('max', 128)) - lobby.add_child(Node.s32_array('friend', [])) - lobby.add_child(Node.u8('var', 4)) + lobby = Node.void("lobby") + lobby.set_attribute("method", "rb5_lobby_read") + lobby.add_child(Node.s32("uid", extid)) + lobby.add_child(Node.s32("plyid", 0)) + lobby.add_child(Node.u8("m_grade", 255)) + lobby.add_child(Node.string("lid", location)) + lobby.add_child(Node.s32("max", 128)) + lobby.add_child(Node.s32_array("friend", [])) + lobby.add_child(Node.u8("var", 4)) call.add_child(lobby) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/lobby/interval") @@ -569,34 +581,34 @@ class ReflecBeatVolzza2(BaseClient): def verify_lobby_rb5_lobby_entry(self, location: str, extid: int) -> int: call = self.call_node() - lobby = Node.void('lobby') - lobby.set_attribute('method', 'rb5_lobby_entry') - e = Node.void('e') + lobby = Node.void("lobby") + lobby.set_attribute("method", "rb5_lobby_entry") + e = Node.void("e") lobby.add_child(e) - e.add_child(Node.s32('eid', 0)) - e.add_child(Node.u16('mid', 79)) - e.add_child(Node.u8('ng', 0)) - e.add_child(Node.s32('uid', extid)) - e.add_child(Node.s32('uattr', 0)) - e.add_child(Node.string('pn', self.NAME)) - e.add_child(Node.s32('plyid', 0)) - e.add_child(Node.s16('mg', 255)) - e.add_child(Node.s32('mopt', 0)) - e.add_child(Node.string('lid', location)) - e.add_child(Node.string('sn', '')) - e.add_child(Node.u8('pref', 51)) - e.add_child(Node.s8('stg', 4)) - e.add_child(Node.s8('pside', 0)) - e.add_child(Node.s16('eatime', 30)) - e.add_child(Node.u8_array('ga', [127, 0, 0, 1])) - e.add_child(Node.u16('gp', 10007)) - e.add_child(Node.u8_array('la', [16, 0, 0, 0])) - e.add_child(Node.u8('ver', 2)) - lobby.add_child(Node.s32_array('friend', [])) + e.add_child(Node.s32("eid", 0)) + e.add_child(Node.u16("mid", 79)) + e.add_child(Node.u8("ng", 0)) + e.add_child(Node.s32("uid", extid)) + e.add_child(Node.s32("uattr", 0)) + e.add_child(Node.string("pn", self.NAME)) + e.add_child(Node.s32("plyid", 0)) + e.add_child(Node.s16("mg", 255)) + e.add_child(Node.s32("mopt", 0)) + e.add_child(Node.string("lid", location)) + e.add_child(Node.string("sn", "")) + e.add_child(Node.u8("pref", 51)) + e.add_child(Node.s8("stg", 4)) + e.add_child(Node.s8("pside", 0)) + e.add_child(Node.s16("eatime", 30)) + e.add_child(Node.u8_array("ga", [127, 0, 0, 1])) + e.add_child(Node.u16("gp", 10007)) + e.add_child(Node.u8_array("la", [16, 0, 0, 0])) + e.add_child(Node.u8("ver", 2)) + lobby.add_child(Node.s32_array("friend", [])) call.add_child(lobby) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/lobby/interval") @@ -621,18 +633,18 @@ class ReflecBeatVolzza2(BaseClient): self.assert_path(resp, "response/lobby/e/gp") self.assert_path(resp, "response/lobby/e/la") self.assert_path(resp, "response/lobby/e/ver") - return resp.child_value('lobby/eid') + return resp.child_value("lobby/eid") def verify_lobby_rb5_lobby_delete_entry(self, eid: int) -> None: call = self.call_node() - lobby = Node.void('lobby') - lobby.set_attribute('method', 'rb5_lobby_delete_entry') - lobby.add_child(Node.s32('eid', eid)) + lobby = Node.void("lobby") + lobby.set_attribute("method", "rb5_lobby_delete_entry") + lobby.add_child(Node.s32("eid", eid)) call.add_child(lobby) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/lobby/@status") @@ -641,21 +653,21 @@ class ReflecBeatVolzza2(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'lobby2', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "lobby2", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -676,12 +688,18 @@ class ReflecBeatVolzza2(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Always get a player start, regardless of new profile or not self.verify_player_rb5_player_start(ref_id) @@ -692,13 +710,17 @@ class ReflecBeatVolzza2(BaseClient): ) else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Verify lobby functionality self.verify_lobby_rb5_lobby_read(location, extid) @@ -709,15 +731,15 @@ class ReflecBeatVolzza2(BaseClient): scores = self.verify_player_rb5_player_read_score_5(ref_id, location) oldscores = self.verify_player_rb5_player_read_score_old_5(ref_id, location) if len(scores) > 0 or len(oldscores) > 0: - raise Exception('Existing scores returned on new card?') + raise Exception("Existing scores returned on new card?") profile = self.verify_player_rb5_player_read(ref_id, card, location) - for val in profile['mycourse']: - if val['score'] != -1: - raise Exception('Existing score returned for new card on course?') - if val['music_id'] != -1: - raise Exception('Existing score returned for new card on course?') - if val['note_grade'] != -1: - raise Exception('Existing score returned for new card on course?') + for val in profile["mycourse"]: + if val["score"] != -1: + raise Exception("Existing score returned for new card on course?") + if val["music_id"] != -1: + raise Exception("Existing score returned for new card on course?") + if val["note_grade"] != -1: + raise Exception("Existing score returned for new card on course?") if cardid is None: # Verify score saving and updating @@ -726,118 +748,137 @@ class ReflecBeatVolzza2(BaseClient): dummyscores = [ # An okay score on a chart { - 'id': 9, - 'chart': 1, - 'clear_type': 9, - 'combo_type': 0, - 'achievement_rate': 7543, - 'score': 432, - 'miss_count': 5, + "id": 9, + "chart": 1, + "clear_type": 9, + "combo_type": 0, + "achievement_rate": 7543, + "score": 432, + "miss_count": 5, }, # A good score on an easier chart of the same song { - 'id': 9, - 'chart': 0, - 'clear_type': 9, - 'combo_type': 1, - 'achievement_rate': 9876, - 'score': 543, - 'miss_count': 0, + "id": 9, + "chart": 0, + "clear_type": 9, + "combo_type": 1, + "achievement_rate": 9876, + "score": 543, + "miss_count": 0, }, # A bad score on a hard chart { - 'id': 6, - 'chart': 2, - 'clear_type': 9, - 'combo_type': 0, - 'achievement_rate': 1234, - 'score': 123, - 'miss_count': 54, + "id": 6, + "chart": 2, + "clear_type": 9, + "combo_type": 0, + "achievement_rate": 1234, + "score": 123, + "miss_count": 54, }, # A terrible score on an easy chart { - 'id': 6, - 'chart': 0, - 'clear_type': 9, - 'combo_type': 0, - 'achievement_rate': 1024, - 'score': 50, - 'miss_count': 90, + "id": 6, + "chart": 0, + "clear_type": 9, + "combo_type": 0, + "achievement_rate": 1024, + "score": 50, + "miss_count": 90, }, ] if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 9, - 'chart': 1, - 'clear_type': 9, - 'combo_type': 0, - 'achievement_rate': 8765, - 'score': 469, - 'miss_count': 1, + "id": 9, + "chart": 1, + "clear_type": 9, + "combo_type": 0, + "achievement_rate": 8765, + "score": 469, + "miss_count": 1, }, # A worse score on another same chart { - 'id': 9, - 'chart': 0, - 'clear_type': 9, - 'combo_type': 0, - 'achievement_rate': 8765, - 'score': 432, - 'miss_count': 15, - 'expected_score': 543, - 'expected_clear_type': 9, - 'expected_combo_type': 1, - 'expected_achievement_rate': 9876, - 'expected_miss_count': 0, + "id": 9, + "chart": 0, + "clear_type": 9, + "combo_type": 0, + "achievement_rate": 8765, + "score": 432, + "miss_count": 15, + "expected_score": 543, + "expected_clear_type": 9, + "expected_combo_type": 1, + "expected_achievement_rate": 9876, + "expected_miss_count": 0, }, ] - self.verify_player_rb5_player_write_5(ref_id, location, scores=dummyscores) + self.verify_player_rb5_player_write_5( + ref_id, location, scores=dummyscores + ) self.verify_player_rb5_player_read(ref_id, card, location) scores = self.verify_player_rb5_player_read_score_5(ref_id, location) for expected in dummyscores: actual = None for received in scores: - if received['id'] == expected['id'] and received['chart'] == expected['chart']: + if ( + received["id"] == expected["id"] + and received["chart"] == expected["chart"] + ): actual = received break if actual is None: - raise Exception(f"Didn't find song {expected['id']} chart {expected['chart']} in response!") + raise Exception( + f"Didn't find song {expected['id']} chart {expected['chart']} in response!" + ) - if 'expected_score' in expected: - expected_score = expected['expected_score'] + if "expected_score" in expected: + expected_score = expected["expected_score"] else: - expected_score = expected['score'] - if 'expected_achievement_rate' in expected: - expected_achievement_rate = expected['expected_achievement_rate'] + expected_score = expected["score"] + if "expected_achievement_rate" in expected: + expected_achievement_rate = expected[ + "expected_achievement_rate" + ] else: - expected_achievement_rate = expected['achievement_rate'] - if 'expected_clear_type' in expected: - expected_clear_type = expected['expected_clear_type'] + expected_achievement_rate = expected["achievement_rate"] + if "expected_clear_type" in expected: + expected_clear_type = expected["expected_clear_type"] else: - expected_clear_type = expected['clear_type'] - if 'expected_combo_type' in expected: - expected_combo_type = expected['expected_combo_type'] + expected_clear_type = expected["clear_type"] + if "expected_combo_type" in expected: + expected_combo_type = expected["expected_combo_type"] else: - expected_combo_type = expected['combo_type'] - if 'expected_miss_count' in expected: - expected_miss_count = expected['expected_miss_count'] + expected_combo_type = expected["combo_type"] + if "expected_miss_count" in expected: + expected_miss_count = expected["expected_miss_count"] else: - expected_miss_count = expected['miss_count'] + expected_miss_count = expected["miss_count"] - if actual['score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'') - if actual['achievement_rate'] != expected_achievement_rate: - raise Exception(f'Expected an achievement rate of \'{expected_achievement_rate}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got achievement rate \'{actual["achievement_rate"]}\'') - if actual['clear_type'] != expected_clear_type: - raise Exception(f'Expected a clear_type of \'{expected_clear_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got clear_type \'{actual["clear_type"]}\'') - if actual['combo_type'] != expected_combo_type: - raise Exception(f'Expected a combo_type of \'{expected_combo_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got combo_type \'{actual["combo_type"]}\'') - if actual['miss_count'] != expected_miss_count: - raise Exception(f'Expected a miss count of \'{expected_miss_count}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got miss count \'{actual["miss_count"]}\'') + if actual["score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'' + ) + if actual["achievement_rate"] != expected_achievement_rate: + raise Exception( + f'Expected an achievement rate of \'{expected_achievement_rate}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got achievement rate \'{actual["achievement_rate"]}\'' + ) + if actual["clear_type"] != expected_clear_type: + raise Exception( + f'Expected a clear_type of \'{expected_clear_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got clear_type \'{actual["clear_type"]}\'' + ) + if actual["combo_type"] != expected_combo_type: + raise Exception( + f'Expected a combo_type of \'{expected_combo_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got combo_type \'{actual["combo_type"]}\'' + ) + if actual["miss_count"] != expected_miss_count: + raise Exception( + f'Expected a miss count of \'{expected_miss_count}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got miss count \'{actual["miss_count"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) @@ -848,101 +889,101 @@ class ReflecBeatVolzza2(BaseClient): # Verify mycourse saving and updating firstcourse = [ { - 'music_id': 51, - 'note_grade': 1, - 'score': 1086, + "music_id": 51, + "note_grade": 1, + "score": 1086, }, { - 'music_id': 37, - 'note_grade': 1, - 'score': 1041, + "music_id": 37, + "note_grade": 1, + "score": 1041, }, { - 'music_id': 102, - 'note_grade': 1, - 'score': 935, + "music_id": 102, + "note_grade": 1, + "score": 935, }, { - 'music_id': 58, - 'note_grade': 1, - 'score': 973, + "music_id": 58, + "note_grade": 1, + "score": 973, }, ] self.verify_player_rb5_player_write_5(ref_id, location, mycourse=firstcourse) profile = self.verify_player_rb5_player_read(ref_id, card, location) - for i in range(len(profile['mycourse'])): - if profile['mycourse'][i]['music_id'] != firstcourse[i]['music_id']: - raise Exception(f'invalid music ID for mycourse entry {i}!') - if profile['mycourse'][i]['note_grade'] != firstcourse[i]['note_grade']: - raise Exception(f'invalid chart for mycourse entry {i}!') - if profile['mycourse'][i]['score'] != firstcourse[i]['score']: - raise Exception(f'invalid score for mycourse entry {i}!') + for i in range(len(profile["mycourse"])): + if profile["mycourse"][i]["music_id"] != firstcourse[i]["music_id"]: + raise Exception(f"invalid music ID for mycourse entry {i}!") + if profile["mycourse"][i]["note_grade"] != firstcourse[i]["note_grade"]: + raise Exception(f"invalid chart for mycourse entry {i}!") + if profile["mycourse"][i]["score"] != firstcourse[i]["score"]: + raise Exception(f"invalid score for mycourse entry {i}!") # Do a worse job on a different course secondcourse = [ { - 'music_id': 2, - 'note_grade': 1, - 'score': 987, + "music_id": 2, + "note_grade": 1, + "score": 987, }, { - 'music_id': 6, - 'note_grade': 1, - 'score': 234, + "music_id": 6, + "note_grade": 1, + "score": 234, }, { - 'music_id': 102, - 'note_grade': 1, - 'score': 876, + "music_id": 102, + "note_grade": 1, + "score": 876, }, { - 'music_id': 58, - 'note_grade': 1, - 'score': 573, + "music_id": 58, + "note_grade": 1, + "score": 573, }, ] self.verify_player_rb5_player_write_5(ref_id, location, mycourse=secondcourse) profile = self.verify_player_rb5_player_read(ref_id, card, location) - for i in range(len(profile['mycourse'])): - if profile['mycourse'][i]['music_id'] != firstcourse[i]['music_id']: - raise Exception(f'invalid music ID for mycourse entry {i}!') - if profile['mycourse'][i]['note_grade'] != firstcourse[i]['note_grade']: - raise Exception(f'invalid chart for mycourse entry {i}!') - if profile['mycourse'][i]['score'] != firstcourse[i]['score']: - raise Exception(f'invalid score for mycourse entry {i}!') + for i in range(len(profile["mycourse"])): + if profile["mycourse"][i]["music_id"] != firstcourse[i]["music_id"]: + raise Exception(f"invalid music ID for mycourse entry {i}!") + if profile["mycourse"][i]["note_grade"] != firstcourse[i]["note_grade"]: + raise Exception(f"invalid chart for mycourse entry {i}!") + if profile["mycourse"][i]["score"] != firstcourse[i]["score"]: + raise Exception(f"invalid score for mycourse entry {i}!") # Now, do better on our course, and verify it updates thirdcourse = [ { - 'music_id': 51, - 'note_grade': 1, - 'score': 1186, + "music_id": 51, + "note_grade": 1, + "score": 1186, }, { - 'music_id': 37, - 'note_grade': 1, - 'score': 1141, + "music_id": 37, + "note_grade": 1, + "score": 1141, }, { - 'music_id': 102, - 'note_grade': 1, - 'score': 1035, + "music_id": 102, + "note_grade": 1, + "score": 1035, }, { - 'music_id': 58, - 'note_grade': 1, - 'score': 1073, + "music_id": 58, + "note_grade": 1, + "score": 1073, }, ] self.verify_player_rb5_player_write_5(ref_id, location, mycourse=thirdcourse) profile = self.verify_player_rb5_player_read(ref_id, card, location) - for i in range(len(profile['mycourse'])): - if profile['mycourse'][i]['music_id'] != thirdcourse[i]['music_id']: - raise Exception(f'invalid music ID for mycourse entry {i}!') - if profile['mycourse'][i]['note_grade'] != thirdcourse[i]['note_grade']: - raise Exception(f'invalid chart for mycourse entry {i}!') - if profile['mycourse'][i]['score'] != thirdcourse[i]['score']: - raise Exception(f'invalid score for mycourse entry {i}!') + for i in range(len(profile["mycourse"])): + if profile["mycourse"][i]["music_id"] != thirdcourse[i]["music_id"]: + raise Exception(f"invalid music ID for mycourse entry {i}!") + if profile["mycourse"][i]["note_grade"] != thirdcourse[i]["note_grade"]: + raise Exception(f"invalid chart for mycourse entry {i}!") + if profile["mycourse"][i]["score"] != thirdcourse[i]["score"]: + raise Exception(f"invalid score for mycourse entry {i}!") # Verify ending game self.verify_player_rb5_player_end(ref_id) diff --git a/bemani/client/sdvx/booth.py b/bemani/client/sdvx/booth.py index 497c939..a205930 100644 --- a/bemani/client/sdvx/booth.py +++ b/bemani/client/sdvx/booth.py @@ -8,30 +8,30 @@ from bemani.protocol import Node class SoundVoltexBoothClient(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_eventlog_write(self, location: str) -> None: call = self.call_node() # Construct node - eventlog = Node.void('eventlog') + eventlog = Node.void("eventlog") call.add_child(eventlog) - eventlog.set_attribute('method', 'write') - eventlog.add_child(Node.u32('retrycnt', 0)) - data = Node.void('data') + eventlog.set_attribute("method", "write") + eventlog.add_child(Node.u32("retrycnt", 0)) + data = Node.void("data") eventlog.add_child(data) - data.add_child(Node.string('eventid', 'S_PWRON')) - data.add_child(Node.s32('eventorder', 0)) - data.add_child(Node.u64('pcbtime', int(time.time() * 1000))) - data.add_child(Node.s64('gamesession', -1)) - data.add_child(Node.string('strdata1', '1.7.6')) - data.add_child(Node.string('strdata2', '')) - data.add_child(Node.s64('numdata1', 1)) - data.add_child(Node.s64('numdata2', 0)) - data.add_child(Node.string('locationid', location)) + data.add_child(Node.string("eventid", "S_PWRON")) + data.add_child(Node.s32("eventorder", 0)) + data.add_child(Node.u64("pcbtime", int(time.time() * 1000))) + data.add_child(Node.s64("gamesession", -1)) + data.add_child(Node.string("strdata1", "1.7.6")) + data.add_child(Node.string("strdata2", "")) + data.add_child(Node.s64("numdata1", 1)) + data.add_child(Node.s64("numdata2", 0)) + data.add_child(Node.string("locationid", location)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/eventlog/gamesession") @@ -42,14 +42,14 @@ class SoundVoltexBoothClient(BaseClient): def verify_game_hiscore(self, location: str) -> None: call = self.call_node() - game = Node.void('game') - game.set_attribute('locid', location) - game.set_attribute('ver', '0') - game.set_attribute('method', 'hiscore') + game = Node.void("game") + game.set_attribute("locid", location) + game.set_attribute("ver", "0") + game.set_attribute("method", "hiscore") call.add_child(game) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/ranking/@id") @@ -62,43 +62,43 @@ class SoundVoltexBoothClient(BaseClient): def verify_game_shop(self, location: str) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'shop') - game.set_attribute('ver', '0') - game.add_child(Node.string('locid', location)) - game.add_child(Node.string('regcode', '.')) - game.add_child(Node.string('locname', '')) - game.add_child(Node.u8('loctype', 0)) - game.add_child(Node.string('cstcode', '')) - game.add_child(Node.string('cpycode', '')) - game.add_child(Node.s32('latde', 0)) - game.add_child(Node.s32('londe', 0)) - game.add_child(Node.u8('accu', 0)) - game.add_child(Node.string('linid', '.')) - game.add_child(Node.u8('linclass', 0)) - game.add_child(Node.ipv4('ipaddr', '0.0.0.0')) - game.add_child(Node.string('hadid', '00010203040506070809')) - game.add_child(Node.string('licid', '00010203040506070809')) - game.add_child(Node.string('actid', self.pcbid)) - game.add_child(Node.s8('appstate', 0)) - game.add_child(Node.s8('c_need', 1)) - game.add_child(Node.s8('c_credit', 2)) - game.add_child(Node.s8('s_credit', 2)) - game.add_child(Node.bool('free_p', True)) - game.add_child(Node.bool('close', True)) - game.add_child(Node.s32('close_t', 1380)) - game.add_child(Node.u32('playc', 0)) - game.add_child(Node.u32('playn', 0)) - game.add_child(Node.u32('playe', 0)) - game.add_child(Node.u32('test_m', 0)) - game.add_child(Node.u32('service', 0)) - game.add_child(Node.bool('paseli', True)) - game.add_child(Node.u32('update', 0)) - game.add_child(Node.string('shopname', '')) + game.set_attribute("method", "shop") + game.set_attribute("ver", "0") + game.add_child(Node.string("locid", location)) + game.add_child(Node.string("regcode", ".")) + game.add_child(Node.string("locname", "")) + game.add_child(Node.u8("loctype", 0)) + game.add_child(Node.string("cstcode", "")) + game.add_child(Node.string("cpycode", "")) + game.add_child(Node.s32("latde", 0)) + game.add_child(Node.s32("londe", 0)) + game.add_child(Node.u8("accu", 0)) + game.add_child(Node.string("linid", ".")) + game.add_child(Node.u8("linclass", 0)) + game.add_child(Node.ipv4("ipaddr", "0.0.0.0")) + game.add_child(Node.string("hadid", "00010203040506070809")) + game.add_child(Node.string("licid", "00010203040506070809")) + game.add_child(Node.string("actid", self.pcbid)) + game.add_child(Node.s8("appstate", 0)) + game.add_child(Node.s8("c_need", 1)) + game.add_child(Node.s8("c_credit", 2)) + game.add_child(Node.s8("s_credit", 2)) + game.add_child(Node.bool("free_p", True)) + game.add_child(Node.bool("close", True)) + game.add_child(Node.s32("close_t", 1380)) + game.add_child(Node.u32("playc", 0)) + game.add_child(Node.u32("playn", 0)) + game.add_child(Node.u32("playe", 0)) + game.add_child(Node.u32("test_m", 0)) + game.add_child(Node.u32("service", 0)) + game.add_child(Node.bool("paseli", True)) + game.add_child(Node.u32("update", 0)) + game.add_child(Node.string("shopname", "")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/nxt_time") @@ -106,17 +106,17 @@ class SoundVoltexBoothClient(BaseClient): def verify_game_new(self, location: str, refid: str) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('name', self.NAME) - game.set_attribute('method', 'new') - game.set_attribute('refid', refid) - game.set_attribute('locid', location) - game.set_attribute('dataid', refid) - game.set_attribute('ver', '0') + game.set_attribute("name", self.NAME) + game.set_attribute("method", "new") + game.set_attribute("refid", refid) + game.set_attribute("locid", location) + game.set_attribute("dataid", refid) + game.set_attribute("ver", "0") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") @@ -124,59 +124,61 @@ class SoundVoltexBoothClient(BaseClient): def verify_game_frozen(self, refid: str, time: int) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('refid', refid) - game.set_attribute('method', 'frozen') - game.set_attribute('ver', '0') - game.add_child(Node.u32('sec', time)) + game.set_attribute("refid", refid) + game.set_attribute("method", "frozen") + game.set_attribute("ver", "0") + game.add_child(Node.u32("sec", time)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/@result") - def verify_game_save(self, location: str, refid: str, packet: int, block: int, exp: int) -> None: + def verify_game_save( + self, location: str, refid: str, packet: int, block: int, exp: int + ) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'save') - game.set_attribute('refid', refid) - game.set_attribute('locid', location) - game.set_attribute('ver', '0') - game.add_child(Node.u8('headphone', 0)) - game.add_child(Node.u8('hispeed', 16)) - game.add_child(Node.u16('appeal_id', 19)) - game.add_child(Node.u16('frame0', 0)) - game.add_child(Node.u16('frame1', 0)) - game.add_child(Node.u16('frame2', 0)) - game.add_child(Node.u16('frame3', 0)) - game.add_child(Node.u16('frame4', 0)) - last = Node.void('last') + game.set_attribute("method", "save") + game.set_attribute("refid", refid) + game.set_attribute("locid", location) + game.set_attribute("ver", "0") + game.add_child(Node.u8("headphone", 0)) + game.add_child(Node.u8("hispeed", 16)) + game.add_child(Node.u16("appeal_id", 19)) + game.add_child(Node.u16("frame0", 0)) + game.add_child(Node.u16("frame1", 0)) + game.add_child(Node.u16("frame2", 0)) + game.add_child(Node.u16("frame3", 0)) + game.add_child(Node.u16("frame4", 0)) + last = Node.void("last") game.add_child(last) - last.set_attribute('music_type', '1') - last.set_attribute('music_id', '29') - last.set_attribute('sort_type', '4') - game.add_child(Node.u32('earned_gamecoin_packet', packet)) - game.add_child(Node.u32('earned_gamecoin_block', block)) - game.add_child(Node.u32('gain_exp', exp)) - game.add_child(Node.u32('m_user_cnt', 0)) - game.add_child(Node.bool_array('have_item', [False] * 512)) - game.add_child(Node.bool_array('have_note', [False] * 512)) - tracking = Node.void('tracking') + last.set_attribute("music_type", "1") + last.set_attribute("music_id", "29") + last.set_attribute("sort_type", "4") + game.add_child(Node.u32("earned_gamecoin_packet", packet)) + game.add_child(Node.u32("earned_gamecoin_block", block)) + game.add_child(Node.u32("gain_exp", exp)) + game.add_child(Node.u32("m_user_cnt", 0)) + game.add_child(Node.bool_array("have_item", [False] * 512)) + game.add_child(Node.bool_array("have_note", [False] * 512)) + tracking = Node.void("tracking") game.add_child(tracking) - m0 = Node.void('m0') + m0 = Node.void("m0") tracking.add_child(m0) - m0.add_child(Node.u8('type', 2)) - m0.add_child(Node.u32('id', 5)) - m0.add_child(Node.u32('score', 774566)) - tracking.add_child(Node.time('p_start', Time.now() - 300)) - tracking.add_child(Node.time('p_end', Time.now())) + m0.add_child(Node.u8("type", 2)) + m0.add_child(Node.u32("id", 5)) + m0.add_child(Node.u32("score", 774566)) + tracking.add_child(Node.time("p_start", Time.now() - 300)) + tracking.add_child(Node.time("p_end", Time.now())) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") @@ -184,13 +186,13 @@ class SoundVoltexBoothClient(BaseClient): def verify_game_common(self) -> None: call = self.call_node() - game = Node.void('game') - game.set_attribute('ver', '0') - game.set_attribute('method', 'common') + game = Node.void("game") + game.set_attribute("ver", "0") + game.set_attribute("method", "common") call.add_child(game) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/limited") @@ -210,20 +212,20 @@ class SoundVoltexBoothClient(BaseClient): def verify_game_buy(self, refid: str, catalogid: int, success: bool) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('refid', refid) - game.set_attribute('ver', '0') - game.set_attribute('method', 'buy') - game.add_child(Node.u32('catalog_id', catalogid)) - game.add_child(Node.u32('earned_gamecoin_packet', 0)) - game.add_child(Node.u32('earned_gamecoin_block', 0)) - game.add_child(Node.u32('open_index', 4)) - game.add_child(Node.u32('currency_type', 1)) - game.add_child(Node.u32('price', 10)) + game.set_attribute("refid", refid) + game.set_attribute("ver", "0") + game.set_attribute("method", "buy") + game.add_child(Node.u32("catalog_id", catalogid)) + game.add_child(Node.u32("earned_gamecoin_packet", 0)) + game.add_child(Node.u32("earned_gamecoin_block", 0)) + game.add_child(Node.u32("open_index", 4)) + game.add_child(Node.u32("currency_type", 1)) + game.add_child(Node.u32("price", 10)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/gamecoin_packet") @@ -231,24 +233,26 @@ class SoundVoltexBoothClient(BaseClient): self.assert_path(resp, "response/game/result") if success: - if resp.child_value('game/result') != 0: - raise Exception('Failed to purchase!') + if resp.child_value("game/result") != 0: + raise Exception("Failed to purchase!") else: - if resp.child_value('game/result') == 0: - raise Exception('Purchased when shouldn\'t have!') + if resp.child_value("game/result") == 0: + raise Exception("Purchased when shouldn't have!") - def verify_game_load(self, cardid: str, refid: str, msg_type: str) -> Dict[str, Any]: + def verify_game_load( + self, cardid: str, refid: str, msg_type: str + ) -> Dict[str, Any]: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('cardid', cardid) - game.set_attribute('dataid', refid) - game.set_attribute('ver', '0') - game.set_attribute('method', 'load') + game.set_attribute("cardid", cardid) + game.set_attribute("dataid", refid) + game.set_attribute("ver", "0") + game.set_attribute("method", "load") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct if msg_type == "new": @@ -277,10 +281,10 @@ class SoundVoltexBoothClient(BaseClient): self.assert_path(resp, "response/game/last/@sort_type") return { - 'name': resp.child_value('game/name'), - 'packet': resp.child_value('game/gamecoin_packet'), - 'block': resp.child_value('game/gamecoin_block'), - 'exp': resp.child_value('game/exp_point'), + "name": resp.child_value("game/name"), + "packet": resp.child_value("game/gamecoin_packet"), + "block": resp.child_value("game/gamecoin_block"), + "exp": resp.child_value("game/exp_point"), } else: raise Exception(f"Invalid game load type {msg_type}") @@ -288,13 +292,13 @@ class SoundVoltexBoothClient(BaseClient): def verify_game_lounge(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'lounge') - game.set_attribute('ver', '0') + game.set_attribute("method", "lounge") + game.set_attribute("ver", "0") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/interval") @@ -302,39 +306,39 @@ class SoundVoltexBoothClient(BaseClient): def verify_game_entry_s(self) -> int: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'entry_s') - game.add_child(Node.u8('c_ver', 22)) - game.add_child(Node.u8('p_num', 1)) - game.add_child(Node.u8('p_rest', 1)) - game.add_child(Node.u8('filter', 1)) - game.add_child(Node.u32('mid', 5)) - game.add_child(Node.u32('sec', 45)) - game.add_child(Node.u16('port', 10007)) - game.add_child(Node.fouru8('gip', [127, 0, 0, 1])) - game.add_child(Node.fouru8('lip', [10, 0, 5, 73])) - game.add_child(Node.bool('claim', True)) + game.set_attribute("ver", "0") + game.set_attribute("method", "entry_s") + game.add_child(Node.u8("c_ver", 22)) + game.add_child(Node.u8("p_num", 1)) + game.add_child(Node.u8("p_rest", 1)) + game.add_child(Node.u8("filter", 1)) + game.add_child(Node.u32("mid", 5)) + game.add_child(Node.u32("sec", 45)) + game.add_child(Node.u16("port", 10007)) + game.add_child(Node.fouru8("gip", [127, 0, 0, 1])) + game.add_child(Node.fouru8("lip", [10, 0, 5, 73])) + game.add_child(Node.bool("claim", True)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/entry_id") - return resp.child_value('game/entry_id') + return resp.child_value("game/entry_id") def verify_game_entry_e(self, eid: int) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'entry_e') - game.set_attribute('ver', '0') - game.add_child(Node.u32('eid', eid)) + game.set_attribute("method", "entry_e") + game.set_attribute("ver", "0") + game.add_child(Node.u32("eid", eid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") @@ -342,62 +346,66 @@ class SoundVoltexBoothClient(BaseClient): def verify_game_load_m(self, refid: str) -> List[Dict[str, int]]: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'load_m') - game.set_attribute('ver', '0') - game.set_attribute('all', '1') - game.set_attribute('dataid', refid) + game.set_attribute("method", "load_m") + game.set_attribute("ver", "0") + game.set_attribute("all", "1") + game.set_attribute("dataid", refid) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") scores = [] - for child in resp.child('game').children: - if child.name != 'music': + for child in resp.child("game").children: + if child.name != "music": continue - musicid = int(child.attribute('music_id')) + musicid = int(child.attribute("music_id")) for typenode in child.children: - if typenode.name != 'type': + if typenode.name != "type": continue - chart = int(typenode.attribute('type_id')) - clear_type = int(typenode.attribute('clear_type')) - score = int(typenode.attribute('score')) - grade = int(typenode.attribute('score_grade')) + chart = int(typenode.attribute("type_id")) + clear_type = int(typenode.attribute("clear_type")) + score = int(typenode.attribute("score")) + grade = int(typenode.attribute("score_grade")) - scores.append({ - 'id': musicid, - 'chart': chart, - 'clear_type': clear_type, - 'score': score, - 'grade': grade, - }) + scores.append( + { + "id": musicid, + "chart": chart, + "clear_type": clear_type, + "score": score, + "grade": grade, + } + ) return scores - def verify_game_save_m(self, location: str, refid: str, score: Dict[str, int]) -> None: + def verify_game_save_m( + self, location: str, refid: str, score: Dict[str, int] + ) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'save_m') - game.set_attribute('max_chain', '0') - game.set_attribute('clear_type', str(score['clear_type'])) - game.set_attribute('music_type', str(score['chart'])) - game.set_attribute('score_grade', str(score['grade'])) - game.set_attribute('locid', location) - game.set_attribute('music_id', str(score['id'])) - game.set_attribute('dataid', refid) - game.set_attribute('ver', '0') - game.set_attribute('score', str(score['score'])) + game.set_attribute("method", "save_m") + game.set_attribute("max_chain", "0") + game.set_attribute("clear_type", str(score["clear_type"])) + game.set_attribute("music_type", str(score["chart"])) + game.set_attribute("score_grade", str(score["grade"])) + game.set_attribute("locid", location) + game.set_attribute("music_id", str(score["id"])) + game.set_attribute("dataid", refid) + game.set_attribute("ver", "0") + game.set_attribute("score", str(score["score"])) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") @@ -406,20 +414,20 @@ class SoundVoltexBoothClient(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -439,25 +447,35 @@ class SoundVoltexBoothClient(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # SDVX doesn't read the new profile, it asks for the profile itself after calling new - self.verify_game_load(card, ref_id, msg_type='new') + self.verify_game_load(card, ref_id, msg_type="new") self.verify_game_new(location, ref_id) - self.verify_game_load(card, ref_id, msg_type='existing') + self.verify_game_load(card, ref_id, msg_type="existing") else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Verify account freezing self.verify_game_frozen(ref_id, 900) @@ -470,57 +488,65 @@ class SoundVoltexBoothClient(BaseClient): if cardid is None: # Verify profile loading and saving - profile = self.verify_game_load(card, ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception(f'Profile has incorrect name {profile["name"]} associated with it!') - if profile['packet'] != 0: - raise Exception('Profile has nonzero blocks associated with it!') - if profile['block'] != 0: - raise Exception('Profile has nonzero packets associated with it!') - if profile['exp'] != 0: - raise Exception('Profile has nonzero exp associated with it!') + profile = self.verify_game_load(card, ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception( + f'Profile has incorrect name {profile["name"]} associated with it!' + ) + if profile["packet"] != 0: + raise Exception("Profile has nonzero blocks associated with it!") + if profile["block"] != 0: + raise Exception("Profile has nonzero packets associated with it!") + if profile["exp"] != 0: + raise Exception("Profile has nonzero exp associated with it!") # Verify purchase failure self.verify_game_buy(ref_id, 1004, False) self.verify_game_save(location, ref_id, packet=123, block=234, exp=42) - profile = self.verify_game_load(card, ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception(f'Profile has incorrect name {profile["name"]} associated with it!') - if profile['packet'] != 123: - raise Exception('Profile has invalid blocks associated with it!') - if profile['block'] != 234: - raise Exception('Profile has invalid packets associated with it!') - if profile['exp'] != 42: - raise Exception('Profile has invalid exp associated with it!') + profile = self.verify_game_load(card, ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception( + f'Profile has incorrect name {profile["name"]} associated with it!' + ) + if profile["packet"] != 123: + raise Exception("Profile has invalid blocks associated with it!") + if profile["block"] != 234: + raise Exception("Profile has invalid packets associated with it!") + if profile["exp"] != 42: + raise Exception("Profile has invalid exp associated with it!") self.verify_game_save(location, ref_id, packet=1, block=2, exp=3) - profile = self.verify_game_load(card, ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception(f'Profile has incorrect name {profile["name"]} associated with it!') - if profile['packet'] != 124: - raise Exception('Profile has invalid blocks associated with it!') - if profile['block'] != 236: - raise Exception('Profile has invalid packets associated with it!') - if profile['exp'] != 45: - raise Exception('Profile has invalid exp associated with it!') + profile = self.verify_game_load(card, ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception( + f'Profile has incorrect name {profile["name"]} associated with it!' + ) + if profile["packet"] != 124: + raise Exception("Profile has invalid blocks associated with it!") + if profile["block"] != 236: + raise Exception("Profile has invalid packets associated with it!") + if profile["exp"] != 45: + raise Exception("Profile has invalid exp associated with it!") # Verify purchase success self.verify_game_buy(ref_id, 1004, True) - profile = self.verify_game_load(card, ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception(f'Profile has incorrect name {profile["name"]} associated with it!') - if profile['packet'] != 124: - raise Exception('Profile has invalid blocks associated with it!') - if profile['block'] != 226: - raise Exception('Profile has invalid packets associated with it!') - if profile['exp'] != 45: - raise Exception('Profile has invalid exp associated with it!') + profile = self.verify_game_load(card, ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception( + f'Profile has incorrect name {profile["name"]} associated with it!' + ) + if profile["packet"] != 124: + raise Exception("Profile has invalid blocks associated with it!") + if profile["block"] != 226: + raise Exception("Profile has invalid packets associated with it!") + if profile["exp"] != 45: + raise Exception("Profile has invalid exp associated with it!") # Verify empty profile has no scores on it scores = self.verify_game_load_m(ref_id) if len(scores) > 0: - raise Exception('Score on an empty profile!') + raise Exception("Score on an empty profile!") # Verify score saving and updating for phase in [1, 2]: @@ -528,57 +554,57 @@ class SoundVoltexBoothClient(BaseClient): dummyscores = [ # An okay score on a chart { - 'id': 1, - 'chart': 1, - 'grade': 3, - 'clear_type': 2, - 'score': 765432, + "id": 1, + "chart": 1, + "grade": 3, + "clear_type": 2, + "score": 765432, }, # A good score on an easier chart of the same song { - 'id': 1, - 'chart': 0, - 'grade': 6, - 'clear_type': 3, - 'score': 7654321, + "id": 1, + "chart": 0, + "grade": 6, + "clear_type": 3, + "score": 7654321, }, # A bad score on a hard chart { - 'id': 2, - 'chart': 2, - 'grade': 1, - 'clear_type': 1, - 'score': 12345, + "id": 2, + "chart": 2, + "grade": 1, + "clear_type": 1, + "score": 12345, }, # A terrible score on an easy chart { - 'id': 3, - 'chart': 0, - 'grade': 1, - 'clear_type': 1, - 'score': 123, + "id": 3, + "chart": 0, + "grade": 1, + "clear_type": 1, + "score": 123, }, ] if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 1, - 'chart': 1, - 'grade': 5, - 'clear_type': 3, - 'score': 8765432, + "id": 1, + "chart": 1, + "grade": 5, + "clear_type": 3, + "score": 8765432, }, # A worse score on another same chart { - 'id': 1, - 'chart': 0, - 'grade': 4, - 'clear_type': 2, - 'score': 6543210, - 'expected_score': 7654321, - 'expected_clear_type': 3, - 'expected_grade': 6, + "id": 1, + "chart": 0, + "grade": 4, + "clear_type": 2, + "score": 6543210, + "expected_score": 7654321, + "expected_clear_type": 3, + "expected_grade": 6, }, ] for dummyscore in dummyscores: @@ -588,32 +614,43 @@ class SoundVoltexBoothClient(BaseClient): for expected in dummyscores: actual = None for received in scores: - if received['id'] == expected['id'] and received['chart'] == expected['chart']: + if ( + received["id"] == expected["id"] + and received["chart"] == expected["chart"] + ): actual = received break if actual is None: - raise Exception(f"Didn't find song {expected['id']} chart {expected['chart']} in response!") + raise Exception( + f"Didn't find song {expected['id']} chart {expected['chart']} in response!" + ) - if 'expected_score' in expected: - expected_score = expected['expected_score'] + if "expected_score" in expected: + expected_score = expected["expected_score"] else: - expected_score = expected['score'] - if 'expected_grade' in expected: - expected_grade = expected['expected_grade'] + expected_score = expected["score"] + if "expected_grade" in expected: + expected_grade = expected["expected_grade"] else: - expected_grade = expected['grade'] - if 'expected_clear_type' in expected: - expected_clear_type = expected['expected_clear_type'] + expected_grade = expected["grade"] + if "expected_clear_type" in expected: + expected_clear_type = expected["expected_clear_type"] else: - expected_clear_type = expected['clear_type'] + expected_clear_type = expected["clear_type"] - if actual['score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'') - if actual['grade'] != expected_grade: - raise Exception(f'Expected a grade of \'{expected_grade}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got grade \'{actual["grade"]}\'') - if actual['clear_type'] != expected_clear_type: - raise Exception(f'Expected a clear_type of \'{expected_clear_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got clear_type \'{actual["clear_type"]}\'') + if actual["score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'' + ) + if actual["grade"] != expected_grade: + raise Exception( + f'Expected a grade of \'{expected_grade}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got grade \'{actual["grade"]}\'' + ) + if actual["clear_type"] != expected_clear_type: + raise Exception( + f'Expected a clear_type of \'{expected_clear_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got clear_type \'{actual["clear_type"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) diff --git a/bemani/client/sdvx/gravitywars_s1.py b/bemani/client/sdvx/gravitywars_s1.py index 14b471d..c0f4e68 100644 --- a/bemani/client/sdvx/gravitywars_s1.py +++ b/bemani/client/sdvx/gravitywars_s1.py @@ -7,30 +7,30 @@ from bemani.protocol import Node class SoundVoltexGravityWarsS1Client(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_eventlog_write(self, location: str) -> None: call = self.call_node() # Construct node - eventlog = Node.void('eventlog') + eventlog = Node.void("eventlog") call.add_child(eventlog) - eventlog.set_attribute('method', 'write') - eventlog.add_child(Node.u32('retrycnt', 0)) - data = Node.void('data') + eventlog.set_attribute("method", "write") + eventlog.add_child(Node.u32("retrycnt", 0)) + data = Node.void("data") eventlog.add_child(data) - data.add_child(Node.string('eventid', 'S_PWRON')) - data.add_child(Node.s32('eventorder', 0)) - data.add_child(Node.u64('pcbtime', int(time.time() * 1000))) - data.add_child(Node.s64('gamesession', -1)) - data.add_child(Node.string('strdata1', '2.3.8')) - data.add_child(Node.string('strdata2', '')) - data.add_child(Node.s64('numdata1', 1)) - data.add_child(Node.s64('numdata2', 0)) - data.add_child(Node.string('locationid', location)) + data.add_child(Node.string("eventid", "S_PWRON")) + data.add_child(Node.s32("eventorder", 0)) + data.add_child(Node.u64("pcbtime", int(time.time() * 1000))) + data.add_child(Node.s64("gamesession", -1)) + data.add_child(Node.string("strdata1", "2.3.8")) + data.add_child(Node.string("strdata2", "")) + data.add_child(Node.s64("numdata1", 1)) + data.add_child(Node.s64("numdata2", 0)) + data.add_child(Node.string("locationid", location)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/eventlog/gamesession") @@ -41,14 +41,14 @@ class SoundVoltexGravityWarsS1Client(BaseClient): def verify_game_hiscore(self, location: str) -> None: call = self.call_node() - game = Node.void('game_3') - game.set_attribute('ver', '0') - game.set_attribute('method', 'hiscore') - game.add_child(Node.string('locid', location)) + game = Node.void("game_3") + game.set_attribute("ver", "0") + game.set_attribute("method", "hiscore") + game.add_child(Node.string("locid", location)) call.add_child(game) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/hitchart/info/id") @@ -70,48 +70,48 @@ class SoundVoltexGravityWarsS1Client(BaseClient): def verify_game_shop(self, location: str) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'shop') - game.set_attribute('ver', '0') - game.add_child(Node.string('locid', location)) - game.add_child(Node.string('regcode', '.')) - game.add_child(Node.string('locname', '')) - game.add_child(Node.u8('loctype', 0)) - game.add_child(Node.string('cstcode', '')) - game.add_child(Node.string('cpycode', '')) - game.add_child(Node.s32('latde', 0)) - game.add_child(Node.s32('londe', 0)) - game.add_child(Node.u8('accu', 0)) - game.add_child(Node.string('linid', '.')) - game.add_child(Node.u8('linclass', 0)) - game.add_child(Node.ipv4('ipaddr', '0.0.0.0')) - game.add_child(Node.string('hadid', '00010203040506070809')) - game.add_child(Node.string('licid', '00010203040506070809')) - game.add_child(Node.string('actid', self.pcbid)) - game.add_child(Node.s8('appstate', 0)) - game.add_child(Node.s8('c_need', 1)) - game.add_child(Node.s8('c_credit', 2)) - game.add_child(Node.s8('s_credit', 2)) - game.add_child(Node.bool('free_p', True)) - game.add_child(Node.bool('close', False)) - game.add_child(Node.s32('close_t', 1380)) - game.add_child(Node.u32('playc', 0)) - game.add_child(Node.u32('playn', 0)) - game.add_child(Node.u32('playe', 0)) - game.add_child(Node.u32('test_m', 0)) - game.add_child(Node.u32('service', 0)) - game.add_child(Node.bool('paseli', True)) - game.add_child(Node.u32('update', 0)) - game.add_child(Node.string('shopname', '')) - game.add_child(Node.bool('newpc', False)) - game.add_child(Node.s32('s_paseli', 206)) - game.add_child(Node.s32('monitor', 1)) - game.add_child(Node.string('romnumber', 'KFC-JA-M01')) - game.add_child(Node.string('etc', 'TaxMode:1,BasicRate:100/1,FirstFree:0')) + game.set_attribute("method", "shop") + game.set_attribute("ver", "0") + game.add_child(Node.string("locid", location)) + game.add_child(Node.string("regcode", ".")) + game.add_child(Node.string("locname", "")) + game.add_child(Node.u8("loctype", 0)) + game.add_child(Node.string("cstcode", "")) + game.add_child(Node.string("cpycode", "")) + game.add_child(Node.s32("latde", 0)) + game.add_child(Node.s32("londe", 0)) + game.add_child(Node.u8("accu", 0)) + game.add_child(Node.string("linid", ".")) + game.add_child(Node.u8("linclass", 0)) + game.add_child(Node.ipv4("ipaddr", "0.0.0.0")) + game.add_child(Node.string("hadid", "00010203040506070809")) + game.add_child(Node.string("licid", "00010203040506070809")) + game.add_child(Node.string("actid", self.pcbid)) + game.add_child(Node.s8("appstate", 0)) + game.add_child(Node.s8("c_need", 1)) + game.add_child(Node.s8("c_credit", 2)) + game.add_child(Node.s8("s_credit", 2)) + game.add_child(Node.bool("free_p", True)) + game.add_child(Node.bool("close", False)) + game.add_child(Node.s32("close_t", 1380)) + game.add_child(Node.u32("playc", 0)) + game.add_child(Node.u32("playn", 0)) + game.add_child(Node.u32("playe", 0)) + game.add_child(Node.u32("test_m", 0)) + game.add_child(Node.u32("service", 0)) + game.add_child(Node.bool("paseli", True)) + game.add_child(Node.u32("update", 0)) + game.add_child(Node.string("shopname", "")) + game.add_child(Node.bool("newpc", False)) + game.add_child(Node.s32("s_paseli", 206)) + game.add_child(Node.s32("monitor", 1)) + game.add_child(Node.string("romnumber", "KFC-JA-M01")) + game.add_child(Node.string("etc", "TaxMode:1,BasicRate:100/1,FirstFree:0")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/nxt_time") @@ -119,17 +119,17 @@ class SoundVoltexGravityWarsS1Client(BaseClient): def verify_game_new(self, location: str, refid: str) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'new') - game.set_attribute('ver', '0') - game.add_child(Node.string('dataid', refid)) - game.add_child(Node.string('refid', refid)) - game.add_child(Node.string('name', self.NAME)) - game.add_child(Node.string('locid', location)) + game.set_attribute("method", "new") + game.set_attribute("ver", "0") + game.add_child(Node.string("dataid", refid)) + game.add_child(Node.string("refid", refid)) + game.add_child(Node.string("name", self.NAME)) + game.add_child(Node.string("locid", location)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3") @@ -137,65 +137,72 @@ class SoundVoltexGravityWarsS1Client(BaseClient): def verify_game_frozen(self, refid: str, time: int) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'frozen') - game.add_child(Node.string('refid', refid)) - game.add_child(Node.u32('sec', time)) + game.set_attribute("ver", "0") + game.set_attribute("method", "frozen") + game.add_child(Node.string("refid", refid)) + game.add_child(Node.u32("sec", time)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/result") - def verify_game_save(self, location: str, refid: str, packet: int, block: int, blaster_energy: int) -> None: + def verify_game_save( + self, location: str, refid: str, packet: int, block: int, blaster_energy: int + ) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'save') - game.set_attribute('ver', '0') - game.add_child(Node.string('refid', refid)) - game.add_child(Node.string('locid', location)) - game.add_child(Node.u8('headphone', 0)) - game.add_child(Node.u16('appeal_id', 1001)) - game.add_child(Node.u16('comment_id', 0)) - game.add_child(Node.s32('music_id', 29)) - game.add_child(Node.u8('music_type', 1)) - game.add_child(Node.u8('sort_type', 1)) - game.add_child(Node.u8('narrow_down', 0)) - game.add_child(Node.u8('gauge_option', 0)) - game.add_child(Node.u32('earned_gamecoin_packet', packet)) - game.add_child(Node.u32('earned_gamecoin_block', block)) - item = Node.void('item') + game.set_attribute("method", "save") + game.set_attribute("ver", "0") + game.add_child(Node.string("refid", refid)) + game.add_child(Node.string("locid", location)) + game.add_child(Node.u8("headphone", 0)) + game.add_child(Node.u16("appeal_id", 1001)) + game.add_child(Node.u16("comment_id", 0)) + game.add_child(Node.s32("music_id", 29)) + game.add_child(Node.u8("music_type", 1)) + game.add_child(Node.u8("sort_type", 1)) + game.add_child(Node.u8("narrow_down", 0)) + game.add_child(Node.u8("gauge_option", 0)) + game.add_child(Node.u32("earned_gamecoin_packet", packet)) + game.add_child(Node.u32("earned_gamecoin_block", block)) + item = Node.void("item") game.add_child(item) - info = Node.void('info') + info = Node.void("info") item.add_child(info) - info.add_child(Node.u32('id', 1)) - info.add_child(Node.u32('type', 5)) - info.add_child(Node.u32('param', 333333376)) - info = Node.void('info') + info.add_child(Node.u32("id", 1)) + info.add_child(Node.u32("type", 5)) + info.add_child(Node.u32("param", 333333376)) + info = Node.void("info") item.add_child(info) - info.add_child(Node.u32('id', 0)) - info.add_child(Node.u32('type', 5)) - info.add_child(Node.u32('param', 600)) - game.add_child(Node.s32_array('hidden_param', [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])) - game.add_child(Node.s16('skill_name_id', -1)) - game.add_child(Node.s32('earned_blaster_energy', blaster_energy)) - game.add_child(Node.u32('blaster_count', 0)) - printn = Node.void('print') + info.add_child(Node.u32("id", 0)) + info.add_child(Node.u32("type", 5)) + info.add_child(Node.u32("param", 600)) + game.add_child( + Node.s32_array( + "hidden_param", + [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ) + ) + game.add_child(Node.s16("skill_name_id", -1)) + game.add_child(Node.s32("earned_blaster_energy", blaster_energy)) + game.add_child(Node.u32("blaster_count", 0)) + printn = Node.void("print") game.add_child(printn) - printn.add_child(Node.s32('count', 0)) - ea_shop = Node.void('ea_shop') + printn.add_child(Node.s32("count", 0)) + ea_shop = Node.void("ea_shop") game.add_child(ea_shop) - ea_shop.add_child(Node.s32('used_packet_booster', 0)) - ea_shop.add_child(Node.s32('used_block_booster', 0)) - game.add_child(Node.s8('start_option', 0)) + ea_shop.add_child(Node.s32("used_packet_booster", 0)) + ea_shop.add_child(Node.s32("used_block_booster", 0)) + game.add_child(Node.s8("start_option", 0)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3") @@ -203,19 +210,19 @@ class SoundVoltexGravityWarsS1Client(BaseClient): def verify_game_common(self, loc: str) -> None: call = self.call_node() - game = Node.void('game_3') - game.set_attribute('ver', '0') - game.set_attribute('method', 'common') - game.add_child(Node.string('locid', loc)) - game.add_child(Node.string('cstcode', '')) - game.add_child(Node.string('cpycode', '')) - game.add_child(Node.string('hadid', '1000')) - game.add_child(Node.string('licid', '1000')) - game.add_child(Node.string('actid', self.pcbid)) + game = Node.void("game_3") + game.set_attribute("ver", "0") + game.set_attribute("method", "common") + game.add_child(Node.string("locid", loc)) + game.add_child(Node.string("cstcode", "")) + game.add_child(Node.string("cpycode", "")) + game.add_child(Node.string("hadid", "1000")) + game.add_child(Node.string("licid", "1000")) + game.add_child(Node.string("actid", self.pcbid)) call.add_child(game) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/music_limited") @@ -235,28 +242,39 @@ class SoundVoltexGravityWarsS1Client(BaseClient): self.assert_path(resp, "response/game_3/skill_course/info/track/music_id") self.assert_path(resp, "response/game_3/skill_course/info/track/music_type") - def verify_game_buy(self, refid: str, catalogtype: int, catalogid: int, currencytype: int, price: int, itemtype: int, itemid: int, param: int, success: bool) -> None: + def verify_game_buy( + self, + refid: str, + catalogtype: int, + catalogid: int, + currencytype: int, + price: int, + itemtype: int, + itemid: int, + param: int, + success: bool, + ) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'buy') - game.add_child(Node.string('refid', refid)) - game.add_child(Node.u8('catalog_type', catalogtype)) - game.add_child(Node.u32('catalog_id', catalogid)) - game.add_child(Node.u32('earned_gamecoin_packet', 0)) - game.add_child(Node.u32('earned_gamecoin_block', 0)) - game.add_child(Node.u32('currency_type', currencytype)) - item = Node.void('item') + game.set_attribute("ver", "0") + game.set_attribute("method", "buy") + game.add_child(Node.string("refid", refid)) + game.add_child(Node.u8("catalog_type", catalogtype)) + game.add_child(Node.u32("catalog_id", catalogid)) + game.add_child(Node.u32("earned_gamecoin_packet", 0)) + game.add_child(Node.u32("earned_gamecoin_block", 0)) + game.add_child(Node.u32("currency_type", currencytype)) + item = Node.void("item") game.add_child(item) - item.add_child(Node.u32('item_type', itemtype)) - item.add_child(Node.u32('item_id', itemid)) - item.add_child(Node.u32('param', param)) - item.add_child(Node.u32('price', price)) + item.add_child(Node.u32("item_type", itemtype)) + item.add_child(Node.u32("item_id", itemid)) + item.add_child(Node.u32("param", param)) + item.add_child(Node.u32("price", price)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/gamecoin_packet") @@ -264,30 +282,32 @@ class SoundVoltexGravityWarsS1Client(BaseClient): self.assert_path(resp, "response/game_3/result") if success: - if resp.child_value('game_3/result') != 0: - raise Exception('Failed to purchase!') + if resp.child_value("game_3/result") != 0: + raise Exception("Failed to purchase!") else: - if resp.child_value('game_3/result') == 0: - raise Exception('Purchased when shouldn\'t have!') + if resp.child_value("game_3/result") == 0: + raise Exception("Purchased when shouldn't have!") - def verify_game_load(self, cardid: str, refid: str, msg_type: str) -> Dict[str, Any]: + def verify_game_load( + self, cardid: str, refid: str, msg_type: str + ) -> Dict[str, Any]: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'load') - game.set_attribute('ver', '0') - game.add_child(Node.string('dataid', refid)) - game.add_child(Node.string('cardid', cardid)) - game.add_child(Node.string('refid', refid)) + game.set_attribute("method", "load") + game.set_attribute("ver", "0") + game.add_child(Node.string("dataid", refid)) + game.add_child(Node.string("cardid", cardid)) + game.add_child(Node.string("refid", refid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct if msg_type == "new": self.assert_path(resp, "response/game_3/result") - if resp.child_value('game_3/result') != 1: + if resp.child_value("game_3/result") != 1: raise Exception("Invalid result for new profile!") return None @@ -308,42 +328,42 @@ class SoundVoltexGravityWarsS1Client(BaseClient): self.assert_path(resp, "response/game_3/story") items: Dict[int, Dict[int, int]] = {} - for child in resp.child('game_3/item').children: - if child.name != 'info': + for child in resp.child("game_3/item").children: + if child.name != "info": continue - itype = child.child_value('type') - iid = child.child_value('id') - param = child.child_value('param') + itype = child.child_value("type") + iid = child.child_value("id") + param = child.child_value("param") if itype not in items: items[itype] = {} items[itype][iid] = param courses: Dict[int, Dict[int, Dict[str, int]]] = {} - for child in resp.child('game_3/skill/course_all').children: - if child.name != 'd': + for child in resp.child("game_3/skill/course_all").children: + if child.name != "d": continue - crsid = child.child_value('crsid') - season = child.child_value('ssnid') - achievement_rate = child.child_value('ar') - clear_type = child.child_value('ct') + crsid = child.child_value("crsid") + season = child.child_value("ssnid") + achievement_rate = child.child_value("ar") + clear_type = child.child_value("ct") if season not in courses: courses[season] = {} courses[season][crsid] = { - 'achievement_rate': achievement_rate, - 'clear_type': clear_type, + "achievement_rate": achievement_rate, + "clear_type": clear_type, } return { - 'name': resp.child_value('game_3/name'), - 'packet': resp.child_value('game_3/gamecoin_packet'), - 'block': resp.child_value('game_3/gamecoin_block'), - 'blaster_energy': resp.child_value('game_3/blaster_energy'), - 'items': items, - 'courses': courses, + "name": resp.child_value("game_3/name"), + "packet": resp.child_value("game_3/gamecoin_packet"), + "block": resp.child_value("game_3/gamecoin_block"), + "blaster_energy": resp.child_value("game_3/blaster_energy"), + "items": items, + "courses": courses, } else: raise Exception(f"Invalid game load type {msg_type}") @@ -351,13 +371,13 @@ class SoundVoltexGravityWarsS1Client(BaseClient): def verify_game_lounge(self) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'lounge') - game.set_attribute('ver', '0') + game.set_attribute("method", "lounge") + game.set_attribute("ver", "0") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/interval") @@ -365,39 +385,39 @@ class SoundVoltexGravityWarsS1Client(BaseClient): def verify_game_entry_s(self) -> int: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'entry_s') - game.add_child(Node.u8('c_ver', 101)) - game.add_child(Node.u8('p_num', 1)) - game.add_child(Node.u8('p_rest', 1)) - game.add_child(Node.u8('filter', 1)) - game.add_child(Node.u32('mid', 416)) - game.add_child(Node.u32('sec', 45)) - game.add_child(Node.u16('port', 10007)) - game.add_child(Node.fouru8('gip', [127, 0, 0, 1])) - game.add_child(Node.fouru8('lip', [10, 0, 5, 73])) - game.add_child(Node.u8('claim', 0)) + game.set_attribute("ver", "0") + game.set_attribute("method", "entry_s") + game.add_child(Node.u8("c_ver", 101)) + game.add_child(Node.u8("p_num", 1)) + game.add_child(Node.u8("p_rest", 1)) + game.add_child(Node.u8("filter", 1)) + game.add_child(Node.u32("mid", 416)) + game.add_child(Node.u32("sec", 45)) + game.add_child(Node.u16("port", 10007)) + game.add_child(Node.fouru8("gip", [127, 0, 0, 1])) + game.add_child(Node.fouru8("lip", [10, 0, 5, 73])) + game.add_child(Node.u8("claim", 0)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/entry_id") - return resp.child_value('game_3/entry_id') + return resp.child_value("game_3/entry_id") def verify_game_entry_e(self, eid: int) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'entry_e') - game.set_attribute('ver', '0') - game.add_child(Node.u32('eid', eid)) + game.set_attribute("method", "entry_e") + game.set_attribute("ver", "0") + game.add_child(Node.u32("eid", eid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3") @@ -405,14 +425,14 @@ class SoundVoltexGravityWarsS1Client(BaseClient): def verify_game_save_e(self, refid: str) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'save_e') - game.set_attribute('ver', '0') - game.add_child(Node.string('refid', refid)) + game.set_attribute("method", "save_e") + game.set_attribute("ver", "0") + game.add_child(Node.string("refid", refid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3") @@ -420,25 +440,25 @@ class SoundVoltexGravityWarsS1Client(BaseClient): def verify_game_play_e(self, location: str, refid: str) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'play_e') - game.add_child(Node.string('dataid', refid)) - game.add_child(Node.s8('mode', 2)) - game.add_child(Node.s16('track_num', 3)) - game.add_child(Node.s32('s_coin', 0)) - game.add_child(Node.s32('s_paseli', 0)) - game.add_child(Node.s16('blaster_count', 0)) - game.add_child(Node.s16('blaster_cartridge', 0)) - game.add_child(Node.string('locid', location)) - game.add_child(Node.u16('drop_frame', 396)) - game.add_child(Node.u16('drop_frame_max', 396)) - game.add_child(Node.u16('drop_count', 1)) - game.add_child(Node.string('etc', 'StoryID:0,StoryPrg:0,PrgPrm:0')) + game.set_attribute("ver", "0") + game.set_attribute("method", "play_e") + game.add_child(Node.string("dataid", refid)) + game.add_child(Node.s8("mode", 2)) + game.add_child(Node.s16("track_num", 3)) + game.add_child(Node.s32("s_coin", 0)) + game.add_child(Node.s32("s_paseli", 0)) + game.add_child(Node.s16("blaster_count", 0)) + game.add_child(Node.s16("blaster_cartridge", 0)) + game.add_child(Node.string("locid", location)) + game.add_child(Node.u16("drop_frame", 396)) + game.add_child(Node.u16("drop_frame_max", 396)) + game.add_child(Node.u16("drop_count", 1)) + game.add_child(Node.string("etc", "StoryID:0,StoryPrg:0,PrgPrm:0")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3") @@ -446,89 +466,95 @@ class SoundVoltexGravityWarsS1Client(BaseClient): def verify_game_load_m(self, refid: str) -> List[Dict[str, int]]: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'load_m') - game.set_attribute('ver', '0') - game.add_child(Node.string('dataid', refid)) + game.set_attribute("method", "load_m") + game.set_attribute("ver", "0") + game.add_child(Node.string("dataid", refid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/new") scores = [] - for child in resp.child('game_3/new').children: - if child.name != 'music': + for child in resp.child("game_3/new").children: + if child.name != "music": continue - musicid = child.child_value('music_id') - chart = child.child_value('music_type') - clear_type = child.child_value('clear_type') - score = child.child_value('score') - grade = child.child_value('score_grade') + musicid = child.child_value("music_id") + chart = child.child_value("music_type") + clear_type = child.child_value("clear_type") + score = child.child_value("score") + grade = child.child_value("score_grade") - scores.append({ - 'id': musicid, - 'chart': chart, - 'clear_type': clear_type, - 'score': score, - 'grade': grade, - }) + scores.append( + { + "id": musicid, + "chart": chart, + "clear_type": clear_type, + "score": score, + "grade": grade, + } + ) return scores - def verify_game_save_m(self, location: str, refid: str, score: Dict[str, int]) -> None: + def verify_game_save_m( + self, location: str, refid: str, score: Dict[str, int] + ) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'save_m') - game.add_child(Node.string('refid', refid)) - game.add_child(Node.string('dataid', refid)) - game.add_child(Node.u32('music_id', score['id'])) - game.add_child(Node.u32('music_type', score['chart'])) - game.add_child(Node.u32('score', score['score'])) - game.add_child(Node.u32('clear_type', score['clear_type'])) - game.add_child(Node.u32('score_grade', score['grade'])) - game.add_child(Node.u32('max_chain', 0)) - game.add_child(Node.u32('critical', 0)) - game.add_child(Node.u32('near', 0)) - game.add_child(Node.u32('error', 0)) - game.add_child(Node.u32('effective_rate', 100)) - game.add_child(Node.u32('btn_rate', 0)) - game.add_child(Node.u32('long_rate', 0)) - game.add_child(Node.u32('vol_rate', 0)) - game.add_child(Node.u8('mode', 0)) - game.add_child(Node.u8('gauge_type', 0)) - game.add_child(Node.u16('online_num', 0)) - game.add_child(Node.u16('local_num', 0)) - game.add_child(Node.string('locid', location)) + game.set_attribute("ver", "0") + game.set_attribute("method", "save_m") + game.add_child(Node.string("refid", refid)) + game.add_child(Node.string("dataid", refid)) + game.add_child(Node.u32("music_id", score["id"])) + game.add_child(Node.u32("music_type", score["chart"])) + game.add_child(Node.u32("score", score["score"])) + game.add_child(Node.u32("clear_type", score["clear_type"])) + game.add_child(Node.u32("score_grade", score["grade"])) + game.add_child(Node.u32("max_chain", 0)) + game.add_child(Node.u32("critical", 0)) + game.add_child(Node.u32("near", 0)) + game.add_child(Node.u32("error", 0)) + game.add_child(Node.u32("effective_rate", 100)) + game.add_child(Node.u32("btn_rate", 0)) + game.add_child(Node.u32("long_rate", 0)) + game.add_child(Node.u32("vol_rate", 0)) + game.add_child(Node.u8("mode", 0)) + game.add_child(Node.u8("gauge_type", 0)) + game.add_child(Node.u16("online_num", 0)) + game.add_child(Node.u16("local_num", 0)) + game.add_child(Node.string("locid", location)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3") - def verify_game_save_c(self, location: str, refid: str, season: int, course: int) -> None: + def verify_game_save_c( + self, location: str, refid: str, season: int, course: int + ) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'save_c') - game.add_child(Node.string('dataid', refid)) - game.add_child(Node.s16('crsid', course)) - game.add_child(Node.s16('ct', 2)) - game.add_child(Node.s16('ar', 15000)) - game.add_child(Node.s32('ssnid', season)) - game.add_child(Node.string('locid', location)) + game.set_attribute("ver", "0") + game.set_attribute("method", "save_c") + game.add_child(Node.string("dataid", refid)) + game.add_child(Node.s16("crsid", course)) + game.add_child(Node.s16("ct", 2)) + game.add_child(Node.s16("ar", 15000)) + game.add_child(Node.s32("ssnid", season)) + game.add_child(Node.string("locid", location)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3") @@ -537,20 +563,20 @@ class SoundVoltexGravityWarsS1Client(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -570,25 +596,35 @@ class SoundVoltexGravityWarsS1Client(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # SDVX doesn't read the new profile, it asks for the profile itself after calling new - self.verify_game_load(card, ref_id, msg_type='new') + self.verify_game_load(card, ref_id, msg_type="new") self.verify_game_new(location, ref_id) - self.verify_game_load(card, ref_id, msg_type='existing') + self.verify_game_load(card, ref_id, msg_type="existing") else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Verify account freezing self.verify_game_frozen(ref_id, 900) @@ -603,85 +639,103 @@ class SoundVoltexGravityWarsS1Client(BaseClient): if cardid is None: # Verify profile loading and saving - profile = self.verify_game_load(card, ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception(f'Profile has incorrect name {profile["name"]} associated with it!') - if profile['packet'] != 0: - raise Exception('Profile has nonzero blocks associated with it!') - if profile['block'] != 0: - raise Exception('Profile has nonzero packets associated with it!') - if profile['blaster_energy'] != 0: - raise Exception('Profile has nonzero blaster energy associated with it!') - if profile['items']: - raise Exception('Profile already has purchased items!') - if profile['courses']: - raise Exception('Profile already has finished courses!') + profile = self.verify_game_load(card, ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception( + f'Profile has incorrect name {profile["name"]} associated with it!' + ) + if profile["packet"] != 0: + raise Exception("Profile has nonzero blocks associated with it!") + if profile["block"] != 0: + raise Exception("Profile has nonzero packets associated with it!") + if profile["blaster_energy"] != 0: + raise Exception( + "Profile has nonzero blaster energy associated with it!" + ) + if profile["items"]: + raise Exception("Profile already has purchased items!") + if profile["courses"]: + raise Exception("Profile already has finished courses!") # Verify purchase failure, try buying song we can't afford self.verify_game_buy(ref_id, 0, 29, 1, 10, 0, 29, 3, False) - self.verify_game_save(location, ref_id, packet=123, block=234, blaster_energy=42) - profile = self.verify_game_load(card, ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception(f'Profile has incorrect name {profile["name"]} associated with it!') - if profile['packet'] != 123: - raise Exception('Profile has invalid blocks associated with it!') - if profile['block'] != 234: - raise Exception('Profile has invalid packets associated with it!') - if profile['blaster_energy'] != 42: - raise Exception('Profile has invalid blaster energy associated with it!') - if 5 not in profile['items']: - raise Exception('Profile doesn\'t have user settings items in it!') - if profile['courses']: - raise Exception('Profile already has finished courses!') + self.verify_game_save( + location, ref_id, packet=123, block=234, blaster_energy=42 + ) + profile = self.verify_game_load(card, ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception( + f'Profile has incorrect name {profile["name"]} associated with it!' + ) + if profile["packet"] != 123: + raise Exception("Profile has invalid blocks associated with it!") + if profile["block"] != 234: + raise Exception("Profile has invalid packets associated with it!") + if profile["blaster_energy"] != 42: + raise Exception( + "Profile has invalid blaster energy associated with it!" + ) + if 5 not in profile["items"]: + raise Exception("Profile doesn't have user settings items in it!") + if profile["courses"]: + raise Exception("Profile already has finished courses!") self.verify_game_save(location, ref_id, packet=1, block=2, blaster_energy=3) - profile = self.verify_game_load(card, ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception(f'Profile has incorrect name {profile["name"]} associated with it!') - if profile['packet'] != 124: - raise Exception('Profile has invalid blocks associated with it!') - if profile['block'] != 236: - raise Exception('Profile has invalid packets associated with it!') - if profile['blaster_energy'] != 45: - raise Exception('Profile has invalid blaster energy associated with it!') - if 5 not in profile['items']: - raise Exception('Profile doesn\'t have user settings items in it!') - if profile['courses']: - raise Exception('Profile has invalid finished courses!') + profile = self.verify_game_load(card, ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception( + f'Profile has incorrect name {profile["name"]} associated with it!' + ) + if profile["packet"] != 124: + raise Exception("Profile has invalid blocks associated with it!") + if profile["block"] != 236: + raise Exception("Profile has invalid packets associated with it!") + if profile["blaster_energy"] != 45: + raise Exception( + "Profile has invalid blaster energy associated with it!" + ) + if 5 not in profile["items"]: + raise Exception("Profile doesn't have user settings items in it!") + if profile["courses"]: + raise Exception("Profile has invalid finished courses!") # Verify purchase success, buy a song we can afford now self.verify_game_buy(ref_id, 0, 29, 1, 10, 0, 29, 3, True) - profile = self.verify_game_load(card, ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception(f'Profile has incorrect name {profile["name"]} associated with it!') - if profile['packet'] != 124: - raise Exception('Profile has invalid blocks associated with it!') - if profile['block'] != 226: - raise Exception('Profile has invalid packets associated with it!') - if profile['blaster_energy'] != 45: - raise Exception('Profile has invalid blaster energy associated with it!') - if 0 not in profile['items'] or 29 not in profile['items'][0]: - raise Exception('Purchase didn\'t add to profile!') - if profile['items'][0][29] != 3: - raise Exception('Purchase parameters are wrong!') - if profile['courses']: - raise Exception('Profile has invalid finished courses!') + profile = self.verify_game_load(card, ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception( + f'Profile has incorrect name {profile["name"]} associated with it!' + ) + if profile["packet"] != 124: + raise Exception("Profile has invalid blocks associated with it!") + if profile["block"] != 226: + raise Exception("Profile has invalid packets associated with it!") + if profile["blaster_energy"] != 45: + raise Exception( + "Profile has invalid blaster energy associated with it!" + ) + if 0 not in profile["items"] or 29 not in profile["items"][0]: + raise Exception("Purchase didn't add to profile!") + if profile["items"][0][29] != 3: + raise Exception("Purchase parameters are wrong!") + if profile["courses"]: + raise Exception("Profile has invalid finished courses!") # Verify that we can finish skill analyzer courses self.verify_game_save_c(location, ref_id, 14, 3) - profile = self.verify_game_load(card, ref_id, msg_type='existing') - if 14 not in profile['courses'] or 3 not in profile['courses'][14]: - raise Exception('Course didn\'t add to profile!') - if profile['courses'][14][3]['achievement_rate'] != 15000: - raise Exception('Course didn\'t save achievement rate!') - if profile['courses'][14][3]['clear_type'] != 2: - raise Exception('Course didn\'t save clear type!') + profile = self.verify_game_load(card, ref_id, msg_type="existing") + if 14 not in profile["courses"] or 3 not in profile["courses"][14]: + raise Exception("Course didn't add to profile!") + if profile["courses"][14][3]["achievement_rate"] != 15000: + raise Exception("Course didn't save achievement rate!") + if profile["courses"][14][3]["clear_type"] != 2: + raise Exception("Course didn't save clear type!") # Verify empty profile has no scores on it scores = self.verify_game_load_m(ref_id) if len(scores) > 0: - raise Exception('Score on an empty profile!') + raise Exception("Score on an empty profile!") # Verify score saving and updating for phase in [1, 2]: @@ -689,57 +743,57 @@ class SoundVoltexGravityWarsS1Client(BaseClient): dummyscores = [ # An okay score on a chart { - 'id': 1, - 'chart': 1, - 'grade': 3, - 'clear_type': 2, - 'score': 765432, + "id": 1, + "chart": 1, + "grade": 3, + "clear_type": 2, + "score": 765432, }, # A good score on an easier chart of the same song { - 'id': 1, - 'chart': 0, - 'grade': 6, - 'clear_type': 3, - 'score': 7654321, + "id": 1, + "chart": 0, + "grade": 6, + "clear_type": 3, + "score": 7654321, }, # A bad score on a hard chart { - 'id': 2, - 'chart': 2, - 'grade': 1, - 'clear_type': 1, - 'score': 12345, + "id": 2, + "chart": 2, + "grade": 1, + "clear_type": 1, + "score": 12345, }, # A terrible score on an easy chart { - 'id': 3, - 'chart': 0, - 'grade': 1, - 'clear_type': 1, - 'score': 123, + "id": 3, + "chart": 0, + "grade": 1, + "clear_type": 1, + "score": 123, }, ] if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 1, - 'chart': 1, - 'grade': 5, - 'clear_type': 3, - 'score': 8765432, + "id": 1, + "chart": 1, + "grade": 5, + "clear_type": 3, + "score": 8765432, }, # A worse score on another same chart { - 'id': 1, - 'chart': 0, - 'grade': 4, - 'clear_type': 2, - 'score': 6543210, - 'expected_score': 7654321, - 'expected_clear_type': 3, - 'expected_grade': 6, + "id": 1, + "chart": 0, + "grade": 4, + "clear_type": 2, + "score": 6543210, + "expected_score": 7654321, + "expected_clear_type": 3, + "expected_grade": 6, }, ] for dummyscore in dummyscores: @@ -749,32 +803,43 @@ class SoundVoltexGravityWarsS1Client(BaseClient): for expected in dummyscores: actual = None for received in scores: - if received['id'] == expected['id'] and received['chart'] == expected['chart']: + if ( + received["id"] == expected["id"] + and received["chart"] == expected["chart"] + ): actual = received break if actual is None: - raise Exception(f"Didn't find song {expected['id']} chart {expected['chart']} in response!") + raise Exception( + f"Didn't find song {expected['id']} chart {expected['chart']} in response!" + ) - if 'expected_score' in expected: - expected_score = expected['expected_score'] + if "expected_score" in expected: + expected_score = expected["expected_score"] else: - expected_score = expected['score'] - if 'expected_grade' in expected: - expected_grade = expected['expected_grade'] + expected_score = expected["score"] + if "expected_grade" in expected: + expected_grade = expected["expected_grade"] else: - expected_grade = expected['grade'] - if 'expected_clear_type' in expected: - expected_clear_type = expected['expected_clear_type'] + expected_grade = expected["grade"] + if "expected_clear_type" in expected: + expected_clear_type = expected["expected_clear_type"] else: - expected_clear_type = expected['clear_type'] + expected_clear_type = expected["clear_type"] - if actual['score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'') - if actual['grade'] != expected_grade: - raise Exception(f'Expected a grade of \'{expected_grade}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got grade \'{actual["grade"]}\'') - if actual['clear_type'] != expected_clear_type: - raise Exception(f'Expected a clear_type of \'{expected_clear_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got clear_type \'{actual["clear_type"]}\'') + if actual["score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'' + ) + if actual["grade"] != expected_grade: + raise Exception( + f'Expected a grade of \'{expected_grade}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got grade \'{actual["grade"]}\'' + ) + if actual["clear_type"] != expected_clear_type: + raise Exception( + f'Expected a clear_type of \'{expected_clear_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got clear_type \'{actual["clear_type"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) diff --git a/bemani/client/sdvx/gravitywars_s2.py b/bemani/client/sdvx/gravitywars_s2.py index 7927d25..8a2ca7f 100644 --- a/bemani/client/sdvx/gravitywars_s2.py +++ b/bemani/client/sdvx/gravitywars_s2.py @@ -7,30 +7,30 @@ from bemani.protocol import Node class SoundVoltexGravityWarsS2Client(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_eventlog_write(self, location: str) -> None: call = self.call_node() # Construct node - eventlog = Node.void('eventlog') + eventlog = Node.void("eventlog") call.add_child(eventlog) - eventlog.set_attribute('method', 'write') - eventlog.add_child(Node.u32('retrycnt', 0)) - data = Node.void('data') + eventlog.set_attribute("method", "write") + eventlog.add_child(Node.u32("retrycnt", 0)) + data = Node.void("data") eventlog.add_child(data) - data.add_child(Node.string('eventid', 'S_PWRON')) - data.add_child(Node.s32('eventorder', 0)) - data.add_child(Node.u64('pcbtime', int(time.time() * 1000))) - data.add_child(Node.s64('gamesession', -1)) - data.add_child(Node.string('strdata1', '2.3.8')) - data.add_child(Node.string('strdata2', '')) - data.add_child(Node.s64('numdata1', 1)) - data.add_child(Node.s64('numdata2', 0)) - data.add_child(Node.string('locationid', location)) + data.add_child(Node.string("eventid", "S_PWRON")) + data.add_child(Node.s32("eventorder", 0)) + data.add_child(Node.u64("pcbtime", int(time.time() * 1000))) + data.add_child(Node.s64("gamesession", -1)) + data.add_child(Node.string("strdata1", "2.3.8")) + data.add_child(Node.string("strdata2", "")) + data.add_child(Node.s64("numdata1", 1)) + data.add_child(Node.s64("numdata2", 0)) + data.add_child(Node.string("locationid", location)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/eventlog/gamesession") @@ -41,14 +41,14 @@ class SoundVoltexGravityWarsS2Client(BaseClient): def verify_game_hiscore(self, location: str) -> None: call = self.call_node() - game = Node.void('game_3') - game.set_attribute('ver', '0') - game.set_attribute('method', 'hiscore') - game.add_child(Node.string('locid', location)) + game = Node.void("game_3") + game.set_attribute("ver", "0") + game.set_attribute("method", "hiscore") + game.add_child(Node.string("locid", location)) call.add_child(game) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/hit/d/id") @@ -63,60 +63,62 @@ class SoundVoltexGravityWarsS2Client(BaseClient): def verify_game_shop(self, location: str) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'shop') - game.set_attribute('ver', '0') - game.add_child(Node.string('locid', location)) - game.add_child(Node.string('regcode', '.')) - game.add_child(Node.string('locname', '')) - game.add_child(Node.u8('loctype', 0)) - game.add_child(Node.string('cstcode', '')) - game.add_child(Node.string('cpycode', '')) - game.add_child(Node.s32('latde', 0)) - game.add_child(Node.s32('londe', 0)) - game.add_child(Node.u8('accu', 0)) - game.add_child(Node.string('linid', '.')) - game.add_child(Node.u8('linclass', 0)) - game.add_child(Node.ipv4('ipaddr', '0.0.0.0')) - game.add_child(Node.string('hadid', '00010203040506070809')) - game.add_child(Node.string('licid', '00010203040506070809')) - game.add_child(Node.string('actid', self.pcbid)) - game.add_child(Node.s8('appstate', 0)) - game.add_child(Node.s8('c_need', 1)) - game.add_child(Node.s8('c_credit', 2)) - game.add_child(Node.s8('s_credit', 2)) - game.add_child(Node.bool('free_p', True)) - game.add_child(Node.bool('close', False)) - game.add_child(Node.s32('close_t', 1380)) - game.add_child(Node.u32('playc', 0)) - game.add_child(Node.u32('playn', 0)) - game.add_child(Node.u32('playe', 0)) - game.add_child(Node.u32('test_m', 0)) - game.add_child(Node.u32('service', 0)) - game.add_child(Node.bool('paseli', True)) - game.add_child(Node.u32('update', 0)) - game.add_child(Node.string('shopname', '')) - game.add_child(Node.bool('newpc', False)) - game.add_child(Node.s32('s_paseli', 206)) - game.add_child(Node.s32('monitor', 1)) - game.add_child(Node.string('romnumber', 'KFC-JA-B01')) - game.add_child(Node.string('etc', 'TaxMode:1,BasicRate:100/1,FirstFree:0')) - setting = Node.void('setting') + game.set_attribute("method", "shop") + game.set_attribute("ver", "0") + game.add_child(Node.string("locid", location)) + game.add_child(Node.string("regcode", ".")) + game.add_child(Node.string("locname", "")) + game.add_child(Node.u8("loctype", 0)) + game.add_child(Node.string("cstcode", "")) + game.add_child(Node.string("cpycode", "")) + game.add_child(Node.s32("latde", 0)) + game.add_child(Node.s32("londe", 0)) + game.add_child(Node.u8("accu", 0)) + game.add_child(Node.string("linid", ".")) + game.add_child(Node.u8("linclass", 0)) + game.add_child(Node.ipv4("ipaddr", "0.0.0.0")) + game.add_child(Node.string("hadid", "00010203040506070809")) + game.add_child(Node.string("licid", "00010203040506070809")) + game.add_child(Node.string("actid", self.pcbid)) + game.add_child(Node.s8("appstate", 0)) + game.add_child(Node.s8("c_need", 1)) + game.add_child(Node.s8("c_credit", 2)) + game.add_child(Node.s8("s_credit", 2)) + game.add_child(Node.bool("free_p", True)) + game.add_child(Node.bool("close", False)) + game.add_child(Node.s32("close_t", 1380)) + game.add_child(Node.u32("playc", 0)) + game.add_child(Node.u32("playn", 0)) + game.add_child(Node.u32("playe", 0)) + game.add_child(Node.u32("test_m", 0)) + game.add_child(Node.u32("service", 0)) + game.add_child(Node.bool("paseli", True)) + game.add_child(Node.u32("update", 0)) + game.add_child(Node.string("shopname", "")) + game.add_child(Node.bool("newpc", False)) + game.add_child(Node.s32("s_paseli", 206)) + game.add_child(Node.s32("monitor", 1)) + game.add_child(Node.string("romnumber", "KFC-JA-B01")) + game.add_child(Node.string("etc", "TaxMode:1,BasicRate:100/1,FirstFree:0")) + setting = Node.void("setting") game.add_child(setting) - setting.add_child(Node.s32('coin_slot', 0)) - setting.add_child(Node.s32('game_start', 1)) - setting.add_child(Node.string('schedule', '0,0,0,0,0,0,0')) - setting.add_child(Node.string('reference', '1,1,1')) - setting.add_child(Node.string('basic_rate', '100,100,100')) - setting.add_child(Node.s32('tax_rate', 1)) - setting.add_child(Node.string('time_service', '0,0,0')) - setting.add_child(Node.string('service_value', '10,10,10')) - setting.add_child(Node.string('service_limit', '10,10,10')) - setting.add_child(Node.string('service_time', '07:00-11:00,07:00-11:00,07:00-11:00')) + setting.add_child(Node.s32("coin_slot", 0)) + setting.add_child(Node.s32("game_start", 1)) + setting.add_child(Node.string("schedule", "0,0,0,0,0,0,0")) + setting.add_child(Node.string("reference", "1,1,1")) + setting.add_child(Node.string("basic_rate", "100,100,100")) + setting.add_child(Node.s32("tax_rate", 1)) + setting.add_child(Node.string("time_service", "0,0,0")) + setting.add_child(Node.string("service_value", "10,10,10")) + setting.add_child(Node.string("service_limit", "10,10,10")) + setting.add_child( + Node.string("service_time", "07:00-11:00,07:00-11:00,07:00-11:00") + ) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/nxt_time") @@ -124,17 +126,17 @@ class SoundVoltexGravityWarsS2Client(BaseClient): def verify_game_new(self, location: str, refid: str) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'new') - game.set_attribute('ver', '0') - game.add_child(Node.string('dataid', refid)) - game.add_child(Node.string('refid', refid)) - game.add_child(Node.string('name', self.NAME)) - game.add_child(Node.string('locid', location)) + game.set_attribute("method", "new") + game.set_attribute("ver", "0") + game.add_child(Node.string("dataid", refid)) + game.add_child(Node.string("refid", refid)) + game.add_child(Node.string("name", self.NAME)) + game.add_child(Node.string("locid", location)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3") @@ -142,37 +144,39 @@ class SoundVoltexGravityWarsS2Client(BaseClient): def verify_game_frozen(self, refid: str, time: int) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'frozen') - game.add_child(Node.string('refid', refid)) - game.add_child(Node.u32('sec', time)) + game.set_attribute("ver", "0") + game.set_attribute("method", "frozen") + game.add_child(Node.string("refid", refid)) + game.add_child(Node.u32("sec", time)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/result") - def verify_game_load(self, cardid: str, refid: str, msg_type: str) -> Dict[str, Any]: + def verify_game_load( + self, cardid: str, refid: str, msg_type: str + ) -> Dict[str, Any]: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'load') - game.set_attribute('ver', '0') - game.add_child(Node.string('dataid', refid)) - game.add_child(Node.string('cardid', cardid)) - game.add_child(Node.string('refid', refid)) + game.set_attribute("method", "load") + game.set_attribute("ver", "0") + game.add_child(Node.string("dataid", refid)) + game.add_child(Node.string("cardid", cardid)) + game.add_child(Node.string("refid", refid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct if msg_type == "new": self.assert_path(resp, "response/game_3/result") - if resp.child_value('game_3/result') != 1: + if resp.child_value("game_3/result") != 1: raise Exception("Invalid result for new profile!") return None @@ -195,93 +199,100 @@ class SoundVoltexGravityWarsS2Client(BaseClient): self.assert_path(resp, "response/game_3/param") items: Dict[int, Dict[int, int]] = {} - for child in resp.child('game_3/item').children: - if child.name != 'info': + for child in resp.child("game_3/item").children: + if child.name != "info": continue - itype = child.child_value('type') - iid = child.child_value('id') - param = child.child_value('param') + itype = child.child_value("type") + iid = child.child_value("id") + param = child.child_value("param") if itype not in items: items[itype] = {} items[itype][iid] = param courses: Dict[int, Dict[int, Dict[str, int]]] = {} - for child in resp.child('game_3/skill/course_all').children: - if child.name != 'd': + for child in resp.child("game_3/skill/course_all").children: + if child.name != "d": continue - crsid = child.child_value('crsid') - season = child.child_value('ssnid') - achievement_rate = child.child_value('ar') - clear_type = child.child_value('ct') + crsid = child.child_value("crsid") + season = child.child_value("ssnid") + achievement_rate = child.child_value("ar") + clear_type = child.child_value("ct") if season not in courses: courses[season] = {} courses[season][crsid] = { - 'achievement_rate': achievement_rate, - 'clear_type': clear_type, + "achievement_rate": achievement_rate, + "clear_type": clear_type, } return { - 'name': resp.child_value('game_3/name'), - 'packet': resp.child_value('game_3/gamecoin_packet'), - 'block': resp.child_value('game_3/gamecoin_block'), - 'blaster_energy': resp.child_value('game_3/blaster_energy'), - 'skill_level': resp.child_value('game_3/skill_level'), - 'items': items, - 'courses': courses, + "name": resp.child_value("game_3/name"), + "packet": resp.child_value("game_3/gamecoin_packet"), + "block": resp.child_value("game_3/gamecoin_block"), + "blaster_energy": resp.child_value("game_3/blaster_energy"), + "skill_level": resp.child_value("game_3/skill_level"), + "items": items, + "courses": courses, } else: raise Exception(f"Invalid game load type {msg_type}") - def verify_game_save(self, location: str, refid: str, packet: int, block: int, blaster_energy: int) -> None: + def verify_game_save( + self, location: str, refid: str, packet: int, block: int, blaster_energy: int + ) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'save') - game.set_attribute('ver', '0') - game.add_child(Node.string('refid', refid)) - game.add_child(Node.string('locid', location)) - game.add_child(Node.u8('headphone', 0)) - game.add_child(Node.u16('appeal_id', 1001)) - game.add_child(Node.u16('comment_id', 0)) - game.add_child(Node.s32('music_id', 29)) - game.add_child(Node.u8('music_type', 1)) - game.add_child(Node.u8('sort_type', 1)) - game.add_child(Node.u8('narrow_down', 0)) - game.add_child(Node.u8('gauge_option', 0)) - game.add_child(Node.u32('earned_gamecoin_packet', packet)) - game.add_child(Node.u32('earned_gamecoin_block', block)) - item = Node.void('item') + game.set_attribute("method", "save") + game.set_attribute("ver", "0") + game.add_child(Node.string("refid", refid)) + game.add_child(Node.string("locid", location)) + game.add_child(Node.u8("headphone", 0)) + game.add_child(Node.u16("appeal_id", 1001)) + game.add_child(Node.u16("comment_id", 0)) + game.add_child(Node.s32("music_id", 29)) + game.add_child(Node.u8("music_type", 1)) + game.add_child(Node.u8("sort_type", 1)) + game.add_child(Node.u8("narrow_down", 0)) + game.add_child(Node.u8("gauge_option", 0)) + game.add_child(Node.u32("earned_gamecoin_packet", packet)) + game.add_child(Node.u32("earned_gamecoin_block", block)) + item = Node.void("item") game.add_child(item) - info = Node.void('info') + info = Node.void("info") item.add_child(info) - info.add_child(Node.u32('id', 1)) - info.add_child(Node.u32('type', 5)) - info.add_child(Node.u32('param', 333333376)) - info = Node.void('info') + info.add_child(Node.u32("id", 1)) + info.add_child(Node.u32("type", 5)) + info.add_child(Node.u32("param", 333333376)) + info = Node.void("info") item.add_child(info) - info.add_child(Node.u32('id', 0)) - info.add_child(Node.u32('type', 5)) - info.add_child(Node.u32('param', 600)) - game.add_child(Node.s32_array('hidden_param', [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])) - game.add_child(Node.s16('skill_name_id', -1)) - game.add_child(Node.s32('earned_blaster_energy', blaster_energy)) - game.add_child(Node.u32('blaster_count', 0)) - printn = Node.void('print') + info.add_child(Node.u32("id", 0)) + info.add_child(Node.u32("type", 5)) + info.add_child(Node.u32("param", 600)) + game.add_child( + Node.s32_array( + "hidden_param", + [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ) + ) + game.add_child(Node.s16("skill_name_id", -1)) + game.add_child(Node.s32("earned_blaster_energy", blaster_energy)) + game.add_child(Node.u32("blaster_count", 0)) + printn = Node.void("print") game.add_child(printn) - printn.add_child(Node.s32('count', 0)) - ea_shop = Node.void('ea_shop') + printn.add_child(Node.s32("count", 0)) + ea_shop = Node.void("ea_shop") game.add_child(ea_shop) - ea_shop.add_child(Node.s32('used_packet_booster', 0)) - ea_shop.add_child(Node.s32('used_block_booster', 0)) - game.add_child(Node.s8('start_option', 0)) + ea_shop.add_child(Node.s32("used_packet_booster", 0)) + ea_shop.add_child(Node.s32("used_block_booster", 0)) + game.add_child(Node.s8("start_option", 0)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3") @@ -289,19 +300,19 @@ class SoundVoltexGravityWarsS2Client(BaseClient): def verify_game_common(self, loc: str) -> None: call = self.call_node() - game = Node.void('game_3') - game.set_attribute('ver', '0') - game.set_attribute('method', 'common') - game.add_child(Node.string('locid', loc)) - game.add_child(Node.string('cstcode', '')) - game.add_child(Node.string('cpycode', '')) - game.add_child(Node.string('hadid', '1000')) - game.add_child(Node.string('licid', '1000')) - game.add_child(Node.string('actid', self.pcbid)) + game = Node.void("game_3") + game.set_attribute("ver", "0") + game.set_attribute("method", "common") + game.add_child(Node.string("locid", loc)) + game.add_child(Node.string("cstcode", "")) + game.add_child(Node.string("cpycode", "")) + game.add_child(Node.string("hadid", "1000")) + game.add_child(Node.string("licid", "1000")) + game.add_child(Node.string("actid", self.pcbid)) call.add_child(game) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/music_limited") @@ -321,28 +332,39 @@ class SoundVoltexGravityWarsS2Client(BaseClient): self.assert_path(resp, "response/game_3/skill_course/info/track/music_id") self.assert_path(resp, "response/game_3/skill_course/info/track/music_type") - def verify_game_buy(self, refid: str, catalogtype: int, catalogid: int, currencytype: int, price: int, itemtype: int, itemid: int, param: int, success: bool) -> None: + def verify_game_buy( + self, + refid: str, + catalogtype: int, + catalogid: int, + currencytype: int, + price: int, + itemtype: int, + itemid: int, + param: int, + success: bool, + ) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'buy') - game.add_child(Node.string('refid', refid)) - game.add_child(Node.u8('catalog_type', catalogtype)) - game.add_child(Node.u32('catalog_id', catalogid)) - game.add_child(Node.u32('earned_gamecoin_packet', 0)) - game.add_child(Node.u32('earned_gamecoin_block', 0)) - game.add_child(Node.u32('currency_type', currencytype)) - item = Node.void('item') + game.set_attribute("ver", "0") + game.set_attribute("method", "buy") + game.add_child(Node.string("refid", refid)) + game.add_child(Node.u8("catalog_type", catalogtype)) + game.add_child(Node.u32("catalog_id", catalogid)) + game.add_child(Node.u32("earned_gamecoin_packet", 0)) + game.add_child(Node.u32("earned_gamecoin_block", 0)) + game.add_child(Node.u32("currency_type", currencytype)) + item = Node.void("item") game.add_child(item) - item.add_child(Node.u32('item_type', itemtype)) - item.add_child(Node.u32('item_id', itemid)) - item.add_child(Node.u32('param', param)) - item.add_child(Node.u32('price', price)) + item.add_child(Node.u32("item_type", itemtype)) + item.add_child(Node.u32("item_id", itemid)) + item.add_child(Node.u32("param", param)) + item.add_child(Node.u32("price", price)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/gamecoin_packet") @@ -350,22 +372,22 @@ class SoundVoltexGravityWarsS2Client(BaseClient): self.assert_path(resp, "response/game_3/result") if success: - if resp.child_value('game_3/result') != 0: - raise Exception('Failed to purchase!') + if resp.child_value("game_3/result") != 0: + raise Exception("Failed to purchase!") else: - if resp.child_value('game_3/result') == 0: - raise Exception('Purchased when shouldn\'t have!') + if resp.child_value("game_3/result") == 0: + raise Exception("Purchased when shouldn't have!") def verify_game_lounge(self) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'lounge') - game.set_attribute('ver', '0') + game.set_attribute("method", "lounge") + game.set_attribute("ver", "0") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/interval") @@ -373,39 +395,39 @@ class SoundVoltexGravityWarsS2Client(BaseClient): def verify_game_entry_s(self) -> int: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'entry_s') - game.add_child(Node.u8('c_ver', 128)) - game.add_child(Node.u8('p_num', 1)) - game.add_child(Node.u8('p_rest', 1)) - game.add_child(Node.u8('filter', 1)) - game.add_child(Node.u32('mid', 416)) - game.add_child(Node.u32('sec', 45)) - game.add_child(Node.u16('port', 10007)) - game.add_child(Node.fouru8('gip', [127, 0, 0, 1])) - game.add_child(Node.fouru8('lip', [10, 0, 5, 73])) - game.add_child(Node.u8('claim', 0)) + game.set_attribute("ver", "0") + game.set_attribute("method", "entry_s") + game.add_child(Node.u8("c_ver", 128)) + game.add_child(Node.u8("p_num", 1)) + game.add_child(Node.u8("p_rest", 1)) + game.add_child(Node.u8("filter", 1)) + game.add_child(Node.u32("mid", 416)) + game.add_child(Node.u32("sec", 45)) + game.add_child(Node.u16("port", 10007)) + game.add_child(Node.fouru8("gip", [127, 0, 0, 1])) + game.add_child(Node.fouru8("lip", [10, 0, 5, 73])) + game.add_child(Node.u8("claim", 0)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/entry_id") - return resp.child_value('game_3/entry_id') + return resp.child_value("game_3/entry_id") def verify_game_entry_e(self, eid: int) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'entry_e') - game.set_attribute('ver', '0') - game.add_child(Node.u32('eid', eid)) + game.set_attribute("method", "entry_e") + game.set_attribute("ver", "0") + game.add_child(Node.u32("eid", eid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3") @@ -413,14 +435,14 @@ class SoundVoltexGravityWarsS2Client(BaseClient): def verify_game_save_e(self, refid: str) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'save_e') - game.set_attribute('ver', '0') - game.add_child(Node.string('refid', refid)) + game.set_attribute("method", "save_e") + game.set_attribute("ver", "0") + game.add_child(Node.string("refid", refid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3") @@ -428,25 +450,25 @@ class SoundVoltexGravityWarsS2Client(BaseClient): def verify_game_play_e(self, location: str, refid: str) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'play_e') - game.add_child(Node.string('dataid', refid)) - game.add_child(Node.s8('mode', 2)) - game.add_child(Node.s16('track_num', 3)) - game.add_child(Node.s32('s_coin', 0)) - game.add_child(Node.s32('s_paseli', 0)) - game.add_child(Node.s16('blaster_count', 0)) - game.add_child(Node.s16('blaster_cartridge', 0)) - game.add_child(Node.string('locid', location)) - game.add_child(Node.u16('drop_frame', 396)) - game.add_child(Node.u16('drop_frame_max', 396)) - game.add_child(Node.u16('drop_count', 1)) - game.add_child(Node.string('etc', 'StoryID:0,StoryPrg:0,PrgPrm:0')) + game.set_attribute("ver", "0") + game.set_attribute("method", "play_e") + game.add_child(Node.string("dataid", refid)) + game.add_child(Node.s8("mode", 2)) + game.add_child(Node.s16("track_num", 3)) + game.add_child(Node.s32("s_coin", 0)) + game.add_child(Node.s32("s_paseli", 0)) + game.add_child(Node.s16("blaster_count", 0)) + game.add_child(Node.s16("blaster_cartridge", 0)) + game.add_child(Node.string("locid", location)) + game.add_child(Node.u16("drop_frame", 396)) + game.add_child(Node.u16("drop_frame_max", 396)) + game.add_child(Node.u16("drop_count", 1)) + game.add_child(Node.string("etc", "StoryID:0,StoryPrg:0,PrgPrm:0")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3") @@ -454,69 +476,73 @@ class SoundVoltexGravityWarsS2Client(BaseClient): def verify_game_load_m(self, refid: str) -> List[Dict[str, int]]: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'load_m') - game.set_attribute('ver', '0') - game.add_child(Node.string('dataid', refid)) + game.set_attribute("method", "load_m") + game.set_attribute("ver", "0") + game.add_child(Node.string("dataid", refid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3/new") scores = [] - for child in resp.child('game_3/new').children: - if child.name != 'music': + for child in resp.child("game_3/new").children: + if child.name != "music": continue - musicid = child.child_value('music_id') - chart = child.child_value('music_type') - clear_type = child.child_value('clear_type') - score = child.child_value('score') - grade = child.child_value('score_grade') + musicid = child.child_value("music_id") + chart = child.child_value("music_type") + clear_type = child.child_value("clear_type") + score = child.child_value("score") + grade = child.child_value("score_grade") - scores.append({ - 'id': musicid, - 'chart': chart, - 'clear_type': clear_type, - 'score': score, - 'grade': grade, - }) + scores.append( + { + "id": musicid, + "chart": chart, + "clear_type": clear_type, + "score": score, + "grade": grade, + } + ) return scores - def verify_game_save_m(self, location: str, refid: str, score: Dict[str, int]) -> None: + def verify_game_save_m( + self, location: str, refid: str, score: Dict[str, int] + ) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'save_m') - game.add_child(Node.string('refid', refid)) - game.add_child(Node.string('dataid', refid)) - game.add_child(Node.u32('music_id', score['id'])) - game.add_child(Node.u32('music_type', score['chart'])) - game.add_child(Node.u32('score', score['score'])) - game.add_child(Node.u32('clear_type', score['clear_type'])) - game.add_child(Node.u32('score_grade', score['grade'])) - game.add_child(Node.u32('max_chain', 0)) - game.add_child(Node.u32('critical', 0)) - game.add_child(Node.u32('near', 0)) - game.add_child(Node.u32('error', 0)) - game.add_child(Node.u32('effective_rate', 100)) - game.add_child(Node.u32('btn_rate', 0)) - game.add_child(Node.u32('long_rate', 0)) - game.add_child(Node.u32('vol_rate', 0)) - game.add_child(Node.u8('mode', 0)) - game.add_child(Node.u8('gauge_type', 0)) - game.add_child(Node.u16('online_num', 0)) - game.add_child(Node.u16('local_num', 0)) - game.add_child(Node.string('locid', location)) + game.set_attribute("ver", "0") + game.set_attribute("method", "save_m") + game.add_child(Node.string("refid", refid)) + game.add_child(Node.string("dataid", refid)) + game.add_child(Node.u32("music_id", score["id"])) + game.add_child(Node.u32("music_type", score["chart"])) + game.add_child(Node.u32("score", score["score"])) + game.add_child(Node.u32("clear_type", score["clear_type"])) + game.add_child(Node.u32("score_grade", score["grade"])) + game.add_child(Node.u32("max_chain", 0)) + game.add_child(Node.u32("critical", 0)) + game.add_child(Node.u32("near", 0)) + game.add_child(Node.u32("error", 0)) + game.add_child(Node.u32("effective_rate", 100)) + game.add_child(Node.u32("btn_rate", 0)) + game.add_child(Node.u32("long_rate", 0)) + game.add_child(Node.u32("vol_rate", 0)) + game.add_child(Node.u8("mode", 0)) + game.add_child(Node.u8("gauge_type", 0)) + game.add_child(Node.u16("online_num", 0)) + game.add_child(Node.u16("local_num", 0)) + game.add_child(Node.string("locid", location)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3") @@ -524,34 +550,36 @@ class SoundVoltexGravityWarsS2Client(BaseClient): def verify_game_load_r(self, refid: str) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('method', 'load_r') - game.set_attribute('ver', '0') - game.add_child(Node.string('dataid', refid)) + game.set_attribute("method", "load_r") + game.set_attribute("ver", "0") + game.add_child(Node.string("dataid", refid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3") - def verify_game_save_c(self, location: str, refid: str, season: int, course: int) -> None: + def verify_game_save_c( + self, location: str, refid: str, season: int, course: int + ) -> None: call = self.call_node() - game = Node.void('game_3') + game = Node.void("game_3") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'save_c') - game.add_child(Node.string('dataid', refid)) - game.add_child(Node.s16('crsid', course)) - game.add_child(Node.s16('ct', 2)) - game.add_child(Node.s16('ar', 15000)) - game.add_child(Node.s32('ssnid', season)) - game.add_child(Node.string('locid', location)) + game.set_attribute("ver", "0") + game.set_attribute("method", "save_c") + game.add_child(Node.string("dataid", refid)) + game.add_child(Node.s16("crsid", course)) + game.add_child(Node.s16("ct", 2)) + game.add_child(Node.s16("ar", 15000)) + game.add_child(Node.s32("ssnid", season)) + game.add_child(Node.string("locid", location)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_3") @@ -560,20 +588,20 @@ class SoundVoltexGravityWarsS2Client(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -593,25 +621,35 @@ class SoundVoltexGravityWarsS2Client(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # SDVX doesn't read the new profile, it asks for the profile itself after calling new - self.verify_game_load(card, ref_id, msg_type='new') + self.verify_game_load(card, ref_id, msg_type="new") self.verify_game_new(location, ref_id) - self.verify_game_load(card, ref_id, msg_type='existing') + self.verify_game_load(card, ref_id, msg_type="existing") else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Verify rivals node (necessary to return but can hold nothing) self.verify_game_load_r(ref_id) @@ -629,85 +667,103 @@ class SoundVoltexGravityWarsS2Client(BaseClient): if cardid is None: # Verify profile loading and saving - profile = self.verify_game_load(card, ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception(f'Profile has incorrect name {profile["name"]} associated with it!') - if profile['packet'] != 0: - raise Exception('Profile has nonzero blocks associated with it!') - if profile['block'] != 0: - raise Exception('Profile has nonzero packets associated with it!') - if profile['blaster_energy'] != 0: - raise Exception('Profile has nonzero blaster energy associated with it!') - if profile['items']: - raise Exception('Profile already has purchased items!') - if profile['courses']: - raise Exception('Profile already has finished courses!') + profile = self.verify_game_load(card, ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception( + f'Profile has incorrect name {profile["name"]} associated with it!' + ) + if profile["packet"] != 0: + raise Exception("Profile has nonzero blocks associated with it!") + if profile["block"] != 0: + raise Exception("Profile has nonzero packets associated with it!") + if profile["blaster_energy"] != 0: + raise Exception( + "Profile has nonzero blaster energy associated with it!" + ) + if profile["items"]: + raise Exception("Profile already has purchased items!") + if profile["courses"]: + raise Exception("Profile already has finished courses!") # Verify purchase failure, try buying song we can't afford self.verify_game_buy(ref_id, 0, 29, 1, 10, 0, 29, 3, False) - self.verify_game_save(location, ref_id, packet=123, block=234, blaster_energy=42) - profile = self.verify_game_load(card, ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception(f'Profile has incorrect name {profile["name"]} associated with it!') - if profile['packet'] != 123: - raise Exception('Profile has invalid blocks associated with it!') - if profile['block'] != 234: - raise Exception('Profile has invalid packets associated with it!') - if profile['blaster_energy'] != 42: - raise Exception('Profile has invalid blaster energy associated with it!') - if 5 not in profile['items']: - raise Exception('Profile doesn\'t have user settings items in it!') - if profile['courses']: - raise Exception('Profile already has finished courses!') + self.verify_game_save( + location, ref_id, packet=123, block=234, blaster_energy=42 + ) + profile = self.verify_game_load(card, ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception( + f'Profile has incorrect name {profile["name"]} associated with it!' + ) + if profile["packet"] != 123: + raise Exception("Profile has invalid blocks associated with it!") + if profile["block"] != 234: + raise Exception("Profile has invalid packets associated with it!") + if profile["blaster_energy"] != 42: + raise Exception( + "Profile has invalid blaster energy associated with it!" + ) + if 5 not in profile["items"]: + raise Exception("Profile doesn't have user settings items in it!") + if profile["courses"]: + raise Exception("Profile already has finished courses!") self.verify_game_save(location, ref_id, packet=1, block=2, blaster_energy=3) - profile = self.verify_game_load(card, ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception(f'Profile has incorrect name {profile["name"]} associated with it!') - if profile['packet'] != 124: - raise Exception('Profile has invalid blocks associated with it!') - if profile['block'] != 236: - raise Exception('Profile has invalid packets associated with it!') - if profile['blaster_energy'] != 45: - raise Exception('Profile has invalid blaster energy associated with it!') - if 5 not in profile['items']: - raise Exception('Profile doesn\'t have user settings items in it!') - if profile['courses']: - raise Exception('Profile has invalid finished courses!') + profile = self.verify_game_load(card, ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception( + f'Profile has incorrect name {profile["name"]} associated with it!' + ) + if profile["packet"] != 124: + raise Exception("Profile has invalid blocks associated with it!") + if profile["block"] != 236: + raise Exception("Profile has invalid packets associated with it!") + if profile["blaster_energy"] != 45: + raise Exception( + "Profile has invalid blaster energy associated with it!" + ) + if 5 not in profile["items"]: + raise Exception("Profile doesn't have user settings items in it!") + if profile["courses"]: + raise Exception("Profile has invalid finished courses!") # Verify purchase success, buy a song we can afford now self.verify_game_buy(ref_id, 0, 29, 1, 10, 0, 29, 3, True) - profile = self.verify_game_load(card, ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception(f'Profile has incorrect name {profile["name"]} associated with it!') - if profile['packet'] != 124: - raise Exception('Profile has invalid blocks associated with it!') - if profile['block'] != 226: - raise Exception('Profile has invalid packets associated with it!') - if profile['blaster_energy'] != 45: - raise Exception('Profile has invalid blaster energy associated with it!') - if 0 not in profile['items'] or 29 not in profile['items'][0]: - raise Exception('Purchase didn\'t add to profile!') - if profile['items'][0][29] != 3: - raise Exception('Purchase parameters are wrong!') - if profile['courses']: - raise Exception('Profile has invalid finished courses!') + profile = self.verify_game_load(card, ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception( + f'Profile has incorrect name {profile["name"]} associated with it!' + ) + if profile["packet"] != 124: + raise Exception("Profile has invalid blocks associated with it!") + if profile["block"] != 226: + raise Exception("Profile has invalid packets associated with it!") + if profile["blaster_energy"] != 45: + raise Exception( + "Profile has invalid blaster energy associated with it!" + ) + if 0 not in profile["items"] or 29 not in profile["items"][0]: + raise Exception("Purchase didn't add to profile!") + if profile["items"][0][29] != 3: + raise Exception("Purchase parameters are wrong!") + if profile["courses"]: + raise Exception("Profile has invalid finished courses!") # Verify that we can finish skill analyzer courses self.verify_game_save_c(location, ref_id, 14, 3) - profile = self.verify_game_load(card, ref_id, msg_type='existing') - if 14 not in profile['courses'] or 3 not in profile['courses'][14]: - raise Exception('Course didn\'t add to profile!') - if profile['courses'][14][3]['achievement_rate'] != 15000: - raise Exception('Course didn\'t save achievement rate!') - if profile['courses'][14][3]['clear_type'] != 2: - raise Exception('Course didn\'t save clear type!') + profile = self.verify_game_load(card, ref_id, msg_type="existing") + if 14 not in profile["courses"] or 3 not in profile["courses"][14]: + raise Exception("Course didn't add to profile!") + if profile["courses"][14][3]["achievement_rate"] != 15000: + raise Exception("Course didn't save achievement rate!") + if profile["courses"][14][3]["clear_type"] != 2: + raise Exception("Course didn't save clear type!") # Verify empty profile has no scores on it scores = self.verify_game_load_m(ref_id) if len(scores) > 0: - raise Exception('Score on an empty profile!') + raise Exception("Score on an empty profile!") # Verify score saving and updating for phase in [1, 2]: @@ -715,57 +771,57 @@ class SoundVoltexGravityWarsS2Client(BaseClient): dummyscores = [ # An okay score on a chart { - 'id': 1, - 'chart': 1, - 'grade': 3, - 'clear_type': 2, - 'score': 765432, + "id": 1, + "chart": 1, + "grade": 3, + "clear_type": 2, + "score": 765432, }, # A good score on an easier chart of the same song { - 'id': 1, - 'chart': 0, - 'grade': 6, - 'clear_type': 3, - 'score': 7654321, + "id": 1, + "chart": 0, + "grade": 6, + "clear_type": 3, + "score": 7654321, }, # A bad score on a hard chart { - 'id': 2, - 'chart': 2, - 'grade': 1, - 'clear_type': 1, - 'score': 12345, + "id": 2, + "chart": 2, + "grade": 1, + "clear_type": 1, + "score": 12345, }, # A terrible score on an easy chart { - 'id': 3, - 'chart': 0, - 'grade': 1, - 'clear_type': 1, - 'score': 123, + "id": 3, + "chart": 0, + "grade": 1, + "clear_type": 1, + "score": 123, }, ] if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 1, - 'chart': 1, - 'grade': 5, - 'clear_type': 3, - 'score': 8765432, + "id": 1, + "chart": 1, + "grade": 5, + "clear_type": 3, + "score": 8765432, }, # A worse score on another same chart { - 'id': 1, - 'chart': 0, - 'grade': 4, - 'clear_type': 2, - 'score': 6543210, - 'expected_score': 7654321, - 'expected_clear_type': 3, - 'expected_grade': 6, + "id": 1, + "chart": 0, + "grade": 4, + "clear_type": 2, + "score": 6543210, + "expected_score": 7654321, + "expected_clear_type": 3, + "expected_grade": 6, }, ] for dummyscore in dummyscores: @@ -775,32 +831,43 @@ class SoundVoltexGravityWarsS2Client(BaseClient): for expected in dummyscores: actual = None for received in scores: - if received['id'] == expected['id'] and received['chart'] == expected['chart']: + if ( + received["id"] == expected["id"] + and received["chart"] == expected["chart"] + ): actual = received break if actual is None: - raise Exception(f"Didn't find song {expected['id']} chart {expected['chart']} in response!") + raise Exception( + f"Didn't find song {expected['id']} chart {expected['chart']} in response!" + ) - if 'expected_score' in expected: - expected_score = expected['expected_score'] + if "expected_score" in expected: + expected_score = expected["expected_score"] else: - expected_score = expected['score'] - if 'expected_grade' in expected: - expected_grade = expected['expected_grade'] + expected_score = expected["score"] + if "expected_grade" in expected: + expected_grade = expected["expected_grade"] else: - expected_grade = expected['grade'] - if 'expected_clear_type' in expected: - expected_clear_type = expected['expected_clear_type'] + expected_grade = expected["grade"] + if "expected_clear_type" in expected: + expected_clear_type = expected["expected_clear_type"] else: - expected_clear_type = expected['clear_type'] + expected_clear_type = expected["clear_type"] - if actual['score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'') - if actual['grade'] != expected_grade: - raise Exception(f'Expected a grade of \'{expected_grade}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got grade \'{actual["grade"]}\'') - if actual['clear_type'] != expected_clear_type: - raise Exception(f'Expected a clear_type of \'{expected_clear_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got clear_type \'{actual["clear_type"]}\'') + if actual["score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'' + ) + if actual["grade"] != expected_grade: + raise Exception( + f'Expected a grade of \'{expected_grade}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got grade \'{actual["grade"]}\'' + ) + if actual["clear_type"] != expected_clear_type: + raise Exception( + f'Expected a clear_type of \'{expected_clear_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got clear_type \'{actual["clear_type"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) diff --git a/bemani/client/sdvx/heavenlyhaven.py b/bemani/client/sdvx/heavenlyhaven.py index 9418b2a..0d684df 100644 --- a/bemani/client/sdvx/heavenlyhaven.py +++ b/bemani/client/sdvx/heavenlyhaven.py @@ -7,30 +7,30 @@ from bemani.protocol import Node class SoundVoltexHeavenlyHavenClient(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_eventlog_write(self, location: str) -> None: call = self.call_node() # Construct node - eventlog = Node.void('eventlog') + eventlog = Node.void("eventlog") call.add_child(eventlog) - eventlog.set_attribute('method', 'write') - eventlog.add_child(Node.u32('retrycnt', 0)) - data = Node.void('data') + eventlog.set_attribute("method", "write") + eventlog.add_child(Node.u32("retrycnt", 0)) + data = Node.void("data") eventlog.add_child(data) - data.add_child(Node.string('eventid', 'S_PWRON')) - data.add_child(Node.s32('eventorder', 0)) - data.add_child(Node.u64('pcbtime', int(time.time() * 1000))) - data.add_child(Node.s64('gamesession', -1)) - data.add_child(Node.string('strdata1', '2.3.8')) - data.add_child(Node.string('strdata2', '')) - data.add_child(Node.s64('numdata1', 1)) - data.add_child(Node.s64('numdata2', 0)) - data.add_child(Node.string('locationid', location)) + data.add_child(Node.string("eventid", "S_PWRON")) + data.add_child(Node.s32("eventorder", 0)) + data.add_child(Node.u64("pcbtime", int(time.time() * 1000))) + data.add_child(Node.s64("gamesession", -1)) + data.add_child(Node.string("strdata1", "2.3.8")) + data.add_child(Node.string("strdata2", "")) + data.add_child(Node.s64("numdata1", 1)) + data.add_child(Node.s64("numdata2", 0)) + data.add_child(Node.string("locationid", location)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/eventlog/gamesession") @@ -41,14 +41,14 @@ class SoundVoltexHeavenlyHavenClient(BaseClient): def verify_game_exception(self, location: str) -> None: call = self.call_node() - game = Node.void('game') - game.set_attribute('method', 'sv4_exception') - game.add_child(Node.string('text', '')) - game.add_child(Node.string('lid', location)) + game = Node.void("game") + game.set_attribute("method", "sv4_exception") + game.add_child(Node.string("text", "")) + game.add_child(Node.string("lid", location)) call.add_child(game) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") @@ -56,14 +56,14 @@ class SoundVoltexHeavenlyHavenClient(BaseClient): def verify_game_hiscore(self, location: str) -> None: call = self.call_node() - game = Node.void('game') - game.set_attribute('ver', '0') - game.set_attribute('method', 'sv4_hiscore') - game.add_child(Node.string('locid', location)) + game = Node.void("game") + game.set_attribute("ver", "0") + game.set_attribute("method", "sv4_hiscore") + game.add_child(Node.string("locid", location)) call.add_child(game) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/sc/d/id") @@ -77,60 +77,62 @@ class SoundVoltexHeavenlyHavenClient(BaseClient): def verify_game_shop(self, location: str) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'sv4_shop') - game.set_attribute('ver', '0') - game.add_child(Node.string('locid', location)) - game.add_child(Node.string('regcode', '.')) - game.add_child(Node.string('locname', '')) - game.add_child(Node.u8('loctype', 0)) - game.add_child(Node.string('cstcode', '')) - game.add_child(Node.string('cpycode', '')) - game.add_child(Node.s32('latde', 0)) - game.add_child(Node.s32('londe', 0)) - game.add_child(Node.u8('accu', 0)) - game.add_child(Node.string('linid', '.')) - game.add_child(Node.u8('linclass', 0)) - game.add_child(Node.ipv4('ipaddr', '0.0.0.0')) - game.add_child(Node.string('hadid', '00010203040506070809')) - game.add_child(Node.string('licid', '00010203040506070809')) - game.add_child(Node.string('actid', self.pcbid)) - game.add_child(Node.s8('appstate', 0)) - game.add_child(Node.s8('c_need', 1)) - game.add_child(Node.s8('c_credit', 2)) - game.add_child(Node.s8('s_credit', 2)) - game.add_child(Node.bool('free_p', True)) - game.add_child(Node.bool('close', False)) - game.add_child(Node.s32('close_t', 1380)) - game.add_child(Node.u32('playc', 0)) - game.add_child(Node.u32('playn', 0)) - game.add_child(Node.u32('playe', 0)) - game.add_child(Node.u32('test_m', 0)) - game.add_child(Node.u32('service', 0)) - game.add_child(Node.bool('paseli', True)) - game.add_child(Node.u32('update', 0)) - game.add_child(Node.string('shopname', '')) - game.add_child(Node.bool('newpc', False)) - game.add_child(Node.s32('s_paseli', 206)) - game.add_child(Node.s32('monitor', 1)) - game.add_child(Node.string('romnumber', 'KFC-JA-B01')) - game.add_child(Node.string('etc', 'TaxMode:1,BasicRate:100/1,FirstFree:0')) - setting = Node.void('setting') + game.set_attribute("method", "sv4_shop") + game.set_attribute("ver", "0") + game.add_child(Node.string("locid", location)) + game.add_child(Node.string("regcode", ".")) + game.add_child(Node.string("locname", "")) + game.add_child(Node.u8("loctype", 0)) + game.add_child(Node.string("cstcode", "")) + game.add_child(Node.string("cpycode", "")) + game.add_child(Node.s32("latde", 0)) + game.add_child(Node.s32("londe", 0)) + game.add_child(Node.u8("accu", 0)) + game.add_child(Node.string("linid", ".")) + game.add_child(Node.u8("linclass", 0)) + game.add_child(Node.ipv4("ipaddr", "0.0.0.0")) + game.add_child(Node.string("hadid", "00010203040506070809")) + game.add_child(Node.string("licid", "00010203040506070809")) + game.add_child(Node.string("actid", self.pcbid)) + game.add_child(Node.s8("appstate", 0)) + game.add_child(Node.s8("c_need", 1)) + game.add_child(Node.s8("c_credit", 2)) + game.add_child(Node.s8("s_credit", 2)) + game.add_child(Node.bool("free_p", True)) + game.add_child(Node.bool("close", False)) + game.add_child(Node.s32("close_t", 1380)) + game.add_child(Node.u32("playc", 0)) + game.add_child(Node.u32("playn", 0)) + game.add_child(Node.u32("playe", 0)) + game.add_child(Node.u32("test_m", 0)) + game.add_child(Node.u32("service", 0)) + game.add_child(Node.bool("paseli", True)) + game.add_child(Node.u32("update", 0)) + game.add_child(Node.string("shopname", "")) + game.add_child(Node.bool("newpc", False)) + game.add_child(Node.s32("s_paseli", 206)) + game.add_child(Node.s32("monitor", 1)) + game.add_child(Node.string("romnumber", "KFC-JA-B01")) + game.add_child(Node.string("etc", "TaxMode:1,BasicRate:100/1,FirstFree:0")) + setting = Node.void("setting") game.add_child(setting) - setting.add_child(Node.s32('coin_slot', 0)) - setting.add_child(Node.s32('game_start', 1)) - setting.add_child(Node.string('schedule', '0,0,0,0,0,0,0')) - setting.add_child(Node.string('reference', '1,1,1')) - setting.add_child(Node.string('basic_rate', '100,100,100')) - setting.add_child(Node.s32('tax_rate', 1)) - setting.add_child(Node.string('time_service', '0,0,0')) - setting.add_child(Node.string('service_value', '10,10,10')) - setting.add_child(Node.string('service_limit', '10,10,10')) - setting.add_child(Node.string('service_time', '07:00-11:00,07:00-11:00,07:00-11:00')) + setting.add_child(Node.s32("coin_slot", 0)) + setting.add_child(Node.s32("game_start", 1)) + setting.add_child(Node.string("schedule", "0,0,0,0,0,0,0")) + setting.add_child(Node.string("reference", "1,1,1")) + setting.add_child(Node.string("basic_rate", "100,100,100")) + setting.add_child(Node.s32("tax_rate", 1)) + setting.add_child(Node.string("time_service", "0,0,0")) + setting.add_child(Node.string("service_value", "10,10,10")) + setting.add_child(Node.string("service_limit", "10,10,10")) + setting.add_child( + Node.string("service_time", "07:00-11:00,07:00-11:00,07:00-11:00") + ) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/nxt_time") @@ -138,17 +140,17 @@ class SoundVoltexHeavenlyHavenClient(BaseClient): def verify_game_new(self, location: str, refid: str) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'sv4_new') - game.set_attribute('ver', '0') - game.add_child(Node.string('dataid', refid)) - game.add_child(Node.string('refid', refid)) - game.add_child(Node.string('name', self.NAME)) - game.add_child(Node.string('locid', location)) + game.set_attribute("method", "sv4_new") + game.set_attribute("ver", "0") + game.add_child(Node.string("dataid", refid)) + game.add_child(Node.string("refid", refid)) + game.add_child(Node.string("name", self.NAME)) + game.add_child(Node.string("locid", location)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") @@ -156,37 +158,39 @@ class SoundVoltexHeavenlyHavenClient(BaseClient): def verify_game_frozen(self, refid: str, time: int) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'sv4_frozen') - game.add_child(Node.string('refid', refid)) - game.add_child(Node.u32('sec', time)) + game.set_attribute("ver", "0") + game.set_attribute("method", "sv4_frozen") + game.add_child(Node.string("refid", refid)) + game.add_child(Node.u32("sec", time)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/result") - def verify_game_load(self, cardid: str, refid: str, msg_type: str) -> Dict[str, Any]: + def verify_game_load( + self, cardid: str, refid: str, msg_type: str + ) -> Dict[str, Any]: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'sv4_load') - game.set_attribute('ver', '0') - game.add_child(Node.string('dataid', refid)) - game.add_child(Node.string('cardid', cardid)) - game.add_child(Node.string('refid', refid)) + game.set_attribute("method", "sv4_load") + game.set_attribute("ver", "0") + game.add_child(Node.string("dataid", refid)) + game.add_child(Node.string("cardid", cardid)) + game.add_child(Node.string("refid", refid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct if msg_type == "new": self.assert_path(resp, "response/game/result") - if resp.child_value('game/result') != 1: + if resp.child_value("game/result") != 1: raise Exception("Invalid result for new profile!") return None @@ -219,94 +223,96 @@ class SoundVoltexHeavenlyHavenClient(BaseClient): self.assert_path(resp, "response/game/pb_infection/block/after") items: Dict[int, Dict[int, int]] = {} - for child in resp.child('game/item').children: - if child.name != 'info': + for child in resp.child("game/item").children: + if child.name != "info": continue - itype = child.child_value('type') - iid = child.child_value('id') - param = child.child_value('param') + itype = child.child_value("type") + iid = child.child_value("id") + param = child.child_value("param") if itype not in items: items[itype] = {} items[itype][iid] = param courses: Dict[int, Dict[int, Dict[str, int]]] = {} - for child in resp.child('game/skill').children: - if child.name != 'course': + for child in resp.child("game/skill").children: + if child.name != "course": continue - crsid = child.child_value('crsid') - season = child.child_value('ssnid') - achievement_rate = child.child_value('ar') - clear_type = child.child_value('ct') - grade = child.child_value('gr') - score = child.child_value('sc') + crsid = child.child_value("crsid") + season = child.child_value("ssnid") + achievement_rate = child.child_value("ar") + clear_type = child.child_value("ct") + grade = child.child_value("gr") + score = child.child_value("sc") if season not in courses: courses[season] = {} courses[season][crsid] = { - 'achievement_rate': achievement_rate, - 'clear_type': clear_type, - 'grade': grade, - 'score': score, + "achievement_rate": achievement_rate, + "clear_type": clear_type, + "grade": grade, + "score": score, } return { - 'name': resp.child_value('game/name'), - 'packet': resp.child_value('game/gamecoin_packet'), - 'block': resp.child_value('game/gamecoin_block'), - 'blaster_energy': resp.child_value('game/blaster_energy'), - 'skill_level': resp.child_value('game/skill_level'), - 'items': items, - 'courses': courses, + "name": resp.child_value("game/name"), + "packet": resp.child_value("game/gamecoin_packet"), + "block": resp.child_value("game/gamecoin_block"), + "blaster_energy": resp.child_value("game/blaster_energy"), + "skill_level": resp.child_value("game/skill_level"), + "items": items, + "courses": courses, } else: raise Exception(f"Invalid game load type {msg_type}") - def verify_game_save(self, location: str, refid: str, packet: int, block: int, blaster_energy: int) -> None: + def verify_game_save( + self, location: str, refid: str, packet: int, block: int, blaster_energy: int + ) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'sv4_save') - game.set_attribute('ver', '0') - game.add_child(Node.string('refid', refid)) - game.add_child(Node.string('locid', location)) - game.add_child(Node.u8('headphone', 0)) - game.add_child(Node.u16('appeal_id', 1001)) - game.add_child(Node.u16('comment_id', 0)) - game.add_child(Node.s32('music_id', 29)) - game.add_child(Node.u8('music_type', 1)) - game.add_child(Node.u8('sort_type', 1)) - game.add_child(Node.u8('narrow_down', 0)) - game.add_child(Node.u8('gauge_option', 0)) - game.add_child(Node.u8('ars_option', 0)) - game.add_child(Node.u8('notes_option', 0)) - game.add_child(Node.u8('early_late_disp', 0)) - game.add_child(Node.s32('draw_adjust', 0)) - game.add_child(Node.u8('eff_c_left', 0)) - game.add_child(Node.u8('eff_c_right', 1)) - game.add_child(Node.u32('earned_gamecoin_packet', packet)) - game.add_child(Node.u32('earned_gamecoin_block', block)) - item = Node.void('item') + game.set_attribute("method", "sv4_save") + game.set_attribute("ver", "0") + game.add_child(Node.string("refid", refid)) + game.add_child(Node.string("locid", location)) + game.add_child(Node.u8("headphone", 0)) + game.add_child(Node.u16("appeal_id", 1001)) + game.add_child(Node.u16("comment_id", 0)) + game.add_child(Node.s32("music_id", 29)) + game.add_child(Node.u8("music_type", 1)) + game.add_child(Node.u8("sort_type", 1)) + game.add_child(Node.u8("narrow_down", 0)) + game.add_child(Node.u8("gauge_option", 0)) + game.add_child(Node.u8("ars_option", 0)) + game.add_child(Node.u8("notes_option", 0)) + game.add_child(Node.u8("early_late_disp", 0)) + game.add_child(Node.s32("draw_adjust", 0)) + game.add_child(Node.u8("eff_c_left", 0)) + game.add_child(Node.u8("eff_c_right", 1)) + game.add_child(Node.u32("earned_gamecoin_packet", packet)) + game.add_child(Node.u32("earned_gamecoin_block", block)) + item = Node.void("item") game.add_child(item) - game.add_child(Node.s16('skill_name_id', 0)) - game.add_child(Node.s16('skill_base_id', 0)) - game.add_child(Node.s16('skill_name', 0)) - game.add_child(Node.s32('earned_blaster_energy', blaster_energy)) - game.add_child(Node.u32('blaster_count', 0)) - printn = Node.void('print') + game.add_child(Node.s16("skill_name_id", 0)) + game.add_child(Node.s16("skill_base_id", 0)) + game.add_child(Node.s16("skill_name", 0)) + game.add_child(Node.s32("earned_blaster_energy", blaster_energy)) + game.add_child(Node.u32("blaster_count", 0)) + printn = Node.void("print") game.add_child(printn) - printn.add_child(Node.s32('count', 0)) - ea_shop = Node.void('ea_shop') + printn.add_child(Node.s32("count", 0)) + ea_shop = Node.void("ea_shop") game.add_child(ea_shop) - ea_shop.add_child(Node.s32('used_packet_booster', 0)) - ea_shop.add_child(Node.s32('used_block_booster', 0)) - game.add_child(Node.s8('start_option', 1)) + ea_shop.add_child(Node.s32("used_packet_booster", 0)) + ea_shop.add_child(Node.s32("used_block_booster", 0)) + game.add_child(Node.s8("start_option", 1)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") @@ -314,19 +320,19 @@ class SoundVoltexHeavenlyHavenClient(BaseClient): def verify_game_common(self, loc: str) -> None: call = self.call_node() - game = Node.void('game') - game.set_attribute('ver', '0') - game.set_attribute('method', 'sv4_common') - game.add_child(Node.string('locid', loc)) - game.add_child(Node.string('cstcode', '')) - game.add_child(Node.string('cpycode', '')) - game.add_child(Node.string('hadid', '00010203040506070809')) - game.add_child(Node.string('licid', '00010203040506070809')) - game.add_child(Node.string('actid', self.pcbid)) + game = Node.void("game") + game.set_attribute("ver", "0") + game.set_attribute("method", "sv4_common") + game.add_child(Node.string("locid", loc)) + game.add_child(Node.string("cstcode", "")) + game.add_child(Node.string("cpycode", "")) + game.add_child(Node.string("hadid", "00010203040506070809")) + game.add_child(Node.string("licid", "00010203040506070809")) + game.add_child(Node.string("actid", self.pcbid)) call.add_child(game) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/music_limited") @@ -352,28 +358,39 @@ class SoundVoltexHeavenlyHavenClient(BaseClient): self.assert_path(resp, "response/game/skill_course/info/track/music_id") self.assert_path(resp, "response/game/skill_course/info/track/music_type") - def verify_game_buy(self, refid: str, catalogtype: int, catalogid: int, currencytype: int, price: int, itemtype: int, itemid: int, param: int, success: bool) -> None: + def verify_game_buy( + self, + refid: str, + catalogtype: int, + catalogid: int, + currencytype: int, + price: int, + itemtype: int, + itemid: int, + param: int, + success: bool, + ) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'sv4_buy') - game.add_child(Node.string('refid', refid)) - game.add_child(Node.u8('catalog_type', catalogtype)) - game.add_child(Node.u32('catalog_id', catalogid)) - game.add_child(Node.u32('earned_gamecoin_packet', 0)) - game.add_child(Node.u32('earned_gamecoin_block', 0)) - game.add_child(Node.u32('currency_type', currencytype)) - item = Node.void('item') + game.set_attribute("ver", "0") + game.set_attribute("method", "sv4_buy") + game.add_child(Node.string("refid", refid)) + game.add_child(Node.u8("catalog_type", catalogtype)) + game.add_child(Node.u32("catalog_id", catalogid)) + game.add_child(Node.u32("earned_gamecoin_packet", 0)) + game.add_child(Node.u32("earned_gamecoin_block", 0)) + game.add_child(Node.u32("currency_type", currencytype)) + item = Node.void("item") game.add_child(item) - item.add_child(Node.u32('item_type', itemtype)) - item.add_child(Node.u32('item_id', itemid)) - item.add_child(Node.u32('param', param)) - item.add_child(Node.u32('price', price)) + item.add_child(Node.u32("item_type", itemtype)) + item.add_child(Node.u32("item_id", itemid)) + item.add_child(Node.u32("param", param)) + item.add_child(Node.u32("price", price)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/gamecoin_packet") @@ -381,22 +398,22 @@ class SoundVoltexHeavenlyHavenClient(BaseClient): self.assert_path(resp, "response/game/result") if success: - if resp.child_value('game/result') != 0: - raise Exception('Failed to purchase!') + if resp.child_value("game/result") != 0: + raise Exception("Failed to purchase!") else: - if resp.child_value('game/result') == 0: - raise Exception('Purchased when shouldn\'t have!') + if resp.child_value("game/result") == 0: + raise Exception("Purchased when shouldn't have!") def verify_game_lounge(self) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'sv4_lounge') - game.set_attribute('ver', '0') + game.set_attribute("method", "sv4_lounge") + game.set_attribute("ver", "0") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/interval") @@ -404,39 +421,39 @@ class SoundVoltexHeavenlyHavenClient(BaseClient): def verify_game_entry_s(self) -> int: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'sv4_entry_s') - game.add_child(Node.u8('c_ver', 174)) - game.add_child(Node.u8('p_num', 1)) - game.add_child(Node.u8('p_rest', 1)) - game.add_child(Node.u8('filter', 1)) - game.add_child(Node.u32('mid', 492)) - game.add_child(Node.u32('sec', 45)) - game.add_child(Node.u16('port', 10007)) - game.add_child(Node.fouru8('gip', [127, 0, 0, 1])) - game.add_child(Node.fouru8('lip', [10, 0, 5, 73])) - game.add_child(Node.u8('claim', 0)) + game.set_attribute("ver", "0") + game.set_attribute("method", "sv4_entry_s") + game.add_child(Node.u8("c_ver", 174)) + game.add_child(Node.u8("p_num", 1)) + game.add_child(Node.u8("p_rest", 1)) + game.add_child(Node.u8("filter", 1)) + game.add_child(Node.u32("mid", 492)) + game.add_child(Node.u32("sec", 45)) + game.add_child(Node.u16("port", 10007)) + game.add_child(Node.fouru8("gip", [127, 0, 0, 1])) + game.add_child(Node.fouru8("lip", [10, 0, 5, 73])) + game.add_child(Node.u8("claim", 0)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/entry_id") - return resp.child_value('game/entry_id') + return resp.child_value("game/entry_id") def verify_game_entry_e(self, eid: int) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'sv4_entry_e') - game.set_attribute('ver', '0') - game.add_child(Node.u32('eid', eid)) + game.set_attribute("method", "sv4_entry_e") + game.set_attribute("ver", "0") + game.add_child(Node.u32("eid", eid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") @@ -444,22 +461,22 @@ class SoundVoltexHeavenlyHavenClient(BaseClient): def verify_game_save_e(self, location: str, cardid: str, refid: str) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'sv4_save_e') - game.set_attribute('ver', '0') - game.add_child(Node.string('locid', location)) - game.add_child(Node.string('cardnumber', cardid)) - game.add_child(Node.string('refid', refid)) - game.add_child(Node.s32('playid', 1)) - game.add_child(Node.bool('is_paseli', False)) - game.add_child(Node.s32('online_num', 0)) - game.add_child(Node.s32('local_num', 0)) - game.add_child(Node.s32('start_option', 0)) - game.add_child(Node.s32('print_num', 0)) + game.set_attribute("method", "sv4_save_e") + game.set_attribute("ver", "0") + game.add_child(Node.string("locid", location)) + game.add_child(Node.string("cardnumber", cardid)) + game.add_child(Node.string("refid", refid)) + game.add_child(Node.s32("playid", 1)) + game.add_child(Node.bool("is_paseli", False)) + game.add_child(Node.s32("online_num", 0)) + game.add_child(Node.s32("local_num", 0)) + game.add_child(Node.s32("start_option", 0)) + game.add_child(Node.s32("print_num", 0)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") @@ -477,47 +494,47 @@ class SoundVoltexHeavenlyHavenClient(BaseClient): def verify_game_play_s(self) -> int: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'sv4_play_s') - game.set_attribute('ver', '0') + game.set_attribute("method", "sv4_play_s") + game.set_attribute("ver", "0") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/play_id") - return resp.child_value('game/play_id') + return resp.child_value("game/play_id") def verify_game_play_e(self, location: str, refid: str, play_id: int) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'sv4_play_e') - game.add_child(Node.string('refid', refid)) - game.add_child(Node.u32('play_id', play_id)) - game.add_child(Node.s8('start_type', 1)) - game.add_child(Node.s8('mode', 2)) - game.add_child(Node.s16('track_num', 3)) - game.add_child(Node.s32('s_coin', 0)) - game.add_child(Node.s32('s_paseli', 247)) - game.add_child(Node.u32('print_card', 0)) - game.add_child(Node.u32('print_result', 0)) - game.add_child(Node.u32('blaster_num', 0)) - game.add_child(Node.u32('today_cnt', 1)) - game.add_child(Node.u32('play_chain', 1)) - game.add_child(Node.u32('week_play_cnt', 0)) - game.add_child(Node.u32('week_chain', 0)) - game.add_child(Node.string('locid', location)) - game.add_child(Node.u16('drop_frame', 16169)) - game.add_child(Node.u16('drop_frame_max', 11984)) - game.add_child(Node.u16('drop_count', 6)) - game.add_child(Node.string('etc', 'play_t:605')) + game.set_attribute("ver", "0") + game.set_attribute("method", "sv4_play_e") + game.add_child(Node.string("refid", refid)) + game.add_child(Node.u32("play_id", play_id)) + game.add_child(Node.s8("start_type", 1)) + game.add_child(Node.s8("mode", 2)) + game.add_child(Node.s16("track_num", 3)) + game.add_child(Node.s32("s_coin", 0)) + game.add_child(Node.s32("s_paseli", 247)) + game.add_child(Node.u32("print_card", 0)) + game.add_child(Node.u32("print_result", 0)) + game.add_child(Node.u32("blaster_num", 0)) + game.add_child(Node.u32("today_cnt", 1)) + game.add_child(Node.u32("play_chain", 1)) + game.add_child(Node.u32("week_play_cnt", 0)) + game.add_child(Node.u32("week_chain", 0)) + game.add_child(Node.string("locid", location)) + game.add_child(Node.u16("drop_frame", 16169)) + game.add_child(Node.u16("drop_frame_max", 11984)) + game.add_child(Node.u16("drop_count", 6)) + game.add_child(Node.string("etc", "play_t:605")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") @@ -525,72 +542,76 @@ class SoundVoltexHeavenlyHavenClient(BaseClient): def verify_game_load_m(self, refid: str) -> List[Dict[str, int]]: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'sv4_load_m') - game.set_attribute('ver', '0') - game.add_child(Node.string('refid', refid)) + game.set_attribute("method", "sv4_load_m") + game.set_attribute("ver", "0") + game.add_child(Node.string("refid", refid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game/music") scores = [] - for child in resp.child('game/music').children: - if child.name != 'info': + for child in resp.child("game/music").children: + if child.name != "info": continue - musicid = child.child_value('param')[0] - chart = child.child_value('param')[1] - clear_type = child.child_value('param')[3] - score = child.child_value('param')[2] - grade = child.child_value('param')[4] + musicid = child.child_value("param")[0] + chart = child.child_value("param")[1] + clear_type = child.child_value("param")[3] + score = child.child_value("param")[2] + grade = child.child_value("param")[4] - scores.append({ - 'id': musicid, - 'chart': chart, - 'clear_type': clear_type, - 'score': score, - 'grade': grade, - }) + scores.append( + { + "id": musicid, + "chart": chart, + "clear_type": clear_type, + "score": score, + "grade": grade, + } + ) return scores - def verify_game_save_m(self, location: str, refid: str, play_id: int, score: Dict[str, int]) -> None: + def verify_game_save_m( + self, location: str, refid: str, play_id: int, score: Dict[str, int] + ) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'sv4_save_m') - game.add_child(Node.string('refid', refid)) - game.add_child(Node.string('dataid', refid)) - game.add_child(Node.u32('play_id', play_id)) - game.add_child(Node.u16('track_no', 0)) - game.add_child(Node.u32('music_id', score['id'])) - game.add_child(Node.u32('music_type', score['chart'])) - game.add_child(Node.u32('score', score['score'])) - game.add_child(Node.u32('clear_type', score['clear_type'])) - game.add_child(Node.u32('score_grade', score['grade'])) - game.add_child(Node.u32('max_chain', 0)) - game.add_child(Node.u32('critical', 0)) - game.add_child(Node.u32('near', 0)) - game.add_child(Node.u32('error', 0)) - game.add_child(Node.u32('effective_rate', 100)) - game.add_child(Node.u32('btn_rate', 0)) - game.add_child(Node.u32('long_rate', 0)) - game.add_child(Node.u32('vol_rate', 0)) - game.add_child(Node.u8('mode', 0)) - game.add_child(Node.u8('gauge_type', 0)) - game.add_child(Node.u8('notes_option', 0)) - game.add_child(Node.u16('online_num', 0)) - game.add_child(Node.u16('local_num', 0)) - game.add_child(Node.string('locid', location)) + game.set_attribute("ver", "0") + game.set_attribute("method", "sv4_save_m") + game.add_child(Node.string("refid", refid)) + game.add_child(Node.string("dataid", refid)) + game.add_child(Node.u32("play_id", play_id)) + game.add_child(Node.u16("track_no", 0)) + game.add_child(Node.u32("music_id", score["id"])) + game.add_child(Node.u32("music_type", score["chart"])) + game.add_child(Node.u32("score", score["score"])) + game.add_child(Node.u32("clear_type", score["clear_type"])) + game.add_child(Node.u32("score_grade", score["grade"])) + game.add_child(Node.u32("max_chain", 0)) + game.add_child(Node.u32("critical", 0)) + game.add_child(Node.u32("near", 0)) + game.add_child(Node.u32("error", 0)) + game.add_child(Node.u32("effective_rate", 100)) + game.add_child(Node.u32("btn_rate", 0)) + game.add_child(Node.u32("long_rate", 0)) + game.add_child(Node.u32("vol_rate", 0)) + game.add_child(Node.u8("mode", 0)) + game.add_child(Node.u8("gauge_type", 0)) + game.add_child(Node.u8("notes_option", 0)) + game.add_child(Node.u16("online_num", 0)) + game.add_child(Node.u16("local_num", 0)) + game.add_child(Node.string("locid", location)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") @@ -598,37 +619,39 @@ class SoundVoltexHeavenlyHavenClient(BaseClient): def verify_game_load_r(self, refid: str) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('method', 'sv4_load_r') - game.set_attribute('ver', '0') - game.add_child(Node.string('refid', refid)) + game.set_attribute("method", "sv4_load_r") + game.set_attribute("ver", "0") + game.add_child(Node.string("refid", refid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") - def verify_game_save_c(self, location: str, refid: str, play_id: int, season: int, course: int) -> None: + def verify_game_save_c( + self, location: str, refid: str, play_id: int, season: int, course: int + ) -> None: call = self.call_node() - game = Node.void('game') + game = Node.void("game") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'sv4_save_c') - game.add_child(Node.string('refid', refid)) - game.add_child(Node.u32('play_id', play_id)) - game.add_child(Node.s32('ssnid', season)) - game.add_child(Node.s16('crsid', course)) - game.add_child(Node.s16('ct', 2)) - game.add_child(Node.s16('ar', 15000)) - game.add_child(Node.u32('sc', 1234567)) - game.add_child(Node.s16('gr', 7)) - game.add_child(Node.string('locid', location)) + game.set_attribute("ver", "0") + game.set_attribute("method", "sv4_save_c") + game.add_child(Node.string("refid", refid)) + game.add_child(Node.u32("play_id", play_id)) + game.add_child(Node.s32("ssnid", season)) + game.add_child(Node.s16("crsid", course)) + game.add_child(Node.s16("ct", 2)) + game.add_child(Node.s16("ar", 15000)) + game.add_child(Node.u32("sc", 1234567)) + game.add_child(Node.s16("gr", 7)) + game.add_child(Node.string("locid", location)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game") @@ -637,21 +660,21 @@ class SoundVoltexHeavenlyHavenClient(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'local2', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "local2", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -672,25 +695,35 @@ class SoundVoltexHeavenlyHavenClient(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # SDVX doesn't read the new profile, it asks for the profile itself after calling new - self.verify_game_load(card, ref_id, msg_type='new') + self.verify_game_load(card, ref_id, msg_type="new") self.verify_game_new(location, ref_id) - self.verify_game_load(card, ref_id, msg_type='existing') + self.verify_game_load(card, ref_id, msg_type="existing") else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Verify rivals node (necessary to return but can hold nothing) self.verify_game_load_r(ref_id) @@ -707,85 +740,103 @@ class SoundVoltexHeavenlyHavenClient(BaseClient): if cardid is None: # Verify profile loading and saving - profile = self.verify_game_load(card, ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception(f'Profile has incorrect name {profile["name"]} associated with it!') - if profile['packet'] != 0: - raise Exception('Profile has nonzero blocks associated with it!') - if profile['block'] != 0: - raise Exception('Profile has nonzero packets associated with it!') - if profile['blaster_energy'] != 0: - raise Exception('Profile has nonzero blaster energy associated with it!') - if profile['items']: - raise Exception('Profile already has purchased items!') - if profile['courses']: - raise Exception('Profile already has finished courses!') + profile = self.verify_game_load(card, ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception( + f'Profile has incorrect name {profile["name"]} associated with it!' + ) + if profile["packet"] != 0: + raise Exception("Profile has nonzero blocks associated with it!") + if profile["block"] != 0: + raise Exception("Profile has nonzero packets associated with it!") + if profile["blaster_energy"] != 0: + raise Exception( + "Profile has nonzero blaster energy associated with it!" + ) + if profile["items"]: + raise Exception("Profile already has purchased items!") + if profile["courses"]: + raise Exception("Profile already has finished courses!") # Verify purchase failure, try buying song we can't afford self.verify_game_buy(ref_id, 0, 29, 1, 10, 0, 29, 3, False) - self.verify_game_save(location, ref_id, packet=123, block=234, blaster_energy=42) - profile = self.verify_game_load(card, ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception(f'Profile has incorrect name {profile["name"]} associated with it!') - if profile['packet'] != 123: - raise Exception('Profile has invalid blocks associated with it!') - if profile['block'] != 234: - raise Exception('Profile has invalid packets associated with it!') - if profile['blaster_energy'] != 42: - raise Exception('Profile has invalid blaster energy associated with it!') - if profile['courses']: - raise Exception('Profile already has finished courses!') + self.verify_game_save( + location, ref_id, packet=123, block=234, blaster_energy=42 + ) + profile = self.verify_game_load(card, ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception( + f'Profile has incorrect name {profile["name"]} associated with it!' + ) + if profile["packet"] != 123: + raise Exception("Profile has invalid blocks associated with it!") + if profile["block"] != 234: + raise Exception("Profile has invalid packets associated with it!") + if profile["blaster_energy"] != 42: + raise Exception( + "Profile has invalid blaster energy associated with it!" + ) + if profile["courses"]: + raise Exception("Profile already has finished courses!") self.verify_game_save(location, ref_id, packet=1, block=2, blaster_energy=3) - profile = self.verify_game_load(card, ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception(f'Profile has incorrect name {profile["name"]} associated with it!') - if profile['packet'] != 124: - raise Exception('Profile has invalid blocks associated with it!') - if profile['block'] != 236: - raise Exception('Profile has invalid packets associated with it!') - if profile['blaster_energy'] != 45: - raise Exception('Profile has invalid blaster energy associated with it!') - if profile['courses']: - raise Exception('Profile has invalid finished courses!') + profile = self.verify_game_load(card, ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception( + f'Profile has incorrect name {profile["name"]} associated with it!' + ) + if profile["packet"] != 124: + raise Exception("Profile has invalid blocks associated with it!") + if profile["block"] != 236: + raise Exception("Profile has invalid packets associated with it!") + if profile["blaster_energy"] != 45: + raise Exception( + "Profile has invalid blaster energy associated with it!" + ) + if profile["courses"]: + raise Exception("Profile has invalid finished courses!") # Verify purchase success, buy a song we can afford now self.verify_game_buy(ref_id, 0, 29, 1, 10, 0, 29, 3, True) - profile = self.verify_game_load(card, ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception(f'Profile has incorrect name {profile["name"]} associated with it!') - if profile['packet'] != 124: - raise Exception('Profile has invalid blocks associated with it!') - if profile['block'] != 226: - raise Exception('Profile has invalid packets associated with it!') - if profile['blaster_energy'] != 45: - raise Exception('Profile has invalid blaster energy associated with it!') - if 0 not in profile['items'] or 29 not in profile['items'][0]: - raise Exception('Purchase didn\'t add to profile!') - if profile['items'][0][29] != 3: - raise Exception('Purchase parameters are wrong!') - if profile['courses']: - raise Exception('Profile has invalid finished courses!') + profile = self.verify_game_load(card, ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception( + f'Profile has incorrect name {profile["name"]} associated with it!' + ) + if profile["packet"] != 124: + raise Exception("Profile has invalid blocks associated with it!") + if profile["block"] != 226: + raise Exception("Profile has invalid packets associated with it!") + if profile["blaster_energy"] != 45: + raise Exception( + "Profile has invalid blaster energy associated with it!" + ) + if 0 not in profile["items"] or 29 not in profile["items"][0]: + raise Exception("Purchase didn't add to profile!") + if profile["items"][0][29] != 3: + raise Exception("Purchase parameters are wrong!") + if profile["courses"]: + raise Exception("Profile has invalid finished courses!") # Verify that we can finish skill analyzer courses self.verify_game_save_c(location, ref_id, play_id, 14, 3) - profile = self.verify_game_load(card, ref_id, msg_type='existing') - if 14 not in profile['courses'] or 3 not in profile['courses'][14]: - raise Exception('Course didn\'t add to profile!') - if profile['courses'][14][3]['achievement_rate'] != 15000: - raise Exception('Course didn\'t save achievement rate!') - if profile['courses'][14][3]['clear_type'] != 2: - raise Exception('Course didn\'t save clear type!') - if profile['courses'][14][3]['score'] != 1234567: - raise Exception('Course didn\'t save score!') - if profile['courses'][14][3]['grade'] != 7: - raise Exception('Course didn\'t save grade!') + profile = self.verify_game_load(card, ref_id, msg_type="existing") + if 14 not in profile["courses"] or 3 not in profile["courses"][14]: + raise Exception("Course didn't add to profile!") + if profile["courses"][14][3]["achievement_rate"] != 15000: + raise Exception("Course didn't save achievement rate!") + if profile["courses"][14][3]["clear_type"] != 2: + raise Exception("Course didn't save clear type!") + if profile["courses"][14][3]["score"] != 1234567: + raise Exception("Course didn't save score!") + if profile["courses"][14][3]["grade"] != 7: + raise Exception("Course didn't save grade!") # Verify empty profile has no scores on it scores = self.verify_game_load_m(ref_id) if len(scores) > 0: - raise Exception('Score on an empty profile!') + raise Exception("Score on an empty profile!") # Verify score saving and updating for phase in [1, 2]: @@ -793,57 +844,57 @@ class SoundVoltexHeavenlyHavenClient(BaseClient): dummyscores = [ # An okay score on a chart { - 'id': 1, - 'chart': 1, - 'grade': 3, - 'clear_type': 2, - 'score': 765432, + "id": 1, + "chart": 1, + "grade": 3, + "clear_type": 2, + "score": 765432, }, # A good score on an easier chart of the same song { - 'id': 1, - 'chart': 0, - 'grade': 6, - 'clear_type': 3, - 'score': 7654321, + "id": 1, + "chart": 0, + "grade": 6, + "clear_type": 3, + "score": 7654321, }, # A bad score on a hard chart { - 'id': 2, - 'chart': 2, - 'grade': 1, - 'clear_type': 1, - 'score': 12345, + "id": 2, + "chart": 2, + "grade": 1, + "clear_type": 1, + "score": 12345, }, # A terrible score on an easy chart { - 'id': 3, - 'chart': 0, - 'grade': 1, - 'clear_type': 1, - 'score': 123, + "id": 3, + "chart": 0, + "grade": 1, + "clear_type": 1, + "score": 123, }, ] if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 1, - 'chart': 1, - 'grade': 5, - 'clear_type': 3, - 'score': 8765432, + "id": 1, + "chart": 1, + "grade": 5, + "clear_type": 3, + "score": 8765432, }, # A worse score on another same chart { - 'id': 1, - 'chart': 0, - 'grade': 4, - 'clear_type': 2, - 'score': 6543210, - 'expected_score': 7654321, - 'expected_clear_type': 3, - 'expected_grade': 6, + "id": 1, + "chart": 0, + "grade": 4, + "clear_type": 2, + "score": 6543210, + "expected_score": 7654321, + "expected_clear_type": 3, + "expected_grade": 6, }, ] for dummyscore in dummyscores: @@ -853,32 +904,43 @@ class SoundVoltexHeavenlyHavenClient(BaseClient): for expected in dummyscores: actual = None for received in scores: - if received['id'] == expected['id'] and received['chart'] == expected['chart']: + if ( + received["id"] == expected["id"] + and received["chart"] == expected["chart"] + ): actual = received break if actual is None: - raise Exception(f"Didn't find song {expected['id']} chart {expected['chart']} in response!") + raise Exception( + f"Didn't find song {expected['id']} chart {expected['chart']} in response!" + ) - if 'expected_score' in expected: - expected_score = expected['expected_score'] + if "expected_score" in expected: + expected_score = expected["expected_score"] else: - expected_score = expected['score'] - if 'expected_grade' in expected: - expected_grade = expected['expected_grade'] + expected_score = expected["score"] + if "expected_grade" in expected: + expected_grade = expected["expected_grade"] else: - expected_grade = expected['grade'] - if 'expected_clear_type' in expected: - expected_clear_type = expected['expected_clear_type'] + expected_grade = expected["grade"] + if "expected_clear_type" in expected: + expected_clear_type = expected["expected_clear_type"] else: - expected_clear_type = expected['clear_type'] + expected_clear_type = expected["clear_type"] - if actual['score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'') - if actual['grade'] != expected_grade: - raise Exception(f'Expected a grade of \'{expected_grade}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got grade \'{actual["grade"]}\'') - if actual['clear_type'] != expected_clear_type: - raise Exception(f'Expected a clear_type of \'{expected_clear_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got clear_type \'{actual["clear_type"]}\'') + if actual["score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'' + ) + if actual["grade"] != expected_grade: + raise Exception( + f'Expected a grade of \'{expected_grade}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got grade \'{actual["grade"]}\'' + ) + if actual["clear_type"] != expected_clear_type: + raise Exception( + f'Expected a clear_type of \'{expected_clear_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got clear_type \'{actual["clear_type"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) diff --git a/bemani/client/sdvx/infiniteinfection.py b/bemani/client/sdvx/infiniteinfection.py index a7a9954..5601f28 100644 --- a/bemani/client/sdvx/infiniteinfection.py +++ b/bemani/client/sdvx/infiniteinfection.py @@ -7,30 +7,30 @@ from bemani.protocol import Node class SoundVoltexInfiniteInfectionClient(BaseClient): - NAME = 'TEST' + NAME = "TEST" def verify_eventlog_write(self, location: str) -> None: call = self.call_node() # Construct node - eventlog = Node.void('eventlog') + eventlog = Node.void("eventlog") call.add_child(eventlog) - eventlog.set_attribute('method', 'write') - eventlog.add_child(Node.u32('retrycnt', 0)) - data = Node.void('data') + eventlog.set_attribute("method", "write") + eventlog.add_child(Node.u32("retrycnt", 0)) + data = Node.void("data") eventlog.add_child(data) - data.add_child(Node.string('eventid', 'S_PWRON')) - data.add_child(Node.s32('eventorder', 0)) - data.add_child(Node.u64('pcbtime', int(time.time() * 1000))) - data.add_child(Node.s64('gamesession', -1)) - data.add_child(Node.string('strdata1', '1.7.6')) - data.add_child(Node.string('strdata2', '')) - data.add_child(Node.s64('numdata1', 1)) - data.add_child(Node.s64('numdata2', 0)) - data.add_child(Node.string('locationid', location)) + data.add_child(Node.string("eventid", "S_PWRON")) + data.add_child(Node.s32("eventorder", 0)) + data.add_child(Node.u64("pcbtime", int(time.time() * 1000))) + data.add_child(Node.s64("gamesession", -1)) + data.add_child(Node.string("strdata1", "1.7.6")) + data.add_child(Node.string("strdata2", "")) + data.add_child(Node.s64("numdata1", 1)) + data.add_child(Node.s64("numdata2", 0)) + data.add_child(Node.string("locationid", location)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/eventlog/gamesession") @@ -41,14 +41,14 @@ class SoundVoltexInfiniteInfectionClient(BaseClient): def verify_game_hiscore(self, location: str) -> None: call = self.call_node() - game = Node.void('game_2') - game.set_attribute('ver', '0') - game.set_attribute('method', 'hiscore') - game.add_child(Node.string('locid', location)) + game = Node.void("game_2") + game.set_attribute("ver", "0") + game.set_attribute("method", "hiscore") + game.add_child(Node.string("locid", location)) call.add_child(game) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_2/hitchart/info/id") @@ -70,48 +70,48 @@ class SoundVoltexInfiniteInfectionClient(BaseClient): def verify_game_shop(self, location: str) -> None: call = self.call_node() - game = Node.void('game_2') + game = Node.void("game_2") call.add_child(game) - game.set_attribute('method', 'shop') - game.set_attribute('ver', '0') - game.add_child(Node.string('locid', location)) - game.add_child(Node.string('regcode', '.')) - game.add_child(Node.string('locname', '')) - game.add_child(Node.u8('loctype', 0)) - game.add_child(Node.string('cstcode', '')) - game.add_child(Node.string('cpycode', '')) - game.add_child(Node.s32('latde', 0)) - game.add_child(Node.s32('londe', 0)) - game.add_child(Node.u8('accu', 0)) - game.add_child(Node.string('linid', '.')) - game.add_child(Node.u8('linclass', 0)) - game.add_child(Node.ipv4('ipaddr', '0.0.0.0')) - game.add_child(Node.string('hadid', '00010203040506070809')) - game.add_child(Node.string('licid', '00010203040506070809')) - game.add_child(Node.string('actid', self.pcbid)) - game.add_child(Node.s8('appstate', 0)) - game.add_child(Node.s8('c_need', 1)) - game.add_child(Node.s8('c_credit', 2)) - game.add_child(Node.s8('s_credit', 2)) - game.add_child(Node.bool('free_p', True)) - game.add_child(Node.bool('close', False)) - game.add_child(Node.s32('close_t', 1380)) - game.add_child(Node.u32('playc', 0)) - game.add_child(Node.u32('playn', 0)) - game.add_child(Node.u32('playe', 0)) - game.add_child(Node.u32('test_m', 0)) - game.add_child(Node.u32('service', 0)) - game.add_child(Node.bool('paseli', True)) - game.add_child(Node.u32('update', 0)) - game.add_child(Node.string('shopname', '')) - game.add_child(Node.bool('newpc', False)) - game.add_child(Node.s32('s_paseli', 206)) - game.add_child(Node.s32('monitor', 1)) - game.add_child(Node.string('romnumber', 'KFC-JA-M01')) - game.add_child(Node.string('etc', 'TaxMode:1,BasicRate:100/1,FirstFree:0')) + game.set_attribute("method", "shop") + game.set_attribute("ver", "0") + game.add_child(Node.string("locid", location)) + game.add_child(Node.string("regcode", ".")) + game.add_child(Node.string("locname", "")) + game.add_child(Node.u8("loctype", 0)) + game.add_child(Node.string("cstcode", "")) + game.add_child(Node.string("cpycode", "")) + game.add_child(Node.s32("latde", 0)) + game.add_child(Node.s32("londe", 0)) + game.add_child(Node.u8("accu", 0)) + game.add_child(Node.string("linid", ".")) + game.add_child(Node.u8("linclass", 0)) + game.add_child(Node.ipv4("ipaddr", "0.0.0.0")) + game.add_child(Node.string("hadid", "00010203040506070809")) + game.add_child(Node.string("licid", "00010203040506070809")) + game.add_child(Node.string("actid", self.pcbid)) + game.add_child(Node.s8("appstate", 0)) + game.add_child(Node.s8("c_need", 1)) + game.add_child(Node.s8("c_credit", 2)) + game.add_child(Node.s8("s_credit", 2)) + game.add_child(Node.bool("free_p", True)) + game.add_child(Node.bool("close", False)) + game.add_child(Node.s32("close_t", 1380)) + game.add_child(Node.u32("playc", 0)) + game.add_child(Node.u32("playn", 0)) + game.add_child(Node.u32("playe", 0)) + game.add_child(Node.u32("test_m", 0)) + game.add_child(Node.u32("service", 0)) + game.add_child(Node.bool("paseli", True)) + game.add_child(Node.u32("update", 0)) + game.add_child(Node.string("shopname", "")) + game.add_child(Node.bool("newpc", False)) + game.add_child(Node.s32("s_paseli", 206)) + game.add_child(Node.s32("monitor", 1)) + game.add_child(Node.string("romnumber", "KFC-JA-M01")) + game.add_child(Node.string("etc", "TaxMode:1,BasicRate:100/1,FirstFree:0")) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_2/nxt_time") @@ -119,17 +119,17 @@ class SoundVoltexInfiniteInfectionClient(BaseClient): def verify_game_new(self, location: str, refid: str) -> None: call = self.call_node() - game = Node.void('game_2') + game = Node.void("game_2") call.add_child(game) - game.set_attribute('method', 'new') - game.set_attribute('ver', '0') - game.add_child(Node.string('dataid', refid)) - game.add_child(Node.string('refid', refid)) - game.add_child(Node.string('name', self.NAME)) - game.add_child(Node.string('locid', location)) + game.set_attribute("method", "new") + game.set_attribute("ver", "0") + game.add_child(Node.string("dataid", refid)) + game.add_child(Node.string("refid", refid)) + game.add_child(Node.string("name", self.NAME)) + game.add_child(Node.string("locid", location)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_2") @@ -137,54 +137,67 @@ class SoundVoltexInfiniteInfectionClient(BaseClient): def verify_game_frozen(self, refid: str, time: int) -> None: call = self.call_node() - game = Node.void('game_2') + game = Node.void("game_2") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'frozen') - game.add_child(Node.string('refid', refid)) - game.add_child(Node.u32('sec', time)) + game.set_attribute("ver", "0") + game.set_attribute("method", "frozen") + game.add_child(Node.string("refid", refid)) + game.add_child(Node.u32("sec", time)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_2/result") - def verify_game_save(self, location: str, refid: str, packet: int, block: int, blaster_energy: int, appealcards: List[int]) -> None: + def verify_game_save( + self, + location: str, + refid: str, + packet: int, + block: int, + blaster_energy: int, + appealcards: List[int], + ) -> None: call = self.call_node() - game = Node.void('game_2') + game = Node.void("game_2") call.add_child(game) - game.set_attribute('method', 'save') - game.set_attribute('ver', '0') - game.add_child(Node.string('refid', refid)) - game.add_child(Node.string('locid', location)) - game.add_child(Node.u8('headphone', 0)) - game.add_child(Node.u8('hispeed', 52)) - game.add_child(Node.u16('appeal_id', 1001)) - game.add_child(Node.u16('comment_id', 0)) - game.add_child(Node.s32('music_id', 29)) - game.add_child(Node.u8('music_type', 1)) - game.add_child(Node.u8('sort_type', 1)) - game.add_child(Node.u8('narrow_down', 0)) - game.add_child(Node.u8('gauge_option', 0)) - game.add_child(Node.u32('earned_gamecoin_packet', packet)) - game.add_child(Node.u32('earned_gamecoin_block', block)) - game.add_child(Node.void('item')) - appealcard = Node.void('appealcard') + game.set_attribute("method", "save") + game.set_attribute("ver", "0") + game.add_child(Node.string("refid", refid)) + game.add_child(Node.string("locid", location)) + game.add_child(Node.u8("headphone", 0)) + game.add_child(Node.u8("hispeed", 52)) + game.add_child(Node.u16("appeal_id", 1001)) + game.add_child(Node.u16("comment_id", 0)) + game.add_child(Node.s32("music_id", 29)) + game.add_child(Node.u8("music_type", 1)) + game.add_child(Node.u8("sort_type", 1)) + game.add_child(Node.u8("narrow_down", 0)) + game.add_child(Node.u8("gauge_option", 0)) + game.add_child(Node.u32("earned_gamecoin_packet", packet)) + game.add_child(Node.u32("earned_gamecoin_block", block)) + game.add_child(Node.void("item")) + appealcard = Node.void("appealcard") game.add_child(appealcard) for card in appealcards: - info = Node.void('info') - info.add_child(Node.u32('id', card)) - info.add_child(Node.u32('count', 0)) + info = Node.void("info") + info.add_child(Node.u32("id", card)) + info.add_child(Node.u32("count", 0)) appealcard.add_child(info) - game.add_child(Node.s32_array('hidden_param', [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])) - game.add_child(Node.s16('skill_name_id', -1)) - game.add_child(Node.s32('earned_blaster_energy', blaster_energy)) - game.add_child(Node.u32('blaster_count', 0)) + game.add_child( + Node.s32_array( + "hidden_param", + [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ) + ) + game.add_child(Node.s16("skill_name_id", -1)) + game.add_child(Node.s32("earned_blaster_energy", blaster_energy)) + game.add_child(Node.u32("blaster_count", 0)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_2") @@ -192,13 +205,13 @@ class SoundVoltexInfiniteInfectionClient(BaseClient): def verify_game_common(self) -> None: call = self.call_node() - game = Node.void('game_2') - game.set_attribute('ver', '0') - game.set_attribute('method', 'common') + game = Node.void("game_2") + game.set_attribute("ver", "0") + game.set_attribute("method", "common") call.add_child(game) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_2/music_limited") @@ -219,26 +232,37 @@ class SoundVoltexInfiniteInfectionClient(BaseClient): self.assert_path(resp, "response/game_2/skill_course/info/track/music_id") self.assert_path(resp, "response/game_2/skill_course/info/track/music_type") - def verify_game_buy(self, refid: str, catalogtype: int, catalogid: int, currencytype: int, price: int, itemtype: int, itemid: int, param: int, success: bool) -> None: + def verify_game_buy( + self, + refid: str, + catalogtype: int, + catalogid: int, + currencytype: int, + price: int, + itemtype: int, + itemid: int, + param: int, + success: bool, + ) -> None: call = self.call_node() - game = Node.void('game_2') + game = Node.void("game_2") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'buy') - game.add_child(Node.string('refid', refid)) - game.add_child(Node.u8('catalog_type', catalogtype)) - game.add_child(Node.u32('catalog_id', catalogid)) - game.add_child(Node.u32('earned_gamecoin_packet', 0)) - game.add_child(Node.u32('earned_gamecoin_block', 0)) - game.add_child(Node.u32('currency_type', currencytype)) - game.add_child(Node.u32('price', price)) - game.add_child(Node.u32('item_type', itemtype)) - game.add_child(Node.u32('item_id', itemid)) - game.add_child(Node.u32('param', param)) + game.set_attribute("ver", "0") + game.set_attribute("method", "buy") + game.add_child(Node.string("refid", refid)) + game.add_child(Node.u8("catalog_type", catalogtype)) + game.add_child(Node.u32("catalog_id", catalogid)) + game.add_child(Node.u32("earned_gamecoin_packet", 0)) + game.add_child(Node.u32("earned_gamecoin_block", 0)) + game.add_child(Node.u32("currency_type", currencytype)) + game.add_child(Node.u32("price", price)) + game.add_child(Node.u32("item_type", itemtype)) + game.add_child(Node.u32("item_id", itemid)) + game.add_child(Node.u32("param", param)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_2/gamecoin_packet") @@ -246,30 +270,32 @@ class SoundVoltexInfiniteInfectionClient(BaseClient): self.assert_path(resp, "response/game_2/result") if success: - if resp.child_value('game_2/result') != 0: - raise Exception('Failed to purchase!') + if resp.child_value("game_2/result") != 0: + raise Exception("Failed to purchase!") else: - if resp.child_value('game_2/result') == 0: - raise Exception('Purchased when shouldn\'t have!') + if resp.child_value("game_2/result") == 0: + raise Exception("Purchased when shouldn't have!") - def verify_game_load(self, cardid: str, refid: str, msg_type: str) -> Dict[str, Any]: + def verify_game_load( + self, cardid: str, refid: str, msg_type: str + ) -> Dict[str, Any]: call = self.call_node() - game = Node.void('game_2') + game = Node.void("game_2") call.add_child(game) - game.set_attribute('method', 'load') - game.set_attribute('ver', '0') - game.add_child(Node.string('dataid', refid)) - game.add_child(Node.string('cardid', cardid)) - game.add_child(Node.string('refid', refid)) + game.set_attribute("method", "load") + game.set_attribute("ver", "0") + game.add_child(Node.string("dataid", refid)) + game.add_child(Node.string("cardid", cardid)) + game.add_child(Node.string("refid", refid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct if msg_type == "new": self.assert_path(resp, "response/game_2/result") - if resp.child_value('game_2/result') != 1: + if resp.child_value("game_2/result") != 1: raise Exception("Invalid result for new profile!") return None @@ -290,53 +316,53 @@ class SoundVoltexInfiniteInfectionClient(BaseClient): self.assert_path(resp, "response/game_2/skill/course_all") items: Dict[int, Dict[int, int]] = {} - for child in resp.child('game_2/item').children: - if child.name != 'info': + for child in resp.child("game_2/item").children: + if child.name != "info": continue - itype = child.child_value('type') - iid = child.child_value('id') - param = child.child_value('param') + itype = child.child_value("type") + iid = child.child_value("id") + param = child.child_value("param") if itype not in items: items[itype] = {} items[itype][iid] = param appealcards: Dict[int, int] = {} - for child in resp.child('game_2/appealcard').children: - if child.name != 'info': + for child in resp.child("game_2/appealcard").children: + if child.name != "info": continue - iid = child.child_value('id') - count = child.child_value('count') + iid = child.child_value("id") + count = child.child_value("count") appealcards[iid] = count courses: Dict[int, Dict[int, Dict[str, int]]] = {} - for child in resp.child('game_2/skill/course_all').children: - if child.name != 'd': + for child in resp.child("game_2/skill/course_all").children: + if child.name != "d": continue - crsid = child.child_value('crsid') - season = child.child_value('ssnid') - achievement_rate = child.child_value('ar') - clear_type = child.child_value('ct') + crsid = child.child_value("crsid") + season = child.child_value("ssnid") + achievement_rate = child.child_value("ar") + clear_type = child.child_value("ct") if season not in courses: courses[season] = {} courses[season][crsid] = { - 'achievement_rate': achievement_rate, - 'clear_type': clear_type, + "achievement_rate": achievement_rate, + "clear_type": clear_type, } return { - 'name': resp.child_value('game_2/name'), - 'packet': resp.child_value('game_2/gamecoin_packet'), - 'block': resp.child_value('game_2/gamecoin_block'), - 'blaster_energy': resp.child_value('game_2/blaster_energy'), - 'items': items, - 'appealcards': appealcards, - 'courses': courses, + "name": resp.child_value("game_2/name"), + "packet": resp.child_value("game_2/gamecoin_packet"), + "block": resp.child_value("game_2/gamecoin_block"), + "blaster_energy": resp.child_value("game_2/blaster_energy"), + "items": items, + "appealcards": appealcards, + "courses": courses, } else: raise Exception(f"Invalid game load type {msg_type}") @@ -344,13 +370,13 @@ class SoundVoltexInfiniteInfectionClient(BaseClient): def verify_game_lounge(self) -> None: call = self.call_node() - game = Node.void('game_2') + game = Node.void("game_2") call.add_child(game) - game.set_attribute('method', 'lounge') - game.set_attribute('ver', '0') + game.set_attribute("method", "lounge") + game.set_attribute("ver", "0") # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_2/interval") @@ -358,39 +384,39 @@ class SoundVoltexInfiniteInfectionClient(BaseClient): def verify_game_entry_s(self) -> int: call = self.call_node() - game = Node.void('game_2') + game = Node.void("game_2") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'entry_s') - game.add_child(Node.u8('c_ver', 69)) - game.add_child(Node.u8('p_num', 1)) - game.add_child(Node.u8('p_rest', 1)) - game.add_child(Node.u8('filter', 1)) - game.add_child(Node.u32('mid', 416)) - game.add_child(Node.u32('sec', 45)) - game.add_child(Node.u16('port', 10007)) - game.add_child(Node.fouru8('gip', [127, 0, 0, 1])) - game.add_child(Node.fouru8('lip', [10, 0, 5, 73])) - game.add_child(Node.u8('claim', 0)) + game.set_attribute("ver", "0") + game.set_attribute("method", "entry_s") + game.add_child(Node.u8("c_ver", 69)) + game.add_child(Node.u8("p_num", 1)) + game.add_child(Node.u8("p_rest", 1)) + game.add_child(Node.u8("filter", 1)) + game.add_child(Node.u32("mid", 416)) + game.add_child(Node.u32("sec", 45)) + game.add_child(Node.u16("port", 10007)) + game.add_child(Node.fouru8("gip", [127, 0, 0, 1])) + game.add_child(Node.fouru8("lip", [10, 0, 5, 73])) + game.add_child(Node.u8("claim", 0)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_2/entry_id") - return resp.child_value('game_2/entry_id') + return resp.child_value("game_2/entry_id") def verify_game_entry_e(self, eid: int) -> None: call = self.call_node() - game = Node.void('game_2') + game = Node.void("game_2") call.add_child(game) - game.set_attribute('method', 'entry_e') - game.set_attribute('ver', '0') - game.add_child(Node.u32('eid', eid)) + game.set_attribute("method", "entry_e") + game.set_attribute("ver", "0") + game.add_child(Node.u32("eid", eid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_2") @@ -398,89 +424,95 @@ class SoundVoltexInfiniteInfectionClient(BaseClient): def verify_game_load_m(self, refid: str) -> List[Dict[str, int]]: call = self.call_node() - game = Node.void('game_2') + game = Node.void("game_2") call.add_child(game) - game.set_attribute('method', 'load_m') - game.set_attribute('ver', '0') - game.add_child(Node.string('dataid', refid)) + game.set_attribute("method", "load_m") + game.set_attribute("ver", "0") + game.add_child(Node.string("dataid", refid)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_2/new") scores = [] - for child in resp.child('game_2/new').children: - if child.name != 'music': + for child in resp.child("game_2/new").children: + if child.name != "music": continue - musicid = child.child_value('music_id') - chart = child.child_value('music_type') - clear_type = child.child_value('clear_type') - score = child.child_value('score') - grade = child.child_value('score_grade') + musicid = child.child_value("music_id") + chart = child.child_value("music_type") + clear_type = child.child_value("clear_type") + score = child.child_value("score") + grade = child.child_value("score_grade") - scores.append({ - 'id': musicid, - 'chart': chart, - 'clear_type': clear_type, - 'score': score, - 'grade': grade, - }) + scores.append( + { + "id": musicid, + "chart": chart, + "clear_type": clear_type, + "score": score, + "grade": grade, + } + ) return scores - def verify_game_save_m(self, location: str, refid: str, score: Dict[str, int]) -> None: + def verify_game_save_m( + self, location: str, refid: str, score: Dict[str, int] + ) -> None: call = self.call_node() - game = Node.void('game_2') + game = Node.void("game_2") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'save_m') - game.add_child(Node.string('refid', refid)) - game.add_child(Node.string('dataid', refid)) - game.add_child(Node.u32('music_id', score['id'])) - game.add_child(Node.u32('music_type', score['chart'])) - game.add_child(Node.u32('score', score['score'])) - game.add_child(Node.u32('clear_type', score['clear_type'])) - game.add_child(Node.u32('score_grade', score['grade'])) - game.add_child(Node.u32('max_chain', 0)) - game.add_child(Node.u32('critical', 0)) - game.add_child(Node.u32('near', 0)) - game.add_child(Node.u32('error', 0)) - game.add_child(Node.u32('effective_rate', 100)) - game.add_child(Node.u32('btn_rate', 0)) - game.add_child(Node.u32('long_rate', 0)) - game.add_child(Node.u32('vol_rate', 0)) - game.add_child(Node.u8('mode', 0)) - game.add_child(Node.u8('gauge_type', 0)) - game.add_child(Node.u16('online_num', 0)) - game.add_child(Node.u16('local_num', 0)) - game.add_child(Node.string('locid', location)) + game.set_attribute("ver", "0") + game.set_attribute("method", "save_m") + game.add_child(Node.string("refid", refid)) + game.add_child(Node.string("dataid", refid)) + game.add_child(Node.u32("music_id", score["id"])) + game.add_child(Node.u32("music_type", score["chart"])) + game.add_child(Node.u32("score", score["score"])) + game.add_child(Node.u32("clear_type", score["clear_type"])) + game.add_child(Node.u32("score_grade", score["grade"])) + game.add_child(Node.u32("max_chain", 0)) + game.add_child(Node.u32("critical", 0)) + game.add_child(Node.u32("near", 0)) + game.add_child(Node.u32("error", 0)) + game.add_child(Node.u32("effective_rate", 100)) + game.add_child(Node.u32("btn_rate", 0)) + game.add_child(Node.u32("long_rate", 0)) + game.add_child(Node.u32("vol_rate", 0)) + game.add_child(Node.u8("mode", 0)) + game.add_child(Node.u8("gauge_type", 0)) + game.add_child(Node.u16("online_num", 0)) + game.add_child(Node.u16("local_num", 0)) + game.add_child(Node.string("locid", location)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_2") - def verify_game_save_c(self, location: str, refid: str, season: int, course: int) -> None: + def verify_game_save_c( + self, location: str, refid: str, season: int, course: int + ) -> None: call = self.call_node() - game = Node.void('game_2') + game = Node.void("game_2") call.add_child(game) - game.set_attribute('ver', '0') - game.set_attribute('method', 'save_c') - game.add_child(Node.string('dataid', refid)) - game.add_child(Node.s16('crsid', course)) - game.add_child(Node.s16('ct', 2)) - game.add_child(Node.s16('ar', 15000)) - game.add_child(Node.s32('ssnid', season)) - game.add_child(Node.string('locid', location)) + game.set_attribute("ver", "0") + game.set_attribute("method", "save_c") + game.add_child(Node.string("dataid", refid)) + game.add_child(Node.s16("crsid", course)) + game.add_child(Node.s16("ct", 2)) + game.add_child(Node.s16("ar", 15000)) + game.add_child(Node.s32("ssnid", season)) + game.add_child(Node.string("locid", location)) # Swap with server - resp = self.exchange('', call) + resp = self.exchange("", call) # Verify that response is correct self.assert_path(resp, "response/game_2") @@ -489,20 +521,20 @@ class SoundVoltexInfiniteInfectionClient(BaseClient): # Verify boot sequence is okay self.verify_services_get( expected_services=[ - 'pcbtracker', - 'pcbevent', - 'local', - 'message', - 'facility', - 'cardmng', - 'package', - 'posevent', - 'pkglist', - 'dlstatus', - 'eacoin', - 'lobby', - 'ntp', - 'keepalive' + "pcbtracker", + "pcbevent", + "local", + "message", + "facility", + "cardmng", + "package", + "posevent", + "pkglist", + "dlstatus", + "eacoin", + "lobby", + "ntp", + "keepalive", ] ) paseli_enabled = self.verify_pcbtracker_alive() @@ -522,25 +554,35 @@ class SoundVoltexInfiniteInfectionClient(BaseClient): print(f"Generated random card ID {card} for use.") if cardid is None: - self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled) + self.verify_cardmng_inquire( + card, msg_type="unregistered", paseli_enabled=paseli_enabled + ) ref_id = self.verify_cardmng_getrefid(card) if len(ref_id) != 16: - raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card') - if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + raise Exception( + f"Invalid refid '{ref_id}' returned when registering card" + ) + if ref_id != self.verify_cardmng_inquire( + card, msg_type="new", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # SDVX doesn't read the new profile, it asks for the profile itself after calling new - self.verify_game_load(card, ref_id, msg_type='new') + self.verify_game_load(card, ref_id, msg_type="new") self.verify_game_new(location, ref_id) - self.verify_game_load(card, ref_id, msg_type='existing') + self.verify_game_load(card, ref_id, msg_type="existing") else: print("Skipping new card checks for existing card") - ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled) + ref_id = self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ) # Verify pin handling and return card handling self.verify_cardmng_authpass(ref_id, correct=True) self.verify_cardmng_authpass(ref_id, correct=False) - if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled): - raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card') + if ref_id != self.verify_cardmng_inquire( + card, msg_type="query", paseli_enabled=paseli_enabled + ): + raise Exception(f"Invalid refid '{ref_id}' returned when querying card") # Verify account freezing self.verify_game_frozen(ref_id, 900) @@ -553,102 +595,134 @@ class SoundVoltexInfiniteInfectionClient(BaseClient): if cardid is None: # Verify profile loading and saving - profile = self.verify_game_load(card, ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception(f'Profile has incorrect name {profile["name"]} associated with it!') - if profile['packet'] != 0: - raise Exception('Profile has nonzero blocks associated with it!') - if profile['block'] != 0: - raise Exception('Profile has nonzero packets associated with it!') - if profile['blaster_energy'] != 0: - raise Exception('Profile has nonzero blaster energy associated with it!') - if profile['items']: - raise Exception('Profile already has purchased items!') - if profile['appealcards']: - raise Exception('Profile already has appeal cards!') - if profile['courses']: - raise Exception('Profile already has finished courses!') + profile = self.verify_game_load(card, ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception( + f'Profile has incorrect name {profile["name"]} associated with it!' + ) + if profile["packet"] != 0: + raise Exception("Profile has nonzero blocks associated with it!") + if profile["block"] != 0: + raise Exception("Profile has nonzero packets associated with it!") + if profile["blaster_energy"] != 0: + raise Exception( + "Profile has nonzero blaster energy associated with it!" + ) + if profile["items"]: + raise Exception("Profile already has purchased items!") + if profile["appealcards"]: + raise Exception("Profile already has appeal cards!") + if profile["courses"]: + raise Exception("Profile already has finished courses!") # Verify purchase failure, try buying song we can't afford self.verify_game_buy(ref_id, 0, 29, 1, 10, 0, 29, 3, False) - self.verify_game_save(location, ref_id, packet=123, block=234, blaster_energy=42, appealcards=[]) - profile = self.verify_game_load(card, ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception(f'Profile has incorrect name {profile["name"]} associated with it!') - if profile['packet'] != 123: - raise Exception('Profile has invalid blocks associated with it!') - if profile['block'] != 234: - raise Exception('Profile has invalid packets associated with it!') - if profile['blaster_energy'] != 42: - raise Exception('Profile has invalid blaster energy associated with it!') - if profile['items']: - raise Exception('Profile already has purchased items!') - if profile['appealcards']: - raise Exception('Profile already has appeal cards!') - if profile['courses']: - raise Exception('Profile already has finished courses!') + self.verify_game_save( + location, + ref_id, + packet=123, + block=234, + blaster_energy=42, + appealcards=[], + ) + profile = self.verify_game_load(card, ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception( + f'Profile has incorrect name {profile["name"]} associated with it!' + ) + if profile["packet"] != 123: + raise Exception("Profile has invalid blocks associated with it!") + if profile["block"] != 234: + raise Exception("Profile has invalid packets associated with it!") + if profile["blaster_energy"] != 42: + raise Exception( + "Profile has invalid blaster energy associated with it!" + ) + if profile["items"]: + raise Exception("Profile already has purchased items!") + if profile["appealcards"]: + raise Exception("Profile already has appeal cards!") + if profile["courses"]: + raise Exception("Profile already has finished courses!") - self.verify_game_save(location, ref_id, packet=1, block=2, blaster_energy=3, appealcards=[]) - profile = self.verify_game_load(card, ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception(f'Profile has incorrect name {profile["name"]} associated with it!') - if profile['packet'] != 124: - raise Exception('Profile has invalid blocks associated with it!') - if profile['block'] != 236: - raise Exception('Profile has invalid packets associated with it!') - if profile['blaster_energy'] != 45: - raise Exception('Profile has invalid blaster energy associated with it!') - if profile['items']: - raise Exception('Profile has invalid purchased items!') - if profile['appealcards']: - raise Exception('Profile has invalid appeal cards!') - if profile['courses']: - raise Exception('Profile has invalid finished courses!') + self.verify_game_save( + location, ref_id, packet=1, block=2, blaster_energy=3, appealcards=[] + ) + profile = self.verify_game_load(card, ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception( + f'Profile has incorrect name {profile["name"]} associated with it!' + ) + if profile["packet"] != 124: + raise Exception("Profile has invalid blocks associated with it!") + if profile["block"] != 236: + raise Exception("Profile has invalid packets associated with it!") + if profile["blaster_energy"] != 45: + raise Exception( + "Profile has invalid blaster energy associated with it!" + ) + if profile["items"]: + raise Exception("Profile has invalid purchased items!") + if profile["appealcards"]: + raise Exception("Profile has invalid appeal cards!") + if profile["courses"]: + raise Exception("Profile has invalid finished courses!") # Verify purchase success, buy a song we can afford now self.verify_game_buy(ref_id, 0, 29, 1, 10, 0, 29, 3, True) - profile = self.verify_game_load(card, ref_id, msg_type='existing') - if profile['name'] != self.NAME: - raise Exception(f'Profile has incorrect name {profile["name"]} associated with it!') - if profile['packet'] != 124: - raise Exception('Profile has invalid blocks associated with it!') - if profile['block'] != 226: - raise Exception('Profile has invalid packets associated with it!') - if profile['blaster_energy'] != 45: - raise Exception('Profile has invalid blaster energy associated with it!') - if 0 not in profile['items'] or 29 not in profile['items'][0]: - raise Exception('Purchase didn\'t add to profile!') - if profile['items'][0][29] != 3: - raise Exception('Purchase parameters are wrong!') - if profile['appealcards']: - raise Exception('Profile has invalid appeal cards!') - if profile['courses']: - raise Exception('Profile has invalid finished courses!') + profile = self.verify_game_load(card, ref_id, msg_type="existing") + if profile["name"] != self.NAME: + raise Exception( + f'Profile has incorrect name {profile["name"]} associated with it!' + ) + if profile["packet"] != 124: + raise Exception("Profile has invalid blocks associated with it!") + if profile["block"] != 226: + raise Exception("Profile has invalid packets associated with it!") + if profile["blaster_energy"] != 45: + raise Exception( + "Profile has invalid blaster energy associated with it!" + ) + if 0 not in profile["items"] or 29 not in profile["items"][0]: + raise Exception("Purchase didn't add to profile!") + if profile["items"][0][29] != 3: + raise Exception("Purchase parameters are wrong!") + if profile["appealcards"]: + raise Exception("Profile has invalid appeal cards!") + if profile["courses"]: + raise Exception("Profile has invalid finished courses!") # Verify that we can earn appeal cards - self.verify_game_save(location, ref_id, packet=0, block=0, blaster_energy=0, appealcards=[1001, 1002, 1003, 1004, 1005]) - profile = self.verify_game_load(card, ref_id, msg_type='existing') + self.verify_game_save( + location, + ref_id, + packet=0, + block=0, + blaster_energy=0, + appealcards=[1001, 1002, 1003, 1004, 1005], + ) + profile = self.verify_game_load(card, ref_id, msg_type="existing") for i in [1001, 1002, 1003, 1004, 1005]: - if i not in profile['appealcards']: - raise Exception(f'Profile missing appeal card {i}') - if profile['appealcards'][i] != 0: - raise Exception(f'Profile has bad count for appeal card {i}') + if i not in profile["appealcards"]: + raise Exception(f"Profile missing appeal card {i}") + if profile["appealcards"][i] != 0: + raise Exception(f"Profile has bad count for appeal card {i}") # Verify that we can finish skill analyzer courses self.verify_game_save_c(location, ref_id, 14, 3) - profile = self.verify_game_load(card, ref_id, msg_type='existing') - if 14 not in profile['courses'] or 3 not in profile['courses'][14]: - raise Exception('Course didn\'t add to profile!') - if profile['courses'][14][3]['achievement_rate'] != 15000: - raise Exception('Course didn\'t save achievement rate!') - if profile['courses'][14][3]['clear_type'] != 2: - raise Exception('Course didn\'t save clear type!') + profile = self.verify_game_load(card, ref_id, msg_type="existing") + if 14 not in profile["courses"] or 3 not in profile["courses"][14]: + raise Exception("Course didn't add to profile!") + if profile["courses"][14][3]["achievement_rate"] != 15000: + raise Exception("Course didn't save achievement rate!") + if profile["courses"][14][3]["clear_type"] != 2: + raise Exception("Course didn't save clear type!") # Verify empty profile has no scores on it scores = self.verify_game_load_m(ref_id) if len(scores) > 0: - raise Exception('Score on an empty profile!') + raise Exception("Score on an empty profile!") # Verify score saving and updating for phase in [1, 2]: @@ -656,57 +730,57 @@ class SoundVoltexInfiniteInfectionClient(BaseClient): dummyscores = [ # An okay score on a chart { - 'id': 1, - 'chart': 1, - 'grade': 3, - 'clear_type': 2, - 'score': 765432, + "id": 1, + "chart": 1, + "grade": 3, + "clear_type": 2, + "score": 765432, }, # A good score on an easier chart of the same song { - 'id': 1, - 'chart': 0, - 'grade': 6, - 'clear_type': 3, - 'score': 7654321, + "id": 1, + "chart": 0, + "grade": 6, + "clear_type": 3, + "score": 7654321, }, # A bad score on a hard chart { - 'id': 2, - 'chart': 2, - 'grade': 1, - 'clear_type': 1, - 'score': 12345, + "id": 2, + "chart": 2, + "grade": 1, + "clear_type": 1, + "score": 12345, }, # A terrible score on an easy chart { - 'id': 3, - 'chart': 0, - 'grade': 1, - 'clear_type': 1, - 'score': 123, + "id": 3, + "chart": 0, + "grade": 1, + "clear_type": 1, + "score": 123, }, ] if phase == 2: dummyscores = [ # A better score on the same chart { - 'id': 1, - 'chart': 1, - 'grade': 5, - 'clear_type': 3, - 'score': 8765432, + "id": 1, + "chart": 1, + "grade": 5, + "clear_type": 3, + "score": 8765432, }, # A worse score on another same chart { - 'id': 1, - 'chart': 0, - 'grade': 4, - 'clear_type': 2, - 'score': 6543210, - 'expected_score': 7654321, - 'expected_clear_type': 3, - 'expected_grade': 6, + "id": 1, + "chart": 0, + "grade": 4, + "clear_type": 2, + "score": 6543210, + "expected_score": 7654321, + "expected_clear_type": 3, + "expected_grade": 6, }, ] for dummyscore in dummyscores: @@ -716,32 +790,43 @@ class SoundVoltexInfiniteInfectionClient(BaseClient): for expected in dummyscores: actual = None for received in scores: - if received['id'] == expected['id'] and received['chart'] == expected['chart']: + if ( + received["id"] == expected["id"] + and received["chart"] == expected["chart"] + ): actual = received break if actual is None: - raise Exception(f"Didn't find song {expected['id']} chart {expected['chart']} in response!") + raise Exception( + f"Didn't find song {expected['id']} chart {expected['chart']} in response!" + ) - if 'expected_score' in expected: - expected_score = expected['expected_score'] + if "expected_score" in expected: + expected_score = expected["expected_score"] else: - expected_score = expected['score'] - if 'expected_grade' in expected: - expected_grade = expected['expected_grade'] + expected_score = expected["score"] + if "expected_grade" in expected: + expected_grade = expected["expected_grade"] else: - expected_grade = expected['grade'] - if 'expected_clear_type' in expected: - expected_clear_type = expected['expected_clear_type'] + expected_grade = expected["grade"] + if "expected_clear_type" in expected: + expected_clear_type = expected["expected_clear_type"] else: - expected_clear_type = expected['clear_type'] + expected_clear_type = expected["clear_type"] - if actual['score'] != expected_score: - raise Exception(f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'') - if actual['grade'] != expected_grade: - raise Exception(f'Expected a grade of \'{expected_grade}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got grade \'{actual["grade"]}\'') - if actual['clear_type'] != expected_clear_type: - raise Exception(f'Expected a clear_type of \'{expected_clear_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got clear_type \'{actual["clear_type"]}\'') + if actual["score"] != expected_score: + raise Exception( + f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'' + ) + if actual["grade"] != expected_grade: + raise Exception( + f'Expected a grade of \'{expected_grade}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got grade \'{actual["grade"]}\'' + ) + if actual["clear_type"] != expected_clear_type: + raise Exception( + f'Expected a clear_type of \'{expected_clear_type}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got clear_type \'{actual["clear_type"]}\'' + ) # Sleep so we don't end up putting in score history on the same second time.sleep(1) diff --git a/bemani/common/__init__.py b/bemani/common/__init__.py index 3ade580..5a2dd18 100644 --- a/bemani/common/__init__.py +++ b/bemani/common/__init__.py @@ -1,7 +1,14 @@ from bemani.common.model import Model from bemani.common.validateddict import ValidatedDict, Profile, PlayStatistics, intish from bemani.common.http import HTTP -from bemani.common.constants import APIConstants, GameConstants, VersionConstants, DBConstants, BroadcastConstants, RegionConstants +from bemani.common.constants import ( + APIConstants, + GameConstants, + VersionConstants, + DBConstants, + BroadcastConstants, + RegionConstants, +) from bemani.common.card import CardCipher, CardCipherException from bemani.common.id import ID from bemani.common.aes import AESCipher diff --git a/bemani/common/aes.py b/bemani/common/aes.py index 140eb6e..bf29764 100644 --- a/bemani/common/aes.py +++ b/bemani/common/aes.py @@ -11,16 +11,16 @@ class AESCipher: def __init__(self, key: str) -> None: self.__padamt = 16 - self.__key = hashlib.sha256(key.encode('utf-8')).digest() + self.__key = hashlib.sha256(key.encode("utf-8")).digest() def _pad(self, s: str) -> str: intermediate = f"{len(s)}.{s}" while len(intermediate) % self.__padamt != 0: - intermediate = intermediate + '-' + intermediate = intermediate + "-" return intermediate def _unpad(self, s: str) -> str: - length, string = s.split('.', 1) + length, string = s.split(".", 1) intlength = int(length) return string[:intlength] @@ -29,10 +29,12 @@ class AESCipher: random = Random.new() iv = random.read(AES.block_size) cipher = AES.new(self.__key, AES.MODE_CBC, iv) - return base64.b64encode(iv + cipher.encrypt(raw.encode('utf-8')), altchars=b"._").decode('utf-8') + return base64.b64encode( + iv + cipher.encrypt(raw.encode("utf-8")), altchars=b"._" + ).decode("utf-8") def decrypt(self, encoded: str) -> str: - enc = base64.b64decode(encoded.encode('utf-8'), altchars=b"._") - iv = enc[:AES.block_size] + enc = base64.b64decode(encoded.encode("utf-8"), altchars=b"._") + iv = enc[: AES.block_size] cipher = AES.new(self.__key, AES.MODE_CBC, iv) - return self._unpad(cipher.decrypt(enc[AES.block_size:]).decode('utf-8')) + return self._unpad(cipher.decrypt(enc[AES.block_size :]).decode("utf-8")) diff --git a/bemani/common/card.py b/bemani/common/card.py index 7a7033e..0b9c722 100644 --- a/bemani/common/card.py +++ b/bemani/common/card.py @@ -15,182 +15,638 @@ class CardCipher: """ KEY: Final[List[int]] = [ - 0x20d0d03c, 0x868ecb41, 0xbcd89c84, 0x4c0e0d0d, - 0x84fc30ac, 0x4cc1890e, 0xfc5418a4, 0x02c50f44, - 0x68acb4e0, 0x06cd4a4e, 0xcc28906c, 0x4f0c8ac0, - 0xb03ca468, 0x884ac7c4, 0x389490d8, 0xcf80c6c2, - 0x58d87404, 0xc48ec444, 0xb4e83c50, 0x498d0147, - 0x64f454c0, 0x4c4701c8, 0xec302cc4, 0xc6c949c1, - 0xc84c00f0, 0xcdcc49cc, 0x883c5cf4, 0x8b0fcb80, - 0x703cc0b0, 0xcb820a8d, 0x78804c8c, 0x4fca830e, - 0x80d0f03c, 0x8ec84f8c, 0x98c89c4c, 0xc80d878f, - 0x54bc949c, 0xc801c5ce, 0x749078dc, 0xc3c80d46, - 0x2c8070f0, 0x0cce4dcf, 0x8c3874e4, 0x8d448ac3, - 0x987cac70, 0xc0c20ac5, 0x288cfc78, 0xc28543c8, - 0x4c8c7434, 0xc50e4f8d, 0x8468f4b4, 0xcb4a0307, - 0x2854dc98, 0x48430b45, 0x6858fce8, 0x4681cd49, - 0xd04808ec, 0x458d0fcb, 0xe0a48ce4, 0x880f8fce, - 0x7434b8fc, 0xce080a8e, 0x5860fc6c, 0x46c886cc, - 0xd01098a4, 0xce090b8c, 0x1044cc2c, 0x86898e0f, - 0xd0809c3c, 0x4a05860f, 0x54b4f80c, 0x4008870e, - 0x1480b88c, 0x0ac8854f, 0x1c9034cc, 0x08444c4e, - 0x0cb83c64, 0x41c08cc6, 0x1c083460, 0xc0c603ce, - 0x2ca0645c, 0x818246cb, 0x0408e454, 0xc5464487, - 0x88607c18, 0xc1424187, 0x284c7c90, 0xc1030509, - 0x40486c94, 0x4603494b, 0xe0404ce4, 0x4109094d, - 0x60443ce4, 0x4c0b8b8d, 0xe054e8bc, 0x02008e89, + 0x20D0D03C, + 0x868ECB41, + 0xBCD89C84, + 0x4C0E0D0D, + 0x84FC30AC, + 0x4CC1890E, + 0xFC5418A4, + 0x02C50F44, + 0x68ACB4E0, + 0x06CD4A4E, + 0xCC28906C, + 0x4F0C8AC0, + 0xB03CA468, + 0x884AC7C4, + 0x389490D8, + 0xCF80C6C2, + 0x58D87404, + 0xC48EC444, + 0xB4E83C50, + 0x498D0147, + 0x64F454C0, + 0x4C4701C8, + 0xEC302CC4, + 0xC6C949C1, + 0xC84C00F0, + 0xCDCC49CC, + 0x883C5CF4, + 0x8B0FCB80, + 0x703CC0B0, + 0xCB820A8D, + 0x78804C8C, + 0x4FCA830E, + 0x80D0F03C, + 0x8EC84F8C, + 0x98C89C4C, + 0xC80D878F, + 0x54BC949C, + 0xC801C5CE, + 0x749078DC, + 0xC3C80D46, + 0x2C8070F0, + 0x0CCE4DCF, + 0x8C3874E4, + 0x8D448AC3, + 0x987CAC70, + 0xC0C20AC5, + 0x288CFC78, + 0xC28543C8, + 0x4C8C7434, + 0xC50E4F8D, + 0x8468F4B4, + 0xCB4A0307, + 0x2854DC98, + 0x48430B45, + 0x6858FCE8, + 0x4681CD49, + 0xD04808EC, + 0x458D0FCB, + 0xE0A48CE4, + 0x880F8FCE, + 0x7434B8FC, + 0xCE080A8E, + 0x5860FC6C, + 0x46C886CC, + 0xD01098A4, + 0xCE090B8C, + 0x1044CC2C, + 0x86898E0F, + 0xD0809C3C, + 0x4A05860F, + 0x54B4F80C, + 0x4008870E, + 0x1480B88C, + 0x0AC8854F, + 0x1C9034CC, + 0x08444C4E, + 0x0CB83C64, + 0x41C08CC6, + 0x1C083460, + 0xC0C603CE, + 0x2CA0645C, + 0x818246CB, + 0x0408E454, + 0xC5464487, + 0x88607C18, + 0xC1424187, + 0x284C7C90, + 0xC1030509, + 0x40486C94, + 0x4603494B, + 0xE0404CE4, + 0x4109094D, + 0x60443CE4, + 0x4C0B8B8D, + 0xE054E8BC, + 0x02008E89, ] LUT_A0: Final[List[int]] = [ - 0x02080008, 0x02082000, 0x00002008, 0x00000000, - 0x02002000, 0x00080008, 0x02080000, 0x02082008, - 0x00000008, 0x02000000, 0x00082000, 0x00002008, - 0x00082008, 0x02002008, 0x02000008, 0x02080000, - 0x00002000, 0x00082008, 0x00080008, 0x02002000, - 0x02082008, 0x02000008, 0x00000000, 0x00082000, - 0x02000000, 0x00080000, 0x02002008, 0x02080008, - 0x00080000, 0x00002000, 0x02082000, 0x00000008, - 0x00080000, 0x00002000, 0x02000008, 0x02082008, - 0x00002008, 0x02000000, 0x00000000, 0x00082000, - 0x02080008, 0x02002008, 0x02002000, 0x00080008, - 0x02082000, 0x00000008, 0x00080008, 0x02002000, - 0x02082008, 0x00080000, 0x02080000, 0x02000008, - 0x00082000, 0x00002008, 0x02002008, 0x02080000, - 0x00000008, 0x02082000, 0x00082008, 0x00000000, - 0x02000000, 0x02080008, 0x00002000, 0x00082008, + 0x02080008, + 0x02082000, + 0x00002008, + 0x00000000, + 0x02002000, + 0x00080008, + 0x02080000, + 0x02082008, + 0x00000008, + 0x02000000, + 0x00082000, + 0x00002008, + 0x00082008, + 0x02002008, + 0x02000008, + 0x02080000, + 0x00002000, + 0x00082008, + 0x00080008, + 0x02002000, + 0x02082008, + 0x02000008, + 0x00000000, + 0x00082000, + 0x02000000, + 0x00080000, + 0x02002008, + 0x02080008, + 0x00080000, + 0x00002000, + 0x02082000, + 0x00000008, + 0x00080000, + 0x00002000, + 0x02000008, + 0x02082008, + 0x00002008, + 0x02000000, + 0x00000000, + 0x00082000, + 0x02080008, + 0x02002008, + 0x02002000, + 0x00080008, + 0x02082000, + 0x00000008, + 0x00080008, + 0x02002000, + 0x02082008, + 0x00080000, + 0x02080000, + 0x02000008, + 0x00082000, + 0x00002008, + 0x02002008, + 0x02080000, + 0x00000008, + 0x02082000, + 0x00082008, + 0x00000000, + 0x02000000, + 0x02080008, + 0x00002000, + 0x00082008, ] LUT_A1: Final[List[int]] = [ - 0x08000004, 0x00020004, 0x00000000, 0x08020200, - 0x00020004, 0x00000200, 0x08000204, 0x00020000, - 0x00000204, 0x08020204, 0x00020200, 0x08000000, - 0x08000200, 0x08000004, 0x08020000, 0x00020204, - 0x00020000, 0x08000204, 0x08020004, 0x00000000, - 0x00000200, 0x00000004, 0x08020200, 0x08020004, - 0x08020204, 0x08020000, 0x08000000, 0x00000204, - 0x00000004, 0x00020200, 0x00020204, 0x08000200, - 0x00000204, 0x08000000, 0x08000200, 0x00020204, - 0x08020200, 0x00020004, 0x00000000, 0x08000200, - 0x08000000, 0x00000200, 0x08020004, 0x00020000, - 0x00020004, 0x08020204, 0x00020200, 0x00000004, - 0x08020204, 0x00020200, 0x00020000, 0x08000204, - 0x08000004, 0x08020000, 0x00020204, 0x00000000, - 0x00000200, 0x08000004, 0x08000204, 0x08020200, - 0x08020000, 0x00000204, 0x00000004, 0x08020004, + 0x08000004, + 0x00020004, + 0x00000000, + 0x08020200, + 0x00020004, + 0x00000200, + 0x08000204, + 0x00020000, + 0x00000204, + 0x08020204, + 0x00020200, + 0x08000000, + 0x08000200, + 0x08000004, + 0x08020000, + 0x00020204, + 0x00020000, + 0x08000204, + 0x08020004, + 0x00000000, + 0x00000200, + 0x00000004, + 0x08020200, + 0x08020004, + 0x08020204, + 0x08020000, + 0x08000000, + 0x00000204, + 0x00000004, + 0x00020200, + 0x00020204, + 0x08000200, + 0x00000204, + 0x08000000, + 0x08000200, + 0x00020204, + 0x08020200, + 0x00020004, + 0x00000000, + 0x08000200, + 0x08000000, + 0x00000200, + 0x08020004, + 0x00020000, + 0x00020004, + 0x08020204, + 0x00020200, + 0x00000004, + 0x08020204, + 0x00020200, + 0x00020000, + 0x08000204, + 0x08000004, + 0x08020000, + 0x00020204, + 0x00000000, + 0x00000200, + 0x08000004, + 0x08000204, + 0x08020200, + 0x08020000, + 0x00000204, + 0x00000004, + 0x08020004, ] LUT_A2: Final[List[int]] = [ - 0x80040100, 0x01000100, 0x80000000, 0x81040100, - 0x00000000, 0x01040000, 0x81000100, 0x80040000, - 0x01040100, 0x81000000, 0x01000000, 0x80000100, - 0x81000000, 0x80040100, 0x00040000, 0x01000000, - 0x81040000, 0x00040100, 0x00000100, 0x80000000, - 0x00040100, 0x81000100, 0x01040000, 0x00000100, - 0x80000100, 0x00000000, 0x80040000, 0x01040100, - 0x01000100, 0x81040000, 0x81040100, 0x00040000, - 0x81040000, 0x80000100, 0x00040000, 0x81000000, - 0x00040100, 0x01000100, 0x80000000, 0x01040000, - 0x81000100, 0x00000000, 0x00000100, 0x80040000, - 0x00000000, 0x81040000, 0x01040100, 0x00000100, - 0x01000000, 0x81040100, 0x80040100, 0x00040000, - 0x81040100, 0x80000000, 0x01000100, 0x80040100, - 0x80040000, 0x00040100, 0x01040000, 0x81000100, - 0x80000100, 0x01000000, 0x81000000, 0x01040100, + 0x80040100, + 0x01000100, + 0x80000000, + 0x81040100, + 0x00000000, + 0x01040000, + 0x81000100, + 0x80040000, + 0x01040100, + 0x81000000, + 0x01000000, + 0x80000100, + 0x81000000, + 0x80040100, + 0x00040000, + 0x01000000, + 0x81040000, + 0x00040100, + 0x00000100, + 0x80000000, + 0x00040100, + 0x81000100, + 0x01040000, + 0x00000100, + 0x80000100, + 0x00000000, + 0x80040000, + 0x01040100, + 0x01000100, + 0x81040000, + 0x81040100, + 0x00040000, + 0x81040000, + 0x80000100, + 0x00040000, + 0x81000000, + 0x00040100, + 0x01000100, + 0x80000000, + 0x01040000, + 0x81000100, + 0x00000000, + 0x00000100, + 0x80040000, + 0x00000000, + 0x81040000, + 0x01040100, + 0x00000100, + 0x01000000, + 0x81040100, + 0x80040100, + 0x00040000, + 0x81040100, + 0x80000000, + 0x01000100, + 0x80040100, + 0x80040000, + 0x00040100, + 0x01040000, + 0x81000100, + 0x80000100, + 0x01000000, + 0x81000000, + 0x01040100, ] LUT_A3: Final[List[int]] = [ - 0x04010801, 0x00000000, 0x00010800, 0x04010000, - 0x04000001, 0x00000801, 0x04000800, 0x00010800, - 0x00000800, 0x04010001, 0x00000001, 0x04000800, - 0x00010001, 0x04010800, 0x04010000, 0x00000001, - 0x00010000, 0x04000801, 0x04010001, 0x00000800, - 0x00010801, 0x04000000, 0x00000000, 0x00010001, - 0x04000801, 0x00010801, 0x04010800, 0x04000001, - 0x04000000, 0x00010000, 0x00000801, 0x04010801, - 0x00010001, 0x04010800, 0x04000800, 0x00010801, - 0x04010801, 0x00010001, 0x04000001, 0x00000000, - 0x04000000, 0x00000801, 0x00010000, 0x04010001, - 0x00000800, 0x04000000, 0x00010801, 0x04000801, - 0x04010800, 0x00000800, 0x00000000, 0x04000001, - 0x00000001, 0x04010801, 0x00010800, 0x04010000, - 0x04010001, 0x00010000, 0x00000801, 0x04000800, - 0x04000801, 0x00000001, 0x04010000, 0x00010800, + 0x04010801, + 0x00000000, + 0x00010800, + 0x04010000, + 0x04000001, + 0x00000801, + 0x04000800, + 0x00010800, + 0x00000800, + 0x04010001, + 0x00000001, + 0x04000800, + 0x00010001, + 0x04010800, + 0x04010000, + 0x00000001, + 0x00010000, + 0x04000801, + 0x04010001, + 0x00000800, + 0x00010801, + 0x04000000, + 0x00000000, + 0x00010001, + 0x04000801, + 0x00010801, + 0x04010800, + 0x04000001, + 0x04000000, + 0x00010000, + 0x00000801, + 0x04010801, + 0x00010001, + 0x04010800, + 0x04000800, + 0x00010801, + 0x04010801, + 0x00010001, + 0x04000001, + 0x00000000, + 0x04000000, + 0x00000801, + 0x00010000, + 0x04010001, + 0x00000800, + 0x04000000, + 0x00010801, + 0x04000801, + 0x04010800, + 0x00000800, + 0x00000000, + 0x04000001, + 0x00000001, + 0x04010801, + 0x00010800, + 0x04010000, + 0x04010001, + 0x00010000, + 0x00000801, + 0x04000800, + 0x04000801, + 0x00000001, + 0x04010000, + 0x00010800, ] LUT_B0: Final[List[int]] = [ - 0x00000400, 0x00000020, 0x00100020, 0x40100000, - 0x40100420, 0x40000400, 0x00000420, 0x00000000, - 0x00100000, 0x40100020, 0x40000020, 0x00100400, - 0x40000000, 0x00100420, 0x00100400, 0x40000020, - 0x40100020, 0x00000400, 0x40000400, 0x40100420, - 0x00000000, 0x00100020, 0x40100000, 0x00000420, - 0x40100400, 0x40000420, 0x00100420, 0x40000000, - 0x40000420, 0x40100400, 0x00000020, 0x00100000, - 0x40000420, 0x00100400, 0x40100400, 0x40000020, - 0x00000400, 0x00000020, 0x00100000, 0x40100400, - 0x40100020, 0x40000420, 0x00000420, 0x00000000, - 0x00000020, 0x40100000, 0x40000000, 0x00100020, - 0x00000000, 0x40100020, 0x00100020, 0x00000420, - 0x40000020, 0x00000400, 0x40100420, 0x00100000, - 0x00100420, 0x40000000, 0x40000400, 0x40100420, - 0x40100000, 0x00100420, 0x00100400, 0x40000400, + 0x00000400, + 0x00000020, + 0x00100020, + 0x40100000, + 0x40100420, + 0x40000400, + 0x00000420, + 0x00000000, + 0x00100000, + 0x40100020, + 0x40000020, + 0x00100400, + 0x40000000, + 0x00100420, + 0x00100400, + 0x40000020, + 0x40100020, + 0x00000400, + 0x40000400, + 0x40100420, + 0x00000000, + 0x00100020, + 0x40100000, + 0x00000420, + 0x40100400, + 0x40000420, + 0x00100420, + 0x40000000, + 0x40000420, + 0x40100400, + 0x00000020, + 0x00100000, + 0x40000420, + 0x00100400, + 0x40100400, + 0x40000020, + 0x00000400, + 0x00000020, + 0x00100000, + 0x40100400, + 0x40100020, + 0x40000420, + 0x00000420, + 0x00000000, + 0x00000020, + 0x40100000, + 0x40000000, + 0x00100020, + 0x00000000, + 0x40100020, + 0x00100020, + 0x00000420, + 0x40000020, + 0x00000400, + 0x40100420, + 0x00100000, + 0x00100420, + 0x40000000, + 0x40000400, + 0x40100420, + 0x40100000, + 0x00100420, + 0x00100400, + 0x40000400, ] LUT_B1: Final[List[int]] = [ - 0x00800000, 0x00001000, 0x00000040, 0x00801042, - 0x00801002, 0x00800040, 0x00001042, 0x00801000, - 0x00001000, 0x00000002, 0x00800002, 0x00001040, - 0x00800042, 0x00801002, 0x00801040, 0x00000000, - 0x00001040, 0x00800000, 0x00001002, 0x00000042, - 0x00800040, 0x00001042, 0x00000000, 0x00800002, - 0x00000002, 0x00800042, 0x00801042, 0x00001002, - 0x00801000, 0x00000040, 0x00000042, 0x00801040, - 0x00801040, 0x00800042, 0x00001002, 0x00801000, - 0x00001000, 0x00000002, 0x00800002, 0x00800040, - 0x00800000, 0x00001040, 0x00801042, 0x00000000, - 0x00001042, 0x00800000, 0x00000040, 0x00001002, - 0x00800042, 0x00000040, 0x00000000, 0x00801042, - 0x00801002, 0x00801040, 0x00000042, 0x00001000, - 0x00001040, 0x00801002, 0x00800040, 0x00000042, - 0x00000002, 0x00001042, 0x00801000, 0x00800002, + 0x00800000, + 0x00001000, + 0x00000040, + 0x00801042, + 0x00801002, + 0x00800040, + 0x00001042, + 0x00801000, + 0x00001000, + 0x00000002, + 0x00800002, + 0x00001040, + 0x00800042, + 0x00801002, + 0x00801040, + 0x00000000, + 0x00001040, + 0x00800000, + 0x00001002, + 0x00000042, + 0x00800040, + 0x00001042, + 0x00000000, + 0x00800002, + 0x00000002, + 0x00800042, + 0x00801042, + 0x00001002, + 0x00801000, + 0x00000040, + 0x00000042, + 0x00801040, + 0x00801040, + 0x00800042, + 0x00001002, + 0x00801000, + 0x00001000, + 0x00000002, + 0x00800002, + 0x00800040, + 0x00800000, + 0x00001040, + 0x00801042, + 0x00000000, + 0x00001042, + 0x00800000, + 0x00000040, + 0x00001002, + 0x00800042, + 0x00000040, + 0x00000000, + 0x00801042, + 0x00801002, + 0x00801040, + 0x00000042, + 0x00001000, + 0x00001040, + 0x00801002, + 0x00800040, + 0x00000042, + 0x00000002, + 0x00001042, + 0x00801000, + 0x00800002, ] LUT_B2: Final[List[int]] = [ - 0x10400000, 0x00404010, 0x00000010, 0x10400010, - 0x10004000, 0x00400000, 0x10400010, 0x00004010, - 0x00400010, 0x00004000, 0x00404000, 0x10000000, - 0x10404010, 0x10000010, 0x10000000, 0x10404000, - 0x00000000, 0x10004000, 0x00404010, 0x00000010, - 0x10000010, 0x10404010, 0x00004000, 0x10400000, - 0x10404000, 0x00400010, 0x10004010, 0x00404000, - 0x00004010, 0x00000000, 0x00400000, 0x10004010, - 0x00404010, 0x00000010, 0x10000000, 0x00004000, - 0x10000010, 0x10004000, 0x00404000, 0x10400010, - 0x00000000, 0x00404010, 0x00004010, 0x10404000, - 0x10004000, 0x00400000, 0x10404010, 0x10000000, - 0x10004010, 0x10400000, 0x00400000, 0x10404010, - 0x00004000, 0x00400010, 0x10400010, 0x00004010, - 0x00400010, 0x00000000, 0x10404000, 0x10000010, - 0x10400000, 0x10004010, 0x00000010, 0x00404000, + 0x10400000, + 0x00404010, + 0x00000010, + 0x10400010, + 0x10004000, + 0x00400000, + 0x10400010, + 0x00004010, + 0x00400010, + 0x00004000, + 0x00404000, + 0x10000000, + 0x10404010, + 0x10000010, + 0x10000000, + 0x10404000, + 0x00000000, + 0x10004000, + 0x00404010, + 0x00000010, + 0x10000010, + 0x10404010, + 0x00004000, + 0x10400000, + 0x10404000, + 0x00400010, + 0x10004010, + 0x00404000, + 0x00004010, + 0x00000000, + 0x00400000, + 0x10004010, + 0x00404010, + 0x00000010, + 0x10000000, + 0x00004000, + 0x10000010, + 0x10004000, + 0x00404000, + 0x10400010, + 0x00000000, + 0x00404010, + 0x00004010, + 0x10404000, + 0x10004000, + 0x00400000, + 0x10404010, + 0x10000000, + 0x10004010, + 0x10400000, + 0x00400000, + 0x10404010, + 0x00004000, + 0x00400010, + 0x10400010, + 0x00004010, + 0x00400010, + 0x00000000, + 0x10404000, + 0x10000010, + 0x10400000, + 0x10004010, + 0x00000010, + 0x00404000, ] LUT_B3: Final[List[int]] = [ - 0x00208080, 0x00008000, 0x20200000, 0x20208080, - 0x00200000, 0x20008080, 0x20008000, 0x20200000, - 0x20008080, 0x00208080, 0x00208000, 0x20000080, - 0x20200080, 0x00200000, 0x00000000, 0x20008000, - 0x00008000, 0x20000000, 0x00200080, 0x00008080, - 0x20208080, 0x00208000, 0x20000080, 0x00200080, - 0x20000000, 0x00000080, 0x00008080, 0x20208000, - 0x00000080, 0x20200080, 0x20208000, 0x00000000, - 0x00000000, 0x20208080, 0x00200080, 0x20008000, - 0x00208080, 0x00008000, 0x20000080, 0x00200080, - 0x20208000, 0x00000080, 0x00008080, 0x20200000, - 0x20008080, 0x20000000, 0x20200000, 0x00208000, - 0x20208080, 0x00008080, 0x00208000, 0x20200080, - 0x00200000, 0x20000080, 0x20008000, 0x00000000, - 0x00008000, 0x00200000, 0x20200080, 0x00208080, - 0x20000000, 0x20208000, 0x00000080, 0x20008080, + 0x00208080, + 0x00008000, + 0x20200000, + 0x20208080, + 0x00200000, + 0x20008080, + 0x20008000, + 0x20200000, + 0x20008080, + 0x00208080, + 0x00208000, + 0x20000080, + 0x20200080, + 0x00200000, + 0x00000000, + 0x20008000, + 0x00008000, + 0x20000000, + 0x00200080, + 0x00008080, + 0x20208080, + 0x00208000, + 0x20000080, + 0x00200080, + 0x20000000, + 0x00000080, + 0x00008080, + 0x20208000, + 0x00000080, + 0x20200080, + 0x20208000, + 0x00000000, + 0x00000000, + 0x20208080, + 0x00200080, + 0x20008000, + 0x00208080, + 0x00008000, + 0x20000080, + 0x00200080, + 0x20208000, + 0x00000080, + 0x00008080, + 0x20200000, + 0x20008080, + 0x20000000, + 0x20200000, + 0x00208000, + 0x20208080, + 0x00008080, + 0x00208000, + 0x20200080, + 0x00200000, + 0x20000080, + 0x20008000, + 0x00000000, + 0x00008000, + 0x00200000, + 0x20200080, + 0x00208080, + 0x20000000, + 0x20208000, + 0x00000080, + 0x20008080, ] VALID_CHARS: Final[str] = "0123456789ABCDEFGHJKLMNPRSTUWXYZ" @@ -201,11 +657,11 @@ class CardCipher: @staticmethod def __type_from_cardid(cardid: str) -> int: - if cardid[:2].upper() == 'E0': + if cardid[:2].upper() == "E0": return 1 - if cardid[:2].upper() == '01': + if cardid[:2].upper() == "01": return 2 - raise CardCipherException('Unrecognized card type') + raise CardCipherException("Unrecognized card type") @staticmethod def encode(cardid: str) -> str: @@ -221,10 +677,10 @@ class CardCipher: """ if len(cardid) != 16: raise CardCipherException( - f'Expected 16-character card ID, got {len(cardid)}', + f"Expected 16-character card ID, got {len(cardid)}", ) - cardint = [int(cardid[i:(i + 2)], 16) for i in range(0, len(cardid), 2)] + cardint = [int(cardid[i : (i + 2)], 16) for i in range(0, len(cardid), 2)] # Reverse bytes reverse = [0] * 8 @@ -242,11 +698,11 @@ class CardCipher: groups = [0] * 16 for i in range(0, 13): groups[i] = ( - (bits[i * 5 + 0] << 4) | - (bits[i * 5 + 1] << 3) | - (bits[i * 5 + 2] << 2) | - (bits[i * 5 + 3] << 1) | - (bits[i * 5 + 4] << 0) + (bits[i * 5 + 0] << 4) + | (bits[i * 5 + 1] << 3) + | (bits[i * 5 + 2] << 2) + | (bits[i * 5 + 3] << 1) + | (bits[i * 5 + 4] << 0) ) # Smear 13 groups out into 14 groups @@ -261,7 +717,7 @@ class CardCipher: groups[15] = CardCipher.__checksum(groups) # Convert to chars and return - return ''.join([CardCipher.VALID_CHARS[i] for i in groups]) + return "".join([CardCipher.VALID_CHARS[i] for i in groups]) @staticmethod def decode(cardid: str) -> str: @@ -277,21 +733,21 @@ class CardCipher: 16 digit card ID (hex values stored as string). """ # First sanitize the input - cardid = cardid.replace(' ', '') - cardid = cardid.replace('-', '') + cardid = cardid.replace(" ", "") + cardid = cardid.replace("-", "") cardid = cardid.upper() for c in CardCipher.CONV_CHARS: cardid = cardid.replace(c, CardCipher.CONV_CHARS[c]) if len(cardid) != 16: raise CardCipherException( - f'Expected 16-character card ID, got {len(cardid)}', + f"Expected 16-character card ID, got {len(cardid)}", ) for c in cardid: if c not in CardCipher.VALID_CHARS: raise CardCipherException( - f'Got unexpected character {c} in card ID', + f"Got unexpected character {c} in card ID", ) # Convert chars to groups @@ -304,14 +760,10 @@ class CardCipher: break # Verify scheme and checksum - if (groups[14] != 1 and groups[14] != 2): - raise CardCipherException( - "Unrecognized card type" - ) + if groups[14] != 1 and groups[14] != 2: + raise CardCipherException("Unrecognized card type") if groups[15] != CardCipher.__checksum(groups): - raise CardCipherException( - "Bad card number" - ) + raise CardCipherException("Bad card number") # Un-smear 14 fields back into 13 for i in range(13, 0, -1): @@ -339,15 +791,13 @@ class CardCipher: def tohex(x: int) -> str: h = hex(x)[2:] while len(h) < 2: - h = '0' + h + h = "0" + h return h.upper() # Convert to a string, verify we have the same type - finalvalue = ''.join([tohex(v) for v in reverse]) + finalvalue = "".join([tohex(v) for v in reverse]) if groups[14] != CardCipher.__type_from_cardid(finalvalue): - raise CardCipherException( - "Card type mismatch" - ) + raise CardCipherException("Card type mismatch") return finalvalue @staticmethod @@ -366,15 +816,21 @@ class CardCipher: def _encode(inbytes: bytes) -> bytes: if len(inbytes) != 8: raise CardCipherException( - f'Expected 8-byte input, got {len(inbytes)}', + f"Expected 8-byte input, got {len(inbytes)}", ) inp = [b for b in inbytes] out = [0] * 8 - CardCipher.__from_int(out, CardCipher.__operatorA(0x00, CardCipher.__to_int(inp))) - CardCipher.__from_int(out, CardCipher.__operatorB(0x20, CardCipher.__to_int(out))) - CardCipher.__from_int(out, CardCipher.__operatorA(0x40, CardCipher.__to_int(out))) + CardCipher.__from_int( + out, CardCipher.__operatorA(0x00, CardCipher.__to_int(inp)) + ) + CardCipher.__from_int( + out, CardCipher.__operatorB(0x20, CardCipher.__to_int(out)) + ) + CardCipher.__from_int( + out, CardCipher.__operatorA(0x40, CardCipher.__to_int(out)) + ) return bytes(out) @@ -382,32 +838,38 @@ class CardCipher: def _decode(inbytes: bytes) -> bytes: if len(inbytes) != 8: raise CardCipherException( - f'Expected 8-byte input, got {len(inbytes)}', + f"Expected 8-byte input, got {len(inbytes)}", ) inp = [b for b in inbytes] out = [0] * 8 - CardCipher.__from_int(out, CardCipher.__operatorB(0x40, CardCipher.__to_int(inp))) - CardCipher.__from_int(out, CardCipher.__operatorA(0x20, CardCipher.__to_int(out))) - CardCipher.__from_int(out, CardCipher.__operatorB(0x00, CardCipher.__to_int(out))) + CardCipher.__from_int( + out, CardCipher.__operatorB(0x40, CardCipher.__to_int(inp)) + ) + CardCipher.__from_int( + out, CardCipher.__operatorA(0x20, CardCipher.__to_int(out)) + ) + CardCipher.__from_int( + out, CardCipher.__operatorB(0x00, CardCipher.__to_int(out)) + ) return bytes(out) @staticmethod def __to_int(data: List[int]) -> int: inX = ( - (data[0] & 0xFF) | - ((data[1] & 0xFF) << 8) | - ((data[2] & 0xFF) << 16) | - ((data[3] & 0xFF) << 24) + (data[0] & 0xFF) + | ((data[1] & 0xFF) << 8) + | ((data[2] & 0xFF) << 16) + | ((data[3] & 0xFF) << 24) ) inY = ( - (data[4] & 0xFF) | - ((data[5] & 0xFF) << 8) | - ((data[6] & 0xFF) << 16) | - ((data[7] & 0xFF) << 24) + (data[4] & 0xFF) + | ((data[5] & 0xFF) << 8) + | ((data[6] & 0xFF) << 16) + | ((data[7] & 0xFF) << 24) ) v7 = ((((inX ^ (inY >> 4)) & 0xF0F0F0F) << 4) ^ inY) & 0xFFFFFFFF @@ -477,27 +939,27 @@ class CardCipher: v20 = CardCipher.__ror(v3 ^ CardCipher.KEY[off + i + 1], 28) v4 ^= ( - CardCipher.LUT_B0[(v20 >> 26) & 0x3F] ^ - CardCipher.LUT_B1[(v20 >> 18) & 0x3F] ^ - CardCipher.LUT_B2[(v20 >> 10) & 0x3F] ^ - CardCipher.LUT_B3[(v20 >> 2) & 0x3F] ^ - CardCipher.LUT_A0[((v3 ^ CardCipher.KEY[off + i]) >> 26) & 0x3F] ^ - CardCipher.LUT_A1[((v3 ^ CardCipher.KEY[off + i]) >> 18) & 0x3F] ^ - CardCipher.LUT_A2[((v3 ^ CardCipher.KEY[off + i]) >> 10) & 0x3F] ^ - CardCipher.LUT_A3[((v3 ^ CardCipher.KEY[off + i]) >> 2) & 0x3F] + CardCipher.LUT_B0[(v20 >> 26) & 0x3F] + ^ CardCipher.LUT_B1[(v20 >> 18) & 0x3F] + ^ CardCipher.LUT_B2[(v20 >> 10) & 0x3F] + ^ CardCipher.LUT_B3[(v20 >> 2) & 0x3F] + ^ CardCipher.LUT_A0[((v3 ^ CardCipher.KEY[off + i]) >> 26) & 0x3F] + ^ CardCipher.LUT_A1[((v3 ^ CardCipher.KEY[off + i]) >> 18) & 0x3F] + ^ CardCipher.LUT_A2[((v3 ^ CardCipher.KEY[off + i]) >> 10) & 0x3F] + ^ CardCipher.LUT_A3[((v3 ^ CardCipher.KEY[off + i]) >> 2) & 0x3F] ) v21 = CardCipher.__ror(v4 ^ CardCipher.KEY[off + i + 3], 28) v3 ^= ( - CardCipher.LUT_B0[(v21 >> 26) & 0x3F] ^ - CardCipher.LUT_B1[(v21 >> 18) & 0x3F] ^ - CardCipher.LUT_B2[(v21 >> 10) & 0x3F] ^ - CardCipher.LUT_B3[(v21 >> 2) & 0x3F] ^ - CardCipher.LUT_A0[((v4 ^ CardCipher.KEY[off + i + 2]) >> 26) & 0x3F] ^ - CardCipher.LUT_A1[((v4 ^ CardCipher.KEY[off + i + 2]) >> 18) & 0x3F] ^ - CardCipher.LUT_A2[((v4 ^ CardCipher.KEY[off + i + 2]) >> 10) & 0x3F] ^ - CardCipher.LUT_A3[((v4 ^ CardCipher.KEY[off + i + 2]) >> 2) & 0x3F] + CardCipher.LUT_B0[(v21 >> 26) & 0x3F] + ^ CardCipher.LUT_B1[(v21 >> 18) & 0x3F] + ^ CardCipher.LUT_B2[(v21 >> 10) & 0x3F] + ^ CardCipher.LUT_B3[(v21 >> 2) & 0x3F] + ^ CardCipher.LUT_A0[((v4 ^ CardCipher.KEY[off + i + 2]) >> 26) & 0x3F] + ^ CardCipher.LUT_A1[((v4 ^ CardCipher.KEY[off + i + 2]) >> 18) & 0x3F] + ^ CardCipher.LUT_A2[((v4 ^ CardCipher.KEY[off + i + 2]) >> 10) & 0x3F] + ^ CardCipher.LUT_A3[((v4 ^ CardCipher.KEY[off + i + 2]) >> 2) & 0x3F] ) return ((v3 & 0xFFFFFFFF) << 32) | (v4 & 0xFFFFFFFF) @@ -511,27 +973,27 @@ class CardCipher: v20 = CardCipher.__ror(v3 ^ CardCipher.KEY[off + 31 - i], 28) v4 ^= ( - CardCipher.LUT_A0[((v3 ^ CardCipher.KEY[off + 30 - i]) >> 26) & 0x3F] ^ - CardCipher.LUT_A1[((v3 ^ CardCipher.KEY[off + 30 - i]) >> 18) & 0x3F] ^ - CardCipher.LUT_A2[((v3 ^ CardCipher.KEY[off + 30 - i]) >> 10) & 0x3F] ^ - CardCipher.LUT_A3[((v3 ^ CardCipher.KEY[off + 30 - i]) >> 2) & 0x3F] ^ - CardCipher.LUT_B0[(v20 >> 26) & 0x3F] ^ - CardCipher.LUT_B1[(v20 >> 18) & 0x3F] ^ - CardCipher.LUT_B2[(v20 >> 10) & 0x3F] ^ - CardCipher.LUT_B3[(v20 >> 2) & 0x3F] + CardCipher.LUT_A0[((v3 ^ CardCipher.KEY[off + 30 - i]) >> 26) & 0x3F] + ^ CardCipher.LUT_A1[((v3 ^ CardCipher.KEY[off + 30 - i]) >> 18) & 0x3F] + ^ CardCipher.LUT_A2[((v3 ^ CardCipher.KEY[off + 30 - i]) >> 10) & 0x3F] + ^ CardCipher.LUT_A3[((v3 ^ CardCipher.KEY[off + 30 - i]) >> 2) & 0x3F] + ^ CardCipher.LUT_B0[(v20 >> 26) & 0x3F] + ^ CardCipher.LUT_B1[(v20 >> 18) & 0x3F] + ^ CardCipher.LUT_B2[(v20 >> 10) & 0x3F] + ^ CardCipher.LUT_B3[(v20 >> 2) & 0x3F] ) v21 = CardCipher.__ror(v4 ^ CardCipher.KEY[off + 29 - i], 28) v3 ^= ( - CardCipher.LUT_A0[((v4 ^ CardCipher.KEY[off + 28 - i]) >> 26) & 0x3F] ^ - CardCipher.LUT_A1[((v4 ^ CardCipher.KEY[off + 28 - i]) >> 18) & 0x3F] ^ - CardCipher.LUT_A2[((v4 ^ CardCipher.KEY[off + 28 - i]) >> 10) & 0x3F] ^ - CardCipher.LUT_A3[((v4 ^ CardCipher.KEY[off + 28 - i]) >> 2) & 0x3F] ^ - CardCipher.LUT_B0[(v21 >> 26) & 0x3F] ^ - CardCipher.LUT_B1[(v21 >> 18) & 0x3F] ^ - CardCipher.LUT_B2[(v21 >> 10) & 0x3F] ^ - CardCipher.LUT_B3[(v21 >> 2) & 0x3F] + CardCipher.LUT_A0[((v4 ^ CardCipher.KEY[off + 28 - i]) >> 26) & 0x3F] + ^ CardCipher.LUT_A1[((v4 ^ CardCipher.KEY[off + 28 - i]) >> 18) & 0x3F] + ^ CardCipher.LUT_A2[((v4 ^ CardCipher.KEY[off + 28 - i]) >> 10) & 0x3F] + ^ CardCipher.LUT_A3[((v4 ^ CardCipher.KEY[off + 28 - i]) >> 2) & 0x3F] + ^ CardCipher.LUT_B0[(v21 >> 26) & 0x3F] + ^ CardCipher.LUT_B1[(v21 >> 18) & 0x3F] + ^ CardCipher.LUT_B2[(v21 >> 10) & 0x3F] + ^ CardCipher.LUT_B3[(v21 >> 2) & 0x3F] ) return ((v3 & 0xFFFFFFFF) << 32) | (v4 & 0xFFFFFFFF) diff --git a/bemani/common/constants.py b/bemani/common/constants.py index f63b4ac..56c76d0 100644 --- a/bemani/common/constants.py +++ b/bemani/common/constants.py @@ -10,16 +10,17 @@ class GameConstants(Enum): game series. They re also used verbatum in MySQL, so any column named 'game' in any of the tables should only contain one of the following strings. """ - BISHI_BASHI: Final[str] = 'bishi' - DANCE_EVOLUTION: Final[str] = 'danevo' - DDR: Final[str] = 'ddr' - IIDX: Final[str] = 'iidx' - JUBEAT: Final[str] = 'jubeat' - MGA: Final[str] = 'mga' - MUSECA: Final[str] = 'museca' - POPN_MUSIC: Final[str] = 'pnm' - REFLEC_BEAT: Final[str] = 'reflec' - SDVX: Final[str] = 'sdvx' + + BISHI_BASHI: Final[str] = "bishi" + DANCE_EVOLUTION: Final[str] = "danevo" + DDR: Final[str] = "ddr" + IIDX: Final[str] = "iidx" + JUBEAT: Final[str] = "jubeat" + MGA: Final[str] = "mga" + MUSECA: Final[str] = "museca" + POPN_MUSIC: Final[str] = "pnm" + REFLEC_BEAT: Final[str] = "reflec" + SDVX: Final[str] = "sdvx" class VersionConstants: @@ -28,6 +29,7 @@ class VersionConstants: since there are multiple keys with the same value. However, all database column named 'version' should contain only values found here. """ + BISHI_BASHI_TSBB: Final[int] = 1 DDR_1STMIX: Final[int] = 1 @@ -142,10 +144,11 @@ class APIConstants(Enum): """ The four types of IDs found in a BEMAPI request or response. """ - ID_TYPE_SERVER: Final[str] = 'server' - ID_TYPE_CARD: Final[str] = 'card' - ID_TYPE_SONG: Final[str] = 'song' - ID_TYPE_INSTANCE: Final[str] = 'instance' + + ID_TYPE_SERVER: Final[str] = "server" + ID_TYPE_CARD: Final[str] = "card" + ID_TYPE_SONG: Final[str] = "song" + ID_TYPE_INSTANCE: Final[str] = "instance" class DBConstants: @@ -230,15 +233,15 @@ class DBConstants: JUBEAT_PLAY_MEDAL_NEARLY_EXCELLENT: Final[int] = 500 JUBEAT_PLAY_MEDAL_EXCELLENT: Final[int] = 600 - MUSECA_GRADE_DEATH: Final[int] = 100 # 没 - MUSECA_GRADE_POOR: Final[int] = 200 # 拙 - MUSECA_GRADE_MEDIOCRE: Final[int] = 300 # 凡 - MUSECA_GRADE_GOOD: Final[int] = 400 # 佳 - MUSECA_GRADE_GREAT: Final[int] = 500 # 良 - MUSECA_GRADE_EXCELLENT: Final[int] = 600 # 優 - MUSECA_GRADE_SUPERB: Final[int] = 700 # 秀 + MUSECA_GRADE_DEATH: Final[int] = 100 # 没 + MUSECA_GRADE_POOR: Final[int] = 200 # 拙 + MUSECA_GRADE_MEDIOCRE: Final[int] = 300 # 凡 + MUSECA_GRADE_GOOD: Final[int] = 400 # 佳 + MUSECA_GRADE_GREAT: Final[int] = 500 # 良 + MUSECA_GRADE_EXCELLENT: Final[int] = 600 # 優 + MUSECA_GRADE_SUPERB: Final[int] = 700 # 秀 MUSECA_GRADE_MASTERPIECE: Final[int] = 800 # 傑 - MUSECA_GRADE_PERFECT: Final[int] = 900 # 傑 + MUSECA_GRADE_PERFECT: Final[int] = 900 # 傑 MUSECA_CLEAR_TYPE_FAILED: Final[int] = 100 MUSECA_CLEAR_TYPE_CLEARED: Final[int] = 200 MUSECA_CLEAR_TYPE_FULL_COMBO: Final[int] = 300 @@ -293,38 +296,38 @@ class BroadcastConstants(Enum): """ # Sections related to the player/song/etc. - DJ_NAME: Final[str] = 'DJ Name' - SONG_NAME: Final[str] = 'Song' - ARTIST_NAME: Final[str] = 'Artist' - DIFFICULTY: Final[str] = 'Difficulty' + DJ_NAME: Final[str] = "DJ Name" + SONG_NAME: Final[str] = "Song" + ARTIST_NAME: Final[str] = "Artist" + DIFFICULTY: Final[str] = "Difficulty" # Section headers. - PLAY_STATS_HEADER: Final[str] = 'Play Stats' + PLAY_STATS_HEADER: Final[str] = "Play Stats" # Stats that relate to the song, but not the current play of the song. - TARGET_EXSCORE: Final[str] = 'Target EXScore' - BEST_CLEAR_STATUS: Final[str] = 'Best Clear' + TARGET_EXSCORE: Final[str] = "Target EXScore" + BEST_CLEAR_STATUS: Final[str] = "Best Clear" # Stats that have to do with the current play of the song. - EXSCORE: Final[str] = 'Your EXScore' - CLEAR_STATUS: Final[str] = 'Clear Status' - PERFECT_GREATS: Final[str] = 'Perfect Greats' - GREATS: Final[str] = 'Greats' - GOODS: Final[str] = 'Goods' - BADS: Final[str] = 'Bads' - POORS: Final[str] = 'Poors' - COMBO_BREAKS: Final[str] = 'Combo Breaks' - SLOWS: Final[str] = 'Slow' - FASTS: Final[str] = 'Fast' - GRADE: Final[str] = 'Grade' - RATE: Final[str] = 'Score Rate' + EXSCORE: Final[str] = "Your EXScore" + CLEAR_STATUS: Final[str] = "Clear Status" + PERFECT_GREATS: Final[str] = "Perfect Greats" + GREATS: Final[str] = "Greats" + GOODS: Final[str] = "Goods" + BADS: Final[str] = "Bads" + POORS: Final[str] = "Poors" + COMBO_BREAKS: Final[str] = "Combo Breaks" + SLOWS: Final[str] = "Slow" + FASTS: Final[str] = "Fast" + GRADE: Final[str] = "Grade" + RATE: Final[str] = "Score Rate" # Added for Pnm - PLAYER_NAME: Final[str] = 'Player Name' - SCORE: Final[str] = 'Your Score' - COOLS: Final[str] = 'Cools' - COMBO: Final[str] = 'Combo' - MEDAL: Final[str] = 'Medal' + PLAYER_NAME: Final[str] = "Player Name" + SCORE: Final[str] = "Your Score" + COOLS: Final[str] = "Cools" + COMBO: Final[str] = "Combo" + MEDAL: Final[str] = "Medal" class _RegionConstants: @@ -465,57 +468,56 @@ class _RegionConstants: @property def LUT(cls) -> Dict[int, str]: return { - cls.HOKKAIDO: '北海道 (Hokkaido)', - cls.AOMORI: '青森県 (Aomori)', - cls.IWATE: '岩手県 (Iwate)', - cls.MIYAGI: '宮城県 (Miyagi)', - cls.AKITA: '秋田県 (Akita)', - cls.YAMAGATA: '山形県 (Yamagata)', - cls.FUKUSHIMA: '福島県 (Fukushima)', - cls.IBARAKI: '茨城県 (Ibaraki)', - cls.TOCHIGI: '栃木県 (Tochigi)', - cls.GUNMA: '群馬県 (Gunma)', - cls.SAITAMA: '埼玉県 (Saitama)', - cls.CHIBA: '千葉県 (Chiba)', - cls.TOKYO: '東京都 (Tokyo)', - cls.KANAGAWA: '神奈川県 (Kanagawa)', - cls.NIIGATA: '新潟県 (Niigata)', - cls.TOYAMA: '富山県 (Toyama)', - cls.ISHIKAWA: '石川県 (Ishikawa)', - cls.FUKUI: '福井県 (Fukui)', - cls.YAMANASHI: '山梨県 (Yamanashi)', - cls.NAGANO: '長野県 (Nagano)', - cls.GIFU: '岐阜県 (Gifu)', - cls.SHIZUOKA: '静岡県 (Shizuoka)', - cls.AICHI: '愛知県 (Aichi)', - cls.MIE: '三重県 (Mie)', - cls.SHIGA: '滋賀県 (Shiga)', - cls.KYOTO: '京都府 (Kyoto)', - cls.OSAKA: '大阪府 (Osaka)', - cls.HYOGO: '兵庫県 (Hyogo)', - cls.NARA: '奈良県 (Nara)', - cls.WAKAYAMA: '和歌山県 (Wakayama)', - cls.TOTTORI: '鳥取県 (Tottori)', - cls.SHIMANE: '島根県 (Shimane)', - cls.OKAYAMA: '岡山県 (Okayama)', - cls.HIROSHIMA: '広島県 (Hiroshima)', - cls.YAMAGUCHI: '山口県 (Yamaguchi)', - cls.TOKUSHIMA: '徳島県 (Tokushima)', - cls.KAGAWA: '香川県 (Kagawa)', - cls.EHIME: '愛媛県 (Ehime)', - cls.KOUCHI: '高知県 (Kochi)', - cls.FUKUOKA: '福岡県 (Fukuoka)', - cls.SAGA: '佐賀県 (Saga)', - cls.NAGASAKI: '長崎県 (Nagasaki)', - cls.KUMAMOTO: '熊本県 (Kumamoto)', - cls.OITA: '大分県 (Oita)', - cls.MIYAZAKI: '宮崎県 (Miyazaki)', - cls.KAGOSHIMA: '鹿児島県 (Kagoshima)', - cls.OKINAWA: '沖縄県 (Okinawa)', - cls.HONG_KONG: '香港 (Hong Kong)', - cls.KOREA: '韓国 (Korea)', - cls.TAIWAN: '台湾 (Taiwan)', - + cls.HOKKAIDO: "北海道 (Hokkaido)", + cls.AOMORI: "青森県 (Aomori)", + cls.IWATE: "岩手県 (Iwate)", + cls.MIYAGI: "宮城県 (Miyagi)", + cls.AKITA: "秋田県 (Akita)", + cls.YAMAGATA: "山形県 (Yamagata)", + cls.FUKUSHIMA: "福島県 (Fukushima)", + cls.IBARAKI: "茨城県 (Ibaraki)", + cls.TOCHIGI: "栃木県 (Tochigi)", + cls.GUNMA: "群馬県 (Gunma)", + cls.SAITAMA: "埼玉県 (Saitama)", + cls.CHIBA: "千葉県 (Chiba)", + cls.TOKYO: "東京都 (Tokyo)", + cls.KANAGAWA: "神奈川県 (Kanagawa)", + cls.NIIGATA: "新潟県 (Niigata)", + cls.TOYAMA: "富山県 (Toyama)", + cls.ISHIKAWA: "石川県 (Ishikawa)", + cls.FUKUI: "福井県 (Fukui)", + cls.YAMANASHI: "山梨県 (Yamanashi)", + cls.NAGANO: "長野県 (Nagano)", + cls.GIFU: "岐阜県 (Gifu)", + cls.SHIZUOKA: "静岡県 (Shizuoka)", + cls.AICHI: "愛知県 (Aichi)", + cls.MIE: "三重県 (Mie)", + cls.SHIGA: "滋賀県 (Shiga)", + cls.KYOTO: "京都府 (Kyoto)", + cls.OSAKA: "大阪府 (Osaka)", + cls.HYOGO: "兵庫県 (Hyogo)", + cls.NARA: "奈良県 (Nara)", + cls.WAKAYAMA: "和歌山県 (Wakayama)", + cls.TOTTORI: "鳥取県 (Tottori)", + cls.SHIMANE: "島根県 (Shimane)", + cls.OKAYAMA: "岡山県 (Okayama)", + cls.HIROSHIMA: "広島県 (Hiroshima)", + cls.YAMAGUCHI: "山口県 (Yamaguchi)", + cls.TOKUSHIMA: "徳島県 (Tokushima)", + cls.KAGAWA: "香川県 (Kagawa)", + cls.EHIME: "愛媛県 (Ehime)", + cls.KOUCHI: "高知県 (Kochi)", + cls.FUKUOKA: "福岡県 (Fukuoka)", + cls.SAGA: "佐賀県 (Saga)", + cls.NAGASAKI: "長崎県 (Nagasaki)", + cls.KUMAMOTO: "熊本県 (Kumamoto)", + cls.OITA: "大分県 (Oita)", + cls.MIYAZAKI: "宮崎県 (Miyazaki)", + cls.KAGOSHIMA: "鹿児島県 (Kagoshima)", + cls.OKINAWA: "沖縄県 (Okinawa)", + cls.HONG_KONG: "香港 (Hong Kong)", + cls.KOREA: "韓国 (Korea)", + cls.TAIWAN: "台湾 (Taiwan)", # The following are different depending on the version of the game, # so we choose the new value. cls.THAILAND: "タイ (Thailand)", @@ -524,7 +526,7 @@ class _RegionConstants: cls.PHILLIPINES: "フィリピン (Phillipines)", cls.MACAO: "マカオ (Macao)", cls.USA: "アメリカ (USA)", - cls.EUROPE: '欧州 (Europe)', + cls.EUROPE: "欧州 (Europe)", cls.NO_MAPPING: "海外 (Other)", } diff --git a/bemani/common/http.py b/bemani/common/http.py index 1e46d01..3b5a883 100644 --- a/bemani/common/http.py +++ b/bemani/common/http.py @@ -3,7 +3,9 @@ from typing import Any, Dict, List, Optional, Tuple class HTTP: @staticmethod - def parse(data: bytes, request: bool=False, response: bool=False) -> Optional[Dict[str, Any]]: + def parse( + data: bytes, request: bool = False, response: bool = False + ) -> Optional[Dict[str, Any]]: """ A very lazy and hastily coded HTTP parser. @@ -37,10 +39,10 @@ class HTTP: try: if request: # Remove the first header as this is the HTTP request - method, uri, version = headerlist.pop(0).split(b' ', 2) + method, uri, version = headerlist.pop(0).split(b" ", 2) elif response: # Remove the first header as this is the HTTP response - version, code, error = headerlist.pop(0).split(b' ', 2) + version, code, error = headerlist.pop(0).split(b" ", 2) else: raise Exception("Logic error!") except ValueError: @@ -53,24 +55,26 @@ class HTTP: # This is lazy because we can have multiple values, but whatever, it works for header in headerlist: name, info = header.split(b":", 1) - key = name.decode('ascii').lower() - value = info.decode('ascii').strip() + key = name.decode("ascii").lower() + value = info.decode("ascii").strip() headers[key] = value preserved.append((key, value)) # Cap post body to length if we have a content-length header - if 'content-length' in headers: - data = data[:int(headers['content-length'])] - valid = len(data) == int(headers['content-length']) - elif 'transfer-encoding' in headers and headers['transfer-encoding'] == 'chunked': - real_data = b'' + if "content-length" in headers: + data = data[: int(headers["content-length"])] + valid = len(data) == int(headers["content-length"]) + elif ( + "transfer-encoding" in headers and headers["transfer-encoding"] == "chunked" + ): + real_data = b"" while True: try: size_bytes, rest = data.split(b"\r\n", 1) except ValueError: # Not enough values to unpack - size_bytes = b'0' + size_bytes = b"0" size = int(size_bytes, 16) @@ -82,7 +86,7 @@ class HTTP: real_data = real_data + rest[:size] # Skip past data and \r\n - data = rest[(size + 2):] + data = rest[(size + 2) :] data = real_data valid = True @@ -91,29 +95,34 @@ class HTTP: if request: return { - 'method': method.decode('ascii').lower(), - 'uri': uri.decode('ascii'), - 'version': version.decode('ascii'), - 'headers': headers, - 'preserved_headers': preserved, - 'data': data, - 'valid': valid, + "method": method.decode("ascii").lower(), + "uri": uri.decode("ascii"), + "version": version.decode("ascii"), + "headers": headers, + "preserved_headers": preserved, + "data": data, + "valid": valid, } elif response: return { - 'code': code.decode('ascii'), - 'version': version.decode('ascii'), - 'error': error.decode('ascii'), - 'headers': headers, - 'preserved_headers': preserved, - 'data': data, - 'valid': valid, + "code": code.decode("ascii"), + "version": version.decode("ascii"), + "error": error.decode("ascii"), + "headers": headers, + "preserved_headers": preserved, + "data": data, + "valid": valid, } else: return None @staticmethod - def generate(parsed_headers: Dict[str, Any], data: bytes, request: bool=False, response: bool=False) -> bytes: + def generate( + parsed_headers: Dict[str, Any], + data: bytes, + request: bool = False, + response: bool = False, + ) -> bytes: """ A very lazy and hastily coded HTTP packet generator. @@ -130,21 +139,25 @@ class HTTP: # Add first part of header if request: - out.append(f'{parsed_headers["method"]} {parsed_headers["uri"]} {parsed_headers["version"]}') + out.append( + f'{parsed_headers["method"]} {parsed_headers["uri"]} {parsed_headers["version"]}' + ) elif response: - out.append(f'{parsed_headers["version"]} {parsed_headers["code"]} {parsed_headers["error"]}') + out.append( + f'{parsed_headers["version"]} {parsed_headers["code"]} {parsed_headers["error"]}' + ) else: raise Exception("Logic error!") # Add the rest of the headers - for header in parsed_headers['preserved_headers']: + for header in parsed_headers["preserved_headers"]: name, value = header - if name.lower() == 'content-length': + if name.lower() == "content-length": # Fix this value = len(data) - elif name.lower() == 'transfer-encoding': + elif name.lower() == "transfer-encoding": # Either we support and strip this, or error! - if value.lower() == 'chunked': + if value.lower() == "chunked": # We support parsing this, but aren't going to re-generate continue else: @@ -154,4 +167,4 @@ class HTTP: out.append(f"{name}: {value}") # Concatenate it with the binary data - return "\r\n".join(out).encode('ascii') + b'\r\n\r\n' + data + return "\r\n".join(out).encode("ascii") + b"\r\n\r\n" + data diff --git a/bemani/common/id.py b/bemani/common/id.py index 3cc7c61..526e7e3 100644 --- a/bemani/common/id.py +++ b/bemani/common/id.py @@ -2,7 +2,6 @@ from typing import Optional class ID: - @staticmethod def format_extid(extid: int) -> str: """ @@ -18,8 +17,8 @@ class ID: """ extid_str = str(extid) while len(extid_str) < 8: - extid_str = '0' + extid_str - return f'{extid_str[0:4]}-{extid_str[4:8]}' + extid_str = "0" + extid_str + return f"{extid_str[0:4]}-{extid_str[4:8]}" @staticmethod def parse_extid(extid: str) -> Optional[int]: @@ -35,20 +34,20 @@ class ID: An integer extid suitable for looking up in a DB. """ try: - if len(extid) == 9 and extid[4:5] == '-': + if len(extid) == 9 and extid[4:5] == "-": return int(extid[0:4] + extid[5:9]) except ValueError: pass return None @staticmethod - def format_machine_id(machine_id: int, region: str = 'US') -> str: + def format_machine_id(machine_id: int, region: str = "US") -> str: """ Take a machine ID as an integer, format it as a string. """ - if region not in {'JP', 'KR', 'TW', 'HK', 'US', 'GB', 'IT', 'ES', 'FR', 'PT'}: - raise Exception(f'Invalid region {region}!') - return f'{region}-{machine_id}' + if region not in {"JP", "KR", "TW", "HK", "US", "GB", "IT", "ES", "FR", "PT"}: + raise Exception(f"Invalid region {region}!") + return f"{region}-{machine_id}" @staticmethod def parse_machine_id(machine_id: str) -> Optional[int]: @@ -56,7 +55,11 @@ class ID: Take a formatted machine ID as a string, returning an int. """ try: - if machine_id[:2] in {'JP', 'KR', 'TW', 'HK', 'US', 'GB', 'IT', 'ES', 'FR', 'PT'} and machine_id[2] == '-': + if ( + machine_id[:2] + in {"JP", "KR", "TW", "HK", "US", "GB", "IT", "ES", "FR", "PT"} + and machine_id[2] == "-" + ): return int(machine_id[3:]) except ValueError: pass diff --git a/bemani/common/model.py b/bemani/common/model.py index 0631567..c4e2c14 100644 --- a/bemani/common/model.py +++ b/bemani/common/model.py @@ -6,7 +6,9 @@ class Model: Object representing a parsed Model String. """ - def __init__(self, gamecode: str, dest: str, spec: str, rev: str, version: Optional[int]) -> None: + def __init__( + self, gamecode: str, dest: str, spec: str, rev: str, version: Optional[int] + ) -> None: """ Initialize a Model object. @@ -25,7 +27,7 @@ class Model: self.version = version @staticmethod - def from_modelstring(model: str) -> 'Model': + def from_modelstring(model: str) -> "Model": """ Parse a modelstring and return a Model @@ -36,17 +38,17 @@ class Model: Returns: A Model object. """ - parts = model.split(':') + parts = model.split(":") if len(parts) == 5: gamecode, dest, spec, rev, version = parts return Model(gamecode, dest, spec, rev, int(version)) elif len(parts) == 4: gamecode, dest, spec, rev = parts return Model(gamecode, dest, spec, rev, None) - raise Exception(f'Couldn\'t parse model {model}') + raise Exception(f"Couldn't parse model {model}") def __str__(self) -> str: if self.version is None: - return f'{self.gamecode}:{self.dest}:{self.spec}:{self.rev}' + return f"{self.gamecode}:{self.dest}:{self.spec}:{self.rev}" else: - return f'{self.gamecode}:{self.dest}:{self.spec}:{self.rev}:{self.version}' + return f"{self.gamecode}:{self.dest}:{self.spec}:{self.rev}:{self.version}" diff --git a/bemani/common/parallel.py b/bemani/common/parallel.py index 88710f0..122f81b 100644 --- a/bemani/common/parallel.py +++ b/bemani/common/parallel.py @@ -1,7 +1,7 @@ import concurrent.futures from typing import Any, Callable, List, TypeVar -T = TypeVar('T') +T = TypeVar("T") class Parallel: @@ -20,8 +20,12 @@ class Parallel: if len(lambdas) == 0: return [] - with concurrent.futures.ThreadPoolExecutor(max_workers=len(lambdas)) as executor: - futures = {executor.submit(lambdas[pos]): pos for pos in range(len(lambdas))} + with concurrent.futures.ThreadPoolExecutor( + max_workers=len(lambdas) + ) as executor: + futures = { + executor.submit(lambdas[pos]): pos for pos in range(len(lambdas)) + } results = [] # List: Tuple[Any, int] for future in concurrent.futures.as_completed(futures): @@ -42,7 +46,9 @@ class Parallel: if len(params) == 0: return [] with concurrent.futures.ThreadPoolExecutor(max_workers=len(params)) as executor: - futures = {executor.submit(lam, params[pos]): pos for pos in range(len(params))} + futures = { + executor.submit(lam, params[pos]): pos for pos in range(len(params)) + } results = [] # List: Tuple[Any, int] for future in concurrent.futures.as_completed(futures): @@ -53,7 +59,7 @@ class Parallel: return [r[0] for r in sorted(results, key=lambda r: r[1])] @staticmethod - def call(lambdas: 'List[Callable[..., Any]]', *params: Any) -> List[Any]: + def call(lambdas: "List[Callable[..., Any]]", *params: Any) -> List[Any]: """ Given a list of callables and zero or more params, calls each callable in parallel with the params specified. Essentially a map of params to multiple @@ -63,8 +69,13 @@ class Parallel: if len(lambdas) == 0: return [] - with concurrent.futures.ThreadPoolExecutor(max_workers=len(lambdas)) as executor: - futures = {executor.submit(lambdas[pos], *params): pos for pos in range(len(lambdas))} + with concurrent.futures.ThreadPoolExecutor( + max_workers=len(lambdas) + ) as executor: + futures = { + executor.submit(lambdas[pos], *params): pos + for pos in range(len(lambdas)) + } results = [] # List: Tuple[Any, int] for future in concurrent.futures.as_completed(futures): diff --git a/bemani/common/pe.py b/bemani/common/pe.py index 18b997c..b9eadbe 100644 --- a/bemani/common/pe.py +++ b/bemani/common/pe.py @@ -1,7 +1,13 @@ import pefile # type: ignore import struct import sys -from iced_x86 import Decoder, Instruction, Formatter, FormatterSyntax, FormatMnemonicOptions +from iced_x86 import ( + Decoder, + Instruction, + Formatter, + FormatterSyntax, + FormatMnemonicOptions, +) from typing import Any, List, Dict, Optional @@ -28,7 +34,9 @@ class Memory: else: # Attempt to return the default. for virtual_start in self.defaults: - if i >= virtual_start and i < (virtual_start + len(self.defaults[virtual_start])): + if i >= virtual_start and i < ( + virtual_start + len(self.defaults[virtual_start]) + ): data.append(self.defaults[virtual_start][i - virtual_start]) break else: @@ -81,7 +89,9 @@ class PEFile: if offset == virtual: return physical - raise Exception(f"Couldn't find physical offset for virtual offset 0x{offset:08x}") + raise Exception( + f"Couldn't find physical offset for virtual offset 0x{offset:08x}" + ) def physical_to_virtual(self, offset: int) -> int: for section in self.__pe.sections: @@ -89,19 +99,25 @@ class PEFile: end = start + section.SizeOfRawData if offset >= start and offset < end: - return (offset - start) + section.VirtualAddress + self.__pe.OPTIONAL_HEADER.ImageBase + return ( + (offset - start) + + section.VirtualAddress + + self.__pe.OPTIONAL_HEADER.ImageBase + ) for virtual, physical in self.__adhoc_mapping.items(): if offset == physical: return virtual - raise Exception(f"Couldn't find virtual offset for physical offset 0x{offset:08x}") + raise Exception( + f"Couldn't find virtual offset for physical offset 0x{offset:08x}" + ) def is_virtual(self, offset: int) -> bool: return offset >= self.__pe.OPTIONAL_HEADER.ImageBase def is_64bit(self) -> bool: - return hex(self.__pe.FILE_HEADER.Machine) == '0x8664' + return hex(self.__pe.FILE_HEADER.Machine) == "0x8664" def emulate_code(self, start: int, end: int, verbose: bool = False) -> None: if self.is_virtual(start): @@ -114,7 +130,11 @@ class PEFile: registers = Registers(0xFFFFFFFFFFFFFFFF if self.is_64bit() else 0xFFFFFFFF) memory = self.__to_memory() - decoder = Decoder(64 if self.is_64bit() else 32, self.data[start:end], ip=self.physical_to_virtual(start)) + decoder = Decoder( + 64 if self.is_64bit() else 32, + self.data[start:end], + ip=self.physical_to_virtual(start), + ) self.__emulate_chunk(registers, memory, [i for i in decoder], verbose) # Replace memory that we care about. @@ -132,7 +152,11 @@ class PEFile: loc = start end = len(self.data) while True: - decoder = Decoder(64 if self.is_64bit() else 32, self.data[loc:end], ip=self.physical_to_virtual(loc)) + decoder = Decoder( + 64 if self.is_64bit() else 32, + self.data[loc:end], + ip=self.physical_to_virtual(loc), + ) chunk = [decoder.decode()] try: @@ -156,7 +180,7 @@ class PEFile: virtual = section.VirtualAddress + self.__pe.OPTIONAL_HEADER.ImageBase length = section.SizeOfRawData physical = self.virtual_to_physical(virtual) - memory.defaults[virtual] = self.data[physical:(physical + length)] + memory.defaults[virtual] = self.data[physical : (physical + length)] for virtual, physical in self.__adhoc_mapping.items(): memory.values[virtual] = self.data[physical] @@ -180,11 +204,20 @@ class PEFile: self.data = bytes(newdata) self.__pe = pefile.PE(data=self.data, fast_load=True) - def __emulate_chunk(self, registers: Registers, memory: Memory, chunk: List[Instruction], verbose: bool) -> None: + def __emulate_chunk( + self, + registers: Registers, + memory: Memory, + chunk: List[Instruction], + verbose: bool, + ) -> None: if verbose: + def vprint(*args: Any, **kwargs: Any) -> None: print(*args, **kwargs, file=sys.stderr) + else: + def vprint(*args: Any, **kwargs: Any) -> None: pass @@ -204,14 +237,19 @@ class PEFile: # Jump to the end, we're done. loc = len(chunk) else: - raise JumpException(destination, f"Jumping to {hex(destination)} which is outside of our evaluation range!") + raise JumpException( + destination, + f"Jumping to {hex(destination)} which is outside of our evaluation range!", + ) formatter = Formatter(FormatterSyntax.NASM) while loc < len(chunk): inst = chunk[loc] loc = loc + 1 - mnemonic = formatter.format_mnemonic(inst, FormatMnemonicOptions.NO_PREFIXES) + mnemonic = formatter.format_mnemonic( + inst, FormatMnemonicOptions.NO_PREFIXES + ) if mnemonic == "mov": dest = formatter.format_operand(inst, 0) @@ -221,7 +259,9 @@ class PEFile: size = get_size(src) or get_size(dest) if size is None: - raise Exception(f"Could not determine size of {mnemonic} operation!") + raise Exception( + f"Could not determine size of {mnemonic} operation!" + ) result = fetch(registers, memory, size, src) assign(registers, memory, size, dest, result) @@ -234,7 +274,9 @@ class PEFile: srcsize = get_size(src) dstsize = get_size(dest) if srcsize is None or dstsize is None: - raise Exception(f"Could not determine size of {mnemonic} operation!") + raise Exception( + f"Could not determine size of {mnemonic} operation!" + ) result = fetch(registers, memory, srcsize, src) assign(registers, memory, dstsize, dest, result) @@ -246,7 +288,9 @@ class PEFile: size = get_size(amt) or get_size(dest) if size is None: - raise Exception(f"Could not determine size of {mnemonic} operation!") + raise Exception( + f"Could not determine size of {mnemonic} operation!" + ) # Special case for adjusting ESP, to make sure our memory contains zeros for reading # out the stack later. @@ -256,7 +300,9 @@ class PEFile: memory.init(min(before, after), max(before, after)) assign(registers, memory, size, dest, after) else: - result = fetch(registers, memory, size, dest) + fetch(registers, memory, size, amt) + result = fetch(registers, memory, size, dest) + fetch( + registers, memory, size, amt + ) assign(registers, memory, size, dest, result) elif mnemonic == "sub": @@ -267,7 +313,9 @@ class PEFile: size = get_size(amt) or get_size(dest) if size is None: - raise Exception(f"Could not determine size of {mnemonic} operation!") + raise Exception( + f"Could not determine size of {mnemonic} operation!" + ) # Special case for adjusting ESP, to make sure our memory contains zeros for reading # out the stack later. @@ -277,7 +325,9 @@ class PEFile: memory.init(min(before, after), max(before, after)) assign(registers, memory, size, dest, after) else: - result = fetch(registers, memory, size, dest) - fetch(registers, memory, size, amt) + result = fetch(registers, memory, size, dest) - fetch( + registers, memory, size, amt + ) assign(registers, memory, size, dest, result) elif mnemonic == "imul": @@ -290,11 +340,19 @@ class PEFile: const = None vprint(f"imul {dest}, {mult}") - size = get_size(mult) or get_size(dest) or (get_size(const) if const is not None else None) + size = ( + get_size(mult) + or get_size(dest) + or (get_size(const) if const is not None else None) + ) if size is None: - raise Exception(f"Could not determine size of {mnemonic} operation!") + raise Exception( + f"Could not determine size of {mnemonic} operation!" + ) if const is None: - result = fetch(registers, memory, size, dest) * fetch(registers, memory, size, mult) + result = fetch(registers, memory, size, dest) * fetch( + registers, memory, size, mult + ) else: result = fetch(registers, memory, size, mult) * get_value(const) assign(registers, memory, size, dest, result) @@ -306,10 +364,18 @@ class PEFile: size = get_size(src) if size is None: - raise Exception(f"Could not determine size of {mnemonic} operation!") + raise Exception( + f"Could not determine size of {mnemonic} operation!" + ) result = fetch(registers, memory, size, src) registers.rsp -= size - assign(registers, memory, size, "[rsp]" if self.is_64bit() else "[esp]", result) + assign( + registers, + memory, + size, + "[rsp]" if self.is_64bit() else "[esp]", + result, + ) elif mnemonic == "pop": dest = formatter.format_operand(inst, 0) @@ -318,8 +384,12 @@ class PEFile: size = get_size(src) if size is None: - raise Exception(f"Could not determine size of {mnemonic} operation!") - result = fetch(registers, memory, size, "[rsp]" if self.is_64bit() else "[esp]") + raise Exception( + f"Could not determine size of {mnemonic} operation!" + ) + result = fetch( + registers, memory, size, "[rsp]" if self.is_64bit() else "[esp]" + ) assign(registers, memory, size, dest, result) registers.rsp += size @@ -331,8 +401,12 @@ class PEFile: size = get_size(op1) or get_size(op2) if size is None: - raise Exception(f"Could not determine size of {mnemonic} operation!") - result = fetch(registers, memory, size, op1) & fetch(registers, memory, size, op2) + raise Exception( + f"Could not determine size of {mnemonic} operation!" + ) + result = fetch(registers, memory, size, op1) & fetch( + registers, memory, size, op2 + ) registers.zf = result == 0 if size == 1: @@ -406,8 +480,12 @@ class PEFile: size = get_size(src) or get_size(dest) if size is None: - raise Exception(f"Could not determine size of {mnemonic} operation!") - result = fetch(registers, memory, size, dest) & fetch(registers, memory, size, src) + raise Exception( + f"Could not determine size of {mnemonic} operation!" + ) + result = fetch(registers, memory, size, dest) & fetch( + registers, memory, size, src + ) assign(registers, memory, size, dest, result) elif mnemonic == "or": @@ -418,8 +496,12 @@ class PEFile: size = get_size(src) or get_size(dest) if size is None: - raise Exception(f"Could not determine size of {mnemonic} operation!") - result = fetch(registers, memory, size, dest) | fetch(registers, memory, size, src) + raise Exception( + f"Could not determine size of {mnemonic} operation!" + ) + result = fetch(registers, memory, size, dest) | fetch( + registers, memory, size, src + ) assign(registers, memory, size, dest, result) elif mnemonic == "xor": @@ -430,8 +512,12 @@ class PEFile: size = get_size(src) or get_size(dest) if size is None: - raise Exception(f"Could not determine size of {mnemonic} operation!") - result = fetch(registers, memory, size, dest) ^ fetch(registers, memory, size, src) + raise Exception( + f"Could not determine size of {mnemonic} operation!" + ) + result = fetch(registers, memory, size, dest) ^ fetch( + registers, memory, size, src + ) assign(registers, memory, size, dest, result) elif mnemonic == "lea": @@ -442,16 +528,22 @@ class PEFile: size = get_size(src) or get_size(dest) if size is None: - raise Exception(f"Could not determine size of {mnemonic} operation!") + raise Exception( + f"Could not determine size of {mnemonic} operation!" + ) result = get_address(registers, src) if result is None: - raise Exception(f"Could not compute effective address for {mnemonic} operation!") + raise Exception( + f"Could not compute effective address for {mnemonic} operation!" + ) assign(registers, memory, size, dest, result) elif mnemonic == "ret": vprint("ret") - raise RetException("Encountered {mnemonic} instruction but we aren't in function context!") + raise RetException( + "Encountered {mnemonic} instruction but we aren't in function context!" + ) else: raise Exception(f"Unsupported mnemonic {mnemonic}!") @@ -502,26 +594,26 @@ def get_address(registers: Registers, indirect: str) -> Optional[int]: indirect = sanitize(indirect[1:-1]) adjust = 0 - if '+' in indirect: - indirect, const = indirect.split('+', 1) + if "+" in indirect: + indirect, const = indirect.split("+", 1) indirect = sanitize(indirect) const = sanitize(const) - if const[-1] == 'h': + if const[-1] == "h": adjust = int(const[:-1], 16) else: adjust = int(const, 10) - elif '-' in indirect: - indirect, const = indirect.split('-', 1) + elif "-" in indirect: + indirect, const = indirect.split("-", 1) indirect = sanitize(indirect) const = sanitize(const) - if const[-1] == 'h': + if const[-1] == "h": adjust = -int(const[:-1], 16) else: adjust = -int(const, 10) - if indirect[-1] == 'h': + if indirect[-1] == "h": return int(indirect[:-1], 16) + adjust # Register-based indirect modes. @@ -591,13 +683,26 @@ def get_size(operand: str) -> Optional[int]: immediate values and indirect memory references. """ - if operand in {'rax', 'rbx', 'rcx', 'rdx', 'rsp', 'rbp', 'rsi', 'rdi'}: + if operand in {"rax", "rbx", "rcx", "rdx", "rsp", "rbp", "rsi", "rdi"}: return 8 - if operand in {'eax', 'ebx', 'ecx', 'edx', 'esp', 'ebp', 'esi', 'edi'}: + if operand in {"eax", "ebx", "ecx", "edx", "esp", "ebp", "esi", "edi"}: return 4 - if operand in {'ax', 'bx', 'cx', 'dx', 'sp', 'bp', 'si', 'di'}: + if operand in {"ax", "bx", "cx", "dx", "sp", "bp", "si", "di"}: return 2 - if operand in {'ah', 'al', 'bh', 'bl', 'ch', 'cl', 'dh', 'dl', 'spl', 'bpl', 'sil', 'dil'}: + if operand in { + "ah", + "al", + "bh", + "bl", + "ch", + "cl", + "dh", + "dl", + "spl", + "bpl", + "sil", + "dil", + }: return 1 if operand[:5] == "byte ": @@ -615,7 +720,9 @@ def get_size(operand: str) -> Optional[int]: return None -def assign(registers: Registers, memory: Memory, size: int, loc: str, value: int) -> None: +def assign( + registers: Registers, memory: Memory, size: int, loc: str, value: int +) -> None: """ Given the registers and memory of our emulator, the size of the operation performed, the location to assign to and the value we should assign, diff --git a/bemani/common/time.py b/bemani/common/time.py index 5c3d857..affd54b 100644 --- a/bemani/common/time.py +++ b/bemani/common/time.py @@ -31,10 +31,7 @@ class Time: """ now = datetime.datetime.utcnow().date() beginning_of_day = datetime.datetime( - now.year, - now.month, - now.day, - tzinfo=tz.tzutc() + now.year, now.month, now.day, tzinfo=tz.tzutc() ) end_of_day = beginning_of_day + datetime.timedelta(days=1) return calendar.timegm(end_of_day.timetuple()) @@ -46,10 +43,7 @@ class Time: """ now = datetime.datetime.utcnow().date() beginning_of_day = datetime.datetime( - now.year, - now.month, - now.day, - tzinfo=tz.tzutc() + now.year, now.month, now.day, tzinfo=tz.tzutc() ) return calendar.timegm(beginning_of_day.timetuple()) @@ -107,7 +101,7 @@ class Time: return [yesterday.year, yesterday.month, yesterday.day] @staticmethod - def week_in_days_since_epoch(timestamp: Optional[int]=None) -> int: + def week_in_days_since_epoch(timestamp: Optional[int] = None) -> int: """ Returns the day number of the beginning of this week, where day zero is the unix epoch at UTC timezone. So if we were one week in, this would return @@ -122,7 +116,7 @@ class Time: return (week - datetime.date(1970, 1, 1)).days @staticmethod - def days_into_year(timestamp: Optional[int]=None) -> List[int]: + def days_into_year(timestamp: Optional[int] = None) -> List[int]: """ Returns a [year, days] list representing the current year, and number of days into the current year. If a timestamp is provided, returns the @@ -135,7 +129,7 @@ class Time: return [date.tm_year, date.tm_yday] @staticmethod - def days_into_week(timestamp: Optional[int]=None) -> int: + def days_into_week(timestamp: Optional[int] = None) -> int: """ Returns an integer representing the number of days into the current week we are, with 0 = monday, 1 = tuesday, etc. If a timestamp is provided, @@ -148,7 +142,7 @@ class Time: return date.tm_wday @staticmethod - def timestamp_from_date(year: int, month: int=1, day: int=1) -> int: + def timestamp_from_date(year: int, month: int = 1, day: int = 1) -> int: """ Given a date (either a year, year/month, or year/month/day), returns the unix timestamp from UTC of that date. Supports out of bounds @@ -161,12 +155,7 @@ class Time: year = year + 1 month = month - 12 - date = datetime.datetime( - year, - month, - day, - tzinfo=tz.tzutc() - ) + date = datetime.datetime(year, month, day, tzinfo=tz.tzutc()) return calendar.timegm(date.timetuple()) @staticmethod diff --git a/bemani/common/validateddict.py b/bemani/common/validateddict.py index 81bb919..17ba855 100644 --- a/bemani/common/validateddict.py +++ b/bemani/common/validateddict.py @@ -4,7 +4,7 @@ from typing import Optional, List, Dict, Any from bemani.common.constants import GameConstants -def intish(val: Any, base: int=10) -> Optional[int]: +def intish(val: Any, base: int = 10) -> Optional[int]: if val is None: return None try: @@ -30,7 +30,7 @@ class ValidatedDict(dict): def clone(self) -> "ValidatedDict": return ValidatedDict(copy.deepcopy(self)) - def get_int(self, name: str, default: int=0) -> int: + def get_int(self, name: str, default: int = 0) -> int: """ Given the name of a value, return an integer stored under that name. @@ -48,7 +48,7 @@ class ValidatedDict(dict): return default return val - def get_float(self, name: str, default: float=0.0) -> float: + def get_float(self, name: str, default: float = 0.0) -> float: """ Given the name of a value, return a float stored under that name. @@ -66,7 +66,7 @@ class ValidatedDict(dict): return default return val - def get_bool(self, name: str, default: bool=False) -> bool: + def get_bool(self, name: str, default: bool = False) -> bool: """ Given the name of a value, return a boolean stored under that name. @@ -84,7 +84,7 @@ class ValidatedDict(dict): return default return val - def get_str(self, name: str, default: str='') -> str: + def get_str(self, name: str, default: str = "") -> str: """ Given the name of a value, return string stored under that name. @@ -102,7 +102,7 @@ class ValidatedDict(dict): return default return val - def get_bytes(self, name: str, default: bytes=b'') -> bytes: + def get_bytes(self, name: str, default: bytes = b"") -> bytes: """ Given the name of a value, return bytes stored under that name. @@ -120,7 +120,9 @@ class ValidatedDict(dict): return default return val - def get_int_array(self, name: str, length: int, default: Optional[List[int]]=None) -> List[int]: + def get_int_array( + self, name: str, length: int, default: Optional[List[int]] = None + ) -> List[int]: """ Given the name of a value, return a list of integers stored under that name. @@ -136,7 +138,7 @@ class ValidatedDict(dict): if default is None: default = [0] * length if len(default) != length: - raise Exception('Gave default of wrong length!') + raise Exception("Gave default of wrong length!") val = self.get(name) if val is None: @@ -150,7 +152,9 @@ class ValidatedDict(dict): return default return val - def get_bool_array(self, name: str, length: int, default: Optional[List[bool]]=None) -> List[bool]: + def get_bool_array( + self, name: str, length: int, default: Optional[List[bool]] = None + ) -> List[bool]: """ Given the name of a value, return a list of booleans stored under that name. @@ -166,7 +170,7 @@ class ValidatedDict(dict): if default is None: default = [False] * length if len(default) != length: - raise Exception('Gave default of wrong length!') + raise Exception("Gave default of wrong length!") val = self.get(name) if val is None: @@ -180,7 +184,9 @@ class ValidatedDict(dict): return default return val - def get_bytes_array(self, name: str, length: int, default: Optional[List[bytes]]=None) -> List[bytes]: + def get_bytes_array( + self, name: str, length: int, default: Optional[List[bytes]] = None + ) -> List[bytes]: """ Given the name of a value, return a list of bytestrings stored under that name. @@ -194,9 +200,9 @@ class ValidatedDict(dict): A list of bytestrings. """ if default is None: - default = [b''] * length + default = [b""] * length if len(default) != length: - raise Exception('Gave default of wrong length!') + raise Exception("Gave default of wrong length!") val = self.get(name) if val is None: @@ -210,7 +216,9 @@ class ValidatedDict(dict): return default return val - def get_str_array(self, name: str, length: int, default: Optional[List[str]]=None) -> List[str]: + def get_str_array( + self, name: str, length: int, default: Optional[List[str]] = None + ) -> List[str]: """ Given the name of a value, return a list of strings stored under that name. @@ -224,9 +232,9 @@ class ValidatedDict(dict): A list of strings. """ if default is None: - default = [''] * length + default = [""] * length if len(default) != length: - raise Exception('Gave default of wrong length!') + raise Exception("Gave default of wrong length!") val = self.get(name) if val is None: @@ -240,7 +248,9 @@ class ValidatedDict(dict): return default return val - def get_dict(self, name: str, default: Optional[Dict[Any, Any]]=None) -> 'ValidatedDict': + def get_dict( + self, name: str, default: Optional[Dict[Any, Any]] = None + ) -> "ValidatedDict": """ Given the name of a value, return a dictionary stored under that name. @@ -452,7 +462,14 @@ class Profile(ValidatedDict): combo and the refid and extid associated wit the profile. """ - def __init__(self, game: GameConstants, version: int, refid: str, extid: int, initial_values: Dict[str, Any] = {}) -> None: + def __init__( + self, + game: GameConstants, + version: int, + refid: str, + extid: int, + initial_values: Dict[str, Any] = {}, + ) -> None: super().__init__(initial_values or {}) self.game = game self.version = version @@ -460,7 +477,9 @@ class Profile(ValidatedDict): self.extid = extid def clone(self) -> "Profile": - return Profile(self.game, self.version, self. refid, self.extid, copy.deepcopy(self)) + return Profile( + self.game, self.version, self.refid, self.extid, copy.deepcopy(self) + ) class PlayStatistics(ValidatedDict): diff --git a/bemani/data/__init__.py b/bemani/data/__init__.py index c2fa389..77e70d3 100644 --- a/bemani/data/__init__.py +++ b/bemani/data/__init__.py @@ -1,7 +1,22 @@ from bemani.data.config import Config from bemani.data.data import Data, DBCreateException from bemani.data.exceptions import ScoreSaveException -from bemani.data.types import User, Achievement, Machine, Arcade, Score, Attempt, News, Link, Song, Event, Server, Client, UserID, ArcadeID +from bemani.data.types import ( + User, + Achievement, + Machine, + Arcade, + Score, + Attempt, + News, + Link, + Song, + Event, + Server, + Client, + UserID, + ArcadeID, +) from bemani.data.remoteuser import RemoteUser from bemani.data.triggers import Triggers diff --git a/bemani/data/api/base.py b/bemani/data/api/base.py index 0366113..37ba452 100644 --- a/bemani/data/api/base.py +++ b/bemani/data/api/base.py @@ -5,7 +5,6 @@ from bemani.data.interfaces import APIProviderInterface class BaseGlobalData: - def __init__(self, api: APIProviderInterface) -> None: self.__localapi = api self.__apiclients: Optional[List[APIClient]] = None @@ -14,6 +13,11 @@ class BaseGlobalData: def clients(self) -> List[APIClient]: if self.__apiclients is None: servers = self.__localapi.get_all_servers() - self.__apiclients = [APIClient(server.uri, server.token, server.allow_stats, server.allow_scores) for server in servers] + self.__apiclients = [ + APIClient( + server.uri, server.token, server.allow_stats, server.allow_scores + ) + for server in servers + ] return self.__apiclients diff --git a/bemani/data/api/client.py b/bemani/data/api/client.py index a92c972..169adb2 100644 --- a/bemani/data/api/client.py +++ b/bemani/data/api/client.py @@ -3,7 +3,13 @@ import requests from typing import Tuple, Dict, List, Any, Optional from typing_extensions import Final -from bemani.common import APIConstants, GameConstants, VersionConstants, DBConstants, ValidatedDict +from bemani.common import ( + APIConstants, + GameConstants, + VersionConstants, + DBConstants, + ValidatedDict, +) class APIException(Exception): @@ -35,45 +41,49 @@ class APIClient: A client that fully speaks BEMAPI and can pull information from a remote server. """ - API_VERSION: Final[str] = 'v1' + API_VERSION: Final[str] = "v1" - def __init__(self, base_uri: str, token: str, allow_stats: bool, allow_scores: bool) -> None: + def __init__( + self, base_uri: str, token: str, allow_stats: bool, allow_scores: bool + ) -> None: self.base_uri = base_uri self.token = token self.allow_stats = allow_stats self.allow_scores = allow_scores def _content_type_valid(self, content_type: str) -> bool: - if ';' in content_type: - left, right = content_type.split(';', 1) + if ";" in content_type: + left, right = content_type.split(";", 1) left = left.strip().lower() right = right.strip().lower() - if left == 'application/json' and ('=' in right): - identifier, charset = right.split('=', 1) + if left == "application/json" and ("=" in right): + identifier, charset = right.split("=", 1) identifier = identifier.strip() charset = charset.strip() - if identifier == 'charset' and charset == 'utf-8': + if identifier == "charset" and charset == "utf-8": # This is valid. return True return False - def __exchange_data(self, request_uri: str, request_args: Dict[str, Any]) -> Dict[str, Any]: - if self.base_uri[-1:] != '/': - uri = f'{self.base_uri}/{request_uri}' + def __exchange_data( + self, request_uri: str, request_args: Dict[str, Any] + ) -> Dict[str, Any]: + if self.base_uri[-1:] != "/": + uri = f"{self.base_uri}/{request_uri}" else: - uri = f'{self.base_uri}{request_uri}' + uri = f"{self.base_uri}{request_uri}" headers = { - 'Authorization': f'Token {self.token}', - 'Content-Type': 'application/json; charset=utf-8', + "Authorization": f"Token {self.token}", + "Content-Type": "application/json; charset=utf-8", } - data = json.dumps(request_args).encode('utf8') + data = json.dumps(request_args).encode("utf8") try: r = requests.request( - 'GET', + "GET", uri, headers=headers, data=data, @@ -81,45 +91,63 @@ class APIClient: timeout=10, ) except Exception: - raise APIException('Failed to query remote server!') + raise APIException("Failed to query remote server!") # Verify that content type is in the form of "application/json; charset=utf-8". - if not self._content_type_valid(r.headers['content-type']): - raise APIException(f'API returned invalid content type \'{r.headers["content-type"]}\'!') + if not self._content_type_valid(r.headers["content-type"]): + raise APIException( + f'API returned invalid content type \'{r.headers["content-type"]}\'!' + ) jsondata = r.json() if r.status_code == 200: return jsondata - if 'error' not in jsondata: - raise APIException(f'API returned error code {r.status_code} but did not include \'error\' attribute in response JSON!') - error = jsondata['error'] + if "error" not in jsondata: + raise APIException( + f"API returned error code {r.status_code} but did not include 'error' attribute in response JSON!" + ) + error = jsondata["error"] if r.status_code == 401: - raise NotAuthorizedAPIException('The API token used is not authorized against this server!') + raise NotAuthorizedAPIException( + "The API token used is not authorized against this server!" + ) if r.status_code == 404: - raise UnsupportedRequestAPIException('The server does not support this game/version or request object!') + raise UnsupportedRequestAPIException( + "The server does not support this game/version or request object!" + ) if r.status_code == 405: - raise UnrecognizedRequestAPIException('The server did not recognize the request!') + raise UnrecognizedRequestAPIException( + "The server did not recognize the request!" + ) if r.status_code == 500: - raise RemoteServerErrorAPIException(f'The server had an error processing the request and returned \'{error}\'') + raise RemoteServerErrorAPIException( + f"The server had an error processing the request and returned '{error}'" + ) if r.status_code == 501: - raise UnsupportedVersionAPIException('The server does not support this version of the API!') - raise APIException('The server returned an invalid status code {}!', format(r.status_code)) + raise UnsupportedVersionAPIException( + "The server does not support this version of the API!" + ) + raise APIException( + "The server returned an invalid status code {}!", format(r.status_code) + ) def __translate(self, game: GameConstants, version: int) -> Tuple[str, str]: servergame = { - GameConstants.DDR: 'ddr', - GameConstants.IIDX: 'iidx', - GameConstants.JUBEAT: 'jubeat', - GameConstants.MUSECA: 'museca', - GameConstants.POPN_MUSIC: 'popnmusic', - GameConstants.REFLEC_BEAT: 'reflecbeat', - GameConstants.SDVX: 'soundvoltex', + GameConstants.DDR: "ddr", + GameConstants.IIDX: "iidx", + GameConstants.JUBEAT: "jubeat", + GameConstants.MUSECA: "museca", + GameConstants.POPN_MUSIC: "popnmusic", + GameConstants.REFLEC_BEAT: "reflecbeat", + GameConstants.SDVX: "soundvoltex", }.get(game) if servergame is None: - raise UnsupportedRequestAPIException('The client does not support this game/version!') + raise UnsupportedRequestAPIException( + "The client does not support this game/version!" + ) if version >= DBConstants.OMNIMIX_VERSION_BUMP: version = version - DBConstants.OMNIMIX_VERSION_BUMP @@ -127,76 +155,86 @@ class APIClient: else: omnimix = False - serverversion = { - GameConstants.DDR: { - VersionConstants.DDR_X2: '12', - VersionConstants.DDR_X3_VS_2NDMIX: '13', - VersionConstants.DDR_2013: '14', - VersionConstants.DDR_2014: '15', - VersionConstants.DDR_ACE: '16', - }, - GameConstants.IIDX: { - VersionConstants.IIDX_TRICORO: '20', - VersionConstants.IIDX_SPADA: '21', - VersionConstants.IIDX_PENDUAL: '22', - VersionConstants.IIDX_COPULA: '23', - VersionConstants.IIDX_SINOBUZ: '24', - VersionConstants.IIDX_CANNON_BALLERS: '25', - VersionConstants.IIDX_ROOTAGE: '26', - }, - GameConstants.JUBEAT: { - VersionConstants.JUBEAT_SAUCER: '5', - VersionConstants.JUBEAT_SAUCER_FULFILL: '5a', - VersionConstants.JUBEAT_PROP: '6', - VersionConstants.JUBEAT_QUBELL: '7', - VersionConstants.JUBEAT_CLAN: '8', - VersionConstants.JUBEAT_FESTO: '9', - }, - GameConstants.MUSECA: { - VersionConstants.MUSECA: '1', - VersionConstants.MUSECA_1_PLUS: '1p', - }, - GameConstants.POPN_MUSIC: { - VersionConstants.POPN_MUSIC_TUNE_STREET: '19', - VersionConstants.POPN_MUSIC_FANTASIA: '20', - VersionConstants.POPN_MUSIC_SUNNY_PARK: '21', - VersionConstants.POPN_MUSIC_LAPISTORIA: '22', - VersionConstants.POPN_MUSIC_ECLALE: '23', - VersionConstants.POPN_MUSIC_USANEKO: '24', - }, - GameConstants.REFLEC_BEAT: { - VersionConstants.REFLEC_BEAT: '1', - VersionConstants.REFLEC_BEAT_LIMELIGHT: '2', - VersionConstants.REFLEC_BEAT_COLETTE: '3as', - VersionConstants.REFLEC_BEAT_GROOVIN: '4u', - VersionConstants.REFLEC_BEAT_VOLZZA: '5', - VersionConstants.REFLEC_BEAT_VOLZZA_2: '5a', - VersionConstants.REFLEC_BEAT_REFLESIA: '6', - }, - GameConstants.SDVX: { - VersionConstants.SDVX_BOOTH: '1', - VersionConstants.SDVX_INFINITE_INFECTION: '2', - VersionConstants.SDVX_GRAVITY_WARS: '3', - VersionConstants.SDVX_HEAVENLY_HAVEN: '4', - }, - }.get(game, {}).get(version) + serverversion = ( + { + GameConstants.DDR: { + VersionConstants.DDR_X2: "12", + VersionConstants.DDR_X3_VS_2NDMIX: "13", + VersionConstants.DDR_2013: "14", + VersionConstants.DDR_2014: "15", + VersionConstants.DDR_ACE: "16", + }, + GameConstants.IIDX: { + VersionConstants.IIDX_TRICORO: "20", + VersionConstants.IIDX_SPADA: "21", + VersionConstants.IIDX_PENDUAL: "22", + VersionConstants.IIDX_COPULA: "23", + VersionConstants.IIDX_SINOBUZ: "24", + VersionConstants.IIDX_CANNON_BALLERS: "25", + VersionConstants.IIDX_ROOTAGE: "26", + }, + GameConstants.JUBEAT: { + VersionConstants.JUBEAT_SAUCER: "5", + VersionConstants.JUBEAT_SAUCER_FULFILL: "5a", + VersionConstants.JUBEAT_PROP: "6", + VersionConstants.JUBEAT_QUBELL: "7", + VersionConstants.JUBEAT_CLAN: "8", + VersionConstants.JUBEAT_FESTO: "9", + }, + GameConstants.MUSECA: { + VersionConstants.MUSECA: "1", + VersionConstants.MUSECA_1_PLUS: "1p", + }, + GameConstants.POPN_MUSIC: { + VersionConstants.POPN_MUSIC_TUNE_STREET: "19", + VersionConstants.POPN_MUSIC_FANTASIA: "20", + VersionConstants.POPN_MUSIC_SUNNY_PARK: "21", + VersionConstants.POPN_MUSIC_LAPISTORIA: "22", + VersionConstants.POPN_MUSIC_ECLALE: "23", + VersionConstants.POPN_MUSIC_USANEKO: "24", + }, + GameConstants.REFLEC_BEAT: { + VersionConstants.REFLEC_BEAT: "1", + VersionConstants.REFLEC_BEAT_LIMELIGHT: "2", + VersionConstants.REFLEC_BEAT_COLETTE: "3as", + VersionConstants.REFLEC_BEAT_GROOVIN: "4u", + VersionConstants.REFLEC_BEAT_VOLZZA: "5", + VersionConstants.REFLEC_BEAT_VOLZZA_2: "5a", + VersionConstants.REFLEC_BEAT_REFLESIA: "6", + }, + GameConstants.SDVX: { + VersionConstants.SDVX_BOOTH: "1", + VersionConstants.SDVX_INFINITE_INFECTION: "2", + VersionConstants.SDVX_GRAVITY_WARS: "3", + VersionConstants.SDVX_HEAVENLY_HAVEN: "4", + }, + } + .get(game, {}) + .get(version) + ) if serverversion is None: - raise UnsupportedRequestAPIException('The client does not support this game/version!') + raise UnsupportedRequestAPIException( + "The client does not support this game/version!" + ) if omnimix: - serverversion = 'o' + serverversion + serverversion = "o" + serverversion return (servergame, serverversion) def get_server_info(self) -> ValidatedDict: - resp = self.__exchange_data('', {}) - return ValidatedDict({ - 'name': resp['name'], - 'email': resp['email'], - 'versions': resp['versions'], - }) + resp = self.__exchange_data("", {}) + return ValidatedDict( + { + "name": resp["name"], + "email": resp["email"], + "versions": resp["versions"], + } + ) - def get_profiles(self, game: GameConstants, version: int, idtype: APIConstants, ids: List[str]) -> List[Dict[str, Any]]: + def get_profiles( + self, game: GameConstants, version: int, idtype: APIConstants, ids: List[str] + ) -> List[Dict[str, Any]]: # Allow remote servers to be disabled if not self.allow_scores: return [] @@ -204,14 +242,14 @@ class APIClient: try: servergame, serverversion = self.__translate(game, version) resp = self.__exchange_data( - f'{self.API_VERSION}/{servergame}/{serverversion}', + f"{self.API_VERSION}/{servergame}/{serverversion}", { - 'ids': ids, - 'type': idtype.value, - 'objects': ['profile'], + "ids": ids, + "type": idtype.value, + "objects": ["profile"], }, ) - return resp['profile'] + return resp["profile"] except APIException: # Couldn't talk to server, assume empty profiles return [] @@ -222,8 +260,8 @@ class APIClient: version: int, idtype: APIConstants, ids: List[str], - since: Optional[int]=None, - until: Optional[int]=None, + since: Optional[int] = None, + until: Optional[int] = None, ) -> List[Dict[str, Any]]: # Allow remote servers to be disabled if not self.allow_scores: @@ -232,24 +270,26 @@ class APIClient: try: servergame, serverversion = self.__translate(game, version) data: Dict[str, Any] = { - 'ids': ids, - 'type': idtype.value, - 'objects': ['records'], + "ids": ids, + "type": idtype.value, + "objects": ["records"], } if since is not None: - data['since'] = since + data["since"] = since if until is not None: - data['until'] = until + data["until"] = until resp = self.__exchange_data( - f'{self.API_VERSION}/{servergame}/{serverversion}', + f"{self.API_VERSION}/{servergame}/{serverversion}", data, ) - return resp['records'] + return resp["records"] except APIException: # Couldn't talk to server, assume empty records return [] - def get_statistics(self, game: GameConstants, version: int, idtype: APIConstants, ids: List[str]) -> List[Dict[str, Any]]: + def get_statistics( + self, game: GameConstants, version: int, idtype: APIConstants, ids: List[str] + ) -> List[Dict[str, Any]]: # Allow remote servers to be disabled if not self.allow_stats: return [] @@ -257,32 +297,34 @@ class APIClient: try: servergame, serverversion = self.__translate(game, version) resp = self.__exchange_data( - f'{self.API_VERSION}/{servergame}/{serverversion}', + f"{self.API_VERSION}/{servergame}/{serverversion}", { - 'ids': ids, - 'type': idtype.value, - 'objects': ['statistics'], + "ids": ids, + "type": idtype.value, + "objects": ["statistics"], }, ) - return resp['statistics'] + return resp["statistics"] except APIException: # Couldn't talk to server, assume empty statistics return [] - def get_catalog(self, game: GameConstants, version: int) -> Dict[str, List[Dict[str, Any]]]: + def get_catalog( + self, game: GameConstants, version: int + ) -> Dict[str, List[Dict[str, Any]]]: # No point disallowing this, since its only ever used for bootstrapping. try: servergame, serverversion = self.__translate(game, version) resp = self.__exchange_data( - f'{self.API_VERSION}/{servergame}/{serverversion}', + f"{self.API_VERSION}/{servergame}/{serverversion}", { - 'ids': [], - 'type': 'server', - 'objects': ['catalog'], + "ids": [], + "type": "server", + "objects": ["catalog"], }, ) - return resp['catalog'] + return resp["catalog"] except APIException: # Couldn't talk to server, assume empty catalog return {} diff --git a/bemani/data/api/game.py b/bemani/data/api/game.py index b187d18..152973b 100644 --- a/bemani/data/api/game.py +++ b/bemani/data/api/game.py @@ -6,7 +6,6 @@ from bemani.data.types import Item class GlobalGameData(BaseGlobalData): - def __translate_sdvx_song_unlock(self, entry: Dict[str, Any]) -> Item: return Item( "song_unlock", @@ -46,7 +45,7 @@ class GlobalGameData(BaseGlobalData): "identifier": entry["identifier"], "name": entry["name"], "type": entry["type"], - } + }, ) def get_items(self, game: GameConstants, version: int) -> List[Item]: @@ -61,9 +60,7 @@ class GlobalGameData(BaseGlobalData): A list of item objects. """ catalogs: List[Dict[str, List[Dict[str, Any]]]] = Parallel.call( - [client.get_catalog for client in self.clients], - game, - version + [client.get_catalog for client in self.clients], game, version ) retval: List[Item] = [] seen: Set[str] = set() @@ -103,7 +100,9 @@ class GlobalGameData(BaseGlobalData): seen.add(key) return retval - def get_item(self, game: GameConstants, version: int, catid: int, cattype: str) -> Optional[ValidatedDict]: + def get_item( + self, game: GameConstants, version: int, catid: int, cattype: str + ) -> Optional[ValidatedDict]: """ Given a game/userid and catalog id/type, find that catalog entry. diff --git a/bemani/data/api/music.py b/bemani/data/api/music.py index a55dc57..ea79b7a 100644 --- a/bemani/data/api/music.py +++ b/bemani/data/api/music.py @@ -1,6 +1,12 @@ from typing import List, Optional, Dict, Any, Tuple, Set -from bemani.common import APIConstants, GameConstants, VersionConstants, DBConstants, Parallel +from bemani.common import ( + APIConstants, + GameConstants, + VersionConstants, + DBConstants, + Parallel, +) from bemani.data.interfaces import APIProviderInterface from bemani.data.api.base import BaseGlobalData from bemani.data.mysql.user import UserData @@ -10,8 +16,9 @@ from bemani.data.types import UserID, Score, Song class GlobalMusicData(BaseGlobalData): - - def __init__(self, api: APIProviderInterface, user: UserData, music: MusicData) -> None: + def __init__( + self, api: APIProviderInterface, user: UserData, music: MusicData + ) -> None: super().__init__(api) self.user = user self.music = music @@ -33,14 +40,16 @@ class GlobalMusicData(BaseGlobalData): def __max(self, int1: int, int2: int) -> int: return max(int1, int2) - def __format_ddr_score(self, version: int, songid: int, songchart: int, data: Dict[str, Any]) -> Score: + def __format_ddr_score( + self, version: int, songid: int, songchart: int, data: Dict[str, Any] + ) -> Score: halo = { - 'none': DBConstants.DDR_HALO_NONE, - 'gfc': DBConstants.DDR_HALO_GOOD_FULL_COMBO, - 'fc': DBConstants.DDR_HALO_GREAT_FULL_COMBO, - 'pfc': DBConstants.DDR_HALO_PERFECT_FULL_COMBO, - 'mfc': DBConstants.DDR_HALO_MARVELOUS_FULL_COMBO, - }.get(data.get('halo'), DBConstants.DDR_HALO_NONE) + "none": DBConstants.DDR_HALO_NONE, + "gfc": DBConstants.DDR_HALO_GOOD_FULL_COMBO, + "fc": DBConstants.DDR_HALO_GREAT_FULL_COMBO, + "pfc": DBConstants.DDR_HALO_PERFECT_FULL_COMBO, + "mfc": DBConstants.DDR_HALO_MARVELOUS_FULL_COMBO, + }.get(data.get("halo"), DBConstants.DDR_HALO_NONE) rank = { "AAA": DBConstants.DDR_RANK_AAA, "AA+": DBConstants.DDR_RANK_AA_PLUS, @@ -58,239 +67,258 @@ class GlobalMusicData(BaseGlobalData): "D+": DBConstants.DDR_RANK_D_PLUS, "D": DBConstants.DDR_RANK_D, "E": DBConstants.DDR_RANK_E, - }.get(data.get('rank'), DBConstants.DDR_RANK_E) + }.get(data.get("rank"), DBConstants.DDR_RANK_E) - ghost = '' + ghost = "" trace: List[int] = [] if version == VersionConstants.DDR_ACE: # DDR Ace is specia - ghost = ''.join([str(x) for x in data.get('ghost', [])]) + ghost = "".join([str(x) for x in data.get("ghost", [])]) else: - trace = [int(x) for x in data.get('ghost', [])] + trace = [int(x) for x in data.get("ghost", [])] return Score( -1, songid, songchart, - int(data.get('points', 0)), - int(data.get('timestamp', -1)), - self.__max(int(data.get('timestamp', -1)), int(data.get('updated', -1))), + int(data.get("points", 0)), + int(data.get("timestamp", -1)), + self.__max(int(data.get("timestamp", -1)), int(data.get("updated", -1))), -1, # No location for remote play 1, # No play info for remote play { - 'combo': int(data.get('combo', -1)), - 'rank': rank, - 'halo': halo, - 'ghost': ghost, - 'trace': trace, + "combo": int(data.get("combo", -1)), + "rank": rank, + "halo": halo, + "ghost": ghost, + "trace": trace, }, ) - def __format_iidx_score(self, version: int, songid: int, songchart: int, data: Dict[str, Any]) -> Score: + def __format_iidx_score( + self, version: int, songid: int, songchart: int, data: Dict[str, Any] + ) -> Score: status = { - 'np': DBConstants.IIDX_CLEAR_STATUS_NO_PLAY, - 'failed': DBConstants.IIDX_CLEAR_STATUS_FAILED, - 'ac': DBConstants.IIDX_CLEAR_STATUS_ASSIST_CLEAR, - 'ec': DBConstants.IIDX_CLEAR_STATUS_EASY_CLEAR, - 'nc': DBConstants.IIDX_CLEAR_STATUS_CLEAR, - 'hc': DBConstants.IIDX_CLEAR_STATUS_HARD_CLEAR, - 'exhc': DBConstants.IIDX_CLEAR_STATUS_EX_HARD_CLEAR, - 'fc': DBConstants.IIDX_CLEAR_STATUS_FULL_COMBO, - }.get(data.get('status'), DBConstants.IIDX_CLEAR_STATUS_NO_PLAY) + "np": DBConstants.IIDX_CLEAR_STATUS_NO_PLAY, + "failed": DBConstants.IIDX_CLEAR_STATUS_FAILED, + "ac": DBConstants.IIDX_CLEAR_STATUS_ASSIST_CLEAR, + "ec": DBConstants.IIDX_CLEAR_STATUS_EASY_CLEAR, + "nc": DBConstants.IIDX_CLEAR_STATUS_CLEAR, + "hc": DBConstants.IIDX_CLEAR_STATUS_HARD_CLEAR, + "exhc": DBConstants.IIDX_CLEAR_STATUS_EX_HARD_CLEAR, + "fc": DBConstants.IIDX_CLEAR_STATUS_FULL_COMBO, + }.get(data.get("status"), DBConstants.IIDX_CLEAR_STATUS_NO_PLAY) return Score( -1, songid, songchart, - int(data.get('points', 0)), - int(data.get('timestamp', -1)), - self.__max(int(data.get('timestamp', -1)), int(data.get('updated', -1))), + int(data.get("points", 0)), + int(data.get("timestamp", -1)), + self.__max(int(data.get("timestamp", -1)), int(data.get("updated", -1))), -1, # No location for remote play 1, # No play info for remote play { - 'clear_status': status, - 'ghost': bytes([int(b) for b in data.get('ghost', [])]), - 'miss_count': int(data.get('miss', -1)), - 'pgreats': int(data.get('pgreat', -1)), - 'greats': int(data.get('great', -1)), + "clear_status": status, + "ghost": bytes([int(b) for b in data.get("ghost", [])]), + "miss_count": int(data.get("miss", -1)), + "pgreats": int(data.get("pgreat", -1)), + "greats": int(data.get("great", -1)), }, ) - def __format_jubeat_score(self, version: int, songid: int, songchart: int, data: Dict[str, Any]) -> Score: + def __format_jubeat_score( + self, version: int, songid: int, songchart: int, data: Dict[str, Any] + ) -> Score: status = { - 'failed': DBConstants.JUBEAT_PLAY_MEDAL_FAILED, - 'cleared': DBConstants.JUBEAT_PLAY_MEDAL_CLEARED, - 'nfc': DBConstants.JUBEAT_PLAY_MEDAL_NEARLY_FULL_COMBO, - 'fc': DBConstants.JUBEAT_PLAY_MEDAL_FULL_COMBO, - 'nec': DBConstants.JUBEAT_PLAY_MEDAL_NEARLY_EXCELLENT, - 'exc': DBConstants.JUBEAT_PLAY_MEDAL_EXCELLENT, - }.get(data.get('status'), DBConstants.JUBEAT_PLAY_MEDAL_FAILED) + "failed": DBConstants.JUBEAT_PLAY_MEDAL_FAILED, + "cleared": DBConstants.JUBEAT_PLAY_MEDAL_CLEARED, + "nfc": DBConstants.JUBEAT_PLAY_MEDAL_NEARLY_FULL_COMBO, + "fc": DBConstants.JUBEAT_PLAY_MEDAL_FULL_COMBO, + "nec": DBConstants.JUBEAT_PLAY_MEDAL_NEARLY_EXCELLENT, + "exc": DBConstants.JUBEAT_PLAY_MEDAL_EXCELLENT, + }.get(data.get("status"), DBConstants.JUBEAT_PLAY_MEDAL_FAILED) return Score( -1, songid, songchart, - int(data.get('points', 0)), - int(data.get('timestamp', -1)), - self.__max(int(data.get('timestamp', -1)), int(data.get('updated', -1))), + int(data.get("points", 0)), + int(data.get("timestamp", -1)), + self.__max(int(data.get("timestamp", -1)), int(data.get("updated", -1))), -1, # No location for remote play 1, # No play info for remote play { - 'medal': status, - 'combo': int(data.get('combo', -1)), - 'ghost': [int(x) for x in data.get('ghost', [])], - 'music_rate': int(data.get('music_rate')), + "medal": status, + "combo": int(data.get("combo", -1)), + "ghost": [int(x) for x in data.get("ghost", [])], + "music_rate": int(data.get("music_rate")), }, ) - def __format_museca_score(self, version: int, songid: int, songchart: int, data: Dict[str, Any]) -> Score: + def __format_museca_score( + self, version: int, songid: int, songchart: int, data: Dict[str, Any] + ) -> Score: rank = { - 'death': DBConstants.MUSECA_GRADE_DEATH, - 'poor': DBConstants.MUSECA_GRADE_POOR, - 'mediocre': DBConstants.MUSECA_GRADE_MEDIOCRE, - 'good': DBConstants.MUSECA_GRADE_GOOD, - 'great': DBConstants.MUSECA_GRADE_GREAT, - 'excellent': DBConstants.MUSECA_GRADE_EXCELLENT, - 'superb': DBConstants.MUSECA_GRADE_SUPERB, - 'masterpiece': DBConstants.MUSECA_GRADE_MASTERPIECE, - 'perfect': DBConstants.MUSECA_GRADE_PERFECT, - }.get(data.get('rank'), DBConstants.MUSECA_GRADE_DEATH) + "death": DBConstants.MUSECA_GRADE_DEATH, + "poor": DBConstants.MUSECA_GRADE_POOR, + "mediocre": DBConstants.MUSECA_GRADE_MEDIOCRE, + "good": DBConstants.MUSECA_GRADE_GOOD, + "great": DBConstants.MUSECA_GRADE_GREAT, + "excellent": DBConstants.MUSECA_GRADE_EXCELLENT, + "superb": DBConstants.MUSECA_GRADE_SUPERB, + "masterpiece": DBConstants.MUSECA_GRADE_MASTERPIECE, + "perfect": DBConstants.MUSECA_GRADE_PERFECT, + }.get(data.get("rank"), DBConstants.MUSECA_GRADE_DEATH) status = { - 'failed': DBConstants.MUSECA_CLEAR_TYPE_FAILED, - 'cleared': DBConstants.MUSECA_CLEAR_TYPE_CLEARED, - 'fc': DBConstants.MUSECA_CLEAR_TYPE_FULL_COMBO, - }.get(data.get('status'), DBConstants.MUSECA_CLEAR_TYPE_FAILED) + "failed": DBConstants.MUSECA_CLEAR_TYPE_FAILED, + "cleared": DBConstants.MUSECA_CLEAR_TYPE_CLEARED, + "fc": DBConstants.MUSECA_CLEAR_TYPE_FULL_COMBO, + }.get(data.get("status"), DBConstants.MUSECA_CLEAR_TYPE_FAILED) return Score( -1, songid, songchart, - int(data.get('points', 0)), - int(data.get('timestamp', -1)), - self.__max(int(data.get('timestamp', -1)), int(data.get('updated', -1))), + int(data.get("points", 0)), + int(data.get("timestamp", -1)), + self.__max(int(data.get("timestamp", -1)), int(data.get("updated", -1))), -1, # No location for remote play 1, # No play info for remote play { - 'grade': rank, - 'clear_type': status, - 'combo': int(data.get('combo', -1)), - 'stats': { - 'btn_rate': int(data.get('buttonrate', -1)), - 'long_rate': int(data.get('longrate', -1)), - 'vol_rate': int(data.get('volrate', -1)), + "grade": rank, + "clear_type": status, + "combo": int(data.get("combo", -1)), + "stats": { + "btn_rate": int(data.get("buttonrate", -1)), + "long_rate": int(data.get("longrate", -1)), + "vol_rate": int(data.get("volrate", -1)), }, }, ) - def __format_popn_score(self, version: int, songid: int, songchart: int, data: Dict[str, Any]) -> Score: + def __format_popn_score( + self, version: int, songid: int, songchart: int, data: Dict[str, Any] + ) -> Score: status = { - 'cf': DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_FAILED, - 'df': DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_FAILED, - 'sf': DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_FAILED, - 'ec': DBConstants.POPN_MUSIC_PLAY_MEDAL_EASY_CLEAR, - 'cc': DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_CLEARED, - 'dc': DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_CLEARED, - 'sc': DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_CLEARED, - 'cfc': DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_FULL_COMBO, - 'dfc': DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_FULL_COMBO, - 'sfc': DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_FULL_COMBO, - 'p': DBConstants.POPN_MUSIC_PLAY_MEDAL_PERFECT, - }.get(data.get('status'), DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_FAILED) + "cf": DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_FAILED, + "df": DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_FAILED, + "sf": DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_FAILED, + "ec": DBConstants.POPN_MUSIC_PLAY_MEDAL_EASY_CLEAR, + "cc": DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_CLEARED, + "dc": DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_CLEARED, + "sc": DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_CLEARED, + "cfc": DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_FULL_COMBO, + "dfc": DBConstants.POPN_MUSIC_PLAY_MEDAL_DIAMOND_FULL_COMBO, + "sfc": DBConstants.POPN_MUSIC_PLAY_MEDAL_STAR_FULL_COMBO, + "p": DBConstants.POPN_MUSIC_PLAY_MEDAL_PERFECT, + }.get(data.get("status"), DBConstants.POPN_MUSIC_PLAY_MEDAL_CIRCLE_FAILED) return Score( -1, songid, songchart, - int(data.get('points', 0)), - int(data.get('timestamp', -1)), - self.__max(int(data.get('timestamp', -1)), int(data.get('updated', -1))), + int(data.get("points", 0)), + int(data.get("timestamp", -1)), + self.__max(int(data.get("timestamp", -1)), int(data.get("updated", -1))), -1, # No location for remote play 1, # No play info for remote play { - 'medal': status, - 'combo': int(data.get('combo', -1)), + "medal": status, + "combo": int(data.get("combo", -1)), }, ) - def __format_reflec_score(self, version: int, songid: int, songchart: int, data: Dict[str, Any]) -> Score: + def __format_reflec_score( + self, version: int, songid: int, songchart: int, data: Dict[str, Any] + ) -> Score: status = { - 'np': DBConstants.REFLEC_BEAT_CLEAR_TYPE_NO_PLAY, - 'failed': DBConstants.REFLEC_BEAT_CLEAR_TYPE_FAILED, - 'cleared': DBConstants.REFLEC_BEAT_CLEAR_TYPE_CLEARED, - 'hc': DBConstants.REFLEC_BEAT_CLEAR_TYPE_HARD_CLEARED, - 'shc': DBConstants.REFLEC_BEAT_CLEAR_TYPE_S_HARD_CLEARED, - }.get(data.get('status'), DBConstants.REFLEC_BEAT_CLEAR_TYPE_NO_PLAY) + "np": DBConstants.REFLEC_BEAT_CLEAR_TYPE_NO_PLAY, + "failed": DBConstants.REFLEC_BEAT_CLEAR_TYPE_FAILED, + "cleared": DBConstants.REFLEC_BEAT_CLEAR_TYPE_CLEARED, + "hc": DBConstants.REFLEC_BEAT_CLEAR_TYPE_HARD_CLEARED, + "shc": DBConstants.REFLEC_BEAT_CLEAR_TYPE_S_HARD_CLEARED, + }.get(data.get("status"), DBConstants.REFLEC_BEAT_CLEAR_TYPE_NO_PLAY) halo = { - 'none': DBConstants.REFLEC_BEAT_COMBO_TYPE_NONE, - 'ac': DBConstants.REFLEC_BEAT_COMBO_TYPE_ALMOST_COMBO, - 'fc': DBConstants.REFLEC_BEAT_COMBO_TYPE_FULL_COMBO, - 'fcaj': DBConstants.REFLEC_BEAT_COMBO_TYPE_FULL_COMBO_ALL_JUST, - }.get(data.get('halo'), DBConstants.REFLEC_BEAT_COMBO_TYPE_NONE) + "none": DBConstants.REFLEC_BEAT_COMBO_TYPE_NONE, + "ac": DBConstants.REFLEC_BEAT_COMBO_TYPE_ALMOST_COMBO, + "fc": DBConstants.REFLEC_BEAT_COMBO_TYPE_FULL_COMBO, + "fcaj": DBConstants.REFLEC_BEAT_COMBO_TYPE_FULL_COMBO_ALL_JUST, + }.get(data.get("halo"), DBConstants.REFLEC_BEAT_COMBO_TYPE_NONE) return Score( -1, songid, songchart, - int(data.get('points', 0)), - int(data.get('timestamp', -1)), - self.__max(int(data.get('timestamp', -1)), int(data.get('updated', -1))), + int(data.get("points", 0)), + int(data.get("timestamp", -1)), + self.__max(int(data.get("timestamp", -1)), int(data.get("updated", -1))), -1, # No location for remote play 1, # No play info for remote play { - 'achievement_rate': int(data.get('rate', -1)), - 'clear_type': status, - 'combo_type': halo, - 'miss_count': int(data.get('miss', -1)), - 'combo': int(data.get('combo', -1)), + "achievement_rate": int(data.get("rate", -1)), + "clear_type": status, + "combo_type": halo, + "miss_count": int(data.get("miss", -1)), + "combo": int(data.get("combo", -1)), }, ) - def __format_sdvx_score(self, version: int, songid: int, songchart: int, data: Dict[str, Any]) -> Score: + def __format_sdvx_score( + self, version: int, songid: int, songchart: int, data: Dict[str, Any] + ) -> Score: status = { - 'np': DBConstants.SDVX_CLEAR_TYPE_NO_PLAY, - 'failed': DBConstants.SDVX_CLEAR_TYPE_FAILED, - 'cleared': DBConstants.SDVX_CLEAR_TYPE_CLEAR, - 'hc': DBConstants.SDVX_CLEAR_TYPE_HARD_CLEAR, - 'uc': DBConstants.SDVX_CLEAR_TYPE_ULTIMATE_CHAIN, - 'puc': DBConstants.SDVX_CLEAR_TYPE_PERFECT_ULTIMATE_CHAIN, - }.get(data.get('status'), DBConstants.SDVX_CLEAR_TYPE_NO_PLAY) + "np": DBConstants.SDVX_CLEAR_TYPE_NO_PLAY, + "failed": DBConstants.SDVX_CLEAR_TYPE_FAILED, + "cleared": DBConstants.SDVX_CLEAR_TYPE_CLEAR, + "hc": DBConstants.SDVX_CLEAR_TYPE_HARD_CLEAR, + "uc": DBConstants.SDVX_CLEAR_TYPE_ULTIMATE_CHAIN, + "puc": DBConstants.SDVX_CLEAR_TYPE_PERFECT_ULTIMATE_CHAIN, + }.get(data.get("status"), DBConstants.SDVX_CLEAR_TYPE_NO_PLAY) rank = { - 'E': DBConstants.SDVX_GRADE_NO_PLAY, - 'D': DBConstants.SDVX_GRADE_D, - 'C': DBConstants.SDVX_GRADE_C, - 'B': DBConstants.SDVX_GRADE_B, - 'A': DBConstants.SDVX_GRADE_A, - 'A+': DBConstants.SDVX_GRADE_A_PLUS, - 'AA': DBConstants.SDVX_GRADE_AA, - 'AA+': DBConstants.SDVX_GRADE_AA_PLUS, - 'AAA': DBConstants.SDVX_GRADE_AAA, - 'AAA+': DBConstants.SDVX_GRADE_AAA_PLUS, - 'S': DBConstants.SDVX_GRADE_S, - }.get(data.get('rank'), DBConstants.SDVX_GRADE_NO_PLAY) + "E": DBConstants.SDVX_GRADE_NO_PLAY, + "D": DBConstants.SDVX_GRADE_D, + "C": DBConstants.SDVX_GRADE_C, + "B": DBConstants.SDVX_GRADE_B, + "A": DBConstants.SDVX_GRADE_A, + "A+": DBConstants.SDVX_GRADE_A_PLUS, + "AA": DBConstants.SDVX_GRADE_AA, + "AA+": DBConstants.SDVX_GRADE_AA_PLUS, + "AAA": DBConstants.SDVX_GRADE_AAA, + "AAA+": DBConstants.SDVX_GRADE_AAA_PLUS, + "S": DBConstants.SDVX_GRADE_S, + }.get(data.get("rank"), DBConstants.SDVX_GRADE_NO_PLAY) return Score( -1, songid, songchart, - int(data.get('points', 0)), - int(data.get('timestamp', -1)), - self.__max(int(data.get('timestamp', -1)), int(data.get('updated', -1))), + int(data.get("points", 0)), + int(data.get("timestamp", -1)), + self.__max(int(data.get("timestamp", -1)), int(data.get("updated", -1))), -1, # No location for remote play 1, # No play info for remote play { - 'grade': rank, - 'clear_type': status, - 'combo': int(data.get('combo', -1)), - 'stats': { - 'btn_rate': int(data.get('buttonrate', -1)), - 'long_rate': int(data.get('longrate', -1)), - 'vol_rate': int(data.get('volrate', -1)), + "grade": rank, + "clear_type": status, + "combo": int(data.get("combo", -1)), + "stats": { + "btn_rate": int(data.get("buttonrate", -1)), + "long_rate": int(data.get("longrate", -1)), + "vol_rate": int(data.get("volrate", -1)), }, }, ) - def __format_score(self, game: GameConstants, version: int, songid: int, songchart: int, data: Dict[str, Any]) -> Optional[Score]: + def __format_score( + self, + game: GameConstants, + version: int, + songid: int, + songchart: int, + data: Dict[str, Any], + ) -> Optional[Score]: if game == GameConstants.DDR: return self.__format_ddr_score(version, songid, songchart, data) if game == GameConstants.IIDX: @@ -307,136 +335,214 @@ class GlobalMusicData(BaseGlobalData): return self.__format_sdvx_score(version, songid, songchart, data) return None - def __merge_ddr_score(self, version: int, oldscore: Score, newscore: Score) -> Score: + def __merge_ddr_score( + self, version: int, oldscore: Score, newscore: Score + ) -> Score: return Score( -1, oldscore.id, oldscore.chart, self.__max(oldscore.points, newscore.points), self.__max(oldscore.timestamp, newscore.timestamp), - self.__max(self.__max(oldscore.update, newscore.update), self.__max(oldscore.timestamp, newscore.timestamp)), + self.__max( + self.__max(oldscore.update, newscore.update), + self.__max(oldscore.timestamp, newscore.timestamp), + ), oldscore.location, # Always propagate location from local setup if possible oldscore.plays + newscore.plays, { - 'rank': self.__max(oldscore.data['rank'], newscore.data['rank']), - 'halo': self.__max(oldscore.data['halo'], newscore.data['halo']), - 'ghost': oldscore.data.get('ghost') if oldscore.points > newscore.points else newscore.data.get('ghost'), - 'trace': oldscore.data.get('trace') if oldscore.points > newscore.points else newscore.data.get('trace'), - 'combo': self.__max(oldscore.data['combo'], newscore.data['combo']), + "rank": self.__max(oldscore.data["rank"], newscore.data["rank"]), + "halo": self.__max(oldscore.data["halo"], newscore.data["halo"]), + "ghost": oldscore.data.get("ghost") + if oldscore.points > newscore.points + else newscore.data.get("ghost"), + "trace": oldscore.data.get("trace") + if oldscore.points > newscore.points + else newscore.data.get("trace"), + "combo": self.__max(oldscore.data["combo"], newscore.data["combo"]), }, ) - def __merge_iidx_score(self, version: int, oldscore: Score, newscore: Score) -> Score: + def __merge_iidx_score( + self, version: int, oldscore: Score, newscore: Score + ) -> Score: return Score( -1, oldscore.id, oldscore.chart, self.__max(oldscore.points, newscore.points), self.__max(oldscore.timestamp, newscore.timestamp), - self.__max(self.__max(oldscore.update, newscore.update), self.__max(oldscore.timestamp, newscore.timestamp)), + self.__max( + self.__max(oldscore.update, newscore.update), + self.__max(oldscore.timestamp, newscore.timestamp), + ), oldscore.location, # Always propagate location from local setup if possible oldscore.plays + newscore.plays, { - 'clear_status': self.__max(oldscore.data['clear_status'], newscore.data['clear_status']), - 'ghost': oldscore.data.get('ghost') if oldscore.points > newscore.points else newscore.data.get('ghost'), - 'miss_count': self.__min(oldscore.data.get_int('miss_count', -1), newscore.data.get_int('miss_count', -1)), - 'pgreats': oldscore.data.get_int('pgreats', -1) if oldscore.points > newscore.points else newscore.data.get_int('pgreats', -1), - 'greats': oldscore.data.get_int('greats', -1) if oldscore.points > newscore.points else newscore.data.get_int('greats', -1), + "clear_status": self.__max( + oldscore.data["clear_status"], newscore.data["clear_status"] + ), + "ghost": oldscore.data.get("ghost") + if oldscore.points > newscore.points + else newscore.data.get("ghost"), + "miss_count": self.__min( + oldscore.data.get_int("miss_count", -1), + newscore.data.get_int("miss_count", -1), + ), + "pgreats": oldscore.data.get_int("pgreats", -1) + if oldscore.points > newscore.points + else newscore.data.get_int("pgreats", -1), + "greats": oldscore.data.get_int("greats", -1) + if oldscore.points > newscore.points + else newscore.data.get_int("greats", -1), }, ) - def __merge_jubeat_score(self, version: int, oldscore: Score, newscore: Score) -> Score: + def __merge_jubeat_score( + self, version: int, oldscore: Score, newscore: Score + ) -> Score: return Score( -1, oldscore.id, oldscore.chart, self.__max(oldscore.points, newscore.points), self.__max(oldscore.timestamp, newscore.timestamp), - self.__max(self.__max(oldscore.update, newscore.update), self.__max(oldscore.timestamp, newscore.timestamp)), + self.__max( + self.__max(oldscore.update, newscore.update), + self.__max(oldscore.timestamp, newscore.timestamp), + ), oldscore.location, # Always propagate location from local setup if possible oldscore.plays + newscore.plays, { - 'ghost': oldscore.data.get('ghost') if oldscore.points > newscore.points else newscore.data.get('ghost'), - 'combo': self.__max(oldscore.data['combo'], newscore.data['combo']), - 'medal': self.__max(oldscore.data['medal'], newscore.data['medal']), - 'music_rate': self.__max(oldscore.data['music_rate'], newscore.data['music_rate']), + "ghost": oldscore.data.get("ghost") + if oldscore.points > newscore.points + else newscore.data.get("ghost"), + "combo": self.__max(oldscore.data["combo"], newscore.data["combo"]), + "medal": self.__max(oldscore.data["medal"], newscore.data["medal"]), + "music_rate": self.__max( + oldscore.data["music_rate"], newscore.data["music_rate"] + ), }, ) - def __merge_museca_score(self, version: int, oldscore: Score, newscore: Score) -> Score: + def __merge_museca_score( + self, version: int, oldscore: Score, newscore: Score + ) -> Score: return Score( -1, oldscore.id, oldscore.chart, self.__max(oldscore.points, newscore.points), self.__max(oldscore.timestamp, newscore.timestamp), - self.__max(self.__max(oldscore.update, newscore.update), self.__max(oldscore.timestamp, newscore.timestamp)), + self.__max( + self.__max(oldscore.update, newscore.update), + self.__max(oldscore.timestamp, newscore.timestamp), + ), oldscore.location, # Always propagate location from local setup if possible oldscore.plays + newscore.plays, { - 'grade': self.__max(oldscore.data['grade'], newscore.data['grade']), - 'clear_type': self.__max(oldscore.data['clear_type'], newscore.data['clear_type']), - 'combo': self.__max(oldscore.data['combo'], newscore.data['combo']), - 'stats': oldscore.data['stats'] if oldscore.points > newscore.points else newscore.data['stats'], + "grade": self.__max(oldscore.data["grade"], newscore.data["grade"]), + "clear_type": self.__max( + oldscore.data["clear_type"], newscore.data["clear_type"] + ), + "combo": self.__max(oldscore.data["combo"], newscore.data["combo"]), + "stats": oldscore.data["stats"] + if oldscore.points > newscore.points + else newscore.data["stats"], }, ) - def __merge_popn_score(self, version: int, oldscore: Score, newscore: Score) -> Score: + def __merge_popn_score( + self, version: int, oldscore: Score, newscore: Score + ) -> Score: return Score( -1, oldscore.id, oldscore.chart, self.__max(oldscore.points, newscore.points), self.__max(oldscore.timestamp, newscore.timestamp), - self.__max(self.__max(oldscore.update, newscore.update), self.__max(oldscore.timestamp, newscore.timestamp)), + self.__max( + self.__max(oldscore.update, newscore.update), + self.__max(oldscore.timestamp, newscore.timestamp), + ), oldscore.location, # Always propagate location from local setup if possible oldscore.plays + newscore.plays, { - 'combo': self.__max(oldscore.data['combo'], newscore.data['combo']), - 'medal': self.__max(oldscore.data['medal'], newscore.data['medal']), + "combo": self.__max(oldscore.data["combo"], newscore.data["combo"]), + "medal": self.__max(oldscore.data["medal"], newscore.data["medal"]), }, ) - def __merge_reflec_score(self, version: int, oldscore: Score, newscore: Score) -> Score: + def __merge_reflec_score( + self, version: int, oldscore: Score, newscore: Score + ) -> Score: return Score( -1, oldscore.id, oldscore.chart, self.__max(oldscore.points, newscore.points), self.__max(oldscore.timestamp, newscore.timestamp), - self.__max(self.__max(oldscore.update, newscore.update), self.__max(oldscore.timestamp, newscore.timestamp)), + self.__max( + self.__max(oldscore.update, newscore.update), + self.__max(oldscore.timestamp, newscore.timestamp), + ), oldscore.location, # Always propagate location from local setup if possible oldscore.plays + newscore.plays, { - 'clear_type': self.__max(oldscore.data['clear_type'], newscore.data['clear_type']), - 'combo_type': self.__max(oldscore.data['combo_type'], newscore.data['combo_type']), - 'miss_count': self.__min(oldscore.data.get_int('miss_count', -1), newscore.data.get_int('miss_count', -1)), - 'combo': self.__max(oldscore.data['combo'], newscore.data['combo']), - 'achievement_rate': self.__max(oldscore.data['achievement_rate'], newscore.data['achievement_rate']), + "clear_type": self.__max( + oldscore.data["clear_type"], newscore.data["clear_type"] + ), + "combo_type": self.__max( + oldscore.data["combo_type"], newscore.data["combo_type"] + ), + "miss_count": self.__min( + oldscore.data.get_int("miss_count", -1), + newscore.data.get_int("miss_count", -1), + ), + "combo": self.__max(oldscore.data["combo"], newscore.data["combo"]), + "achievement_rate": self.__max( + oldscore.data["achievement_rate"], newscore.data["achievement_rate"] + ), }, ) - def __merge_sdvx_score(self, version: int, oldscore: Score, newscore: Score) -> Score: + def __merge_sdvx_score( + self, version: int, oldscore: Score, newscore: Score + ) -> Score: return Score( -1, oldscore.id, oldscore.chart, self.__max(oldscore.points, newscore.points), self.__max(oldscore.timestamp, newscore.timestamp), - self.__max(self.__max(oldscore.update, newscore.update), self.__max(oldscore.timestamp, newscore.timestamp)), + self.__max( + self.__max(oldscore.update, newscore.update), + self.__max(oldscore.timestamp, newscore.timestamp), + ), oldscore.location, # Always propagate location from local setup if possible oldscore.plays + newscore.plays, { - 'grade': self.__max(oldscore.data['grade'], newscore.data['grade']), - 'clear_type': self.__max(oldscore.data['clear_type'], newscore.data['clear_type']), - 'combo': self.__max(oldscore.data.get_int('combo', 1), newscore.data.get_int('combo', -1)), - 'stats': oldscore.data['stats'] if oldscore.points > newscore.points else newscore.data['stats'], + "grade": self.__max(oldscore.data["grade"], newscore.data["grade"]), + "clear_type": self.__max( + oldscore.data["clear_type"], newscore.data["clear_type"] + ), + "combo": self.__max( + oldscore.data.get_int("combo", 1), + newscore.data.get_int("combo", -1), + ), + "stats": oldscore.data["stats"] + if oldscore.points > newscore.points + else newscore.data["stats"], }, ) - def __merge_score(self, game: GameConstants, version: int, oldscore: Score, newscore: Score) -> Score: + def __merge_score( + self, game: GameConstants, version: int, oldscore: Score, newscore: Score + ) -> Score: if oldscore.id != newscore.id or oldscore.chart != newscore.chart: - raise Exception('Logic error! Tried to merge scores from different song/charts!') + raise Exception( + "Logic error! Tried to merge scores from different song/charts!" + ) if game == GameConstants.DDR: return self.__merge_ddr_score(version, oldscore, newscore) @@ -455,40 +561,57 @@ class GlobalMusicData(BaseGlobalData): return oldscore - def get_score(self, game: GameConstants, version: int, userid: UserID, songid: int, songchart: int) -> Optional[Score]: + def get_score( + self, + game: GameConstants, + version: int, + userid: UserID, + songid: int, + songchart: int, + ) -> Optional[Score]: # Helper function so we can iterate over all servers for a single card def get_scores_for_card(cardid: str) -> List[Score]: - return Parallel.flatten(Parallel.call( - [client.get_records for client in self.clients], - game, - version, - APIConstants.ID_TYPE_INSTANCE, - [songid, songchart, cardid], - )) + return Parallel.flatten( + Parallel.call( + [client.get_records for client in self.clients], + game, + version, + APIConstants.ID_TYPE_INSTANCE, + [songid, songchart, cardid], + ) + ) relevant_cards = self.__get_cardids(userid) if RemoteUser.is_remote(userid): # No need to look up local score for this user - scores = Parallel.flatten(Parallel.map( - get_scores_for_card, - relevant_cards, - )) - localscore = None - else: - localscore, scores = Parallel.execute([ - lambda: self.music.get_score(game, version, userid, songid, songchart), - lambda: Parallel.flatten(Parallel.map( + scores = Parallel.flatten( + Parallel.map( get_scores_for_card, relevant_cards, - )), - ]) + ) + ) + localscore = None + else: + localscore, scores = Parallel.execute( + [ + lambda: self.music.get_score( + game, version, userid, songid, songchart + ), + lambda: Parallel.flatten( + Parallel.map( + get_scores_for_card, + relevant_cards, + ) + ), + ] + ) topscore = localscore for score in scores: - if int(score['song']) != songid: + if int(score["song"]) != songid: continue - if int(score['chart']) != songchart: + if int(score["chart"]) != songchart: continue newscore = self.__format_score(game, version, songid, songchart, score) @@ -507,26 +630,14 @@ class GlobalMusicData(BaseGlobalData): game: GameConstants, version: int, userid: UserID, - since: Optional[int]=None, - until: Optional[int]=None, + since: Optional[int] = None, + until: Optional[int] = None, ) -> List[Score]: relevant_cards = self.__get_cardids(userid) if RemoteUser.is_remote(userid): # No need to look up local score for this user - scores = Parallel.flatten(Parallel.call( - [client.get_records for client in self.clients], - game, - version, - APIConstants.ID_TYPE_CARD, - relevant_cards, - since, - until, - )) - localscores: List[Score] = [] - else: - localscores, scores = Parallel.execute([ - lambda: self.music.get_scores(game, version, userid, since, until), - lambda: Parallel.flatten(Parallel.call( + scores = Parallel.flatten( + Parallel.call( [client.get_records for client in self.clients], game, version, @@ -534,8 +645,26 @@ class GlobalMusicData(BaseGlobalData): relevant_cards, since, until, - )), - ]) + ) + ) + localscores: List[Score] = [] + else: + localscores, scores = Parallel.execute( + [ + lambda: self.music.get_scores(game, version, userid, since, until), + lambda: Parallel.flatten( + Parallel.call( + [client.get_records for client in self.clients], + game, + version, + APIConstants.ID_TYPE_CARD, + relevant_cards, + since, + until, + ) + ), + ] + ) allscores: Dict[int, Dict[int, Score]] = {} @@ -553,8 +682,8 @@ class GlobalMusicData(BaseGlobalData): # Second, merge in remote scorse for remotescore in scores: - songid = int(remotescore['song']) - chart = int(remotescore['chart']) + songid = int(remotescore["song"]) + chart = int(remotescore["chart"]) newscore = self.__format_score(game, version, songid, chart, remotescore) oldscore = get_score(songid, chart) @@ -599,7 +728,7 @@ class GlobalMusicData(BaseGlobalData): # Second, merge in remote scorse for remotescore in remotescores: # Figure out the userid of this score - cardids = sorted([card.upper() for card in remotescore.get('cards', [])]) + cardids = sorted([card.upper() for card in remotescore.get("cards", [])]) if len(cardids) == 0: continue @@ -610,8 +739,8 @@ class GlobalMusicData(BaseGlobalData): else: userid = RemoteUser.card_to_userid(cardids[0]) - songid = int(remotescore['song']) - chart = int(remotescore['chart']) + songid = int(remotescore["song"]) + chart = int(remotescore["chart"]) newscore = self.__format_score(game, version, songid, chart, remotescore) oldscore = get_score(userid, songid, chart) @@ -632,20 +761,18 @@ class GlobalMusicData(BaseGlobalData): def get_all_scores( self, game: GameConstants, - version: Optional[int]=None, - userid: Optional[UserID]=None, - songid: Optional[int]=None, - songchart: Optional[int]=None, - since: Optional[int]=None, - until: Optional[int]=None, + version: Optional[int] = None, + userid: Optional[UserID] = None, + songid: Optional[int] = None, + songchart: Optional[int] = None, + since: Optional[int] = None, + until: Optional[int] = None, ) -> List[Tuple[UserID, Score]]: # First, pass off to local-only if this was called with parameters we don't support - if ( - version is None or - userid is not None or - songid is None - ): - return self.music.get_all_scores(game, version, userid, songid, songchart, since, until) + if version is None or userid is not None or songid is None: + return self.music.get_all_scores( + game, version, userid, songid, songchart, since, until + ) # Now, figure out the request key based on passed in parameters if songchart is None: @@ -654,21 +781,29 @@ class GlobalMusicData(BaseGlobalData): songkey = [songid, songchart] # Now, fetch all the scores remotely and locally - localcards, localscores, remotescores = Parallel.execute([ - self.user.get_all_cards, - lambda: self.music.get_all_scores(game, version, userid, songid, songchart, since, until), - lambda: Parallel.flatten(Parallel.call( - [client.get_records for client in self.clients], - game, - version, - APIConstants.ID_TYPE_SONG, - songkey, - since, - until, - )), - ]) + localcards, localscores, remotescores = Parallel.execute( + [ + self.user.get_all_cards, + lambda: self.music.get_all_scores( + game, version, userid, songid, songchart, since, until + ), + lambda: Parallel.flatten( + Parallel.call( + [client.get_records for client in self.clients], + game, + version, + APIConstants.ID_TYPE_SONG, + songkey, + since, + until, + ) + ), + ] + ) - return self.__merge_global_scores(game, version, localcards, localscores, remotescores) + return self.__merge_global_scores( + game, version, localcards, localscores, remotescores + ) def __merge_global_records( self, @@ -686,7 +821,9 @@ class GlobalMusicData(BaseGlobalData): allscores[score.id] = {} allscores[score.id][score.chart] = (userid, score) - def get_score(songid: int, songchart: int) -> Tuple[Optional[UserID], Optional[Score]]: + def get_score( + songid: int, songchart: int + ) -> Tuple[Optional[UserID], Optional[Score]]: return allscores.get(songid, {}).get(songchart, (None, None)) # First, seed with local records @@ -696,7 +833,7 @@ class GlobalMusicData(BaseGlobalData): # Second, merge in remote records for remotescore in remotescores: # Figure out the userid of this score - cardids = sorted([card.upper() for card in remotescore.get('cards', [])]) + cardids = sorted([card.upper() for card in remotescore.get("cards", [])]) if len(cardids) == 0: continue @@ -707,8 +844,8 @@ class GlobalMusicData(BaseGlobalData): else: userid = RemoteUser.card_to_userid(cardids[0]) - songid = int(remotescore['song']) - chart = int(remotescore['chart']) + songid = int(remotescore["song"]) + chart = int(remotescore["chart"]) newscore = self.__format_score(game, version, songid, chart, remotescore) oldid, oldscore = get_score(songid, chart) @@ -717,7 +854,9 @@ class GlobalMusicData(BaseGlobalData): else: # if IDs are the same then we should merge them if oldid == userid: - add_score(userid, self.__merge_score(game, version, oldscore, newscore)) + add_score( + userid, self.__merge_score(game, version, oldscore, newscore) + ) else: # if the IDs are different we need to check which score actually belongs if newscore.points > oldscore.points: @@ -727,46 +866,52 @@ class GlobalMusicData(BaseGlobalData): finalscores: List[Tuple[UserID, Score]] = [] for songid in allscores: for chart in allscores[songid]: - finalscores.append((allscores[songid][chart][0], allscores[songid][chart][1])) + finalscores.append( + (allscores[songid][chart][0], allscores[songid][chart][1]) + ) return finalscores def get_all_records( self, game: GameConstants, - version: Optional[int]=None, - userlist: Optional[List[UserID]]=None, - locationlist: Optional[List[int]]=None, + version: Optional[int] = None, + userlist: Optional[List[UserID]] = None, + locationlist: Optional[List[int]] = None, ) -> List[Tuple[UserID, Score]]: # First, pass off to local-only if this was called with parameters we don't support - if ( - version is None or - userlist is not None or - locationlist is not None - ): + if version is None or userlist is not None or locationlist is not None: return self.music.get_all_records(game, version, userlist, locationlist) # Now, fetch all records remotely and locally - localcards, localscores, remotescores = Parallel.execute([ - self.user.get_all_cards, - lambda: self.music.get_all_records(game, version, userlist, locationlist), - lambda: Parallel.flatten(Parallel.call( - [client.get_records for client in self.clients], - game, - version, - APIConstants.ID_TYPE_SERVER, - [], - )), - ]) + localcards, localscores, remotescores = Parallel.execute( + [ + self.user.get_all_cards, + lambda: self.music.get_all_records( + game, version, userlist, locationlist + ), + lambda: Parallel.flatten( + Parallel.call( + [client.get_records for client in self.clients], + game, + version, + APIConstants.ID_TYPE_SERVER, + [], + ) + ), + ] + ) - return self.__merge_global_records(game, version, localcards, localscores, remotescores) + return self.__merge_global_records( + game, version, localcards, localscores, remotescores + ) def get_clear_rates( self, game: GameConstants, version: int, - songid: Optional[int]=None, - songchart: Optional[int]=None, + songid: Optional[int] = None, + songchart: Optional[int] = None, ) -> Dict[int, Dict[int, Dict[str, int]]]: """ Given an optional songid, or optional songid and songchart, looks up clear rates @@ -790,32 +935,36 @@ class GlobalMusicData(BaseGlobalData): """ if songid is None and songchart is None: - statistics = Parallel.flatten(Parallel.call( - [client.get_statistics for client in self.clients], - game, - version, - APIConstants.ID_TYPE_SERVER, - [], - )) + statistics = Parallel.flatten( + Parallel.call( + [client.get_statistics for client in self.clients], + game, + version, + APIConstants.ID_TYPE_SERVER, + [], + ) + ) elif songid is not None: if songchart is None: ids = [songid] else: ids = [songid, songchart] - statistics = Parallel.flatten(Parallel.call( - [client.get_statistics for client in self.clients], - game, - version, - APIConstants.ID_TYPE_SONG, - ids, - )) + statistics = Parallel.flatten( + Parallel.call( + [client.get_statistics for client in self.clients], + game, + version, + APIConstants.ID_TYPE_SONG, + ids, + ) + ) else: statistics = [] retval: Dict[int, Dict[int, Dict[str, int]]] = {} for stat in statistics: - songid = stat.get('song') - songchart = stat.get('chart') + songid = stat.get("song") + songchart = stat.get("chart") if songid is None or songchart is None: continue @@ -826,9 +975,9 @@ class GlobalMusicData(BaseGlobalData): retval[songid] = {} if songchart not in retval[songid]: retval[songid][songchart] = { - 'plays': 0, - 'clears': 0, - 'combos': 0, + "plays": 0, + "clears": 0, + "combos": 0, } def get_val(v: str) -> int: @@ -837,9 +986,9 @@ class GlobalMusicData(BaseGlobalData): out = 0 return out - retval[songid][songchart]['plays'] += get_val('plays') - retval[songid][songchart]['clears'] += get_val('clears') - retval[songid][songchart]['combos'] += get_val('combos') + retval[songid][songchart]["plays"] += get_val("plays") + retval[songid][songchart]["clears"] += get_val("clears") + retval[songid][songchart]["combos"] += get_val("combos") return retval @@ -862,18 +1011,18 @@ class GlobalMusicData(BaseGlobalData): artist=artist, genre=genre, data={ - 'groove': { - 'air': int(data['groove']['air']), - 'chaos': int(data['groove']['chaos']), - 'freeze': int(data['groove']['freeze']), - 'stream': int(data['groove']['stream']), - 'voltage': int(data['groove']['voltage']), + "groove": { + "air": int(data["groove"]["air"]), + "chaos": int(data["groove"]["chaos"]), + "freeze": int(data["groove"]["freeze"]), + "stream": int(data["groove"]["stream"]), + "voltage": int(data["groove"]["voltage"]), }, - 'bpm_min': int(data['bpm_min']), - 'bpm_max': int(data['bpm_max']), - 'category': int(data['category']), - 'difficulty': int(data['difficulty']), - 'edit_id': int(data['editid']), + "bpm_min": int(data["bpm_min"]), + "bpm_max": int(data["bpm_max"]), + "category": int(data["category"]), + "difficulty": int(data["difficulty"]), + "edit_id": int(data["editid"]), }, ) @@ -896,10 +1045,10 @@ class GlobalMusicData(BaseGlobalData): artist=artist, genre=genre, data={ - 'bpm_min': int(data['bpm_min']), - 'bpm_max': int(data['bpm_max']), - 'notecount': int(data['notecount']), - 'difficulty': int(data['difficulty']), + "bpm_min": int(data["bpm_min"]), + "bpm_max": int(data["bpm_max"]), + "notecount": int(data["notecount"]), + "difficulty": int(data["difficulty"]), }, ) @@ -922,23 +1071,23 @@ class GlobalMusicData(BaseGlobalData): 6: VersionConstants.JUBEAT_PROP, 7: VersionConstants.JUBEAT_QUBELL, 8: VersionConstants.JUBEAT_CLAN, - 9: VersionConstants.JUBEAT_FESTO + 9: VersionConstants.JUBEAT_FESTO, }.get(int(songid / 10000000), VersionConstants.JUBEAT) # Map the category to the version numbers defined on BEMAPI. categorymapping = { - '1': VersionConstants.JUBEAT, - '2': VersionConstants.JUBEAT_RIPPLES, - '2a': VersionConstants.JUBEAT_RIPPLES_APPEND, - '3': VersionConstants.JUBEAT_KNIT, - '3a': VersionConstants.JUBEAT_KNIT_APPEND, - '4': VersionConstants.JUBEAT_COPIOUS, - '4a': VersionConstants.JUBEAT_COPIOUS_APPEND, - '5': VersionConstants.JUBEAT_SAUCER, - '5a': VersionConstants.JUBEAT_SAUCER_FULFILL, - '6': VersionConstants.JUBEAT_PROP, - '7': VersionConstants.JUBEAT_QUBELL, - '8': VersionConstants.JUBEAT_CLAN, - '9': VersionConstants.JUBEAT_FESTO, + "1": VersionConstants.JUBEAT, + "2": VersionConstants.JUBEAT_RIPPLES, + "2a": VersionConstants.JUBEAT_RIPPLES_APPEND, + "3": VersionConstants.JUBEAT_KNIT, + "3a": VersionConstants.JUBEAT_KNIT_APPEND, + "4": VersionConstants.JUBEAT_COPIOUS, + "4a": VersionConstants.JUBEAT_COPIOUS_APPEND, + "5": VersionConstants.JUBEAT_SAUCER, + "5a": VersionConstants.JUBEAT_SAUCER_FULFILL, + "6": VersionConstants.JUBEAT_PROP, + "7": VersionConstants.JUBEAT_QUBELL, + "8": VersionConstants.JUBEAT_CLAN, + "9": VersionConstants.JUBEAT_FESTO, } return Song( game=GameConstants.JUBEAT, @@ -949,10 +1098,12 @@ class GlobalMusicData(BaseGlobalData): artist=artist, genre=genre, data={ - 'bpm_min': int(data['bpm_min']), - 'bpm_max': int(data['bpm_max']), - 'difficulty': int(data['difficulty']), - 'version': categorymapping.get(data.get('category', '0'), defaultcategory), + "bpm_min": int(data["bpm_min"]), + "bpm_max": int(data["bpm_max"]), + "difficulty": int(data["difficulty"]), + "version": categorymapping.get( + data.get("category", "0"), defaultcategory + ), }, ) @@ -975,10 +1126,10 @@ class GlobalMusicData(BaseGlobalData): artist=artist, genre=genre, data={ - 'bpm_min': int(data['bpm_min']), - 'bpm_max': int(data['bpm_max']), - 'limited': int(data['limited']), - 'difficulty': int(data['difficulty']), + "bpm_min": int(data["bpm_min"]), + "bpm_max": int(data["bpm_max"]), + "limited": int(data["limited"]), + "difficulty": int(data["difficulty"]), }, ) @@ -1001,8 +1152,8 @@ class GlobalMusicData(BaseGlobalData): artist=artist, genre=genre, data={ - 'difficulty': int(data['difficulty']), - 'category': str(data['category']), + "difficulty": int(data["difficulty"]), + "category": str(data["category"]), }, ) @@ -1025,9 +1176,9 @@ class GlobalMusicData(BaseGlobalData): artist=artist, genre=genre, data={ - 'difficulty': int(data['difficulty']), - 'folder': int(data['category']), - 'chart_id': str(data['musicid']), + "difficulty": int(data["difficulty"]), + "folder": int(data["category"]), + "chart_id": str(data["musicid"]), }, ) @@ -1050,10 +1201,10 @@ class GlobalMusicData(BaseGlobalData): artist=artist, genre=genre, data={ - 'bpm_min': int(data['bpm_min']), - 'bpm_max': int(data['bpm_max']), - 'limited': int(data['limited']), - 'difficulty': int(data['difficulty']), + "bpm_min": int(data["bpm_min"]), + "bpm_max": int(data["bpm_max"]), + "limited": int(data["limited"]), + "difficulty": int(data["difficulty"]), }, ) @@ -1069,25 +1220,39 @@ class GlobalMusicData(BaseGlobalData): data: Dict[str, Any], ) -> Optional[Song]: if game == GameConstants.DDR: - return self.__format_ddr_song(version, songid, songchart, name, artist, genre, data) + return self.__format_ddr_song( + version, songid, songchart, name, artist, genre, data + ) if game == GameConstants.IIDX: - return self.__format_iidx_song(version, songid, songchart, name, artist, genre, data) + return self.__format_iidx_song( + version, songid, songchart, name, artist, genre, data + ) if game == GameConstants.JUBEAT: - return self.__format_jubeat_song(version, songid, songchart, name, artist, genre, data) + return self.__format_jubeat_song( + version, songid, songchart, name, artist, genre, data + ) if game == GameConstants.MUSECA: - return self.__format_museca_song(version, songid, songchart, name, artist, genre, data) + return self.__format_museca_song( + version, songid, songchart, name, artist, genre, data + ) if game == GameConstants.POPN_MUSIC: - return self.__format_popn_song(version, songid, songchart, name, artist, genre, data) + return self.__format_popn_song( + version, songid, songchart, name, artist, genre, data + ) if game == GameConstants.REFLEC_BEAT: - return self.__format_reflec_song(version, songid, songchart, name, artist, genre, data) + return self.__format_reflec_song( + version, songid, songchart, name, artist, genre, data + ) if game == GameConstants.SDVX: - return self.__format_sdvx_song(version, songid, songchart, name, artist, genre, data) + return self.__format_sdvx_song( + version, songid, songchart, name, artist, genre, data + ) return None def get_all_songs( self, game: GameConstants, - version: Optional[int]=None, + version: Optional[int] = None, ) -> List[Song]: """ Given a game and a version, look up all song/chart combos associated with that game. @@ -1106,22 +1271,20 @@ class GlobalMusicData(BaseGlobalData): return [] catalogs: List[Dict[str, List[Dict[str, Any]]]] = Parallel.call( - [client.get_catalog for client in self.clients], - game, - version + [client.get_catalog for client in self.clients], game, version ) retval: List[Song] = [] seen: Set[str] = set() for catalog in catalogs: - for entry in catalog.get('songs', []): + for entry in catalog.get("songs", []): song = self.__format_song( game, version, - int(entry['song']), - int(entry['chart']), - str(entry['title'] if entry['title'] is not None else "") or None, - str(entry['artist'] if entry['artist'] is not None else "") or None, - str(entry['genre'] if entry['genre'] is not None else "") or None, + int(entry["song"]), + int(entry["chart"]), + str(entry["title"] if entry["title"] is not None else "") or None, + str(entry["artist"] if entry["artist"] is not None else "") or None, + str(entry["genre"] if entry["genre"] is not None else "") or None, entry, ) if song is None: diff --git a/bemani/data/api/user.py b/bemani/data/api/user.py index 1650602..9c3ca14 100644 --- a/bemani/data/api/user.py +++ b/bemani/data/api/user.py @@ -9,40 +9,39 @@ from bemani.data.types import UserID class GlobalUserData(BaseGlobalData): - def __init__(self, api: APIProviderInterface, user: UserData) -> None: super().__init__(api) self.user = user def __format_ddr_profile(self, updates: Profile, profile: Profile) -> None: - area = profile.get_int('area', -1) + area = profile.get_int("area", -1) if area != -1: - updates['area'] = area + updates["area"] = area def __format_iidx_profile(self, updates: Profile, profile: Profile) -> None: - area = profile.get_int('area', -1) + area = profile.get_int("area", -1) if area != -1: - updates['pid'] = area + updates["pid"] = area - qpro = profile.get_dict('qpro') - updates['qpro'] = {} + qpro = profile.get_dict("qpro") + updates["qpro"] = {} - head = qpro.get_int('head', -1) + head = qpro.get_int("head", -1) if head != -1: - updates['qpro']['head'] = head - hair = qpro.get_int('hair', -1) + updates["qpro"]["head"] = head + hair = qpro.get_int("hair", -1) if hair != -1: - updates['qpro']['hair'] = hair - face = qpro.get_int('face', -1) + updates["qpro"]["hair"] = hair + face = qpro.get_int("face", -1) if face != -1: - updates['qpro']['face'] = face - body = qpro.get_int('body', -1) + updates["qpro"]["face"] = face + body = qpro.get_int("body", -1) if body != -1: - updates['qpro']['body'] = body - hand = qpro.get_int('hand', -1) + updates["qpro"]["body"] = body + hand = qpro.get_int("hand", -1) if hand != -1: - updates['qpro']['hand'] = hand + updates["qpro"]["hand"] = hand def __format_jubeat_profile(self, updates: Profile, profile: Profile) -> None: pass @@ -51,14 +50,14 @@ class GlobalUserData(BaseGlobalData): pass def __format_popn_profile(self, updates: Profile, profile: Profile) -> None: - chara = profile.get_int('character', -1) + chara = profile.get_int("character", -1) if chara != -1: - updates['chara'] = chara + updates["chara"] = chara def __format_reflec_profile(self, updates: Profile, profile: Profile) -> None: - icon = profile.get_int('icon', -1) + icon = profile.get_int("icon", -1) if icon != -1: - updates['config'] = {'icon_id': icon} + updates["config"] = {"icon_id": icon} def __format_sdvx_profile(self, updates: Profile, profile: Profile) -> None: pass @@ -70,7 +69,7 @@ class GlobalUserData(BaseGlobalData): profile.refid, profile.extid, { - 'name': profile.get('name', ''), + "name": profile.get("name", ""), }, ) @@ -91,24 +90,28 @@ class GlobalUserData(BaseGlobalData): return new - def __profile_request(self, game: GameConstants, version: int, userid: UserID, exact: bool) -> Optional[Profile]: + def __profile_request( + self, game: GameConstants, version: int, userid: UserID, exact: bool + ) -> Optional[Profile]: # First, get or create the extid/refid for this virtual user cardid = RemoteUser.userid_to_card(userid) refid = self.user.get_refid(game, version, userid) extid = self.user.get_extid(game, version, userid) - profiles = Parallel.flatten(Parallel.call( - [client.get_profiles for client in self.clients], - game, - version, - APIConstants.ID_TYPE_CARD, - [cardid], - )) + profiles = Parallel.flatten( + Parallel.call( + [client.get_profiles for client in self.clients], + game, + version, + APIConstants.ID_TYPE_CARD, + [cardid], + ) + ) for profile in profiles: - cards = [card.upper() for card in profile.get('cards', [])] + cards = [card.upper() for card in profile.get("cards", [])] if cardid in cards: # Don't take non-exact matches. - exact_match = profile.get('match', 'partial') == 'exact' + exact_match = profile.get("match", "partial") == "exact" if exact and (not exact_match): # This is a partial match, not for this game/version continue @@ -132,36 +135,40 @@ class GlobalUserData(BaseGlobalData): userid = RemoteUser.card_to_userid(cardid) return userid - def from_refid(self, game: GameConstants, version: int, refid: str) -> Optional[UserID]: + def from_refid( + self, game: GameConstants, version: int, refid: str + ) -> Optional[UserID]: return self.user.from_refid(game, version, refid) - def from_extid(self, game: GameConstants, version: int, extid: int) -> Optional[UserID]: + def from_extid( + self, game: GameConstants, version: int, extid: int + ) -> Optional[UserID]: return self.user.from_extid(game, version, extid) - def get_profile(self, game: GameConstants, version: int, userid: UserID) -> Optional[Profile]: + def get_profile( + self, game: GameConstants, version: int, userid: UserID + ) -> Optional[Profile]: if RemoteUser.is_remote(userid): return self.__profile_request(game, version, userid, exact=True) else: return self.user.get_profile(game, version, userid) - def get_any_profile(self, game: GameConstants, version: int, userid: UserID) -> Optional[Profile]: + def get_any_profile( + self, game: GameConstants, version: int, userid: UserID + ) -> Optional[Profile]: if RemoteUser.is_remote(userid): return self.__profile_request(game, version, userid, exact=False) else: return self.user.get_any_profile(game, version, userid) - def get_any_profiles(self, game: GameConstants, version: int, userids: List[UserID]) -> List[Tuple[UserID, Optional[Profile]]]: + def get_any_profiles( + self, game: GameConstants, version: int, userids: List[UserID] + ) -> List[Tuple[UserID, Optional[Profile]]]: if len(userids) == 0: return [] - remote_ids = [ - userid for userid in userids - if RemoteUser.is_remote(userid) - ] - local_ids = [ - userid for userid in userids - if not RemoteUser.is_remote(userid) - ] + remote_ids = [userid for userid in userids if RemoteUser.is_remote(userid)] + local_ids = [userid for userid in userids if not RemoteUser.is_remote(userid)] if len(remote_ids) == 0: # We only have local profiles here, just pass on to the underlying layer @@ -170,23 +177,29 @@ class GlobalUserData(BaseGlobalData): # We have to fetch some local profiles and some remote profiles, and then # merge them together card_to_userid = { - RemoteUser.userid_to_card(userid): userid - for userid in remote_ids + RemoteUser.userid_to_card(userid): userid for userid in remote_ids } - local_profiles, remote_profiles = Parallel.execute([ - lambda: self.user.get_any_profiles(game, version, local_ids), - lambda: Parallel.flatten(Parallel.call( - [client.get_profiles for client in self.clients], - game, - version, - APIConstants.ID_TYPE_CARD, - [RemoteUser.userid_to_card(userid) for userid in remote_ids], - )) - ]) + local_profiles, remote_profiles = Parallel.execute( + [ + lambda: self.user.get_any_profiles(game, version, local_ids), + lambda: Parallel.flatten( + Parallel.call( + [client.get_profiles for client in self.clients], + game, + version, + APIConstants.ID_TYPE_CARD, + [ + RemoteUser.userid_to_card(userid) + for userid in remote_ids + ], + ) + ), + ] + ) for profile in remote_profiles: - cards = [card.upper() for card in profile.get('cards', [])] + cards = [card.upper() for card in profile.get("cards", [])] for card in cards: # Map it back to the requested user userid = card_to_userid.get(card) @@ -194,7 +207,7 @@ class GlobalUserData(BaseGlobalData): continue # Sanitize the returned data - exact_match = profile.get('match', 'partial') == 'exact' + exact_match = profile.get("match", "partial") == "exact" refid = self.user.get_refid(game, version, userid) extid = self.user.get_extid(game, version, userid) @@ -223,26 +236,32 @@ class GlobalUserData(BaseGlobalData): return local_profiles - def get_all_profiles(self, game: GameConstants, version: int) -> List[Tuple[UserID, Profile]]: + def get_all_profiles( + self, game: GameConstants, version: int + ) -> List[Tuple[UserID, Profile]]: # Fetch local and remote profiles, and then merge by adding remote profiles to local # profiles when we don't have a profile for that user ID yet. - local_cards, local_profiles, remote_profiles = Parallel.execute([ - self.user.get_all_cards, - lambda: self.user.get_all_profiles(game, version), - lambda: Parallel.flatten(Parallel.call( - [client.get_profiles for client in self.clients], - game, - version, - APIConstants.ID_TYPE_SERVER, - [], - )), - ]) + local_cards, local_profiles, remote_profiles = Parallel.execute( + [ + self.user.get_all_cards, + lambda: self.user.get_all_profiles(game, version), + lambda: Parallel.flatten( + Parallel.call( + [client.get_profiles for client in self.clients], + game, + version, + APIConstants.ID_TYPE_SERVER, + [], + ) + ), + ] + ) card_to_id = {cardid: userid for (cardid, userid) in local_cards} id_to_profile = {userid: profile for (userid, profile) in local_profiles} for profile in remote_profiles: - cardids = sorted([card.upper() for card in profile.get('cards', [])]) + cardids = sorted([card.upper() for card in profile.get("cards", [])]) if len(cardids) == 0: # We don't care about anonymous profiles continue @@ -252,7 +271,7 @@ class GlobalUserData(BaseGlobalData): # We have a local version of this profile! continue - exact_match = profile.get('match', 'partial') == 'exact' + exact_match = profile.get("match", "partial") == "exact" if not exact_match: continue diff --git a/bemani/data/config.py b/bemani/data/config.py index f3b2929..dc8f3e8 100644 --- a/bemani/data/config.py +++ b/bemani/data/config.py @@ -13,32 +13,36 @@ class Database: @property def address(self) -> str: - return str(self.__config.get('database', {}).get('address', 'localhost')) + return str(self.__config.get("database", {}).get("address", "localhost")) @property def database(self) -> str: - return str(self.__config.get('database', {}).get('database', 'bemani')) + return str(self.__config.get("database", {}).get("database", "bemani")) @property def user(self) -> str: - return str(self.__config.get('database', {}).get('user', 'bemani')) + return str(self.__config.get("database", {}).get("user", "bemani")) @property def password(self) -> str: - return str(self.__config.get('database', {}).get('password', 'bemani')) + return str(self.__config.get("database", {}).get("password", "bemani")) @property def engine(self) -> Engine: - engine = self.__config.get('database', {}).get('engine') + engine = self.__config.get("database", {}).get("engine") if engine is None: - raise Exception("Config object is not instantiated properly, no SQLAlchemy engine present!") + raise Exception( + "Config object is not instantiated properly, no SQLAlchemy engine present!" + ) if not isinstance(engine, Engine): - raise Exception("Config object is not instantiated properly, engine property is not a SQLAlchemy Engine!") + raise Exception( + "Config object is not instantiated properly, engine property is not a SQLAlchemy Engine!" + ) return engine @property def read_only(self) -> bool: - return bool(self.__config.get('database', {}).get('read_only', False)) + return bool(self.__config.get("database", {}).get("read_only", False)) class Server: @@ -47,41 +51,41 @@ class Server: @property def address(self) -> str: - return str(self.__config.get('server', {}).get('address', '127.0.0.1')) + return str(self.__config.get("server", {}).get("address", "127.0.0.1")) @property def keepalive(self) -> str: - return str(self.__config.get('server', {}).get('keepalive', self.address)) + return str(self.__config.get("server", {}).get("keepalive", self.address)) @property def port(self) -> int: - return int(self.__config.get('server', {}).get('port', 80)) + return int(self.__config.get("server", {}).get("port", 80)) @property def https(self) -> bool: - return bool(self.__config.get('server', {}).get('https', False)) + return bool(self.__config.get("server", {}).get("https", False)) @property def uri(self) -> Optional[str]: - uri = self.__config.get('server', {}).get('uri') + uri = self.__config.get("server", {}).get("uri") return str(uri) if uri else None @property def redirect(self) -> Optional[str]: - redirect = self.__config.get('server', {}).get('redirect') + redirect = self.__config.get("server", {}).get("redirect") return str(redirect) if redirect else None @property def enforce_pcbid(self) -> bool: - return bool(self.__config.get('server', {}).get('enforce_pcbid', False)) + return bool(self.__config.get("server", {}).get("enforce_pcbid", False)) @property def pcbid_self_grant_limit(self) -> int: - return int(self.__config.get('server', {}).get('pcbid_self_grant_limit', 0)) + return int(self.__config.get("server", {}).get("pcbid_self_grant_limit", 0)) @property def region(self) -> int: - region = int(self.__config.get('server', {}).get('region', RegionConstants.USA)) + region = int(self.__config.get("server", {}).get("region", RegionConstants.USA)) if region in {RegionConstants.EUROPE, RegionConstants.NO_MAPPING}: # Bogus values we support. return region @@ -98,9 +102,11 @@ class Client: @property def address(self) -> str: - address = self.__config.get('client', {}).get('address') + address = self.__config.get("client", {}).get("address") if address is None: - raise Exception("Config object is not instantiated properly, no client address present!") + raise Exception( + "Config object is not instantiated properly, no client address present!" + ) return str(address) @@ -110,14 +116,16 @@ class Machine: @property def pcbid(self) -> str: - pcbid = self.__config.get('machine', {}).get('pcbid') + pcbid = self.__config.get("machine", {}).get("pcbid") if pcbid is None: - raise Exception("Config object is not instantiated properly, no machine pcbid present!") + raise Exception( + "Config object is not instantiated properly, no machine pcbid present!" + ) return str(pcbid) @property def arcade(self) -> Optional[ArcadeID]: - return self.__config.get('machine', {}).get('arcade') + return self.__config.get("machine", {}).get("arcade") class PASELI: @@ -126,11 +134,11 @@ class PASELI: @property def enabled(self) -> bool: - return bool(self.__config.get('paseli', {}).get('enabled', False)) + return bool(self.__config.get("paseli", {}).get("enabled", False)) @property def infinite(self) -> bool: - return bool(self.__config.get('paseli', {}).get('infinite', False)) + return bool(self.__config.get("paseli", {}).get("infinite", False)) class WebHooks: @@ -143,7 +151,7 @@ class DiscordWebHooks: self.__config = parent_config def __getitem__(self, key: GameConstants) -> Optional[str]: - uri = self.__config.get('webhooks', {}).get('discord', {}).get(key.value) + uri = self.__config.get("webhooks", {}).get("discord", {}).get(key.value) return str(uri) if uri else None @@ -161,55 +169,61 @@ class Config(dict): def clone(self) -> "Config": # Somehow its not possible to clone this object if an instantiated Engine is present, # so we do a little shenanigans here. - engine = self.get('database', {}).get('engine') + engine = self.get("database", {}).get("engine") if engine is not None: - self['database']['engine'] = None + self["database"]["engine"] = None clone = Config(copy.deepcopy(self)) if engine is not None: - self['database']['engine'] = engine - clone['database']['engine'] = engine + self["database"]["engine"] = engine + clone["database"]["engine"] = engine return clone @property def filename(self) -> str: - filename = self.get('filename') + filename = self.get("filename") if filename is None: - raise Exception("Config object is not instantiated properly, no filename present!") + raise Exception( + "Config object is not instantiated properly, no filename present!" + ) return os.path.abspath(str(filename)) @property def support(self) -> Set[GameConstants]: - support = self.get('support') + support = self.get("support") if support is None: - raise Exception("Config object is not instantiated properly, no support list present!") + raise Exception( + "Config object is not instantiated properly, no support list present!" + ) if not isinstance(support, set): - raise Exception("Config object is not instantiated properly, support property is not a Set!") + raise Exception( + "Config object is not instantiated properly, support property is not a Set!" + ) return support @property def secret_key(self) -> str: - return str(self.get('secret_key', 'youdidntchangethisatalldidyou?')) + return str(self.get("secret_key", "youdidntchangethisatalldidyou?")) @property def name(self) -> str: - return str(self.get('name', 'e-AMUSEMENT Network')) + return str(self.get("name", "e-AMUSEMENT Network")) @property def email(self) -> str: - return str(self.get('email', 'nobody@nowhere.com')) + return str(self.get("email", "nobody@nowhere.com")) @property def cache_dir(self) -> str: - return os.path.abspath(str(self.get('cache_dir', '/tmp'))) + return os.path.abspath(str(self.get("cache_dir", "/tmp"))) @property def theme(self) -> str: - return str(self.get('theme', 'default')) + return str(self.get("theme", "default")) @property def event_log_duration(self) -> Optional[int]: - duration = self.get('event_log_duration') + duration = self.get("event_log_duration") return int(duration) if duration else None diff --git a/bemani/data/data.py b/bemani/data/data.py index c25a608..7777db5 100644 --- a/bemani/data/data.py +++ b/bemani/data/data.py @@ -135,20 +135,24 @@ class Data: def __exists(self) -> bool: # See if the DB was already created try: - cursor = self.__session.execute(text('SELECT COUNT(version_num) AS count FROM alembic_version')) - return (cursor.fetchone()['count'] == 1) + cursor = self.__session.execute( + text("SELECT COUNT(version_num) AS count FROM alembic_version") + ) + return cursor.fetchone()["count"] == 1 except ProgrammingError: return False def __alembic_cmd(self, command: str, *args: str) -> None: - base_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'migrations') + base_dir = os.path.join( + os.path.abspath(os.path.dirname(__file__)), "migrations" + ) alembicArgs = [ - '-c', - os.path.join(base_dir, 'alembic.ini'), - '-x', - f'script_location={base_dir}', - '-x', - f'sqlalchemy.url={self.__url}', + "-c", + os.path.join(base_dir, "alembic.ini"), + "-x", + f"script_location={base_dir}", + "-x", + f"sqlalchemy.url={self.__url}", command, ] alembicArgs.extend(args) @@ -161,7 +165,9 @@ class Data: """ if self.__exists(): # Cowardly refused to do anything, we should be using the upgrade path instead. - raise DBCreateException('Tables already created, use upgrade to upgrade schema!') + raise DBCreateException( + "Tables already created, use upgrade to upgrade schema!" + ) metadata.create_all( self.__config.database.engine.connect(), @@ -170,8 +176,8 @@ class Data: # Stamp the end revision as if alembic had created it, so it can take off after this. self.__alembic_cmd( - 'stamp', - 'head', + "stamp", + "head", ) def generate(self, message: str, allow_empty: bool) -> None: @@ -179,18 +185,24 @@ class Data: Generate upgrade scripts using alembic. """ if not self.__exists(): - raise DBCreateException('Tables have not been created yet, use create to create them!') + raise DBCreateException( + "Tables have not been created yet, use create to create them!" + ) # Verify that there are actual changes, and refuse to create empty migration scripts - context = MigrationContext.configure(self.__config.database.engine.connect(), opts={'compare_type': True}) + context = MigrationContext.configure( + self.__config.database.engine.connect(), opts={"compare_type": True} + ) diff = compare_metadata(context, metadata) if (not allow_empty) and (len(diff) == 0): - raise DBCreateException('There is nothing different between code and the DB, refusing to create migration!') + raise DBCreateException( + "There is nothing different between code and the DB, refusing to create migration!" + ) self.__alembic_cmd( - 'revision', - '--autogenerate', - '-m', + "revision", + "--autogenerate", + "-m", message, ) @@ -199,11 +211,13 @@ class Data: Upgrade an existing DB to the current model. """ if not self.__exists(): - raise DBCreateException('Tables have not been created yet, use create to create them!') + raise DBCreateException( + "Tables have not been created yet, use create to create them!" + ) self.__alembic_cmd( - 'upgrade', - 'head', + "upgrade", + "head", ) def close(self) -> None: diff --git a/bemani/data/interfaces.py b/bemani/data/interfaces.py index 5af858e..1abfbcb 100644 --- a/bemani/data/interfaces.py +++ b/bemani/data/interfaces.py @@ -5,7 +5,6 @@ from bemani.data.types import Server class APIProviderInterface(ABC): - @abstractmethod def get_all_servers(self) -> List[Server]: """ diff --git a/bemani/data/mysql/api.py b/bemani/data/mysql/api.py index 6ff21a5..07c140a 100644 --- a/bemani/data/mysql/api.py +++ b/bemani/data/mysql/api.py @@ -13,13 +13,13 @@ Table for storing registered clients to a data exchange API, as added by an admin. """ client = Table( - 'client', + "client", metadata, - Column('id', Integer, nullable=False, primary_key=True), - Column('timestamp', Integer, nullable=False, index=True), - Column('name', String(255), nullable=False), - Column('token', String(36), nullable=False), - mysql_charset='utf8mb4', + Column("id", Integer, nullable=False, primary_key=True), + Column("timestamp", Integer, nullable=False, index=True), + Column("name", String(255), nullable=False), + Column("token", String(36), nullable=False), + mysql_charset="utf8mb4", ) """ @@ -27,19 +27,18 @@ Table for storing remote servers to a data exchange API, as added by an admin. """ server = Table( - 'server', + "server", metadata, - Column('id', Integer, nullable=False, primary_key=True), - Column('timestamp', Integer, nullable=False, index=True), - Column('uri', String(1024), nullable=False), - Column('token', String(64), nullable=False), - Column('config', Integer, nullable=False), - mysql_charset='utf8mb4', + Column("id", Integer, nullable=False, primary_key=True), + Column("timestamp", Integer, nullable=False, index=True), + Column("uri", String(1024), nullable=False), + Column("token", String(64), nullable=False), + Column("config", Integer, nullable=False), + mysql_charset="utf8mb4", ) class APIData(APIProviderInterface, BaseData): - def get_all_clients(self) -> List[Client]: """ Grab all authorized clients in the system. @@ -51,11 +50,12 @@ class APIData(APIProviderInterface, BaseData): cursor = self.execute(sql) return [ Client( - result['id'], - result['timestamp'], - result['name'], - result['token'], - ) for result in cursor.fetchall() + result["id"], + result["timestamp"], + result["name"], + result["token"], + ) + for result in cursor.fetchall() ] def validate_client(self, token: str) -> bool: @@ -69,8 +69,8 @@ class APIData(APIProviderInterface, BaseData): True if the client is authorized, False otherwise. """ sql = "SELECT count(*) AS count FROM client WHERE token = :token" - cursor = self.execute(sql, {'token': token}) - return cursor.fetchone()['count'] == 1 + cursor = self.execute(sql, {"token": token}) + return cursor.fetchone()["count"] == 1 def create_client(self, name: str) -> int: """ @@ -86,9 +86,9 @@ class APIData(APIProviderInterface, BaseData): cursor = self.execute( sql, { - 'timestamp': Time.now(), - 'name': name, - 'token': str(uuid.uuid4()), + "timestamp": Time.now(), + "name": name, + "token": str(uuid.uuid4()), }, ) return cursor.lastrowid @@ -104,7 +104,7 @@ class APIData(APIProviderInterface, BaseData): A Client object if the client entry was found or None otherwise. """ sql = "SELECT timestamp, name, token FROM client WHERE id = :id" - cursor = self.execute(sql, {'id': clientid}) + cursor = self.execute(sql, {"id": clientid}) if cursor.rowcount != 1: # Couldn't find an entry with this ID return None @@ -112,9 +112,9 @@ class APIData(APIProviderInterface, BaseData): result = cursor.fetchone() return Client( clientid, - result['timestamp'], - result['name'], - result['token'], + result["timestamp"], + result["name"], + result["token"], ) def put_client(self, client: Client) -> None: @@ -125,7 +125,7 @@ class APIData(APIProviderInterface, BaseData): client - A Client object to be updated. """ sql = "UPDATE client SET name = :name WHERE id = :id" - self.execute(sql, {'id': client.id, 'name': client.name}) + self.execute(sql, {"id": client.id, "name": client.name}) def destroy_client(self, clientid: int) -> None: """ @@ -135,7 +135,7 @@ class APIData(APIProviderInterface, BaseData): clientid - Integer specifying client ID. """ sql = "DELETE FROM client WHERE id = :id LIMIT 1" - self.execute(sql, {'id': clientid}) + self.execute(sql, {"id": clientid}) def get_all_servers(self) -> List[Server]: """ @@ -144,14 +144,15 @@ class APIData(APIProviderInterface, BaseData): Returns: A list of Server objects sorted by add time. """ + def format_result(result: Dict[str, Any]) -> Server: - allow_stats = (result['config'] & 0x1) == 0 - allow_scores = (result['config'] & 0x2) == 0 + allow_stats = (result["config"] & 0x1) == 0 + allow_scores = (result["config"] & 0x2) == 0 return Server( - result['id'], - result['timestamp'], - result['uri'], - result['token'], + result["id"], + result["timestamp"], + result["uri"], + result["token"], allow_stats, allow_scores, ) @@ -175,9 +176,9 @@ class APIData(APIProviderInterface, BaseData): cursor = self.execute( sql, { - 'timestamp': Time.now(), - 'uri': uri, - 'token': token, + "timestamp": Time.now(), + "uri": uri, + "token": token, }, ) return cursor.lastrowid @@ -193,19 +194,19 @@ class APIData(APIProviderInterface, BaseData): A Server object if the server entry was found or None otherwise. """ sql = "SELECT timestamp, uri, token, config FROM server WHERE id = :id" - cursor = self.execute(sql, {'id': serverid}) + cursor = self.execute(sql, {"id": serverid}) if cursor.rowcount != 1: # Couldn't find an entry with this ID return None result = cursor.fetchone() - allow_stats = (result['config'] & 0x1) == 0 - allow_scores = (result['config'] & 0x2) == 0 + allow_stats = (result["config"] & 0x1) == 0 + allow_scores = (result["config"] & 0x2) == 0 return Server( serverid, - result['timestamp'], - result['uri'], - result['token'], + result["timestamp"], + result["uri"], + result["token"], allow_stats, allow_scores, ) @@ -223,7 +224,15 @@ class APIData(APIProviderInterface, BaseData): if not server.allow_scores: config = config | 0x2 sql = "UPDATE server SET uri = :uri, token = :token, config = :config WHERE id = :id" - self.execute(sql, {'id': server.id, 'uri': server.uri, 'token': server.token, 'config': config}) + self.execute( + sql, + { + "id": server.id, + "uri": server.uri, + "token": server.token, + "config": config, + }, + ) def destroy_server(self, serverid: int) -> None: """ @@ -233,4 +242,4 @@ class APIData(APIProviderInterface, BaseData): serverid - Integer specifying server ID. """ sql = "DELETE FROM server WHERE id = :id LIMIT 1" - self.execute(sql, {'id': serverid}) + self.execute(sql, {"id": serverid}) diff --git a/bemani/data/mysql/base.py b/bemani/data/mysql/base.py index e986c7f..51adcd2 100644 --- a/bemani/data/mysql/base.py +++ b/bemani/data/mysql/base.py @@ -19,13 +19,13 @@ Table for storing session IDs, so a session ID can be used to look up an arbitra This is currently used for user logins, user and arcade PASELI sessions. """ session = Table( - 'session', + "session", metadata, - Column('id', Integer, nullable=False), - Column('type', String(32), nullable=False), - Column('session', String(32), nullable=False, unique=True), - Column('expiration', Integer), - mysql_charset='utf8mb4', + Column("id", Integer, nullable=False), + Column("type", String(32), nullable=False), + Column("session", String(32), nullable=False, unique=True), + Column("expiration", Integer), + mysql_charset="utf8mb4", ) @@ -33,7 +33,7 @@ class _BytesEncoder(json.JSONEncoder): def default(self, obj: Any) -> Any: if isinstance(obj, bytes): # We're abusing lists here, we have a mixed type - return ['__bytes__'] + [b for b in obj] # type: ignore + return ["__bytes__"] + [b for b in obj] return json.JSONEncoder.default(self, obj) @@ -56,7 +56,12 @@ class BaseData: self.__config = config self.__conn = conn - def execute(self, sql: str, params: Optional[Dict[str, Any]]=None, safe_write_operation: bool=False) -> CursorResult: + def execute( + self, + sql: str, + params: Optional[Dict[str, Any]] = None, + safe_write_operation: bool = False, + ) -> CursorResult: """ Given a SQL string and some parameters, execute the query and return the result. @@ -77,7 +82,7 @@ class BaseData: ]: includes = all(s in lowered for s in write_statement_group) if includes and not safe_write_operation: - raise Exception('Read-only mode is active!') + raise Exception("Read-only mode is active!") return self.__conn.execute( text(sql), params if params is not None else {}, @@ -105,7 +110,7 @@ class BaseData: if type(jd) == list: # Could be serialized by us, could be a normal list. - if len(jd) >= 1 and jd[0] == '__bytes__': + if len(jd) >= 1 and jd[0] == "__bytes__": # This is a serialized bytestring return bytes(jd[1:]) @@ -132,15 +137,19 @@ class BaseData: """ # Look up the user account, making sure to expire old sessions sql = "SELECT id FROM session WHERE session = :session AND type = :type AND expiration > :timestamp" - cursor = self.execute(sql, {'session': session, 'type': sesstype, 'timestamp': Time.now()}) + cursor = self.execute( + sql, {"session": session, "type": sesstype, "timestamp": Time.now()} + ) if cursor.rowcount != 1: # Couldn't find a user with this session return None result = cursor.fetchone() - return result['id'] + return result["id"] - def _create_session(self, opid: int, optype: str, expiration: int=(30 * 86400)) -> str: + def _create_session( + self, opid: int, optype: str, expiration: int = (30 * 86400) + ) -> str: """ Given an ID, create a session string. @@ -153,21 +162,29 @@ class BaseData: """ # Create a new session that is unique while True: - session = ''.join(random.choice('0123456789ABCDEF') for _ in range(BaseData.SESSION_LENGTH)) + session = "".join( + random.choice("0123456789ABCDEF") + for _ in range(BaseData.SESSION_LENGTH) + ) sql = "SELECT session FROM session WHERE session = :session" - cursor = self.execute(sql, {'session': session}) + cursor = self.execute(sql, {"session": session}) if cursor.rowcount == 0: # Make sure sessions expire in a reasonable amount of time expiration = Time.now() + expiration # Use that session sql = ( - "INSERT INTO session (id, session, type, expiration) " + - "VALUES (:id, :session, :optype, :expiration)" + "INSERT INTO session (id, session, type, expiration) " + + "VALUES (:id, :session, :optype, :expiration)" ) cursor = self.execute( sql, - {'id': opid, 'session': session, 'optype': optype, 'expiration': expiration}, + { + "id": opid, + "session": session, + "optype": optype, + "expiration": expiration, + }, safe_write_operation=True, ) if cursor.rowcount == 1: @@ -182,8 +199,10 @@ class BaseData: """ # Remove the session token sql = "DELETE FROM session WHERE session = :session AND type = :sesstype" - self.execute(sql, {'session': session, 'sesstype': sesstype}, safe_write_operation=True) + self.execute( + sql, {"session": session, "sesstype": sesstype}, safe_write_operation=True + ) # Also weed out any other defunct sessions sql = "DELETE FROM session WHERE expiration < :timestamp" - self.execute(sql, {'timestamp': Time.now()}, safe_write_operation=True) + self.execute(sql, {"timestamp": Time.now()}, safe_write_operation=True) diff --git a/bemani/data/mysql/game.py b/bemani/data/mysql/game.py index ce09497..316c11c 100644 --- a/bemani/data/mysql/game.py +++ b/bemani/data/mysql/game.py @@ -13,28 +13,28 @@ game, such as play statistics. This table intentionally doesn't have a key on game version, just game string and userid. """ game_settings = Table( - 'game_settings', + "game_settings", metadata, - Column('game', String(32), nullable=False), - Column('userid', BigInteger(unsigned=True), nullable=False), - Column('data', JSON, nullable=False), - UniqueConstraint('game', 'userid', name='game_userid'), - mysql_charset='utf8mb4', + Column("game", String(32), nullable=False), + Column("userid", BigInteger(unsigned=True), nullable=False), + Column("data", JSON, nullable=False), + UniqueConstraint("game", "userid", name="game_userid"), + mysql_charset="utf8mb4", ) """ Table for storing shop items that are server-side verified. """ catalog = Table( - 'catalog', + "catalog", metadata, - Column('game', String(32), nullable=False), - Column('version', Integer, nullable=False), - Column('id', Integer, nullable=False), - Column('type', String(64), nullable=False), - Column('data', JSON, nullable=False), - UniqueConstraint('game', 'version', 'id', 'type', name='game_version_id_type'), - mysql_charset='utf8mb4', + Column("game", String(32), nullable=False), + Column("version", Integer, nullable=False), + Column("id", Integer, nullable=False), + Column("type", String(64), nullable=False), + Column("data", JSON, nullable=False), + UniqueConstraint("game", "version", "id", "type", name="game_version_id_type"), + mysql_charset="utf8mb4", ) """ @@ -43,15 +43,15 @@ game, such as course scores. This table intentionally doesn't have a key on game version, just game string and userid. """ series_achievement = Table( - 'series_achievement', + "series_achievement", metadata, - Column('game', String(32), nullable=False), - Column('userid', BigInteger(unsigned=True), nullable=False), - Column('id', Integer, nullable=False), - Column('type', String(64), nullable=False), - Column('data', JSON, nullable=False), - UniqueConstraint('game', 'userid', 'id', 'type', name='game_userid_id_type'), - mysql_charset='utf8mb4', + Column("game", String(32), nullable=False), + Column("userid", BigInteger(unsigned=True), nullable=False), + Column("id", Integer, nullable=False), + Column("type", String(64), nullable=False), + Column("data", JSON, nullable=False), + UniqueConstraint("game", "userid", "id", "type", name="game_userid_id_type"), + mysql_charset="utf8mb4", ) """ @@ -59,22 +59,25 @@ Table for storing time-based game settings that aren't tied to a user account, such as dailies, weeklies, etc. """ time_sensitive_settings = Table( - 'time_sensitive_settings', + "time_sensitive_settings", metadata, - Column('game', String(32), nullable=False), - Column('version', Integer, nullable=False), - Column('name', String(32), nullable=False), - Column('start_time', Integer, nullable=False, index=True), - Column('end_time', Integer, nullable=False, index=True), - Column('data', JSON, nullable=False), - UniqueConstraint('game', 'version', 'name', 'start_time', name='game_version_name_start_time'), - mysql_charset='utf8mb4', + Column("game", String(32), nullable=False), + Column("version", Integer, nullable=False), + Column("name", String(32), nullable=False), + Column("start_time", Integer, nullable=False, index=True), + Column("end_time", Integer, nullable=False, index=True), + Column("data", JSON, nullable=False), + UniqueConstraint( + "game", "version", "name", "start_time", name="game_version_name_start_time" + ), + mysql_charset="utf8mb4", ) class GameData(BaseData): - - def get_settings(self, game: GameConstants, userid: UserID) -> Optional[ValidatedDict]: + def get_settings( + self, game: GameConstants, userid: UserID + ) -> Optional[ValidatedDict]: """ Given a game and a user ID, look up game-wide settings as a dictionary. @@ -91,16 +94,18 @@ class GameData(BaseData): if there are no settings for this game/user. """ sql = "SELECT data FROM game_settings WHERE game = :game AND userid = :userid" - cursor = self.execute(sql, {'game': game.value, 'userid': userid}) + cursor = self.execute(sql, {"game": game.value, "userid": userid}) if cursor.rowcount != 1: # Settings doesn't exist return None result = cursor.fetchone() - return ValidatedDict(self.deserialize(result['data'])) + return ValidatedDict(self.deserialize(result["data"])) - def put_settings(self, game: GameConstants, userid: UserID, settings: Dict[str, Any]) -> None: + def put_settings( + self, game: GameConstants, userid: UserID, settings: Dict[str, Any] + ) -> None: """ Given a game and a user ID, save game-wide settings to the DB. @@ -111,13 +116,22 @@ class GameData(BaseData): """ # Add settings json to game settings sql = ( - "INSERT INTO game_settings (game, userid, data) " + - "VALUES (:game, :userid, :data) " + - "ON DUPLICATE KEY UPDATE data=VALUES(data)" + "INSERT INTO game_settings (game, userid, data) " + + "VALUES (:game, :userid, :data) " + + "ON DUPLICATE KEY UPDATE data=VALUES(data)" + ) + self.execute( + sql, + {"game": game.value, "userid": userid, "data": self.serialize(settings)}, ) - self.execute(sql, {'game': game.value, 'userid': userid, 'data': self.serialize(settings)}) - def get_achievement(self, game: GameConstants, userid: UserID, achievementid: int, achievementtype: str) -> Optional[ValidatedDict]: + def get_achievement( + self, + game: GameConstants, + userid: UserID, + achievementid: int, + achievementtype: str, + ) -> Optional[ValidatedDict]: """ Given a game/userid and achievement id/type, find that achievement. @@ -137,15 +151,25 @@ class GameData(BaseData): "SELECT data FROM series_achievement " "WHERE game = :game AND userid = :userid AND id = :id AND type = :type" ) - cursor = self.execute(sql, {'game': game.value, 'userid': userid, 'id': achievementid, 'type': achievementtype}) + cursor = self.execute( + sql, + { + "game": game.value, + "userid": userid, + "id": achievementid, + "type": achievementtype, + }, + ) if cursor.rowcount != 1: # score doesn't exist return None result = cursor.fetchone() - return ValidatedDict(self.deserialize(result['data'])) + return ValidatedDict(self.deserialize(result["data"])) - def get_achievements(self, game: GameConstants, userid: UserID) -> List[Achievement]: + def get_achievements( + self, game: GameConstants, userid: UserID + ) -> List[Achievement]: """ Given a game/userid, find all achievements @@ -157,22 +181,29 @@ class GameData(BaseData): A list of Achievement objects. """ sql = "SELECT id, type, data FROM series_achievement WHERE game = :game AND userid = :userid" - cursor = self.execute(sql, {'game': game.value, 'userid': userid}) + cursor = self.execute(sql, {"game": game.value, "userid": userid}) achievements = [] for result in cursor.fetchall(): achievements.append( Achievement( - result['id'], - result['type'], + result["id"], + result["type"], None, - self.deserialize(result['data']), + self.deserialize(result["data"]), ) ) return achievements - def put_achievement(self, game: GameConstants, userid: UserID, achievementid: int, achievementtype: str, data: Dict[str, Any]) -> None: + def put_achievement( + self, + game: GameConstants, + userid: UserID, + achievementid: int, + achievementtype: str, + data: Dict[str, Any], + ) -> None: """ Given a game/userid and achievement id/type, save an achievement. @@ -185,13 +216,24 @@ class GameData(BaseData): """ # Add achievement JSON to achievements sql = ( - "INSERT INTO series_achievement (game, userid, id, type, data) " + - "VALUES (:game, :userid, :id, :type, :data) " + - "ON DUPLICATE KEY UPDATE data=VALUES(data)" + "INSERT INTO series_achievement (game, userid, id, type, data) " + + "VALUES (:game, :userid, :id, :type, :data) " + + "ON DUPLICATE KEY UPDATE data=VALUES(data)" + ) + self.execute( + sql, + { + "game": game.value, + "userid": userid, + "id": achievementid, + "type": achievementtype, + "data": self.serialize(data), + }, ) - self.execute(sql, {'game': game.value, 'userid': userid, 'id': achievementid, 'type': achievementtype, 'data': self.serialize(data)}) - def get_time_sensitive_settings(self, game: GameConstants, version: int, name: str) -> Optional[ValidatedDict]: + def get_time_sensitive_settings( + self, game: GameConstants, version: int, name: str + ) -> Optional[ValidatedDict]: """ Given a game/version/name, look up the current time-sensitive settings for this game. @@ -209,18 +251,23 @@ class GameData(BaseData): "SELECT data, start_time, end_time FROM time_sensitive_settings WHERE " "game = :game AND version = :version AND name = :name AND start_time <= :time AND end_time > :time" ) - cursor = self.execute(sql, {'game': game.value, 'version': version, 'name': name, 'time': Time.now()}) + cursor = self.execute( + sql, + {"game": game.value, "version": version, "name": name, "time": Time.now()}, + ) if cursor.rowcount != 1: # setting doesn't exist return None result = cursor.fetchone() - retval = ValidatedDict(self.deserialize(result['data'])) - retval['start_time'] = result['start_time'] - retval['end_time'] = result['end_time'] + retval = ValidatedDict(self.deserialize(result["data"])) + retval["start_time"] = result["start_time"] + retval["end_time"] = result["end_time"] return retval - def get_all_time_sensitive_settings(self, game: GameConstants, version: int, name: str) -> List[ValidatedDict]: + def get_all_time_sensitive_settings( + self, game: GameConstants, version: int, name: str + ) -> List[ValidatedDict]: """ Given a game/version/name, look up all of the time-sensitive settings for this game. @@ -238,20 +285,24 @@ class GameData(BaseData): "SELECT data, start_time, end_time FROM time_sensitive_settings WHERE " "game = :game AND version = :version AND name = :name" ) - cursor = self.execute(sql, {'game': game.value, 'version': version, 'name': name}) + cursor = self.execute( + sql, {"game": game.value, "version": version, "name": name} + ) if cursor.rowcount == 0: # setting doesn't exist return [] settings = [] for result in cursor.fetchall(): - retval = ValidatedDict(self.deserialize(result['data'])) - retval['start_time'] = result['start_time'] - retval['end_time'] = result['end_time'] + retval = ValidatedDict(self.deserialize(result["data"])) + retval["start_time"] = result["start_time"] + retval["end_time"] = result["end_time"] settings.append(retval) return settings - def put_time_sensitive_settings(self, game: GameConstants, version: int, name: str, settings: Dict[str, Any]) -> None: + def put_time_sensitive_settings( + self, game: GameConstants, version: int, name: str, settings: Dict[str, Any] + ) -> None: """ Given a game/version/name and a settings dictionary that contains 'start_time' and 'end_time', as seconds since the unix epoch (UTC), update the DB to store or update this time-sensitive @@ -264,15 +315,15 @@ class GameData(BaseData): name - The name of the setting we are concerned with. settings - A dictionary containing at least 'start_time' and 'end_time'. """ - start_time = settings['start_time'] - end_time = settings['end_time'] - del settings['start_time'] - del settings['end_time'] + start_time = settings["start_time"] + end_time = settings["end_time"] + del settings["start_time"] + del settings["end_time"] if start_time > end_time: - raise Exception('Start time is greater than end time!') + raise Exception("Start time is greater than end time!") if start_time == end_time: - raise Exception('This setting spans zero seconds!') + raise Exception("This setting spans zero seconds!") # Verify that this isn't overlapping some event. sql = """ @@ -287,18 +338,20 @@ class GameData(BaseData): cursor = self.execute( sql, { - 'game': game.value, - 'version': version, - 'name': name, - 'start_time': start_time, - 'end_time': end_time, + "game": game.value, + "version": version, + "name": name, + "start_time": start_time, + "end_time": end_time, }, ) for result in cursor.fetchall(): - if result['start_time'] == start_time and result['end_time'] == end_time: + if result["start_time"] == start_time and result["end_time"] == end_time: # This is just this event being updated, that's fine. continue - raise Exception(f'This event overlaps an existing one with start time {result["start_time"]} and end time {result["end_time"]}') + raise Exception( + f'This event overlaps an existing one with start time {result["start_time"]} and end time {result["end_time"]}' + ) # Insert or update this setting sql = ( @@ -309,16 +362,18 @@ class GameData(BaseData): self.execute( sql, { - 'game': game.value, - 'version': version, - 'name': name, - 'start_time': start_time, - 'end_time': end_time, - 'data': self.serialize(settings), + "game": game.value, + "version": version, + "name": name, + "start_time": start_time, + "end_time": end_time, + "data": self.serialize(settings), }, ) - def get_item(self, game: GameConstants, version: int, catid: int, cattype: str) -> Optional[ValidatedDict]: + def get_item( + self, game: GameConstants, version: int, catid: int, cattype: str + ) -> Optional[ValidatedDict]: """ Given a game/userid and catalog id/type, find that catalog entry. @@ -338,13 +393,15 @@ class GameData(BaseData): "SELECT data FROM catalog " "WHERE game = :game AND version = :version AND id = :id AND type = :type" ) - cursor = self.execute(sql, {'game': game.value, 'version': version, 'id': catid, 'type': cattype}) + cursor = self.execute( + sql, {"game": game.value, "version": version, "id": catid, "type": cattype} + ) if cursor.rowcount != 1: # entry doesn't exist return None result = cursor.fetchone() - return ValidatedDict(self.deserialize(result['data'])) + return ValidatedDict(self.deserialize(result["data"])) def get_items(self, game: GameConstants, version: int) -> List[Item]: """ @@ -358,15 +415,15 @@ class GameData(BaseData): A list of Item objects. """ sql = "SELECT id, type, data FROM catalog WHERE game = :game AND version = :version" - cursor = self.execute(sql, {'game': game.value, 'version': version}) + cursor = self.execute(sql, {"game": game.value, "version": version}) catalog = [] for result in cursor.fetchall(): catalog.append( Item( - result['type'], - result['id'], - self.deserialize(result['data']), + result["type"], + result["id"], + self.deserialize(result["data"]), ) ) diff --git a/bemani/data/mysql/lobby.py b/bemani/data/mysql/lobby.py index bbeeb2d..cd74c39 100644 --- a/bemani/data/mysql/lobby.py +++ b/bemani/data/mysql/lobby.py @@ -15,38 +15,39 @@ live. Mostly, this is used to store IP addresses and such for players that could potentially match. """ playsession = Table( - 'playsession', + "playsession", metadata, - Column('id', Integer, nullable=False, primary_key=True), - Column('game', String(32), nullable=False), - Column('version', Integer, nullable=False), - Column('userid', BigInteger(unsigned=True), nullable=False), - Column('time', Integer, nullable=False, index=True), - Column('data', JSON, nullable=False), - UniqueConstraint('game', 'version', 'userid', name='game_version_userid'), - mysql_charset='utf8mb4', + Column("id", Integer, nullable=False, primary_key=True), + Column("game", String(32), nullable=False), + Column("version", Integer, nullable=False), + Column("userid", BigInteger(unsigned=True), nullable=False), + Column("time", Integer, nullable=False, index=True), + Column("data", JSON, nullable=False), + UniqueConstraint("game", "version", "userid", name="game_version_userid"), + mysql_charset="utf8mb4", ) """ Table for storing open lobbies for matching between games. """ lobby = Table( - 'lobby', + "lobby", metadata, - Column('id', Integer, nullable=False, primary_key=True), - Column('game', String(32), nullable=False), - Column('version', Integer, nullable=False), - Column('userid', BigInteger(unsigned=True), nullable=False), - Column('time', Integer, nullable=False, index=True), - Column('data', JSON, nullable=False), - UniqueConstraint('game', 'version', 'userid', name='game_version_userid'), - mysql_charset='utf8mb4', + Column("id", Integer, nullable=False, primary_key=True), + Column("game", String(32), nullable=False), + Column("version", Integer, nullable=False), + Column("userid", BigInteger(unsigned=True), nullable=False), + Column("time", Integer, nullable=False, index=True), + Column("data", JSON, nullable=False), + UniqueConstraint("game", "version", "userid", name="game_version_userid"), + mysql_charset="utf8mb4", ) class LobbyData(BaseData): - - def get_play_session_info(self, game: GameConstants, version: int, userid: UserID) -> Optional[ValidatedDict]: + def get_play_session_info( + self, game: GameConstants, version: int, userid: UserID + ) -> Optional[ValidatedDict]: """ Given a game, version and a user ID, look up play session information for that user. @@ -69,10 +70,10 @@ class LobbyData(BaseData): cursor = self.execute( sql, { - 'game': game.value, - 'version': version, - 'userid': userid, - 'time': Time.now() - Time.SECONDS_IN_HOUR, + "game": game.value, + "version": version, + "userid": userid, + "time": Time.now() - Time.SECONDS_IN_HOUR, }, ) @@ -81,12 +82,14 @@ class LobbyData(BaseData): return None result = cursor.fetchone() - data = ValidatedDict(self.deserialize(result['data'])) - data['id'] = result['id'] - data['time'] = result['time'] + data = ValidatedDict(self.deserialize(result["data"])) + data["id"] = result["id"] + data["time"] = result["time"] return data - def get_all_play_session_infos(self, game: GameConstants, version: int) -> List[Tuple[UserID, ValidatedDict]]: + def get_all_play_session_infos( + self, game: GameConstants, version: int + ) -> List[Tuple[UserID, ValidatedDict]]: """ Given a game and version, look up all play session information. @@ -106,21 +109,23 @@ class LobbyData(BaseData): cursor = self.execute( sql, { - 'game': game.value, - 'version': version, - 'time': Time.now() - Time.SECONDS_IN_HOUR, + "game": game.value, + "version": version, + "time": Time.now() - Time.SECONDS_IN_HOUR, }, ) ret = [] for result in cursor.fetchall(): - data = ValidatedDict(self.deserialize(result['data'])) - data['id'] = result['id'] - data['time'] = result['time'] - ret.append((UserID(result['userid']), data)) + data = ValidatedDict(self.deserialize(result["data"])) + data["id"] = result["id"] + data["time"] = result["time"] + ret.append((UserID(result["userid"]), data)) return ret - def put_play_session_info(self, game: GameConstants, version: int, userid: UserID, data: Dict[str, Any]) -> None: + def put_play_session_info( + self, game: GameConstants, version: int, userid: UserID, data: Dict[str, Any] + ) -> None: """ Given a game, version and a user ID, save play session information for that user. @@ -131,29 +136,31 @@ class LobbyData(BaseData): data - A dictionary of play session information to store. """ data = copy.deepcopy(data) - if 'id' in data: - del data['id'] - if 'time' in data: - del data['time'] + if "id" in data: + del data["id"] + if "time" in data: + del data["time"] # Add json to player session sql = ( - "INSERT INTO playsession (game, version, userid, time, data) " + - "VALUES (:game, :version, :userid, :time, :data) " + - "ON DUPLICATE KEY UPDATE time=VALUES(time), data=VALUES(data)" + "INSERT INTO playsession (game, version, userid, time, data) " + + "VALUES (:game, :version, :userid, :time, :data) " + + "ON DUPLICATE KEY UPDATE time=VALUES(time), data=VALUES(data)" ) self.execute( sql, { - 'game': game.value, - 'version': version, - 'userid': userid, - 'time': Time.now(), - 'data': self.serialize(data), + "game": game.value, + "version": version, + "userid": userid, + "time": Time.now(), + "data": self.serialize(data), }, ) - def destroy_play_session_info(self, game: GameConstants, version: int, userid: UserID) -> None: + def destroy_play_session_info( + self, game: GameConstants, version: int, userid: UserID + ) -> None: """ Given a game, version and a user ID, throw away session info for that play session. @@ -163,22 +170,22 @@ class LobbyData(BaseData): userid - Integer identifying a user, as possibly looked up by UserData. """ # Kill this play session - sql = ( - "DELETE FROM playsession WHERE game = :game AND version = :version AND userid = :userid" - ) + sql = "DELETE FROM playsession WHERE game = :game AND version = :version AND userid = :userid" self.execute( sql, { - 'game': game.value, - 'version': version, - 'userid': userid, + "game": game.value, + "version": version, + "userid": userid, }, ) # Prune any orphaned lobbies too sql = "DELETE FROM playsession WHERE time <= :time" - self.execute(sql, {'time': Time.now() - Time.SECONDS_IN_HOUR}) + self.execute(sql, {"time": Time.now() - Time.SECONDS_IN_HOUR}) - def get_lobby(self, game: GameConstants, version: int, userid: UserID) -> Optional[ValidatedDict]: + def get_lobby( + self, game: GameConstants, version: int, userid: UserID + ) -> Optional[ValidatedDict]: """ Given a game, version and a user ID, look up lobby information for that user. @@ -201,10 +208,10 @@ class LobbyData(BaseData): cursor = self.execute( sql, { - 'game': game.value, - 'version': version, - 'userid': userid, - 'time': Time.now() - Time.SECONDS_IN_HOUR, + "game": game.value, + "version": version, + "userid": userid, + "time": Time.now() - Time.SECONDS_IN_HOUR, }, ) @@ -213,12 +220,14 @@ class LobbyData(BaseData): return None result = cursor.fetchone() - data = ValidatedDict(self.deserialize(result['data'])) - data['id'] = result['id'] - data['time'] = result['time'] + data = ValidatedDict(self.deserialize(result["data"])) + data["id"] = result["id"] + data["time"] = result["time"] return data - def get_all_lobbies(self, game: GameConstants, version: int) -> List[Tuple[UserID, ValidatedDict]]: + def get_all_lobbies( + self, game: GameConstants, version: int + ) -> List[Tuple[UserID, ValidatedDict]]: """ Given a game and version, look up all active lobbies. @@ -236,21 +245,23 @@ class LobbyData(BaseData): cursor = self.execute( sql, { - 'game': game.value, - 'version': version, - 'time': Time.now() - Time.SECONDS_IN_HOUR, + "game": game.value, + "version": version, + "time": Time.now() - Time.SECONDS_IN_HOUR, }, ) ret = [] for result in cursor.fetchall(): - data = ValidatedDict(self.deserialize(result['data'])) - data['id'] = result['id'] - data['time'] = result['time'] - ret.append((UserID(result['userid']), data)) + data = ValidatedDict(self.deserialize(result["data"])) + data["id"] = result["id"] + data["time"] = result["time"] + ret.append((UserID(result["userid"]), data)) return ret - def put_lobby(self, game: GameConstants, version: int, userid: UserID, data: Dict[str, Any]) -> None: + def put_lobby( + self, game: GameConstants, version: int, userid: UserID, data: Dict[str, Any] + ) -> None: """ Given a game, version and a user ID, save lobby information for that user. @@ -261,25 +272,25 @@ class LobbyData(BaseData): data - A dictionary of lobby information to store. """ data = copy.deepcopy(data) - if 'id' in data: - del data['id'] - if 'time' in data: - del data['time'] + if "id" in data: + del data["id"] + if "time" in data: + del data["time"] # Add json to lobby sql = ( - "INSERT INTO lobby (game, version, userid, time, data) " + - "VALUES (:game, :version, :userid, :time, :data) " + - "ON DUPLICATE KEY UPDATE time=VALUES(time), data=VALUES(data)" + "INSERT INTO lobby (game, version, userid, time, data) " + + "VALUES (:game, :version, :userid, :time, :data) " + + "ON DUPLICATE KEY UPDATE time=VALUES(time), data=VALUES(data)" ) self.execute( sql, { - 'game': game.value, - 'version': version, - 'userid': userid, - 'time': Time.now(), - 'data': self.serialize(data), + "game": game.value, + "version": version, + "userid": userid, + "time": Time.now(), + "data": self.serialize(data), }, ) @@ -293,7 +304,7 @@ class LobbyData(BaseData): """ # Delete this lobby sql = "DELETE FROM lobby WHERE id = :id" - self.execute(sql, {'id': lobbyid}) + self.execute(sql, {"id": lobbyid}) # Prune any orphaned lobbies too sql = "DELETE FROM lobby WHERE time <= :time" - self.execute(sql, {'time': Time.now() - Time.SECONDS_IN_HOUR}) + self.execute(sql, {"time": Time.now() - Time.SECONDS_IN_HOUR}) diff --git a/bemani/data/mysql/machine.py b/bemani/data/mysql/machine.py index af7888e..319ae53 100644 --- a/bemani/data/mysql/machine.py +++ b/bemani/data/mysql/machine.py @@ -15,18 +15,18 @@ a connection. It is also used for settings such as port forwarding and which arc a machine belongs to for the purpose of PASELI balance. """ machine = Table( - 'machine', + "machine", metadata, - Column('id', Integer, nullable=False, primary_key=True), - Column('pcbid', String(20), nullable=False, unique=True), - Column('name', String(255), nullable=False), - Column('description', String(255), nullable=False), - Column('arcadeid', Integer), - Column('port', Integer, nullable=False, unique=True), - Column('game', String(20)), - Column('version', Integer), - Column('data', JSON), - mysql_charset='utf8mb4', + Column("id", Integer, nullable=False, primary_key=True), + Column("pcbid", String(20), nullable=False, unique=True), + Column("name", String(255), nullable=False), + Column("description", String(255), nullable=False), + Column("arcadeid", Integer), + Column("port", Integer, nullable=False, unique=True), + Column("game", String(20)), + Column("version", Integer), + Column("data", JSON), + mysql_charset="utf8mb4", ) """ @@ -34,27 +34,27 @@ Table for storing an arcade, to which zero or more machines may belong. This all an arcade to override some global settings such as PASELI enabled and infinite. """ arcade = Table( - 'arcade', + "arcade", metadata, - Column('id', Integer, nullable=False, primary_key=True), - Column('name', String(255), nullable=False), - Column('description', String(255), nullable=False), - Column('pin', String(8), nullable=False), - Column('pref', Integer, nullable=False), - Column('data', JSON), - mysql_charset='utf8mb4', + Column("id", Integer, nullable=False, primary_key=True), + Column("name", String(255), nullable=False), + Column("description", String(255), nullable=False), + Column("pin", String(8), nullable=False), + Column("pref", Integer, nullable=False), + Column("data", JSON), + mysql_charset="utf8mb4", ) """ Table for storing arcade ownership. This allows for more than one owner to own an arcade. """ arcade_owner = Table( - 'arcade_owner', + "arcade_owner", metadata, - Column('userid', BigInteger(unsigned=True), nullable=False), - Column('arcadeid', Integer, nullable=False), - UniqueConstraint('userid', 'arcadeid', name='userid_arcadeid'), - mysql_charset='utf8mb4', + Column("userid", BigInteger(unsigned=True), nullable=False), + Column("arcadeid", Integer, nullable=False), + UniqueConstraint("userid", "arcadeid", name="userid_arcadeid"), + mysql_charset="utf8mb4", ) """ @@ -63,15 +63,17 @@ owner to change settings related to a particular game, such as the events active shop ranking courses. """ arcade_settings = Table( - 'arcade_settings', + "arcade_settings", metadata, - Column('arcadeid', Integer, nullable=False), - Column('game', String(32), nullable=False), - Column('version', Integer, nullable=False), - Column('type', String(64), nullable=False), - Column('data', JSON, nullable=False), - UniqueConstraint('arcadeid', 'game', 'version', 'type', name='arcadeid_game_version_type'), - mysql_charset='utf8mb4', + Column("arcadeid", Integer, nullable=False), + Column("game", String(32), nullable=False), + Column("version", Integer, nullable=False), + Column("type", String(64), nullable=False), + Column("data", JSON, nullable=False), + UniqueConstraint( + "arcadeid", "game", "version", "type", name="arcadeid_game_version_type" + ), + mysql_charset="utf8mb4", ) @@ -97,13 +99,13 @@ class MachineData(BaseData): there is no machine matching this port. """ sql = "SELECT pcbid FROM machine WHERE port = :port LIMIT 1" - cursor = self.execute(sql, {'port': port}) + cursor = self.execute(sql, {"port": port}) if cursor.rowcount != 1: # Machine doesn't exist return None result = cursor.fetchone() - return result['pcbid'] + return result["pcbid"] def from_machine_id(self, machine_id: int) -> Optional[str]: """ @@ -117,13 +119,13 @@ class MachineData(BaseData): there is no machine matching this ID. """ sql = "SELECT pcbid FROM machine WHERE id = :id LIMIT 1" - cursor = self.execute(sql, {'id': machine_id}) + cursor = self.execute(sql, {"id": machine_id}) if cursor.rowcount != 1: # Machine doesn't exist return None result = cursor.fetchone() - return result['pcbid'] + return result["pcbid"] def from_userid(self, userid: UserID) -> List[ArcadeID]: """ @@ -136,8 +138,8 @@ class MachineData(BaseData): A list of integer IDs of the arcades this user owns. """ sql = "SELECT arcadeid FROM arcade_owner WHERE userid = :userid" - cursor = self.execute(sql, {'userid': userid}) - return [ArcadeID(result['arcadeid']) for result in cursor.fetchall()] + cursor = self.execute(sql, {"userid": userid}) + return [ArcadeID(result["arcadeid"]) for result in cursor.fetchall()] def from_session(self, session: str) -> Optional[ArcadeID]: """ @@ -149,7 +151,7 @@ class MachineData(BaseData): Returns: User ID as an integer if found, or None if the session is expired or doesn't exist. """ - arcadeid = self._from_session(session, 'arcadeid') + arcadeid = self._from_session(session, "arcadeid") if arcadeid is None: return None return ArcadeID(arcadeid) @@ -165,25 +167,25 @@ class MachineData(BaseData): A Machine object representing a machine, or None if not found. """ sql = "SELECT name, description, arcadeid, id, port, game, version, data FROM machine WHERE pcbid = :pcbid" - cursor = self.execute(sql, {'pcbid': pcbid}) + cursor = self.execute(sql, {"pcbid": pcbid}) if cursor.rowcount != 1: # Machine doesn't exist return None result = cursor.fetchone() return Machine( - result['id'], + result["id"], pcbid, - result['name'], - result['description'], - result['arcadeid'], - result['port'], - GameConstants(result['game']) if result['game'] else None, - result['version'], - self.deserialize(result['data']), + result["name"], + result["description"], + result["arcadeid"], + result["port"], + GameConstants(result["game"]) if result["game"] else None, + result["version"], + self.deserialize(result["data"]), ) - def get_all_machines(self, arcade: Optional[ArcadeID]=None) -> List[Machine]: + def get_all_machines(self, arcade: Optional[ArcadeID] = None) -> List[Machine]: """ Look up all machines on the network. @@ -193,22 +195,23 @@ class MachineData(BaseData): sql = "SELECT pcbid, name, description, arcadeid, id, port, game, version, data FROM machine" data = {} if arcade is not None: - sql = sql + ' WHERE arcadeid = :arcade' - data['arcade'] = arcade + sql = sql + " WHERE arcadeid = :arcade" + data["arcade"] = arcade cursor = self.execute(sql, data) return [ Machine( - result['id'], - result['pcbid'], - result['name'], - result['description'], - result['arcadeid'], - result['port'], - GameConstants(result['game']) if result['game'] else None, - result['version'], - self.deserialize(result['data']), - ) for result in cursor.fetchall() + result["id"], + result["pcbid"], + result["name"], + result["description"], + result["arcadeid"], + result["port"], + GameConstants(result["game"]) if result["game"] else None, + result["version"], + self.deserialize(result["data"]), + ) + for result in cursor.fetchall() ] def put_machine(self, machine: Machine) -> None: @@ -220,24 +223,30 @@ class MachineData(BaseData): """ # Update machine name based on game sql = ( - "UPDATE `machine` SET name = :name, description = :description, arcadeid = :arcadeid, " + - "port = :port, game = :game, version = :version, data = :data WHERE pcbid = :pcbid LIMIT 1" + "UPDATE `machine` SET name = :name, description = :description, arcadeid = :arcadeid, " + + "port = :port, game = :game, version = :version, data = :data WHERE pcbid = :pcbid LIMIT 1" ) self.execute( sql, { - 'name': machine.name, - 'description': machine.description, - 'arcadeid': machine.arcade, - 'port': machine.port, - 'game': machine.game.value if machine.game else None, - 'version': machine.version, - 'pcbid': machine.pcbid, - 'data': self.serialize(machine.data) + "name": machine.name, + "description": machine.description, + "arcadeid": machine.arcade, + "port": machine.port, + "game": machine.game.value if machine.game else None, + "version": machine.version, + "pcbid": machine.pcbid, + "data": self.serialize(machine.data), }, ) - def create_machine(self, pcbid: str, name: str='なし', description: str='', arcade: Optional[ArcadeID]=None) -> Machine: + def create_machine( + self, + pcbid: str, + name: str = "なし", + description: str = "", + arcade: Optional[ArcadeID] = None, + ) -> Machine: """ Given a PCBID, create a new machine entry. @@ -262,7 +271,7 @@ class MachineData(BaseData): else: # Grab highest port result = cursor.fetchone() - port = result['port'] + port = result["port"] if port is not None: port = port + 1 # Default if we didn't get a port @@ -272,10 +281,19 @@ class MachineData(BaseData): # Add new machine try: sql = ( - "INSERT INTO `machine` (pcbid, name, description, port, arcadeid) " + - "VALUES (:pcbid, :name, :description, :port, :arcadeid)" + "INSERT INTO `machine` (pcbid, name, description, port, arcadeid) " + + "VALUES (:pcbid, :name, :description, :port, :arcadeid)" + ) + self.execute( + sql, + { + "pcbid": pcbid, + "name": name, + "description": description, + "port": port, + "arcadeid": arcade, + }, ) - self.execute(sql, {'pcbid': pcbid, 'name': name, 'description': description, 'port': port, 'arcadeid': arcade}) except Exception: # Failed to add machine, try with new port continue @@ -292,9 +310,16 @@ class MachineData(BaseData): pcbid - The PCBID as returned from a game. """ sql = "DELETE FROM `machine` WHERE pcbid = :pcbid LIMIT 1" - self.execute(sql, {'pcbid': pcbid}) + self.execute(sql, {"pcbid": pcbid}) - def create_arcade(self, name: str, description: str, region: int, data: Dict[str, Any], owners: List[UserID]) -> Arcade: + def create_arcade( + self, + name: str, + description: str, + region: int, + data: Dict[str, Any], + owners: List[UserID], + ) -> Arcade: """ Given a set of values, create a new arcade and return the ID of that arcade. @@ -302,24 +327,26 @@ class MachineData(BaseData): An Arcade object representing this arcade """ sql = ( - "INSERT INTO arcade (name, description, pref, data, pin) " + - "VALUES (:name, :desc, :pref, :data, '00000000')" + "INSERT INTO arcade (name, description, pref, data, pin) " + + "VALUES (:name, :desc, :pref, :data, '00000000')" ) cursor = self.execute( sql, { - 'name': name, - 'desc': description, - 'pref': region, - 'data': self.serialize(data), + "name": name, + "desc": description, + "pref": region, + "data": self.serialize(data), }, ) if cursor.rowcount != 1: - raise ArcadeCreationException('Failed to create arcade!') + raise ArcadeCreationException("Failed to create arcade!") arcadeid = cursor.lastrowid for owner in owners: - sql = "INSERT INTO arcade_owner (userid, arcadeid) VALUES(:userid, :arcadeid)" - self.execute(sql, {'userid': owner, 'arcadeid': arcadeid}) + sql = ( + "INSERT INTO arcade_owner (userid, arcadeid) VALUES(:userid, :arcadeid)" + ) + self.execute(sql, {"userid": owner, "arcadeid": arcadeid}) new_arcade = self.get_arcade(arcadeid) if new_arcade is None: raise Exception("Failed to create an arcade!") @@ -335,10 +362,8 @@ class MachineData(BaseData): Returns: An Arcade object if this arcade was found, or None otherwise. """ - sql = ( - "SELECT name, description, pin, pref, data FROM arcade WHERE id = :id" - ) - cursor = self.execute(sql, {'id': arcadeid}) + sql = "SELECT name, description, pin, pref, data FROM arcade WHERE id = :id" + cursor = self.execute(sql, {"id": arcadeid}) if cursor.rowcount != 1: # Arcade doesn't exist return None @@ -346,16 +371,16 @@ class MachineData(BaseData): result = cursor.fetchone() sql = "SELECT userid FROM arcade_owner WHERE arcadeid = :id" - cursor = self.execute(sql, {'id': arcadeid}) + cursor = self.execute(sql, {"id": arcadeid}) return Arcade( arcadeid, - result['name'], - result['description'], - result['pin'], - result['pref'], - self.deserialize(result['data']), - [owner['userid'] for owner in cursor.fetchall()], + result["name"], + result["description"], + result["pin"], + result["pref"], + self.deserialize(result["data"]), + [owner["userid"] for owner in cursor.fetchall()], ) def put_arcade(self, arcade: Arcade) -> None: @@ -367,26 +392,28 @@ class MachineData(BaseData): """ # Update machine name based on game sql = ( - "UPDATE `arcade` " + - "SET name = :name, description = :desc, pin = :pin, pref = :pref, data = :data " + - "WHERE id = :arcadeid" + "UPDATE `arcade` " + + "SET name = :name, description = :desc, pin = :pin, pref = :pref, data = :data " + + "WHERE id = :arcadeid" ) self.execute( sql, { - 'name': arcade.name, - 'desc': arcade.description, - 'pin': arcade.pin, - 'pref': arcade.region, - 'data': self.serialize(arcade.data), - 'arcadeid': arcade.id, + "name": arcade.name, + "desc": arcade.description, + "pin": arcade.pin, + "pref": arcade.region, + "data": self.serialize(arcade.data), + "arcadeid": arcade.id, }, ) sql = "DELETE FROM `arcade_owner` WHERE arcadeid = :arcadeid" - self.execute(sql, {'arcadeid': arcade.id}) + self.execute(sql, {"arcadeid": arcade.id}) for owner in arcade.owners: - sql = "INSERT INTO arcade_owner (userid, arcadeid) VALUES(:userid, :arcadeid)" - self.execute(sql, {'userid': owner, 'arcadeid': arcade.id}) + sql = ( + "INSERT INTO arcade_owner (userid, arcadeid) VALUES(:userid, :arcadeid)" + ) + self.execute(sql, {"userid": owner, "arcadeid": arcade.id}) def destroy_arcade(self, arcadeid: ArcadeID) -> None: """ @@ -397,11 +424,11 @@ class MachineData(BaseData): arcadeid - Integer specifying the arcade to delete. """ sql = "DELETE FROM `arcade` WHERE id = :arcadeid LIMIT 1" - self.execute(sql, {'arcadeid': arcadeid}) + self.execute(sql, {"arcadeid": arcadeid}) sql = "DELETE FROM `arcade_owner` WHERE arcadeid = :arcadeid" - self.execute(sql, {'arcadeid': arcadeid}) + self.execute(sql, {"arcadeid": arcadeid}) sql = "UPDATE `machine` SET arcadeid = NULL WHERE arcadeid = :arcadeid" - self.execute(sql, {'arcadeid': arcadeid}) + self.execute(sql, {"arcadeid": arcadeid}) def get_all_arcades(self) -> List[Arcade]: """ @@ -414,8 +441,8 @@ class MachineData(BaseData): cursor = self.execute(sql) arcade_to_owners: Dict[int, List[UserID]] = {} for row in cursor.fetchall(): - arcade = row['arcadeid'] - owner = UserID(row['userid']) + arcade = row["arcadeid"] + owner = UserID(row["userid"]) if arcade not in arcade_to_owners: arcade_to_owners[arcade] = [] arcade_to_owners[arcade].append(owner) @@ -424,17 +451,20 @@ class MachineData(BaseData): cursor = self.execute(sql) return [ Arcade( - ArcadeID(result['id']), - result['name'], - result['description'], - result['pin'], - result['pref'], - self.deserialize(result['data']), - arcade_to_owners.get(result['id'], []), - ) for result in cursor.fetchall() + ArcadeID(result["id"]), + result["name"], + result["description"], + result["pin"], + result["pref"], + self.deserialize(result["data"]), + arcade_to_owners.get(result["id"], []), + ) + for result in cursor.fetchall() ] - def get_settings(self, arcadeid: ArcadeID, game: GameConstants, version: int, setting: str) -> Optional[ValidatedDict]: + def get_settings( + self, arcadeid: ArcadeID, game: GameConstants, version: int, setting: str + ) -> Optional[ValidatedDict]: """ Given an arcade and a game/version combo, look up this particular setting. @@ -448,16 +478,26 @@ class MachineData(BaseData): A dictionary representing game settings, or None if there are no settings for this game/user. """ sql = "SELECT data FROM arcade_settings WHERE arcadeid = :id AND game = :game AND version = :version AND type = :type" - cursor = self.execute(sql, {'id': arcadeid, 'game': game.value, 'version': version, 'type': setting}) + cursor = self.execute( + sql, + {"id": arcadeid, "game": game.value, "version": version, "type": setting}, + ) if cursor.rowcount != 1: # Settings doesn't exist return None result = cursor.fetchone() - return ValidatedDict(self.deserialize(result['data'])) + return ValidatedDict(self.deserialize(result["data"])) - def put_settings(self, arcadeid: ArcadeID, game: GameConstants, version: int, setting: str, data: Dict[str, Any]) -> None: + def put_settings( + self, + arcadeid: ArcadeID, + game: GameConstants, + version: int, + setting: str, + data: Dict[str, Any], + ) -> None: """ Given an arcade and a game/version combo, update the particular setting. @@ -469,11 +509,20 @@ class MachineData(BaseData): data - A dictionary that should be saved for this setting. """ sql = ( - "INSERT INTO arcade_settings (arcadeid, game, version, type, data) " + - "VALUES (:id, :game, :version, :type, :data) " + "INSERT INTO arcade_settings (arcadeid, game, version, type, data) " + + "VALUES (:id, :game, :version, :type, :data) " "ON DUPLICATE KEY UPDATE data=VALUES(data)" ) - self.execute(sql, {'id': arcadeid, 'game': game.value, 'version': version, 'type': setting, 'data': self.serialize(data)}) + self.execute( + sql, + { + "id": arcadeid, + "game": game.value, + "version": version, + "type": setting, + "data": self.serialize(data), + }, + ) def get_balances(self, arcadeid: ArcadeID) -> List[Tuple[UserID, int]]: """ @@ -486,16 +535,18 @@ class MachineData(BaseData): The PASELI balance for each user at this arcade. """ sql = "SELECT userid, balance FROM balance WHERE arcadeid = :arcadeid" - cursor = self.execute(sql, {'arcadeid': arcadeid}) + cursor = self.execute(sql, {"arcadeid": arcadeid}) balances = [] for entry in cursor.fetchall(): - balances.append(( - UserID(entry['userid']), - entry['balance'], - )) + balances.append( + ( + UserID(entry["userid"]), + entry["balance"], + ) + ) return balances - def create_session(self, arcadeid: ArcadeID, expiration: int=(30 * 86400)) -> str: + def create_session(self, arcadeid: ArcadeID, expiration: int = (30 * 86400)) -> str: """ Given a user ID, create a session string. @@ -506,7 +557,7 @@ class MachineData(BaseData): Returns: A string that can be used as a session ID. """ - return self._create_session(arcadeid, 'arcadeid', expiration) + return self._create_session(arcadeid, "arcadeid", expiration) def destroy_session(self, session: str) -> None: """ @@ -515,4 +566,4 @@ class MachineData(BaseData): Parameters: session - A session string as returned from create_session. """ - self._destroy_session(session, 'arcadeid') + self._destroy_session(session, "arcadeid") diff --git a/bemani/data/mysql/music.py b/bemani/data/mysql/music.py index c76b541..c3850a7 100644 --- a/bemani/data/mysql/music.py +++ b/bemani/data/mysql/music.py @@ -20,18 +20,18 @@ managed by the music table. This is so we can support keeping the same score acr multiple games, even if the game changes the ID it refers to the song by. """ score = Table( - 'score', + "score", metadata, - Column('id', Integer, nullable=False, primary_key=True), - Column('userid', BigInteger(unsigned=True), nullable=False), - Column('musicid', Integer, nullable=False, index=True), - Column('points', Integer, nullable=False, index=True), - Column('timestamp', Integer, nullable=False, index=True), - Column('update', Integer, nullable=False, index=True), - Column('lid', Integer, nullable=False, index=True), - Column('data', JSON, nullable=False), - UniqueConstraint('userid', 'musicid', name='userid_musicid'), - mysql_charset='utf8mb4', + Column("id", Integer, nullable=False, primary_key=True), + Column("userid", BigInteger(unsigned=True), nullable=False), + Column("musicid", Integer, nullable=False, index=True), + Column("points", Integer, nullable=False, index=True), + Column("timestamp", Integer, nullable=False, index=True), + Column("update", Integer, nullable=False, index=True), + Column("lid", Integer, nullable=False, index=True), + Column("data", JSON, nullable=False), + UniqueConstraint("userid", "musicid", name="userid_musicid"), + mysql_charset="utf8mb4", ) """ @@ -40,18 +40,18 @@ or updated in score will be written into this table as well, for looking up hist over time. """ score_history = Table( - 'score_history', + "score_history", metadata, - Column('id', Integer, nullable=False, primary_key=True), - Column('userid', BigInteger(unsigned=True), nullable=False), - Column('musicid', Integer, nullable=False, index=True), - Column('points', Integer, nullable=False), - Column('timestamp', Integer, nullable=False, index=True), - Column('lid', Integer, nullable=False, index=True), - Column('new_record', Integer, nullable=False), - Column('data', JSON, nullable=False), - UniqueConstraint('userid', 'musicid', 'timestamp', name='userid_musicid_timestamp'), - mysql_charset='utf8mb4', + Column("id", Integer, nullable=False, primary_key=True), + Column("userid", BigInteger(unsigned=True), nullable=False), + Column("musicid", Integer, nullable=False, index=True), + Column("points", Integer, nullable=False), + Column("timestamp", Integer, nullable=False, index=True), + Column("lid", Integer, nullable=False, index=True), + Column("new_record", Integer, nullable=False), + Column("data", JSON, nullable=False), + UniqueConstraint("userid", "musicid", "timestamp", name="userid_musicid_timestamp"), + mysql_charset="utf8mb4", ) """ @@ -64,25 +64,28 @@ as the game version changes. In this way, a song which is in multiple versions o the game can be found when playing each version. """ music = Table( - 'music', + "music", metadata, - Column('id', Integer, nullable=False, index=True), - Column('songid', Integer, nullable=False), - Column('chart', Integer, nullable=False), - Column('game', String(32), nullable=False, index=True), - Column('version', Integer, nullable=False, index=True), - Column('name', String(255)), - Column('artist', String(255)), - Column('genre', String(255)), - Column('data', JSON), - UniqueConstraint('songid', 'chart', 'game', 'version', name='songid_chart_game_version'), - mysql_charset='utf8mb4', + Column("id", Integer, nullable=False, index=True), + Column("songid", Integer, nullable=False), + Column("chart", Integer, nullable=False), + Column("game", String(32), nullable=False, index=True), + Column("version", Integer, nullable=False, index=True), + Column("name", String(255)), + Column("artist", String(255)), + Column("genre", String(255)), + Column("data", JSON), + UniqueConstraint( + "songid", "chart", "game", "version", name="songid_chart_game_version" + ), + mysql_charset="utf8mb4", ) class MusicData(BaseData): - - def __get_musicid(self, game: GameConstants, version: int, songid: int, songchart: int) -> int: + def __get_musicid( + self, game: GameConstants, version: int, songid: int, songchart: int + ) -> int: """ Given a game/version/songid/chart, look up the unique music ID for this song. @@ -95,15 +98,23 @@ class MusicData(BaseData): Returns: Integer representing music ID if found or raises an exception otherwise. """ - sql = ( - "SELECT id FROM music WHERE songid = :songid AND chart = :chart AND game = :game AND version = :version" + sql = "SELECT id FROM music WHERE songid = :songid AND chart = :chart AND game = :game AND version = :version" + cursor = self.execute( + sql, + { + "songid": songid, + "chart": songchart, + "game": game.value, + "version": version, + }, ) - cursor = self.execute(sql, {'songid': songid, 'chart': songchart, 'game': game.value, 'version': version}) if cursor.rowcount != 1: # music doesn't exist - raise Exception(f'Song {songid} chart {songchart} doesn\'t exist for game {game} version {version}') + raise Exception( + f"Song {songid} chart {songchart} doesn't exist for game {game} version {version}" + ) result = cursor.fetchone() - return result['id'] + return result["id"] def put_score( self, @@ -116,7 +127,7 @@ class MusicData(BaseData): points: int, data: Dict[str, Any], new_record: bool, - timestamp: Optional[int]=None, + timestamp: Optional[int] = None, ) -> None: """ Given a game/version/song/chart and user ID, save a new/updated high score. @@ -141,29 +152,29 @@ class MusicData(BaseData): if new_record: # We want to update the timestamp/location to now if its a new record. sql = ( - "INSERT INTO `score` (`userid`, `musicid`, `points`, `data`, `timestamp`, `update`, `lid`) " + - "VALUES (:userid, :musicid, :points, :data, :timestamp, :update, :location) " + - "ON DUPLICATE KEY UPDATE data = VALUES(data), points = VALUES(points), " + - "timestamp = VALUES(timestamp), `update` = VALUES(`update`), lid = VALUES(lid)" + "INSERT INTO `score` (`userid`, `musicid`, `points`, `data`, `timestamp`, `update`, `lid`) " + + "VALUES (:userid, :musicid, :points, :data, :timestamp, :update, :location) " + + "ON DUPLICATE KEY UPDATE data = VALUES(data), points = VALUES(points), " + + "timestamp = VALUES(timestamp), `update` = VALUES(`update`), lid = VALUES(lid)" ) else: # We only want to add the timestamp if it is new. sql = ( - "INSERT INTO `score` (`userid`, `musicid`, `points`, `data`, `timestamp`, `update`, `lid`) " + - "VALUES (:userid, :musicid, :points, :data, :timestamp, :update, :location) " + - "ON DUPLICATE KEY UPDATE data = VALUES(data), points = VALUES(points), `update` = VALUES(`update`)" + "INSERT INTO `score` (`userid`, `musicid`, `points`, `data`, `timestamp`, `update`, `lid`) " + + "VALUES (:userid, :musicid, :points, :data, :timestamp, :update, :location) " + + "ON DUPLICATE KEY UPDATE data = VALUES(data), points = VALUES(points), `update` = VALUES(`update`)" ) self.execute( sql, { - 'userid': userid, - 'musicid': musicid, - 'points': points, - 'data': self.serialize(data), - 'timestamp': ts, - 'update': ts, - 'location': location, - } + "userid": userid, + "musicid": musicid, + "points": points, + "data": self.serialize(data), + "timestamp": ts, + "update": ts, + "location": location, + }, ) def put_attempt( @@ -177,7 +188,7 @@ class MusicData(BaseData): points: int, data: Dict[str, Any], new_record: bool, - timestamp: Optional[int]=None, + timestamp: Optional[int] = None, ) -> None: """ Given a game/version/song/chart and user ID, save a single score attempt. @@ -203,28 +214,35 @@ class MusicData(BaseData): # Add to score history sql = ( - "INSERT INTO `score_history` (userid, musicid, timestamp, lid, new_record, points, data) " + - "VALUES (:userid, :musicid, :timestamp, :location, :new_record, :points, :data)" + "INSERT INTO `score_history` (userid, musicid, timestamp, lid, new_record, points, data) " + + "VALUES (:userid, :musicid, :timestamp, :location, :new_record, :points, :data)" ) try: self.execute( sql, { - 'userid': userid if userid is not None else 0, - 'musicid': musicid, - 'timestamp': ts, - 'location': location, - 'new_record': 1 if new_record else 0, - 'points': points, - 'data': self.serialize(data), + "userid": userid if userid is not None else 0, + "musicid": musicid, + "timestamp": ts, + "location": location, + "new_record": 1 if new_record else 0, + "points": points, + "data": self.serialize(data), }, ) except IntegrityError: raise ScoreSaveException( - f'There is already an attempt by {userid if userid is not None else 0} for music id {musicid} at {ts}' + f"There is already an attempt by {userid if userid is not None else 0} for music id {musicid} at {ts}" ) - def get_score(self, game: GameConstants, version: int, userid: UserID, songid: int, songchart: int) -> Optional[Score]: + def get_score( + self, + game: GameConstants, + version: int, + userid: UserID, + songid: int, + songchart: int, + ) -> Optional[Score]: """ Look up a user's previous high score. @@ -239,19 +257,19 @@ class MusicData(BaseData): The optional data stored by the game previously, or None if no score exists. """ sql = ( - "SELECT music.songid AS songid, music.chart AS chart, score.id AS scorekey, score.timestamp AS timestamp, score.update AS `update`, score.lid AS lid, " + - "(select COUNT(score_history.timestamp) FROM score_history WHERE score_history.musicid = music.id AND score_history.userid = :userid) AS plays, " + - "score.points AS points, score.data AS data FROM score, music WHERE score.userid = :userid AND score.musicid = music.id " + - "AND music.game = :game AND music.version = :version AND music.songid = :songid AND music.chart = :songchart" + "SELECT music.songid AS songid, music.chart AS chart, score.id AS scorekey, score.timestamp AS timestamp, score.update AS `update`, score.lid AS lid, " + + "(select COUNT(score_history.timestamp) FROM score_history WHERE score_history.musicid = music.id AND score_history.userid = :userid) AS plays, " + + "score.points AS points, score.data AS data FROM score, music WHERE score.userid = :userid AND score.musicid = music.id " + + "AND music.game = :game AND music.version = :version AND music.songid = :songid AND music.chart = :songchart" ) cursor = self.execute( sql, { - 'userid': userid, - 'game': game.value, - 'version': version, - 'songid': songid, - 'songchart': songchart, + "userid": userid, + "game": game.value, + "version": version, + "songid": songid, + "songchart": songchart, }, ) if cursor.rowcount != 1: @@ -260,18 +278,20 @@ class MusicData(BaseData): result = cursor.fetchone() return Score( - result['scorekey'], - result['songid'], - result['chart'], - result['points'], - result['timestamp'], - result['update'], - result['lid'], - result['plays'], - self.deserialize(result['data']), + result["scorekey"], + result["songid"], + result["chart"], + result["points"], + result["timestamp"], + result["update"], + result["lid"], + result["plays"], + self.deserialize(result["data"]), ) - def get_score_by_key(self, game: GameConstants, version: int, key: int) -> Optional[Tuple[UserID, Score]]: + def get_score_by_key( + self, game: GameConstants, version: int, key: int + ) -> Optional[Tuple[UserID, Score]]: """ Look up previous high score by key. @@ -284,18 +304,18 @@ class MusicData(BaseData): The optional data stored by the game previously, or None if no score exists. """ sql = ( - "SELECT music.songid AS songid, music.chart AS chart, score.id AS scorekey, score.timestamp AS timestamp, score.update AS `update`, " + - "score.userid AS userid, score.lid AS lid, " + - "(select COUNT(score_history.timestamp) FROM score_history WHERE score_history.musicid = music.id AND score_history.userid = score.userid) AS plays, " + - "score.points AS points, score.data AS data FROM score, music WHERE score.id = :scorekey AND score.musicid = music.id " + - "AND music.game = :game AND music.version = :version" + "SELECT music.songid AS songid, music.chart AS chart, score.id AS scorekey, score.timestamp AS timestamp, score.update AS `update`, " + + "score.userid AS userid, score.lid AS lid, " + + "(select COUNT(score_history.timestamp) FROM score_history WHERE score_history.musicid = music.id AND score_history.userid = score.userid) AS plays, " + + "score.points AS points, score.data AS data FROM score, music WHERE score.id = :scorekey AND score.musicid = music.id " + + "AND music.game = :game AND music.version = :version" ) cursor = self.execute( sql, { - 'game': game.value, - 'version': version, - 'scorekey': key, + "game": game.value, + "version": version, + "scorekey": key, }, ) if cursor.rowcount != 1: @@ -304,18 +324,18 @@ class MusicData(BaseData): result = cursor.fetchone() return ( - UserID(result['userid']), + UserID(result["userid"]), Score( - result['scorekey'], - result['songid'], - result['chart'], - result['points'], - result['timestamp'], - result['update'], - result['lid'], - result['plays'], - self.deserialize(result['data']), - ) + result["scorekey"], + result["songid"], + result["chart"], + result["points"], + result["timestamp"], + result["update"], + result["lid"], + result["plays"], + self.deserialize(result["data"]), + ), ) def get_scores( @@ -323,8 +343,8 @@ class MusicData(BaseData): game: GameConstants, version: int, userid: UserID, - since: Optional[int]=None, - until: Optional[int]=None, + since: Optional[int] = None, + until: Optional[int] = None, ) -> List[Score]: """ Look up all of a user's previous high scores. @@ -338,36 +358,47 @@ class MusicData(BaseData): A list of Score objects representing all high scores for a game. """ sql = ( - "SELECT music.songid AS songid, music.chart AS chart, score.id AS scorekey, score.timestamp AS timestamp, score.update AS `update`, score.lid AS lid, " + - "(select COUNT(score_history.timestamp) FROM score_history WHERE score_history.musicid = music.id AND score_history.userid = :userid) AS plays, " + - "score.points AS points, score.data AS data FROM score, music WHERE score.userid = :userid AND score.musicid = music.id " + - "AND music.game = :game AND music.version = :version" + "SELECT music.songid AS songid, music.chart AS chart, score.id AS scorekey, score.timestamp AS timestamp, score.update AS `update`, score.lid AS lid, " + + "(select COUNT(score_history.timestamp) FROM score_history WHERE score_history.musicid = music.id AND score_history.userid = :userid) AS plays, " + + "score.points AS points, score.data AS data FROM score, music WHERE score.userid = :userid AND score.musicid = music.id " + + "AND music.game = :game AND music.version = :version" ) if since is not None: - sql = sql + ' AND score.update >= :since' + sql = sql + " AND score.update >= :since" if until is not None: - sql = sql + ' AND score.update < :until' - cursor = self.execute(sql, {'userid': userid, 'game': game.value, 'version': version, 'since': since, 'until': until}) + sql = sql + " AND score.update < :until" + cursor = self.execute( + sql, + { + "userid": userid, + "game": game.value, + "version": version, + "since": since, + "until": until, + }, + ) scores = [] for result in cursor.fetchall(): scores.append( Score( - result['scorekey'], - result['songid'], - result['chart'], - result['points'], - result['timestamp'], - result['update'], - result['lid'], - result['plays'], - self.deserialize(result['data']), + result["scorekey"], + result["songid"], + result["chart"], + result["points"], + result["timestamp"], + result["update"], + result["lid"], + result["plays"], + self.deserialize(result["data"]), ) ) return scores - def get_most_played(self, game: GameConstants, version: int, userid: UserID, count: int) -> List[Tuple[int, int]]: + def get_most_played( + self, game: GameConstants, version: int, userid: UserID, count: int + ) -> List[Tuple[int, int]]: """ Look up a user's most played songs. @@ -381,22 +412,25 @@ class MusicData(BaseData): A list of tuples, containing the songid and the number of plays across all charts for that song. """ sql = ( - "SELECT music.songid AS songid, COUNT(score_history.timestamp) AS plays FROM score_history, music " + - "WHERE score_history.userid = :userid AND score_history.musicid = music.id " + - "AND music.game = :game AND music.version = :version " + - "GROUP BY songid ORDER BY plays DESC LIMIT :count" + "SELECT music.songid AS songid, COUNT(score_history.timestamp) AS plays FROM score_history, music " + + "WHERE score_history.userid = :userid AND score_history.musicid = music.id " + + "AND music.game = :game AND music.version = :version " + + "GROUP BY songid ORDER BY plays DESC LIMIT :count" + ) + cursor = self.execute( + sql, + {"userid": userid, "game": game.value, "version": version, "count": count}, ) - cursor = self.execute(sql, {'userid': userid, 'game': game.value, 'version': version, 'count': count}) most_played = [] for result in cursor.fetchall(): - most_played.append( - (result['songid'], result['plays']) - ) + most_played.append((result["songid"], result["plays"])) return most_played - def get_last_played(self, game: GameConstants, version: int, userid: UserID, count: int) -> List[Tuple[int, int]]: + def get_last_played( + self, game: GameConstants, version: int, userid: UserID, count: int + ) -> List[Tuple[int, int]]: """ Look up a user's last played songs. @@ -410,18 +444,19 @@ class MusicData(BaseData): A list of tuples, containing the songid and the last played time for this song. """ sql = ( - "SELECT DISTINCT(music.songid) AS songid, score_history.timestamp AS timestamp FROM score_history, music " + - "WHERE score_history.userid = :userid AND score_history.musicid = music.id " + - "AND music.game = :game AND music.version = :version " + - "ORDER BY timestamp DESC LIMIT :count" + "SELECT DISTINCT(music.songid) AS songid, score_history.timestamp AS timestamp FROM score_history, music " + + "WHERE score_history.userid = :userid AND score_history.musicid = music.id " + + "AND music.game = :game AND music.version = :version " + + "ORDER BY timestamp DESC LIMIT :count" + ) + cursor = self.execute( + sql, + {"userid": userid, "game": game.value, "version": version, "count": count}, ) - cursor = self.execute(sql, {'userid': userid, 'game': game.value, 'version': version, 'count': count}) last_played = [] for result in cursor.fetchall(): - last_played.append( - (result['songid'], result['timestamp']) - ) + last_played.append((result["songid"], result["timestamp"])) return last_played @@ -430,7 +465,7 @@ class MusicData(BaseData): game: GameConstants, version: int, count: int, - days: Optional[int]=None, + days: Optional[int] = None, ) -> List[Tuple[int, int]]: """ Look up a game's most played songs. @@ -444,8 +479,8 @@ class MusicData(BaseData): A list of tuples, containing the songid and the number of plays across all charts for that song. """ sql = ( - "SELECT music.songid AS songid, COUNT(score_history.timestamp) AS plays FROM score_history, music " + - "WHERE score_history.musicid = music.id AND music.game = :game AND music.version = :version " + "SELECT music.songid AS songid, COUNT(score_history.timestamp) AS plays FROM score_history, music " + + "WHERE score_history.musicid = music.id AND music.game = :game AND music.version = :version " ) timestamp: Optional[int] = None if days is not None: @@ -454,13 +489,19 @@ class MusicData(BaseData): timestamp = Time.now() - (Time.SECONDS_IN_DAY * days) sql = sql + "GROUP BY songid ORDER BY plays DESC LIMIT :count" - cursor = self.execute(sql, {'game': game.value, 'version': version, 'count': count, 'timestamp': timestamp}) + cursor = self.execute( + sql, + { + "game": game.value, + "version": version, + "count": count, + "timestamp": timestamp, + }, + ) most_played = [] for result in cursor.fetchall(): - most_played.append( - (result['songid'], result['plays']) - ) + most_played.append((result["songid"], result["plays"])) return most_played @@ -484,11 +525,19 @@ class MusicData(BaseData): A Song object representing the song details """ sql = ( - "SELECT music.name AS name, music.artist AS artist, music.genre AS genre, music.data AS data " + - "FROM music WHERE music.game = :game AND music.version = :version AND " + - "music.songid = :songid AND music.chart = :songchart" + "SELECT music.name AS name, music.artist AS artist, music.genre AS genre, music.data AS data " + + "FROM music WHERE music.game = :game AND music.version = :version AND " + + "music.songid = :songid AND music.chart = :songchart" + ) + cursor = self.execute( + sql, + { + "game": game.value, + "version": version, + "songid": songid, + "songchart": songchart, + }, ) - cursor = self.execute(sql, {'game': game.value, 'version': version, 'songid': songid, 'songchart': songchart}) if cursor.rowcount != 1: # music doesn't exist return None @@ -498,16 +547,16 @@ class MusicData(BaseData): version, songid, songchart, - result['name'], - result['artist'], - result['genre'], - self.deserialize(result['data']), + result["name"], + result["artist"], + result["genre"], + self.deserialize(result["data"]), ) def get_all_songs( self, game: GameConstants, - version: Optional[int]=None, + version: Optional[int] = None, ) -> List[Song]: """ Given a game and a version, look up all song/chart combos associated with that game. @@ -523,10 +572,10 @@ class MusicData(BaseData): "SELECT version, songid, chart, name, artist, genre, data FROM music " "WHERE music.game = :game" ) - params: Dict[str, Any] = {'game': game.value} + params: Dict[str, Any] = {"game": game.value} if version is not None: sql += " AND music.version = :version" - params['version'] = version + params["version"] = version else: sql += " ORDER BY music.version DESC" cursor = self.execute(sql, params) @@ -536,13 +585,13 @@ class MusicData(BaseData): all_songs.append( Song( game, - result['version'], - result['songid'], - result['chart'], - result['name'], - result['artist'], - result['genre'], - self.deserialize(result['data']), + result["version"], + result["songid"], + result["chart"], + result["name"], + result["artist"], + result["genre"], + self.deserialize(result["data"]), ) ) @@ -575,19 +624,19 @@ class MusicData(BaseData): ) if interested_versions is not None: sql += f" AND music.version in ({','.join(str(int(v)) for v in interested_versions)})" - cursor = self.execute(sql, {'musicid': musicid}) + cursor = self.execute(sql, {"musicid": musicid}) all_songs = [] for result in cursor.fetchall(): all_songs.append( Song( game, - result['version'], - result['songid'], - result['chart'], - result['name'], - result['artist'], - result['genre'], - self.deserialize(result['data']), + result["version"], + result["songid"], + result["chart"], + result["name"], + result["artist"], + result["genre"], + self.deserialize(result["data"]), ) ) return all_songs @@ -595,12 +644,12 @@ class MusicData(BaseData): def get_all_scores( self, game: GameConstants, - version: Optional[int]=None, - userid: Optional[UserID]=None, - songid: Optional[int]=None, - songchart: Optional[int]=None, - since: Optional[int]=None, - until: Optional[int]=None, + version: Optional[int] = None, + userid: Optional[UserID] = None, + songid: Optional[int] = None, + songchart: Optional[int] = None, + since: Optional[int] = None, + until: Optional[int] = None, ) -> List[Tuple[UserID, Score]]: """ Look up all of a game's high scores for all users. @@ -614,35 +663,23 @@ class MusicData(BaseData): """ # First, construct the queries for grabbing the songid/chart if version is not None: - songidquery = ( - 'SELECT songid FROM music WHERE music.id = score.musicid AND game = :game AND version = :version' - ) - chartquery = ( - 'SELECT chart FROM music WHERE music.id = score.musicid AND game = :game AND version = :version' - ) + songidquery = "SELECT songid FROM music WHERE music.id = score.musicid AND game = :game AND version = :version" + chartquery = "SELECT chart FROM music WHERE music.id = score.musicid AND game = :game AND version = :version" else: - songidquery = ( - 'SELECT songid FROM music WHERE music.id = score.musicid AND game = :game ORDER BY version DESC LIMIT 1' - ) - chartquery = ( - 'SELECT chart FROM music WHERE music.id = score.musicid AND game = :game ORDER BY version DESC LIMIT 1' - ) + songidquery = "SELECT songid FROM music WHERE music.id = score.musicid AND game = :game ORDER BY version DESC LIMIT 1" + chartquery = "SELECT chart FROM music WHERE music.id = score.musicid AND game = :game ORDER BY version DESC LIMIT 1" # Select statement for getting play count - playselect = ( - 'SELECT COUNT(timestamp) FROM score_history WHERE score_history.musicid = score.musicid AND score_history.userid = score.userid' - ) + playselect = "SELECT COUNT(timestamp) FROM score_history WHERE score_history.musicid = score.musicid AND score_history.userid = score.userid" # Now, construct the inner select statement so we can choose which scores we care about - innerselect = ( - 'SELECT DISTINCT(id) FROM music WHERE game = :game' - ) + innerselect = "SELECT DISTINCT(id) FROM music WHERE game = :game" if version is not None: - innerselect = innerselect + ' AND version = :version' + innerselect = innerselect + " AND version = :version" if songid is not None: - innerselect = innerselect + ' AND songid = :songid' + innerselect = innerselect + " AND songid = :songid" if songchart is not None: - innerselect = innerselect + ' AND chart = :songchart' + innerselect = innerselect + " AND chart = :songchart" # Finally, construct the full query sql = ( @@ -652,40 +689,43 @@ class MusicData(BaseData): # Now, limit the query if userid is not None: - sql = sql + ' AND userid = :userid' + sql = sql + " AND userid = :userid" if since is not None: - sql = sql + ' AND score.update >= :since' + sql = sql + " AND score.update >= :since" if until is not None: - sql = sql + ' AND score.update < :until' + sql = sql + " AND score.update < :until" # Now, query itself - cursor = self.execute(sql, { - 'game': game.value, - 'version': version, - 'userid': userid, - 'songid': songid, - 'songchart': songchart, - 'since': since, - 'until': until, - }) + cursor = self.execute( + sql, + { + "game": game.value, + "version": version, + "userid": userid, + "songid": songid, + "songchart": songchart, + "since": since, + "until": until, + }, + ) # Objectify result scores = [] for result in cursor.fetchall(): scores.append( ( - UserID(result['userid']), + UserID(result["userid"]), Score( - result['scorekey'], - result['songid'], - result['chart'], - result['points'], - result['timestamp'], - result['update'], - result['lid'], - result['plays'], - self.deserialize(result['data']), - ) + result["scorekey"], + result["songid"], + result["chart"], + result["points"], + result["timestamp"], + result["update"], + result["lid"], + result["plays"], + self.deserialize(result["data"]), + ), ) ) @@ -694,9 +734,9 @@ class MusicData(BaseData): def get_all_records( self, game: GameConstants, - version: Optional[int]=None, - userlist: Optional[List[UserID]]=None, - locationlist: Optional[List[int]]=None, + version: Optional[int] = None, + userlist: Optional[List[UserID]] = None, + locationlist: Optional[List[int]] = None, ) -> List[Tuple[UserID, Score]]: """ Look up all of a game's records, only returning the top score for each song. For score ties, @@ -716,28 +756,18 @@ class MusicData(BaseData): """ # First, construct the queries for grabbing the songid/chart if version is not None: - songidquery = ( - 'SELECT songid FROM music WHERE music.id = score.musicid AND game = :game AND version = :version' - ) - chartquery = ( - 'SELECT chart FROM music WHERE music.id = score.musicid AND game = :game AND version = :version' - ) + songidquery = "SELECT songid FROM music WHERE music.id = score.musicid AND game = :game AND version = :version" + chartquery = "SELECT chart FROM music WHERE music.id = score.musicid AND game = :game AND version = :version" else: - songidquery = ( - 'SELECT songid FROM music WHERE music.id = score.musicid AND game = :game ORDER BY version DESC LIMIT 1' - ) - chartquery = ( - 'SELECT chart FROM music WHERE music.id = score.musicid AND game = :game ORDER BY version DESC LIMIT 1' - ) + songidquery = "SELECT songid FROM music WHERE music.id = score.musicid AND game = :game ORDER BY version DESC LIMIT 1" + chartquery = "SELECT chart FROM music WHERE music.id = score.musicid AND game = :game ORDER BY version DESC LIMIT 1" # Next, get a list of all songs that were played given the input criteria - musicid_sql = ( - "SELECT DISTINCT(score.musicid) FROM score, music WHERE score.musicid = music.id AND music.game = :game" - ) - params: Dict[str, Any] = {'game': game.value} + musicid_sql = "SELECT DISTINCT(score.musicid) FROM score, music WHERE score.musicid = music.id AND music.game = :game" + params: Dict[str, Any] = {"game": game.value} if version is not None: - musicid_sql = musicid_sql + ' AND music.version = :version' - params['version'] = version + musicid_sql = musicid_sql + " AND music.version = :version" + params["version"] = version # Figure out where the record was earned if locationlist is not None: @@ -745,7 +775,7 @@ class MusicData(BaseData): # We don't have any locations, but SQL will shit the bed, so lets add a default one. locationlist.append(-1) location_sql = "AND score.lid IN :locationlist" - params['locationlist'] = tuple(locationlist) + params["locationlist"] = tuple(locationlist) else: location_sql = "" @@ -755,17 +785,19 @@ class MusicData(BaseData): # We don't have any users, but SQL will shit the bed, so lets add a fake one. userlist.append(UserID(-1)) user_sql = f"SELECT userid FROM score WHERE score.musicid = played.musicid AND score.userid IN :userlist {location_sql} ORDER BY points DESC, timestamp DESC LIMIT 1" - params['userlist'] = tuple(userlist) + params["userlist"] = tuple(userlist) else: user_sql = f"SELECT userid FROM score WHERE score.musicid = played.musicid {location_sql} ORDER BY points DESC, timestamp DESC LIMIT 1" - records_sql = f"SELECT ({user_sql}) AS userid, musicid FROM ({musicid_sql}) played" + records_sql = ( + f"SELECT ({user_sql}) AS userid, musicid FROM ({musicid_sql}) played" + ) # Now, join it up against the score and music table to grab the info we need sql = ( - "SELECT ({}) AS songid, ({}) AS chart, score.points AS points, score.userid AS userid, score.id AS scorekey, score.data AS data, " + - "score.timestamp AS timestamp, score.update AS `update`, " + - "score.lid AS lid, (select COUNT(score_history.timestamp) FROM score_history WHERE score_history.musicid = score.musicid) AS plays " + - "FROM score, ({}) records WHERE records.userid = score.userid AND records.musicid = score.musicid" + "SELECT ({}) AS songid, ({}) AS chart, score.points AS points, score.userid AS userid, score.id AS scorekey, score.data AS data, " + + "score.timestamp AS timestamp, score.update AS `update`, " + + "score.lid AS lid, (select COUNT(score_history.timestamp) FROM score_history WHERE score_history.musicid = score.musicid) AS plays " + + "FROM score, ({}) records WHERE records.userid = score.userid AND records.musicid = score.musicid" ).format(songidquery, chartquery, records_sql) cursor = self.execute(sql, params) @@ -773,24 +805,26 @@ class MusicData(BaseData): for result in cursor.fetchall(): scores.append( ( - UserID(result['userid']), + UserID(result["userid"]), Score( - result['scorekey'], - result['songid'], - result['chart'], - result['points'], - result['timestamp'], - result['update'], - result['lid'], - result['plays'], - self.deserialize(result['data']), - ) + result["scorekey"], + result["songid"], + result["chart"], + result["points"], + result["timestamp"], + result["update"], + result["lid"], + result["plays"], + self.deserialize(result["data"]), + ), ) ) return scores - def get_attempt_by_key(self, game: GameConstants, version: int, key: int) -> Optional[Tuple[UserID, Attempt]]: + def get_attempt_by_key( + self, game: GameConstants, version: int, key: int + ) -> Optional[Tuple[UserID, Attempt]]: """ Look up a previous attempt by key. @@ -803,16 +837,16 @@ class MusicData(BaseData): The optional data stored by the game previously, or None if no score exists. """ sql = ( - "SELECT music.songid AS songid, music.chart AS chart, score_history.id AS scorekey, score_history.timestamp AS timestamp, score_history.userid AS userid, " + - "score_history.lid AS lid, score_history.new_record AS new_record, score_history.points AS points, score_history.data AS data FROM score_history, music " + - "WHERE score_history.id = :scorekey AND score_history.musicid = music.id AND music.game = :game AND music.version = :version" + "SELECT music.songid AS songid, music.chart AS chart, score_history.id AS scorekey, score_history.timestamp AS timestamp, score_history.userid AS userid, " + + "score_history.lid AS lid, score_history.new_record AS new_record, score_history.points AS points, score_history.data AS data FROM score_history, music " + + "WHERE score_history.id = :scorekey AND score_history.musicid = music.id AND music.game = :game AND music.version = :version" ) cursor = self.execute( sql, { - 'game': game.value, - 'version': version, - 'scorekey': key, + "game": game.value, + "version": version, + "scorekey": key, }, ) if cursor.rowcount != 1: @@ -821,29 +855,29 @@ class MusicData(BaseData): result = cursor.fetchone() return ( - UserID(result['userid']), + UserID(result["userid"]), Attempt( - result['scorekey'], - result['songid'], - result['chart'], - result['points'], - result['timestamp'], - result['lid'], - True if result['new_record'] == 1 else False, - self.deserialize(result['data']), - ) + result["scorekey"], + result["songid"], + result["chart"], + result["points"], + result["timestamp"], + result["lid"], + True if result["new_record"] == 1 else False, + self.deserialize(result["data"]), + ), ) def get_all_attempts( self, game: GameConstants, - version: Optional[int]=None, - userid: Optional[UserID]=None, - songid: Optional[int]=None, - songchart: Optional[int]=None, - timelimit: Optional[int]=None, - limit: Optional[int]=None, - offset: Optional[int]=None, + version: Optional[int] = None, + userid: Optional[UserID] = None, + songid: Optional[int] = None, + songchart: Optional[int] = None, + timelimit: Optional[int] = None, + limit: Optional[int] = None, + offset: Optional[int] = None, ) -> List[Tuple[Optional[UserID], Attempt]]: """ Look up all of the attempts to score for a particular game. @@ -857,30 +891,20 @@ class MusicData(BaseData): """ # First, construct the queries for grabbing the songid/chart if version is not None: - songidquery = ( - 'SELECT songid FROM music WHERE music.id = score_history.musicid AND game = :game AND version = :version' - ) - chartquery = ( - 'SELECT chart FROM music WHERE music.id = score_history.musicid AND game = :game AND version = :version' - ) + songidquery = "SELECT songid FROM music WHERE music.id = score_history.musicid AND game = :game AND version = :version" + chartquery = "SELECT chart FROM music WHERE music.id = score_history.musicid AND game = :game AND version = :version" else: - songidquery = ( - 'SELECT songid FROM music WHERE music.id = score_history.musicid AND game = :game ORDER BY version DESC LIMIT 1' - ) - chartquery = ( - 'SELECT chart FROM music WHERE music.id = score_history.musicid AND game = :game ORDER BY version DESC LIMIT 1' - ) + songidquery = "SELECT songid FROM music WHERE music.id = score_history.musicid AND game = :game ORDER BY version DESC LIMIT 1" + chartquery = "SELECT chart FROM music WHERE music.id = score_history.musicid AND game = :game ORDER BY version DESC LIMIT 1" # Now, construct the inner select statement so we can choose which scores we care about - innerselect = ( - 'SELECT DISTINCT(id) FROM music WHERE game = :game' - ) + innerselect = "SELECT DISTINCT(id) FROM music WHERE game = :game" if version is not None: - innerselect = innerselect + ' AND version = :version' + innerselect = innerselect + " AND version = :version" if songid is not None: - innerselect = innerselect + ' AND songid = :songid' + innerselect = innerselect + " AND songid = :songid" if songchart is not None: - innerselect = innerselect + ' AND chart = :songchart' + innerselect = innerselect + " AND chart = :songchart" # Finally, construct the full query sql = ( @@ -890,43 +914,46 @@ class MusicData(BaseData): # Now, limit the query if userid is not None: - sql = sql + ' AND userid = :userid' + sql = sql + " AND userid = :userid" if timelimit is not None: - sql = sql + ' AND timestamp >= :timestamp' - sql = sql + ' ORDER BY timestamp DESC' + sql = sql + " AND timestamp >= :timestamp" + sql = sql + " ORDER BY timestamp DESC" if limit is not None: - sql = sql + ' LIMIT :limit' + sql = sql + " LIMIT :limit" if offset is not None: - sql = sql + ' OFFSET :offset' + sql = sql + " OFFSET :offset" # Now, query itself - cursor = self.execute(sql, { - 'game': game.value, - 'version': version, - 'userid': userid, - 'songid': songid, - 'songchart': songchart, - 'timestamp': timelimit, - 'limit': limit, - 'offset': offset, - }) + cursor = self.execute( + sql, + { + "game": game.value, + "version": version, + "userid": userid, + "songid": songid, + "songchart": songchart, + "timestamp": timelimit, + "limit": limit, + "offset": offset, + }, + ) # Now objectify the attempts attempts = [] for result in cursor.fetchall(): attempts.append( ( - UserID(result['userid']) if result['userid'] > 0 else None, + UserID(result["userid"]) if result["userid"] > 0 else None, Attempt( - result['scorekey'], - result['songid'], - result['chart'], - result['points'], - result['timestamp'], - result['lid'], - True if result['new_record'] == 1 else False, - self.deserialize(result['data']), - ) + result["scorekey"], + result["songid"], + result["chart"], + result["points"], + result["timestamp"], + result["lid"], + True if result["new_record"] == 1 else False, + self.deserialize(result["data"]), + ), ) ) diff --git a/bemani/data/mysql/network.py b/bemani/data/mysql/network.py index 9f5c167..5c037ac 100644 --- a/bemani/data/mysql/network.py +++ b/bemani/data/mysql/network.py @@ -12,13 +12,13 @@ Table for storing network news, as edited by an admin. This is displayed on the front page of the frontend of the network. """ news = Table( - 'news', + "news", metadata, - Column('id', Integer, nullable=False, primary_key=True), - Column('timestamp', Integer, nullable=False, index=True), - Column('title', String(255), nullable=False), - Column('body', Text, nullable=False), - mysql_charset='utf8mb4', + Column("id", Integer, nullable=False, primary_key=True), + Column("timestamp", Integer, nullable=False, index=True), + Column("title", String(255), nullable=False), + Column("body", Text, nullable=False), + mysql_charset="utf8mb4", ) """ @@ -26,16 +26,18 @@ Table for storing scheduled work history, so that individual game code can determine if it should run scheduled work or not. """ scheduled_work = Table( - 'scheduled_work', + "scheduled_work", metadata, - Column('game', String(32), nullable=False), - Column('version', Integer, nullable=False), - Column('name', String(32), nullable=False), - Column('schedule', String(32), nullable=False), - Column('year', Integer), - Column('day', Integer), - UniqueConstraint('game', 'version', 'name', 'schedule', name='game_version_name_schedule'), - mysql_charset='utf8mb4', + Column("game", String(32), nullable=False), + Column("version", Integer, nullable=False), + Column("name", String(32), nullable=False), + Column("schedule", String(32), nullable=False), + Column("year", Integer), + Column("day", Integer), + UniqueConstraint( + "game", "version", "name", "schedule", name="game_version_name_schedule" + ), + mysql_charset="utf8mb4", ) """ @@ -44,20 +46,19 @@ song selection, etc. Anything that could be inspected later to verify correct operation of the network. """ audit = Table( - 'audit', + "audit", metadata, - Column('id', Integer, nullable=False, primary_key=True), - Column('timestamp', Integer, nullable=False, index=True), - Column('userid', BigInteger(unsigned=True), index=True), - Column('arcadeid', Integer, index=True), - Column('type', String(64), nullable=False, index=True), - Column('data', JSON, nullable=False), - mysql_charset='utf8mb4', + Column("id", Integer, nullable=False, primary_key=True), + Column("timestamp", Integer, nullable=False, index=True), + Column("userid", BigInteger(unsigned=True), index=True), + Column("arcadeid", Integer, index=True), + Column("type", String(64), nullable=False, index=True), + Column("data", JSON, nullable=False), + mysql_charset="utf8mb4", ) class NetworkData(BaseData): - def get_all_news(self) -> List[News]: """ Grab all news in the system. @@ -69,11 +70,12 @@ class NetworkData(BaseData): cursor = self.execute(sql) return [ News( - result['id'], - result['timestamp'], - result['title'], - result['body'], - ) for result in cursor.fetchall() + result["id"], + result["timestamp"], + result["title"], + result["body"], + ) + for result in cursor.fetchall() ] def create_news(self, title: str, body: str) -> int: @@ -88,7 +90,9 @@ class NetworkData(BaseData): The ID of the newly created entry. """ sql = "INSERT INTO news (timestamp, title, body) VALUES (:timestamp, :title, :body)" - cursor = self.execute(sql, {'timestamp': Time.now(), 'title': title, 'body': body}) + cursor = self.execute( + sql, {"timestamp": Time.now(), "title": title, "body": body} + ) return cursor.lastrowid def get_news(self, newsid: int) -> Optional[News]: @@ -102,7 +106,7 @@ class NetworkData(BaseData): A News object if the news entry was found or None otherwise. """ sql = "SELECT timestamp, title, body FROM news WHERE id = :id" - cursor = self.execute(sql, {'id': newsid}) + cursor = self.execute(sql, {"id": newsid}) if cursor.rowcount != 1: # Couldn't find an entry with this ID return None @@ -110,9 +114,9 @@ class NetworkData(BaseData): result = cursor.fetchone() return News( newsid, - result['timestamp'], - result['title'], - result['body'], + result["timestamp"], + result["title"], + result["body"], ) def put_news(self, news: News) -> None: @@ -123,7 +127,7 @@ class NetworkData(BaseData): news - A News object to be updated. """ sql = "UPDATE news SET title = :title, body = :body WHERE id = :id" - self.execute(sql, {'id': news.id, 'title': news.title, 'body': news.body}) + self.execute(sql, {"id": news.id, "title": news.title, "body": news.body}) def destroy_news(self, newsid: int) -> None: """ @@ -133,102 +137,120 @@ class NetworkData(BaseData): newsid - Integer specifying news ID. """ sql = "DELETE FROM news WHERE id = :id LIMIT 1" - self.execute(sql, {'id': newsid}) + self.execute(sql, {"id": newsid}) def get_schedule_duration(self, schedule: str) -> Tuple[int, int]: """ Given a schedule type, returns the timestamp for the start and end of the current schedule of this type. """ - if schedule not in ['daily', 'weekly']: - raise Exception('Logic error, specify either \'daily\' or \'weekly\' for schedule type!') + if schedule not in ["daily", "weekly"]: + raise Exception( + "Logic error, specify either 'daily' or 'weekly' for schedule type!" + ) - if schedule == 'daily': + if schedule == "daily": return (Time.beginning_of_today(), Time.end_of_today()) - if schedule == 'weekly': + if schedule == "weekly": return (Time.beginning_of_this_week(), Time.end_of_this_week()) # Should never happen return (0, 0) - def should_schedule(self, game: GameConstants, version: int, name: str, schedule: str) -> bool: + def should_schedule( + self, game: GameConstants, version: int, name: str, schedule: str + ) -> bool: """ Given a game/version/name pair and a schedule value, return whether this scheduled work is overdue or not. """ - if schedule not in ['daily', 'weekly']: - raise Exception('Logic error, specify either \'daily\' or \'weekly\' for schedule type!') + if schedule not in ["daily", "weekly"]: + raise Exception( + "Logic error, specify either 'daily' or 'weekly' for schedule type!" + ) sql = ( "SELECT year, day FROM scheduled_work " "WHERE game = :game AND version = :version AND " "name = :name AND schedule = :schedule" ) - cursor = self.execute(sql, {'game': game.value, 'version': version, 'name': name, 'schedule': schedule}) + cursor = self.execute( + sql, + { + "game": game.value, + "version": version, + "name": name, + "schedule": schedule, + }, + ) if cursor.rowcount != 1: # No scheduled work was registered, so time to get going! return True result = cursor.fetchone() - if schedule == 'daily': + if schedule == "daily": # Just look at the day and year, make sure it matches year, day = Time.days_into_year() - if year != result['year']: + if year != result["year"]: # Wrong year, so we certainly need to run! return True - if day != result['day']: + if day != result["day"]: # Wrong day and we're daily, so need to run! return True - if schedule == 'weekly': + if schedule == "weekly": # Find the beginning of the week (Monday), as days since epoch. - if Time.week_in_days_since_epoch() != result['day']: + if Time.week_in_days_since_epoch() != result["day"]: # Wrong week, so we should run! return True # We have already run this work for this schedule return False - def mark_scheduled(self, game: GameConstants, version: int, name: str, schedule: str) -> None: - if schedule not in ['daily', 'weekly']: - raise Exception('Logic error, specify either \'daily\' or \'weekly\' for schedule type!') + def mark_scheduled( + self, game: GameConstants, version: int, name: str, schedule: str + ) -> None: + if schedule not in ["daily", "weekly"]: + raise Exception( + "Logic error, specify either 'daily' or 'weekly' for schedule type!" + ) - if schedule == 'daily': + if schedule == "daily": year, day = Time.days_into_year() sql = ( - "INSERT INTO scheduled_work (game, version, name, schedule, year, day) " + - "VALUES (:game, :version, :name, :schedule, :year, :day) " + - "ON DUPLICATE KEY UPDATE year=VALUES(year), day=VALUES(day)" + "INSERT INTO scheduled_work (game, version, name, schedule, year, day) " + + "VALUES (:game, :version, :name, :schedule, :year, :day) " + + "ON DUPLICATE KEY UPDATE year=VALUES(year), day=VALUES(day)" ) self.execute( sql, { - 'game': game.value, - 'version': version, - 'name': name, - 'schedule': schedule, - 'year': year, - 'day': day, + "game": game.value, + "version": version, + "name": name, + "schedule": schedule, + "year": year, + "day": day, }, ) - if schedule == 'weekly': + if schedule == "weekly": days = Time.week_in_days_since_epoch() sql = ( - "INSERT INTO scheduled_work (game, version, name, schedule, day) " + - "VALUES (:game, :version, :name, :schedule, :day) " + - "ON DUPLICATE KEY UPDATE day=VALUES(day)" + "INSERT INTO scheduled_work (game, version, name, schedule, day) " + + "VALUES (:game, :version, :name, :schedule, :day) " + + "ON DUPLICATE KEY UPDATE day=VALUES(day)" ) self.execute( sql, { - 'game': game.value, - 'version': version, - 'name': name, - 'schedule': schedule, - 'day': days, + "game": game.value, + "version": version, + "name": name, + "schedule": schedule, + "day": days, }, ) @@ -236,23 +258,32 @@ class NetworkData(BaseData): self, event: str, data: Dict[str, Any], - timestamp: Optional[int]=None, - userid: Optional[UserID]=None, - arcadeid: Optional[ArcadeID]=None, + timestamp: Optional[int] = None, + userid: Optional[UserID] = None, + arcadeid: Optional[ArcadeID] = None, ) -> None: if timestamp is None: timestamp = Time.now() sql = "INSERT INTO audit (timestamp, userid, arcadeid, type, data) VALUES (:ts, :uid, :aid, :type, :data)" - self.execute(sql, {'ts': timestamp, 'type': event, 'data': self.serialize(data), 'uid': userid, 'aid': arcadeid}) + self.execute( + sql, + { + "ts": timestamp, + "type": event, + "data": self.serialize(data), + "uid": userid, + "aid": arcadeid, + }, + ) def get_events( self, - userid: Optional[UserID]=None, - arcadeid: Optional[ArcadeID]=None, - event: Optional[str]=None, - limit: Optional[int]=None, - since_id: Optional[int]=None, - until_id: Optional[int]=None, + userid: Optional[UserID] = None, + arcadeid: Optional[ArcadeID] = None, + event: Optional[str] = None, + limit: Optional[int] = None, + since_id: Optional[int] = None, + until_id: Optional[int] = None, ) -> List[Event]: # Base query sql = "SELECT id, timestamp, userid, arcadeid, type, data FROM audit " @@ -276,25 +307,35 @@ class NetworkData(BaseData): sql = sql + "ORDER BY id DESC" if limit is not None: sql = sql + " LIMIT :limit" - cursor = self.execute(sql, {'userid': userid, 'arcadeid': arcadeid, 'event': event, 'limit': limit, 'since_id': since_id, 'until_id': until_id}) + cursor = self.execute( + sql, + { + "userid": userid, + "arcadeid": arcadeid, + "event": event, + "limit": limit, + "since_id": since_id, + "until_id": until_id, + }, + ) events = [] for result in cursor.fetchall(): - if result['userid'] is not None: - userid = UserID(result['userid']) + if result["userid"] is not None: + userid = UserID(result["userid"]) else: userid = None - if result['arcadeid'] is not None: - arcadeid = ArcadeID(result['arcadeid']) + if result["arcadeid"] is not None: + arcadeid = ArcadeID(result["arcadeid"]) else: arcadeid = None events.append( Event( - result['id'], - result['timestamp'], + result["id"], + result["timestamp"], userid, arcadeid, - result['type'], - self.deserialize(result['data']), + result["type"], + self.deserialize(result["data"]), ), ) return events @@ -305,4 +346,4 @@ class NetworkData(BaseData): all events older than this timestamp. """ sql = "DELETE FROM audit WHERE timestamp < :ts" - self.execute(sql, {'ts': oldest_event_ts}) + self.execute(sql, {"ts": oldest_event_ts}) diff --git a/bemani/data/mysql/user.py b/bemani/data/mysql/user.py index 5500623..9f71c98 100644 --- a/bemani/data/mysql/user.py +++ b/bemani/data/mysql/user.py @@ -20,15 +20,15 @@ to use the network. However, an active user account is required before creating a web login. """ user = Table( - 'user', + "user", metadata, - Column('id', Integer, nullable=False, primary_key=True), - Column('pin', String(4), nullable=False), - Column('username', String(255), unique=True), - Column('password', String(255)), - Column('email', String(255)), - Column('admin', Integer), - mysql_charset='utf8mb4', + Column("id", Integer, nullable=False, primary_key=True), + Column("pin", String(4), nullable=False), + Column("username", String(255), unique=True), + Column("password", String(255)), + Column("email", String(255)), + Column("admin", Integer), + mysql_charset="utf8mb4", ) """ @@ -38,11 +38,11 @@ a new user will be created to associate with a card, but it can later be unlinked. """ card = Table( - 'card', + "card", metadata, - Column('id', String(16), nullable=False, unique=True), - Column('userid', BigInteger(unsigned=True), nullable=False, index=True), - mysql_charset='utf8mb4', + Column("id", String(16), nullable=False, unique=True), + Column("userid", BigInteger(unsigned=True), nullable=False, index=True), + mysql_charset="utf8mb4", ) """ @@ -50,13 +50,13 @@ Table representing an extid for a user across a game series. Each game series on the network gets its own extid (8 digit number) for each user. """ extid = Table( - 'extid', + "extid", metadata, - Column('game', String(32), nullable=False), - Column('extid', Integer, nullable=False, unique=True), - Column('userid', BigInteger(unsigned=True), nullable=False), - UniqueConstraint('game', 'userid', name='game_userid'), - mysql_charset='utf8mb4', + Column("game", String(32), nullable=False), + Column("extid", Integer, nullable=False, unique=True), + Column("userid", BigInteger(unsigned=True), nullable=False), + UniqueConstraint("game", "userid", name="game_userid"), + mysql_charset="utf8mb4", ) """ @@ -69,25 +69,25 @@ Note that a user might have an extid/refid for a game without a profile, but a user cannot have a profile without an extid/refid. """ refid = Table( - 'refid', + "refid", metadata, - Column('game', String(32), nullable=False), - Column('version', Integer, nullable=False), - Column('refid', String(16), nullable=False, unique=True), - Column('userid', BigInteger(unsigned=True), nullable=False), - UniqueConstraint('game', 'version', 'userid', name='game_version_userid'), - mysql_charset='utf8mb4', + Column("game", String(32), nullable=False), + Column("version", Integer, nullable=False), + Column("refid", String(16), nullable=False, unique=True), + Column("userid", BigInteger(unsigned=True), nullable=False), + UniqueConstraint("game", "version", "userid", name="game_version_userid"), + mysql_charset="utf8mb4", ) """ Table for storing JSON profile blobs, indexed by refid. """ profile = Table( - 'profile', + "profile", metadata, - Column('refid', String(16), nullable=False, unique=True), - Column('data', JSON, nullable=False), - mysql_charset='utf8mb4', + Column("refid", String(16), nullable=False, unique=True), + Column("data", JSON, nullable=False), + mysql_charset="utf8mb4", ) """ @@ -97,14 +97,14 @@ achievement. Examples would be tran medals, event unlocks, items earned, etc. """ achievement = Table( - 'achievement', + "achievement", metadata, - Column('refid', String(16), nullable=False), - Column('id', Integer, nullable=False), - Column('type', String(64), nullable=False), - Column('data', JSON, nullable=False), - UniqueConstraint('refid', 'id', 'type', name='refid_id_type'), - mysql_charset='utf8mb4', + Column("refid", String(16), nullable=False), + Column("id", Integer, nullable=False), + Column("type", String(64), nullable=False), + Column("data", JSON, nullable=False), + UniqueConstraint("refid", "id", "type", name="refid_id_type"), + mysql_charset="utf8mb4", ) """ @@ -116,15 +116,17 @@ the blob does not need to be equal across different instances of the same achievement for the same user. Examples would be calorie earnings for DDR. """ time_based_achievement = Table( - 'time_based_achievement', + "time_based_achievement", metadata, - Column('refid', String(16), nullable=False), - Column('id', Integer, nullable=False), - Column('type', String(64), nullable=False), - Column('timestamp', Integer, nullable=False, index=True), - Column('data', JSON, nullable=False), - UniqueConstraint('refid', 'id', 'type', 'timestamp', name='refid_id_type_timestamp'), - mysql_charset='utf8mb4', + Column("refid", String(16), nullable=False), + Column("id", Integer, nullable=False), + Column("type", String(64), nullable=False), + Column("timestamp", Integer, nullable=False, index=True), + Column("data", JSON, nullable=False), + UniqueConstraint( + "refid", "id", "type", "timestamp", name="refid_id_type_timestamp" + ), + mysql_charset="utf8mb4", ) """ @@ -132,13 +134,13 @@ Table for storing a user's PASELI balance, given an arcade. There is no global balance on this network. """ balance = Table( - 'balance', + "balance", metadata, - Column('userid', BigInteger(unsigned=True), nullable=False), - Column('arcadeid', Integer, nullable=False), - Column('balance', Integer, nullable=False), - UniqueConstraint('userid', 'arcadeid', name='userid_arcadeid'), - mysql_charset='utf8mb4', + Column("userid", BigInteger(unsigned=True), nullable=False), + Column("arcadeid", Integer, nullable=False), + Column("balance", Integer, nullable=False), + UniqueConstraint("userid", "arcadeid", name="userid_arcadeid"), + mysql_charset="utf8mb4", ) """ @@ -147,16 +149,23 @@ may be. Typically used for rivals. etc. """ link = Table( - 'link', + "link", metadata, - Column('game', String(32), nullable=False), - Column('version', Integer, nullable=False), - Column('userid', BigInteger(unsigned=True), nullable=False), - Column('type', String(64), nullable=False), - Column('other_userid', BigInteger(unsigned=True), nullable=False), - Column('data', JSON, nullable=False), - UniqueConstraint('game', 'version', 'userid', 'type', 'other_userid', name='game_version_userid_type_other_uuserid'), - mysql_charset='utf8mb4', + Column("game", String(32), nullable=False), + Column("version", Integer, nullable=False), + Column("userid", BigInteger(unsigned=True), nullable=False), + Column("type", String(64), nullable=False), + Column("other_userid", BigInteger(unsigned=True), nullable=False), + Column("data", JSON, nullable=False), + UniqueConstraint( + "game", + "version", + "userid", + "type", + "other_userid", + name="game_version_userid_type_other_uuserid", + ), + mysql_charset="utf8mb4", ) @@ -183,13 +192,13 @@ class UserData(BaseData): """ # First, look up the user account sql = "SELECT userid FROM card WHERE id = :id" - cursor = self.execute(sql, {'id': cardid}) + cursor = self.execute(sql, {"id": cardid}) if cursor.rowcount != 1: # Couldn't find a user with this card return None result = cursor.fetchone() - return UserID(result['userid']) + return UserID(result["userid"]) def from_username(self, username: str) -> Optional[UserID]: """ @@ -202,15 +211,17 @@ class UserData(BaseData): User ID as an integer if found, or None if not. """ sql = "SELECT id FROM user WHERE username = :username" - cursor = self.execute(sql, {'username': username}) + cursor = self.execute(sql, {"username": username}) if cursor.rowcount != 1: # Couldn't find this username return None result = cursor.fetchone() - return UserID(result['id']) + return UserID(result["id"]) - def from_refid(self, game: GameConstants, version: int, refid: str) -> Optional[UserID]: + def from_refid( + self, game: GameConstants, version: int, refid: str + ) -> Optional[UserID]: """ Given a generated RefID, look up a user ID. @@ -227,15 +238,19 @@ class UserData(BaseData): """ # First, look up the user account sql = "SELECT userid FROM refid WHERE game = :game AND version = :version AND refid = :refid" - cursor = self.execute(sql, {'game': game.value, 'version': version, 'refid': refid}) + cursor = self.execute( + sql, {"game": game.value, "version": version, "refid": refid} + ) if cursor.rowcount != 1: # Couldn't find a user with this refid return None result = cursor.fetchone() - return UserID(result['userid']) + return UserID(result["userid"]) - def from_extid(self, game: GameConstants, version: int, extid: int) -> Optional[UserID]: + def from_extid( + self, game: GameConstants, version: int, extid: int + ) -> Optional[UserID]: """ Given a generated ExtID, look up a user ID. @@ -252,13 +267,13 @@ class UserData(BaseData): """ # First, look up the user account sql = "SELECT userid FROM extid WHERE game = :game AND extid = :extid" - cursor = self.execute(sql, {'game': game.value, 'extid': extid}) + cursor = self.execute(sql, {"game": game.value, "extid": extid}) if cursor.rowcount != 1: # Couldn't find a user with this refid return None result = cursor.fetchone() - return UserID(result['userid']) + return UserID(result["userid"]) def from_session(self, session: str) -> Optional[UserID]: """ @@ -270,7 +285,7 @@ class UserData(BaseData): Returns: User ID as an integer if found, or None if the session is expired or doesn't exist. """ - userid = self._from_session(session, 'userid') + userid = self._from_session(session, "userid") if userid is None: return None return UserID(userid) @@ -286,13 +301,13 @@ class UserData(BaseData): A User object if found, or None otherwise. """ sql = "SELECT username, email, admin FROM user WHERE id = :userid" - cursor = self.execute(sql, {'userid': userid}) + cursor = self.execute(sql, {"userid": userid}) if cursor.rowcount != 1: # User doesn't exist, but we have a reference? return None result = cursor.fetchone() - return User(userid, result['username'], result['email'], result['admin'] == 1) + return User(userid, result["username"], result["email"], result["admin"] == 1) def get_all_users(self) -> List[User]: """ @@ -304,7 +319,12 @@ class UserData(BaseData): sql = "SELECT id, username, email, admin FROM user" cursor = self.execute(sql) return [ - User(UserID(result['id']), result['username'], result['email'], result['admin'] == 1) + User( + UserID(result["id"]), + result["username"], + result["email"], + result["admin"] == 1, + ) for result in cursor.fetchall() ] @@ -320,7 +340,7 @@ class UserData(BaseData): """ sql = "SELECT username FROM user WHERE username is not null" cursor = self.execute(sql) - return [res['username'] for res in cursor.fetchall()] + return [res["username"] for res in cursor.fetchall()] def get_all_cards(self) -> List[Tuple[str, UserID]]: """ @@ -331,7 +351,9 @@ class UserData(BaseData): """ sql = "SELECT id, userid FROM card" cursor = self.execute(sql) - return [(str(res['id']).upper(), UserID(res['userid'])) for res in cursor.fetchall()] + return [ + (str(res["id"]).upper(), UserID(res["userid"])) for res in cursor.fetchall() + ] def get_cards(self, userid: UserID) -> List[str]: """ @@ -344,8 +366,8 @@ class UserData(BaseData): A list of strings representing card IDs. """ sql = "SELECT id FROM card WHERE userid = :userid" - cursor = self.execute(sql, {'userid': userid}) - return [str(res['id']).upper() for res in cursor.fetchall()] + cursor = self.execute(sql, {"userid": userid}) + return [str(res["id"]).upper() for res in cursor.fetchall()] def add_card(self, userid: UserID, cardid: str) -> None: """ @@ -359,21 +381,21 @@ class UserData(BaseData): cardid - 16-digit card ID to add. """ sql = "INSERT INTO card (userid, id) VALUES (:userid, :cardid)" - self.execute(sql, {'userid': userid, 'cardid': cardid}) + self.execute(sql, {"userid": userid, "cardid": cardid}) oldid = RemoteUser.card_to_userid(cardid) if RemoteUser.is_remote(oldid): # Kill any refid/extid that related to this card, since its now associated # with another existing account. sql = "DELETE FROM extid WHERE userid = :oldid" - self.execute(sql, {'oldid': oldid}) + self.execute(sql, {"oldid": oldid}) sql = "DELETE FROM refid WHERE userid = :oldid" - self.execute(sql, {'oldid': oldid}) + self.execute(sql, {"oldid": oldid}) # Point at the new account for any rivals against this card. Note that this # might result in a duplicate rival, but its a very small edge case. sql = "UPDATE link SET other_userid = :newid WHERE other_userid = :oldid" - self.execute(sql, {'newid': userid, 'oldid': oldid}) + self.execute(sql, {"newid": userid, "oldid": oldid}) def destroy_card(self, userid: UserID, cardid: str) -> None: """ @@ -387,7 +409,7 @@ class UserData(BaseData): cardid - 16-digit card ID to remove. """ sql = "DELETE FROM card WHERE id = :cardid AND userid = :userid LIMIT 1" - self.execute(sql, {'cardid': cardid, 'userid': userid}) + self.execute(sql, {"cardid": cardid, "userid": userid}) def put_user(self, user: User) -> None: """ @@ -400,10 +422,10 @@ class UserData(BaseData): self.execute( sql, { - 'username': user.username, - 'email': user.email, - 'admin': 1 if user.admin else 0, - 'userid': user.id, + "username": user.username, + "email": user.email, + "admin": 1 if user.admin else 0, + "userid": user.id, }, ) @@ -419,13 +441,13 @@ class UserData(BaseData): True if PIN is valid, False otherwise. """ sql = "SELECT pin FROM user WHERE id = :userid" - cursor = self.execute(sql, {'userid': userid}) + cursor = self.execute(sql, {"userid": userid}) if cursor.rowcount != 1: # User doesn't exist, but we have a reference? return False result = cursor.fetchone() - return pin == result['pin'] + return pin == result["pin"] def update_pin(self, userid: UserID, pin: str) -> None: """ @@ -436,7 +458,7 @@ class UserData(BaseData): pin - 4 digit string returned by the game for PIN entry. """ sql = "UPDATE user SET pin = :pin WHERE id = :userid" - self.execute(sql, {'pin': pin, 'userid': userid}) + self.execute(sql, {"pin": pin, "userid": userid}) def validate_password(self, userid: UserID, password: str) -> bool: """ @@ -450,13 +472,13 @@ class UserData(BaseData): True if password is valid, False otherwise. """ sql = "SELECT password FROM user WHERE id = :userid" - cursor = self.execute(sql, {'userid': userid}) + cursor = self.execute(sql, {"userid": userid}) if cursor.rowcount != 1: # User doesn't exist, but we have a reference? return False result = cursor.fetchone() - passhash = result['password'] + passhash = result["password"] try: # Verifying the password @@ -474,9 +496,11 @@ class UserData(BaseData): """ passhash = pbkdf2_sha512.hash(password) sql = "UPDATE user SET password = :hash WHERE id = :userid" - self.execute(sql, {'hash': passhash, 'userid': userid}) + self.execute(sql, {"hash": passhash, "userid": userid}) - def get_profile(self, game: GameConstants, version: int, userid: UserID) -> Optional[Profile]: + def get_profile( + self, game: GameConstants, version: int, userid: UserID + ) -> Optional[Profile]: """ Given a game/version/userid, look up the associated profile. @@ -489,12 +513,14 @@ class UserData(BaseData): A dictionary previously stored by a game class if found, or None otherwise. """ sql = ( - "SELECT refid.refid AS refid, extid.extid AS extid, profile.data AS data " + - "FROM refid, extid, profile " + - "WHERE refid.userid = :userid AND refid.game = :game AND refid.version = :version AND " + "SELECT refid.refid AS refid, extid.extid AS extid, profile.data AS data " + + "FROM refid, extid, profile " + + "WHERE refid.userid = :userid AND refid.game = :game AND refid.version = :version AND " "extid.userid = refid.userid AND extid.game = refid.game AND profile.refid = refid.refid" ) - cursor = self.execute(sql, {'userid': userid, 'game': game.value, 'version': version}) + cursor = self.execute( + sql, {"userid": userid, "game": game.value, "version": version} + ) if cursor.rowcount != 1: # Profile doesn't exist return None @@ -503,12 +529,14 @@ class UserData(BaseData): return Profile( game, version, - result['refid'], - result['extid'], - self.deserialize(result['data']), + result["refid"], + result["extid"], + self.deserialize(result["data"]), ) - def get_any_profile(self, game: GameConstants, version: int, userid: UserID) -> Optional[Profile]: + def get_any_profile( + self, game: GameConstants, version: int, userid: UserID + ) -> Optional[Profile]: """ Given a game/version/userid, look up the associated profile. If the profile for that version doesn't exist, try another profile, failing only if there is no profile for any version of @@ -532,7 +560,9 @@ class UserData(BaseData): else: return None - def get_any_profiles(self, game: GameConstants, version: int, userids: List[UserID]) -> List[Tuple[UserID, Optional[Profile]]]: + def get_any_profiles( + self, game: GameConstants, version: int, userids: List[UserID] + ) -> List[Tuple[UserID, Optional[Profile]]]: """ Does the exact same thing as get_any_profile but across a list of users instead of one. Provided purely as a convenience function. @@ -549,12 +579,12 @@ class UserData(BaseData): if not userids: return [] sql = "SELECT version, userid FROM refid WHERE game = :game AND userid IN :userids AND refid IN (SELECT refid FROM profile)" - cursor = self.execute(sql, {'game': game.value, 'userids': userids}) + cursor = self.execute(sql, {"game": game.value, "userids": userids}) profilever: Dict[UserID, int] = {} for result in cursor.fetchall(): - tuid = UserID(result['userid']) - tver = result['version'] + tuid = UserID(result["userid"]) + tver = result["version"] if tuid not in profilever: # Just assign it the first profile we find @@ -577,7 +607,9 @@ class UserData(BaseData): result.append((uid, self.get_profile(game, profilever[uid], uid))) return result - def get_games_played(self, userid: UserID, game: Optional[GameConstants] = None) -> List[Tuple[GameConstants, int]]: + def get_games_played( + self, userid: UserID, game: Optional[GameConstants] = None + ) -> List[Tuple[GameConstants, int]]: """ Given a user ID, look up all game/version combos this user has played. @@ -589,19 +621,21 @@ class UserData(BaseData): A List of Tuples of game, version for each game/version the user has played. """ sql = "SELECT game, version FROM refid WHERE userid = :userid AND refid IN (SELECT refid FROM profile)" - vals: Dict[str, Any] = {'userid': userid} + vals: Dict[str, Any] = {"userid": userid} if game is not None: sql += " AND game = :game" - vals['game'] = game.value + vals["game"] = game.value cursor = self.execute(sql, vals) profiles = [] for result in cursor.fetchall(): - profiles.append((GameConstants(result['game']), result['version'])) + profiles.append((GameConstants(result["game"]), result["version"])) return profiles - def get_all_profiles(self, game: GameConstants, version: int) -> List[Tuple[UserID, Profile]]: + def get_all_profiles( + self, game: GameConstants, version: int + ) -> List[Tuple[UserID, Profile]]: """ Given a game/version, look up all user profiles for that game. @@ -618,20 +652,20 @@ class UserData(BaseData): "WHERE refid.game = :game AND refid.version = :version " "AND refid.refid = profile.refid AND extid.game = refid.game AND extid.userid = refid.userid" ) - cursor = self.execute(sql, {'game': game.value, 'version': version}) + cursor = self.execute(sql, {"game": game.value, "version": version}) profiles = [] for result in cursor.fetchall(): profiles.append( ( - UserID(result['userid']), + UserID(result["userid"]), Profile( game, version, - result['refid'], - result['extid'], - self.deserialize(result['data']), - ) + result["refid"], + result["extid"], + self.deserialize(result["data"]), + ), ) ) @@ -652,11 +686,17 @@ class UserData(BaseData): "SELECT refid.userid AS userid FROM refid " "WHERE refid.game = :game AND refid.version = :version" ) - cursor = self.execute(sql, {'game': game.value, 'version': version}) + cursor = self.execute(sql, {"game": game.value, "version": version}) - return [UserID(result['userid']) for result in cursor.fetchall()] + return [UserID(result["userid"]) for result in cursor.fetchall()] - def get_all_achievements(self, game: GameConstants, version: int, achievementid: Optional[int] = None, achievementtype: Optional[str] = None) -> List[Tuple[UserID, Achievement]]: + def get_all_achievements( + self, + game: GameConstants, + version: int, + achievementid: Optional[int] = None, + achievementtype: Optional[str] = None, + ) -> List[Tuple[UserID, Achievement]]: """ Given a game/version, find all achievements for all players. @@ -672,32 +712,34 @@ class UserData(BaseData): "refid.userid AS userid FROM achievement, refid WHERE refid.game = :game AND " "refid.version = :version AND refid.refid = achievement.refid" ) - params: Dict[str, Any] = {'game': game.value, 'version': version} + params: Dict[str, Any] = {"game": game.value, "version": version} if achievementtype is not None: sql += " AND achievement.type = :type" - params['type'] = achievementtype + params["type"] = achievementtype if achievementid is not None: sql += " AND achievement.id = :id" - params['id'] = achievementid + params["id"] = achievementid cursor = self.execute(sql, params) achievements = [] for result in cursor.fetchall(): achievements.append( ( - UserID(result['userid']), + UserID(result["userid"]), Achievement( - result['id'], - result['type'], + result["id"], + result["type"], None, - self.deserialize(result['data']), + self.deserialize(result["data"]), ), ) ) return achievements - def put_profile(self, game: GameConstants, version: int, userid: UserID, profile: Profile) -> None: + def put_profile( + self, game: GameConstants, version: int, userid: UserID, profile: Profile + ) -> None: """ Given a game/version/userid, save an associated profile. @@ -711,11 +753,11 @@ class UserData(BaseData): # Add profile json to game profile sql = ( - "INSERT INTO profile (refid, data) " + - "VALUES (:refid, :json) " + - "ON DUPLICATE KEY UPDATE data=VALUES(data)" + "INSERT INTO profile (refid, data) " + + "VALUES (:refid, :json) " + + "ON DUPLICATE KEY UPDATE data=VALUES(data)" ) - self.execute(sql, {'refid': refid, 'json': self.serialize(profile)}) + self.execute(sql, {"refid": refid, "json": self.serialize(profile)}) # Update profile details just in case this was a new profile that was just saved. profile.game = game @@ -737,9 +779,16 @@ class UserData(BaseData): # Delete profile JSON to unlink the profile for this game/version. sql = "DELETE FROM profile WHERE refid = :refid LIMIT 1" - self.execute(sql, {'refid': refid}) + self.execute(sql, {"refid": refid}) - def get_achievement(self, game: GameConstants, version: int, userid: UserID, achievementid: int, achievementtype: str) -> Optional[ValidatedDict]: + def get_achievement( + self, + game: GameConstants, + version: int, + userid: UserID, + achievementid: int, + achievementtype: str, + ) -> Optional[ValidatedDict]: """ Given a game/version/userid and achievement id/type, find that achievement. @@ -757,18 +806,20 @@ class UserData(BaseData): A dictionary as stored by a game class previously, or None if not found. """ refid = self.get_refid(game, version, userid) - sql = ( - "SELECT data FROM achievement WHERE refid = :refid AND id = :id AND type = :type" + sql = "SELECT data FROM achievement WHERE refid = :refid AND id = :id AND type = :type" + cursor = self.execute( + sql, {"refid": refid, "id": achievementid, "type": achievementtype} ) - cursor = self.execute(sql, {'refid': refid, 'id': achievementid, 'type': achievementtype}) if cursor.rowcount != 1: # score doesn't exist return None result = cursor.fetchone() - return ValidatedDict(self.deserialize(result['data'])) + return ValidatedDict(self.deserialize(result["data"])) - def get_achievements(self, game: GameConstants, version: int, userid: UserID) -> List[Achievement]: + def get_achievements( + self, game: GameConstants, version: int, userid: UserID + ) -> List[Achievement]: """ Given a game/version/userid, find all achievements @@ -782,22 +833,30 @@ class UserData(BaseData): """ refid = self.get_refid(game, version, userid) sql = "SELECT id, type, data FROM achievement WHERE refid = :refid" - cursor = self.execute(sql, {'refid': refid}) + cursor = self.execute(sql, {"refid": refid}) achievements = [] for result in cursor.fetchall(): achievements.append( Achievement( - result['id'], - result['type'], + result["id"], + result["type"], None, - self.deserialize(result['data']), + self.deserialize(result["data"]), ) ) return achievements - def put_achievement(self, game: GameConstants, version: int, userid: UserID, achievementid: int, achievementtype: str, data: Dict[str, Any]) -> None: + def put_achievement( + self, + game: GameConstants, + version: int, + userid: UserID, + achievementid: int, + achievementtype: str, + data: Dict[str, Any], + ) -> None: """ Given a game/version/userid and achievement id/type, save an achievement. @@ -813,13 +872,28 @@ class UserData(BaseData): # Add achievement JSON to achievements sql = ( - "INSERT INTO achievement (refid, id, type, data) " + - "VALUES (:refid, :id, :type, :data) " + - "ON DUPLICATE KEY UPDATE data=VALUES(data)" + "INSERT INTO achievement (refid, id, type, data) " + + "VALUES (:refid, :id, :type, :data) " + + "ON DUPLICATE KEY UPDATE data=VALUES(data)" + ) + self.execute( + sql, + { + "refid": refid, + "id": achievementid, + "type": achievementtype, + "data": self.serialize(data), + }, ) - self.execute(sql, {'refid': refid, 'id': achievementid, 'type': achievementtype, 'data': self.serialize(data)}) - def destroy_achievement(self, game: GameConstants, version: int, userid: UserID, achievementid: int, achievementtype: str) -> None: + def destroy_achievement( + self, + game: GameConstants, + version: int, + userid: UserID, + achievementid: int, + achievementtype: str, + ) -> None: """ Given a game/version/userid and achievement id/type, delete an achievement. @@ -836,16 +910,18 @@ class UserData(BaseData): sql = ( "DELETE FROM achievement WHERE refid = :refid AND id = :id AND type = :type" ) - self.execute(sql, {'refid': refid, 'id': achievementid, 'type': achievementtype}) + self.execute( + sql, {"refid": refid, "id": achievementid, "type": achievementtype} + ) def get_time_based_achievements( self, game: GameConstants, version: int, userid: UserID, - achievementtype: Optional[str]=None, - since: Optional[int]=None, - until: Optional[int]=None, + achievementtype: Optional[str] = None, + since: Optional[int] = None, + until: Optional[int] = None, ) -> List[Achievement]: """ Given a game/version/userid, find all time-based achievements @@ -869,16 +945,19 @@ class UserData(BaseData): sql += " AND timestamp >= :since" if until is not None: sql += " AND timestamp < :until" - cursor = self.execute(sql, {'refid': refid, 'type': achievementtype, 'since': since, 'until': until}) + cursor = self.execute( + sql, + {"refid": refid, "type": achievementtype, "since": since, "until": until}, + ) achievements = [] for result in cursor.fetchall(): achievements.append( Achievement( - result['id'], - result['type'], - result['timestamp'], - self.deserialize(result['data']), + result["id"], + result["type"], + result["timestamp"], + self.deserialize(result["data"]), ) ) @@ -909,12 +988,23 @@ class UserData(BaseData): # Add achievement JSON to achievements sql = ( - "INSERT INTO time_based_achievement (refid, id, type, timestamp, data) " + - "VALUES (:refid, :id, :type, :ts, :data)" + "INSERT INTO time_based_achievement (refid, id, type, timestamp, data) " + + "VALUES (:refid, :id, :type, :ts, :data)" + ) + self.execute( + sql, + { + "refid": refid, + "id": achievementid, + "type": achievementtype, + "ts": Time.now(), + "data": self.serialize(data), + }, ) - self.execute(sql, {'refid': refid, 'id': achievementid, 'type': achievementtype, 'ts': Time.now(), 'data': self.serialize(data)}) - def get_all_time_based_achievements(self, game: GameConstants, version: int) -> List[Tuple[UserID, Achievement]]: + def get_all_time_based_achievements( + self, game: GameConstants, version: int + ) -> List[Tuple[UserID, Achievement]]: """ Given a game/version, find all time-based achievements for all players. @@ -931,25 +1021,32 @@ class UserData(BaseData): "refid.userid AS userid FROM time_based_achievement, refid WHERE refid.game = :game AND " "refid.version = :version AND refid.refid = time_based_achievement.refid" ) - cursor = self.execute(sql, {'game': game.value, 'version': version}) + cursor = self.execute(sql, {"game": game.value, "version": version}) achievements = [] for result in cursor.fetchall(): achievements.append( ( - UserID(result['userid']), + UserID(result["userid"]), Achievement( - result['id'], - result['type'], - result['timestamp'], - self.deserialize(result['data']), + result["id"], + result["type"], + result["timestamp"], + self.deserialize(result["data"]), ), ) ) return achievements - def get_link(self, game: GameConstants, version: int, userid: UserID, linktype: str, other_userid: UserID) -> Optional[ValidatedDict]: + def get_link( + self, + game: GameConstants, + version: int, + userid: UserID, + linktype: str, + other_userid: UserID, + ) -> Optional[ValidatedDict]: """ Given a game/version/userid and link type + other userid, find that link. @@ -966,18 +1063,27 @@ class UserData(BaseData): Returns: A dictionary as stored by a game class previously, or None if not found. """ - sql = ( - "SELECT data FROM link WHERE game = :game AND version = :version AND userid = :userid AND type = :type AND other_userid = :other_userid" + sql = "SELECT data FROM link WHERE game = :game AND version = :version AND userid = :userid AND type = :type AND other_userid = :other_userid" + cursor = self.execute( + sql, + { + "game": game.value, + "version": version, + "userid": userid, + "type": linktype, + "other_userid": other_userid, + }, ) - cursor = self.execute(sql, {'game': game.value, 'version': version, 'userid': userid, 'type': linktype, 'other_userid': other_userid}) if cursor.rowcount != 1: # score doesn't exist return None result = cursor.fetchone() - return ValidatedDict(self.deserialize(result['data'])) + return ValidatedDict(self.deserialize(result["data"])) - def get_links(self, game: GameConstants, version: int, userid: UserID) -> List[Link]: + def get_links( + self, game: GameConstants, version: int, userid: UserID + ) -> List[Link]: """ Given a game/version/userid, find all links between this user and other users @@ -990,22 +1096,32 @@ class UserData(BaseData): A list of Link objects. """ sql = "SELECT type, other_userid, data FROM link WHERE game = :game AND version = :version AND userid = :userid" - cursor = self.execute(sql, {'game': game.value, 'version': version, 'userid': userid}) + cursor = self.execute( + sql, {"game": game.value, "version": version, "userid": userid} + ) links = [] for result in cursor.fetchall(): links.append( Link( userid, - result['type'], - UserID(result['other_userid']), - self.deserialize(result['data']), + result["type"], + UserID(result["other_userid"]), + self.deserialize(result["data"]), ) ) return links - def put_link(self, game: GameConstants, version: int, userid: UserID, linktype: str, other_userid: UserID, data: Dict[str, Any]) -> None: + def put_link( + self, + game: GameConstants, + version: int, + userid: UserID, + linktype: str, + other_userid: UserID, + data: Dict[str, Any], + ) -> None: """ Given a game/version/userid and link id + other_userid, save an link. @@ -1023,9 +1139,26 @@ class UserData(BaseData): "VALUES (:game, :version, :userid, :type, :other_userid, :data) " "ON DUPLICATE KEY UPDATE data=VALUES(data)" ) - self.execute(sql, {'game': game.value, 'version': version, 'userid': userid, 'type': linktype, 'other_userid': other_userid, 'data': self.serialize(data)}) + self.execute( + sql, + { + "game": game.value, + "version": version, + "userid": userid, + "type": linktype, + "other_userid": other_userid, + "data": self.serialize(data), + }, + ) - def destroy_link(self, game: GameConstants, version: int, userid: UserID, linktype: str, other_userid: UserID) -> None: + def destroy_link( + self, + game: GameConstants, + version: int, + userid: UserID, + linktype: str, + other_userid: UserID, + ) -> None: """ Given a game/version/userid and link id + other_userid, destroy the link. @@ -1036,10 +1169,17 @@ class UserData(BaseData): linktype - The type of link. other_userid - Integer user ID of the account we're linked to. """ - sql = ( - "DELETE FROM link WHERE game = :game AND version = :version AND userid = :userid AND type = :type AND other_userid = :other_userid" + sql = "DELETE FROM link WHERE game = :game AND version = :version AND userid = :userid AND type = :type AND other_userid = :other_userid" + self.execute( + sql, + { + "game": game.value, + "version": version, + "userid": userid, + "type": linktype, + "other_userid": other_userid, + }, ) - self.execute(sql, {'game': game.value, 'version': version, 'userid': userid, 'type': linktype, 'other_userid': other_userid}) def get_balance(self, userid: UserID, arcadeid: ArcadeID) -> int: """ @@ -1053,14 +1193,16 @@ class UserData(BaseData): The PASELI balance for this user at this arcade. """ sql = "SELECT balance FROM balance WHERE userid = :userid AND arcadeid = :arcadeid" - cursor = self.execute(sql, {'userid': userid, 'arcadeid': arcadeid}) + cursor = self.execute(sql, {"userid": userid, "arcadeid": arcadeid}) if cursor.rowcount == 1: result = cursor.fetchone() - return result['balance'] + return result["balance"] else: return 0 - def update_balance(self, userid: UserID, arcadeid: ArcadeID, delta: int) -> Optional[int]: + def update_balance( + self, userid: UserID, arcadeid: ArcadeID, delta: int + ) -> Optional[int]: """ Given a user and an arcade ID, update the PASELI balance for that arcade. @@ -1076,12 +1218,12 @@ class UserData(BaseData): "INSERT INTO balance (userid, arcadeid, balance) VALUES (:userid, :arcadeid, :delta) " "ON DUPLICATE KEY UPDATE balance = balance + :delta" ) - self.execute(sql, {'delta': delta, 'userid': userid, 'arcadeid': arcadeid}) + self.execute(sql, {"delta": delta, "userid": userid, "arcadeid": arcadeid}) newbalance = self.get_balance(userid, arcadeid) if newbalance < 0: # Went under while grabbing, put the balance back and return nothing sql = "UPDATE balance SET balance = balance - :delta WHERE userid = :userid AND arcadeid = :arcadeid" - self.execute(sql, {'delta': delta, 'userid': userid, 'arcadeid': arcadeid}) + self.execute(sql, {"delta": delta, "userid": userid, "arcadeid": arcadeid}) return None return newbalance @@ -1099,10 +1241,12 @@ class UserData(BaseData): and returns it, which can be used for creating/looking up a profile in the future. """ sql = "SELECT refid FROM refid WHERE userid = :userid AND game = :game AND version = :version" - cursor = self.execute(sql, {'userid': userid, 'game': game.value, 'version': version}) + cursor = self.execute( + sql, {"userid": userid, "game": game.value, "version": version} + ) if cursor.rowcount == 1: result = cursor.fetchone() - return result['refid'] + return result["refid"] else: return self.create_refid(game, version, userid) @@ -1122,10 +1266,10 @@ class UserData(BaseData): def fetch_extid() -> Optional[int]: sql = "SELECT extid FROM extid WHERE userid = :userid AND game = :game" - cursor = self.execute(sql, {'userid': userid, 'game': game.value}) + cursor = self.execute(sql, {"userid": userid, "game": game.value}) if cursor.rowcount == 1: result = cursor.fetchone() - return result['extid'] + return result["extid"] else: return None @@ -1140,7 +1284,7 @@ class UserData(BaseData): else: raise AccountCreationException() - def create_session(self, userid: UserID, expiration: int=(30 * 86400)) -> str: + def create_session(self, userid: UserID, expiration: int = (30 * 86400)) -> str: """ Given a user ID, create a session string. @@ -1151,7 +1295,7 @@ class UserData(BaseData): Returns: A string that can be used as a session ID. """ - return self._create_session(userid, 'userid', expiration) + return self._create_session(userid, "userid", expiration) def destroy_session(self, session: str) -> None: """ @@ -1160,7 +1304,7 @@ class UserData(BaseData): Parameters: session - A session string as returned from create_session. """ - self._destroy_session(session, 'userid') + self._destroy_session(session, "userid") def create_refid(self, game: GameConstants, version: int, userid: UserID) -> str: """ @@ -1183,36 +1327,48 @@ class UserData(BaseData): while True: extid = random.randint(0, 89999999) + 10000000 sql = "SELECT extid FROM extid WHERE extid = :extid" - cursor = self.execute(sql, {'extid': extid}) + cursor = self.execute(sql, {"extid": extid}) if cursor.rowcount == 0: break # Use that extid sql = ( - "INSERT INTO extid (game, extid, userid) " + - "VALUES (:game, :extid, :userid)" + "INSERT INTO extid (game, extid, userid) " + + "VALUES (:game, :extid, :userid)" ) try: - cursor = self.execute(sql, {'game': game.value, 'extid': extid, 'userid': userid}) + cursor = self.execute( + sql, {"game": game.value, "extid": extid, "userid": userid} + ) except IntegrityError: # User already has an ExtID for this game series pass # Create a new refid that is unique while True: - refid = ''.join(random.choice('0123456789ABCDEF') for _ in range(UserData.REF_ID_LENGTH)) + refid = "".join( + random.choice("0123456789ABCDEF") for _ in range(UserData.REF_ID_LENGTH) + ) sql = "SELECT refid FROM refid WHERE refid = :refid" - cursor = self.execute(sql, {'refid': refid}) + cursor = self.execute(sql, {"refid": refid}) if cursor.rowcount == 0: break # Use that refid sql = ( - "INSERT INTO refid (game, version, refid, userid) " + - "VALUES (:game, :version, :refid, :userid)" + "INSERT INTO refid (game, version, refid, userid) " + + "VALUES (:game, :version, :refid, :userid)" ) try: - cursor = self.execute(sql, {'game': game.value, 'version': version, 'refid': refid, 'userid': userid}) + cursor = self.execute( + sql, + { + "game": game.value, + "version": version, + "refid": refid, + "userid": userid, + }, + ) if cursor.rowcount != 1: raise AccountCreationException() return refid @@ -1220,10 +1376,12 @@ class UserData(BaseData): # We maybe lost the race? Look up the ID from another creation. Don't call get_refid # because it calls us, so we don't want an infinite loop. sql = "SELECT refid FROM refid WHERE userid = :userid AND game = :game AND version = :version" - cursor = self.execute(sql, {'userid': userid, 'game': game.value, 'version': version}) + cursor = self.execute( + sql, {"userid": userid, "game": game.value, "version": version} + ) if cursor.rowcount == 1: result = cursor.fetchone() - return result['refid'] + return result["refid"] # Shouldn't be possible, but here we are raise AccountCreationException() @@ -1240,14 +1398,14 @@ class UserData(BaseData): """ # First, create a user account sql = "INSERT INTO user (pin, admin) VALUES (:pin, 0)" - cursor = self.execute(sql, {'pin': pin}) + cursor = self.execute(sql, {"pin": pin}) if cursor.rowcount != 1: return None userid = cursor.lastrowid # Now, insert the card, tying it to the account sql = "INSERT INTO card (id, userid) VALUES (:cardid, :userid)" - cursor = self.execute(sql, {'cardid': cardid, 'userid': userid}) + cursor = self.execute(sql, {"cardid": cardid, "userid": userid}) if cursor.rowcount != 1: return None @@ -1258,11 +1416,11 @@ class UserData(BaseData): oldid = RemoteUser.card_to_userid(cardid) if RemoteUser.is_remote(oldid): sql = "UPDATE extid SET userid = :newid WHERE userid = :oldid" - self.execute(sql, {'newid': userid, 'oldid': oldid}) + self.execute(sql, {"newid": userid, "oldid": oldid}) sql = "UPDATE refid SET userid = :newid WHERE userid = :oldid" - self.execute(sql, {'newid': userid, 'oldid': oldid}) + self.execute(sql, {"newid": userid, "oldid": oldid}) sql = "UPDATE link SET other_userid = :newid WHERE other_userid = :oldid" - self.execute(sql, {'newid': userid, 'oldid': oldid}) + self.execute(sql, {"newid": userid, "oldid": oldid}) # Finally, return the user ID return userid diff --git a/bemani/data/remoteuser.py b/bemani/data/remoteuser.py index db81090..510d284 100644 --- a/bemani/data/remoteuser.py +++ b/bemani/data/remoteuser.py @@ -18,9 +18,9 @@ class RemoteUser: def userid_to_card(userid: UserID) -> str: cardid = hex(abs(userid))[2:].upper() if len(cardid) <= 8: - raise Exception('Got invalid card back when converting from UserID!') + raise Exception("Got invalid card back when converting from UserID!") if len(cardid) < 16: - cardid = ('0' * (16 - len(cardid))) + cardid + cardid = ("0" * (16 - len(cardid))) + cardid return cardid @staticmethod diff --git a/bemani/data/triggers.py b/bemani/data/triggers.py index 08041ea..ba22e89 100644 --- a/bemani/data/triggers.py +++ b/bemani/data/triggers.py @@ -11,22 +11,23 @@ class Triggers: """ Class for broadcasting data to some outside service """ + def __init__(self, config: Config) -> None: self.config = config def __gameconst_to_series(self, game: GameConstants) -> str: return { - GameConstants.BISHI_BASHI: 'Bishi Bashi', - GameConstants.DANCE_EVOLUTION: 'Dance Evolution', - GameConstants.DDR: 'Dance Dance Revolution', - GameConstants.IIDX: 'Beatmania IIDX', - GameConstants.JUBEAT: 'Jubeat', - GameConstants.MGA: 'Metal Gear Arcade', - GameConstants.MUSECA: 'MÚSECA', - GameConstants.POPN_MUSIC: 'Pop\'n Music', - GameConstants.REFLEC_BEAT: 'Reflec Beat', - GameConstants.SDVX: 'Sound Voltex', - }.get(game, 'Unknown') + GameConstants.BISHI_BASHI: "Bishi Bashi", + GameConstants.DANCE_EVOLUTION: "Dance Evolution", + GameConstants.DDR: "Dance Dance Revolution", + GameConstants.IIDX: "Beatmania IIDX", + GameConstants.JUBEAT: "Jubeat", + GameConstants.MGA: "Metal Gear Arcade", + GameConstants.MUSECA: "MÚSECA", + GameConstants.POPN_MUSIC: "Pop'n Music", + GameConstants.REFLEC_BEAT: "Reflec Beat", + GameConstants.SDVX: "Sound Voltex", + }.get(game, "Unknown") def has_broadcast_destination(self, game: GameConstants) -> bool: # For now we only support discord @@ -36,25 +37,43 @@ class Triggers: # Nothing is hooked up for this game, so there is no destination. return False - def broadcast_score(self, data: Dict[BroadcastConstants, str], game: GameConstants, song: Song) -> None: + def broadcast_score( + self, data: Dict[BroadcastConstants, str], game: GameConstants, song: Song + ) -> None: # For now we only support discord if self.config.webhooks.discord[game] is not None: self.broadcast_score_discord(data, game, song) - def broadcast_score_discord(self, data: Dict[BroadcastConstants, str], game: GameConstants, song: Song) -> None: + def broadcast_score_discord( + self, data: Dict[BroadcastConstants, str], game: GameConstants, song: Song + ) -> None: if game in {GameConstants.IIDX, GameConstants.POPN_MUSIC}: now = datetime.now() webhook = DiscordWebhook(url=self.config.webhooks.discord[game]) - scoreembed = DiscordEmbed(title=f'New {self.__gameconst_to_series(game)} Score!', color='fbba08') - scoreembed.set_footer(text=(now.strftime('Score was recorded on %m/%d/%y at %H:%M:%S'))) + scoreembed = DiscordEmbed( + title=f"New {self.__gameconst_to_series(game)} Score!", color="fbba08" + ) + scoreembed.set_footer( + text=(now.strftime("Score was recorded on %m/%d/%y at %H:%M:%S")) + ) # lets give it an author - song_url = f"{self.config.server.uri}/{game.value}/topscores/{song.id}" if self.config.server.uri is not None else None + song_url = ( + f"{self.config.server.uri}/{game.value}/topscores/{song.id}" + if self.config.server.uri is not None + else None + ) scoreembed.set_author(name=self.config.name, url=song_url) for item, value in data.items(): inline = True - if item in {BroadcastConstants.DJ_NAME, BroadcastConstants.PLAYER_NAME, BroadcastConstants.SONG_NAME, BroadcastConstants.ARTIST_NAME, BroadcastConstants.PLAY_STATS_HEADER}: + if item in { + BroadcastConstants.DJ_NAME, + BroadcastConstants.PLAYER_NAME, + BroadcastConstants.SONG_NAME, + BroadcastConstants.ARTIST_NAME, + BroadcastConstants.PLAY_STATS_HEADER, + }: inline = False scoreembed.add_embed_field(name=item.value, value=value, inline=inline) webhook.add_embed(scoreembed) diff --git a/bemani/data/types.py b/bemani/data/types.py index 9540c43..7d4d2e5 100644 --- a/bemani/data/types.py +++ b/bemani/data/types.py @@ -2,8 +2,8 @@ from typing import Optional, List, Dict, Any, NewType from bemani.common import ValidatedDict, GameConstants -UserID = NewType('UserID', int) -ArcadeID = NewType('ArcadeID', int) +UserID = NewType("UserID", int) +ArcadeID = NewType("ArcadeID", int) class User: @@ -15,7 +15,9 @@ class User: more cards, or swap out a card for a new one. """ - def __init__(self, userid: UserID, username: Optional[str], email: Optional[str], admin: bool) -> None: + def __init__( + self, userid: UserID, username: Optional[str], email: Optional[str], admin: bool + ) -> None: """ Initialize the user object. @@ -43,7 +45,13 @@ class Achievement: that can have some attached data, such as item unlocks, tran medals, course progress, etc. """ - def __init__(self, achievementid: int, achievementtype: str, timestamp: Optional[int], data: Dict[str, Any]) -> None: + def __init__( + self, + achievementid: int, + achievementtype: str, + timestamp: Optional[int], + data: Dict[str, Any], + ) -> None: """ Initialize the achievement object. @@ -68,7 +76,9 @@ class Link: determined by the game that needs this linkage. """ - def __init__(self, userid: UserID, linktype: str, other_userid: UserID, data: Dict[str, Any]) -> None: + def __init__( + self, userid: UserID, linktype: str, other_userid: UserID, data: Dict[str, Any] + ) -> None: """ Initialize the achievement object. @@ -144,7 +154,16 @@ class Arcade: crediting accounts. Machines belong to either no arcade or a single arcase. """ - def __init__(self, arcadeid: ArcadeID, name: str, description: str, pin: str, region: int, data: Dict[str, Any], owners: List[UserID]) -> None: + def __init__( + self, + arcadeid: ArcadeID, + name: str, + description: str, + pin: str, + region: int, + data: Dict[str, Any], + owners: List[UserID], + ) -> None: """ Initialize the arcade instance. @@ -329,7 +348,15 @@ class Event: invalid PCBIDs trying to connect, or more mundate events such as daily selection. """ - def __init__(self, auditid: int, timestamp: int, userid: Optional[UserID], arcadeid: Optional[ArcadeID], event: str, data: Dict[str, Any]) -> None: + def __init__( + self, + auditid: int, + timestamp: int, + userid: Optional[UserID], + arcadeid: Optional[ArcadeID], + event: str, + data: Dict[str, Any], + ) -> None: """ Initialize the audit event object. @@ -405,7 +432,15 @@ class Server: to for pulling data. """ - def __init__(self, serverid: int, timestamp: int, uri: str, token: str, allow_stats: bool, allow_scores: bool) -> None: + def __init__( + self, + serverid: int, + timestamp: int, + uri: str, + token: str, + allow_stats: bool, + allow_scores: bool, + ) -> None: """ Initialize the server object. diff --git a/bemani/format/afp/__init__.py b/bemani/format/afp/__init__.py index 449872b..b9bde36 100644 --- a/bemani/format/afp/__init__.py +++ b/bemani/format/afp/__init__.py @@ -20,45 +20,54 @@ from .swf import ( ) from .container import TXP2File, PMAN, Texture, TextureRegion, Unknown1, Unknown2 from .render import AFPRenderer -from .types import Matrix, Color, Point, Rectangle, AP2Tag, AP2Action, AP2Object, AP2Pointer +from .types import ( + Matrix, + Color, + Point, + Rectangle, + AP2Tag, + AP2Action, + AP2Object, + AP2Pointer, +) from .decompile import ByteCode, ByteCodeDecompiler __all__ = [ - 'Shape', - 'DrawParams', - 'SWF', - 'Frame', - 'Tag', - 'TagPointer', - 'AP2ShapeTag', - 'AP2DefineFontTag', - 'AP2TextChar', - 'AP2TextLine', - 'AP2DefineMorphShapeTag', - 'AP2DefineButtonTag', - 'AP2DefineTextTag', - 'AP2DoActionTag', - 'AP2PlaceObjectTag', - 'AP2RemoveObjectTag', - 'AP2DefineSpriteTag', - 'AP2DefineEditTextTag', - 'NamedTagReference', - 'TXP2File', - 'PMAN', - 'Texture', - 'TextureRegion', - 'Unknown1', - 'Unknown2', - 'AFPRenderer', - 'Matrix', - 'Color', - 'Point', - 'Rectangle', - 'AP2Tag', - 'AP2Action', - 'AP2Object', - 'AP2Pointer', - 'ByteCode', - 'ByteCodeDecompiler', + "Shape", + "DrawParams", + "SWF", + "Frame", + "Tag", + "TagPointer", + "AP2ShapeTag", + "AP2DefineFontTag", + "AP2TextChar", + "AP2TextLine", + "AP2DefineMorphShapeTag", + "AP2DefineButtonTag", + "AP2DefineTextTag", + "AP2DoActionTag", + "AP2PlaceObjectTag", + "AP2RemoveObjectTag", + "AP2DefineSpriteTag", + "AP2DefineEditTextTag", + "NamedTagReference", + "TXP2File", + "PMAN", + "Texture", + "TextureRegion", + "Unknown1", + "Unknown2", + "AFPRenderer", + "Matrix", + "Color", + "Point", + "Rectangle", + "AP2Tag", + "AP2Action", + "AP2Object", + "AP2Pointer", + "ByteCode", + "ByteCodeDecompiler", ] diff --git a/bemani/format/afp/blend/blend.py b/bemani/format/afp/blend/blend.py index f5990e9..c30bf59 100644 --- a/bemani/format/afp/blend/blend.py +++ b/bemani/format/afp/blend/blend.py @@ -36,10 +36,16 @@ def blend_normal( srcremainder = 1.0 - srcpercent new_alpha = max(min(0.0, srcpercent + destpercent * srcremainder), 1.0) return ( - clamp(((dest[0] * destpercent * srcremainder) + (src[0] * srcpercent)) / new_alpha), - clamp(((dest[1] * destpercent * srcremainder) + (src[1] * srcpercent)) / new_alpha), - clamp(((dest[2] * destpercent * srcremainder) + (src[2] * srcpercent)) / new_alpha), - clamp(255 * new_alpha) + clamp( + ((dest[0] * destpercent * srcremainder) + (src[0] * srcpercent)) / new_alpha + ), + clamp( + ((dest[1] * destpercent * srcremainder) + (src[1] * srcpercent)) / new_alpha + ), + clamp( + ((dest[2] * destpercent * srcremainder) + (src[2] * srcpercent)) / new_alpha + ), + clamp(255 * new_alpha), ) @@ -114,9 +120,18 @@ def blend_multiply( src_alpha = src[3] / 255.0 src_remainder = 1.0 - src_alpha return ( - clamp((255 * ((dest[0] / 255.0) * (src[0] / 255.0) * src_alpha)) + (dest[0] * src_remainder)), - clamp((255 * ((dest[1] / 255.0) * (src[1] / 255.0) * src_alpha)) + (dest[1] * src_remainder)), - clamp((255 * ((dest[2] / 255.0) * (src[2] / 255.0) * src_alpha)) + (dest[2] * src_remainder)), + clamp( + (255 * ((dest[0] / 255.0) * (src[0] / 255.0) * src_alpha)) + + (dest[0] * src_remainder) + ), + clamp( + (255 * ((dest[1] / 255.0) * (src[1] / 255.0) * src_alpha)) + + (dest[1] * src_remainder) + ), + clamp( + (255 * ((dest[2] / 255.0) * (src[2] / 255.0) * src_alpha)) + + (dest[2] * src_remainder) + ), dest[3], ) @@ -237,7 +252,7 @@ def pixel_renderer( if maskbytes is not None and maskbytes[maskoff] == 0: # This pixel is masked off! - return imgbytes[imgoff:(imgoff + 4)] + return imgbytes[imgoff : (imgoff + 4)] if aa_mode != AAMode.NONE: r = 0 @@ -257,8 +272,20 @@ def pixel_renderer( xswing = 0.5 * max(1.0, xscale) yswing = 0.5 * max(1.0, yscale) - xpoints = [0.5 - xswing, 0.5 - (xswing / 2.0), 0.5, 0.5 + (xswing / 2.0), 0.5 + xswing] - ypoints = [0.5 - yswing, 0.5 - (yswing / 2.0), 0.5, 0.5 + (yswing / 2.0), 0.5 + yswing] + xpoints = [ + 0.5 - xswing, + 0.5 - (xswing / 2.0), + 0.5, + 0.5 + (xswing / 2.0), + 0.5 + xswing, + ] + ypoints = [ + 0.5 - yswing, + 0.5 - (yswing / 2.0), + 0.5, + 0.5 + (yswing / 2.0), + 0.5 + yswing, + ] # First, figure out if we can use bilinear resampling. bilinear = False @@ -266,7 +293,12 @@ def pixel_renderer( aaloc = callback(Point(imgx + 0.5, imgy + 0.5)) if aaloc is not None: aax, aay, _ = aaloc.as_tuple() - if not (aax <= 0 or aay <= 0 or aax >= (texwidth - 1) or aay >= (texheight - 1)): + if not ( + aax <= 0 + or aay <= 0 + or aax >= (texwidth - 1) + or aay >= (texheight - 1) + ): bilinear = True # Now perform the desired AA operation. @@ -300,13 +332,25 @@ def pixel_renderer( average = [255, 255, 255, 0] else: # Interpolate in the X direction on both Y axis. - y0r = ((texbytes[tex00] * tex00percent * (1.0 - aaxrem)) + (texbytes[tex10] * tex10percent * aaxrem)) - y0g = ((texbytes[tex00 + 1] * tex00percent * (1.0 - aaxrem)) + (texbytes[tex10 + 1] * tex10percent * aaxrem)) - y0b = ((texbytes[tex00 + 2] * tex00percent * (1.0 - aaxrem)) + (texbytes[tex10 + 2] * tex10percent * aaxrem)) + y0r = (texbytes[tex00] * tex00percent * (1.0 - aaxrem)) + ( + texbytes[tex10] * tex10percent * aaxrem + ) + y0g = (texbytes[tex00 + 1] * tex00percent * (1.0 - aaxrem)) + ( + texbytes[tex10 + 1] * tex10percent * aaxrem + ) + y0b = (texbytes[tex00 + 2] * tex00percent * (1.0 - aaxrem)) + ( + texbytes[tex10 + 2] * tex10percent * aaxrem + ) - y1r = ((texbytes[tex01] * tex01percent * (1.0 - aaxrem)) + (texbytes[tex11] * tex11percent * aaxrem)) - y1g = ((texbytes[tex01 + 1] * tex01percent * (1.0 - aaxrem)) + (texbytes[tex11 + 1] * tex11percent * aaxrem)) - y1b = ((texbytes[tex01 + 2] * tex01percent * (1.0 - aaxrem)) + (texbytes[tex11 + 2] * tex11percent * aaxrem)) + y1r = (texbytes[tex01] * tex01percent * (1.0 - aaxrem)) + ( + texbytes[tex11] * tex11percent * aaxrem + ) + y1g = (texbytes[tex01 + 1] * tex01percent * (1.0 - aaxrem)) + ( + texbytes[tex11 + 1] * tex11percent * aaxrem + ) + y1b = (texbytes[tex01 + 2] * tex01percent * (1.0 - aaxrem)) + ( + texbytes[tex11 + 2] * tex11percent * aaxrem + ) # Now interpolate the Y direction to get the final pixel value. average = [ @@ -320,7 +364,12 @@ def pixel_renderer( for addx in xpoints: xloc = imgx + addx yloc = imgy + addy - if xloc < 0.0 or yloc < 0.0 or xloc >= imgwidth or yloc >= imgheight: + if ( + xloc < 0.0 + or yloc < 0.0 + or xloc >= imgwidth + or yloc >= imgheight + ): continue texloc = callback(Point(xloc, yloc)) @@ -355,7 +404,7 @@ def pixel_renderer( if count == 0: # None of the samples existed in-bounds. - return imgbytes[imgoff:(imgoff + 4)] + return imgbytes[imgoff : (imgoff + 4)] # Average the pixels. Make sure to divide out the alpha in preparation for blending. alpha = a // denom @@ -364,25 +413,38 @@ def pixel_renderer( average = [255, 255, 255, alpha] else: apercent = alpha / 255.0 - average = [int((r / denom) / apercent), int((g / denom) / apercent), int((b / denom) / apercent), alpha] + average = [ + int((r / denom) / apercent), + int((g / denom) / apercent), + int((b / denom) / apercent), + alpha, + ] # Finally, blend it with the destination. - return blend_point(add_color, mult_color, average, imgbytes[imgoff:(imgoff + 4)], blendfunc) + return blend_point( + add_color, mult_color, average, imgbytes[imgoff : (imgoff + 4)], blendfunc + ) else: # Calculate what texture pixel data goes here. texloc = callback(Point(imgx + 0.5, imgy + 0.5)) if texloc is None: - return imgbytes[imgoff:(imgoff + 4)] + return imgbytes[imgoff : (imgoff + 4)] texx, texy, _ = texloc.as_tuple() # If we're out of bounds, don't update. if texx < 0 or texy < 0 or texx >= texwidth or texy >= texheight: - return imgbytes[imgoff:(imgoff + 4)] + return imgbytes[imgoff : (imgoff + 4)] # Blend it. texoff = (texx + (texy * texwidth)) * 4 - return blend_point(add_color, mult_color, texbytes[texoff:(texoff + 4)], imgbytes[imgoff:(imgoff + 4)], blendfunc) + return blend_point( + add_color, + mult_color, + texbytes[texoff : (texoff + 4)], + imgbytes[imgoff : (imgoff + 4)], + blendfunc, + ) def affine_line_renderer( @@ -408,14 +470,16 @@ def affine_line_renderer( if imgy is None: return - rowbytes = bytearray(imgbytes[(imgy * imgwidth * 4):((imgy + 1) * imgwidth * 4)]) + rowbytes = bytearray( + imgbytes[(imgy * imgwidth * 4) : ((imgy + 1) * imgwidth * 4)] + ) for imgx in range(imgwidth): if imgx < minx or imgx >= maxx: # No need to even consider this pixel. continue else: # Blit new pixel into the correct range. - rowbytes[(imgx * 4):((imgx + 1) * 4)] = pixel_renderer( + rowbytes[(imgx * 4) : ((imgx + 1) * 4)] = pixel_renderer( imgx, imgy, imgwidth, @@ -488,11 +552,11 @@ def affine_composite( cores = multiprocessing.cpu_count() if single_threaded or cores < 2: # Get the data in an easier to manipulate and faster to update fashion. - imgbytes = bytearray(img.tobytes('raw', 'RGBA')) - texbytes = texture.tobytes('raw', 'RGBA') + imgbytes = bytearray(img.tobytes("raw", "RGBA")) + texbytes = texture.tobytes("raw", "RGBA") if mask: alpha = mask.split()[-1] - maskbytes = alpha.tobytes('raw', 'L') + maskbytes = alpha.tobytes("raw", "L") else: maskbytes = None @@ -501,7 +565,7 @@ def affine_composite( for imgx in range(minx, maxx): # Determine offset imgoff = (imgx + (imgy * imgwidth)) * 4 - imgbytes[imgoff:(imgoff + 4)] = pixel_renderer( + imgbytes[imgoff : (imgoff + 4)] = pixel_renderer( imgx, imgy, imgwidth, @@ -520,13 +584,13 @@ def affine_composite( aa_mode, ) - img = Image.frombytes('RGBA', (imgwidth, imgheight), bytes(imgbytes)) + img = Image.frombytes("RGBA", (imgwidth, imgheight), bytes(imgbytes)) else: - imgbytes = img.tobytes('raw', 'RGBA') - texbytes = texture.tobytes('raw', 'RGBA') + imgbytes = img.tobytes("raw", "RGBA") + texbytes = texture.tobytes("raw", "RGBA") if mask: alpha = mask.split()[-1] - maskbytes = alpha.tobytes('raw', 'L') + maskbytes = alpha.tobytes("raw", "L") else: maskbytes = None @@ -574,7 +638,7 @@ def affine_composite( expected += 1 lines: List[bytes] = [ - imgbytes[x:(x + (imgwidth * 4))] + imgbytes[x : (x + (imgwidth * 4))] for x in range( 0, imgwidth * imgheight * 4, @@ -594,7 +658,7 @@ def affine_composite( if interrupted: raise KeyboardInterrupt() - img = Image.frombytes('RGBA', (imgwidth, imgheight), b''.join(lines)) + img = Image.frombytes("RGBA", (imgwidth, imgheight), b"".join(lines)) return img @@ -631,14 +695,16 @@ def perspective_line_renderer( if imgy is None: return - rowbytes = bytearray(imgbytes[(imgy * imgwidth * 4):((imgy + 1) * imgwidth * 4)]) + rowbytes = bytearray( + imgbytes[(imgy * imgwidth * 4) : ((imgy + 1) * imgwidth * 4)] + ) for imgx in range(imgwidth): if imgx < minx or imgx >= maxx: # No need to even consider this pixel. continue else: # Blit new pixel into the correct range. - rowbytes[(imgx * 4):((imgx + 1) * 4)] = pixel_renderer( + rowbytes[(imgx * 4) : ((imgx + 1) * 4)] = pixel_renderer( imgx, imgy, imgwidth, @@ -686,17 +752,19 @@ def perspective_composite( texheight = texture.height # Get the perspective-correct inverse matrix for looking up texture coordinates. - inverse_matrix, minx, miny, maxx, maxy = perspective_calculate(imgwidth, imgheight, texwidth, texheight, transform, camera, focal_length) + inverse_matrix, minx, miny, maxx, maxy = perspective_calculate( + imgwidth, imgheight, texwidth, texheight, transform, camera, focal_length + ) if inverse_matrix is None: # This texture is entirely off of the screen. return img # Get the data in an easier to manipulate and faster to update fashion. - imgbytes = bytearray(img.tobytes('raw', 'RGBA')) - texbytes = texture.tobytes('raw', 'RGBA') + imgbytes = bytearray(img.tobytes("raw", "RGBA")) + texbytes = texture.tobytes("raw", "RGBA") if mask: alpha = mask.split()[-1] - maskbytes = alpha.tobytes('raw', 'L') + maskbytes = alpha.tobytes("raw", "L") else: maskbytes = None @@ -711,11 +779,11 @@ def perspective_composite( cores = multiprocessing.cpu_count() if single_threaded or cores < 2: # Get the data in an easier to manipulate and faster to update fashion. - imgbytes = bytearray(img.tobytes('raw', 'RGBA')) - texbytes = texture.tobytes('raw', 'RGBA') + imgbytes = bytearray(img.tobytes("raw", "RGBA")) + texbytes = texture.tobytes("raw", "RGBA") if mask: alpha = mask.split()[-1] - maskbytes = alpha.tobytes('raw', 'L') + maskbytes = alpha.tobytes("raw", "L") else: maskbytes = None @@ -724,7 +792,7 @@ def perspective_composite( for imgx in range(minx, maxx): # Determine offset imgoff = (imgx + (imgy * imgwidth)) * 4 - imgbytes[imgoff:(imgoff + 4)] = pixel_renderer( + imgbytes[imgoff : (imgoff + 4)] = pixel_renderer( imgx, imgy, imgwidth, @@ -743,13 +811,13 @@ def perspective_composite( aa_mode, ) - img = Image.frombytes('RGBA', (imgwidth, imgheight), bytes(imgbytes)) + img = Image.frombytes("RGBA", (imgwidth, imgheight), bytes(imgbytes)) else: - imgbytes = img.tobytes('raw', 'RGBA') - texbytes = texture.tobytes('raw', 'RGBA') + imgbytes = img.tobytes("raw", "RGBA") + texbytes = texture.tobytes("raw", "RGBA") if mask: alpha = mask.split()[-1] - maskbytes = alpha.tobytes('raw', 'L') + maskbytes = alpha.tobytes("raw", "L") else: maskbytes = None @@ -799,7 +867,7 @@ def perspective_composite( expected += 1 lines: List[bytes] = [ - imgbytes[x:(x + (imgwidth * 4))] + imgbytes[x : (x + (imgwidth * 4))] for x in range( 0, imgwidth * imgheight * 4, @@ -819,5 +887,5 @@ def perspective_composite( if interrupted: raise KeyboardInterrupt() - img = Image.frombytes('RGBA', (imgwidth, imgheight), b''.join(lines)) + img = Image.frombytes("RGBA", (imgwidth, imgheight), b"".join(lines)) return img diff --git a/bemani/format/afp/container.py b/bemani/format/afp/container.py index 8e4a58e..10e2d32 100644 --- a/bemani/format/afp/container.py +++ b/bemani/format/afp/container.py @@ -11,7 +11,14 @@ from bemani.protocol.node import Node from .swf import SWF from .geo import Shape -from .util import TrackedCoverage, VerboseOutput, scramble_text, descramble_text, pad, align +from .util import ( + TrackedCoverage, + VerboseOutput, + scramble_text, + descramble_text, + pad, + align, +) class PMAN: @@ -31,9 +38,9 @@ class PMAN: def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { - 'flags': [self.flags1, self.flags2, self.flags3], - 'entries': self.entries, - 'ordering': self.ordering, + "flags": [self.flags1, self.flags2, self.flags3], + "entries": self.entries, + "ordering": self.ordering, } @@ -66,19 +73,27 @@ class Texture: def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { - 'name': self.name, - 'width': self.width, - 'height': self.height, - 'fmt': self.fmt, - 'header_flags': [self.header_flags1, self.header_flags2, self.header_flags3], - 'fmt_flags': self.fmtflags, - 'raw': self.raw.hex(), - 'compressed': self.compressed.hex() if self.compressed is not None else None, + "name": self.name, + "width": self.width, + "height": self.height, + "fmt": self.fmt, + "header_flags": [ + self.header_flags1, + self.header_flags2, + self.header_flags3, + ], + "fmt_flags": self.fmtflags, + "raw": self.raw.hex(), + "compressed": self.compressed.hex() + if self.compressed is not None + else None, } class TextureRegion: - def __init__(self, textureno: int, left: int, top: int, right: int, bottom: int) -> None: + def __init__( + self, textureno: int, left: int, top: int, right: int, bottom: int + ) -> None: self.textureno = textureno self.left = left self.top = top @@ -87,22 +102,22 @@ class TextureRegion: def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { - 'texture': self.textureno, - 'left': self.left, - 'top': self.top, - 'right': self.right, - 'bottom': self.bottom, + "texture": self.textureno, + "left": self.left, + "top": self.top, + "right": self.right, + "bottom": self.bottom, } def __repr__(self) -> str: return ( - f"texture: {self.textureno}, " + - f"left: {self.left / 2}, " + - f"top: {self.top / 2}, " + - f"right: {self.right / 2}, " + - f"bottom: {self.bottom / 2}, " + - f"width: {(self.right - self.left) / 2}, " + - f"height: {(self.bottom - self.top) / 2}" + f"texture: {self.textureno}, " + + f"left: {self.left / 2}, " + + f"top: {self.top / 2}, " + + f"right: {self.right / 2}, " + + f"bottom: {self.bottom / 2}, " + + f"width: {(self.right - self.left) / 2}, " + + f"height: {(self.bottom - self.top) / 2}" ) @@ -119,8 +134,8 @@ class Unknown1: def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { - 'name': self.name, - 'data': self.data.hex(), + "name": self.name, + "data": self.data.hex(), } @@ -135,7 +150,7 @@ class Unknown2: def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { - 'data': self.data.hex(), + "data": self.data.hex(), } @@ -217,25 +232,27 @@ class TXP2File(TrackedCoverage, VerboseOutput): def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { - 'endian': self.endian, - 'features': self.features, - 'file_flags': self.file_flags.hex(), - 'obfuscated': self.text_obfuscated, - 'legacy_lz': self.legacy_lz, - 'modern_lz': self.modern_lz, - 'textures': [tex.as_dict(*args, **kwargs) for tex in self.textures], - 'texturemap': self.texturemap.as_dict(*args, **kwargs), - 'textureregion': [reg.as_dict(*args, **kwargs) for reg in self.texture_to_region], - 'regionmap': self.regionmap.as_dict(*args, **kwargs), - 'swfdata': [data.as_dict(*args, **kwargs) for data in self.swfdata], - 'swfmap': self.swfmap.as_dict(*args, **kwargs), - 'fontdata': str(self.fontdata) if self.fontdata is not None else None, - 'shapes': [shape.as_dict(*args, **kwargs) for shape in self.shapes], - 'shapemap': self.shapemap.as_dict(*args, **kwargs), - 'unknown1': [unk.as_dict(*args, **kwargs) for unk in self.unknown1], - 'unknown1map': self.unk_pman1.as_dict(*args, **kwargs), - 'unknown2': [unk.as_dict(*args, **kwargs) for unk in self.unknown2], - 'unknown2map': self.unk_pman2.as_dict(*args, **kwargs), + "endian": self.endian, + "features": self.features, + "file_flags": self.file_flags.hex(), + "obfuscated": self.text_obfuscated, + "legacy_lz": self.legacy_lz, + "modern_lz": self.modern_lz, + "textures": [tex.as_dict(*args, **kwargs) for tex in self.textures], + "texturemap": self.texturemap.as_dict(*args, **kwargs), + "textureregion": [ + reg.as_dict(*args, **kwargs) for reg in self.texture_to_region + ], + "regionmap": self.regionmap.as_dict(*args, **kwargs), + "swfdata": [data.as_dict(*args, **kwargs) for data in self.swfdata], + "swfmap": self.swfmap.as_dict(*args, **kwargs), + "fontdata": str(self.fontdata) if self.fontdata is not None else None, + "shapes": [shape.as_dict(*args, **kwargs) for shape in self.shapes], + "shapemap": self.shapemap.as_dict(*args, **kwargs), + "unknown1": [unk.as_dict(*args, **kwargs) for unk in self.unknown1], + "unknown1map": self.unk_pman1.as_dict(*args, **kwargs), + "unknown2": [unk.as_dict(*args, **kwargs) for unk in self.unknown2], + "unknown2map": self.unk_pman2.as_dict(*args, **kwargs), } @staticmethod @@ -255,22 +272,32 @@ class TXP2File(TrackedCoverage, VerboseOutput): result = 0 for byte in bytestream: for i in range(6): - result = TXP2File.poly(result) ^ TXP2File.cap32((result << 1) | ((byte >> i) & 1)) + result = TXP2File.poly(result) ^ TXP2File.cap32( + (result << 1) | ((byte >> i) & 1) + ) return result def get_until_null(self, offset: int) -> bytes: out = b"" while self.data[offset] != 0: - out += self.data[offset:(offset + 1)] + out += self.data[offset : (offset + 1)] offset += 1 return out def descramble_pman(self, offset: int) -> PMAN: # Unclear what the first three unknowns are, but the fourth # looks like it could possibly be two int16s indicating unknown? - magic, expect_zero, flags1, flags2, numentries, flags3, data_offset = struct.unpack( + ( + magic, + expect_zero, + flags1, + flags2, + numentries, + flags3, + data_offset, + ) = struct.unpack( f"{self.endian}4sIIIIII", - self.data[offset:(offset + 28)], + self.data[offset : (offset + 28)], ) self.add_coverage(offset, 28) @@ -292,7 +319,7 @@ class TXP2File(TrackedCoverage, VerboseOutput): file_offset = data_offset + (i * 12) name_crc, entry_no, nameoffset = struct.unpack( f"{self.endian}III", - self.data[file_offset:(file_offset + 12)], + self.data[file_offset : (file_offset + 12)], ) self.add_coverage(file_offset, 12) @@ -306,7 +333,7 @@ class TXP2File(TrackedCoverage, VerboseOutput): ordering[entry_no] = i self.vprint(f" {entry_no}: {name}, offset: {hex(nameoffset)}") - if name_crc != TXP2File.crc32(name.encode('ascii')): + if name_crc != TXP2File.crc32(name.encode("ascii")): raise Exception(f"Name CRC failed for {name}") for i, n in enumerate(names): @@ -366,18 +393,22 @@ class TXP2File(TrackedCoverage, VerboseOutput): if feature_mask & 0x01: # List of textures that exist in the file, with pointers to their data. - length, offset = struct.unpack(f"{self.endian}II", self.data[header_offset:(header_offset + 8)]) + length, offset = struct.unpack( + f"{self.endian}II", self.data[header_offset : (header_offset + 8)] + ) self.add_coverage(header_offset, 8) header_offset += 8 - self.vprint(f"Bit 0x000001 - textures; count: {length}, offset: {hex(offset)}") + self.vprint( + f"Bit 0x000001 - textures; count: {length}, offset: {hex(offset)}" + ) for x in range(length): interesting_offset = offset + (x * 12) if interesting_offset != 0: name_offset, texture_length, texture_offset = struct.unpack( f"{self.endian}III", - self.data[interesting_offset:(interesting_offset + 12)], + self.data[interesting_offset : (interesting_offset + 12)], ) self.add_coverage(interesting_offset, 12) @@ -395,17 +426,23 @@ class TXP2File(TrackedCoverage, VerboseOutput): # Get size, round up to nearest power of 4 inflated_size, deflated_size = struct.unpack( ">II", - self.data[texture_offset:(texture_offset + 8)], + self.data[texture_offset : (texture_offset + 8)], ) self.add_coverage(texture_offset, 8) if deflated_size != (texture_length - 8): - raise Exception("We got an incorrect length for lz texture!") - self.vprint(f" {name}, length: {texture_length}, offset: {hex(texture_offset)}, deflated_size: {deflated_size}, inflated_size: {inflated_size}") + raise Exception( + "We got an incorrect length for lz texture!" + ) + self.vprint( + f" {name}, length: {texture_length}, offset: {hex(texture_offset)}, deflated_size: {deflated_size}, inflated_size: {inflated_size}" + ) inflated_size = (inflated_size + 3) & (~3) # Get the data offset. lz_data_offset = texture_offset + 8 - lz_data = self.data[lz_data_offset:(lz_data_offset + deflated_size)] + lz_data = self.data[ + lz_data_offset : (lz_data_offset + deflated_size) + ] self.add_coverage(lz_data_offset, deflated_size) # This takes forever, so skip it if we're pretending. @@ -414,17 +451,25 @@ class TXP2File(TrackedCoverage, VerboseOutput): else: inflated_size, deflated_size = struct.unpack( ">II", - self.data[texture_offset:(texture_offset + 8)], + self.data[texture_offset : (texture_offset + 8)], ) # I'm guessing how raw textures work because I haven't seen them. # I assume they're like the above, so lets put in some asertions. if deflated_size != (texture_length - 8): - raise Exception("We got an incorrect length for raw texture!") - self.vprint(f" {name}, length: {texture_length}, offset: {hex(texture_offset)}, deflated_size: {deflated_size}, inflated_size: {inflated_size}") + raise Exception( + "We got an incorrect length for raw texture!" + ) + self.vprint( + f" {name}, length: {texture_length}, offset: {hex(texture_offset)}, deflated_size: {deflated_size}, inflated_size: {inflated_size}" + ) # Just grab the raw data. - raw_data = self.data[(texture_offset + 8):(texture_offset + 8 + deflated_size)] + raw_data = self.data[ + (texture_offset + 8) : ( + texture_offset + 8 + deflated_size + ) + ] self.add_coverage(texture_offset, deflated_size + 8) ( @@ -446,16 +491,24 @@ class TXP2File(TrackedCoverage, VerboseOutput): # I have only ever observed the following values across two different games. # Don't want to keep the chunk around so let's assert our assumptions. if (expected_zero1 | expected_zero2) != 0: - raise Exception("Found unexpected non-zero value in texture header!") - if raw_data[32:44] != b'\0' * 12: - raise Exception("Found unexpected non-zero value in texture header!") + raise Exception( + "Found unexpected non-zero value in texture header!" + ) + if raw_data[32:44] != b"\0" * 12: + raise Exception( + "Found unexpected non-zero value in texture header!" + ) # This is almost ALWAYS 3, but I've seen it be 1 as well, so I guess we have to # round-trip it if we want to write files back out. I have no clue what it's for. # I've seen it be 1 only on files used for fonts so far, but I am not sure there # is any correlation there. - header_flags3 = struct.unpack(f"{self.endian}I", raw_data[44:48])[0] - if raw_data[48:64] != b'\0' * 16: - raise Exception("Found unexpected non-zero value in texture header!") + header_flags3 = struct.unpack( + f"{self.endian}I", raw_data[44:48] + )[0] + if raw_data[48:64] != b"\0" * 16: + raise Exception( + "Found unexpected non-zero value in texture header!" + ) fmt = fmtflags & 0xFF # Extract flags that the game cares about. @@ -482,7 +535,7 @@ class TXP2File(TrackedCoverage, VerboseOutput): for i in range(width * height): pixel = struct.unpack( f"{self.endian}H", - raw_data[(64 + (i * 2)):(66 + (i * 2))], + raw_data[(64 + (i * 2)) : (66 + (i * 2))], )[0] # Extract the raw values @@ -495,23 +548,33 @@ class TXP2File(TrackedCoverage, VerboseOutput): green = green | (green >> 6) blue = blue | (blue >> 5) - newdata.append( - struct.pack(" 0: for i in range(length): descriptor_offset = offset + (10 * i) texture_no, left, top, right, bottom = struct.unpack( f"{self.endian}HHHHH", - self.data[descriptor_offset:(descriptor_offset + 10)], + self.data[descriptor_offset : (descriptor_offset + 10)], ) self.add_coverage(descriptor_offset, 10) @@ -690,7 +781,9 @@ class TXP2File(TrackedCoverage, VerboseOutput): if feature_mask & 0x10: # Names of the graphics regions, so we can look into the texture_to_region # mapping above. Used by shapes to find the right region offset given a name. - offset = struct.unpack(f"{self.endian}I", self.data[header_offset:(header_offset + 4)])[0] + offset = struct.unpack( + f"{self.endian}I", self.data[header_offset : (header_offset + 4)] + )[0] self.add_coverage(header_offset, 4) header_offset += 4 @@ -709,22 +802,30 @@ class TXP2File(TrackedCoverage, VerboseOutput): if feature_mask & 0x40: # Two unknown bytes, first is a length or a count. Secound is # an optional offset to grab another set of bytes from. - length, offset = struct.unpack(f"{self.endian}II", self.data[header_offset:(header_offset + 8)]) + length, offset = struct.unpack( + f"{self.endian}II", self.data[header_offset : (header_offset + 8)] + ) self.add_coverage(header_offset, 8) header_offset += 8 - self.vprint(f"Bit 0x000040 - unknown; count: {length}, offset: {hex(offset)}") + self.vprint( + f"Bit 0x000040 - unknown; count: {length}, offset: {hex(offset)}" + ) if offset != 0 and length > 0: for i in range(length): unk_offset = offset + (i * 16) - name_offset = struct.unpack(f"{self.endian}I", self.data[unk_offset:(unk_offset + 4)])[0] + name_offset = struct.unpack( + f"{self.endian}I", self.data[unk_offset : (unk_offset + 4)] + )[0] self.add_coverage(unk_offset, 4) # The game does some very bizarre bit-shifting. Its clear tha the first value # points at a name structure, but its not in the correct endianness. This replicates # the weird logic seen in game disassembly. - name_offset = (((name_offset >> 7) & 0x1FF) << 16) + ((name_offset >> 16) & 0xFFFF) + name_offset = (((name_offset >> 7) & 0x1FF) << 16) + ( + (name_offset >> 16) & 0xFFFF + ) if name_offset != 0: # Let's decode this until the first null. bytedata = self.get_until_null(name_offset) @@ -735,7 +836,7 @@ class TXP2File(TrackedCoverage, VerboseOutput): self.unknown1.append( Unknown1( name=name, - data=self.data[(unk_offset + 4):(unk_offset + 16)], + data=self.data[(unk_offset + 4) : (unk_offset + 16)], ) ) self.add_coverage(unk_offset + 4, 12) @@ -745,7 +846,9 @@ class TXP2File(TrackedCoverage, VerboseOutput): if feature_mask & 0x80: # One unknown byte, treated as an offset. This is clearly the mapping for the parsed # structures from 0x40, but I don't know what those are. - offset = struct.unpack(f"{self.endian}I", self.data[header_offset:(header_offset + 4)])[0] + offset = struct.unpack( + f"{self.endian}I", self.data[header_offset : (header_offset + 4)] + )[0] self.add_coverage(header_offset, 4) header_offset += 4 @@ -760,17 +863,21 @@ class TXP2File(TrackedCoverage, VerboseOutput): if feature_mask & 0x100: # Two unknown bytes, first is a length or a count. Secound is # an optional offset to grab another set of bytes from. - length, offset = struct.unpack(f"{self.endian}II", self.data[header_offset:(header_offset + 8)]) + length, offset = struct.unpack( + f"{self.endian}II", self.data[header_offset : (header_offset + 8)] + ) self.add_coverage(header_offset, 8) header_offset += 8 - self.vprint(f"Bit 0x000100 - unknown; count: {length}, offset: {hex(offset)}") + self.vprint( + f"Bit 0x000100 - unknown; count: {length}, offset: {hex(offset)}" + ) if offset != 0 and length > 0: for i in range(length): unk_offset = offset + (i * 4) self.unknown2.append( - Unknown2(self.data[unk_offset:(unk_offset + 4)]) + Unknown2(self.data[unk_offset : (unk_offset + 4)]) ) self.add_coverage(unk_offset, 4) else: @@ -779,7 +886,9 @@ class TXP2File(TrackedCoverage, VerboseOutput): if feature_mask & 0x200: # One unknown byte, treated as an offset. Almost positive its a string mapping # for the above 0x100 structure. That's how this file format appears to work. - offset = struct.unpack(f"{self.endian}I", self.data[header_offset:(header_offset + 4)])[0] + offset = struct.unpack( + f"{self.endian}I", self.data[header_offset : (header_offset + 4)] + )[0] self.add_coverage(header_offset, 4) header_offset += 4 @@ -795,7 +904,9 @@ class TXP2File(TrackedCoverage, VerboseOutput): # One unknown byte, treated as an offset. I have no idea what this is used for, # it seems to be empty data in files that I've looked at, it doesn't go to any # structure or mapping. - offset = struct.unpack(f"{self.endian}I", self.data[header_offset:(header_offset + 4)])[0] + offset = struct.unpack( + f"{self.endian}I", self.data[header_offset : (header_offset + 4)] + )[0] self.add_coverage(header_offset, 4) header_offset += 4 @@ -806,18 +917,22 @@ class TXP2File(TrackedCoverage, VerboseOutput): if feature_mask & 0x800: # SWF raw data that is loaded and passed to AFP core. It is equivalent to the # afp files in an IFS container. - length, offset = struct.unpack(f"{self.endian}II", self.data[header_offset:(header_offset + 8)]) + length, offset = struct.unpack( + f"{self.endian}II", self.data[header_offset : (header_offset + 8)] + ) self.add_coverage(header_offset, 8) header_offset += 8 - self.vprint(f"Bit 0x000800 - swfdata; count: {length}, offset: {hex(offset)}") + self.vprint( + f"Bit 0x000800 - swfdata; count: {length}, offset: {hex(offset)}" + ) for x in range(length): interesting_offset = offset + (x * 12) if interesting_offset != 0: name_offset, swf_length, swf_offset = struct.unpack( f"{self.endian}III", - self.data[interesting_offset:(interesting_offset + 12)], + self.data[interesting_offset : (interesting_offset + 12)], ) self.add_coverage(interesting_offset, 12) if name_offset != 0: @@ -825,14 +940,13 @@ class TXP2File(TrackedCoverage, VerboseOutput): bytedata = self.get_until_null(name_offset) self.add_coverage(name_offset, len(bytedata) + 1, unique=False) name = descramble_text(bytedata, self.text_obfuscated) - self.vprint(f" {name}, length: {swf_length}, offset: {hex(swf_offset)}") + self.vprint( + f" {name}, length: {swf_length}, offset: {hex(swf_offset)}" + ) if swf_offset != 0: self.swfdata.append( - SWF( - name, - self.data[swf_offset:(swf_offset + swf_length)] - ) + SWF(name, self.data[swf_offset : (swf_offset + swf_length)]) ) self.add_coverage(swf_offset, swf_length) else: @@ -840,7 +954,9 @@ class TXP2File(TrackedCoverage, VerboseOutput): if feature_mask & 0x1000: # A mapping structure that allows looking up SWF data by name. - offset = struct.unpack(f"{self.endian}I", self.data[header_offset:(header_offset + 4)])[0] + offset = struct.unpack( + f"{self.endian}I", self.data[header_offset : (header_offset + 4)] + )[0] self.add_coverage(header_offset, 4) header_offset += 4 @@ -855,18 +971,22 @@ class TXP2File(TrackedCoverage, VerboseOutput): # These are shapes as used with the SWF data above. They contain mappings between a # loaded texture shape and the region that contains data. They are equivalent to the # geo files found in an IFS container. - length, offset = struct.unpack(f"{self.endian}II", self.data[header_offset:(header_offset + 8)]) + length, offset = struct.unpack( + f"{self.endian}II", self.data[header_offset : (header_offset + 8)] + ) self.add_coverage(header_offset, 8) header_offset += 8 - self.vprint(f"Bit 0x002000 - shapes; count: {length}, offset: {hex(offset)}") + self.vprint( + f"Bit 0x002000 - shapes; count: {length}, offset: {hex(offset)}" + ) for x in range(length): shape_base_offset = offset + (x * 12) if shape_base_offset != 0: name_offset, shape_length, shape_offset = struct.unpack( f"{self.endian}III", - self.data[shape_base_offset:(shape_base_offset + 12)], + self.data[shape_base_offset : (shape_base_offset + 12)], ) self.add_coverage(shape_base_offset, 12) @@ -881,13 +1001,15 @@ class TXP2File(TrackedCoverage, VerboseOutput): if shape_offset != 0: shape = Shape( name, - self.data[shape_offset:(shape_offset + shape_length)], + self.data[shape_offset : (shape_offset + shape_length)], ) shape.parse(text_obfuscated=self.text_obfuscated) self.shapes.append(shape) self.add_coverage(shape_offset, shape_length) - self.vprint(f" {name}, length: {shape_length}, offset: {hex(shape_offset)}") + self.vprint( + f" {name}, length: {shape_length}, offset: {hex(shape_offset)}" + ) for line in str(shape).split(os.linesep): self.vprint(f" {line}") @@ -896,7 +1018,9 @@ class TXP2File(TrackedCoverage, VerboseOutput): if feature_mask & 0x4000: # Mapping so that shapes can be looked up by name to get their offset. - offset = struct.unpack(f"{self.endian}I", self.data[header_offset:(header_offset + 4)])[0] + offset = struct.unpack( + f"{self.endian}I", self.data[header_offset : (header_offset + 4)] + )[0] self.add_coverage(header_offset, 4) header_offset += 4 @@ -910,7 +1034,9 @@ class TXP2File(TrackedCoverage, VerboseOutput): if feature_mask & 0x8000: # One unknown byte, treated as an offset. I have no idea what this is because # the games I've looked at don't include this bit. - offset = struct.unpack(f"{self.endian}I", self.data[header_offset:(header_offset + 4)])[0] + offset = struct.unpack( + f"{self.endian}I", self.data[header_offset : (header_offset + 4)] + )[0] self.add_coverage(header_offset, 4) header_offset += 4 @@ -925,16 +1051,22 @@ class TXP2File(TrackedCoverage, VerboseOutput): if feature_mask & 0x10000: # Included font package, BINXRPC encoded. This is basically a texture sheet with an XML # pointing at the region in the texture sheet for every renderable character. - offset = struct.unpack(f"{self.endian}I", self.data[header_offset:(header_offset + 4)])[0] + offset = struct.unpack( + f"{self.endian}I", self.data[header_offset : (header_offset + 4)] + )[0] self.add_coverage(header_offset, 4) header_offset += 4 # I am not sure what the unknown byte is for. It always appears as # all zeros in all files I've looked at. - expect_zero, length, binxrpc_offset = struct.unpack(f"{self.endian}III", self.data[offset:(offset + 12)]) + expect_zero, length, binxrpc_offset = struct.unpack( + f"{self.endian}III", self.data[offset : (offset + 12)] + ) self.add_coverage(offset, 12) - self.vprint(f"Bit 0x010000 - fontinfo; offset: {hex(offset)}, binxrpc offset: {hex(binxrpc_offset)}") + self.vprint( + f"Bit 0x010000 - fontinfo; offset: {hex(offset)}, binxrpc offset: {hex(binxrpc_offset)}" + ) if expect_zero != 0: # If we find non-zero versions of this, then that means updating the file is @@ -942,7 +1074,9 @@ class TXP2File(TrackedCoverage, VerboseOutput): raise Exception("Expected a zero in font package header!") if binxrpc_offset != 0: - self.fontdata = self.benc.decode(self.data[binxrpc_offset:(binxrpc_offset + length)]) + self.fontdata = self.benc.decode( + self.data[binxrpc_offset : (binxrpc_offset + length)] + ) self.add_coverage(binxrpc_offset, length) else: self.fontdata = None @@ -952,7 +1086,9 @@ class TXP2File(TrackedCoverage, VerboseOutput): if feature_mask & 0x20000: # This is the byteswapping headers that allow us to byteswap the SWF data before passing it # to AFP core. It is equivalent to the bsi files in an IFS container. - offset = struct.unpack(f"{self.endian}I", self.data[header_offset:(header_offset + 4)])[0] + offset = struct.unpack( + f"{self.endian}I", self.data[header_offset : (header_offset + 4)] + )[0] self.add_coverage(header_offset, 4) header_offset += 4 @@ -967,9 +1103,11 @@ class TXP2File(TrackedCoverage, VerboseOutput): # I've observed and seems to make sense. expect_zero, afp_header_length, afp_header = struct.unpack( f"{self.endian}III", - self.data[structure_offset:(structure_offset + 12)] + self.data[structure_offset : (structure_offset + 12)], + ) + self.vprint( + f" length: {afp_header_length}, offset: {hex(afp_header)}" ) - self.vprint(f" length: {afp_header_length}, offset: {hex(afp_header)}") self.add_coverage(structure_offset, 12) if expect_zero != 0: @@ -977,7 +1115,9 @@ class TXP2File(TrackedCoverage, VerboseOutput): # potentially unsafe as we could rewrite it incorrectly. So, let's assert! raise Exception("Expected a zero in SWF header!") - self.swfdata[i].descramble_info = self.data[afp_header:(afp_header + afp_header_length)] + self.swfdata[i].descramble_info = self.data[ + afp_header : (afp_header + afp_header_length) + ] self.add_coverage(afp_header, afp_header_length) else: self.vprint("Bit 0x020000 - swfheaders; NOT PRESENT") @@ -1010,7 +1150,9 @@ class TXP2File(TrackedCoverage, VerboseOutput): return data - def write_pman(self, data: bytes, offset: int, pman: PMAN, string_offsets: Dict[str, int]) -> bytes: + def write_pman( + self, data: bytes, offset: int, pman: PMAN, string_offsets: Dict[str, int] + ) -> bytes: # First, lay down the PMAN header if self.endian == "<": magic = b"PMAN" @@ -1039,7 +1181,7 @@ class TXP2File(TrackedCoverage, VerboseOutput): # Now, lay down the individual entries datas: List[bytes] = [b""] * len(pman.entries) for entry_no, name in enumerate(pman.entries): - name_crc = TXP2File.crc32(name.encode('ascii')) + name_crc = TXP2File.crc32(name.encode("ascii")) if name not in string_offsets: # We haven't written this string out yet, so put it on our pending list. @@ -1074,13 +1216,13 @@ class TXP2File(TrackedCoverage, VerboseOutput): # The true file header, containing magic, some file flags, file length and # header length. - header: bytes = b'' + header: bytes = b"" # The bitfield structure that dictates what's found in the file and where. - bitfields: bytes = b'' + bitfields: bytes = b"" # The data itself. - body: bytes = b'' + body: bytes = b"" # First, plop down the file magic as well as the unknown file flags we # roundtripped. @@ -1168,20 +1310,27 @@ class TXP2File(TrackedCoverage, VerboseOutput): fmtflags = (texture.fmtflags & 0xFFFFFF00) | (texture.fmt & 0xFF) - raw_texture = struct.pack( - f"{self.endian}4sIIIHHIII", - magic, - texture.header_flags1, - texture.header_flags2, - 64 + len(texture.raw), - texture.width, - texture.height, - fmtflags, - 0, - 0, - ) + (b'\0' * 12) + struct.pack( - f"{self.endian}I", texture.header_flags3, - ) + (b'\0' * 16) + texture.raw + raw_texture = ( + struct.pack( + f"{self.endian}4sIIIHHIII", + magic, + texture.header_flags1, + texture.header_flags2, + 64 + len(texture.raw), + texture.width, + texture.height, + fmtflags, + 0, + 0, + ) + + (b"\0" * 12) + + struct.pack( + f"{self.endian}I", + texture.header_flags3, + ) + + (b"\0" * 16) + + texture.raw + ) if self.legacy_lz: raise Exception("We don't support legacy lz mode!") @@ -1202,7 +1351,8 @@ class TXP2File(TrackedCoverage, VerboseOutput): ">II", len(raw_texture), len(compressed_texture), - ) + compressed_texture, + ) + + compressed_texture, ) else: # We just need to place the raw texture down. @@ -1213,7 +1363,8 @@ class TXP2File(TrackedCoverage, VerboseOutput): ">II", len(raw_texture), len(raw_texture), - ) + raw_texture, + ) + + raw_texture, ) # Now, make sure the texture block is padded to 4 bytes, so we can figure out @@ -1252,7 +1403,9 @@ class TXP2File(TrackedCoverage, VerboseOutput): body = pad(body, offset) # First, lay down pointers and length, regardless of number of entries. - bitchunks[3] = struct.pack(f"{self.endian}II", len(self.texture_to_region), offset) + bitchunks[3] = struct.pack( + f"{self.endian}II", len(self.texture_to_region), offset + ) for bounds in self.texture_to_region: body += struct.pack( @@ -1286,7 +1439,10 @@ class TXP2File(TrackedCoverage, VerboseOutput): string_offset += len(entry1.name) + 1 # Write out the chunk itself. - body += struct.pack(f"{self.endian}I", string_offsets[entry1.name]) + entry1.data + body += ( + struct.pack(f"{self.endian}I", string_offsets[entry1.name]) + + entry1.data + ) # Now, put down the strings that were new in this chunk. body = self.write_strings(body, pending_strings) @@ -1315,7 +1471,9 @@ class TXP2File(TrackedCoverage, VerboseOutput): # Now, calculate where we can put SWF data and their names. swfdata_offset = align(len(body) + (len(self.swfdata) * 12)) - string_offset = align(swfdata_offset + sum(align(len(a.data)) for a in self.swfdata)) + string_offset = align( + swfdata_offset + sum(align(len(a.data)) for a in self.swfdata) + ) swfdata = b"" # Now, lay them out. @@ -1350,7 +1508,9 @@ class TXP2File(TrackedCoverage, VerboseOutput): # Now, calculate where we can put shapes and their names. shape_offset = align(len(body) + (len(self.shapes) * 12)) - string_offset = align(shape_offset + sum(align(len(s.data)) for s in self.shapes)) + string_offset = align( + shape_offset + sum(align(len(s.data)) for s in self.shapes) + ) shapedata = b"" # Now, lay them out. @@ -1501,7 +1661,11 @@ class TXP2File(TrackedCoverage, VerboseOutput): body = pad(body, offset) + texture_data # Now, update the patch location to make sure we point at the texture data. - body = body[:fix_offset] + struct.pack(f"{self.endian}I", offset) + body[(fix_offset + 4):] + body = ( + body[:fix_offset] + + struct.pack(f"{self.endian}I", offset) + + body[(fix_offset + 4) :] + ) # Bit 0x40000 is for lz options. @@ -1521,7 +1685,7 @@ class TXP2File(TrackedCoverage, VerboseOutput): # Skip over padding to the body that we inserted specifically to track offsets # against the headers. - return header + bitfields + body[(header_length + 24):] + return header + bitfields + body[(header_length + 24) :] def update_texture(self, name: str, png_data: bytes) -> None: for texture in self.textures: @@ -1533,7 +1697,7 @@ class TXP2File(TrackedCoverage, VerboseOutput): raise Exception("Cannot update texture with different size!") # Now, get the raw image data. - img = img.convert('RGBA') + img = img.convert("RGBA") texture.img = img # Now, refresh the raw texture data for when we write it out. @@ -1563,7 +1727,9 @@ class TXP2File(TrackedCoverage, VerboseOutput): # Now, figure out if the PNG data we got is valid. sprite_img = Image.open(io.BytesIO(png_data)) - if sprite_img.width != ((region.right // 2) - (region.left // 2)) or sprite_img.height != ((region.bottom // 2) - (region.top // 2)): + if sprite_img.width != ( + (region.right // 2) - (region.left // 2) + ) or sprite_img.height != ((region.bottom // 2) - (region.top // 2)): raise Exception("Cannot update sprite with different size!") # Now, copy the data over and update the raw texture. @@ -1581,11 +1747,12 @@ class TXP2File(TrackedCoverage, VerboseOutput): struct.pack( f"{self.endian}H", ( - (((pixel[0] >> 3) & 0x1F) << 11) | - (((pixel[1] >> 2) & 0x3F) << 5) | - ((pixel[2] >> 3) & 0x1F) - ) - ) for pixel in texture.img.getdata() + (((pixel[0] >> 3) & 0x1F) << 11) + | (((pixel[1] >> 2) & 0x3F) << 5) + | ((pixel[2] >> 3) & 0x1F) + ), + ) + for pixel in texture.img.getdata() ) elif texture.fmt == 0x13: # 16-bit A1R5G55 texture format. @@ -1593,12 +1760,13 @@ class TXP2File(TrackedCoverage, VerboseOutput): struct.pack( f"{self.endian}H", ( - (0x8000 if pixel[3] >= 128 else 0x0000) | - (((pixel[0] >> 3) & 0x1F) << 10) | - (((pixel[1] >> 3) & 0x1F) << 5) | - ((pixel[2] >> 3) & 0x1F) - ) - ) for pixel in texture.img.getdata() + (0x8000 if pixel[3] >= 128 else 0x0000) + | (((pixel[0] >> 3) & 0x1F) << 10) + | (((pixel[1] >> 3) & 0x1F) << 5) + | ((pixel[2] >> 3) & 0x1F) + ), + ) + for pixel in texture.img.getdata() ) elif texture.fmt == 0x1F: # 16-bit 4-4-4-4 RGBA format. @@ -1606,12 +1774,13 @@ class TXP2File(TrackedCoverage, VerboseOutput): struct.pack( f"{self.endian}H", ( - ((pixel[2] >> 4) & 0xF) | - (((pixel[1] >> 4) & 0xF) << 4) | - (((pixel[0] >> 4) & 0xF) << 8) | - (((pixel[3] >> 4) & 0xF) << 12) - ) - ) for pixel in texture.img.getdata() + ((pixel[2] >> 4) & 0xF) + | (((pixel[1] >> 4) & 0xF) << 4) + | (((pixel[0] >> 4) & 0xF) << 8) + | (((pixel[3] >> 4) & 0xF) << 12) + ), + ) + for pixel in texture.img.getdata() ) elif texture.fmt == 0x20: # 32-bit RGBA format @@ -1622,10 +1791,13 @@ class TXP2File(TrackedCoverage, VerboseOutput): pixel[1], pixel[0], pixel[3], - ) for pixel in texture.img.getdata() + ) + for pixel in texture.img.getdata() ) else: - raise Exception(f"Unsupported format {hex(texture.fmt)} for texture {texture.name}") + raise Exception( + f"Unsupported format {hex(texture.fmt)} for texture {texture.name}" + ) # Make sure we don't use the old compressed data. texture.compressed = None diff --git a/bemani/format/afp/decompile.py b/bemani/format/afp/decompile.py index 4cde68a..8df006f 100644 --- a/bemani/format/afp/decompile.py +++ b/bemani/format/afp/decompile.py @@ -1,5 +1,16 @@ import os -from typing import Any, Dict, List, Sequence, Tuple, Set, Union, Optional, Callable, cast +from typing import ( + Any, + Dict, + List, + Sequence, + Tuple, + Set, + Union, + Optional, + Callable, + cast, +) from .types import ( AP2Action, @@ -78,7 +89,9 @@ from .util import VerboseOutput class ByteCode: # A list of bytecodes to execute. - def __init__(self, name: Optional[str], actions: Sequence[AP2Action], end_offset: int) -> None: + def __init__( + self, name: Optional[str], actions: Sequence[AP2Action], end_offset: int + ) -> None: self.name = name self.actions = list(actions) self.start_offset = self.actions[0].offset if actions else None @@ -89,20 +102,22 @@ class ByteCode: decompiler.decompile(verbose=verbose) code = decompiler.as_string(prefix=" " if self.name else "", verbose=verbose) if self.name: - opar = '{' - cpar = '}' - code = f"{self.name}(){os.linesep}{opar}{os.linesep}{code}{os.linesep}{cpar}" + opar = "{" + cpar = "}" + code = ( + f"{self.name}(){os.linesep}{opar}{os.linesep}{code}{os.linesep}{cpar}" + ) return code def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: - if kwargs.get('decompile_bytecode', False): + if kwargs.get("decompile_bytecode", False): return { - 'code': self.decompile(verbose=kwargs.get('verbose', False)), + "code": self.decompile(verbose=kwargs.get("verbose", False)), } else: return { - 'actions': [a.as_dict(*args, **kwargs) for a in self.actions], - 'end_offset': self.end_offset, + "actions": [a.as_dict(*args, **kwargs) for a in self.actions], + "end_offset": self.end_offset, } def __repr__(self) -> str: @@ -128,9 +143,13 @@ class ControlFlow: def is_last(self, offset: int) -> bool: return self.end == (offset + 1) - def split(self, offset: int, link: bool = False) -> Tuple["ControlFlow", "ControlFlow"]: + def split( + self, offset: int, link: bool = False + ) -> Tuple["ControlFlow", "ControlFlow"]: if not self.contains(offset): - raise Exception(f"Logic error, this ControlFlow does not contain offset {offset}") + raise Exception( + f"Logic error, this ControlFlow does not contain offset {offset}" + ) # First, make the second half that the first half will point to. second = ControlFlow( @@ -193,7 +212,9 @@ class MaybeStackEntry(Expression): return f"MaybeStackEntry({self.parent_stack_id})" def render(self, parent_prefix: str, nested: bool = False) -> str: - raise Exception("Logic error, a MaybeStackEntry should never make it to the render stage!") + raise Exception( + "Logic error, a MaybeStackEntry should never make it to the render stage!" + ) class MultiAction(ConvertedAction): @@ -211,7 +232,12 @@ ArbitraryOpcode = Union[AP2Action, ConvertedAction] class IntermediateIf(ConvertedAction): - def __init__(self, parent_action: Union[IfAction, IfExpr], true_statements: Sequence[Statement], false_statements: Sequence[Statement]) -> None: + def __init__( + self, + parent_action: Union[IfAction, IfExpr], + true_statements: Sequence[Statement], + false_statements: Sequence[Statement], + ) -> None: self.parent_action = parent_action self.true_statements = list(true_statements) self.false_statements = list(false_statements) @@ -226,23 +252,29 @@ class IntermediateIf(ConvertedAction): false_entries.extend([f" {s}" for s in str(action).split(os.linesep)]) if self.false_statements: - return os.linesep.join([ - f"if <{self.parent_action}> {{", - os.linesep.join(true_entries), - "} else {", - os.linesep.join(false_entries), - "}" - ]) + return os.linesep.join( + [ + f"if <{self.parent_action}> {{", + os.linesep.join(true_entries), + "} else {", + os.linesep.join(false_entries), + "}", + ] + ) else: - return os.linesep.join([ - f"if <{self.parent_action}> {{", - os.linesep.join(true_entries), - "}" - ]) + return os.linesep.join( + [f"if <{self.parent_action}> {{", os.linesep.join(true_entries), "}"] + ) class ByteCodeChunk: - def __init__(self, id: int, actions: Sequence[ArbitraryOpcode], next_chunks: List[int] = [], previous_chunks: List[int] = []) -> None: + def __init__( + self, + id: int, + actions: Sequence[ArbitraryOpcode], + next_chunks: List[int] = [], + previous_chunks: List[int] = [], + ) -> None: self.id = id self.actions = list(actions) self.next_chunks = next_chunks or [] @@ -253,17 +285,27 @@ class ByteCodeChunk: for action in self.actions: if isinstance(action, DefineFunction2Action): # Special case, since we will decompile this later, we don't want to print it now. - entries.append(f" {action.offset}: {AP2Action.action_to_name(action.opcode)}, Name: {action.name or ''}, Flags: {hex(action.flags)}") + entries.append( + f" {action.offset}: {AP2Action.action_to_name(action.opcode)}, Name: {action.name or ''}, Flags: {hex(action.flags)}" + ) else: entries.extend([f" {s}" for s in str(action).split(os.linesep)]) return ( - f"ByteCodeChunk({os.linesep}" + - f" ID: {self.id}{os.linesep}" + - (f" Previous Chunks: {', '.join(str(n) for n in self.previous_chunks)}{os.linesep}" if self.previous_chunks else f" Start Chunk{os.linesep}") + - f"{os.linesep.join(entries)}{os.linesep}" + - (f" Next Chunks: {', '.join(str(n) for n in self.next_chunks)}{os.linesep}" if self.next_chunks else f" End Chunk{os.linesep}") + - ")" + f"ByteCodeChunk({os.linesep}" + + f" ID: {self.id}{os.linesep}" + + ( + f" Previous Chunks: {', '.join(str(n) for n in self.previous_chunks)}{os.linesep}" + if self.previous_chunks + else f" Start Chunk{os.linesep}" + ) + + f"{os.linesep.join(entries)}{os.linesep}" + + ( + f" Next Chunks: {', '.join(str(n) for n in self.next_chunks)}{os.linesep}" + if self.next_chunks + else f" End Chunk{os.linesep}" + ) + + ")" ) @@ -276,7 +318,9 @@ class Loop: self.id = id # Calculate predecessors (who points into it) and successors (who we point out of). - ided_chunks: Dict[int, ArbitraryCodeChunk] = {chunk.id: chunk for chunk in chunks} + ided_chunks: Dict[int, ArbitraryCodeChunk] = { + chunk.id: chunk for chunk in chunks + } self.previous_chunks: List[int] = [] self.next_chunks: List[int] = [] @@ -297,17 +341,32 @@ class Loop: entries.extend([f" {s}" for s in str(chunk).split(os.linesep)]) return ( - f"Loop({os.linesep}" + - f" ID: {self.id}{os.linesep}" + - (f" Previous Chunks: {', '.join(str(n) for n in self.previous_chunks)}{os.linesep}" if self.previous_chunks else f" Start Chunk{os.linesep}") + - f"{os.linesep.join(entries)}{os.linesep}" + - (f" Next Chunks: {', '.join(str(n) for n in self.next_chunks)}{os.linesep}" if self.next_chunks else f" End Chunk{os.linesep}") + - ")" + f"Loop({os.linesep}" + + f" ID: {self.id}{os.linesep}" + + ( + f" Previous Chunks: {', '.join(str(n) for n in self.previous_chunks)}{os.linesep}" + if self.previous_chunks + else f" Start Chunk{os.linesep}" + ) + + f"{os.linesep.join(entries)}{os.linesep}" + + ( + f" Next Chunks: {', '.join(str(n) for n in self.next_chunks)}{os.linesep}" + if self.next_chunks + else f" End Chunk{os.linesep}" + ) + + ")" ) class IfBody: - def __init__(self, id: int, true_chunks: Sequence[ArbitraryCodeChunk], false_chunks: Sequence[ArbitraryCodeChunk], next_chunk: Optional[int], previous_chunk: int) -> None: + def __init__( + self, + id: int, + true_chunks: Sequence[ArbitraryCodeChunk], + false_chunks: Sequence[ArbitraryCodeChunk], + next_chunk: Optional[int], + previous_chunk: int, + ) -> None: # The ID in this case is what the previous block points at. It does not # have any bearing on the ID of the true and false chunks. self.id = id @@ -329,15 +388,23 @@ class IfBody: false_entries.extend([f" {s}" for s in str(chunk).split(os.linesep)]) return ( - f"IfBody({os.linesep}" + - f" ID: {self.id}{os.linesep}" + - (f" Previous Chunks: {', '.join(str(n) for n in self.previous_chunks)}{os.linesep}" if self.previous_chunks else f" Start Chunk{os.linesep}") + - f" True Chunks:{os.linesep}" + - f"{os.linesep.join(true_entries)}{os.linesep}" + - f" False Chunks:{os.linesep}" + - f"{os.linesep.join(false_entries)}{os.linesep}" + - (f" Next Chunks: {', '.join(str(n) for n in self.next_chunks)}{os.linesep}" if self.next_chunks else f" End Chunk{os.linesep}") + - ")" + f"IfBody({os.linesep}" + + f" ID: {self.id}{os.linesep}" + + ( + f" Previous Chunks: {', '.join(str(n) for n in self.previous_chunks)}{os.linesep}" + if self.previous_chunks + else f" Start Chunk{os.linesep}" + ) + + f" True Chunks:{os.linesep}" + + f"{os.linesep.join(true_entries)}{os.linesep}" + + f" False Chunks:{os.linesep}" + + f"{os.linesep.join(false_entries)}{os.linesep}" + + ( + f" Next Chunks: {', '.join(str(n) for n in self.next_chunks)}{os.linesep}" + if self.next_chunks + else f" End Chunk{os.linesep}" + ) + + ")" ) @@ -349,7 +416,9 @@ class InsertionLocation(Statement): return f"" def render(self, prefix: str, verbose: bool = False) -> List[str]: - raise Exception("Logic error, an InsertionLocation should never make it to the render stage!") + raise Exception( + "Logic error, an InsertionLocation should never make it to the render stage!" + ) class OriginalCallLocation(Statement): @@ -360,7 +429,9 @@ class OriginalCallLocation(Statement): return f"" def render(self, prefix: str, verbose: bool = False) -> List[str]: - raise Exception("Logic error, an InsertionLocation should never make it to the render stage!") + raise Exception( + "Logic error, an InsertionLocation should never make it to the render stage!" + ) class BitVector: @@ -378,25 +449,33 @@ class BitVector: def setBit(self, bit: int) -> "BitVector": if bit < 0 or bit >= len(self.__bits): - raise Exception(f"Logic error, trying to set bit {bit} of a bitvector length {len(self.__bits)}!") + raise Exception( + f"Logic error, trying to set bit {bit} of a bitvector length {len(self.__bits)}!" + ) self.__bits[bit] = True return self def clearBit(self, bit: int) -> "BitVector": if bit < 0 or bit >= len(self.__bits): - raise Exception(f"Logic error, trying to set bit {bit} of a bitvector length {len(self.__bits)}!") + raise Exception( + f"Logic error, trying to set bit {bit} of a bitvector length {len(self.__bits)}!" + ) self.__bits[bit] = False return self def orVector(self, other: "BitVector") -> "BitVector": if len(self.__bits) != len(other.__bits): - raise Exception(f"Logic error, trying to combine bitvector of size {len(self.__bits)} with another of size {len(other.__bits)}!") + raise Exception( + f"Logic error, trying to combine bitvector of size {len(self.__bits)} with another of size {len(other.__bits)}!" + ) self.__bits = {i: (self.__bits[i] or other.__bits[i]) for i in self.__bits} return self def andVector(self, other: "BitVector") -> "BitVector": if len(self.__bits) != len(other.__bits): - raise Exception(f"Logic error, trying to combine bitvector of size {len(self.__bits)} with another of size {len(other.__bits)}!") + raise Exception( + f"Logic error, trying to combine bitvector of size {len(self.__bits)} with another of size {len(other.__bits)}!" + ) self.__bits = {i: (self.__bits[i] and other.__bits[i]) for i in self.__bits} return self @@ -404,7 +483,9 @@ class BitVector: if not isinstance(other, BitVector): return NotImplemented if len(self.__bits) != len(other.__bits): - raise Exception(f"Logic error, trying to compare bitvector of size {len(self.__bits)} with another of size {len(other.__bits)}!") + raise Exception( + f"Logic error, trying to compare bitvector of size {len(self.__bits)} with another of size {len(other.__bits)}!" + ) for i in self.__bits: if self.__bits[i] != other.__bits[i]: @@ -439,7 +520,9 @@ class ByteCodeDecompiler(VerboseOutput): raise Exception("Call decompile() first before retrieving statements!") return self.__statements - def _graph_control_flow(self, bytecode: ByteCode) -> Tuple[List[ByteCodeChunk], Dict[int, int]]: + def _graph_control_flow( + self, bytecode: ByteCode + ) -> Tuple[List[ByteCodeChunk], Dict[int, int]]: # Start by assuming that the whole bytecode never directs flow. This is, confusingly, # indexed by AP2Action offset, not by actual bytecode offset, so we can avoid the # prickly problem of opcodes that take more than one byte in the data. @@ -459,7 +542,9 @@ class ByteCodeDecompiler(VerboseOutput): if cf.contains(opcodeno): return start - raise Exception(f"Logic error, offset {opcodeno} somehow not in our control flow graph!") + raise Exception( + f"Logic error, offset {opcodeno} somehow not in our control flow graph!" + ) # Now, walk the entire bytecode, and every control flow point split the graph at that point. for i, action in enumerate(bytecode.actions): @@ -477,7 +562,9 @@ class ByteCodeDecompiler(VerboseOutput): first, second = flows[current_action_flow].split(next_action) first.next_flow = [end] - self.vprint(f"{action} action split {flows[current_action_flow]} into {first}, {second}") + self.vprint( + f"{action} action split {flows[current_action_flow]} into {first}, {second}" + ) flows[current_action_flow] = first flows[next_action] = second @@ -489,7 +576,9 @@ class ByteCodeDecompiler(VerboseOutput): # link it to the end. flows[current_action_flow].next_flow = [end] - self.vprint(f"{action} action repointed {flows[current_action_flow]} to end") + self.vprint( + f"{action} action repointed {flows[current_action_flow]} to end" + ) elif action.opcode == AP2Action.JUMP: # Unconditional control flow redirection after this, we should split the # section if necessary and point this section at the new offset. @@ -504,7 +593,9 @@ class ByteCodeDecompiler(VerboseOutput): if action.jump_offset == bytecode.end_offset: dest_action = end else: - raise Exception(f"{action} jumps to an opcode that doesn't exist!") + raise Exception( + f"{action} jumps to an opcode that doesn't exist!" + ) # If the destination action flow already starts with the jump offset, # then we're good, we just need to point our current split at this new @@ -512,9 +603,13 @@ class ByteCodeDecompiler(VerboseOutput): # that flow so we can point to the opcode directly. dest_action_flow = find(dest_action) if not flows[dest_action_flow].is_first(dest_action): - first, second = flows[dest_action_flow].split(dest_action, link=True) + first, second = flows[dest_action_flow].split( + dest_action, link=True + ) - self.vprint(f"{action} action required split of {flows[dest_action_flow]} into {first, second}") + self.vprint( + f"{action} action required split of {flows[dest_action_flow]} into {first, second}" + ) flows[dest_action_flow] = first flows[dest_action] = second @@ -531,7 +626,9 @@ class ByteCodeDecompiler(VerboseOutput): first, second = flows[current_action_flow].split(next_action) first.next_flow = [dest_action_flow] - self.vprint(f"{action} action split {flows[current_action_flow]} into {first}, {second}") + self.vprint( + f"{action} action split {flows[current_action_flow]} into {first}, {second}" + ) flows[current_action_flow] = first flows[next_action] = second @@ -542,7 +639,9 @@ class ByteCodeDecompiler(VerboseOutput): # link it to the end. flows[current_action_flow].next_flow = [dest_action_flow] - self.vprint(f"{action} action repointed {flows[current_action_flow]} to new chunk") + self.vprint( + f"{action} action repointed {flows[current_action_flow]} to new chunk" + ) elif action.opcode == AP2Action.IF: # Conditional control flow redirection after this, we should split the # section if necessary and point this section at the new offset as well @@ -558,7 +657,9 @@ class ByteCodeDecompiler(VerboseOutput): if action.jump_if_true_offset == bytecode.end_offset: dest_action = end else: - raise Exception(f"{action} conditionally jumps to an opcode that doesn't exist!") + raise Exception( + f"{action} conditionally jumps to an opcode that doesn't exist!" + ) # If the destination action flow already starts with the jump offset, # then we're good, we just need to point our current split at this new @@ -566,9 +667,13 @@ class ByteCodeDecompiler(VerboseOutput): # that flow so we can point to the opcode directly. dest_action_flow = find(dest_action) if not flows[dest_action_flow].is_first(dest_action): - first, second = flows[dest_action_flow].split(dest_action, link=True) + first, second = flows[dest_action_flow].split( + dest_action, link=True + ) - self.vprint(f"{action} action required split of {flows[dest_action_flow]} into {first, second}") + self.vprint( + f"{action} action required split of {flows[dest_action_flow]} into {first, second}" + ) flows[dest_action_flow] = first flows[dest_action] = second @@ -585,7 +690,9 @@ class ByteCodeDecompiler(VerboseOutput): first, second = flows[current_action_flow].split(next_action) first.next_flow = [next_action, dest_action_flow] - self.vprint(f"{action} action split {flows[current_action_flow]} into {first}, {second}") + self.vprint( + f"{action} action split {flows[current_action_flow]} into {first}, {second}" + ) flows[current_action_flow] = first flows[next_action] = second @@ -594,12 +701,19 @@ class ByteCodeDecompiler(VerboseOutput): # earlier in the chain jumping to the opcode after this. # We need to unlink the current flow from the second and # link it to the end. - flows[current_action_flow].next_flow = [next_action, dest_action_flow] + flows[current_action_flow].next_flow = [ + next_action, + dest_action_flow, + ] - self.vprint(f"{action} action repointed {flows[current_action_flow]} to new chunk") + self.vprint( + f"{action} action repointed {flows[current_action_flow]} to new chunk" + ) elif action.opcode == AP2Action.IF2: # We don't emit this anymore, so this is a problem. - raise Exception("Logic error, unexpected AP2Action.IF2 opcode which we should not emit in parsing stage!") + raise Exception( + "Logic error, unexpected AP2Action.IF2 opcode which we should not emit in parsing stage!" + ) # Finally, return chunks of contiguous execution. chunks: List[ByteCodeChunk] = [] @@ -617,7 +731,13 @@ class ByteCodeDecompiler(VerboseOutput): next_chunks.append(bytecode.end_offset) else: next_chunks.append(bytecode.actions[ano].offset) - chunks.append(ByteCodeChunk(bytecode.actions[flow.beginning].offset, bytecode.actions[flow.beginning:flow.end], next_chunks)) + chunks.append( + ByteCodeChunk( + bytecode.actions[flow.beginning].offset, + bytecode.actions[flow.beginning : flow.end], + next_chunks, + ) + ) # Calculate who points to us as well, for posterity. We can still use chunk.id as # the offset of the chunk since we haven't converted yet. @@ -634,17 +754,27 @@ class ByteCodeDecompiler(VerboseOutput): # Now, eliminate any dead code since it will trip us up later. Chunk ID is still the # offset of the first entry in the chunk since we haven't assigned IDs yet. while True: - dead_chunk_ids = {c.id for c in chunks if not c.previous_chunks and c.id != bytecode.start_offset} + dead_chunk_ids = { + c.id + for c in chunks + if not c.previous_chunks and c.id != bytecode.start_offset + } if dead_chunk_ids: - self.vprint(f"Elimitating dead code chunks {', '.join(str(d) for d in dead_chunk_ids)}") + self.vprint( + f"Elimitating dead code chunks {', '.join(str(d) for d in dead_chunk_ids)}" + ) chunks = [c for c in chunks if c.id not in dead_chunk_ids] for chunk in chunks: for c in chunk.next_chunks: if c in dead_chunk_ids: # Hoo this shouldn't be possible! - raise Exception(f"Logic error, chunk ID {chunk.id} points at a dead code chunk we're eliminating!") - chunk.previous_chunks = [c for c in chunk.previous_chunks if c not in dead_chunk_ids] + raise Exception( + f"Logic error, chunk ID {chunk.id} points at a dead code chunk we're eliminating!" + ) + chunk.previous_chunks = [ + c for c in chunk.previous_chunks if c not in dead_chunk_ids + ] else: break @@ -679,7 +809,9 @@ class ByteCodeDecompiler(VerboseOutput): chunk.previous_chunks = [offset_to_id[c] for c in chunk.previous_chunks] # Add the "return" chunk now that we've converted everything. - chunks.append(ByteCodeChunk(end_chunk_id, [], [], previous_chunks=end_previous_chunks)) + chunks.append( + ByteCodeChunk(end_chunk_id, [], [], previous_chunks=end_previous_chunks) + ) # Verify a few invariants about the tree we just created. num_start_chunks = 0 @@ -689,28 +821,51 @@ class ByteCodeDecompiler(VerboseOutput): num_end_chunks += 1 if not chunk.previous_chunks: if bytecode.start_offset is None: - raise Exception("Logic error, expected a start offset for bytecode chunk, we shouldn't be decompiling empty bytecode!") + raise Exception( + "Logic error, expected a start offset for bytecode chunk, we shouldn't be decompiling empty bytecode!" + ) if chunk.id != offset_to_id[bytecode.start_offset]: - raise Exception(f"Start of graph found at ID {chunk.id} but expected to be {offset_to_id[bytecode.start_offset]}!") + raise Exception( + f"Start of graph found at ID {chunk.id} but expected to be {offset_to_id[bytecode.start_offset]}!" + ) num_start_chunks += 1 if chunk.actions: # We haven't done any fixing up, we're guaranteed this is an AP2Action. last_action = cast(AP2Action, chunk.actions[-1]) - if last_action.opcode in [AP2Action.THROW, AP2Action.RETURN, AP2Action.JUMP, AP2Action.END] and len(chunk.next_chunks) != 1: - raise Exception(f"Chunk ID {chunk.id} has control flow action expecting one next chunk but has {len(chunk.next_chunks)}!") + if ( + last_action.opcode + in [ + AP2Action.THROW, + AP2Action.RETURN, + AP2Action.JUMP, + AP2Action.END, + ] + and len(chunk.next_chunks) != 1 + ): + raise Exception( + f"Chunk ID {chunk.id} has control flow action expecting one next chunk but has {len(chunk.next_chunks)}!" + ) if len(chunk.next_chunks) == 2 and last_action.opcode != AP2Action.IF: - raise Exception(f"Chunk ID {chunk.id} has two next chunks but control flow action is not an if statement!") + raise Exception( + f"Chunk ID {chunk.id} has two next chunks but control flow action is not an if statement!" + ) if len(chunk.next_chunks) > 2: - raise Exception(f"Chunk ID {chunk.id} has more than two next chunks!") + raise Exception( + f"Chunk ID {chunk.id} has more than two next chunks!" + ) # Num start chunks can be 0 (if the start chunk is a loop beginning) or 1 (if its a normal chunk). if num_start_chunks > 1: - raise Exception(f"Found {num_start_chunks} start chunks but expecting at most 1!") + raise Exception( + f"Found {num_start_chunks} start chunks but expecting at most 1!" + ) # Num end chunks can only be 1 as we created an artificial end chunk. if num_end_chunks != 1: - raise Exception(f"Found {num_end_chunks} end chunks but expecting exactly 1!") + raise Exception( + f"Found {num_end_chunks} end chunks but expecting exactly 1!" + ) # Now that we're satisfied with the tree we created, return it. return (chunks, offset_to_id) @@ -730,17 +885,23 @@ class ByteCodeDecompiler(VerboseOutput): raise Exception("Logic error, no start block found!") return start_id - def __compute_dominators(self, start_id: int, chunks: Sequence[ByteCodeChunk]) -> Dict[int, Set[int]]: + def __compute_dominators( + self, start_id: int, chunks: Sequence[ByteCodeChunk] + ) -> Dict[int, Set[int]]: # Compute dominators recursively chunklen = len(chunks) - dominators: Dict[int, BitVector] = {chunk.id: BitVector(chunklen, init=True) for chunk in chunks} + dominators: Dict[int, BitVector] = { + chunk.id: BitVector(chunklen, init=True) for chunk in chunks + } dominators[start_id].setAllBitsTo(False).setBit(start_id) # Verify that the chunk IDs are contiguous. Otherwise this algorithm fails, since it # assigns an integer ID to each bit in a bitfield contiguously. for chunk in chunks: if chunk.id < 0 or chunk.id >= len(chunks): - raise Exception("Chunk ID {chunk.id} is outside of our created BitVector, the ID space of chunks is non-contiguous!") + raise Exception( + "Chunk ID {chunk.id} is outside of our created BitVector, the ID space of chunks is non-contiguous!" + ) changed = True while changed: @@ -768,14 +929,20 @@ class ByteCodeDecompiler(VerboseOutput): header_chunks = [c for c in loop.chunks if c.id == loop.id] if len(header_chunks) != 1: # Should never happen, only one should match ID. - raise Exception("Logic error, didn't find the header chunk based on Loop ID!") + raise Exception( + "Logic error, didn't find the header chunk based on Loop ID!" + ) header_chunk = header_chunks[0] # Identify external jumps from the header. - break_points = [i for i in header_chunk.next_chunks if i not in internal_jump_points] + break_points = [ + i for i in header_chunk.next_chunks if i not in internal_jump_points + ] if len(break_points) > 1: # We should not have two exits here, if so this isn't a loop! - raise Exception("Logic error, loop has more than one next chunk to jump to on break!") + raise Exception( + "Logic error, loop has more than one next chunk to jump to on break!" + ) if not break_points: # This might be possible, but I don't know how to deal with it. raise Exception("Logic error, loop has no chunk to jump to on break!") @@ -784,7 +951,9 @@ class ByteCodeDecompiler(VerboseOutput): break_point = break_points[0] continue_point = header_chunk.id - self.vprint(f"Loop ID {loop.id} breaks to {break_point} and continues to {continue_point}") + self.vprint( + f"Loop ID {loop.id} breaks to {break_point} and continues to {continue_point}" + ) # Now, go through each chunk, identify whether it has an if, and fix up the # if statements. @@ -792,7 +961,9 @@ class ByteCodeDecompiler(VerboseOutput): if not chunk.next_chunks: # All chunks need a next chunk of some type, the only one that doesn't # is the end chunk which should never be part of a loop. - raise Exception(f"Logic error, chunk ID {chunk.id} has no successor and we haven't broken the graph yet!") + raise Exception( + f"Logic error, chunk ID {chunk.id} has no successor and we haven't broken the graph yet!" + ) if not isinstance(chunk, ByteCodeChunk): # We don't need to fix up loops, we already did this in a previous # fixup. @@ -804,30 +975,42 @@ class ByteCodeDecompiler(VerboseOutput): # This is either an unconditional break/continue or an # internal jump. if len(chunk.next_chunks) != 1: - raise Exception(f"Logic error, chunk ID {chunk.id} has jump control action but {len(chunk.next_chunks)} next chunks!") + raise Exception( + f"Logic error, chunk ID {chunk.id} has jump control action but {len(chunk.next_chunks)} next chunks!" + ) next_chunk = chunk.next_chunks[0] if next_chunk == break_point: - self.vprint("Converting jump to loop break into break statement.") + self.vprint( + "Converting jump to loop break into break statement." + ) chunk.actions[-1] = BreakStatement() chunk.next_chunks = [] elif next_chunk == continue_point: - self.vprint("Converting jump to loop continue into continue statement.") + self.vprint( + "Converting jump to loop continue into continue statement." + ) chunk.actions[-1] = ContinueStatement() chunk.next_chunks = [] elif next_chunk not in internal_jump_points: if next_chunk == offset_map[self.bytecode.end_offset]: - self.vprint("Converting jump to external point into return statement.") + self.vprint( + "Converting jump to external point into return statement." + ) chunk.actions[-1] = NullReturnStatement() else: - self.vprint("Converting jump to external point into goto statement.") + self.vprint( + "Converting jump to external point into goto statement." + ) chunk.actions[-1] = GotoStatement(next_chunk) chunk.next_chunks = [] continue if last_action.opcode == AP2Action.IF: # Calculate true and false jump points. - true_jump_point, false_jump_point = self.__get_jump_points(chunk, offset_map) + true_jump_point, false_jump_point = self.__get_jump_points( + chunk, offset_map + ) end_offset = offset_map[self.bytecode.end_offset] # Calculate true and false jump points, see if they are break/continue/goto. @@ -837,39 +1020,67 @@ class ByteCodeDecompiler(VerboseOutput): # continue or return statement). true_action: Optional[Statement] = None if true_jump_point == break_point: - self.vprint("Converting jump if true to loop break into break statement.") + self.vprint( + "Converting jump if true to loop break into break statement." + ) true_action = BreakStatement() - chunk.next_chunks = [n for n in chunk.next_chunks if n != true_jump_point] + chunk.next_chunks = [ + n for n in chunk.next_chunks if n != true_jump_point + ] elif true_jump_point == continue_point: - self.vprint("Converting jump if true to loop continue into continue statement.") + self.vprint( + "Converting jump if true to loop continue into continue statement." + ) true_action = ContinueStatement() - chunk.next_chunks = [n for n in chunk.next_chunks if n != true_jump_point] + chunk.next_chunks = [ + n for n in chunk.next_chunks if n != true_jump_point + ] elif true_jump_point not in internal_jump_points: if true_jump_point == end_offset: - self.vprint("Converting jump if true to external point into return statement.") + self.vprint( + "Converting jump if true to external point into return statement." + ) true_action = NullReturnStatement() else: - self.vprint("Converting jump if true to external point into goto statement.") + self.vprint( + "Converting jump if true to external point into goto statement." + ) true_action = GotoStatement(true_jump_point) - chunk.next_chunks = [n for n in chunk.next_chunks if n != true_jump_point] + chunk.next_chunks = [ + n for n in chunk.next_chunks if n != true_jump_point + ] false_action: Optional[Statement] = None if false_jump_point == break_point: - self.vprint("Converting jump if false to loop break into break statement.") + self.vprint( + "Converting jump if false to loop break into break statement." + ) false_action = BreakStatement() - chunk.next_chunks = [n for n in chunk.next_chunks if n != false_jump_point] + chunk.next_chunks = [ + n for n in chunk.next_chunks if n != false_jump_point + ] elif false_jump_point == continue_point: - self.vprint("Converting jump if false to loop continue into continue statement.") + self.vprint( + "Converting jump if false to loop continue into continue statement." + ) false_action = ContinueStatement() - chunk.next_chunks = [n for n in chunk.next_chunks if n != false_jump_point] + chunk.next_chunks = [ + n for n in chunk.next_chunks if n != false_jump_point + ] elif false_jump_point not in internal_jump_points: if false_jump_point == end_offset: - self.vprint("Converting jump if false to external point into return statement.") + self.vprint( + "Converting jump if false to external point into return statement." + ) false_action = NullReturnStatement() else: - self.vprint("Converting jump if false to external point into goto statement.") + self.vprint( + "Converting jump if false to external point into goto statement." + ) false_action = GotoStatement(false_jump_point) - chunk.next_chunks = [n for n in chunk.next_chunks if n != false_jump_point] + chunk.next_chunks = [ + n for n in chunk.next_chunks if n != false_jump_point + ] if true_action is None and false_action is None: # This is an internal-only if statement, we don't care. We will handle it in @@ -882,11 +1093,19 @@ class ByteCodeDecompiler(VerboseOutput): [false_action] if false_action else [], ) - if last_action.opcode in [AP2Action.RETURN, AP2Action.THROW, AP2Action.END]: + if last_action.opcode in [ + AP2Action.RETURN, + AP2Action.THROW, + AP2Action.END, + ]: if len(chunk.next_chunks) != 1: - raise Exception(f"Logic error, chunkd ID {chunk.id} returns, throws or end to multiple blocks!") + raise Exception( + f"Logic error, chunkd ID {chunk.id} returns, throws or end to multiple blocks!" + ) if chunk.next_chunks[0] != offset_map[self.bytecode.end_offset]: - raise Exception(f"Expected chunk ID {chunk.id} to jump to return block but jumped elsewhere!") + raise Exception( + f"Expected chunk ID {chunk.id} to jump to return block but jumped elsewhere!" + ) # We will convert this later. self.vprint("Severing link to return address.") chunk.next_chunks = [] @@ -895,19 +1114,39 @@ class ByteCodeDecompiler(VerboseOutput): for chunk in loop.chunks: for n in chunk.next_chunks: if n not in internal_jump_points: - raise Exception(f"Found unconverted next chunk {n} in chunk ID {chunk.id}, for loop ID {loop.id} with break point {break_point}!") + raise Exception( + f"Found unconverted next chunk {n} in chunk ID {chunk.id}, for loop ID {loop.id} with break point {break_point}!" + ) if isinstance(chunk, ByteCodeChunk): last_action = chunk.actions[-1] if isinstance(last_action, AP2Action): - if last_action.opcode == AP2Action.IF and len(chunk.next_chunks) != 2: - raise Exception(f"Somehow messed up the next pointers on if statement in chunk ID {chunk.id}!") - if last_action.opcode == AP2Action.JUMP and len(chunk.next_chunks) != 1: - raise Exception(f"Somehow messed up the next pointers on control flow statement in chunk ID {chunk.id}!") - if last_action.opcode in [AP2Action.RETURN, AP2Action.THROW, AP2Action.END] and len(chunk.next_chunks) != 0: - raise Exception(f"Somehow messed up the next pointers on control flow statement in chunk ID {chunk.id}!") + if ( + last_action.opcode == AP2Action.IF + and len(chunk.next_chunks) != 2 + ): + raise Exception( + f"Somehow messed up the next pointers on if statement in chunk ID {chunk.id}!" + ) + if ( + last_action.opcode == AP2Action.JUMP + and len(chunk.next_chunks) != 1 + ): + raise Exception( + f"Somehow messed up the next pointers on control flow statement in chunk ID {chunk.id}!" + ) + if ( + last_action.opcode + in [AP2Action.RETURN, AP2Action.THROW, AP2Action.END] + and len(chunk.next_chunks) != 0 + ): + raise Exception( + f"Somehow messed up the next pointers on control flow statement in chunk ID {chunk.id}!" + ) else: if len(chunk.next_chunks) > 1: - raise Exception(f"Somehow messed up the next pointers on converted statement in chunk ID {chunk.id}!") + raise Exception( + f"Somehow messed up the next pointers on converted statement in chunk ID {chunk.id}!" + ) # Now, we have converted all external jumps to either break or goto, so we don't # need to keep track of the next chunk aside from the break location. We know this @@ -924,7 +1163,9 @@ class ByteCodeDecompiler(VerboseOutput): dominators: Dict[int, Set[int]], offset_map: Dict[int, int], ) -> List[Union[ByteCodeChunk, Loop]]: - chunks_by_id: Dict[int, Union[ByteCodeChunk, Loop]] = {chunk.id: chunk for chunk in chunks} + chunks_by_id: Dict[int, Union[ByteCodeChunk, Loop]] = { + chunk.id: chunk for chunk in chunks + } # Go through and gather up all loops in the chunks. loops: Dict[int, Set[int]] = {} @@ -951,7 +1192,9 @@ class ByteCodeDecompiler(VerboseOutput): blocks.add(predecessor) blocks_to_examine.append(predecessor) - self.vprint(f"Found loop with header {header} and blocks {', '.join(str(b) for b in blocks)}.") + self.vprint( + f"Found loop with header {header} and blocks {', '.join(str(b) for b in blocks)}." + ) # Now, make sure we scoop up any remaining if/else bodies not found in the backwards walk. changed: bool = True @@ -965,18 +1208,26 @@ class ByteCodeDecompiler(VerboseOutput): continue add_id: Optional[int] = None for cid, doms in dominators.items(): - if dominators[b] == doms - {cid} and cid not in blocks and cid != header: + if ( + dominators[b] == doms - {cid} + and cid not in blocks + and cid != header + ): add_id = cid break if add_id is not None: - self.vprint(f"Chunk {cid} should be included in loop list!") + self.vprint( + f"Chunk {cid} should be included in loop list!" + ) blocks.add(add_id) changed = True break # We found a loop! if header in loops: - raise Exception(f"Logic error, loop with header {header} was already found!") + raise Exception( + f"Logic error, loop with header {header} was already found!" + ) loops[header] = blocks # Now, we need to reduce our list of chunks down to non-loops only. We do this @@ -995,12 +1246,16 @@ class ByteCodeDecompiler(VerboseOutput): # This particular block of code is the header of another loop, # so we shouldn't convert this loop until we handle the inner # loop. - self.vprint(f"Skipping loop with header {header} for now because it contains another unconverted loop with header {block}.") + self.vprint( + f"Skipping loop with header {header} for now because it contains another unconverted loop with header {block}." + ) break else: # This loop does not contain any loops of its own. It is safe to # convert. - self.vprint(f"Converting loop with header {header} and blocks {', '.join(str(b) for b in blocks)}.") + self.vprint( + f"Converting loop with header {header} and blocks {', '.join(str(b) for b in blocks)}." + ) new_loop = Loop(header, [chunks_by_id[i] for i in blocks]) # Eliminate jumps that are to the beginning/end of the loop to @@ -1009,7 +1264,9 @@ class ByteCodeDecompiler(VerboseOutput): # to a break/continue/goto. new_loop = self.__analyze_loop_jumps(new_loop, offset_map) if len(new_loop.next_chunks) != 1: - raise Exception(f"Newly created loop ID {new_loop.id} has more than one exit point!") + raise Exception( + f"Newly created loop ID {new_loop.id} has more than one exit point!" + ) chunks_by_id[header] = new_loop # These blocks are now part of the loop, so we need to remove them @@ -1020,13 +1277,18 @@ class ByteCodeDecompiler(VerboseOutput): if delete_header is None: # We must find at LEAST one loop that has no inner loops of its own. - raise Exception("Logic error, we found no fixable loops, yet have at least one loop to fix up!") + raise Exception( + "Logic error, we found no fixable loops, yet have at least one loop to fix up!" + ) # Remove this loop from the processing list del loops[delete_header] # Go through and remove the rest of the chunks from the rest of the loops - loops = {header: {block for block in blocks if block not in delete_blocks} for (header, blocks) in loops.items()} + loops = { + header: {block for block in blocks if block not in delete_blocks} + for (header, blocks) in loops.items() + } # Also remove the rest of the chunks from our IDed chunks as they are part of this loop now. for block in delete_blocks: @@ -1037,7 +1299,9 @@ class ByteCodeDecompiler(VerboseOutput): for nextid in chunk_or_loop.next_chunks: if nextid in delete_blocks: # Woah, we point at a chunk inside this loop that isn't the header! - raise Exception(f"Logic error, chunkd ID {chunk_id} points into loop ID {delete_header} body!") + raise Exception( + f"Logic error, chunkd ID {chunk_id} points into loop ID {delete_header} body!" + ) # Update our master list of chunks we deleted. deleted_chunks.update(delete_blocks) @@ -1046,10 +1310,14 @@ class ByteCodeDecompiler(VerboseOutput): updated_chunks = [chunks_by_id[i] for i in chunks_by_id] for new_chunk in updated_chunks: if new_chunk.id in deleted_chunks: - raise Exception(f"Chunk ID {new_chunk.id} in list of chunks we converted but we expected it to be deleted!") + raise Exception( + f"Chunk ID {new_chunk.id} in list of chunks we converted but we expected it to be deleted!" + ) return updated_chunks - def __get_jump_points(self, chunk: ByteCodeChunk, offset_map: Dict[int, int]) -> Tuple[int, int]: + def __get_jump_points( + self, chunk: ByteCodeChunk, offset_map: Dict[int, int] + ) -> Tuple[int, int]: action = chunk.actions[-1] if isinstance(action, IfAction): @@ -1057,7 +1325,9 @@ class ByteCodeDecompiler(VerboseOutput): false_jump_points = [n for n in chunk.next_chunks if n != true_jump_point] if len(false_jump_points) != 1: if chunk.next_chunks[0] != chunk.next_chunks[1]: - raise Exception(f"Logic error, got more than one false jump point for if statement in chunk {chunk.id}") + raise Exception( + f"Logic error, got more than one false jump point for if statement in chunk {chunk.id}" + ) else: false_jump_point = true_jump_point else: @@ -1065,9 +1335,13 @@ class ByteCodeDecompiler(VerboseOutput): return true_jump_point, false_jump_point else: - raise Exception(f"Logic error, expecting JumpAction but got {action} in chunk {chunk.id}!") + raise Exception( + f"Logic error, expecting JumpAction but got {action} in chunk {chunk.id}!" + ) - def __break_graph(self, chunks: Sequence[Union[ByteCodeChunk, Loop]], offset_map: Dict[int, int]) -> None: + def __break_graph( + self, chunks: Sequence[Union[ByteCodeChunk, Loop]], offset_map: Dict[int, int] + ) -> None: for chunk in chunks: if chunk.id == offset_map[self.bytecode.end_offset]: # Don't examine the sentinel we keep around as a jump point for returns. @@ -1078,35 +1352,56 @@ class ByteCodeDecompiler(VerboseOutput): # At this point, we know chunk.chunks is a Union[ByteCodeChunk, Loop] because we haven't run # any if detection yet. - self.__break_graph(cast(List[Union[ByteCodeChunk, Loop]], chunk.chunks), offset_map) + self.__break_graph( + cast(List[Union[ByteCodeChunk, Loop]], chunk.chunks), offset_map + ) else: # Examine the last instruction. last_action = chunk.actions[-1] if isinstance(last_action, AP2Action): - if last_action.opcode in [AP2Action.THROW, AP2Action.RETURN, AP2Action.END]: + if last_action.opcode in [ + AP2Action.THROW, + AP2Action.RETURN, + AP2Action.END, + ]: # The last action already dictates what we should do here. Break # the chain at this point. - self.vprint(f"Breaking chain on {chunk.id} because it is a {last_action}.") + self.vprint( + f"Breaking chain on {chunk.id} because it is a {last_action}." + ) chunk.next_chunks = [] - elif len(chunk.next_chunks) == 1 and chunk.next_chunks[0] == offset_map[self.bytecode.end_offset]: + elif ( + len(chunk.next_chunks) == 1 + and chunk.next_chunks[0] == offset_map[self.bytecode.end_offset] + ): # The jump point for this is the end of the function. If it is a jump, # then we should replace it with a return. If it is not a jump, we should # add a return. if last_action.opcode == AP2Action.JUMP: - self.vprint(f"Converting jump to end of code in {chunk.id} into a null return.") + self.vprint( + f"Converting jump to end of code in {chunk.id} into a null return." + ) chunk.actions[-1] = NullReturnStatement() else: if last_action.opcode == AP2Action.IF: - raise Exception(f"Logic error, unexpected if statement with only one successor in {chunk.id}!") - self.vprint(f"Converting fall-through to end of code in {chunk.id} into a null return.") + raise Exception( + f"Logic error, unexpected if statement with only one successor in {chunk.id}!" + ) + self.vprint( + f"Converting fall-through to end of code in {chunk.id} into a null return." + ) chunk.actions.append(NullReturnStatement()) chunk.next_chunks = [] elif len(chunk.next_chunks) == 2: if last_action.opcode != AP2Action.IF: - raise Exception(f"Logic error, expected if statement with two successors in {chunk.id}!") + raise Exception( + f"Logic error, expected if statement with two successors in {chunk.id}!" + ) # This is an if statement, let's see if any of the arms point to a return. - true_jump_point, false_jump_point = self.__get_jump_points(chunk, offset_map) + true_jump_point, false_jump_point = self.__get_jump_points( + chunk, offset_map + ) end_offset = offset_map[self.bytecode.end_offset] # It's possible for the true and false jump point to be equal, if the code being @@ -1114,15 +1409,23 @@ class ByteCodeDecompiler(VerboseOutput): # result for this case (true and false cases both containing the same return). true_action: Optional[Statement] = None if true_jump_point == end_offset: - self.vprint(f"Converting jump if true to external point into return statement in {chunk.id}.") + self.vprint( + f"Converting jump if true to external point into return statement in {chunk.id}." + ) true_action = NullReturnStatement() - chunk.next_chunks = [c for c in chunk.next_chunks if c != true_jump_point] + chunk.next_chunks = [ + c for c in chunk.next_chunks if c != true_jump_point + ] false_action: Optional[Statement] = None if false_jump_point == end_offset: - self.vprint(f"Converting jump if false to external point into return statement in {chunk.id}.") + self.vprint( + f"Converting jump if false to external point into return statement in {chunk.id}." + ) false_action = NullReturnStatement() - chunk.next_chunks = [c for c in chunk.next_chunks if c != false_jump_point] + chunk.next_chunks = [ + c for c in chunk.next_chunks if c != false_jump_point + ] if true_action or false_action: chunk.actions[-1] = IntermediateIf( @@ -1131,7 +1434,9 @@ class ByteCodeDecompiler(VerboseOutput): [false_action] if false_action else [], ) - def __find_shallowest_successor(self, start_chunk: int, chunks_by_id: Dict[int, ArbitraryCodeChunk]) -> Optional[int]: + def __find_shallowest_successor( + self, start_chunk: int, chunks_by_id: Dict[int, ArbitraryCodeChunk] + ) -> Optional[int]: if len(chunks_by_id[start_chunk].next_chunks) != 2: # We don't care about this, the successor is the next chunk! raise Exception("Logic error!") @@ -1151,7 +1456,13 @@ class ByteCodeDecompiler(VerboseOutput): # in the part of the tree that we're interested in. We are also not interested in # goto/return/throw statements as they should be treated the same as not finding an # end. - new_candidates.extend([c for c in chunks_by_id[candidate].next_chunks if c not in visited and c in chunks_by_id]) + new_candidates.extend( + [ + c + for c in chunks_by_id[candidate].next_chunks + if c not in visited and c in chunks_by_id + ] + ) candidates = new_candidates # Now, lets do the same with the right, and the first one we encounter that's visited is our guy. @@ -1162,19 +1473,32 @@ class ByteCodeDecompiler(VerboseOutput): return possible_candidates.pop() if len(possible_candidates) > 1: # This shouldn't be possible, I don't think? Let's enforce it as an invariant because I don't know what it means if this happens. - raise Exception(f"Logic error, found too many candidates {possible_candidates} as shallowest successor to {start_chunk}!") + raise Exception( + f"Logic error, found too many candidates {possible_candidates} as shallowest successor to {start_chunk}!" + ) new_candidates = [] for candidate in candidates: # We can't take the same shortcut here as above, as we are trying to ask the question # of what's the shallowest successor, not color them in. - new_candidates.extend([c for c in chunks_by_id[candidate].next_chunks if c in chunks_by_id]) + new_candidates.extend( + [ + c + for c in chunks_by_id[candidate].next_chunks + if c in chunks_by_id + ] + ) candidates = new_candidates # If we didn't find a successor, that means one of the control paths leads to end of execution. return None - def __gather_chunks(self, start_chunk: int, end_chunk: Optional[int], chunks_by_id: Dict[int, ArbitraryCodeChunk]) -> List[ArbitraryCodeChunk]: + def __gather_chunks( + self, + start_chunk: int, + end_chunk: Optional[int], + chunks_by_id: Dict[int, ArbitraryCodeChunk], + ) -> List[ArbitraryCodeChunk]: # Gather all chunks starting with the start_chunk, walking the tree until we hit # end_chunk. Return all chunks in that walk up to but not including the end_chunk. # If end_chunk is None, then just walk the tree until we hit the end, including all @@ -1213,12 +1537,22 @@ class ByteCodeDecompiler(VerboseOutput): if chunks and num_start_chunks != 1: # We're allowed to gather zero chunks (say an if with no else), but if we gather at least one # chunk, we should better have one and only one start to the flow. - raise Exception(f"Logic error, splitting chunks by start chunk {start_chunk} should leave us with one start, but we got {num_start_chunks}!") + raise Exception( + f"Logic error, splitting chunks by start chunk {start_chunk} should leave us with one start, but we got {num_start_chunks}!" + ) return chunks - def __separate_ifs(self, start_id: int, end_id: Optional[int], chunks: Sequence[ArbitraryCodeChunk], offset_map: Dict[int, int]) -> List[ArbitraryCodeChunk]: - chunks_by_id: Dict[int, ArbitraryCodeChunk] = {chunk.id: chunk for chunk in chunks} + def __separate_ifs( + self, + start_id: int, + end_id: Optional[int], + chunks: Sequence[ArbitraryCodeChunk], + offset_map: Dict[int, int], + ) -> List[ArbitraryCodeChunk]: + chunks_by_id: Dict[int, ArbitraryCodeChunk] = { + chunk.id: chunk for chunk in chunks + } cur_id = start_id self.vprint(f"Separating if statements out of graph starting at {start_id}") @@ -1227,14 +1561,20 @@ class ByteCodeDecompiler(VerboseOutput): cur_chunk = chunks_by_id[cur_id] if isinstance(cur_chunk, Loop): self.vprint(f"Examining loop {cur_chunk.id} body for if statements...") - cur_chunk.chunks = self.__separate_ifs(cur_chunk.id, None, cur_chunk.chunks, offset_map) - self.vprint(f"Finished examining loop {cur_chunk.id} body for if statements...") + cur_chunk.chunks = self.__separate_ifs( + cur_chunk.id, None, cur_chunk.chunks, offset_map + ) + self.vprint( + f"Finished examining loop {cur_chunk.id} body for if statements..." + ) # Filter out anything pointing at the end chunk, since we know that's where we will end up # when we leave this if statement anyway. Don't do this for if statements as we need to # preserve the jump point in that case. if len(chunks_by_id[cur_id].next_chunks) == 1: - chunks_by_id[cur_id].next_chunks = [c for c in chunks_by_id[cur_id].next_chunks if c != end_id] + chunks_by_id[cur_id].next_chunks = [ + c for c in chunks_by_id[cur_id].next_chunks if c != end_id + ] if not chunks_by_id[cur_id].next_chunks: # We're done! @@ -1248,22 +1588,33 @@ class ByteCodeDecompiler(VerboseOutput): if next_id not in chunks_by_id: # We need to go to the next chunk, but we don't own it. Convert it to a goto. if isinstance(cur_chunk, Loop): - self.vprint(f"Loop ID {cur_id} needs a goto outside of this if.") + self.vprint( + f"Loop ID {cur_id} needs a goto outside of this if." + ) cur_chunk.post_statements.append(GotoStatement(next_id)) chunks_by_id[cur_id].next_chunks = [] break else: - raise Exception(f"Logic error, we can't jump to chunk {next_id} for if {cur_id} as it is outside of our scope!") + raise Exception( + f"Logic error, we can't jump to chunk {next_id} for if {cur_id} as it is outside of our scope!" + ) cur_id = next_id continue last_action = cur_chunk.actions[-1] if isinstance(last_action, IfAction): - raise Exception(f"Logic error, IfAction with only one child in chunk {cur_chunk}!") + raise Exception( + f"Logic error, IfAction with only one child in chunk {cur_chunk}!" + ) next_id = chunks_by_id[cur_id].next_chunks[0] - if isinstance(last_action, AP2Action) and last_action.opcode in [AP2Action.THROW, AP2Action.RETURN, AP2Action.END, AP2Action.JUMP]: + if isinstance(last_action, AP2Action) and last_action.opcode in [ + AP2Action.THROW, + AP2Action.RETURN, + AP2Action.END, + AP2Action.JUMP, + ]: if next_id not in chunks_by_id: # This is just a goto/chunk, move on to the next one. self.vprint(f"Chunk ID {cur_id} is a goto outside of this if.") @@ -1273,7 +1624,9 @@ class ByteCodeDecompiler(VerboseOutput): else: if next_id not in chunks_by_id: # We need to go to the next chunk, but we don't own it. Convert it to a goto. - self.vprint(f"Chunk ID {cur_id} needs a goto outside of this if.") + self.vprint( + f"Chunk ID {cur_id} needs a goto outside of this if." + ) cur_chunk.actions.append(GotoStatement(next_id)) chunks_by_id[cur_id].next_chunks = [] break @@ -1284,20 +1637,28 @@ class ByteCodeDecompiler(VerboseOutput): if not isinstance(cur_chunk, ByteCodeChunk): # We should only be looking at bytecode chunks at this point, all other # types should have a single next chunk. - raise Exception(f"Logic error, found converted Loop or If chunk {cur_chunk.id} with multiple successors!") + raise Exception( + f"Logic error, found converted Loop or If chunk {cur_chunk.id} with multiple successors!" + ) if len(chunks_by_id[cur_id].next_chunks) != 2: # This needs to be an if statement. - raise Exception(f"Logic error, expected 2 successors but got {len(chunks_by_id[cur_id].next_chunks)} in chunk {cur_chunk.id}!") + raise Exception( + f"Logic error, expected 2 successors but got {len(chunks_by_id[cur_id].next_chunks)} in chunk {cur_chunk.id}!" + ) last_action = cur_chunk.actions[-1] if not isinstance(last_action, IfAction): # This needs, again, to be an if statement. - raise Exception("Logic error, only IfActions can have multiple successors in chunk {cur_chunk.id}!") + raise Exception( + "Logic error, only IfActions can have multiple successors in chunk {cur_chunk.id}!" + ) # This should be an if statement. Figure out if it is an if-else or an # if, and if both branches return. if_end = self.__find_shallowest_successor(cur_id, chunks_by_id) - true_jump_point, false_jump_point = self.__get_jump_points(cur_chunk, offset_map) + true_jump_point, false_jump_point = self.__get_jump_points( + cur_chunk, offset_map + ) if true_jump_point == false_jump_point: # This is an optimized-away if statement, render it out as an empty intermediate If # and set the jump point to the next location. @@ -1320,45 +1681,79 @@ class ByteCodeDecompiler(VerboseOutput): cur_id = next_id continue - self.vprint(f"Chunk ID {cur_id} is an if statement with true node {true_jump_point} and false node {false_jump_point} and ending at {if_end}") + self.vprint( + f"Chunk ID {cur_id} is an if statement with true node {true_jump_point} and false node {false_jump_point} and ending at {if_end}" + ) true_chunks: List[ArbitraryCodeChunk] = [] if true_jump_point not in chunks_by_id and true_jump_point != if_end: - self.vprint(f"If statement true jump point {true_jump_point} is a goto!") - true_chunks.append(ByteCodeChunk(self.__goto_body_id, [GotoStatement(true_jump_point)])) + self.vprint( + f"If statement true jump point {true_jump_point} is a goto!" + ) + true_chunks.append( + ByteCodeChunk(self.__goto_body_id, [GotoStatement(true_jump_point)]) + ) self.__goto_body_id -= 1 elif true_jump_point not in {if_end, end_id}: - self.vprint(f"Gathering true path starting with {true_jump_point} and ending with {if_end} and detecting if statements within it as well.") + self.vprint( + f"Gathering true path starting with {true_jump_point} and ending with {if_end} and detecting if statements within it as well." + ) # First, grab all the chunks in this if statement body. - true_chunks = self.__gather_chunks(true_jump_point, if_end, chunks_by_id) - self.vprint(f"True chunks are {', '.join(str(c.id) for c in true_chunks)}") + true_chunks = self.__gather_chunks( + true_jump_point, if_end, chunks_by_id + ) + self.vprint( + f"True chunks are {', '.join(str(c.id) for c in true_chunks)}" + ) # Delete these chunks from our chunk mapping since we're putting them in an if body. for chunk in true_chunks: del chunks_by_id[chunk.id] # Now, recursively attempt to detect if statements within this chunk as well. - true_chunks = self.__separate_ifs(true_jump_point, if_end if if_end is not None else end_id, true_chunks, offset_map) + true_chunks = self.__separate_ifs( + true_jump_point, + if_end if if_end is not None else end_id, + true_chunks, + offset_map, + ) false_chunks: List[ArbitraryCodeChunk] = [] if false_jump_point not in chunks_by_id and false_jump_point != if_end: - self.vprint(f"If statement false jump point {false_jump_point} is a goto!") - false_chunks.append(ByteCodeChunk(self.__goto_body_id, [GotoStatement(false_jump_point)])) + self.vprint( + f"If statement false jump point {false_jump_point} is a goto!" + ) + false_chunks.append( + ByteCodeChunk( + self.__goto_body_id, [GotoStatement(false_jump_point)] + ) + ) self.__goto_body_id -= 1 elif false_jump_point not in {if_end, end_id}: - self.vprint(f"Gathering false path starting with {false_jump_point} and ending with {if_end} and detecting if statements within it as well.") + self.vprint( + f"Gathering false path starting with {false_jump_point} and ending with {if_end} and detecting if statements within it as well." + ) # First, grab all the chunks in this if statement body. - false_chunks = self.__gather_chunks(false_jump_point, if_end, chunks_by_id) - self.vprint(f"False chunks are {', '.join(str(c.id) for c in false_chunks)}") + false_chunks = self.__gather_chunks( + false_jump_point, if_end, chunks_by_id + ) + self.vprint( + f"False chunks are {', '.join(str(c.id) for c in false_chunks)}" + ) # Delete these chunks from our chunk mapping since we're putting them in an if body. for chunk in false_chunks: del chunks_by_id[chunk.id] # Now, recursively attempt to detect if statements within this chunk as well. - false_chunks = self.__separate_ifs(false_jump_point, if_end if if_end is not None else end_id, false_chunks, offset_map) + false_chunks = self.__separate_ifs( + false_jump_point, + if_end if if_end is not None else end_id, + false_chunks, + offset_map, + ) if (not true_chunks) and (not false_chunks): # We should have at least one! @@ -1372,8 +1767,12 @@ class ByteCodeDecompiler(VerboseOutput): # Add a new if body that this current chunk points to. At this point, chunks_by_id contains # none of the chunks in the true or false bodies of the if, so we add it back to the graph # in the form of an IfBody. - self.vprint(f"Created new IfBody for chunk {cur_id} to point at, ending at {if_id}") - chunks_by_id[if_id] = IfBody(if_id, true_chunks, false_chunks, if_end, cur_id) + self.vprint( + f"Created new IfBody for chunk {cur_id} to point at, ending at {if_id}" + ) + chunks_by_id[if_id] = IfBody( + if_id, true_chunks, false_chunks, if_end, cur_id + ) chunks_by_id[cur_id].next_chunks = [if_id] if if_end is not None: @@ -1383,13 +1782,19 @@ class ByteCodeDecompiler(VerboseOutput): # This if statement encompases all the rest of the statements, we're done. break - self.vprint(f"Finished separating if statements out of graph starting at {start_id}") + self.vprint( + f"Finished separating if statements out of graph starting at {start_id}" + ) return [c for _, c in chunks_by_id.items()] - def __check_graph(self, start_id: int, chunks: Sequence[ArbitraryCodeChunk]) -> List[ArbitraryCodeChunk]: + def __check_graph( + self, start_id: int, chunks: Sequence[ArbitraryCodeChunk] + ) -> List[ArbitraryCodeChunk]: # Recursively go through and verify that all entries to the graph have only one link. # Also, clean up the graph. - chunks_by_id: Dict[int, ArbitraryCodeChunk] = {chunk.id: chunk for chunk in chunks} + chunks_by_id: Dict[int, ArbitraryCodeChunk] = { + chunk.id: chunk for chunk in chunks + } new_chunks: List[ArbitraryCodeChunk] = [] while True: @@ -1405,11 +1810,17 @@ class ByteCodeDecompiler(VerboseOutput): if cur_chunk.true_chunks: self.vprint(f"Cleaning up graph of IfBody {cur_chunk.id} true case") true_start = self.__get_entry_block(cur_chunk.true_chunks) - cur_chunk.true_chunks = self.__check_graph(true_start, cur_chunk.true_chunks) + cur_chunk.true_chunks = self.__check_graph( + true_start, cur_chunk.true_chunks + ) if cur_chunk.false_chunks: - self.vprint(f"Cleaning up graph of IfBody {cur_chunk.id} false case") + self.vprint( + f"Cleaning up graph of IfBody {cur_chunk.id} false case" + ) false_start = self.__get_entry_block(cur_chunk.false_chunks) - cur_chunk.false_chunks = self.__check_graph(false_start, cur_chunk.false_chunks) + cur_chunk.false_chunks = self.__check_graph( + false_start, cur_chunk.false_chunks + ) # Now, check to make sure that we have only one exit pointer. num_exits = len(cur_chunk.next_chunks) @@ -1429,7 +1840,9 @@ class ByteCodeDecompiler(VerboseOutput): # Return the tree, stripped of all dead code (most likely just the return sentinel). return new_chunks - def __eval_stack(self, chunk: ByteCodeChunk, stack: List[Any], offset_map: Dict[int, int]) -> Tuple[List[ConvertedAction], List[Any]]: + def __eval_stack( + self, chunk: ByteCodeChunk, stack: List[Any], offset_map: Dict[int, int] + ) -> Tuple[List[ConvertedAction], List[Any]]: # Make a copy of the stack so we can safely modify it ourselves. stack = [s for s in stack] @@ -1454,7 +1867,7 @@ class ByteCodeDecompiler(VerboseOutput): IfAction.COMP_LT, IfAction.COMP_GT, IfAction.COMP_LT_EQUALS, - IfAction.COMP_GT_EQUALS + IfAction.COMP_GT_EQUALS, ]: conditional2 = stack.pop() conditional1 = stack.pop() @@ -1473,7 +1886,11 @@ class ByteCodeDecompiler(VerboseOutput): elif action.comparison in [IfAction.COMP_BITAND, IfAction.COMP_NOT_BITAND]: conditional2 = stack.pop() conditional1 = stack.pop() - comp = TwoParameterIf.NOT_EQUALS if action.comparison == IfAction.COMP_BITAND else TwoParameterIf.EQUALS + comp = ( + TwoParameterIf.NOT_EQUALS + if action.comparison == IfAction.COMP_BITAND + else TwoParameterIf.EQUALS + ) return TwoParameterIf( ArithmeticExpression(conditional1, "&", conditional2), @@ -1499,7 +1916,9 @@ class ByteCodeDecompiler(VerboseOutput): if action.name: # This defines a global function, so it won't go on the stack. - chunk.actions[i] = SetVariableStatement(action.name, NewFunction(action.flags, decompiler)) + chunk.actions[i] = SetVariableStatement( + action.name, NewFunction(action.flags, decompiler) + ) else: # This defines a function object, most likely for attaching to a member of an object. stack.append(NewFunction(action.flags, decompiler)) @@ -1517,12 +1936,14 @@ class ByteCodeDecompiler(VerboseOutput): frame = stack.pop() if action.additional_frames: - frame = ArithmeticExpression(frame, '+', action.additional_frames) + frame = ArithmeticExpression(frame, "+", action.additional_frames) - chunk.actions[i] = MultiAction([ - GotoFrameStatement(frame), - after, - ]) + chunk.actions[i] = MultiAction( + [ + GotoFrameStatement(frame), + after, + ] + ) continue if isinstance(action, StoreRegisterAction): @@ -1595,9 +2016,9 @@ class ByteCodeDecompiler(VerboseOutput): variable_name, ArithmeticExpression( Variable(variable_name), - "+" if action.amount_to_add >= 0 else '-', + "+" if action.amount_to_add >= 0 else "-", abs(action.amount_to_add), - ) + ), ) continue @@ -1606,9 +2027,9 @@ class ByteCodeDecompiler(VerboseOutput): action.register, ArithmeticExpression( action.register, - "+" if action.amount_to_add >= 0 else '-', + "+" if action.amount_to_add >= 0 else "-", abs(action.amount_to_add), - ) + ), ) continue @@ -1655,7 +2076,7 @@ class ByteCodeDecompiler(VerboseOutput): if action.opcode == AP2Action.TO_NUMBER: obj_ref = stack.pop() - stack.append(FunctionCall(self.__insertion_id, 'int', [obj_ref])) + stack.append(FunctionCall(self.__insertion_id, "int", [obj_ref])) chunk.actions[i] = OriginalCallLocation(self.__insertion_id) self.__insertion_id += 1 @@ -1663,7 +2084,7 @@ class ByteCodeDecompiler(VerboseOutput): if action.opcode == AP2Action.TO_STRING: obj_ref = stack.pop() - stack.append(FunctionCall(self.__insertion_id, 'str', [obj_ref])) + stack.append(FunctionCall(self.__insertion_id, "str", [obj_ref])) chunk.actions[i] = OriginalCallLocation(self.__insertion_id) self.__insertion_id += 1 @@ -1671,14 +2092,14 @@ class ByteCodeDecompiler(VerboseOutput): if action.opcode == AP2Action.INCREMENT: obj_ref = stack.pop() - stack.append(ArithmeticExpression(obj_ref, '+', 1)) + stack.append(ArithmeticExpression(obj_ref, "+", 1)) chunk.actions[i] = NopStatement() continue if action.opcode == AP2Action.DECREMENT: obj_ref = stack.pop() - stack.append(ArithmeticExpression(obj_ref, '-', 1)) + stack.append(ArithmeticExpression(obj_ref, "-", 1)) chunk.actions[i] = NopStatement() continue @@ -1693,7 +2114,11 @@ class ByteCodeDecompiler(VerboseOutput): if action.opcode == AP2Action.INSTANCEOF: name_ref = stack.pop() obj_to_check = stack.pop() - stack.append(FunctionCall(self.__insertion_id, 'isinstance', [obj_to_check, name_ref])) + stack.append( + FunctionCall( + self.__insertion_id, "isinstance", [obj_to_check, name_ref] + ) + ) chunk.actions[i] = OriginalCallLocation(self.__insertion_id) self.__insertion_id += 1 @@ -1701,7 +2126,9 @@ class ByteCodeDecompiler(VerboseOutput): if action.opcode == AP2Action.TYPEOF: obj_to_check = stack.pop() - stack.append(FunctionCall(self.__insertion_id, 'typeof', [obj_to_check])) + stack.append( + FunctionCall(self.__insertion_id, "typeof", [obj_to_check]) + ) chunk.actions[i] = OriginalCallLocation(self.__insertion_id) self.__insertion_id += 1 @@ -1718,7 +2145,11 @@ class ByteCodeDecompiler(VerboseOutput): params = [] for _ in range(num_params): params.append(stack.pop()) - stack.append(MethodCall(self.__insertion_id, object_reference, method_name, params)) + stack.append( + MethodCall( + self.__insertion_id, object_reference, method_name, params + ) + ) chunk.actions[i] = OriginalCallLocation(self.__insertion_id) self.__insertion_id += 1 @@ -1734,7 +2165,9 @@ class ByteCodeDecompiler(VerboseOutput): params = [] for _ in range(num_params): params.append(stack.pop()) - stack.append(FunctionCall(self.__insertion_id, function_name, params)) + stack.append( + FunctionCall(self.__insertion_id, function_name, params) + ) chunk.actions[i] = OriginalCallLocation(self.__insertion_id) self.__insertion_id += 1 @@ -1781,7 +2214,9 @@ class ByteCodeDecompiler(VerboseOutput): else: # This is probably a reference to a variable by # string concatenation. - chunk.actions[i] = SetMemberStatement(GLOBAL, local_name, set_value) + chunk.actions[i] = SetMemberStatement( + GLOBAL, local_name, set_value + ) continue @@ -1819,7 +2254,9 @@ class ByteCodeDecompiler(VerboseOutput): raise Exception("Logic error!") object_reference = stack.pop() - chunk.actions[i] = SetMemberStatement(object_reference, member_name, set_value) + chunk.actions[i] = SetMemberStatement( + object_reference, member_name, set_value + ) continue if action.opcode == AP2Action.GET_PROPERTY: @@ -1831,7 +2268,9 @@ class ByteCodeDecompiler(VerboseOutput): # SET_PROPERTY so this is just here for documentation. raise Exception("Logic error!") object_reference = stack.pop() - stack.append(Member(object_reference, StringConstant(property_int + 0x100))) + stack.append( + Member(object_reference, StringConstant(property_int + 0x100)) + ) chunk.actions[i] = NopStatement() continue @@ -1843,14 +2282,20 @@ class ByteCodeDecompiler(VerboseOutput): raise Exception("Logic error!") object_reference = stack.pop() - chunk.actions[i] = SetMemberStatement(object_reference, StringConstant(property_int + 0x100), set_value) + chunk.actions[i] = SetMemberStatement( + object_reference, + StringConstant(property_int + 0x100), + set_value, + ) continue if action.opcode == AP2Action.DEFINE_LOCAL: set_value = stack.pop() local_name = stack.pop() if not isinstance(local_name, (str, StringConstant)): - raise Exception(f"Logic error, local name {local_name} is not a string!") + raise Exception( + f"Logic error, local name {local_name} is not a string!" + ) chunk.actions[i] = SetLocalStatement(local_name, set_value) continue @@ -1858,7 +2303,9 @@ class ByteCodeDecompiler(VerboseOutput): if action.opcode == AP2Action.DEFINE_LOCAL2: local_name = stack.pop() if not isinstance(local_name, (str, StringConstant)): - raise Exception(f"Logic error, local name {local_name} is not a string!") + raise Exception( + f"Logic error, local name {local_name} is not a string!" + ) chunk.actions[i] = SetLocalStatement(local_name, UNDEFINED) continue @@ -2047,7 +2494,9 @@ class ByteCodeDecompiler(VerboseOutput): if action.opcode == AP2Action.CAST_OP: obj_ref = stack.pop() class_ref = stack.pop() - stack.append(FunctionCall(self.__insertion_id, 'cast', [obj_ref, class_ref])) + stack.append( + FunctionCall(self.__insertion_id, "cast", [obj_ref, class_ref]) + ) chunk.actions[i] = OriginalCallLocation(self.__insertion_id) self.__insertion_id += 1 @@ -2118,7 +2567,9 @@ class ByteCodeDecompiler(VerboseOutput): ) continue - raise Exception(f"Unexpected action {action}, the cases above should be exhaustive!") + raise Exception( + f"Unexpected action {action}, the cases above should be exhaustive!" + ) # Now, clean up code generation. new_actions: List[ConvertedAction] = [] @@ -2143,14 +2594,28 @@ class ByteCodeDecompiler(VerboseOutput): # Finally, return everything we did. return new_actions, stack - def __eval_chunks(self, start_id: int, chunks: Sequence[ArbitraryCodeChunk], offset_map: Dict[int, int]) -> List[Statement]: + def __eval_chunks( + self, + start_id: int, + chunks: Sequence[ArbitraryCodeChunk], + offset_map: Dict[int, int], + ) -> List[Statement]: stack: Dict[int, List[Any]] = {start_id: []} insertables: Dict[int, List[Statement]] = {} orphaned_functions: Dict[int, Union[FunctionCall, MethodCall]] = {} other_locs: Dict[int, int] = {} # Convert all chunks to a list of statements. - statements = self.__eval_chunks_impl(start_id, chunks, None, stack, insertables, orphaned_functions, other_locs, offset_map) + statements = self.__eval_chunks_impl( + start_id, + chunks, + None, + stack, + insertables, + orphaned_functions, + other_locs, + offset_map, + ) # Now, go through and fix up any insertables. def fixup(statements: Sequence[Statement]) -> List[Statement]: @@ -2168,14 +2633,22 @@ class ByteCodeDecompiler(VerboseOutput): if isinstance(statement, InsertionLocation): # Convert to any statements we need to insert. if statement.location in insertables: - self.vprint(f"Inserting temp variable assignments into insertion location {statement.location}") + self.vprint( + f"Inserting temp variable assignments into insertion location {statement.location}" + ) for stmt in insertables[statement.location]: new_statements.append(stmt) elif isinstance(statement, OriginalCallLocation): # Convert any orphaned function calls to calls without an assignment. if statement.insertion_id in orphaned_functions: - self.vprint(f"Inserting orphaned function into insertion location {statement.insertion_id}") - new_statements.append(ExpressionStatement(orphaned_functions[statement.insertion_id])) + self.vprint( + f"Inserting orphaned function into insertion location {statement.insertion_id}" + ) + new_statements.append( + ExpressionStatement( + orphaned_functions[statement.insertion_id] + ) + ) del orphaned_functions[statement.insertion_id] else: new_statements.append(statement) @@ -2184,7 +2657,9 @@ class ByteCodeDecompiler(VerboseOutput): statements = fixup(statements) if orphaned_functions: - raise Exception(f"Unexpected leftover orphan functions {orphaned_functions}!") + raise Exception( + f"Unexpected leftover orphan functions {orphaned_functions}!" + ) # Make sure we consumed the stack. for cid, leftovers in stack.items(): @@ -2205,13 +2680,19 @@ class ByteCodeDecompiler(VerboseOutput): other_stack_locs: Dict[int, int], offset_map: Dict[int, int], ) -> List[Statement]: - chunks_by_id: Dict[int, ArbitraryCodeChunk] = {chunk.id: chunk for chunk in chunks} + chunks_by_id: Dict[int, ArbitraryCodeChunk] = { + chunk.id: chunk for chunk in chunks + } statements: List[Statement] = [] - def reconcile_stacks(cur_chunk: int, new_stack_id: int, new_stack: List[Any]) -> List[Statement]: + def reconcile_stacks( + cur_chunk: int, new_stack_id: int, new_stack: List[Any] + ) -> List[Statement]: if new_stack_id in stacks: if cur_chunk == other_stack_locs[new_stack_id]: - raise Exception("Logic error, cannot reconcile variable names with self!") + raise Exception( + "Logic error, cannot reconcile variable names with self!" + ) other_chunk = other_stack_locs[new_stack_id] if len(stacks[new_stack_id]) != len(new_stack): min_len = min(len(stacks[new_stack_id]), len(new_stack)) @@ -2223,29 +2704,38 @@ class ByteCodeDecompiler(VerboseOutput): # It doesn't matter what it is, just mark the stack entry as being poisoned since # we couldn't reconcile it. We want to throw an exception down the line if we # run into this value, as we needed it but only sometimes got it. - borrow_vals = [MaybeStackEntry(new_stack_id) for _ in range(borrows)] + borrow_vals = [ + MaybeStackEntry(new_stack_id) for _ in range(borrows) + ] if min_len > 0: - stacks[new_stack_id] = [*borrow_vals, *stacks[new_stack_id][-min_len:]] + stacks[new_stack_id] = [ + *borrow_vals, + *stacks[new_stack_id][-min_len:], + ] new_stack = [*borrow_vals, new_stack[-min_len:]] else: stacks[new_stack_id] = [*borrow_vals] new_stack = [*borrow_vals] - self.vprint(f"Chopped off {borrows} values from longest stack and replaced with MaybeStackEntry for {new_stack_id}") + self.vprint( + f"Chopped off {borrows} values from longest stack and replaced with MaybeStackEntry for {new_stack_id}" + ) if len(new_stack) != len(stacks[new_stack_id]): - raise Exception(f"Logic error, expected {new_stack} and {stacks[new_stack_id]} to be equal length!") + raise Exception( + f"Logic error, expected {new_stack} and {stacks[new_stack_id]} to be equal length!" + ) self.vprint( - f"Merging stack {stacks[new_stack_id]} for chunk ID {new_stack_id} with {new_stack}, " + - f"and scheduling chunks {cur_chunk} and {other_chunk} for variable definitions." + f"Merging stack {stacks[new_stack_id]} for chunk ID {new_stack_id} with {new_stack}, " + + f"and scheduling chunks {cur_chunk} and {other_chunk} for variable definitions." ) stack: List[Any] = [] definitions: List[Statement] = [] for j in range(len(new_stack)): # Walk the stack backwards to mimic the order in which a stack entry would be pulled. - i = (len(new_stack) - (j + 1)) + i = len(new_stack) - (j + 1) new_entry = new_stack[i] old_entry = stacks[new_stack_id][i] @@ -2254,27 +2744,41 @@ class ByteCodeDecompiler(VerboseOutput): # This is already converted in another stack, so we just need to use the same. tmpname = old_entry.name - insertables[cur_chunk] = insertables.get(cur_chunk, []) + [SetVariableStatement(tmpname, new_entry)] + insertables[cur_chunk] = insertables.get(cur_chunk, []) + [ + SetVariableStatement(tmpname, new_entry) + ] stack.append(TempVariable(tmpname)) - self.vprint(f"Reusing temporary variable {tmpname} to hold stack value {new_stack[i]}") + self.vprint( + f"Reusing temporary variable {tmpname} to hold stack value {new_stack[i]}" + ) else: tmpname = f"tempvar_{self.__tmpvar_id}" self.__tmpvar_id += 1 - insertables[cur_chunk] = insertables.get(cur_chunk, []) + [SetVariableStatement(tmpname, new_entry)] - insertables[other_chunk] = insertables.get(other_chunk, []) + [SetVariableStatement(tmpname, old_entry)] + insertables[cur_chunk] = insertables.get(cur_chunk, []) + [ + SetVariableStatement(tmpname, new_entry) + ] + insertables[other_chunk] = insertables.get( + other_chunk, [] + ) + [SetVariableStatement(tmpname, old_entry)] stack.append(TempVariable(tmpname)) - self.vprint(f"Creating temporary variable {tmpname} to hold stack values {new_stack[i]} and {stacks[new_stack_id][i]}") + self.vprint( + f"Creating temporary variable {tmpname} to hold stack values {new_stack[i]} and {stacks[new_stack_id][i]}" + ) else: stack.append(new_entry) - self.vprint(f"Redefining stack for chunk ID {new_stack_id} to be {stack} after merging multiple paths") + self.vprint( + f"Redefining stack for chunk ID {new_stack_id} to be {stack} after merging multiple paths" + ) stacks[new_stack_id] = stack[::-1] return definitions else: - self.vprint(f"Defining stack for chunk ID {new_stack_id} to be {new_stack} based on evaluation of {cur_chunk}") + self.vprint( + f"Defining stack for chunk ID {new_stack_id} to be {new_stack} based on evaluation of {cur_chunk}" + ) other_stack_locs[new_stack_id] = cur_chunk stacks[new_stack_id] = new_stack return [] @@ -2290,7 +2794,16 @@ class ByteCodeDecompiler(VerboseOutput): if isinstance(chunk, Loop): # Evaluate the loop. No need to update per-chunk stacks here since we will do it in a child eval. self.vprint(f"Evaluating graph in Loop {chunk.id}") - loop_statements = self.__eval_chunks_impl(chunk.id, chunk.chunks, next_chunk_id, stacks, insertables, orphaned_functions, other_stack_locs, offset_map) + loop_statements = self.__eval_chunks_impl( + chunk.id, + chunk.chunks, + next_chunk_id, + stacks, + insertables, + orphaned_functions, + other_stack_locs, + offset_map, + ) statements.append(DoWhileStatement(loop_statements)) statements.extend(chunk.post_statements) elif isinstance(chunk, IfBody): @@ -2304,14 +2817,20 @@ class ByteCodeDecompiler(VerboseOutput): # Grab the computed start stack for this ID if chunk.id not in stacks: # We somehow failed to assign a stack to this chunk but got here anyway? - raise Exception(f"Logic error, stack for {chunk.id} does not exist!") + raise Exception( + f"Logic error, stack for {chunk.id} does not exist!" + ) stack = stacks[chunk.id] del stacks[chunk.id] # Calculate the statements for this chunk, as well as the leftover stack entries. - self.vprint(f"Evaluating graph of ByteCodeChunk {chunk.id} with stack {stack}") - new_statements, stack_leftovers = self.__eval_stack(chunk, stack, offset_map) + self.vprint( + f"Evaluating graph of ByteCodeChunk {chunk.id} with stack {stack}" + ) + new_statements, stack_leftovers = self.__eval_stack( + chunk, stack, offset_map + ) # We need to check and see if the last entry is an IfExpr, and hoist it # into a statement here. @@ -2321,11 +2840,15 @@ class ByteCodeDecompiler(VerboseOutput): if not isinstance(if_body_chunk, IfBody): # IfBody should always follow a chunk that ends with an if. - raise Exception(f"Logic error, expecting an IfBody chunk but got {if_body_chunk}!") + raise Exception( + f"Logic error, expecting an IfBody chunk but got {if_body_chunk}!" + ) if if_body in stacks: # Nothing should ever create a stack pointing at an IfBody except this code here. - raise Exception(f"Logic error, IfBody ID {if_body} already has a stack {stacks[if_body]}!") + raise Exception( + f"Logic error, IfBody ID {if_body} already has a stack {stacks[if_body]}!" + ) # Recalculate next chunk ID since we're calculating two chunks here. if len(if_body_chunk.next_chunks) > 1: @@ -2335,7 +2858,9 @@ class ByteCodeDecompiler(VerboseOutput): next_chunk_id = if_body_chunk.next_chunks[0] else: next_chunk_id = next_id - self.vprint(f"Recalculated next ID for IfBody {if_body} to be {next_chunk_id}") + self.vprint( + f"Recalculated next ID for IfBody {if_body} to be {next_chunk_id}" + ) # Make sure if its an if with only one body (true/false) that we track # the stack in this case as well. @@ -2347,7 +2872,9 @@ class ByteCodeDecompiler(VerboseOutput): # Evaluate the if body true_statements: List[Statement] = [] if if_body_chunk.true_chunks: - self.vprint(f"Evaluating graph of IfBody {if_body_chunk.id} true case") + self.vprint( + f"Evaluating graph of IfBody {if_body_chunk.id} true case" + ) true_start = self.__get_entry_block(if_body_chunk.true_chunks) if true_start in stacks: raise Exception("Logic error, unexpected stack for if!") @@ -2355,7 +2882,9 @@ class ByteCodeDecompiler(VerboseOutput): # The stack for both of these is the leftovers from the previous evaluation as they # rollover. stacks[true_start] = [s for s in stack_leftovers] - self.vprint(f"True start {true_start} of IfBody has stack {stacks[true_start]}") + self.vprint( + f"True start {true_start} of IfBody has stack {stacks[true_start]}" + ) true_statements = self.__eval_chunks_impl( true_start, if_body_chunk.true_chunks, @@ -2368,12 +2897,16 @@ class ByteCodeDecompiler(VerboseOutput): ) else: if next_chunk_id is None: - raise Exception("Logic error, cannot reconcile stacks when next chunk is the end!") + raise Exception( + "Logic error, cannot reconcile stacks when next chunk is the end!" + ) reconcile_stacks(chunk.id, next_chunk_id, stack_leftovers) false_statements: List[Statement] = [] if if_body_chunk.false_chunks: - self.vprint(f"Evaluating graph of IfBody {if_body_chunk.id} false case") + self.vprint( + f"Evaluating graph of IfBody {if_body_chunk.id} false case" + ) false_start = self.__get_entry_block(if_body_chunk.false_chunks) if false_start in stacks: raise Exception("Logic error, unexpected stack for if!") @@ -2381,7 +2914,9 @@ class ByteCodeDecompiler(VerboseOutput): # The stack for both of these is the leftovers from the previous evaluation as they # rollover. stacks[false_start] = [s for s in stack_leftovers] - self.vprint(f"False start {false_start} of IfBody has stack {stacks[false_start]}") + self.vprint( + f"False start {false_start} of IfBody has stack {stacks[false_start]}" + ) false_statements = self.__eval_chunks_impl( false_start, if_body_chunk.false_chunks, @@ -2394,7 +2929,9 @@ class ByteCodeDecompiler(VerboseOutput): ) else: if next_chunk_id is None: - raise Exception("Logic error, cannot reconcile stacks when next chunk is the end!") + raise Exception( + "Logic error, cannot reconcile stacks when next chunk is the end!" + ) reconcile_stacks(chunk.id, next_chunk_id, stack_leftovers) # Convert this IfExpr to a full-blown IfStatement. @@ -2414,7 +2951,10 @@ class ByteCodeDecompiler(VerboseOutput): if isinstance(last_new_statement, GotoStatement): # Replace the next IDs with just the goto. new_next_ids = {last_new_statement.location} - elif isinstance(last_new_statement, (ThrowStatement, NullReturnStatement, ReturnStatement)): + elif isinstance( + last_new_statement, + (ThrowStatement, NullReturnStatement, ReturnStatement), + ): # We don't have a next ID, we're returning. new_next_ids = set() elif isinstance(last_new_statement, IntermediateIf): @@ -2422,18 +2962,32 @@ class ByteCodeDecompiler(VerboseOutput): # inside the true/false chunks. intermediates: List[Statement] = [] if len(last_new_statement.true_statements) > 1: - raise Exception(f"Logic error, expected only one true statement in intermediate if {last_new_statement}!") + raise Exception( + f"Logic error, expected only one true statement in intermediate if {last_new_statement}!" + ) else: intermediates.extend(last_new_statement.true_statements) if len(last_new_statement.false_statements) > 1: - raise Exception(f"Logic error, expected only one false statement in intermediate if {last_new_statement}!") + raise Exception( + f"Logic error, expected only one false statement in intermediate if {last_new_statement}!" + ) else: - intermediates.extend(last_new_statement.false_statements) + intermediates.extend( + last_new_statement.false_statements + ) for stmt in intermediates: if isinstance(stmt, GotoStatement): new_next_ids.add(stmt.location) - elif isinstance(stmt, (ThrowStatement, NullReturnStatement, ReturnStatement, ContinueStatement)): + elif isinstance( + stmt, + ( + ThrowStatement, + NullReturnStatement, + ReturnStatement, + ContinueStatement, + ), + ): # Do nothing. Three of these cases point at the end of the program, one # points back at the top of the loop which we've already covered. Maybe # we should assert here like we do below? Not sure. @@ -2443,20 +2997,28 @@ class ByteCodeDecompiler(VerboseOutput): if next_id is not None: new_next_ids.add(next_id) else: - raise Exception(f"Logic error, unexpected statement {stmt}!") + raise Exception( + f"Logic error, unexpected statement {stmt}!" + ) if new_next_ids: for new_next_id in new_next_ids: - reconcile_stacks(chunk.id, new_next_id, [s for s in stack_leftovers]) + reconcile_stacks( + chunk.id, new_next_id, [s for s in stack_leftovers] + ) # Insert a sentinel for where temporary variables can be added if we # need to in the future. - sentinels: List[Union[Statement, IntermediateIf]] = [InsertionLocation(chunk.id)] + sentinels: List[Union[Statement, IntermediateIf]] = [ + InsertionLocation(chunk.id) + ] # If we have a goto or intermediate if, we need to insert the tempvar assignment before it. # This is because in both cases we will redirect control flow, so we need to make sure # tempvar assignment happens before that redirection for the code to make sense. - if new_statements and isinstance(new_statements[-1], (GotoStatement, IntermediateIf)): + if new_statements and isinstance( + new_statements[-1], (GotoStatement, IntermediateIf) + ): sentinels.append(new_statements[-1]) new_statements = new_statements[:-1] @@ -2464,14 +3026,28 @@ class ByteCodeDecompiler(VerboseOutput): new_statements.extend(sentinels) else: # We have nowhere else to go, verify that we have an empty stack. - orphans = [s for s in stack_leftovers if isinstance(s, (FunctionCall, MethodCall))] - stack_leftovers = [s for s in stack_leftovers if not isinstance(s, (MaybeStackEntry, FunctionCall, MethodCall))] + orphans = [ + s + for s in stack_leftovers + if isinstance(s, (FunctionCall, MethodCall)) + ] + stack_leftovers = [ + s + for s in stack_leftovers + if not isinstance( + s, (MaybeStackEntry, FunctionCall, MethodCall) + ) + ] for func in orphans: if func.insertion_ref in orphaned_functions: - raise Exception(f"Logic error, already have an insertion ID {func.insertion_ref}!") + raise Exception( + f"Logic error, already have an insertion ID {func.insertion_ref}!" + ) orphaned_functions[func.insertion_ref] = func if stack_leftovers: - raise Exception(f"Logic error, reached execution end and have stack entries {stack_leftovers} still!") + raise Exception( + f"Logic error, reached execution end and have stack entries {stack_leftovers} still!" + ) # Verify that we converted all the statements properly. for statement in new_statements: @@ -2479,10 +3055,17 @@ class ByteCodeDecompiler(VerboseOutput): # Intermediate if conditional (such as a break/return/goto inside # a loop. if not isinstance(statement.parent_action, IfExpr): - raise Exception(f"Logic error, found unconverted IntermediateIf {statement}!") + raise Exception( + f"Logic error, found unconverted IntermediateIf {statement}!" + ) - if not statement.true_statements and not statement.false_statements: - self.vprint(f"Skipping adding if statement {statement} because it is an empty sentinel!") + if ( + not statement.true_statements + and not statement.false_statements + ): + self.vprint( + f"Skipping adding if statement {statement} because it is an empty sentinel!" + ) else: statements.append( IfStatement( @@ -2505,7 +3088,11 @@ class ByteCodeDecompiler(VerboseOutput): return statements - def __walk(self, statements: Sequence[Statement], do: Callable[[Statement], Optional[Statement]]) -> List[Statement]: + def __walk( + self, + statements: Sequence[Statement], + do: Callable[[Statement], Optional[Statement]], + ) -> List[Statement]: new_statements: List[Statement] = [] for statement in statements: @@ -2514,15 +3101,20 @@ class ByteCodeDecompiler(VerboseOutput): new_statement.body = self.__walk(new_statement.body, do) new_statements.append(new_statement) elif isinstance(new_statement, IfStatement): - new_statement.true_statements = self.__walk(new_statement.true_statements, do) - new_statement.false_statements = self.__walk(new_statement.false_statements, do) + new_statement.true_statements = self.__walk( + new_statement.true_statements, do + ) + new_statement.false_statements = self.__walk( + new_statement.false_statements, do + ) new_statements.append(new_statement) elif isinstance(new_statement, SwitchStatement): new_statement.cases = [ SwitchCase( case.const, self.__walk(case.statements, do), - ) for case in new_statement.cases + ) + for case in new_statement.cases ] new_statements.append(new_statement) elif new_statement: @@ -2530,7 +3122,9 @@ class ByteCodeDecompiler(VerboseOutput): return new_statements - def __collapse_identical_labels(self, statements: Sequence[Statement]) -> Tuple[List[Statement], bool]: + def __collapse_identical_labels( + self, statements: Sequence[Statement] + ) -> Tuple[List[Statement], bool]: # Go through and find labels that point at gotos, remove them and point the # gotos to those labels at the second gotos. statements = list(statements) @@ -2540,10 +3134,11 @@ class ByteCodeDecompiler(VerboseOutput): for i in range(len(statements)): cur_statement = statements[i] - next_statement = statements[i + 1] if (i < len(statements) - 1) else None - if ( - isinstance(cur_statement, DefineLabelStatement) and - isinstance(next_statement, GotoStatement) + next_statement = ( + statements[i + 1] if (i < len(statements) - 1) else None + ) + if isinstance(cur_statement, DefineLabelStatement) and isinstance( + next_statement, GotoStatement ): label_and_goto[cur_statement.location] = next_statement.location @@ -2551,8 +3146,12 @@ class ByteCodeDecompiler(VerboseOutput): label_and_goto.update(find_labels_and_gotos(cur_statement.body)) elif isinstance(cur_statement, IfStatement): - label_and_goto.update(find_labels_and_gotos(cur_statement.true_statements)) - label_and_goto.update(find_labels_and_gotos(cur_statement.false_statements)) + label_and_goto.update( + find_labels_and_gotos(cur_statement.true_statements) + ) + label_and_goto.update( + find_labels_and_gotos(cur_statement.false_statements) + ) elif isinstance(cur_statement, SwitchStatement): for case in cur_statement.cases: @@ -2597,18 +3196,25 @@ class ByteCodeDecompiler(VerboseOutput): return statements, changed - def __remove_goto_return(self, statements: Sequence[Statement]) -> Tuple[List[Statement], bool]: + def __remove_goto_return( + self, statements: Sequence[Statement] + ) -> Tuple[List[Statement], bool]: # Go through and find labels that point at returns, convert any gotos pointing # at them to returns. - def find_labels(statements: Sequence[Statement], parent_next_statement: Optional[Statement]) -> Set[int]: + def find_labels( + statements: Sequence[Statement], parent_next_statement: Optional[Statement] + ) -> Set[int]: labels: Set[int] = set() for i in range(len(statements)): cur_statement = statements[i] - next_statement = statements[i + 1] if (i < len(statements) - 1) else parent_next_statement - if ( - isinstance(cur_statement, DefineLabelStatement) and - isinstance(next_statement, NullReturnStatement) + next_statement = ( + statements[i + 1] + if (i < len(statements) - 1) + else parent_next_statement + ) + if isinstance(cur_statement, DefineLabelStatement) and isinstance( + next_statement, NullReturnStatement ): labels.add(cur_statement.location) @@ -2616,8 +3222,12 @@ class ByteCodeDecompiler(VerboseOutput): labels.update(find_labels(cur_statement.body, next_statement)) elif isinstance(cur_statement, IfStatement): - labels.update(find_labels(cur_statement.true_statements, next_statement)) - labels.update(find_labels(cur_statement.false_statements, next_statement)) + labels.update( + find_labels(cur_statement.true_statements, next_statement) + ) + labels.update( + find_labels(cur_statement.false_statements, next_statement) + ) elif isinstance(cur_statement, SwitchStatement): for case in cur_statement.cases: @@ -2641,19 +3251,26 @@ class ByteCodeDecompiler(VerboseOutput): statements = self.__walk(statements, update_gotos) return statements, updated - def __eliminate_useless_returns(self, statements: Sequence[Statement]) -> Tuple[List[Statement], bool]: + def __eliminate_useless_returns( + self, statements: Sequence[Statement] + ) -> Tuple[List[Statement], bool]: # Go through and find returns that are on the "last" line. Basically, any # return statement where the next statement is another return statement # or the end of a function. - def find_returns(statements: Sequence[Statement], parent_next_statement: Statement) -> Set[NullReturnStatement]: + def find_returns( + statements: Sequence[Statement], parent_next_statement: Statement + ) -> Set[NullReturnStatement]: returns: Set[NullReturnStatement] = set() for i in range(len(statements)): cur_statement = statements[i] - next_statement = statements[i + 1] if (i < len(statements) - 1) else parent_next_statement - if ( - isinstance(cur_statement, NullReturnStatement) and - isinstance(next_statement, NullReturnStatement) + next_statement = ( + statements[i + 1] + if (i < len(statements) - 1) + else parent_next_statement + ) + if isinstance(cur_statement, NullReturnStatement) and isinstance( + next_statement, NullReturnStatement ): returns.add(cur_statement) @@ -2661,8 +3278,12 @@ class ByteCodeDecompiler(VerboseOutput): returns.update(find_returns(cur_statement.body, next_statement)) elif isinstance(cur_statement, IfStatement): - returns.update(find_returns(cur_statement.true_statements, next_statement)) - returns.update(find_returns(cur_statement.false_statements, next_statement)) + returns.update( + find_returns(cur_statement.true_statements, next_statement) + ) + returns.update( + find_returns(cur_statement.false_statements, next_statement) + ) elif isinstance(cur_statement, SwitchStatement): for case in cur_statement.cases: @@ -2688,28 +3309,32 @@ class ByteCodeDecompiler(VerboseOutput): statements = self.__walk(statements, remove_returns) return statements, updated - def __remove_useless_gotos(self, statements: Sequence[Statement]) -> Tuple[List[Statement], bool]: + def __remove_useless_gotos( + self, statements: Sequence[Statement] + ) -> Tuple[List[Statement], bool]: # Go through and find gotos that point at the very next line and remove them. # This can happen due to the way we analyze if statements. statements = list(statements) - def find_goto_next_line(statements: Sequence[Statement], next_instruction: Statement) -> List[Statement]: + def find_goto_next_line( + statements: Sequence[Statement], next_instruction: Statement + ) -> List[Statement]: gotos: List[Statement] = [] for i in range(len(statements)): cur_statement = statements[i] - next_statement = statements[i + 1] if (i < len(statements) - 1) else next_instruction + next_statement = ( + statements[i + 1] if (i < len(statements) - 1) else next_instruction + ) - if ( - isinstance(cur_statement, GotoStatement) and - isinstance(next_statement, DefineLabelStatement) + if isinstance(cur_statement, GotoStatement) and isinstance( + next_statement, DefineLabelStatement ): if cur_statement.location == next_statement.location: gotos.append(cur_statement) - if ( - isinstance(cur_statement, GotoStatement) and - isinstance(next_statement, GotoStatement) + if isinstance(cur_statement, GotoStatement) and isinstance( + next_statement, GotoStatement ): if cur_statement.location == next_statement.location: gotos.append(cur_statement) @@ -2717,14 +3342,24 @@ class ByteCodeDecompiler(VerboseOutput): elif isinstance(cur_statement, DoWhileStatement): # Loops do not "flow" into the next line, they can only "break" to the next # line. Goto of the next line has already been converted to a "break" statement. - gotos.extend(find_goto_next_line(cur_statement.body, NopStatement())) + gotos.extend( + find_goto_next_line(cur_statement.body, NopStatement()) + ) elif isinstance(cur_statement, IfStatement): # The next statement for both the if and else body is the next statement we have # looked up, either the next statement in this group of statements, or the next # statement in the parent. - gotos.extend(find_goto_next_line(cur_statement.true_statements, next_statement)) - gotos.extend(find_goto_next_line(cur_statement.false_statements, next_statement)) + gotos.extend( + find_goto_next_line( + cur_statement.true_statements, next_statement + ) + ) + gotos.extend( + find_goto_next_line( + cur_statement.false_statements, next_statement + ) + ) elif isinstance(cur_statement, SwitchStatement): # Switch cases do not "flow" into the next line, they flow into the next switch @@ -2754,10 +3389,20 @@ class ByteCodeDecompiler(VerboseOutput): return NopStatement() for case in cases: - if case.statements and isinstance(case.statements[-1], BreakStatement): - gotos.extend(find_goto_next_line(case.statements[:-1], next_statement)) + if case.statements and isinstance( + case.statements[-1], BreakStatement + ): + gotos.extend( + find_goto_next_line( + case.statements[:-1], next_statement + ) + ) else: - gotos.extend(find_goto_next_line(case.statements, get_next_instruction(case))) + gotos.extend( + find_goto_next_line( + case.statements, get_next_instruction(case) + ) + ) return gotos @@ -2782,7 +3427,9 @@ class ByteCodeDecompiler(VerboseOutput): return statements, changed - def __eliminate_unused_labels(self, statements: Sequence[Statement]) -> Tuple[List[Statement], bool]: + def __eliminate_unused_labels( + self, statements: Sequence[Statement] + ) -> Tuple[List[Statement], bool]: # Go through and find labels that nothing is pointing at, and remove them. locations: Set[int] = set() @@ -2805,34 +3452,49 @@ class ByteCodeDecompiler(VerboseOutput): return self.__walk(statements, remove_label), changed - def __eliminate_useless_continues(self, statements: Sequence[Statement]) -> Tuple[List[Statement], bool]: + def __eliminate_useless_continues( + self, statements: Sequence[Statement] + ) -> Tuple[List[Statement], bool]: # Go through and find continues that are on the "last" line of a while. Basically, any # continue statement where the next statement is another continue statement or the end # of a loop. - def find_continues(statements: Sequence[Statement], parent_next_statement: Statement) -> Set[ContinueStatement]: + def find_continues( + statements: Sequence[Statement], parent_next_statement: Statement + ) -> Set[ContinueStatement]: continues: Set[ContinueStatement] = set() for i in range(len(statements)): cur_statement = statements[i] - next_statement = statements[i + 1] if (i < len(statements) - 1) else parent_next_statement - if ( - isinstance(cur_statement, ContinueStatement) and - isinstance(next_statement, ContinueStatement) + next_statement = ( + statements[i + 1] + if (i < len(statements) - 1) + else parent_next_statement + ) + if isinstance(cur_statement, ContinueStatement) and isinstance( + next_statement, ContinueStatement ): continues.add(cur_statement) elif isinstance(cur_statement, DoWhileStatement): # Clever hack, where we pretend the next value after the loop is a continue, # because hitting the bottom of a loop is actually a continue. - continues.update(find_continues(cur_statement.body, ContinueStatement())) + continues.update( + find_continues(cur_statement.body, ContinueStatement()) + ) elif isinstance(cur_statement, IfStatement): - continues.update(find_continues(cur_statement.true_statements, next_statement)) - continues.update(find_continues(cur_statement.false_statements, next_statement)) + continues.update( + find_continues(cur_statement.true_statements, next_statement) + ) + continues.update( + find_continues(cur_statement.false_statements, next_statement) + ) elif isinstance(cur_statement, SwitchStatement): for case in cur_statement.cases: - continues.update(find_continues(case.statements, next_statement)) + continues.update( + find_continues(case.statements, next_statement) + ) return continues @@ -2854,17 +3516,24 @@ class ByteCodeDecompiler(VerboseOutput): statements = self.__walk(statements, remove_continues) return statements, updated - def __eliminate_useless_breaks(self, statements: Sequence[Statement]) -> Tuple[List[Statement], bool]: + def __eliminate_useless_breaks( + self, statements: Sequence[Statement] + ) -> Tuple[List[Statement], bool]: # Go through and find breaks that show up just before another break logically. - def find_breaks(statements: Sequence[Statement], parent_next_statement: Statement) -> Set[BreakStatement]: + def find_breaks( + statements: Sequence[Statement], parent_next_statement: Statement + ) -> Set[BreakStatement]: breaks: Set[BreakStatement] = set() for i in range(len(statements)): cur_statement = statements[i] - next_statement = statements[i + 1] if (i < len(statements) - 1) else parent_next_statement - if ( - isinstance(cur_statement, BreakStatement) and - isinstance(next_statement, BreakStatement) + next_statement = ( + statements[i + 1] + if (i < len(statements) - 1) + else parent_next_statement + ) + if isinstance(cur_statement, BreakStatement) and isinstance( + next_statement, BreakStatement ): breaks.add(cur_statement) @@ -2873,8 +3542,12 @@ class ByteCodeDecompiler(VerboseOutput): breaks.update(find_breaks(cur_statement.body, NopStatement())) elif isinstance(cur_statement, IfStatement): - breaks.update(find_breaks(cur_statement.true_statements, next_statement)) - breaks.update(find_breaks(cur_statement.false_statements, next_statement)) + breaks.update( + find_breaks(cur_statement.true_statements, next_statement) + ) + breaks.update( + find_breaks(cur_statement.false_statements, next_statement) + ) elif isinstance(cur_statement, SwitchStatement): # The next entry after a switch can be a break, as it applies to a different statement. @@ -2952,9 +3625,13 @@ class ByteCodeDecompiler(VerboseOutput): return statement.valueref return None - def __extract_condition(self, possible_if: Statement, required_variable: Optional[str]) -> Tuple[Optional[IfExpr], List[Statement]]: + def __extract_condition( + self, possible_if: Statement, required_variable: Optional[str] + ) -> Tuple[Optional[IfExpr], List[Statement]]: if isinstance(possible_if, IfStatement): - if len(possible_if.true_statements) == 1 and isinstance(possible_if.true_statements[0], BreakStatement): + if len(possible_if.true_statements) == 1 and isinstance( + possible_if.true_statements[0], BreakStatement + ): # This is possibly a candidate, check the condition's variable usage. if isinstance(possible_if.cond, IsUndefinedIf): if required_variable is not None: @@ -2996,27 +3673,37 @@ class ByteCodeDecompiler(VerboseOutput): return possible_if.cond, possible_if.false_statements return None, [] - def __convert_loops(self, statements: Sequence[Statement]) -> Tuple[List[Statement], bool]: + def __convert_loops( + self, statements: Sequence[Statement] + ) -> Tuple[List[Statement], bool]: # Convert any do {} while loops that resemble for statements into actual for statements. # First, we need to hoist any increment to the actual end of the loop in case its in the # last statement of some if/else condition. This isn't going to be perfectly accurate because # there can be all sorts of bizarre for statements, but it should be good enough for most # cases to make better code. - def convert_loops(statements: Sequence[Statement]) -> Tuple[List[Statement], bool]: + def convert_loops( + statements: Sequence[Statement], + ) -> Tuple[List[Statement], bool]: new_statements: List[Statement] = [] updated_statements: Dict[DoWhileStatement, DoWhileStatement] = {} changed: bool = False for i in range(len(statements)): cur_statement = statements[i] - next_statement = statements[i + 1] if (i < len(statements) - 1) else None + next_statement = ( + statements[i + 1] if (i < len(statements) - 1) else None + ) if isinstance(cur_statement, IfStatement): # Don't care about this, but we need to recursively walk its children. - cur_statement.true_statements, new_changed = convert_loops(cur_statement.true_statements) + cur_statement.true_statements, new_changed = convert_loops( + cur_statement.true_statements + ) changed = changed or new_changed - cur_statement.false_statements, new_changed = convert_loops(cur_statement.false_statements) + cur_statement.false_statements, new_changed = convert_loops( + cur_statement.false_statements + ) changed = changed or new_changed new_statements.append(cur_statement) @@ -3038,7 +3725,9 @@ class ByteCodeDecompiler(VerboseOutput): # This might be a candidate for white statement hoisting. if len(cur_statement.body) > 0: # Let's see if the first statement is an if statement with a break. - possible_cond, false_body = self.__extract_condition(cur_statement.body[0], None) + possible_cond, false_body = self.__extract_condition( + cur_statement.body[0], None + ) else: possible_cond = None @@ -3057,21 +3746,34 @@ class ByteCodeDecompiler(VerboseOutput): new_statements.append(cur_statement) elif ( - isinstance(cur_statement, (SetMemberStatement, StoreRegisterStatement, SetVariableStatement, SetLocalStatement)) and - isinstance(next_statement, DoWhileStatement) and - not isinstance(next_statement, ForStatement) + isinstance( + cur_statement, + ( + SetMemberStatement, + StoreRegisterStatement, + SetVariableStatement, + SetLocalStatement, + ), + ) + and isinstance(next_statement, DoWhileStatement) + and not isinstance(next_statement, ForStatement) ): # This is a possible conversion that hasn't been converted yet. Let's try to grab # the increment variable. if next_statement.body: - inc_variable = self.__get_increment_variable(next_statement.body[-1]) + inc_variable = self.__get_increment_variable( + next_statement.body[-1] + ) else: inc_variable = None # Now that we know what's being incremented, let's see if it matches our # initializer. inc_assignment = None - if inc_variable is not None and inc_variable != cur_statement.code_equiv(): + if ( + inc_variable is not None + and inc_variable != cur_statement.code_equiv() + ): # This doesn't match, so let's kill our reference. inc_variable = None else: @@ -3085,7 +3787,9 @@ class ByteCodeDecompiler(VerboseOutput): possible_cond = next_statement.cond.invert() if isinstance(possible_cond, TwoParameterIf): try: - if_variable = object_ref(possible_cond.conditional2, "") + if_variable = object_ref( + possible_cond.conditional2, "" + ) if inc_variable == if_variable: possible_cond = possible_cond.swap() except Exception: @@ -3093,7 +3797,9 @@ class ByteCodeDecompiler(VerboseOutput): false_body = [] else: # Let's see if the first statement is an if statement with a break. - possible_cond, false_body = self.__extract_condition(next_statement.body[0], inc_variable) + possible_cond, false_body = self.__extract_condition( + next_statement.body[0], inc_variable + ) else: possible_cond = None @@ -3105,7 +3811,12 @@ class ByteCodeDecompiler(VerboseOutput): possible_cond.invert(), inc_assignment, # Drop the increment and the if statement, since we are incorporating them. - false_body + (next_statement.body[:-1] if isinstance(next_statement, WhileStatement) else next_statement.body[1:-1]), + false_body + + ( + next_statement.body[:-1] + if isinstance(next_statement, WhileStatement) + else next_statement.body[1:-1] + ), local=isinstance(cur_statement, SetLocalStatement), ) changed = True @@ -3119,7 +3830,9 @@ class ByteCodeDecompiler(VerboseOutput): return convert_loops(statements) - def __swap_empty_ifs(self, statements: Sequence[Statement]) -> Tuple[List[Statement], bool]: + def __swap_empty_ifs( + self, statements: Sequence[Statement] + ) -> Tuple[List[Statement], bool]: # Get rid of empty if statements. If statements with empty if bodies and nonempty # else bodies will also be swapped. changed: bool = False @@ -3139,7 +3852,9 @@ class ByteCodeDecompiler(VerboseOutput): statement.false_statements, statement.true_statements, ) - elif (not statement.true_statements) and (not statement.false_statements): + elif (not statement.true_statements) and ( + not statement.false_statements + ): # Drop the if, it has no body. changed = True updated = True @@ -3152,7 +3867,9 @@ class ByteCodeDecompiler(VerboseOutput): if not changed: return statements, updated - def __swap_ugly_ifexprs(self, statements: Sequence[Statement]) -> Tuple[List[Statement], bool]: + def __swap_ugly_ifexprs( + self, statements: Sequence[Statement] + ) -> Tuple[List[Statement], bool]: # Swap if expressions that have a constant on the LHS. changed: bool = False @@ -3162,9 +3879,12 @@ class ByteCodeDecompiler(VerboseOutput): if isinstance(statement, IfStatement): if isinstance(statement.cond, TwoParameterIf): if ( - isinstance(statement.cond.conditional1, (str, int, bool, float, StringConstant)) and - isinstance(statement.cond.conditional2, Expression) and - not isinstance(statement.cond.conditional2, StringConstant) + isinstance( + statement.cond.conditional1, + (str, int, bool, float, StringConstant), + ) + and isinstance(statement.cond.conditional2, Expression) + and not isinstance(statement.cond.conditional2, StringConstant) ): changed = True return IfStatement( @@ -3177,7 +3897,9 @@ class ByteCodeDecompiler(VerboseOutput): statements = self.__walk(statements, swap_ugly_ifs) return statements, changed - def __drop_unneeded_else(self, statements: Sequence[Statement]) -> Tuple[List[Statement], bool]: + def __drop_unneeded_else( + self, statements: Sequence[Statement] + ) -> Tuple[List[Statement], bool]: # If an if has an else, but the last line of the if is a break/continue/return/throw/goto # then the else body doesn't need to exist, so hoist it up into the parent. If the false # statement also has an exit condition, don't drop it for asthetics. @@ -3188,15 +3910,25 @@ class ByteCodeDecompiler(VerboseOutput): for statement in statements: if isinstance(statement, IfStatement): if ( - statement.true_statements and - statement.false_statements and - isinstance( + statement.true_statements + and statement.false_statements + and isinstance( statement.true_statements[-1], - (ReturnStatement, NullReturnStatement, ThrowStatement, GotoStatement), - ) and - not isinstance( + ( + ReturnStatement, + NullReturnStatement, + ThrowStatement, + GotoStatement, + ), + ) + and not isinstance( statement.false_statements[-1], - (ReturnStatement, NullReturnStatement, ThrowStatement, GotoStatement), + ( + ReturnStatement, + NullReturnStatement, + ThrowStatement, + GotoStatement, + ), ) ): # We need to walk both halves still, but once we're done, swap the true/false @@ -3212,23 +3944,27 @@ class ByteCodeDecompiler(VerboseOutput): statement.cond = statement.cond.invert() new_statements.append(statement) elif ( - statement.true_statements and - statement.false_statements and - isinstance( + statement.true_statements + and statement.false_statements + and isinstance( statement.true_statements[-1], (BreakStatement, ContinueStatement), - ) and - not isinstance( + ) + and not isinstance( statement.false_statements[-1], (BreakStatement, ContinueStatement), ) ): # We need to walk both halves still, but once we're done, hoist the false # statements up to our level. - statement.true_statements, new_changed = update_ifs(statement.true_statements) + statement.true_statements, new_changed = update_ifs( + statement.true_statements + ) changed = changed or new_changed - new_false_statements, new_changed = update_ifs(statement.false_statements) + new_false_statements, new_changed = update_ifs( + statement.false_statements + ) changed = changed or new_changed statement.false_statements = [] @@ -3236,10 +3972,14 @@ class ByteCodeDecompiler(VerboseOutput): new_statements.append(statement) new_statements.extend(new_false_statements) else: - statement.true_statements, new_changed = update_ifs(statement.true_statements) + statement.true_statements, new_changed = update_ifs( + statement.true_statements + ) changed = changed or new_changed - statement.false_statements, new_changed = update_ifs(statement.false_statements) + statement.false_statements, new_changed = update_ifs( + statement.false_statements + ) changed = changed or new_changed new_statements.append(statement) @@ -3264,7 +4004,9 @@ class ByteCodeDecompiler(VerboseOutput): return update_ifs(statements) - def __gather_flow(self, parent_conditional: IfExpr, statements: Sequence[Statement]) -> Tuple[IfExpr, Dict[int, IfExpr], List[Tuple[IfExpr, Statement]]]: + def __gather_flow( + self, parent_conditional: IfExpr, statements: Sequence[Statement] + ) -> Tuple[IfExpr, Dict[int, IfExpr], List[Tuple[IfExpr, Statement]]]: flowed_statements: List[Tuple[IfExpr, Statement]] = [] running_conditional: IfExpr = parent_conditional gotos: Dict[int, IfExpr] = {} @@ -3277,8 +4019,14 @@ class ByteCodeDecompiler(VerboseOutput): for statement in statements: if isinstance(statement, IfStatement): - true_cond, true_gotos, true_statements = self.__gather_flow(AndIf(running_conditional, statement.cond).simplify(), statement.true_statements) - false_cond, false_gotos, false_statements = self.__gather_flow(AndIf(running_conditional, statement.cond.invert()).simplify(), statement.false_statements) + true_cond, true_gotos, true_statements = self.__gather_flow( + AndIf(running_conditional, statement.cond).simplify(), + statement.true_statements, + ) + false_cond, false_gotos, false_statements = self.__gather_flow( + AndIf(running_conditional, statement.cond.invert()).simplify(), + statement.false_statements, + ) flowed_statements.append((running_conditional, statement)) flowed_statements.extend(true_statements) @@ -3291,18 +4039,38 @@ class ByteCodeDecompiler(VerboseOutput): if true_cond == IsBooleanIf(False) and false_cond == IsBooleanIf(False): # Both conditionals exited. running_conditional = IsBooleanIf(False) - elif true_cond != IsBooleanIf(False) and false_cond == IsBooleanIf(False): + elif true_cond != IsBooleanIf(False) and false_cond == IsBooleanIf( + False + ): # The subsequent statements are only parented by the true conditional. - running_conditional = AndIf(true_cond, running_conditional).simplify() - elif true_cond == IsBooleanIf(False) and false_cond != IsBooleanIf(False): + running_conditional = AndIf( + true_cond, running_conditional + ).simplify() + elif true_cond == IsBooleanIf(False) and false_cond != IsBooleanIf( + False + ): # The subsequent statements are only parented by the false conditional. - running_conditional = AndIf(false_cond, running_conditional).simplify() + running_conditional = AndIf( + false_cond, running_conditional + ).simplify() else: # We are parented by either of the true/false cases. - running_conditional = OrIf(AndIf(true_cond, running_conditional), AndIf(false_cond, running_conditional)).simplify() + running_conditional = OrIf( + AndIf(true_cond, running_conditional), + AndIf(false_cond, running_conditional), + ).simplify() else: flowed_statements.append((running_conditional, statement)) - if isinstance(statement, (NullReturnStatement, ReturnStatement, ThrowStatement, BreakStatement, ContinueStatement)): + if isinstance( + statement, + ( + NullReturnStatement, + ReturnStatement, + ThrowStatement, + BreakStatement, + ContinueStatement, + ), + ): # We shouldn't find any more statements after this, unless there's a label. running_conditional = IsBooleanIf(False) elif isinstance(statement, GotoStatement): @@ -3319,9 +4087,21 @@ class ByteCodeDecompiler(VerboseOutput): # to which there is no goto. if stmt.location not in gotos: continue - goto_conditional = OrIf(gotos[stmt.location], goto_conditional).simplify() + goto_conditional = OrIf( + gotos[stmt.location], goto_conditional + ).simplify() flowed_statements[i] = (OrIf(cond, goto_conditional).simplify(), stmt) - if isinstance(stmt, (NullReturnStatement, ReturnStatement, ThrowStatement, GotoStatement, BreakStatement, ContinueStatement)): + if isinstance( + stmt, + ( + NullReturnStatement, + ReturnStatement, + ThrowStatement, + GotoStatement, + BreakStatement, + ContinueStatement, + ), + ): # The current running conditional no longer applies after this statement. goto_conditional = IsBooleanIf(False) @@ -3380,10 +4160,16 @@ class ByteCodeDecompiler(VerboseOutput): # Finally, simplify it for ease of comparison. return combined.simplify() - def get_candidate_group(conditional: IfExpr, statement: IfStatement) -> Optional[List[IfStatement]]: + def get_candidate_group( + conditional: IfExpr, statement: IfStatement + ) -> Optional[List[IfStatement]]: candidate_statements: List[IfStatement] = [statement] - if candidates and (not statement.false_statements) and len(statement.true_statements) == 1: + if ( + candidates + and (not statement.false_statements) + and len(statement.true_statements) == 1 + ): while True: # First, is the current combination a valid combined or statement? candidate_true_expr = get_compound_if(candidate_statements) @@ -3412,7 +4198,9 @@ class ByteCodeDecompiler(VerboseOutput): if isinstance(statement, IfStatement): # See if this is a compound if pattern. - candidate_statements = get_candidate_group(parent_conditional, statement) + candidate_statements = get_candidate_group( + parent_conditional, statement + ) if candidate_statements is None: # Move past this statement, we don't care about it. i += 1 @@ -3480,7 +4268,17 @@ class ByteCodeDecompiler(VerboseOutput): false_statements.append(statement) i += 1 - if isinstance(statement, (NullReturnStatement, ReturnStatement, ThrowStatement, GotoStatement, BreakStatement, ContinueStatement)): + if isinstance( + statement, + ( + NullReturnStatement, + ReturnStatement, + ThrowStatement, + GotoStatement, + BreakStatement, + ContinueStatement, + ), + ): break # Now, add this new if statement, but make sure to gather up @@ -3525,7 +4323,9 @@ class ByteCodeDecompiler(VerboseOutput): ) changed = changed or child_changed false_statements, child_changed = self.__hoist_compound_ifs( - AndIf(parent_conditional, stmt.cond.invert()).simplify(), + AndIf( + parent_conditional, stmt.cond.invert() + ).simplify(), stmt.false_statements, candidates, flow, @@ -3539,7 +4339,9 @@ class ByteCodeDecompiler(VerboseOutput): ) ) elif isinstance(stmt, DoWhileStatement): - new_body, child_changed = self.__rearrange_compound_ifs(stmt.body) + new_body, child_changed = self.__rearrange_compound_ifs( + stmt.body + ) changed = changed or child_changed stmt.body = new_body new_statements.append(stmt) @@ -3549,13 +4351,17 @@ class ByteCodeDecompiler(VerboseOutput): # We hoisted a compound if, so report a change. changed = True elif isinstance(statement, DoWhileStatement): - statement.body, child_changed = self.__rearrange_compound_ifs(statement.body) + statement.body, child_changed = self.__rearrange_compound_ifs( + statement.body + ) changed = changed or child_changed new_statements.append(statement) i += 1 elif isinstance(statement, SwitchStatement): for case in statement.cases: - case.statements, new_changed = self.__rearrange_compound_ifs(case.statements) + case.statements, new_changed = self.__rearrange_compound_ifs( + case.statements + ) changed = changed or new_changed new_statements.append(statement) i += 1 @@ -3565,16 +4371,22 @@ class ByteCodeDecompiler(VerboseOutput): return new_statements, changed - def __rearrange_compound_ifs(self, statements: Sequence[Statement]) -> Tuple[List[Statement], bool]: + def __rearrange_compound_ifs( + self, statements: Sequence[Statement] + ) -> Tuple[List[Statement], bool]: candidates = self.__gather_candidates(statements) if candidates: _, _, flow = self.__gather_flow(IsBooleanIf(True), statements) else: flow = [] - return self.__hoist_compound_ifs(IsBooleanIf(True), statements, candidates, flow) + return self.__hoist_compound_ifs( + IsBooleanIf(True), statements, candidates, flow + ) - def __convert_switches(self, statements: Sequence[Statement]) -> Tuple[List[Statement], bool]: + def __convert_switches( + self, statements: Sequence[Statement] + ) -> Tuple[List[Statement], bool]: # Convert any cascading if statements comparing the same variable/register against a series # of constants into switch statements. This relies on a previous run of anohter optimizer # that ensures that variables show up on the LHS of if statements. @@ -3585,19 +4397,31 @@ class ByteCodeDecompiler(VerboseOutput): def get_lhs(statement: IfStatement) -> Optional[Expression]: if not isinstance(statement.cond, TwoParameterIf): return None - if statement.cond.comp not in {TwoParameterIf.EQUALS, TwoParameterIf.NOT_EQUALS, TwoParameterIf.STRICT_EQUALS, TwoParameterIf.STRICT_NOT_EQUALS}: + if statement.cond.comp not in { + TwoParameterIf.EQUALS, + TwoParameterIf.NOT_EQUALS, + TwoParameterIf.STRICT_EQUALS, + TwoParameterIf.STRICT_NOT_EQUALS, + }: return None - if not isinstance(statement.cond.conditional1, (Variable, Register, Member)): + if not isinstance( + statement.cond.conditional1, (Variable, Register, Member) + ): return None # We intentionally widen this to allow variables, since there are a lot of places in # various code that uses variables as constants. We made up the entire language we are # decompiling into so we can allow this for readability. - if not isinstance(statement.cond.conditional2, (str, bool, float, int, StringConstant, Variable)): + if not isinstance( + statement.cond.conditional2, + (str, bool, float, int, StringConstant, Variable), + ): return None return statement.cond.conditional1 - def get_next_candidate(statements: List[Statement], lhs: Expression) -> Optional[IfStatement]: + def get_next_candidate( + statements: List[Statement], lhs: Expression + ) -> Optional[IfStatement]: if len(statements) != 1: return None statement = statements[0] @@ -3695,7 +4519,10 @@ class ByteCodeDecompiler(VerboseOutput): cond = cast(TwoParameterIf, statement.cond) # If its already switched, leave it alone. - if cond.comp in {TwoParameterIf.EQUALS, TwoParameterIf.STRICT_EQUALS}: + if cond.comp in { + TwoParameterIf.EQUALS, + TwoParameterIf.STRICT_EQUALS, + }: new_batch.append(statement) return statement @@ -3726,7 +4553,9 @@ class ByteCodeDecompiler(VerboseOutput): # This isn't even an if statement. We should never hit # this but the type checker wants to be happy. return statement - if has_break(cur_statement.true_statements) or has_break(cur_statement.false_statements): + if has_break(cur_statement.true_statements) or has_break( + cur_statement.false_statements + ): # This code already uses a 'break' statement. If we stuck it # in a switch, it would change the sematics of this statement # so we have no choice but to ignore this. @@ -3809,7 +4638,9 @@ class ByteCodeDecompiler(VerboseOutput): statements = self.__walk(statements, replace_if_with_switch) return statements, changed - def __convert_if_gotos(self, statements: Sequence[Statement]) -> Tuple[List[Statement], bool]: + def __convert_if_gotos( + self, statements: Sequence[Statement] + ) -> Tuple[List[Statement], bool]: # Find if statements in the middle of a chunk of code whose last true statement is # a return/goto/throw. Take all the following statements and put them in the if # statement's false clause. We do this because it allows us to recognize other @@ -3825,9 +4656,17 @@ class ByteCodeDecompiler(VerboseOutput): if isinstance(cur_statement, IfStatement): if ( - cur_statement.true_statements and - not cur_statement.false_statements and - isinstance(cur_statement.true_statements[-1], (NullReturnStatement, ReturnStatement, ThrowStatement, GotoStatement)) + cur_statement.true_statements + and not cur_statement.false_statements + and isinstance( + cur_statement.true_statements[-1], + ( + NullReturnStatement, + ReturnStatement, + ThrowStatement, + GotoStatement, + ), + ) ): # This is a candidate! Take all following statements up until the possible label we jump to and # make them into the false chunk. @@ -3842,7 +4681,9 @@ class ByteCodeDecompiler(VerboseOutput): false_statements: List[Statement] = [] while i < len(statements): false_statement = statements[i] - if stop_at_label is not None and isinstance(false_statement, DefineLabelStatement): + if stop_at_label is not None and isinstance( + false_statement, DefineLabelStatement + ): if stop_at_label == false_statement.location: # Exit early, the rest of the code including this # label is not part of the else case. @@ -3860,23 +4701,31 @@ class ByteCodeDecompiler(VerboseOutput): i += 1 # Regardless of whether we modified the if statement, recurse down its true and false path. - cur_statement.true_statements, new_changed = self.__convert_if_gotos(cur_statement.true_statements) + cur_statement.true_statements, new_changed = self.__convert_if_gotos( + cur_statement.true_statements + ) changed = changed or new_changed - cur_statement.false_statements, new_changed = self.__convert_if_gotos(cur_statement.false_statements) + cur_statement.false_statements, new_changed = self.__convert_if_gotos( + cur_statement.false_statements + ) changed = changed or new_changed new_statements.append(cur_statement) elif isinstance(cur_statement, SwitchStatement): for case in cur_statement.cases: - case.statements, new_changed = self.__convert_if_gotos(case.statements) + case.statements, new_changed = self.__convert_if_gotos( + case.statements + ) changed = changed or new_changed new_statements.append(cur_statement) i += 1 elif isinstance(cur_statement, DoWhileStatement): - cur_statement.body, new_changed = self.__convert_if_gotos(cur_statement.body) + cur_statement.body, new_changed = self.__convert_if_gotos( + cur_statement.body + ) changed = changed or new_changed new_statements.append(cur_statement) @@ -3888,20 +4737,30 @@ class ByteCodeDecompiler(VerboseOutput): return new_statements, changed - def __convert_switch_gotos(self, statements: Sequence[Statement]) -> Tuple[List[Statement], bool]: + def __convert_switch_gotos( + self, statements: Sequence[Statement] + ) -> Tuple[List[Statement], bool]: # Go through and find switch cases that goto the next line in the switch, replacing those # with break statements. - def find_gotos(statements: Sequence[Statement], parent_next_statement: Statement, goto_next_statement: Statement) -> Set[GotoStatement]: + def find_gotos( + statements: Sequence[Statement], + parent_next_statement: Statement, + goto_next_statement: Statement, + ) -> Set[GotoStatement]: gotos: Set[GotoStatement] = set() for i in range(len(statements)): cur_statement = statements[i] - next_statement = statements[i + 1] if (i < len(statements) - 1) else parent_next_statement + next_statement = ( + statements[i + 1] + if (i < len(statements) - 1) + else parent_next_statement + ) if ( - isinstance(cur_statement, GotoStatement) and - isinstance(goto_next_statement, DefineLabelStatement) and - cur_statement.location == goto_next_statement.location + isinstance(cur_statement, GotoStatement) + and isinstance(goto_next_statement, DefineLabelStatement) + and cur_statement.location == goto_next_statement.location ): # We are jumping to a location where we could insert a break. gotos.add(cur_statement) @@ -3909,11 +4768,25 @@ class ByteCodeDecompiler(VerboseOutput): elif isinstance(cur_statement, DoWhileStatement): # We don't want to track gotos into while loops because replacing one of # these with a break would change the flow. - gotos.update(find_gotos(cur_statement.body, next_statement, NopStatement())) + gotos.update( + find_gotos(cur_statement.body, next_statement, NopStatement()) + ) elif isinstance(cur_statement, IfStatement): - gotos.update(find_gotos(cur_statement.true_statements, next_statement, goto_next_statement)) - gotos.update(find_gotos(cur_statement.false_statements, next_statement, goto_next_statement)) + gotos.update( + find_gotos( + cur_statement.true_statements, + next_statement, + goto_next_statement, + ) + ) + gotos.update( + find_gotos( + cur_statement.false_statements, + next_statement, + goto_next_statement, + ) + ) elif isinstance(cur_statement, SwitchStatement): # The next entry after this switch is what we're interested in, so pass it @@ -3921,7 +4794,9 @@ class ByteCodeDecompiler(VerboseOutput): # We don't care about the semantic next statement for the purposes of this # call, so just set it as a NOP. for case in cur_statement.cases: - gotos.update(find_gotos(case.statements, NopStatement(), next_statement)) + gotos.update( + find_gotos(case.statements, NopStatement(), next_statement) + ) return gotos @@ -4015,16 +4890,22 @@ class ByteCodeDecompiler(VerboseOutput): if unmatched_gotos: formatted_labels = ", ".join(f"label_{x}" for x in unmatched_gotos) - raise Exception(f"Logic error, gotos found jumping to the following labels which don't exist: {formatted_labels}") + raise Exception( + f"Logic error, gotos found jumping to the following labels which don't exist: {formatted_labels}" + ) if unmatched_labels and self.optimize: formatted_labels = ", ".join(f"label_{x}" for x in unmatched_labels) - raise Exception(f"Logic error, labels found with no gotos pointing at them: {formatted_labels}") + raise Exception( + f"Logic error, labels found with no gotos pointing at them: {formatted_labels}" + ) def __verify_no_empty_ifs(self, statements: Sequence[Statement]) -> None: def check_ifs(statement: Statement) -> Optional[Statement]: if isinstance(statement, IfStatement): if (not statement.true_statements) and (not statement.false_statements): - raise Exception(f"If statement {statement} has no true or false statements inside it!") + raise Exception( + f"If statement {statement} has no true or false statements inside it!" + ) return statement self.__walk(statements, check_ifs) @@ -4055,7 +4936,9 @@ class ByteCodeDecompiler(VerboseOutput): # Now, separate chunks out into chunks and loops. self.vprint("Identifying and separating loops...") - chunks_and_loops = self.__separate_loops(start_id, chunks, dominators, offset_map) + chunks_and_loops = self.__separate_loops( + start_id, chunks, dominators, offset_map + ) # Now, break the graph anywhere where we have control # flow that ends the execution (return, throw, goto end). @@ -4064,7 +4947,9 @@ class ByteCodeDecompiler(VerboseOutput): # Now, identify any remaining control flow logic. self.vprint("Identifying and separating ifs...") - chunks_loops_and_ifs = self.__separate_ifs(start_id, None, chunks_and_loops, offset_map) + chunks_loops_and_ifs = self.__separate_ifs( + start_id, None, chunks_and_loops, offset_map + ) # At this point, we *should* have a directed graph where there are no # backwards refs and every fork has been identified as an if. This means diff --git a/bemani/format/afp/geo.py b/bemani/format/afp/geo.py index 75d857c..6f841c4 100644 --- a/bemani/format/afp/geo.py +++ b/bemani/format/afp/geo.py @@ -39,27 +39,29 @@ class Shape: def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { - 'name': self.name, - 'vertex_points': [p.as_dict(*args, **kwargs) for p in self.vertex_points], - 'tex_points': [p.as_dict(*args, **kwargs) for p in self.tex_points], - 'tex_colors': [c.as_dict(*args, **kwargs) for c in self.tex_colors], - 'draw_params': [d.as_dict(*args, **kwargs) for d in self.draw_params], - 'bounds': self.bounds.as_dict(args, kwargs), + "name": self.name, + "vertex_points": [p.as_dict(*args, **kwargs) for p in self.vertex_points], + "tex_points": [p.as_dict(*args, **kwargs) for p in self.tex_points], + "tex_colors": [c.as_dict(*args, **kwargs) for c in self.tex_colors], + "draw_params": [d.as_dict(*args, **kwargs) for d in self.draw_params], + "bounds": self.bounds.as_dict(args, kwargs), } def __repr__(self) -> str: - return os.linesep.join([ - *[f"vertex point: {vertex}" for vertex in self.vertex_points], - *[f"tex point: {tex}" for tex in self.tex_points], - *[f"tex color: {color}" for color in self.tex_colors], - *[f"draw params: {params}" for params in self.draw_params], - *[f"bounds: {self.bounds}"], - ]) + return os.linesep.join( + [ + *[f"vertex point: {vertex}" for vertex in self.vertex_points], + *[f"tex point: {tex}" for tex in self.tex_points], + *[f"tex color: {color}" for color in self.tex_colors], + *[f"draw params: {params}" for params in self.draw_params], + *[f"bounds: {self.bounds}"], + ] + ) def get_until_null(self, offset: int) -> bytes: out = b"" while self.data[offset] != 0: - out += self.data[offset:(offset + 1)] + out += self.data[offset : (offset + 1)] offset += 1 return out @@ -85,12 +87,25 @@ class Shape: if fileflags & ~0xC: raise Exception(f"Unexpected file flags {fileflags} in GE2D structure!") - vertex_count, tex_count, color_count, label_count, render_params_count, _ = struct.unpack( + ( + vertex_count, + tex_count, + color_count, + label_count, + render_params_count, + _, + ) = struct.unpack( f"{endian}HHHHHH", self.data[20:32], ) - vertex_offset, tex_offset, color_offset, label_offset, render_params_offset = struct.unpack( + ( + vertex_offset, + tex_offset, + color_offset, + label_offset, + render_params_offset, + ) = struct.unpack( f"{endian}IIIII", self.data[32:52], ) @@ -101,7 +116,9 @@ class Shape: # point and there are only 8 of them, making a rectangle. for vertexno in range(vertex_count): vertexno_offset = vertex_offset + (8 * vertexno) - x, y = struct.unpack(f"{endian}ff", self.data[vertexno_offset:vertexno_offset + 8]) + x, y = struct.unpack( + f"{endian}ff", self.data[vertexno_offset : vertexno_offset + 8] + ) vertex_points.append(Point(x, y)) self.vertex_points = vertex_points @@ -109,7 +126,9 @@ class Shape: if tex_offset != 0: for texno in range(tex_count): texno_offset = tex_offset + (8 * texno) - x, y = struct.unpack(f"{endian}ff", self.data[texno_offset:texno_offset + 8]) + x, y = struct.unpack( + f"{endian}ff", self.data[texno_offset : texno_offset + 8] + ) tex_points.append(Point(x, y)) self.tex_points = tex_points @@ -117,7 +136,9 @@ class Shape: if color_offset != 0: for colorno in range(color_count): colorno_offset = color_offset + (4 * colorno) - rgba = struct.unpack(f"{endian}I", self.data[colorno_offset:colorno_offset + 4])[0] + rgba = struct.unpack( + f"{endian}I", self.data[colorno_offset : colorno_offset + 4] + )[0] color = Color( a=(rgba & 0xFF) / 255.0, b=((rgba >> 8) & 0xFF) / 255.0, @@ -131,7 +152,9 @@ class Shape: if label_offset != 0: for labelno in range(label_count): labelno_offset = label_offset + (4 * labelno) - labelptr = struct.unpack(f"{endian}I", self.data[labelno_offset:labelno_offset + 4])[0] + labelptr = struct.unpack( + f"{endian}I", self.data[labelno_offset : labelno_offset + 4] + )[0] bytedata = self.get_until_null(labelptr) labels.append(descramble_text(bytedata, text_obfuscated)) @@ -175,19 +198,34 @@ class Shape: # are used when drawing shapes, whether to use a blend value or draw a primitive, etc. for render_paramsno in range(render_params_count): render_paramsno_offset = render_params_offset + (16 * render_paramsno) - mode, flags, tex1, tex2, trianglecount, unk, rgba, triangleoffset = struct.unpack( + ( + mode, + flags, + tex1, + tex2, + trianglecount, + unk, + rgba, + triangleoffset, + ) = struct.unpack( f"{endian}BBBBHHII", - self.data[(render_paramsno_offset):(render_paramsno_offset + 16)] + self.data[(render_paramsno_offset) : (render_paramsno_offset + 16)], ) if mode != 4: raise Exception("Unexpected mode in GE2D structure!") if (flags & 0x2) and not labels: - raise Exception("GE2D structure has a texture, but no region labels present!") + raise Exception( + "GE2D structure has a texture, but no region labels present!" + ) if (flags & 0x2) and (tex1 == 0xFF): - raise Exception("GE2D structure requests a texture, but no texture pointer present!") + raise Exception( + "GE2D structure requests a texture, but no texture pointer present!" + ) if tex2 != 0xFF: - raise Exception("GE2D structure requests a second texture, but we don't support this!") + raise Exception( + "GE2D structure requests a second texture, but we don't support this!" + ) if unk != 0x0: raise Exception("Unhandled unknown data in GE2D structure!") @@ -200,8 +238,17 @@ class Shape: verticies: List[int] = [] for render_paramstriangleno in range(trianglecount): - render_paramstriangleno_offset = triangleoffset + (2 * render_paramstriangleno) - tex_offset = struct.unpack(f"{endian}H", self.data[render_paramstriangleno_offset:(render_paramstriangleno_offset + 2)])[0] + render_paramstriangleno_offset = triangleoffset + ( + 2 * render_paramstriangleno + ) + tex_offset = struct.unpack( + f"{endian}H", + self.data[ + render_paramstriangleno_offset : ( + render_paramstriangleno_offset + 2 + ) + ], + )[0] verticies.append(tex_offset) # Seen bits are 0x1, 0x2, 0x4, 0x8 so far. @@ -247,10 +294,10 @@ class DrawParams: def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { - 'flags': self.flags, - 'region': self.region, - 'vertexes': self.vertexes, - 'blend': self.blend.as_dict(*args, **kwargs) if self.blend else None, + "flags": self.flags, + "region": self.region, + "vertexes": self.vertexes, + "blend": self.blend.as_dict(*args, **kwargs) if self.blend else None, } def __repr__(self) -> str: diff --git a/bemani/format/afp/render.py b/bemani/format/afp/render.py index 2c70fc7..f5a4968 100644 --- a/bemani/format/afp/render.py +++ b/bemani/format/afp/render.py @@ -47,7 +47,13 @@ class RegisteredClip: # SWF as well as AP2DefineSpriteTags which are essentially embedded movie clips. The # tag_id is the AP2DefineSpriteTag that created us, or None if this is the clip for # the root of the movie. - def __init__(self, tag_id: Optional[int], frames: List[Frame], tags: List[Tag], labels: Dict[str, int]) -> None: + def __init__( + self, + tag_id: Optional[int], + frames: List[Frame], + tags: List[Tag], + labels: Dict[str, int], + ) -> None: self.tag_id = tag_id self.frames = frames self.tags = tags @@ -59,7 +65,14 @@ class RegisteredClip: class RegisteredShape: # A shape that we are rendering, as placed by some placed clip somewhere. - def __init__(self, tag_id: int, vertex_points: List[Point], tex_points: List[Point], tex_colors: List[Color], draw_params: List[DrawParams]) -> None: + def __init__( + self, + tag_id: int, + vertex_points: List[Point], + tex_points: List[Point], + tex_colors: List[Color], + draw_params: List[DrawParams], + ) -> None: self.tag_id = tag_id self.vertex_points: List[Point] = vertex_points self.tex_points: List[Point] = tex_points @@ -122,7 +135,9 @@ class PlacedObject: self.visible: bool = True @property - def source(self) -> Union[RegisteredClip, RegisteredShape, RegisteredImage, RegisteredDummy]: + def source( + self, + ) -> Union[RegisteredClip, RegisteredShape, RegisteredImage, RegisteredDummy]: raise NotImplementedError("Only implemented in subclass!") @property @@ -153,7 +168,17 @@ class PlacedShape(PlacedObject): mask: Optional[Mask], source: RegisteredShape, ) -> None: - super().__init__(object_id, depth, rotation_origin, transform, projection, mult_color, add_color, blend, mask) + super().__init__( + object_id, + depth, + rotation_origin, + transform, + projection, + mult_color, + add_color, + blend, + mask, + ) self.__source = source @property @@ -180,7 +205,17 @@ class PlacedClip(PlacedObject): mask: Optional[Mask], source: RegisteredClip, ) -> None: - super().__init__(object_id, depth, rotation_origin, transform, projection, mult_color, add_color, blend, mask) + super().__init__( + object_id, + depth, + rotation_origin, + transform, + projection, + mult_color, + add_color, + blend, + mask, + ) self.placed_objects: List[PlacedObject] = [] self.frame: int = 0 self.unplayed_tags: List[int] = [i for i in range(len(source.tags))] @@ -221,9 +256,9 @@ class PlacedClip(PlacedObject): def __repr__(self) -> str: return ( - f"PlacedClip(object_id={self.object_id}, depth={self.depth}, source={self.source}, frame={self.frame}, " + - f"requested_frame={self.requested_frame}, total_frames={len(self.source.frames)}, playing={self.playing}, " + - f"finished={self.finished})" + f"PlacedClip(object_id={self.object_id}, depth={self.depth}, source={self.source}, frame={self.frame}, " + + f"requested_frame={self.requested_frame}, total_frames={len(self.source.frames)}, playing={self.playing}, " + + f"finished={self.finished})" ) def __resolve_frame(self, frame: Any) -> Optional[int]: @@ -344,7 +379,17 @@ class PlacedImage(PlacedObject): mask: Optional[Mask], source: RegisteredImage, ) -> None: - super().__init__(object_id, depth, rotation_origin, transform, projection, mult_color, add_color, blend, mask) + super().__init__( + object_id, + depth, + rotation_origin, + transform, + projection, + mult_color, + add_color, + blend, + mask, + ) self.__source = source @property @@ -370,7 +415,17 @@ class PlacedDummy(PlacedObject): mask: Optional[Mask], source: RegisteredDummy, ) -> None: - super().__init__(object_id, depth, rotation_origin, transform, projection, mult_color, add_color, blend, mask) + super().__init__( + object_id, + depth, + rotation_origin, + transform, + projection, + mult_color, + add_color, + blend, + mask, + ) self.__source = source @property @@ -409,7 +464,7 @@ class Global: # This is identical to regular gotoAndPlay, however it also recursively # goes through and sets all child clips playing as well. try: - meth = getattr(self.clip, 'gotoAndPlay') + meth = getattr(self.clip, "gotoAndPlay") # Call it, set the return on the stack. retval = meth(frame) @@ -426,10 +481,14 @@ class Global: return retval except AttributeError: # Function does not exist! - print(f"WARNING: Tried to call gotoAndPlay({frame}) on {self.clip} but that method doesn't exist!") + print( + f"WARNING: Tried to call gotoAndPlay({frame}) on {self.clip} but that method doesn't exist!" + ) return UNDEFINED - def __find_parent(self, parent: PlacedClip, child: PlacedClip) -> Optional[PlacedClip]: + def __find_parent( + self, parent: PlacedClip, child: PlacedClip + ) -> Optional[PlacedClip]: for obj in parent.placed_objects: if obj is child: # This is us, so the parent is our parent. @@ -446,9 +505,18 @@ class Global: class AEPLib: - def aep_set_rect_mask(self, thisptr: Any, left: Any, right: Any, top: Any, bottom: Any) -> None: - if not isinstance(left, (int, float)) or not isinstance(right, (int, float)) or not isinstance(top, (int, float)) or not isinstance(bottom, (int, float)): - print(f"WARNING: Ignoring aeplib.aep_set_rect_mask call with invalid parameters {left}, {right}, {top}, {bottom}!") + def aep_set_rect_mask( + self, thisptr: Any, left: Any, right: Any, top: Any, bottom: Any + ) -> None: + if ( + not isinstance(left, (int, float)) + or not isinstance(right, (int, float)) + or not isinstance(top, (int, float)) + or not isinstance(bottom, (int, float)) + ): + print( + f"WARNING: Ignoring aeplib.aep_set_rect_mask call with invalid parameters {left}, {right}, {top}, {bottom}!" + ) return if isinstance(thisptr, PlacedObject): thisptr.mask = Mask( @@ -460,7 +528,9 @@ class AEPLib: ), ) else: - print(f"WARNING: Ignoring aeplib.aep_set_rect_mask call with unrecognized target {thisptr}!") + print( + f"WARNING: Ignoring aeplib.aep_set_rect_mask call with unrecognized target {thisptr}!" + ) def aep_set_set_frame(self, thisptr: Any, frame: Any) -> None: # This appears to be some sort of callback that the game or other animations can use to figure out @@ -471,49 +541,59 @@ class AEPLib: def aep_set_frame_control(self, thisptr: Any, depth: Any, frame: Any) -> None: if not isinstance(thisptr, PlacedClip): - print(f"WARNING: Ignoring aeplib.aep_set_frame_control with unrecognized current object {thisptr}!") + print( + f"WARNING: Ignoring aeplib.aep_set_frame_control with unrecognized current object {thisptr}!" + ) return for obj in thisptr.placed_objects: if obj.depth == depth: if not isinstance(obj, PlacedClip): - print(f"WARNING: Ignoring aeplib.aep_set_frame_control called on object {obj} at depth {depth}!") + print( + f"WARNING: Ignoring aeplib.aep_set_frame_control called on object {obj} at depth {depth}!" + ) return obj.setInvisibleUntil(frame) return - print(f"WARNING: Ignoring aeplib.aep_set_frame_control called on nonexistent object at depth {depth}!") + print( + f"WARNING: Ignoring aeplib.aep_set_frame_control called on nonexistent object at depth {depth}!" + ) def gotoAndPlay(self, thisptr: Any, frame: Any) -> Any: # This appears to be a wrapper to allow calling gotoAndPlay on clips. try: - meth = getattr(thisptr, 'gotoAndPlay') + meth = getattr(thisptr, "gotoAndPlay") # Call it, set the return on the stack. return meth(frame) except AttributeError: # Function does not exist! - print(f"WARNING: Tried to call gotoAndPlay({frame}) on {thisptr} but that method doesn't exist!") + print( + f"WARNING: Tried to call gotoAndPlay({frame}) on {thisptr} but that method doesn't exist!" + ) return UNDEFINED def gotoAndStop(self, thisptr: Any, frame: Any) -> Any: # This appears to be a wrapper to allow calling gotoAndStop on clips. try: - meth = getattr(thisptr, 'gotoAndStop') + meth = getattr(thisptr, "gotoAndStop") # Call it, set the return on the stack. return meth(frame) except AttributeError: # Function does not exist! - print(f"WARNING: Tried to call gotoAndStop({frame}) on {thisptr} but that method doesn't exist!") + print( + f"WARNING: Tried to call gotoAndStop({frame}) on {thisptr} but that method doesn't exist!" + ) return UNDEFINED def deepGotoAndPlay(self, thisptr: Any, frame: Any) -> Any: # This is identical to regular gotoAndPlay, however it also recursively # goes through and sets all child clips playing as well. try: - meth = getattr(thisptr, 'gotoAndPlay') + meth = getattr(thisptr, "gotoAndPlay") # Call it, set the return on the stack. retval = meth(frame) @@ -530,14 +610,16 @@ class AEPLib: return retval except AttributeError: # Function does not exist! - print(f"WARNING: Tried to call gotoAndPlay({frame}) on {thisptr} but that method doesn't exist!") + print( + f"WARNING: Tried to call gotoAndPlay({frame}) on {thisptr} but that method doesn't exist!" + ) return UNDEFINED def deepGotoAndStop(self, thisptr: Any, frame: Any) -> Any: # This is identical to regular gotoAndStop, however it also recursively # goes through and sets all child clips stopped as well. try: - meth = getattr(thisptr, 'gotoAndStop') + meth = getattr(thisptr, "gotoAndStop") # Call it, set the return on the stack. retval = meth(frame) @@ -554,46 +636,63 @@ class AEPLib: return retval except AttributeError: # Function does not exist! - print(f"WARNING: Tried to call gotoAndStop({frame}) on {thisptr} but that method doesn't exist!") + print( + f"WARNING: Tried to call gotoAndStop({frame}) on {thisptr} but that method doesn't exist!" + ) return UNDEFINED def play(self, thisptr: Any) -> Any: # This appears to be a wrapper to allow calling play on clips. try: - meth = getattr(thisptr, 'play') + meth = getattr(thisptr, "play") # Call it, set the return on the stack. return meth() except AttributeError: # Function does not exist! - print(f"WARNING: Tried to call play() on {thisptr} but that method doesn't exist!") + print( + f"WARNING: Tried to call play() on {thisptr} but that method doesn't exist!" + ) return UNDEFINED def stop(self, thisptr: Any) -> Any: # This appears to be a wrapper to allow calling stop on clips. try: - meth = getattr(thisptr, 'stop') + meth = getattr(thisptr, "stop") # Call it, set the return on the stack. return meth() except AttributeError: # Function does not exist! - print(f"WARNING: Tried to call stop() on {thisptr} but that method doesn't exist!") + print( + f"WARNING: Tried to call stop() on {thisptr} but that method doesn't exist!" + ) return UNDEFINED class ASDLib: def sound_play(self, sound: Any) -> None: if not isinstance(sound, str): - print(f"WARNING: Ignoring asdlib.sound_play call with invalid parameters {sound}!") - print(f"WARNING: Requested sound {sound} be played but we don't support sound yet!") + print( + f"WARNING: Ignoring asdlib.sound_play call with invalid parameters {sound}!" + ) + print( + f"WARNING: Requested sound {sound} be played but we don't support sound yet!" + ) MissingThis = object() class AFPRenderer(VerboseOutput): - def __init__(self, shapes: Dict[str, Shape] = {}, textures: Dict[str, Image.Image] = {}, swfs: Dict[str, SWF] = {}, single_threaded: bool = False, enable_aa: bool = False) -> None: + def __init__( + self, + shapes: Dict[str, Shape] = {}, + textures: Dict[str, Image.Image] = {}, + swfs: Dict[str, SWF] = {}, + single_threaded: bool = False, + enable_aa: bool = False, + ) -> None: super().__init__() # Options for rendering @@ -606,14 +705,17 @@ class AFPRenderer(VerboseOutput): self.swfs: Dict[str, SWF] = swfs # Internal render parameters. - self.__registered_objects: Dict[int, Union[RegisteredShape, RegisteredClip, RegisteredImage, RegisteredDummy]] = {} + self.__registered_objects: Dict[ + int, + Union[RegisteredShape, RegisteredClip, RegisteredImage, RegisteredDummy], + ] = {} self.__root: Optional[PlacedClip] = None self.__camera: Optional[PlacedCamera] = None # List of imports that we provide stub implementations for. self.__stubbed_swfs: Set[str] = { - 'aeplib.aeplib', - 'aeplib.__Packages.aeplib', + "aeplib.aeplib", + "aeplib.__Packages.aeplib", } def add_shape(self, name: str, data: Shape) -> None: @@ -650,10 +752,18 @@ class AFPRenderer(VerboseOutput): # This is the SWF we care about. with self.debugging(verbose): swf.color = background_color or swf.color - yield from self.__render(swf, only_depths, only_frames, movie_transform, background_image, overridden_width, overridden_height) + yield from self.__render( + swf, + only_depths, + only_frames, + movie_transform, + background_image, + overridden_width, + overridden_height, + ) return - raise Exception(f'{path} not found in registered SWFs!') + raise Exception(f"{path} not found in registered SWFs!") def compute_path_location( self, @@ -665,7 +775,7 @@ class AFPRenderer(VerboseOutput): # This is the SWF we care about. return swf.location - raise Exception(f'{path} not found in registered SWFs!') + raise Exception(f"{path} not found in registered SWFs!") def compute_path_frames( self, @@ -678,7 +788,7 @@ class AFPRenderer(VerboseOutput): # This is the SWF we care about. return len(swf.frames) - raise Exception(f'{path} not found in registered SWFs!') + raise Exception(f"{path} not found in registered SWFs!") def compute_path_frame_duration( self, @@ -692,7 +802,7 @@ class AFPRenderer(VerboseOutput): spf = 1.0 / swf.fps return int(spf * 1000.0) - raise Exception(f'{path} not found in registered SWFs!') + raise Exception(f"{path} not found in registered SWFs!") def compute_path_size( self, @@ -704,24 +814,32 @@ class AFPRenderer(VerboseOutput): if swf.exported_name == path: return swf.location - raise Exception(f'{path} not found in registered SWFs!') + raise Exception(f"{path} not found in registered SWFs!") def list_paths(self, verbose: bool = False) -> Generator[str, None, None]: # Given the loaded animations, return a list of possible paths to render. for _name, swf in self.swfs.items(): yield swf.exported_name - def __execute_bytecode(self, bytecode: ByteCode, clip: PlacedClip, thisptr: Optional[Any] = MissingThis, prefix: str="") -> None: + def __execute_bytecode( + self, + bytecode: ByteCode, + clip: PlacedClip, + thisptr: Optional[Any] = MissingThis, + prefix: str = "", + ) -> None: if self.__root is None: - raise Exception("Logic error, executing bytecode outside of a rendering movie clip!") + raise Exception( + "Logic error, executing bytecode outside of a rendering movie clip!" + ) thisobj = clip if (thisptr is MissingThis) else thisptr globalobj = Global(self.__root, clip) location: int = 0 stack: List[Any] = [] variables: Dict[str, Any] = { - 'aeplib': AEPLib(), - 'asdlib': ASDLib(), + "aeplib": AEPLib(), + "asdlib": ASDLib(), } registers: List[Any] = [UNDEFINED] * 256 @@ -732,7 +850,9 @@ class AFPRenderer(VerboseOutput): if action.opcode == AP2Action.END: # End the execution. - self.vprint(f"{prefix} Ending bytecode execution.", component="bytecode") + self.vprint( + f"{prefix} Ending bytecode execution.", component="bytecode" + ) break elif action.opcode == AP2Action.GET_VARIABLE: varname = stack.pop() @@ -749,9 +869,14 @@ class AFPRenderer(VerboseOutput): obj = stack.pop() if not hasattr(obj, attribute): - print(f"WARNING: Tried to set attribute {attribute} on {obj} to {set_value} but that attribute doesn't exist!") + print( + f"WARNING: Tried to set attribute {attribute} on {obj} to {set_value} but that attribute doesn't exist!" + ) else: - self.vprint(f"{prefix} Setting attribute {attribute} on {obj} to {set_value}", component="bytecode") + self.vprint( + f"{prefix} Setting attribute {attribute} on {obj} to {set_value}", + component="bytecode", + ) setattr(obj, attribute, set_value) elif action.opcode == AP2Action.CALL_METHOD: # Grab the method name. @@ -763,21 +888,28 @@ class AFPRenderer(VerboseOutput): # Grab the parameters to pass to the function. num_params = stack.pop() if not isinstance(num_params, int): - raise Exception("Logic error, cannot get number of parameters to method call!") + raise Exception( + "Logic error, cannot get number of parameters to method call!" + ) params = [] for _ in range(num_params): params.append(stack.pop()) # Look up the python function we're calling. try: - self.vprint(f"{prefix} Calling method {methname}({', '.join(repr(s) for s in params)}) on {obj}", component="bytecode") + self.vprint( + f"{prefix} Calling method {methname}({', '.join(repr(s) for s in params)}) on {obj}", + component="bytecode", + ) meth = getattr(obj, methname) # Call it, set the return on the stack. stack.append(meth(*params)) except AttributeError: # Function does not exist! - print(f"WARNING: Tried to call {methname}({', '.join(repr(s) for s in params)}) on {obj} but that method doesn't exist!") + print( + f"WARNING: Tried to call {methname}({', '.join(repr(s) for s in params)}) on {obj} but that method doesn't exist!" + ) stack.append(UNDEFINED) elif action.opcode == AP2Action.CALL_FUNCTION: # Grab the method name. @@ -786,21 +918,28 @@ class AFPRenderer(VerboseOutput): # Grab the parameters to pass to the function. num_params = stack.pop() if not isinstance(num_params, int): - raise Exception("Logic error, cannot get number of parameters to function call!") + raise Exception( + "Logic error, cannot get number of parameters to function call!" + ) params = [] for _ in range(num_params): params.append(stack.pop()) # Look up the python function we're calling. try: - self.vprint(f"{prefix} Calling global function {funcname}({', '.join(repr(s) for s in params)})", component="bytecode") + self.vprint( + f"{prefix} Calling global function {funcname}({', '.join(repr(s) for s in params)})", + component="bytecode", + ) func = getattr(globalobj, funcname) # Call it, set the return on the stack. stack.append(func(*params)) except AttributeError: # Function does not exist! - print(f"WARNING: Tried to call {funcname}({', '.join(repr(s) for s in params)}) on {globalobj} but that function doesn't exist!") + print( + f"WARNING: Tried to call {funcname}({', '.join(repr(s) for s in params)}) on {globalobj} but that function doesn't exist!" + ) stack.append(UNDEFINED) elif isinstance(action, PushAction): for obj in action.objects: @@ -846,11 +985,16 @@ class AFPRenderer(VerboseOutput): self.vprint(f"{prefix}Bytecode engine finished.", component="bytecode") - def __place(self, tag: Tag, operating_clip: PlacedClip, prefix: str = "") -> Tuple[Optional[PlacedClip], bool]: + def __place( + self, tag: Tag, operating_clip: PlacedClip, prefix: str = "" + ) -> Tuple[Optional[PlacedClip], bool]: # "Place" a tag on the screen. Most of the time, this means performing the action of the tag, # such as defining a shape (registering it with our shape list) or adding/removing an object. if isinstance(tag, AP2ShapeTag): - self.vprint(f"{prefix} Loading {tag.reference} into object slot {tag.id}", component="tags") + self.vprint( + f"{prefix} Loading {tag.reference} into object slot {tag.id}", + component="tags", + ) if tag.reference not in self.shapes: raise Exception(f"Cannot find shape reference {tag.reference}!") @@ -867,7 +1011,10 @@ class AFPRenderer(VerboseOutput): return None, False elif isinstance(tag, AP2ImageTag): - self.vprint(f"{prefix} Loading {tag.reference} into object slot {tag.id}", component="tags") + self.vprint( + f"{prefix} Loading {tag.reference} into object slot {tag.id}", + component="tags", + ) if tag.reference not in self.textures: raise Exception(f"Cannot find texture reference {tag.reference}!") @@ -881,10 +1028,15 @@ class AFPRenderer(VerboseOutput): return None, False elif isinstance(tag, AP2DefineSpriteTag): - self.vprint(f"{prefix} Loading Sprite into object slot {tag.id}", component="tags") + self.vprint( + f"{prefix} Loading Sprite into object slot {tag.id}", + component="tags", + ) # Register a new clip that we might reference to execute. - self.__registered_objects[tag.id] = RegisteredClip(tag.id, tag.frames, tag.tags, tag.labels) + self.__registered_objects[tag.id] = RegisteredClip( + tag.id, tag.frames, tag.tags, tag.labels + ) # Didn't place a new clip, didn't change anything. return None, False @@ -892,9 +1044,13 @@ class AFPRenderer(VerboseOutput): elif isinstance(tag, AP2PlaceObjectTag): if tag.unrecognized_options: if tag.source_tag_id is not None: - print(f"WARNING: Place object tag referencing {tag.source_tag_id} includes unparsed options and might not display properly!") + print( + f"WARNING: Place object tag referencing {tag.source_tag_id} includes unparsed options and might not display properly!" + ) else: - print(f"WARNING: Place object tag on depth {tag.depth} includes unparsed options and might not display properly!") + print( + f"WARNING: Place object tag on depth {tag.depth} includes unparsed options and might not display properly!" + ) if tag.update: for i in range(len(operating_clip.placed_objects) - 1, -1, -1): @@ -903,14 +1059,28 @@ class AFPRenderer(VerboseOutput): if obj.object_id == tag.object_id and obj.depth == tag.depth: new_mult_color = tag.mult_color or obj.mult_color new_add_color = tag.add_color or obj.add_color - new_transform = obj.transform.update(tag.transform) if tag.transform is not None else obj.transform + new_transform = ( + obj.transform.update(tag.transform) + if tag.transform is not None + else obj.transform + ) new_rotation_origin = tag.rotation_origin or obj.rotation_origin new_blend = tag.blend or obj.blend - new_projection = tag.projection if tag.projection != AP2PlaceObjectTag.PROJECTION_NONE else obj.projection + new_projection = ( + tag.projection + if tag.projection != AP2PlaceObjectTag.PROJECTION_NONE + else obj.projection + ) - if tag.source_tag_id is not None and tag.source_tag_id != obj.source.tag_id: + if ( + tag.source_tag_id is not None + and tag.source_tag_id != obj.source.tag_id + ): # This completely updates the pointed-at object. - self.vprint(f"{prefix} Replacing Object source {obj.source.tag_id} with {tag.source_tag_id} on object with Object ID {tag.object_id} onto Depth {tag.depth}", component="tags") + self.vprint( + f"{prefix} Replacing Object source {obj.source.tag_id} with {tag.source_tag_id} on object with Object ID {tag.object_id} onto Depth {tag.depth}", + component="tags", + ) newobj = self.__registered_objects[tag.source_tag_id] if isinstance(newobj, RegisteredShape): @@ -979,10 +1149,15 @@ class AFPRenderer(VerboseOutput): # Didn't place a new clip, changed the parent clip. return None, True else: - raise Exception(f"Unrecognized object with Tag ID {tag.source_tag_id}!") + raise Exception( + f"Unrecognized object with Tag ID {tag.source_tag_id}!" + ) else: # As far as I can tell, pretty much only color and matrix stuff can be updated. - self.vprint(f"{prefix} Updating Object ID {tag.object_id} on Depth {tag.depth}", component="tags") + self.vprint( + f"{prefix} Updating Object ID {tag.object_id} on Depth {tag.depth}", + component="tags", + ) obj.mult_color = new_mult_color obj.add_color = new_add_color obj.transform = new_transform @@ -992,14 +1167,21 @@ class AFPRenderer(VerboseOutput): return None, True # Didn't place a new clip, did change something. - print(f"WARNING: Couldn't find tag {tag.object_id} on depth {tag.depth} to update!") + print( + f"WARNING: Couldn't find tag {tag.object_id} on depth {tag.depth} to update!" + ) return None, False else: if tag.source_tag_id is None: - raise Exception("Cannot place a tag with no source ID and no update flags!") + raise Exception( + "Cannot place a tag with no source ID and no update flags!" + ) if tag.source_tag_id in self.__registered_objects: - self.vprint(f"{prefix} Placing Object {tag.source_tag_id} with Object ID {tag.object_id} onto Depth {tag.depth}", component="tags") + self.vprint( + f"{prefix} Placing Object {tag.source_tag_id} with Object ID {tag.object_id} onto Depth {tag.depth}", + component="tags", + ) newobj = self.__registered_objects[tag.source_tag_id] if isinstance(newobj, RegisteredShape): @@ -1056,9 +1238,13 @@ class AFPRenderer(VerboseOutput): for flags, code in tag.triggers.items(): if flags & AP2Trigger.ON_LOAD: for bytecode in code: - self.__execute_bytecode(bytecode, placed_clip, prefix=prefix + " ") + self.__execute_bytecode( + bytecode, placed_clip, prefix=prefix + " " + ) else: - print("WARNING: Unhandled PLACE_OBJECT trigger with flags {flags}!") + print( + "WARNING: Unhandled PLACE_OBJECT trigger with flags {flags}!" + ) # Placed a new clip, changed the parent. return placed_clip, True @@ -1081,25 +1267,34 @@ class AFPRenderer(VerboseOutput): # Didn't place a new clip, changed the parent clip. return None, True else: - raise Exception(f"Unrecognized object with Tag ID {tag.source_tag_id}!") + raise Exception( + f"Unrecognized object with Tag ID {tag.source_tag_id}!" + ) - raise Exception(f"Cannot find a shape or sprite with Tag ID {tag.source_tag_id}!") + raise Exception( + f"Cannot find a shape or sprite with Tag ID {tag.source_tag_id}!" + ) elif isinstance(tag, AP2RemoveObjectTag): - self.vprint(f"{prefix} Removing Object ID {tag.object_id} from Depth {tag.depth}", component="tags") + self.vprint( + f"{prefix} Removing Object ID {tag.object_id} from Depth {tag.depth}", + component="tags", + ) if tag.object_id != 0: # Remove the identified object by object ID and depth. # Remember removed objects so we can stop any clips. removed_objects = [ - obj for obj in operating_clip.placed_objects + obj + for obj in operating_clip.placed_objects if obj.object_id == tag.object_id and obj.depth == tag.depth ] # Get rid of the objects that we're removing from the master list. operating_clip.placed_objects = [ - obj for obj in operating_clip.placed_objects - if not(obj.object_id == tag.object_id and obj.depth == tag.depth) + obj + for obj in operating_clip.placed_objects + if not (obj.object_id == tag.object_id and obj.depth == tag.depth) ] else: # Remove the last placed object at this depth. The placed objects list isn't @@ -1110,12 +1305,19 @@ class AFPRenderer(VerboseOutput): real_index = len(operating_clip.placed_objects) - (i + 1) if operating_clip.placed_objects[real_index].depth == tag.depth: - removed_objects = operating_clip.placed_objects[real_index:(real_index + 1)] - operating_clip.placed_objects = operating_clip.placed_objects[:real_index] + operating_clip.placed_objects[(real_index + 1):] + removed_objects = operating_clip.placed_objects[ + real_index : (real_index + 1) + ] + operating_clip.placed_objects = ( + operating_clip.placed_objects[:real_index] + + operating_clip.placed_objects[(real_index + 1) :] + ) break if not removed_objects: - print(f"WARNING: Couldn't find object to remove by ID {tag.object_id} and depth {tag.depth}!") + print( + f"WARNING: Couldn't find object to remove by ID {tag.object_id} and depth {tag.depth}!" + ) # TODO: Handle ON_UNLOAD triggers for this object. I don't think I've ever seen one # on any object so this might be a pedantic request. @@ -1125,7 +1327,9 @@ class AFPRenderer(VerboseOutput): elif isinstance(tag, AP2DoActionTag): self.vprint(f"{prefix} Execution action tag.", component="tags") - self.__execute_bytecode(tag.bytecode, operating_clip, prefix=prefix + " ") + self.__execute_bytecode( + tag.bytecode, operating_clip, prefix=prefix + " " + ) # Didn't place a new clip. return None, False @@ -1175,13 +1379,21 @@ class AFPRenderer(VerboseOutput): if mask.rectangle is None: # Calculate the new mask rectangle. mask.rectangle = affine_composite( - Image.new('RGBA', (int(mask.bounds.right), int(mask.bounds.bottom)), (0, 0, 0, 0)), + Image.new( + "RGBA", + (int(mask.bounds.right), int(mask.bounds.bottom)), + (0, 0, 0, 0), + ), Color(0.0, 0.0, 0.0, 0.0), Color(1.0, 1.0, 1.0, 1.0), Matrix.identity().translate(Point(mask.bounds.left, mask.bounds.top)), None, 0, - Image.new('RGBA', (int(mask.bounds.width), int(mask.bounds.height)), (255, 0, 0, 255)), + Image.new( + "RGBA", + (int(mask.bounds.width), int(mask.bounds.height)), + (255, 0, 0, 255), + ), single_threaded=self.__single_threaded, aa_mode=AAMode.NONE, ) @@ -1189,7 +1401,9 @@ class AFPRenderer(VerboseOutput): # Draw the mask onto a new image. if projection == AP2PlaceObjectTag.PROJECTION_AFFINE: calculated_mask = affine_composite( - Image.new('RGBA', (parent_mask.width, parent_mask.height), (0, 0, 0, 0)), + Image.new( + "RGBA", (parent_mask.width, parent_mask.height), (0, 0, 0, 0) + ), Color(0.0, 0.0, 0.0, 0.0), Color(1.0, 1.0, 1.0, 1.0), transform, @@ -1201,9 +1415,13 @@ class AFPRenderer(VerboseOutput): ) elif projection == AP2PlaceObjectTag.PROJECTION_PERSPECTIVE: if self.__camera is None: - print("WARNING: Element requests perspective projection but no camera exists!") + print( + "WARNING: Element requests perspective projection but no camera exists!" + ) calculated_mask = affine_composite( - Image.new('RGBA', (parent_mask.width, parent_mask.height), (0, 0, 0, 0)), + Image.new( + "RGBA", (parent_mask.width, parent_mask.height), (0, 0, 0, 0) + ), Color(0.0, 0.0, 0.0, 0.0), Color(1.0, 1.0, 1.0, 1.0), transform, @@ -1215,7 +1433,9 @@ class AFPRenderer(VerboseOutput): ) else: calculated_mask = perspective_composite( - Image.new('RGBA', (parent_mask.width, parent_mask.height), (0, 0, 0, 0)), + Image.new( + "RGBA", (parent_mask.width, parent_mask.height), (0, 0, 0, 0) + ), Color(0.0, 0.0, 0.0, 0.0), Color(1.0, 1.0, 1.0, 1.0), transform, @@ -1252,27 +1472,47 @@ class AFPRenderer(VerboseOutput): parent_add_color: Color, parent_blend: int, only_depths: Optional[List[int]] = None, - prefix: str="", + prefix: str = "", ) -> Image.Image: if not renderable.visible: - self.vprint(f"{prefix} Ignoring invisible placed object ID {renderable.object_id} from sprite {renderable.source.tag_id} on Depth {renderable.depth}", component="render") + self.vprint( + f"{prefix} Ignoring invisible placed object ID {renderable.object_id} from sprite {renderable.source.tag_id} on Depth {renderable.depth}", + component="render", + ) return img - self.vprint(f"{prefix} Rendering placed object ID {renderable.object_id} from sprite {renderable.source.tag_id} onto Depth {renderable.depth}", component="render") + self.vprint( + f"{prefix} Rendering placed object ID {renderable.object_id} from sprite {renderable.source.tag_id} onto Depth {renderable.depth}", + component="render", + ) # Compute the affine transformation matrix for this object. - transform = renderable.transform.multiply(parent_transform).translate(Point.identity().subtract(renderable.rotation_origin)) - projection = AP2PlaceObjectTag.PROJECTION_PERSPECTIVE if parent_projection == AP2PlaceObjectTag.PROJECTION_PERSPECTIVE else renderable.projection + transform = renderable.transform.multiply(parent_transform).translate( + Point.identity().subtract(renderable.rotation_origin) + ) + projection = ( + AP2PlaceObjectTag.PROJECTION_PERSPECTIVE + if parent_projection == AP2PlaceObjectTag.PROJECTION_PERSPECTIVE + else renderable.projection + ) # Calculate blending and blend color if it is present. - mult_color = (renderable.mult_color or Color(1.0, 1.0, 1.0, 1.0)).multiply(parent_mult_color) - add_color = (renderable.add_color or Color(0.0, 0.0, 0.0, 0.0)).multiply(parent_mult_color).add(parent_add_color) + mult_color = (renderable.mult_color or Color(1.0, 1.0, 1.0, 1.0)).multiply( + parent_mult_color + ) + add_color = ( + (renderable.add_color or Color(0.0, 0.0, 0.0, 0.0)) + .multiply(parent_mult_color) + .add(parent_add_color) + ) blend = renderable.blend or 0 if parent_blend not in {0, 1, 2} and blend in {0, 1, 2}: blend = parent_blend if renderable.mask: - mask = self.__apply_mask(parent_mask, transform, projection, renderable.mask) + mask = self.__apply_mask( + parent_mask, transform, projection, renderable.mask + ) else: mask = parent_mask @@ -1293,7 +1533,18 @@ class AFPRenderer(VerboseOutput): for obj in renderable.placed_objects: if obj.depth != depth: continue - img = self.__render_object(img, obj, transform, projection, mask, mult_color, add_color, blend, only_depths=new_only_depths, prefix=prefix + " ") + img = self.__render_object( + img, + obj, + transform, + projection, + mask, + mult_color, + add_color, + blend, + only_depths=new_only_depths, + prefix=prefix + " ", + ) elif isinstance(renderable, PlacedShape): if only_depths is not None and renderable.depth not in only_depths: # Not on the correct depth plane. @@ -1317,7 +1568,9 @@ class AFPRenderer(VerboseOutput): if params.flags & 0x2: # We need to look up the texture for this. if params.region not in self.textures: - raise Exception(f"Cannot find texture reference {params.region}!") + raise Exception( + f"Cannot find texture reference {params.region}!" + ) texture = self.textures[params.region] if params.flags & 0x8: @@ -1331,7 +1584,9 @@ class AFPRenderer(VerboseOutput): if len(shape.vertex_points) != 4: print("WARNING: Unsupported non-rectangle shape!") if params.blend is None: - raise Exception("Logic error, rectangle without a blend color!") + raise Exception( + "Logic error, rectangle without a blend color!" + ) x_points = set(p.x for p in shape.vertex_points) y_points = set(p.y for p in shape.vertex_points) @@ -1353,14 +1608,22 @@ class AFPRenderer(VerboseOutput): if bad: print("WARNING: Unsupported non-rectangle shape!") - shape.rectangle = Image.new('RGBA', (int(right - left), int(bottom - top)), (params.blend.as_tuple())) + shape.rectangle = Image.new( + "RGBA", + (int(right - left), int(bottom - top)), + (params.blend.as_tuple()), + ) texture = shape.rectangle rectangle = True if texture is not None: if projection == AP2PlaceObjectTag.PROJECTION_AFFINE: if self.__enable_aa: - aamode = AAMode.UNSCALED_SSAA_ONLY if rectangle else AAMode.SSAA_OR_BILINEAR + aamode = ( + AAMode.UNSCALED_SSAA_ONLY + if rectangle + else AAMode.SSAA_OR_BILINEAR + ) else: aamode = AAMode.NONE @@ -1378,11 +1641,17 @@ class AFPRenderer(VerboseOutput): elif projection == AP2PlaceObjectTag.PROJECTION_PERSPECTIVE: if self.__camera is None: if self.__enable_aa: - aamode = AAMode.UNSCALED_SSAA_ONLY if rectangle else AAMode.SSAA_OR_BILINEAR + aamode = ( + AAMode.UNSCALED_SSAA_ONLY + if rectangle + else AAMode.SSAA_OR_BILINEAR + ) else: aamode = AAMode.NONE - print("WARNING: Element requests perspective projection but no camera exists!") + print( + "WARNING: Element requests perspective projection but no camera exists!" + ) img = affine_composite( img, add_color, @@ -1396,7 +1665,11 @@ class AFPRenderer(VerboseOutput): ) else: if self.__enable_aa: - aamode = AAMode.UNSCALED_SSAA_ONLY if rectangle else AAMode.SSAA_ONLY + aamode = ( + AAMode.UNSCALED_SSAA_ONLY + if rectangle + else AAMode.SSAA_ONLY + ) else: aamode = AAMode.NONE @@ -1431,11 +1704,15 @@ class AFPRenderer(VerboseOutput): blend, texture, single_threaded=self.__single_threaded, - aa_mode=AAMode.SSAA_OR_BILINEAR if self.__enable_aa else AAMode.NONE, + aa_mode=AAMode.SSAA_OR_BILINEAR + if self.__enable_aa + else AAMode.NONE, ) elif projection == AP2PlaceObjectTag.PROJECTION_PERSPECTIVE: if self.__camera is None: - print("WARNING: Element requests perspective projection but no camera exists!") + print( + "WARNING: Element requests perspective projection but no camera exists!" + ) img = affine_composite( img, add_color, @@ -1445,7 +1722,9 @@ class AFPRenderer(VerboseOutput): blend, texture, single_threaded=self.__single_threaded, - aa_mode=AAMode.SSAA_OR_BILINEAR if self.__enable_aa else AAMode.NONE, + aa_mode=AAMode.SSAA_OR_BILINEAR + if self.__enable_aa + else AAMode.NONE, ) else: img = perspective_composite( @@ -1483,15 +1762,24 @@ class AFPRenderer(VerboseOutput): # None of our children (or their children, etc...) or ourselves is dirty. return False - def __process_tags(self, clip: PlacedClip, only_dirty: bool, prefix: str = " ") -> bool: - self.vprint(f"{prefix}Handling {'dirty updates on ' if only_dirty else ''}placed clip {clip.object_id} at depth {clip.depth}", component="tags") + def __process_tags( + self, clip: PlacedClip, only_dirty: bool, prefix: str = " " + ) -> bool: + self.vprint( + f"{prefix}Handling {'dirty updates on ' if only_dirty else ''}placed clip {clip.object_id} at depth {clip.depth}", + component="tags", + ) # Track whether anything in ourselves or our children changes during this processing. changed = False # Make sure to set the requested frame if it isn't set by an external force. if clip.requested_frame is None: - if not clip.playing or only_dirty or (clip.finished and clip is self.__root): + if ( + not clip.playing + or only_dirty + or (clip.finished and clip is self.__root) + ): # We aren't playing this clip because its either paused or finished, # or it isn't dirty and we're doing dirty updates only. So, we don't # need to advance to any frame. @@ -1510,10 +1798,15 @@ class AFPRenderer(VerboseOutput): if clip.frame > clip.requested_frame: # Rewind this clip to the beginning so we can replay until the requested frame. if clip is self.__root: - print("WARNING: Root clip was rewound, its possible this animation plays forever!") + print( + "WARNING: Root clip was rewound, its possible this animation plays forever!" + ) clip.rewind() - self.vprint(f"{prefix} Processing frame {clip.frame} on our way to frame {clip.requested_frame}", component="tags") + self.vprint( + f"{prefix} Processing frame {clip.frame} on our way to frame {clip.requested_frame}", + component="tags", + ) # Clips that are part of our own placed objects which we should handle. child_clips = [c for c in clip.placed_objects if isinstance(c, PlacedClip)] @@ -1527,48 +1820,81 @@ class AFPRenderer(VerboseOutput): # See if we have any orphans that need to be placed before this frame will work. for unplayed_tag in clip.unplayed_tags: if unplayed_tag < frame.start_tag_offset: - self.vprint(f"{prefix} Including orphaned tag {unplayed_tag} in frame evaluation", component="tags") + self.vprint( + f"{prefix} Including orphaned tag {unplayed_tag} in frame evaluation", + component="tags", + ) played_tags.add(unplayed_tag) orphans.append(clip.source.tags[unplayed_tag]) - for tagno in range(frame.start_tag_offset, frame.start_tag_offset + frame.num_tags): + for tagno in range( + frame.start_tag_offset, frame.start_tag_offset + frame.num_tags + ): played_tags.add(tagno) # Check these off our future todo list. - clip.unplayed_tags = [t for t in clip.unplayed_tags if t not in played_tags] + clip.unplayed_tags = [ + t for t in clip.unplayed_tags if t not in played_tags + ] # Grab the normal list of tags, add to the orphans. - tags = orphans + clip.source.tags[frame.start_tag_offset:(frame.start_tag_offset + frame.num_tags)] + tags = ( + orphans + + clip.source.tags[ + frame.start_tag_offset : ( + frame.start_tag_offset + frame.num_tags + ) + ] + ) for tagno, tag in enumerate(tags): # Perform the action of this tag. - self.vprint(f"{prefix} Sprite Tag ID: {clip.source.tag_id}, Current Tag: {frame.start_tag_offset + tagno}, Num Tags: {frame.num_tags}", component="tags") + self.vprint( + f"{prefix} Sprite Tag ID: {clip.source.tag_id}, Current Tag: {frame.start_tag_offset + tagno}, Num Tags: {frame.num_tags}", + component="tags", + ) new_clip, clip_changed = self.__place(tag, clip, prefix=prefix) changed = changed or clip_changed # If we create a new movie clip, process it as well for this frame. if new_clip: # These are never dirty-only updates as they're fresh-placed. - changed = self.__process_tags(new_clip, False, prefix=prefix + " ") or changed + changed = ( + self.__process_tags(new_clip, False, prefix=prefix + " ") + or changed + ) # Now, advance the frame for this clip since we processed the frame. clip.advance() # Now, handle each of the existing clips. for child in child_clips: - changed = self.__process_tags(child, only_dirty, prefix=prefix + " ") or changed + changed = ( + self.__process_tags(child, only_dirty, prefix=prefix + " ") + or changed + ) # See if we're done with this clip. if clip.frame == clip.requested_frame: clip.requested_frame = None break - self.vprint(f"{prefix}Finished handling {'dirty updates on ' if only_dirty else ''}placed clip {clip.object_id} at depth {clip.depth}", component="tags") + self.vprint( + f"{prefix}Finished handling {'dirty updates on ' if only_dirty else ''}placed clip {clip.object_id} at depth {clip.depth}", + component="tags", + ) # Return if anything was modified. return changed - def __handle_imports(self, swf: SWF) -> Dict[int, Union[RegisteredShape, RegisteredClip, RegisteredImage, RegisteredDummy]]: - external_objects: Dict[int, Union[RegisteredShape, RegisteredClip, RegisteredImage, RegisteredDummy]] = {} + def __handle_imports( + self, swf: SWF + ) -> Dict[ + int, Union[RegisteredShape, RegisteredClip, RegisteredImage, RegisteredDummy] + ]: + external_objects: Dict[ + int, + Union[RegisteredShape, RegisteredClip, RegisteredImage, RegisteredDummy], + ] = {} # Go through, recursively resolve imports for all SWF files. for tag_id, imp in swf.imported_tags.items(): @@ -1576,16 +1902,22 @@ class AFPRenderer(VerboseOutput): if other.exported_name == imp.swf: # This SWF should have the tag reference. if imp.tag not in other.exported_tags: - print(f"WARNING: {swf.exported_name} imports {imp} but that import is not in {other.exported_name}!") + print( + f"WARNING: {swf.exported_name} imports {imp} but that import is not in {other.exported_name}!" + ) external_objects[tag_id] = RegisteredDummy(tag_id) break else: - external_objects[tag_id] = self.__find_import(other, other.exported_tags[imp.tag]) + external_objects[tag_id] = self.__find_import( + other, other.exported_tags[imp.tag] + ) break else: # Only display a warning if we don't have our own stub implementation of this SWF. if repr(imp) not in self.__stubbed_swfs: - print(f"WARNING: {swf.exported_name} imports {imp} but that SWF is not in our library!") + print( + f"WARNING: {swf.exported_name} imports {imp} but that SWF is not in our library!" + ) external_objects[tag_id] = RegisteredDummy(tag_id) # Fix up tag IDs to point at our local definition of them. @@ -1595,11 +1927,15 @@ class AFPRenderer(VerboseOutput): # Return our newly populated registered object table containing all imports! return external_objects - def __find_import(self, swf: SWF, tag_id: int) -> Union[RegisteredShape, RegisteredClip, RegisteredImage, RegisteredDummy]: + def __find_import( + self, swf: SWF, tag_id: int + ) -> Union[RegisteredShape, RegisteredClip, RegisteredImage, RegisteredDummy]: if tag_id in swf.imported_tags: external_objects = self.__handle_imports(swf) if tag_id not in external_objects: - raise Exception(f"Logic error, tag ID {tag_id} is an export for {swf.exported_name} but we didn't populate it!") + raise Exception( + f"Logic error, tag ID {tag_id} is an export for {swf.exported_name} but we didn't populate it!" + ) return external_objects[tag_id] # We need to do a basic placement to find the registered object so we can return it. @@ -1612,14 +1948,22 @@ class AFPRenderer(VerboseOutput): tag = self.__find_tag(root_clip, tag_id) if tag is None: - print(f"WARNING: {swf.exported_name} exports {swf.imported_tags[tag_id]} but does not manifest an object!") + print( + f"WARNING: {swf.exported_name} exports {swf.imported_tags[tag_id]} but does not manifest an object!" + ) return RegisteredDummy(tag_id) return tag - def __find_tag(self, clip: RegisteredClip, tag_id: int) -> Optional[Union[RegisteredShape, RegisteredClip, RegisteredImage, RegisteredDummy]]: + def __find_tag( + self, clip: RegisteredClip, tag_id: int + ) -> Optional[ + Union[RegisteredShape, RegisteredClip, RegisteredImage, RegisteredDummy] + ]: # Fake-execute this clip to find the tag we need to manifest. for frame in clip.frames: - tags = clip.tags[frame.start_tag_offset:(frame.start_tag_offset + frame.num_tags)] + tags = clip.tags[ + frame.start_tag_offset : (frame.start_tag_offset + frame.num_tags) + ] for tag in tags: # Attempt to place any tags. @@ -1627,7 +1971,9 @@ class AFPRenderer(VerboseOutput): if tag.id == tag_id: # We need to be able to see this shape to place it. if tag.reference not in self.shapes: - raise Exception(f"Cannot find shape reference {tag.reference}!") + raise Exception( + f"Cannot find shape reference {tag.reference}!" + ) # This matched, so this is the import. return RegisteredShape( @@ -1642,7 +1988,9 @@ class AFPRenderer(VerboseOutput): if tag.id == tag_id: # We need to be able to see this shape to place it. if tag.reference not in self.textures: - raise Exception(f"Cannot find texture reference {tag.reference}!") + raise Exception( + f"Cannot find texture reference {tag.reference}!" + ) # This matched, so this is the import. return RegisteredImage( @@ -1685,7 +2033,9 @@ class AFPRenderer(VerboseOutput): # Calculate actual size based on given movie transform. actual_width = overridden_width or swf.location.width actual_height = overridden_height or swf.location.height - resized_width, resized_height, _ = movie_transform.multiply_point(Point(actual_width, actual_height)).as_tuple() + resized_width, resized_height, _ = movie_transform.multiply_point( + Point(actual_width, actual_height) + ).as_tuple() if round(swf.location.top, 2) != 0.0 or round(swf.location.left, 2) != 0.0: # TODO: If the location top/left is nonzero, we need move the root transform @@ -1737,8 +2087,13 @@ class AFPRenderer(VerboseOutput): # Register the background images with the texture library. for background_frame in range(background_frames): - if background_image[background_frame].width != imgwidth or background_image[background_frame].height != imgheight: - raise Exception(f"Frame {background_frame + 1} of background image sequence has different dimensions than others!") + if ( + background_image[background_frame].width != imgwidth + or background_image[background_frame].height != imgheight + ): + raise Exception( + f"Frame {background_frame + 1} of background image sequence has different dimensions than others!" + ) name = f"{swf.exported_name}_inserted_background_{background_frame}" self.textures[name] = background_image[background_frame].convert("RGBA") @@ -1753,12 +2108,14 @@ class AFPRenderer(VerboseOutput): Color(0.0, 0.0, 0.0, 0.0), 0, None, - background_object + background_object, ) root_clip.placed_objects.append(background_container) # Create the root mask for where to draw the root clip. - movie_mask = Image.new("RGBA", (resized_width, resized_height), color=(255, 0, 0, 255)) + movie_mask = Image.new( + "RGBA", (resized_width, resized_height), color=(255, 0, 0, 255) + ) # These could possibly be overwritten from an external source of we wanted. actual_mult_color = Color(1.0, 1.0, 1.0, 1.0) @@ -1773,7 +2130,10 @@ class AFPRenderer(VerboseOutput): try: while root_clip.playing and not root_clip.finished: # Create a new image to render into. - self.vprint(f"Rendering frame {frameno + 1}/{len(root_clip.source.frames)}", component="core") + self.vprint( + f"Rendering frame {frameno + 1}/{len(root_clip.source.frames)}", + component="core", + ) # Go through all registered clips, place all needed tags. changed = self.__process_tags(root_clip, False) @@ -1788,7 +2148,9 @@ class AFPRenderer(VerboseOutput): if obj is background_container: break else: - self.vprint("Root clip was rewound, re-placing background image on clip.") + self.vprint( + "Root clip was rewound, re-placing background image on clip." + ) root_clip.placed_objects.append(background_container) # Now, update the background image if we need to. @@ -1800,27 +2162,42 @@ class AFPRenderer(VerboseOutput): # Adjust camera based on the movie's scaling. if self.__camera is not None and not self.__camera.adjusted: - self.__camera.center = movie_transform.multiply_point(self.__camera.center) + self.__camera.center = movie_transform.multiply_point( + self.__camera.center + ) self.__camera.adjusted = True # If we're only rendering some frames, don't bother to do the draw operations # if we aren't going to return the frame. if only_frames and (frameno + 1) not in only_frames: - self.vprint(f"Skipped rendering frame {frameno + 1}/{len(root_clip.source.frames)}", component="core") + self.vprint( + f"Skipped rendering frame {frameno + 1}/{len(root_clip.source.frames)}", + component="core", + ) last_rendered_frame = None frameno += 1 continue if changed or last_rendered_frame is None: - if last_width != root_clip._width or last_height != root_clip._height: + if ( + last_width != root_clip._width + or last_height != root_clip._height + ): last_width = root_clip._width last_height = root_clip._height - if root_clip._width > actual_width or root_clip._height > actual_height: - print(f"WARNING: Root clip requested to resize to {last_width}x{last_height} which overflows root canvas!") + if ( + root_clip._width > actual_width + or root_clip._height > actual_height + ): + print( + f"WARNING: Root clip requested to resize to {last_width}x{last_height} which overflows root canvas!" + ) # Now, render out the placed objects. color = swf.color or Color(0.0, 0.0, 0.0, 0.0) - curimage = Image.new("RGBA", (resized_width, resized_height), color=color.as_tuple()) + curimage = Image.new( + "RGBA", (resized_width, resized_height), color=color.as_tuple() + ) curimage = self.__render_object( curimage, root_clip, @@ -1838,7 +2215,10 @@ class AFPRenderer(VerboseOutput): curimage = last_rendered_frame.copy() # Return that frame, advance our bookkeeping. - self.vprint(f"Finished rendering frame {frameno + 1}/{len(root_clip.source.frames)}", component="core") + self.vprint( + f"Finished rendering frame {frameno + 1}/{len(root_clip.source.frames)}", + component="core", + ) last_rendered_frame = curimage frameno += 1 yield curimage @@ -1848,7 +2228,9 @@ class AFPRenderer(VerboseOutput): break except KeyboardInterrupt: # Allow ctrl-c to end early and render a partial animation. - print(f"WARNING: Interrupted early, will render only {frameno}/{len(root_clip.source.frames)} frames of animation!") + print( + f"WARNING: Interrupted early, will render only {frameno}/{len(root_clip.source.frames)} frames of animation!" + ) # Clean up self.__root = None diff --git a/bemani/format/afp/swf.py b/bemani/format/afp/swf.py index 28fdde8..4a8cba0 100644 --- a/bemani/format/afp/swf.py +++ b/bemani/format/afp/swf.py @@ -45,8 +45,8 @@ class NamedTagReference: def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { - 'swf': self.swf, - 'tag': self.tag, + "swf": self.swf, + "tag": self.tag, } def __repr__(self) -> str: @@ -56,19 +56,25 @@ class NamedTagReference: class TagPointer: # A pointer to a tag in this SWF by Tag ID and containing an optional initialization bytecode # to run for this tag when it is placed/executed. - def __init__(self, id: Optional[int], init_bytecode: Optional[ByteCode] = None) -> None: + def __init__( + self, id: Optional[int], init_bytecode: Optional[ByteCode] = None + ) -> None: self.id = id self.init_bytecode = init_bytecode def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { - 'id': self.id, - 'init_bytecode': self.init_bytecode.as_dict(*args, **kwargs) if self.init_bytecode else None, + "id": self.id, + "init_bytecode": self.init_bytecode.as_dict(*args, **kwargs) + if self.init_bytecode + else None, } class Frame: - def __init__(self, start_tag_offset: int, num_tags: int, imported_tags: List[TagPointer] = []) -> None: + def __init__( + self, start_tag_offset: int, num_tags: int, imported_tags: List[TagPointer] = [] + ) -> None: # The start tag offset into the tag list where we should begin placing/executing tags for this frame. self.start_tag_offset = start_tag_offset @@ -83,9 +89,9 @@ class Frame: def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { - 'start_tag_offset': self.start_tag_offset, - 'num_tags': self.num_tags, - 'imported_tags': [i.as_dict(*args, **kwargs) for i in self.imported_tags], + "start_tag_offset": self.start_tag_offset, + "num_tags": self.num_tags, + "imported_tags": [i.as_dict(*args, **kwargs) for i in self.imported_tags], } @@ -96,8 +102,8 @@ class Tag: def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { - 'id': self.id, - 'type': self.__class__.__name__, + "id": self.id, + "type": self.__class__.__name__, } @@ -113,7 +119,7 @@ class AP2ShapeTag(Tag): def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { **super().as_dict(*args, **kwargs), - 'reference': self.reference, + "reference": self.reference, } @@ -129,14 +135,21 @@ class AP2ImageTag(Tag): def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { **super().as_dict(*args, **kwargs), - 'reference': self.reference, + "reference": self.reference, } class AP2DefineFontTag(Tag): id: int - def __init__(self, id: int, fontname: str, xml_prefix: str, heights: List[int], text_indexes: List[int]) -> None: + def __init__( + self, + id: int, + fontname: str, + xml_prefix: str, + heights: List[int], + text_indexes: List[int], + ) -> None: super().__init__(id) # The font name is just the pretty name of the font. @@ -159,10 +172,10 @@ class AP2DefineFontTag(Tag): def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { **super().as_dict(*args, **kwargs), - 'fontname': self.fontname, - 'xml_prefix': self.xml_prefix, - 'heights': self.heights, - 'text_indexes': self.text_indexes, + "fontname": self.fontname, + "xml_prefix": self.xml_prefix, + "heights": self.heights, + "text_indexes": self.text_indexes, } @@ -179,13 +192,20 @@ class AP2TextChar: def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { - 'font_text_index': self.font_text_index, - 'width': self.width, + "font_text_index": self.font_text_index, + "width": self.width, } class AP2TextLine: - def __init__(self, font_tag: Optional[int], height: int, xpos: float, ypos: float, entries: List[AP2TextChar]) -> None: + def __init__( + self, + font_tag: Optional[int], + height: int, + xpos: float, + ypos: float, + entries: List[AP2TextChar], + ) -> None: self.font_tag = font_tag self.font_height = height self.xpos = xpos @@ -194,11 +214,11 @@ class AP2TextLine: def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { - 'font_tag': self.font_tag, - 'font_height': self.font_height, - 'xpos': self.xpos, - 'ypos': self.ypos, - 'entries': [e.as_dict(*args, **kwargs) for e in self.entries], + "font_tag": self.font_tag, + "font_height": self.font_height, + "xpos": self.xpos, + "ypos": self.ypos, + "entries": [e.as_dict(*args, **kwargs) for e in self.entries], } @@ -231,7 +251,9 @@ class AP2DefineButtonTag(Tag): class AP2PlaceCameraTag(Tag): - def __init__(self, camera_id: int, center: Optional[Point], focal_length: float) -> None: + def __init__( + self, camera_id: int, center: Optional[Point], focal_length: float + ) -> None: super().__init__(None) # This is not actually Tag ID, just a way to refer to the camera. Confusing, I know. @@ -244,7 +266,9 @@ class AP2PlaceCameraTag(Tag): return { **super().as_dict(*args, **kwargs), "camera_id": self.camera_id, - 'center': self.center.as_dict(*args, **kwargs) if self.center is not None else None, + "center": self.center.as_dict(*args, **kwargs) + if self.center is not None + else None, "focal_length": self.focal_length, } @@ -260,7 +284,7 @@ class AP2DefineTextTag(Tag): def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { **super().as_dict(*args, **kwargs), - 'lines': [line.as_dict(*args, **kwargs) for line in self.lines], + "lines": [line.as_dict(*args, **kwargs) for line in self.lines], } @@ -276,7 +300,7 @@ class AP2DoActionTag(Tag): def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { **super().as_dict(*args, **kwargs), - 'bytecode': self.bytecode.as_dict(*args, **kwargs), + "bytecode": self.bytecode.as_dict(*args, **kwargs), } @@ -350,19 +374,34 @@ class AP2PlaceObjectTag(Tag): def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { **super().as_dict(*args, **kwargs), - 'object_id': self.object_id, - 'depth': self.depth, - 'source_tag_id': self.source_tag_id, - 'movie_name': self.movie_name, - 'label_name': self.label_name, - 'blend': self.blend, - 'update': self.update, - 'transform': self.transform.as_dict(*args, **kwargs) if self.transform is not None else None, - 'rotation_origin': self.rotation_origin.as_dict(*args, **kwargs) if self.rotation_origin is not None else None, - 'projection': 'none' if self.projection == self.PROJECTION_NONE else ('affine' if self.projection == self.PROJECTION_AFFINE else 'perspective'), - 'mult_color': self.mult_color.as_dict(*args, **kwargs) if self.mult_color is not None else None, - 'add_color': self.add_color.as_dict(*args, **kwargs) if self.add_color is not None else None, - 'triggers': {i: [b.as_dict(*args, **kwargs) for b in t] for (i, t) in self.triggers.items()} + "object_id": self.object_id, + "depth": self.depth, + "source_tag_id": self.source_tag_id, + "movie_name": self.movie_name, + "label_name": self.label_name, + "blend": self.blend, + "update": self.update, + "transform": self.transform.as_dict(*args, **kwargs) + if self.transform is not None + else None, + "rotation_origin": self.rotation_origin.as_dict(*args, **kwargs) + if self.rotation_origin is not None + else None, + "projection": "none" + if self.projection == self.PROJECTION_NONE + else ( + "affine" if self.projection == self.PROJECTION_AFFINE else "perspective" + ), + "mult_color": self.mult_color.as_dict(*args, **kwargs) + if self.mult_color is not None + else None, + "add_color": self.add_color.as_dict(*args, **kwargs) + if self.add_color is not None + else None, + "triggers": { + i: [b.as_dict(*args, **kwargs) for b in t] + for (i, t) in self.triggers.items() + }, } def __repr__(self) -> str: @@ -383,15 +422,17 @@ class AP2RemoveObjectTag(Tag): def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { **super().as_dict(*args, **kwargs), - 'object_id': self.object_id, - 'depth': self.depth, + "object_id": self.object_id, + "depth": self.depth, } class AP2DefineSpriteTag(Tag): id: int - def __init__(self, id: int, tags: List[Tag], frames: List[Frame], labels: Dict[str, int]) -> None: + def __init__( + self, id: int, tags: List[Tag], frames: List[Frame], labels: Dict[str, int] + ) -> None: super().__init__(id) # The list of tags that this sprite consists of. Sprites are, much like vanilla @@ -407,16 +448,24 @@ class AP2DefineSpriteTag(Tag): def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { **super().as_dict(*args, **kwargs), - 'tags': [t.as_dict(*args, **kwargs) for t in self.tags], - 'frames': [f.as_dict(*args, **kwargs) for f in self.frames], - 'labels': self.labels, + "tags": [t.as_dict(*args, **kwargs) for t in self.tags], + "frames": [f.as_dict(*args, **kwargs) for f in self.frames], + "labels": self.labels, } class AP2DefineEditTextTag(Tag): id: int - def __init__(self, id: int, font_tag_id: int, font_height: int, rect: Rectangle, color: Color, default_text: Optional[str] = None) -> None: + def __init__( + self, + id: int, + font_tag_id: int, + font_height: int, + rect: Rectangle, + color: Color, + default_text: Optional[str] = None, + ) -> None: super().__init__(id) # The ID of the Ap2DefineFontTag that we want to use for the text. @@ -438,11 +487,11 @@ class AP2DefineEditTextTag(Tag): def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { **super().as_dict(*args, **kwargs), - 'font_tag_id': self.font_tag_id, - 'font_height': self.font_height, - 'rect': self.rect.as_dict(*args, **kwargs), - 'color': self.color.as_dict(*args, **kwargs), - 'default_text': self.default_text, + "font_tag_id": self.font_tag_id, + "font_height": self.font_height, + "rect": self.rect.as_dict(*args, **kwargs), + "color": self.color.as_dict(*args, **kwargs), + "default_text": self.default_text, } @@ -517,21 +566,32 @@ class SWF(VerboseOutput, TrackedCoverage): def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { - 'name': self.name, - 'exported_name': self.exported_name, - 'data_version': self.data_version, - 'container_version': self.container_version, - 'fps': self.fps, - 'color': self.color.as_dict(*args, **kwargs) if self.color is not None else None, - 'location': self.location.as_dict(*args, **kwargs), - 'exported_tags': self.exported_tags, - 'imported_tags': {i: self.imported_tags[i].as_dict(*args, **kwargs) for i in self.imported_tags}, - 'tags': [t.as_dict(*args, **kwargs) for t in self.tags], - 'frames': [f.as_dict(*args, **kwargs) for f in self.frames], - 'labels': self.labels, + "name": self.name, + "exported_name": self.exported_name, + "data_version": self.data_version, + "container_version": self.container_version, + "fps": self.fps, + "color": self.color.as_dict(*args, **kwargs) + if self.color is not None + else None, + "location": self.location.as_dict(*args, **kwargs), + "exported_tags": self.exported_tags, + "imported_tags": { + i: self.imported_tags[i].as_dict(*args, **kwargs) + for i in self.imported_tags + }, + "tags": [t.as_dict(*args, **kwargs) for t in self.tags], + "frames": [f.as_dict(*args, **kwargs) for f in self.frames], + "labels": self.labels, } - def __parse_bytecode(self, bytecode_name: Optional[str], datachunk: bytes, string_offsets: List[int] = [], prefix: str = "") -> ByteCode: + def __parse_bytecode( + self, + bytecode_name: Optional[str], + datachunk: bytes, + string_offsets: List[int] = [], + prefix: str = "", + ) -> ByteCode: # First, we need to check if this is a SWF-style bytecode or an AP2 bytecode. ap2_sentinel = struct.unpack("B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + opcode = struct.unpack(">B", datachunk[offset_ptr : (offset_ptr + 1)])[0] action_name = AP2Action.action_to_name(opcode) lineno = offset_ptr if opcode in AP2Action.actions_without_params(): # Simple opcodes need no parsing, they can go directly onto the stack. - self.vprint(f"{prefix} {lineno}: {action_name}", component="bytecode") + self.vprint( + f"{prefix} {lineno}: {action_name}", component="bytecode" + ) offset_ptr += 1 actions.append(AP2Action(lineno, opcode)) elif opcode == AP2Action.DEFINE_FUNCTION2: - function_flags, funcname_offset, bytecode_offset, _, bytecode_count = struct.unpack( + ( + function_flags, + funcname_offset, + bytecode_offset, + _, + bytecode_count, + ) = struct.unpack( ">HHHBH", - datachunk[(offset_ptr + 1):(offset_ptr + 10)], + datachunk[(offset_ptr + 1) : (offset_ptr + 10)], ) if funcname_offset == 0: @@ -583,26 +659,42 @@ class SWF(VerboseOutput, TrackedCoverage): funcname = self.__get_string(funcname_offset) offset_ptr += 10 + (3 * bytecode_offset) - self.vprint(f"{prefix} {lineno}: {action_name} Flags: {hex(function_flags)}, Name: {funcname or ''}, ByteCode Offset: {hex(bytecode_offset)}, ByteCode Length: {hex(bytecode_count)}", component="bytecode") + self.vprint( + f"{prefix} {lineno}: {action_name} Flags: {hex(function_flags)}, Name: {funcname or ''}, ByteCode Offset: {hex(bytecode_offset)}, ByteCode Length: {hex(bytecode_count)}", + component="bytecode", + ) # No name for this chunk, it will only ever be decompiled and printed in the context of another # chunk. - function = self.__parse_bytecode(None, datachunk[offset_ptr:(offset_ptr + bytecode_count)], string_offsets=string_offsets, prefix=prefix + " ") + function = self.__parse_bytecode( + None, + datachunk[offset_ptr : (offset_ptr + bytecode_count)], + string_offsets=string_offsets, + prefix=prefix + " ", + ) self.vprint(f"{prefix} END_{action_name}", component="bytecode") - actions.append(DefineFunction2Action(lineno, funcname, function_flags, function)) + actions.append( + DefineFunction2Action(lineno, funcname, function_flags, function) + ) offset_ptr += bytecode_count elif opcode == AP2Action.PUSH: - obj_count = struct.unpack(">B", datachunk[(offset_ptr + 1):(offset_ptr + 2)])[0] + obj_count = struct.unpack( + ">B", datachunk[(offset_ptr + 1) : (offset_ptr + 2)] + )[0] offset_ptr += 2 - self.vprint(f"{prefix} {lineno}: {action_name}", component="bytecode") + self.vprint( + f"{prefix} {lineno}: {action_name}", component="bytecode" + ) objects: List[Any] = [] while obj_count > 0: - obj_to_create = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + obj_to_create = struct.unpack( + ">B", datachunk[offset_ptr : (offset_ptr + 1)] + )[0] offset_ptr += 1 if obj_to_create == 0x0: @@ -611,11 +703,15 @@ class SWF(VerboseOutput, TrackedCoverage): self.vprint(f"{prefix} INTEGER: 0", component="bytecode") elif obj_to_create == 0x1: # Float object, represented internally as a double. - fval = struct.unpack(">f", datachunk[offset_ptr:(offset_ptr + 4)])[0] + fval = struct.unpack( + ">f", datachunk[offset_ptr : (offset_ptr + 4)] + )[0] objects.append(fval) offset_ptr += 4 - self.vprint(f"{prefix} FLOAT: {fval}", component="bytecode") + self.vprint( + f"{prefix} FLOAT: {fval}", component="bytecode" + ) elif obj_to_create == 0x2: # Null pointer object. objects.append(NULL) @@ -626,254 +722,438 @@ class SWF(VerboseOutput, TrackedCoverage): self.vprint(f"{prefix} UNDEFINED", component="bytecode") elif obj_to_create == 0x4: # Register value. - regno = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + regno = struct.unpack( + ">B", datachunk[offset_ptr : (offset_ptr + 1)] + )[0] objects.append(Register(regno)) offset_ptr += 1 - self.vprint(f"{prefix} REGISTER NO: {regno}", component="bytecode") + self.vprint( + f"{prefix} REGISTER NO: {regno}", + component="bytecode", + ) elif obj_to_create == 0x5: # Boolean "TRUE" object. objects.append(True) - self.vprint(f"{prefix} BOOLEAN: True", component="bytecode") + self.vprint( + f"{prefix} BOOLEAN: True", component="bytecode" + ) elif obj_to_create == 0x6: # Boolean "FALSE" object. objects.append(False) - self.vprint(f"{prefix} BOOLEAN: False", component="bytecode") + self.vprint( + f"{prefix} BOOLEAN: False", component="bytecode" + ) elif obj_to_create == 0x7: # Integer object. - ival = struct.unpack(">i", datachunk[offset_ptr:(offset_ptr + 4)])[0] + ival = struct.unpack( + ">i", datachunk[offset_ptr : (offset_ptr + 4)] + )[0] objects.append(ival) offset_ptr += 4 - self.vprint(f"{prefix} INTEGER: {ival}", component="bytecode") + self.vprint( + f"{prefix} INTEGER: {ival}", component="bytecode" + ) elif obj_to_create == 0x8: # String constant object. - const_offset = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + const_offset = struct.unpack( + ">B", datachunk[offset_ptr : (offset_ptr + 1)] + )[0] const = self.__get_string(string_offsets[const_offset]) objects.append(const) offset_ptr += 1 - self.vprint(f"{prefix} STRING CONST: {const}", component="bytecode") + self.vprint( + f"{prefix} STRING CONST: {const}", + component="bytecode", + ) elif obj_to_create == 0x9: # String constant, but with 16 bits for the offset. Probably not used except # on the largest files. - const_offset = struct.unpack(">H", datachunk[offset_ptr:(offset_ptr + 2)])[0] + const_offset = struct.unpack( + ">H", datachunk[offset_ptr : (offset_ptr + 2)] + )[0] const = self.__get_string(string_offsets[const_offset]) objects.append(const) offset_ptr += 2 - self.vprint(f"{prefix} STRING CONST: {const}", component="bytecode") - elif obj_to_create == 0xa: + self.vprint( + f"{prefix} STRING CONST: {const}", + component="bytecode", + ) + elif obj_to_create == 0xA: # NaN constant. objects.append(float("nan")) self.vprint(f"{prefix} NAN", component="bytecode") - elif obj_to_create == 0xb: + elif obj_to_create == 0xB: # Infinity constant. objects.append(float("inf")) self.vprint(f"{prefix} INFINITY", component="bytecode") - elif obj_to_create == 0xc: + elif obj_to_create == 0xC: # Pointer to "this" object, whatever currently is executing the bytecode. objects.append(THIS) - self.vprint(f"{prefix} POINTER TO THIS", component="bytecode") - elif obj_to_create == 0xd: + self.vprint( + f"{prefix} POINTER TO THIS", component="bytecode" + ) + elif obj_to_create == 0xD: # Pointer to "root" object, which is the movieclip this bytecode exists in. objects.append(ROOT) - self.vprint(f"{prefix} POINTER TO ROOT", component="bytecode") - elif obj_to_create == 0xe: + self.vprint( + f"{prefix} POINTER TO ROOT", component="bytecode" + ) + elif obj_to_create == 0xE: # Pointer to "parent" object, whatever currently is executing the bytecode. # This seems to be the parent of the movie clip, or the current movieclip # if that isn't set. objects.append(PARENT) - self.vprint(f"{prefix} POINTER TO PARENT", component="bytecode") - elif obj_to_create == 0xf: + self.vprint( + f"{prefix} POINTER TO PARENT", component="bytecode" + ) + elif obj_to_create == 0xF: # Current movie clip. objects.append(CLIP) - self.vprint(f"{prefix} POINTER TO CURRENT MOVIECLIP", component="bytecode") + self.vprint( + f"{prefix} POINTER TO CURRENT MOVIECLIP", + component="bytecode", + ) elif obj_to_create == 0x10: # Property constant with no alias. - propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x100 + propertyval = ( + struct.unpack( + ">B", datachunk[offset_ptr : (offset_ptr + 1)] + )[0] + + 0x100 + ) objects.append(StringConstant(propertyval)) offset_ptr += 1 - self.vprint(f"{prefix} PROPERTY CONST NAME: {StringConstant.property_to_name(propertyval)}", component="bytecode") + self.vprint( + f"{prefix} PROPERTY CONST NAME: {StringConstant.property_to_name(propertyval)}", + component="bytecode", + ) elif obj_to_create == 0x11: # Property constant referencing a string table entry. - propertyval, reference = struct.unpack(">BB", datachunk[offset_ptr:(offset_ptr + 2)]) + propertyval, reference = struct.unpack( + ">BB", datachunk[offset_ptr : (offset_ptr + 2)] + ) propertyval += 0x100 referenceval = self.__get_string(string_offsets[reference]) objects.append(StringConstant(propertyval, referenceval)) offset_ptr += 2 - self.vprint(f"{prefix} PROPERTY CONST NAME: {StringConstant.property_to_name(propertyval)}, ALIAS: {referenceval}", component="bytecode") + self.vprint( + f"{prefix} PROPERTY CONST NAME: {StringConstant.property_to_name(propertyval)}, ALIAS: {referenceval}", + component="bytecode", + ) elif obj_to_create == 0x12: # Same as above, but with allowance for a 16-bit constant offset. - propertyval, reference = struct.unpack(">BH", datachunk[offset_ptr:(offset_ptr + 3)]) + propertyval, reference = struct.unpack( + ">BH", datachunk[offset_ptr : (offset_ptr + 3)] + ) propertyval += 0x100 referenceval = self.__get_string(string_offsets[reference]) objects.append(StringConstant(propertyval, referenceval)) offset_ptr += 3 - self.vprint(f"{prefix} PROPERTY CONST NAME: {StringConstant.property_to_name(propertyval)}, ALIAS: {referenceval}", component="bytecode") + self.vprint( + f"{prefix} PROPERTY CONST NAME: {StringConstant.property_to_name(propertyval)}, ALIAS: {referenceval}", + component="bytecode", + ) elif obj_to_create == 0x13: # Class property name. - propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x300 + propertyval = ( + struct.unpack( + ">B", datachunk[offset_ptr : (offset_ptr + 1)] + )[0] + + 0x300 + ) objects.append(StringConstant(propertyval)) offset_ptr += 1 - self.vprint(f"{prefix} CLASS CONST NAME: {StringConstant.property_to_name(propertyval)}", component="bytecode") + self.vprint( + f"{prefix} CLASS CONST NAME: {StringConstant.property_to_name(propertyval)}", + component="bytecode", + ) elif obj_to_create == 0x14: # Class property constant with alias. - propertyval, reference = struct.unpack(">BB", datachunk[offset_ptr:(offset_ptr + 2)]) + propertyval, reference = struct.unpack( + ">BB", datachunk[offset_ptr : (offset_ptr + 2)] + ) propertyval += 0x300 referenceval = self.__get_string(string_offsets[reference]) objects.append(StringConstant(propertyval, referenceval)) offset_ptr += 2 - self.vprint(f"{prefix} CLASS CONST NAME: {StringConstant.property_to_name(propertyval)}, ALIAS: {referenceval}", component="bytecode") + self.vprint( + f"{prefix} CLASS CONST NAME: {StringConstant.property_to_name(propertyval)}, ALIAS: {referenceval}", + component="bytecode", + ) # One would expect 0x15 to be identical to 0x12 but for class properties instead. However, it appears # that this has been omitted from game binaries. elif obj_to_create == 0x16: # Func property name. - propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x400 + propertyval = ( + struct.unpack( + ">B", datachunk[offset_ptr : (offset_ptr + 1)] + )[0] + + 0x400 + ) objects.append(StringConstant(propertyval)) offset_ptr += 1 - self.vprint(f"{prefix} FUNC CONST NAME: {StringConstant.property_to_name(propertyval)}", component="bytecode") + self.vprint( + f"{prefix} FUNC CONST NAME: {StringConstant.property_to_name(propertyval)}", + component="bytecode", + ) elif obj_to_create == 0x17: # Func property name referencing a string table entry. - propertyval, reference = struct.unpack(">BB", datachunk[offset_ptr:(offset_ptr + 2)]) + propertyval, reference = struct.unpack( + ">BB", datachunk[offset_ptr : (offset_ptr + 2)] + ) propertyval += 0x400 referenceval = self.__get_string(string_offsets[reference]) objects.append(StringConstant(propertyval, referenceval)) offset_ptr += 2 - self.vprint(f"{prefix} FUNC CONST NAME: {StringConstant.property_to_name(propertyval)}, ALIAS: {referenceval}", component="bytecode") + self.vprint( + f"{prefix} FUNC CONST NAME: {StringConstant.property_to_name(propertyval)}, ALIAS: {referenceval}", + component="bytecode", + ) # Same comment with 0x15 applies here with 0x18. elif obj_to_create == 0x19: # Other property name. - propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x200 + propertyval = ( + struct.unpack( + ">B", datachunk[offset_ptr : (offset_ptr + 1)] + )[0] + + 0x200 + ) objects.append(StringConstant(propertyval)) offset_ptr += 1 - self.vprint(f"{prefix} OTHER CONST NAME: {StringConstant.property_to_name(propertyval)}", component="bytecode") - elif obj_to_create == 0x1a: + self.vprint( + f"{prefix} OTHER CONST NAME: {StringConstant.property_to_name(propertyval)}", + component="bytecode", + ) + elif obj_to_create == 0x1A: # Other property name referencing a string table entry. - propertyval, reference = struct.unpack(">BB", datachunk[offset_ptr:(offset_ptr + 2)]) + propertyval, reference = struct.unpack( + ">BB", datachunk[offset_ptr : (offset_ptr + 2)] + ) propertyval += 0x200 referenceval = self.__get_string(string_offsets[reference]) objects.append(StringConstant(propertyval, referenceval)) offset_ptr += 2 - self.vprint(f"{prefix} OTHER CONST NAME: {StringConstant.property_to_name(propertyval)}, ALIAS: {referenceval}", component="bytecode") + self.vprint( + f"{prefix} OTHER CONST NAME: {StringConstant.property_to_name(propertyval)}, ALIAS: {referenceval}", + component="bytecode", + ) # Same comment with 0x15 and 0x18 applies here with 0x1b. - elif obj_to_create == 0x1c: + elif obj_to_create == 0x1C: # Event property name. - propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x500 + propertyval = ( + struct.unpack( + ">B", datachunk[offset_ptr : (offset_ptr + 1)] + )[0] + + 0x500 + ) objects.append(StringConstant(propertyval)) offset_ptr += 1 - self.vprint(f"{prefix} EVENT CONST NAME: {StringConstant.property_to_name(propertyval)}", component="bytecode") - elif obj_to_create == 0x1d: + self.vprint( + f"{prefix} EVENT CONST NAME: {StringConstant.property_to_name(propertyval)}", + component="bytecode", + ) + elif obj_to_create == 0x1D: # Event property name referencing a string table entry. - propertyval, reference = struct.unpack(">BB", datachunk[offset_ptr:(offset_ptr + 2)]) + propertyval, reference = struct.unpack( + ">BB", datachunk[offset_ptr : (offset_ptr + 2)] + ) propertyval += 0x500 referenceval = self.__get_string(string_offsets[reference]) objects.append(StringConstant(propertyval, referenceval)) offset_ptr += 2 - self.vprint(f"{prefix} EVENT CONST NAME: {StringConstant.property_to_name(propertyval)}, ALIAS: {referenceval}", component="bytecode") + self.vprint( + f"{prefix} EVENT CONST NAME: {StringConstant.property_to_name(propertyval)}, ALIAS: {referenceval}", + component="bytecode", + ) # Same comment with 0x15, 0x18 and 0x1b applies here with 0x1e. - elif obj_to_create == 0x1f: + elif obj_to_create == 0x1F: # Key constants. - propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x600 + propertyval = ( + struct.unpack( + ">B", datachunk[offset_ptr : (offset_ptr + 1)] + )[0] + + 0x600 + ) objects.append(StringConstant(propertyval)) offset_ptr += 1 - self.vprint(f"{prefix} KEY CONST NAME: {StringConstant.property_to_name(propertyval)}", component="bytecode") + self.vprint( + f"{prefix} KEY CONST NAME: {StringConstant.property_to_name(propertyval)}", + component="bytecode", + ) elif obj_to_create == 0x20: # Key property name referencing a string table entry. - propertyval, reference = struct.unpack(">BB", datachunk[offset_ptr:(offset_ptr + 2)]) + propertyval, reference = struct.unpack( + ">BB", datachunk[offset_ptr : (offset_ptr + 2)] + ) propertyval += 0x600 referenceval = self.__get_string(string_offsets[reference]) objects.append(StringConstant(propertyval, referenceval)) offset_ptr += 2 - self.vprint(f"{prefix} KEY CONST NAME: {StringConstant.property_to_name(propertyval)}, ALIAS: {referenceval}", component="bytecode") + self.vprint( + f"{prefix} KEY CONST NAME: {StringConstant.property_to_name(propertyval)}, ALIAS: {referenceval}", + component="bytecode", + ) # Same comment with 0x15, 0x18, 0x1b and 0x1e applies here with 0x21. elif obj_to_create == 0x22: # Pointer to global object. objects.append(GLOBAL) - self.vprint(f"{prefix} POINTER TO GLOBAL OBJECT", component="bytecode") + self.vprint( + f"{prefix} POINTER TO GLOBAL OBJECT", + component="bytecode", + ) elif obj_to_create == 0x23: # Negative infinity. objects.append(float("-inf")) self.vprint(f"{prefix} -INFINITY", component="bytecode") elif obj_to_create == 0x24: # Some other property name. - propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x700 + propertyval = ( + struct.unpack( + ">B", datachunk[offset_ptr : (offset_ptr + 1)] + )[0] + + 0x700 + ) objects.append(StringConstant(propertyval)) offset_ptr += 1 - self.vprint(f"{prefix} ETC2 CONST NAME: {StringConstant.property_to_name(propertyval)}", component="bytecode") + self.vprint( + f"{prefix} ETC2 CONST NAME: {StringConstant.property_to_name(propertyval)}", + component="bytecode", + ) # Possibly in newer binaries, 0x25 and 0x26 are implemented as 8-bit and 16-bit alias pointer # versions of 0x24. elif obj_to_create == 0x27: # Some other property name. - propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x800 + propertyval = ( + struct.unpack( + ">B", datachunk[offset_ptr : (offset_ptr + 1)] + )[0] + + 0x800 + ) objects.append(StringConstant(propertyval)) offset_ptr += 1 - self.vprint(f"{prefix} ORGFUNC2 CONST NAME: {StringConstant.property_to_name(propertyval)}", component="bytecode") + self.vprint( + f"{prefix} ORGFUNC2 CONST NAME: {StringConstant.property_to_name(propertyval)}", + component="bytecode", + ) # Possibly in newer binaries, 0x28 and 0x29 are implemented as 8-bit and 16-bit alias pointer # versions of 0x27. - elif obj_to_create == 0x2a: + elif obj_to_create == 0x2A: # Some other property name. - propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x900 + propertyval = ( + struct.unpack( + ">B", datachunk[offset_ptr : (offset_ptr + 1)] + )[0] + + 0x900 + ) objects.append(StringConstant(propertyval)) offset_ptr += 1 - self.vprint(f"{prefix} ETCFUNC2 CONST NAME: {StringConstant.property_to_name(propertyval)}", component="bytecode") + self.vprint( + f"{prefix} ETCFUNC2 CONST NAME: {StringConstant.property_to_name(propertyval)}", + component="bytecode", + ) # Possibly in newer binaries, 0x2b and 0x2c are implemented as 8-bit and 16-bit alias pointer # versions of 0x2a. - elif obj_to_create == 0x2d: + elif obj_to_create == 0x2D: # Some other property name. - propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0xa00 + propertyval = ( + struct.unpack( + ">B", datachunk[offset_ptr : (offset_ptr + 1)] + )[0] + + 0xA00 + ) objects.append(StringConstant(propertyval)) offset_ptr += 1 - self.vprint(f"{prefix} EVENT2 CONST NAME: {StringConstant.property_to_name(propertyval)}", component="bytecode") + self.vprint( + f"{prefix} EVENT2 CONST NAME: {StringConstant.property_to_name(propertyval)}", + component="bytecode", + ) # Possibly in newer binaries, 0x2e and 0x2f are implemented as 8-bit and 16-bit alias pointer # versions of 0x2d. elif obj_to_create == 0x30: # Some other property name. - propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0xb00 + propertyval = ( + struct.unpack( + ">B", datachunk[offset_ptr : (offset_ptr + 1)] + )[0] + + 0xB00 + ) objects.append(StringConstant(propertyval)) offset_ptr += 1 - self.vprint(f"{prefix} EVENT METHOD CONST NAME: {StringConstant.property_to_name(propertyval)}", component="bytecode") + self.vprint( + f"{prefix} EVENT METHOD CONST NAME: {StringConstant.property_to_name(propertyval)}", + component="bytecode", + ) # Possibly in newer binaries, 0x31 and 0x32 are implemented as 8-bit and 16-bit alias pointer # versions of 0x30. elif obj_to_create == 0x33: # Signed 64 bit integer init. Uses special "S64" type. - int64 = struct.unpack(">q", datachunk[offset_ptr:(offset_ptr + 8)]) + int64 = struct.unpack( + ">q", datachunk[offset_ptr : (offset_ptr + 8)] + ) objects.append(int64) offset_ptr += 8 - self.vprint(f"{prefix} INTEGER: {int64}", component="bytecode") + self.vprint( + f"{prefix} INTEGER: {int64}", component="bytecode" + ) elif obj_to_create == 0x34: # Some other property names. - propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0xc00 + propertyval = ( + struct.unpack( + ">B", datachunk[offset_ptr : (offset_ptr + 1)] + )[0] + + 0xC00 + ) objects.append(StringConstant(propertyval)) offset_ptr += 1 - self.vprint(f"{prefix} GENERIC CONST NAME: {StringConstant.property_to_name(propertyval)}", component="bytecode") + self.vprint( + f"{prefix} GENERIC CONST NAME: {StringConstant.property_to_name(propertyval)}", + component="bytecode", + ) # Possibly in newer binaries, 0x35 and 0x36 are implemented as 8-bit and 16-bit alias pointer # versions of 0x34. elif obj_to_create == 0x37: # Integer object but one byte. - ival = struct.unpack(">b", datachunk[offset_ptr:(offset_ptr + 1)])[0] + ival = struct.unpack( + ">b", datachunk[offset_ptr : (offset_ptr + 1)] + )[0] objects.append(ival) offset_ptr += 1 - self.vprint(f"{prefix} INTEGER: {ival}", component="bytecode") + self.vprint( + f"{prefix} INTEGER: {ival}", component="bytecode" + ) elif obj_to_create == 0x38: # Some other property names. - propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0xd00 + propertyval = ( + struct.unpack( + ">B", datachunk[offset_ptr : (offset_ptr + 1)] + )[0] + + 0xD00 + ) objects.append(StringConstant(propertyval)) offset_ptr += 1 - self.vprint(f"{prefix} GENERIC2 CONST NAME: {StringConstant.property_to_name(propertyval)}", component="bytecode") + self.vprint( + f"{prefix} GENERIC2 CONST NAME: {StringConstant.property_to_name(propertyval)}", + component="bytecode", + ) # Possibly in newer binaries, 0x39 and 0x3a are implemented as 8-bit and 16-bit alias pointer # versions of 0x38. else: - raise Exception(f"Unsupported object {hex(obj_to_create)} to push!") + raise Exception( + f"Unsupported object {hex(obj_to_create)} to push!" + ) obj_count -= 1 @@ -881,105 +1161,188 @@ class SWF(VerboseOutput, TrackedCoverage): actions.append(PushAction(lineno, objects)) elif opcode == AP2Action.INIT_REGISTER: - obj_count = struct.unpack(">B", datachunk[(offset_ptr + 1):(offset_ptr + 2)])[0] + obj_count = struct.unpack( + ">B", datachunk[(offset_ptr + 1) : (offset_ptr + 2)] + )[0] offset_ptr += 2 - self.vprint(f"{prefix} {lineno}: {action_name}", component="bytecode") + self.vprint( + f"{prefix} {lineno}: {action_name}", component="bytecode" + ) init_registers: List[Register] = [] while obj_count > 0: - register_no = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + register_no = struct.unpack( + ">B", datachunk[offset_ptr : (offset_ptr + 1)] + )[0] init_registers.append(Register(register_no)) offset_ptr += 1 obj_count -= 1 - self.vprint(f"{prefix} REGISTER NO: {register_no}", component="bytecode") + self.vprint( + f"{prefix} REGISTER NO: {register_no}", + component="bytecode", + ) self.vprint(f"{prefix} END_{action_name}", component="bytecode") actions.append(InitRegisterAction(lineno, init_registers)) elif opcode == AP2Action.STORE_REGISTER: - obj_count = struct.unpack(">B", datachunk[(offset_ptr + 1):(offset_ptr + 2)])[0] + obj_count = struct.unpack( + ">B", datachunk[(offset_ptr + 1) : (offset_ptr + 2)] + )[0] offset_ptr += 2 - self.vprint(f"{prefix} {lineno}: {action_name}", component="bytecode") + self.vprint( + f"{prefix} {lineno}: {action_name}", component="bytecode" + ) store_registers: List[Register] = [] while obj_count > 0: - register_no = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + register_no = struct.unpack( + ">B", datachunk[offset_ptr : (offset_ptr + 1)] + )[0] store_registers.append(Register(register_no)) offset_ptr += 1 obj_count -= 1 - self.vprint(f"{prefix} REGISTER NO: {register_no}", component="bytecode") + self.vprint( + f"{prefix} REGISTER NO: {register_no}", + component="bytecode", + ) self.vprint(f"{prefix} END_{action_name}", component="bytecode") - actions.append(StoreRegisterAction(lineno, store_registers, preserve_stack=True)) + actions.append( + StoreRegisterAction(lineno, store_registers, preserve_stack=True) + ) elif opcode == AP2Action.STORE_REGISTER2: - register_no = struct.unpack(">B", datachunk[(offset_ptr + 1):(offset_ptr + 2)])[0] + register_no = struct.unpack( + ">B", datachunk[(offset_ptr + 1) : (offset_ptr + 2)] + )[0] offset_ptr += 2 - self.vprint(f"{prefix} {lineno}: {action_name}", component="bytecode") - self.vprint(f"{prefix} REGISTER NO: {register_no}", component="bytecode") + self.vprint( + f"{prefix} {lineno}: {action_name}", component="bytecode" + ) + self.vprint( + f"{prefix} REGISTER NO: {register_no}", component="bytecode" + ) self.vprint(f"{prefix} END_{action_name}", component="bytecode") - actions.append(StoreRegisterAction(lineno, [Register(register_no)], preserve_stack=False)) + actions.append( + StoreRegisterAction( + lineno, [Register(register_no)], preserve_stack=False + ) + ) elif opcode == AP2Action.IF: - jump_if_true_offset = struct.unpack(">h", datachunk[(offset_ptr + 1):(offset_ptr + 3)])[0] - jump_if_true_offset += (lineno + 3) + jump_if_true_offset = struct.unpack( + ">h", datachunk[(offset_ptr + 1) : (offset_ptr + 3)] + )[0] + jump_if_true_offset += lineno + 3 offset_ptr += 3 - self.vprint(f"{prefix} {lineno}: Offset If True: {jump_if_true_offset}", component="bytecode") - actions.append(IfAction(lineno, IfAction.COMP_IS_TRUE, jump_if_true_offset)) + self.vprint( + f"{prefix} {lineno}: Offset If True: {jump_if_true_offset}", + component="bytecode", + ) + actions.append( + IfAction(lineno, IfAction.COMP_IS_TRUE, jump_if_true_offset) + ) elif opcode == AP2Action.IF2: - if2_type, jump_if_true_offset = struct.unpack(">Bh", datachunk[(offset_ptr + 1):(offset_ptr + 4)]) - jump_if_true_offset += (lineno + 4) + if2_type, jump_if_true_offset = struct.unpack( + ">Bh", datachunk[(offset_ptr + 1) : (offset_ptr + 4)] + ) + jump_if_true_offset += lineno + 4 offset_ptr += 4 - self.vprint(f"{prefix} {lineno}: {action_name} {IfAction.comparison_to_str(if2_type)}, Offset If True: {jump_if_true_offset}", component="bytecode") + self.vprint( + f"{prefix} {lineno}: {action_name} {IfAction.comparison_to_str(if2_type)}, Offset If True: {jump_if_true_offset}", + component="bytecode", + ) actions.append(IfAction(lineno, if2_type, jump_if_true_offset)) elif opcode == AP2Action.JUMP: - jump_offset = struct.unpack(">h", datachunk[(offset_ptr + 1):(offset_ptr + 3)])[0] - jump_offset += (lineno + 3) + jump_offset = struct.unpack( + ">h", datachunk[(offset_ptr + 1) : (offset_ptr + 3)] + )[0] + jump_offset += lineno + 3 offset_ptr += 3 - self.vprint(f"{prefix} {lineno}: {action_name} Offset: {jump_offset}", component="bytecode") + self.vprint( + f"{prefix} {lineno}: {action_name} Offset: {jump_offset}", + component="bytecode", + ) actions.append(JumpAction(lineno, jump_offset)) elif opcode == AP2Action.WITH: - skip_offset = struct.unpack(">H", datachunk[(offset_ptr + 1):(offset_ptr + 3)])[0] + skip_offset = struct.unpack( + ">H", datachunk[(offset_ptr + 1) : (offset_ptr + 3)] + )[0] offset_ptr += 3 # TODO: I have absolutely no idea what the data which exists in the bytecode buffer at this point # represents... - unknown_data = datachunk[offset_ptr:(offset_ptr + skip_offset)] + unknown_data = datachunk[offset_ptr : (offset_ptr + skip_offset)] offset_ptr += skip_offset - self.vprint(f"{prefix} {lineno}: {action_name} Unknown Data Length: {skip_offset}", component="bytecode") + self.vprint( + f"{prefix} {lineno}: {action_name} Unknown Data Length: {skip_offset}", + component="bytecode", + ) actions.append(WithAction(lineno, unknown_data)) elif opcode == AP2Action.ADD_NUM_VARIABLE: - amount_to_add = struct.unpack(">b", datachunk[(offset_ptr + 1):(offset_ptr + 2)])[0] + amount_to_add = struct.unpack( + ">b", datachunk[(offset_ptr + 1) : (offset_ptr + 2)] + )[0] offset_ptr += 2 - self.vprint(f"{prefix} {lineno}: {action_name} Add Value: {amount_to_add}", component="bytecode") + self.vprint( + f"{prefix} {lineno}: {action_name} Add Value: {amount_to_add}", + component="bytecode", + ) actions.append(AddNumVariableAction(lineno, amount_to_add)) elif opcode == AP2Action.GET_URL2: - get_url_action = struct.unpack(">B", datachunk[(offset_ptr + 1):(offset_ptr + 2)])[0] + get_url_action = struct.unpack( + ">B", datachunk[(offset_ptr + 1) : (offset_ptr + 2)] + )[0] offset_ptr += 2 - self.vprint(f"{prefix} {lineno}: {action_name} URL Action: {get_url_action >> 6}", component="bytecode") + self.vprint( + f"{prefix} {lineno}: {action_name} URL Action: {get_url_action >> 6}", + component="bytecode", + ) actions.append(GetURL2Action(lineno, get_url_action >> 6)) elif opcode == AP2Action.START_DRAG: - constraint = struct.unpack(">b", datachunk[(offset_ptr + 1):(offset_ptr + 2)])[0] + constraint = struct.unpack( + ">b", datachunk[(offset_ptr + 1) : (offset_ptr + 2)] + )[0] offset_ptr += 2 - self.vprint(f"{prefix} {lineno}: {action_name} Constrain Mouse: {'yes' if constraint > 0 else ('no' if constraint == 0 else 'check stack')}", component="bytecode") - actions.append(StartDragAction(lineno, constrain=True if constraint > 0 else (False if constraint == 0 else None))) + self.vprint( + f"{prefix} {lineno}: {action_name} Constrain Mouse: {'yes' if constraint > 0 else ('no' if constraint == 0 else 'check stack')}", + component="bytecode", + ) + actions.append( + StartDragAction( + lineno, + constrain=True + if constraint > 0 + else (False if constraint == 0 else None), + ) + ) elif opcode == AP2Action.ADD_NUM_REGISTER: - register_no, amount_to_add = struct.unpack(">Bb", datachunk[(offset_ptr + 1):(offset_ptr + 3)]) + register_no, amount_to_add = struct.unpack( + ">Bb", datachunk[(offset_ptr + 1) : (offset_ptr + 3)] + ) offset_ptr += 3 - self.vprint(f"{prefix} {lineno}: {action_name} Register No: {register_no}, Add Value: {amount_to_add}", component="bytecode") - actions.append(AddNumRegisterAction(lineno, Register(register_no), amount_to_add)) + self.vprint( + f"{prefix} {lineno}: {action_name} Register No: {register_no}, Add Value: {amount_to_add}", + component="bytecode", + ) + actions.append( + AddNumRegisterAction(lineno, Register(register_no), amount_to_add) + ) elif opcode == AP2Action.GOTO_FRAME2: - flags = struct.unpack(">B", datachunk[(offset_ptr + 1):(offset_ptr + 2)])[0] + flags = struct.unpack( + ">B", datachunk[(offset_ptr + 1) : (offset_ptr + 2)] + )[0] offset_ptr += 2 if flags & 0x1: @@ -989,15 +1352,24 @@ class SWF(VerboseOutput, TrackedCoverage): if flags & 0x2: # Additional frames to add on top of stack value. - additional_frames = struct.unpack(">H", datachunk[offset_ptr:(offset_ptr + 2)])[0] + additional_frames = struct.unpack( + ">H", datachunk[offset_ptr : (offset_ptr + 2)] + )[0] offset_ptr += 2 else: additional_frames = 0 - self.vprint(f"{prefix} {lineno}: {action_name} AND {post} Additional Frames: {additional_frames}", component="bytecode") - actions.append(GotoFrame2Action(lineno, additional_frames, stop=bool(flags & 0x1))) + self.vprint( + f"{prefix} {lineno}: {action_name} AND {post} Additional Frames: {additional_frames}", + component="bytecode", + ) + actions.append( + GotoFrame2Action(lineno, additional_frames, stop=bool(flags & 0x1)) + ) else: - raise Exception(f"Can't advance, no handler for opcode {opcode} ({hex(opcode)})!") + raise Exception( + f"Can't advance, no handler for opcode {opcode} ({hex(opcode)})!" + ) return ByteCode(bytecode_name, actions, offset_ptr) @@ -1017,17 +1389,24 @@ class SWF(VerboseOutput, TrackedCoverage): if size != 4: raise Exception(f"Invalid shape size {size}") - unknown, shape_id = struct.unpack("> 2) * + (((unk_flags & 0x10) | 0x8) >> 2) + * # Either 1 or 2, depending on unk_flags & 0x1 set. - ((unk_flags & 1) + 1) * + ((unk_flags & 1) + 1) + * # Raw size as read from the header above. - unk_size * + unk_size + * # I assume this is some number of shorts, much like many other # file formats, so this is why all of these counts are doubled. 2 ) - self.vprint(f"{prefix} WTF: {hex(unk_flags)}, {unk_size}, {chunk_size}", component="tags") + self.vprint( + f"{prefix} WTF: {hex(unk_flags)}, {unk_size}, {chunk_size}", + component="tags", + ) # Skip past data. running_pointer += chunk_size @@ -1545,23 +2078,33 @@ class SWF(VerboseOutput, TrackedCoverage): # I have no idea what this is, but the two shorts that it pulls out are assigned # to the same variables as those in 0x2000000000, so they're obviously linked. unhandled_flags &= ~0x1000000000 - unk1, unk2, unk3 = struct.unpack(" extra_data: # There seems to be some amount of data left over at the end, not sure what it # is or does. I don't see any references to it being used in the tag loader. pass - self.vprint(f"{prefix} Tag ID: {text_id}, Count of Entries: {text_data_count}, Count of Sub Entries: {sub_data_total_count}", component="tags") + self.vprint( + f"{prefix} Tag ID: {text_id}, Count of Entries: {text_data_count}, Count of Sub Entries: {sub_data_total_count}", + component="tags", + ) lines: List[AP2TextLine] = [] for i in range(text_data_count): chunk_data_offset = dataoffset + text_data_offset + (20 * i) - chunk_flags, sub_data_count, font_tag, font_height, xpos, ypos, sub_data_offset, rgba = struct.unpack( + ( + chunk_flags, + sub_data_count, + font_tag, + font_height, + xpos, + ypos, + sub_data_offset, + rgba, + ) = struct.unpack( "> 24) & 0xFF) / 255.0, ) - self.vprint(f"{prefix} Font Tag: {font_tag}, Font Height: {font_height}, X: {xpos}, Y: {ypos}, Count of Sub-Entries: {sub_data_count}, Color: {color}", component="tags") + self.vprint( + f"{prefix} Font Tag: {font_tag}, Font Height: {font_height}, X: {xpos}, Y: {ypos}, Count of Sub-Entries: {sub_data_count}, Color: {color}", + component="tags", + ) base_offset = dataoffset + (sub_data_offset * 4) + sub_data_base_offset offsets: List[AP2TextChar] = [] @@ -1706,14 +2279,17 @@ class SWF(VerboseOutput, TrackedCoverage): sub_chunk_offset = base_offset + (i * 4) font_text_index, xoff = struct.unpack( "> 2) & 0x3}, Unk4-2: {(unk4 & 0x3)}", component="tags") + self.vprint( + f"{prefix} {label} Flags: {hex(flags)}, Unk3: {unk3}, Unk4-1: {(unk4 >> 2) & 0x3}, Unk4-2: {(unk4 & 0x3)}", + component="tags", + ) unprocessed_flags = flags if flags & 0x1: - int1, int2 = struct.unpack("> 24) & 0xFF) / 255.0, ) - self.vprint(f"{prefix} Start Color: {color1}, End Color: {color2}", component="tags") + self.vprint( + f"{prefix} Start Color: {color1}, End Color: {color2}", + component="tags", + ) if flags & 0x8: - a1, d1, a2, d2, b1, c1, b2, c2, tx1, ty1, tx2, ty2 = struct.unpack("> 16) & 0xFF) / 255.0 multcolor.b = float((rgba >> 8) & 0xFF) / 255.0 multcolor.a = float(rgba & 0xFF) / 255.0 - self.vprint(f"{prefix} Mult Color: {multcolor}", component="tags") + self.vprint( + f"{prefix} Mult Color: {multcolor}", component="tags" + ) if flags & 0x4000: # Additive color present, smaller integers. - rgba = struct.unpack("> 16) & 0xFF) / 255.0 addcolor.b = float((rgba >> 8) & 0xFF) / 255.0 addcolor.a = float(rgba & 0xFF) / 255.0 - self.vprint(f"{prefix} Add Color: {addcolor}", component="tags") + self.vprint( + f"{prefix} Add Color: {addcolor}", component="tags" + ) if flags & 0x8000: # Some sort of filter data? Not sure what this is either. Needs more investigation # if I encounter files with it. - count, filter_size = struct.unpack(" Tuple[List[Tag], List[Frame], Dict[str, int]]: - name_reference_flags, name_reference_count, frame_count, tags_count, name_reference_offset, frame_offset, tags_offset = struct.unpack( - "> 20) & 0xFFF frames.append(Frame(start_tag_offset, num_tags_to_play)) - self.vprint(f"{prefix} Frame Start Tag: {start_tag_offset}, Count: {num_tags_to_play}", component="tags") + self.vprint( + f"{prefix} Frame Start Tag: {start_tag_offset}, Count: {num_tags_to_play}", + component="tags", + ) for j in range(num_tags_to_play): if start_tag_offset + j in tag_to_frame: raise Exception("Logic error!") @@ -2168,7 +2961,7 @@ class SWF(VerboseOutput, TrackedCoverage): tags: List[Tag] = [] self.vprint(f"{prefix}Number of Tags: {tags_count}", component="tags") for i in range(tags_count): - tag = struct.unpack("> 22) & 0x3FF @@ -2177,20 +2970,44 @@ class SWF(VerboseOutput, TrackedCoverage): if size > 0x200000: raise Exception(f"Invalid tag size {size} ({hex(size)})") - self.vprint(f"{prefix} Tag: {hex(tagid)} ({AP2Tag.tag_to_name(tagid)}), Size: {hex(size)}, Offset: {hex(tags_offset + 4)}", component="tags") - tags.append(self.__parse_tag(ap2_version, afp_version, ap2data, tagid, size, tags_offset + 4, sprite, tag_to_frame.get(i, 'orphan'), prefix=prefix)) - tags_offset += ((size + 3) & 0xFFFFFFFC) + 4 # Skip past tag header and data, rounding to the nearest 4 bytes. + self.vprint( + f"{prefix} Tag: {hex(tagid)} ({AP2Tag.tag_to_name(tagid)}), Size: {hex(size)}, Offset: {hex(tags_offset + 4)}", + component="tags", + ) + tags.append( + self.__parse_tag( + ap2_version, + afp_version, + ap2data, + tagid, + size, + tags_offset + 4, + sprite, + tag_to_frame.get(i, "orphan"), + prefix=prefix, + ) + ) + tags_offset += ( + (size + 3) & 0xFFFFFFFC + ) + 4 # Skip past tag header and data, rounding to the nearest 4 bytes. # Finally, parse frame labels - self.vprint(f"{prefix}Number of Frame Labels: {name_reference_count}, Flags: {hex(name_reference_flags)}", component="tags") + self.vprint( + f"{prefix}Number of Frame Labels: {name_reference_count}, Flags: {hex(name_reference_flags)}", + component="tags", + ) labels: Dict[str, int] = {} for _ in range(name_reference_count): - frameno, stringoffset = struct.unpack("> 13) & 0x7 - loops = ((swapword >> 7) & 0x3F) + loops = (swapword >> 7) & 0x3F data_offset += offset if swap_type == 0: @@ -2224,12 +3041,16 @@ class SWF(VerboseOutput, TrackedCoverage): # Reverse the bytes for _ in range(loops + 1): - data[data_offset:(data_offset + swap_len[swap_type])] = data[data_offset:(data_offset + swap_len[swap_type])][::-1] + data[data_offset : (data_offset + swap_len[swap_type])] = data[ + data_offset : (data_offset + swap_len[swap_type]) + ][::-1] data_offset += swap_len[swap_type] return bytes(data) - def __descramble_stringtable(self, scrambled_data: bytes, stringtable_offset: int, stringtable_size: int) -> bytes: + def __descramble_stringtable( + self, scrambled_data: bytes, stringtable_offset: int, stringtable_size: int + ) -> bytes: data = bytearray(scrambled_data) curstring: List[int] = [] curloc = stringtable_offset @@ -2243,7 +3064,10 @@ class SWF(VerboseOutput, TrackedCoverage): if byte == 0: if curstring: # We found a string! - self.__strings[curloc - stringtable_offset] = (bytes(curstring).decode('utf8'), False) + self.__strings[curloc - stringtable_offset] = ( + bytes(curstring).decode("utf8"), + False, + ) curloc = stringtable_offset + i + 1 curstring = [] curloc = stringtable_offset + i + 1 @@ -2275,15 +3099,27 @@ class SWF(VerboseOutput, TrackedCoverage): data = self.__descramble(self.data, self.descramble_info) # Start with the basic file header. - magic, length, version, nameoffset, flags, left, right, top, bottom = struct.unpack("<4sIHHIHHHH", data[0:24]) + ( + magic, + length, + version, + nameoffset, + flags, + left, + right, + top, + bottom, + ) = struct.unpack("<4sIHHIHHHH", data[0:24]) self.add_coverage(0, 24) ap2_data_version = magic[0] & 0xFF magic = bytes([magic[3] & 0x7F, magic[2] & 0x7F, magic[1] & 0x7F, 0x0]) - if magic != b'AP2\x00': + if magic != b"AP2\x00": raise Exception(f"Unrecognzied magic {magic}!") if length != len(data): - raise Exception(f"Unexpected length in AFP header, {length} != {len(data)}!") + raise Exception( + f"Unexpected length in AFP header, {length} != {len(data)}!" + ) if ap2_data_version not in [7, 8, 9, 10]: raise Exception(f"Unsupported AP2 container version {ap2_data_version}!") if version != 0x200: @@ -2344,12 +3180,16 @@ class SWF(VerboseOutput, TrackedCoverage): # Get exported SWF name. self.exported_name = self.__get_string(nameoffset) self.vprint(f"{os.linesep}AFP name: {self.name}", component="core") - self.vprint(f"Container Version: {hex(self.container_version)}", component="core") + self.vprint( + f"Container Version: {hex(self.container_version)}", component="core" + ) self.vprint(f"Version: {hex(self.data_version)}", component="core") self.vprint(f"Exported Name: {self.exported_name}", component="core") self.vprint(f"SWF Flags: {hex(flags)}", component="core") if flags & 0x1: - self.vprint(f" 0x1: Movie background color: {self.color}", component="core") + self.vprint( + f" 0x1: Movie background color: {self.color}", component="core" + ) else: self.vprint(" 0x1: No movie background color", component="core") if flags & 0x2: @@ -2357,10 +3197,17 @@ class SWF(VerboseOutput, TrackedCoverage): else: self.vprint(" 0x2: FPS is a float", component="core") if flags & 0x4: - self.vprint(" 0x4: Imported tag initializer section present", component="core") + self.vprint( + " 0x4: Imported tag initializer section present", component="core" + ) else: - self.vprint(" 0x4: Imported tag initializer section not present", component="core") - self.vprint(f"Dimensions: {int(self.location.width)}x{int(self.location.height)}", component="core") + self.vprint( + " 0x4: Imported tag initializer section not present", component="core" + ) + self.vprint( + f"Dimensions: {int(self.location.width)}x{int(self.location.height)}", + component="core", + ) self.vprint(f"Requested FPS: {self.fps}", component="core") # Exported assets @@ -2373,19 +3220,26 @@ class SWF(VerboseOutput, TrackedCoverage): self.exported_tags = {} self.vprint(f"Number of Exported Tags: {num_exported_assets}", component="tags") for assetno in range(num_exported_assets): - asset_tag_id, asset_string_offset = struct.unpack("= len(self.frames): - raise Exception(f"Unexpected frame {frame}, we only have {len(self.frames)} frames in this movie!") + raise Exception( + f"Unexpected frame {frame}, we only have {len(self.frames)} frames in this movie!" + ) self.frames[frame].imported_tags.append(TagPointer(tag_id, bytecode)) if verbose: diff --git a/bemani/format/afp/types/__init__.py b/bemani/format/afp/types/__init__.py index 7232cd9..91b71a1 100644 --- a/bemani/format/afp/types/__init__.py +++ b/bemani/format/afp/types/__init__.py @@ -91,91 +91,91 @@ from .aa import AAMode __all__ = [ - 'Matrix', - 'Color', - 'Point', - 'Rectangle', - 'AP2Tag', - 'AP2Action', - 'AP2Object', - 'AP2Pointer', - 'AP2Trigger', - 'Expression', - 'GenericObject', - 'NULL', - 'UNDEFINED', - 'THIS', - 'ROOT', - 'PARENT', - 'CLIP', - 'GLOBAL', - 'Register', - 'StringConstant', - 'ArithmeticExpression', - 'MethodCall', - 'FunctionCall', - 'GetTimeFunctionCall', - 'GetPathFunctionCall', - 'Variable', - 'Member', - 'NewFunction', - 'NewObject', - 'Array', - 'Object', - 'NotExpression', - 'name_ref', - 'value_ref', - 'object_ref', - 'ConvertedAction', - 'Statement', - 'StopMovieStatement', - 'PlayMovieStatement', - 'GotoFrameStatement', - 'GetURL2Statement', - 'NextFrameStatement', - 'PreviousFrameStatement', - 'StopSoundStatement', - 'CloneSpriteStatement', - 'RemoveSpriteStatement', - 'DebugTraceStatement', - 'NopStatement', - 'IfStatement', - 'SwitchStatement', - 'SwitchCase', - 'DoWhileStatement', - 'WhileStatement', - 'ForStatement', - 'BreakStatement', - 'ContinueStatement', - 'ReturnStatement', - 'NullReturnStatement', - 'ThrowStatement', - 'GotoStatement', - 'DefineLabelStatement', - 'SetVariableStatement', - 'DeleteVariableStatement', - 'SetMemberStatement', - 'DeleteMemberStatement', - 'SetLocalStatement', - 'StoreRegisterStatement', - 'ExpressionStatement', - 'IfExpr', - 'IsUndefinedIf', - 'IsBooleanIf', - 'TwoParameterIf', - 'AndIf', - 'OrIf', - 'PushAction', - 'InitRegisterAction', - 'StoreRegisterAction', - 'IfAction', - 'JumpAction', - 'WithAction', - 'GotoFrame2Action', - 'AddNumVariableAction', - 'AddNumRegisterAction', - 'GetURL2Action', - 'StartDragAction', - 'DefineFunction2Action', - 'AAMode', + "Matrix", + "Color", + "Point", + "Rectangle", + "AP2Tag", + "AP2Action", + "AP2Object", + "AP2Pointer", + "AP2Trigger", + "Expression", + "GenericObject", + "NULL", + "UNDEFINED", + "THIS", + "ROOT", + "PARENT", + "CLIP", + "GLOBAL", + "Register", + "StringConstant", + "ArithmeticExpression", + "MethodCall", + "FunctionCall", + "GetTimeFunctionCall", + "GetPathFunctionCall", + "Variable", + "Member", + "NewFunction", + "NewObject", + "Array", + "Object", + "NotExpression", + "name_ref", + "value_ref", + "object_ref", + "ConvertedAction", + "Statement", + "StopMovieStatement", + "PlayMovieStatement", + "GotoFrameStatement", + "GetURL2Statement", + "NextFrameStatement", + "PreviousFrameStatement", + "StopSoundStatement", + "CloneSpriteStatement", + "RemoveSpriteStatement", + "DebugTraceStatement", + "NopStatement", + "IfStatement", + "SwitchStatement", + "SwitchCase", + "DoWhileStatement", + "WhileStatement", + "ForStatement", + "BreakStatement", + "ContinueStatement", + "ReturnStatement", + "NullReturnStatement", + "ThrowStatement", + "GotoStatement", + "DefineLabelStatement", + "SetVariableStatement", + "DeleteVariableStatement", + "SetMemberStatement", + "DeleteMemberStatement", + "SetLocalStatement", + "StoreRegisterStatement", + "ExpressionStatement", + "IfExpr", + "IsUndefinedIf", + "IsBooleanIf", + "TwoParameterIf", + "AndIf", + "OrIf", + "PushAction", + "InitRegisterAction", + "StoreRegisterAction", + "IfAction", + "JumpAction", + "WithAction", + "GotoFrame2Action", + "AddNumVariableAction", + "AddNumRegisterAction", + "GetURL2Action", + "StartDragAction", + "DefineFunction2Action", + "AAMode", ] diff --git a/bemani/format/afp/types/ap2.py b/bemani/format/afp/types/ap2.py index da6347d..4f4775e 100644 --- a/bemani/format/afp/types/ap2.py +++ b/bemani/format/afp/types/ap2.py @@ -22,9 +22,9 @@ class AP2Object: STRING: Final[int] = 0x7 POINTER: Final[int] = 0x8 OBJECT: Final[int] = 0x9 - INFINITY: Final[int] = 0xa - CONST_STRING: Final[int] = 0xb - BUILT_IN_FUNCTION: Final[int] = 0xc + INFINITY: Final[int] = 0xA + CONST_STRING: Final[int] = 0xB + BUILT_IN_FUNCTION: Final[int] = 0xC class AP2Pointer: @@ -52,12 +52,12 @@ class AP2Pointer: xmlNode_W: Final[int] = 0x57 textFormat_W: Final[int] = 0x58 sharedObject_W: Final[int] = 0x59 - sharedObjectData_W: Final[int] = 0x5a - textField_W: Final[int] = 0x5b - xmlAttrib_W: Final[int] = 0x5c - bitmapdata_W: Final[int] = 0x5d - matrix_W: Final[int] = 0x5e - point_W: Final[int] = 0x5f + sharedObjectData_W: Final[int] = 0x5A + textField_W: Final[int] = 0x5B + xmlAttrib_W: Final[int] = 0x5C + bitmapdata_W: Final[int] = 0x5D + matrix_W: Final[int] = 0x5E + point_W: Final[int] = 0x5F ColorMatrixFilter_W: Final[int] = 0x60 String_W: Final[int] = 0x61 Boolean_W: Final[int] = 0x62 @@ -67,27 +67,27 @@ class AP2Pointer: super_W: Final[int] = 0x66 transform_W: Final[int] = 0x68 colorTransform_W: Final[int] = 0x69 - rectangle_W: Final[int] = 0x6a + rectangle_W: Final[int] = 0x6A # All of these can have prototypes, not sure what the "C" stands for. Object_C: Final[int] = 0x78 MovieClip_C: Final[int] = 0x79 - Sound_C: Final[int] = 0x7a - Color_C: Final[int] = 0x7b - Date_C: Final[int] = 0x7c - Array_C: Final[int] = 0x7d - XML_C: Final[int] = 0x7e - XMLNode_C: Final[int] = 0x7f + Sound_C: Final[int] = 0x7A + Color_C: Final[int] = 0x7B + Date_C: Final[int] = 0x7C + Array_C: Final[int] = 0x7D + XML_C: Final[int] = 0x7E + XMLNode_C: Final[int] = 0x7F TextFormat_C: Final[int] = 0x80 TextField_C: Final[int] = 0x83 BitmapData_C: Final[int] = 0x85 matrix_C: Final[int] = 0x86 point_C: Final[int] = 0x87 String_C: Final[int] = 0x89 - Boolean_C: Final[int] = 0x8a - Number_C: Final[int] = 0x8b - Function_C: Final[int] = 0x8c - aplib_C: Final[int] = 0x8f + Boolean_C: Final[int] = 0x8A + Number_C: Final[int] = 0x8B + Function_C: Final[int] = 0x8C + aplib_C: Final[int] = 0x8F transform_C: Final[int] = 0x90 colorTransform_C: Final[int] = 0x91 rectangle_C: Final[int] = 0x92 @@ -95,18 +95,18 @@ class AP2Pointer: XMLController_C: Final[int] = 0x94 eManager_C: Final[int] = 0x95 - stage_O: Final[int] = 0xa0 - math_O: Final[int] = 0xa1 - key_O: Final[int] = 0xa2 - mouse_O: Final[int] = 0xa3 - system_O: Final[int] = 0xa4 - sharedObject_O: Final[int] = 0xa5 - flash_O: Final[int] = 0xa6 - global_O: Final[int] = 0xa7 + stage_O: Final[int] = 0xA0 + math_O: Final[int] = 0xA1 + key_O: Final[int] = 0xA2 + mouse_O: Final[int] = 0xA3 + system_O: Final[int] = 0xA4 + sharedObject_O: Final[int] = 0xA5 + flash_O: Final[int] = 0xA6 + global_O: Final[int] = 0xA7 - display_P: Final[int] = 0xb4 - geom_P: Final[int] = 0xb5 - filtesr_P: Final[int] = 0xb6 + display_P: Final[int] = 0xB4 + geom_P: Final[int] = 0xB5 + filtesr_P: Final[int] = 0xB6 class AP2Trigger: @@ -139,12 +139,12 @@ class AP2Tag: DEFINE_BUTTON: Final[int] = 0x7 JPEG_TABLES: Final[int] = 0x8 BACKGROUND_COLOR: Final[int] = 0x9 - DEFINE_FONT: Final[int] = 0xa - DEFINE_TEXT: Final[int] = 0xb - DO_ACTION: Final[int] = 0xc - DEFINE_FONT_INFO: Final[int] = 0xd - DEFINE_SOUND: Final[int] = 0xe - START_SOUND: Final[int] = 0xf + DEFINE_FONT: Final[int] = 0xA + DEFINE_TEXT: Final[int] = 0xB + DO_ACTION: Final[int] = 0xC + DEFINE_FONT_INFO: Final[int] = 0xD + DEFINE_SOUND: Final[int] = 0xE + START_SOUND: Final[int] = 0xF DEFINE_BUTTON_SOUND: Final[int] = 0x11 SOUND_STREAM_HEAD: Final[int] = 0x12 SOUND_STREAM_BLOCK: Final[int] = 0x13 @@ -153,8 +153,8 @@ class AP2Tag: DEFINE_SHAPE2: Final[int] = 0x16 DEFINE_BUTTON_CXFORM: Final[int] = 0x17 PROTECT: Final[int] = 0x18 - PLACE_OBJECT2: Final[int] = 0x1a - REMOVE_OBJECT2: Final[int] = 0x1c + PLACE_OBJECT2: Final[int] = 0x1A + REMOVE_OBJECT2: Final[int] = 0x1C DEFINE_SHAPE3: Final[int] = 0x20 DEFINE_TEXT2: Final[int] = 0x21 DEFINE_BUTTON2: Final[int] = 0x22 @@ -162,24 +162,24 @@ class AP2Tag: DEFINE_BITS_LOSSLESS2: Final[int] = 0x24 DEFINE_EDIT_TEXT: Final[int] = 0x25 DEFINE_SPRITE: Final[int] = 0x27 - FRAME_LABEL: Final[int] = 0x2b - SOUND_STREAM_HEAD2: Final[int] = 0x2d - DEFINE_MORPH_SHAPE: Final[int] = 0x2e + FRAME_LABEL: Final[int] = 0x2B + SOUND_STREAM_HEAD2: Final[int] = 0x2D + DEFINE_MORPH_SHAPE: Final[int] = 0x2E DEFINE_FONT2: Final[int] = 0x30 EXPORT_ASSETS: Final[int] = 0x38 IMPORT_ASSETS: Final[int] = 0x39 - DO_INIT_ACTION: Final[int] = 0x3b - DEFINE_VIDEO_STREAM: Final[int] = 0x3c - VIDEO_FRAME: Final[int] = 0x3d - DEFINE_FONT_INFO2: Final[int] = 0x3e + DO_INIT_ACTION: Final[int] = 0x3B + DEFINE_VIDEO_STREAM: Final[int] = 0x3C + VIDEO_FRAME: Final[int] = 0x3D + DEFINE_FONT_INFO2: Final[int] = 0x3E ENABLE_DEBUGGER2: Final[int] = 0x40 SCRIPT_LIMITS: Final[int] = 0x41 SET_TAB_INDEX: Final[int] = 0x42 PLACE_OBJECT3: Final[int] = 0x46 IMPORT_ASSETS2: Final[int] = 0x47 - DEFINE_FONT3: Final[int] = 0x4b - METADATA: Final[int] = 0x4d - DEFINE_SCALING_GRID: Final[int] = 0x4e + DEFINE_FONT3: Final[int] = 0x4B + METADATA: Final[int] = 0x4D + DEFINE_SCALING_GRID: Final[int] = 0x4E DEFINE_SHAPE4: Final[int] = 0x53 DEFINE_MORPH_SHAPE2: Final[int] = 0x54 SCENE_LABEL: Final[int] = 0x56 @@ -188,15 +188,15 @@ class AP2Tag: AFP_SOUND_STREAM_BLOCK: Final[int] = 0x66 AFP_DEFINE_FONT: Final[int] = 0x67 AFP_DEFINE_SHAPE: Final[int] = 0x68 - AEP_PLACE_OBJECT: Final[int] = 0x6e + AEP_PLACE_OBJECT: Final[int] = 0x6E AP2_DEFINE_FONT: Final[int] = 0x78 AP2_DEFINE_SPRITE: Final[int] = 0x79 - AP2_DO_ACTION: Final[int] = 0x7a - AP2_DEFINE_BUTTON: Final[int] = 0x7b - AP2_DEFINE_BUTTON_SOUND: Final[int] = 0x7c - AP2_DEFINE_TEXT: Final[int] = 0x7d - AP2_DEFINE_EDIT_TEXT: Final[int] = 0x7e - AP2_PLACE_OBJECT: Final[int] = 0x7f + AP2_DO_ACTION: Final[int] = 0x7A + AP2_DEFINE_BUTTON: Final[int] = 0x7B + AP2_DEFINE_BUTTON_SOUND: Final[int] = 0x7C + AP2_DEFINE_TEXT: Final[int] = 0x7D + AP2_DEFINE_EDIT_TEXT: Final[int] = 0x7E + AP2_PLACE_OBJECT: Final[int] = 0x7F AP2_REMOVE_OBJECT: Final[int] = 0x80 AP2_START_SOUND: Final[int] = 0x81 AP2_DEFINE_MORPH_SHAPE: Final[int] = 0x82 @@ -210,82 +210,82 @@ class AP2Tag: @classmethod def tag_to_name(cls, tagid: int) -> str: resources: Dict[int, str] = { - cls.END: 'END', - cls.SHOW_FRAME: 'SHOW_FRAME', - cls.DEFINE_SHAPE: 'DEFINE_SHAPE', - cls.PLACE_OBJECT: 'PLACE_OBJECT', - cls.REMOVE_OBJECT: 'REMOVE_OBJECT', - cls.DEFINE_BITS: 'DEFINE_BITS', - cls.DEFINE_BUTTON: 'DEFINE_BUTTON', - cls.JPEG_TABLES: 'JPEG_TABLES', - cls.BACKGROUND_COLOR: 'BACKGROUND_COLOR', - cls.DEFINE_FONT: 'DEFINE_FONT', - cls.DEFINE_TEXT: 'DEFINE_TEXT', - cls.DO_ACTION: 'DO_ACTION', - cls.DEFINE_FONT_INFO: 'DEFINE_FONT_INFO', - cls.DEFINE_SOUND: 'DEFINE_SOUND', - cls.START_SOUND: 'START_SOUND', - cls.DEFINE_BUTTON_SOUND: 'DEFINE_BUTTON_SOUND', - cls.SOUND_STREAM_HEAD: 'SOUND_STREAM_HEAD', - cls.SOUND_STREAM_BLOCK: 'SOUND_STREAM_BLOCK', - cls.DEFINE_BITS_LOSSLESS: 'DEFINE_BITS_LOSSLESS', - cls.DEFINE_BITS_JPEG2: 'DEFINE_BITS_JPEG2', - cls.DEFINE_SHAPE2: 'DEFINE_SHAPE2', - cls.DEFINE_BUTTON_CXFORM: 'DEFINE_BUTTON_CXFORM', - cls.PROTECT: 'PROTECT', - cls.PLACE_OBJECT2: 'PLACE_OBJECT2', - cls.REMOVE_OBJECT2: 'REMOVE_OBJECT2', - cls.DEFINE_SHAPE3: 'DEFINE_SHAPE3', - cls.DEFINE_TEXT2: 'DEFINE_TEXT2', - cls.DEFINE_BUTTON2: 'DEFINE_BUTTON2', - cls.DEFINE_BITS_JPEG3: 'DEFINE_BITS_JPEG3', - cls.DEFINE_BITS_LOSSLESS2: 'DEFINE_BITS_LOSSLESS2', - cls.DEFINE_EDIT_TEXT: 'DEFINE_EDIT_TEXT', - cls.DEFINE_SPRITE: 'DEFINE_SPRITE', - cls.FRAME_LABEL: 'FRAME_LABEL', - cls.SOUND_STREAM_HEAD2: 'SOUND_STREAM_HEAD2', - cls.DEFINE_MORPH_SHAPE: 'DEFINE_MORPH_SHAPE', - cls.DEFINE_FONT2: 'DEFINE_FONT2', - cls.EXPORT_ASSETS: 'EXPORT_ASSETS', - cls.IMPORT_ASSETS: 'IMPORT_ASSETS', - cls.DO_INIT_ACTION: 'DO_INIT_ACTION', - cls.DEFINE_VIDEO_STREAM: 'DEFINE_VIDEO_STREAM', - cls.VIDEO_FRAME: 'VIDEO_FRAME', - cls.DEFINE_FONT_INFO2: 'DEFINE_FONT_INFO2', - cls.ENABLE_DEBUGGER2: 'ENABLE_DEBUGGER2', - cls.SCRIPT_LIMITS: 'SCRIPT_LIMITS', - cls.SET_TAB_INDEX: 'SET_TAB_INDEX', - cls.PLACE_OBJECT3: 'PLACE_OBJECT3', - cls.IMPORT_ASSETS2: 'IMPORT_ASSETS2', - cls.DEFINE_FONT3: 'DEFINE_FONT3', - cls.DEFINE_SCALING_GRID: 'DEFINE_SCALING_GRID', - cls.METADATA: 'METADATA', - cls.DEFINE_SHAPE4: 'DEFINE_SHAPE4', - cls.DEFINE_MORPH_SHAPE2: 'DEFINE_MORPH_SHAPE2', - cls.SCENE_LABEL: 'SCENE_LABEL', - cls.AFP_IMAGE: 'AFP_IMAGE', - cls.AFP_DEFINE_SOUND: 'AFP_DEFINE_SOUND', - cls.AFP_SOUND_STREAM_BLOCK: 'AFP_SOUND_STREAM_BLOCK', - cls.AFP_DEFINE_FONT: 'AFP_DEFINE_FONT', - cls.AFP_DEFINE_SHAPE: 'AFP_DEFINE_SHAPE', - cls.AEP_PLACE_OBJECT: 'AEP_PLACE_OBJECT', - cls.AP2_DEFINE_FONT: 'AP2_DEFINE_FONT', - cls.AP2_DEFINE_SPRITE: 'AP2_DEFINE_SPRITE', - cls.AP2_DO_ACTION: 'AP2_DO_ACTION', - cls.AP2_DEFINE_BUTTON: 'AP2_DEFINE_BUTTON', - cls.AP2_DEFINE_BUTTON_SOUND: 'AP2_DEFINE_BUTTON_SOUND', - cls.AP2_DEFINE_TEXT: 'AP2_DEFINE_TEXT', - cls.AP2_DEFINE_EDIT_TEXT: 'AP2_DEFINE_EDIT_TEXT', - cls.AP2_PLACE_OBJECT: 'AP2_PLACE_OBJECT', - cls.AP2_REMOVE_OBJECT: 'AP2_REMOVE_OBJECT', - cls.AP2_START_SOUND: 'AP2_START_SOUND', - cls.AP2_DEFINE_MORPH_SHAPE: 'AP2_DEFINE_MORPH_SHAPE', - cls.AP2_IMAGE: 'AP2_IMAGE', - cls.AP2_SHAPE: 'AP2_SHAPE', - cls.AP2_SOUND: 'AP2_SOUND', - cls.AP2_VIDEO: 'AP2_VIDEO', - cls.AP2_PLACE_CAMERA: 'AP2_PLACE_CAMERA', - cls.AP2_SCALING_GRID: 'AP2_SCALING_GRID', + cls.END: "END", + cls.SHOW_FRAME: "SHOW_FRAME", + cls.DEFINE_SHAPE: "DEFINE_SHAPE", + cls.PLACE_OBJECT: "PLACE_OBJECT", + cls.REMOVE_OBJECT: "REMOVE_OBJECT", + cls.DEFINE_BITS: "DEFINE_BITS", + cls.DEFINE_BUTTON: "DEFINE_BUTTON", + cls.JPEG_TABLES: "JPEG_TABLES", + cls.BACKGROUND_COLOR: "BACKGROUND_COLOR", + cls.DEFINE_FONT: "DEFINE_FONT", + cls.DEFINE_TEXT: "DEFINE_TEXT", + cls.DO_ACTION: "DO_ACTION", + cls.DEFINE_FONT_INFO: "DEFINE_FONT_INFO", + cls.DEFINE_SOUND: "DEFINE_SOUND", + cls.START_SOUND: "START_SOUND", + cls.DEFINE_BUTTON_SOUND: "DEFINE_BUTTON_SOUND", + cls.SOUND_STREAM_HEAD: "SOUND_STREAM_HEAD", + cls.SOUND_STREAM_BLOCK: "SOUND_STREAM_BLOCK", + cls.DEFINE_BITS_LOSSLESS: "DEFINE_BITS_LOSSLESS", + cls.DEFINE_BITS_JPEG2: "DEFINE_BITS_JPEG2", + cls.DEFINE_SHAPE2: "DEFINE_SHAPE2", + cls.DEFINE_BUTTON_CXFORM: "DEFINE_BUTTON_CXFORM", + cls.PROTECT: "PROTECT", + cls.PLACE_OBJECT2: "PLACE_OBJECT2", + cls.REMOVE_OBJECT2: "REMOVE_OBJECT2", + cls.DEFINE_SHAPE3: "DEFINE_SHAPE3", + cls.DEFINE_TEXT2: "DEFINE_TEXT2", + cls.DEFINE_BUTTON2: "DEFINE_BUTTON2", + cls.DEFINE_BITS_JPEG3: "DEFINE_BITS_JPEG3", + cls.DEFINE_BITS_LOSSLESS2: "DEFINE_BITS_LOSSLESS2", + cls.DEFINE_EDIT_TEXT: "DEFINE_EDIT_TEXT", + cls.DEFINE_SPRITE: "DEFINE_SPRITE", + cls.FRAME_LABEL: "FRAME_LABEL", + cls.SOUND_STREAM_HEAD2: "SOUND_STREAM_HEAD2", + cls.DEFINE_MORPH_SHAPE: "DEFINE_MORPH_SHAPE", + cls.DEFINE_FONT2: "DEFINE_FONT2", + cls.EXPORT_ASSETS: "EXPORT_ASSETS", + cls.IMPORT_ASSETS: "IMPORT_ASSETS", + cls.DO_INIT_ACTION: "DO_INIT_ACTION", + cls.DEFINE_VIDEO_STREAM: "DEFINE_VIDEO_STREAM", + cls.VIDEO_FRAME: "VIDEO_FRAME", + cls.DEFINE_FONT_INFO2: "DEFINE_FONT_INFO2", + cls.ENABLE_DEBUGGER2: "ENABLE_DEBUGGER2", + cls.SCRIPT_LIMITS: "SCRIPT_LIMITS", + cls.SET_TAB_INDEX: "SET_TAB_INDEX", + cls.PLACE_OBJECT3: "PLACE_OBJECT3", + cls.IMPORT_ASSETS2: "IMPORT_ASSETS2", + cls.DEFINE_FONT3: "DEFINE_FONT3", + cls.DEFINE_SCALING_GRID: "DEFINE_SCALING_GRID", + cls.METADATA: "METADATA", + cls.DEFINE_SHAPE4: "DEFINE_SHAPE4", + cls.DEFINE_MORPH_SHAPE2: "DEFINE_MORPH_SHAPE2", + cls.SCENE_LABEL: "SCENE_LABEL", + cls.AFP_IMAGE: "AFP_IMAGE", + cls.AFP_DEFINE_SOUND: "AFP_DEFINE_SOUND", + cls.AFP_SOUND_STREAM_BLOCK: "AFP_SOUND_STREAM_BLOCK", + cls.AFP_DEFINE_FONT: "AFP_DEFINE_FONT", + cls.AFP_DEFINE_SHAPE: "AFP_DEFINE_SHAPE", + cls.AEP_PLACE_OBJECT: "AEP_PLACE_OBJECT", + cls.AP2_DEFINE_FONT: "AP2_DEFINE_FONT", + cls.AP2_DEFINE_SPRITE: "AP2_DEFINE_SPRITE", + cls.AP2_DO_ACTION: "AP2_DO_ACTION", + cls.AP2_DEFINE_BUTTON: "AP2_DEFINE_BUTTON", + cls.AP2_DEFINE_BUTTON_SOUND: "AP2_DEFINE_BUTTON_SOUND", + cls.AP2_DEFINE_TEXT: "AP2_DEFINE_TEXT", + cls.AP2_DEFINE_EDIT_TEXT: "AP2_DEFINE_EDIT_TEXT", + cls.AP2_PLACE_OBJECT: "AP2_PLACE_OBJECT", + cls.AP2_REMOVE_OBJECT: "AP2_REMOVE_OBJECT", + cls.AP2_START_SOUND: "AP2_START_SOUND", + cls.AP2_DEFINE_MORPH_SHAPE: "AP2_DEFINE_MORPH_SHAPE", + cls.AP2_IMAGE: "AP2_IMAGE", + cls.AP2_SHAPE: "AP2_SHAPE", + cls.AP2_SOUND: "AP2_SOUND", + cls.AP2_VIDEO: "AP2_VIDEO", + cls.AP2_PLACE_CAMERA: "AP2_PLACE_CAMERA", + cls.AP2_SCALING_GRID: "AP2_SCALING_GRID", } return resources.get(tagid, f"") @@ -557,80 +557,80 @@ class AP2Action: @classmethod def action_to_name(cls, actionid: int) -> str: resources: Dict[int, str] = { - cls.END: 'END', - cls.NEXT_FRAME: 'NEXT_FRAME', - cls.PREVIOUS_FRAME: 'PREVIOUS_FRAME', - cls.PLAY: 'PLAY', - cls.STOP: 'STOP', - cls.STOP_SOUND: 'STOP_SOUND', - cls.SUBTRACT: 'SUBTRACT', - cls.MULTIPLY: 'MULTIPLY', - cls.DIVIDE: 'DIVIDE', - cls.NOT: 'NOT', - cls.POP: 'POP', - cls.GET_VARIABLE: 'GET_VARIABLE', - cls.SET_VARIABLE: 'SET_VARIABLE', - cls.GET_PROPERTY: 'GET_PROPERTY', - cls.SET_PROPERTY: 'SET_PROPERTY', - cls.CLONE_SPRITE: 'CLONE_SPRITE', - cls.REMOVE_SPRITE: 'REMOVE_SPRITE', - cls.TRACE: 'TRACE', - cls.START_DRAG: 'START_DRAG', - cls.END_DRAG: 'END_DRAG', - cls.THROW: 'THROW', - cls.CAST_OP: 'CAST_OP', - cls.IMPLEMENTS_OP: 'IMPLEMENTS_OP', - cls.GET_TIME: 'GET_TIME', - cls.DELETE: 'DELETE', - cls.DELETE2: 'DELETE2', - cls.DEFINE_LOCAL: 'DEFINE_LOCAL', - cls.CALL_FUNCTION: 'CALL_FUNCTION', - cls.RETURN: 'RETURN', - cls.MODULO: 'MODULO', - cls.NEW_OBJECT: 'NEW_OBJECT', - cls.DEFINE_LOCAL2: 'DEFINE_LOCAL2', - cls.INIT_ARRAY: 'INIT_ARRAY', - cls.INIT_OBJECT: 'INIT_OBJECT', - cls.TYPEOF: 'TYPEOF', - cls.TARGET_PATH: 'TARGET_PATH', - cls.ADD2: 'ADD2', - cls.LESS2: 'LESS2', - cls.EQUALS2: 'EQUALS2', - cls.TO_NUMBER: 'TO_NUMBER', - cls.TO_STRING: 'TO_STRING', - cls.PUSH_DUPLICATE: 'PUSH_DUPLICATE', - cls.STACK_SWAP: 'STACK_SWAP', - cls.GET_MEMBER: 'GET_MEMBER', - cls.SET_MEMBER: 'SET_MEMBER', - cls.INCREMENT: 'INCREMENT', - cls.DECREMENT: 'DECREMENT', - cls.CALL_METHOD: 'CALL_METHOD', - cls.NEW_METHOD: 'NEW_METHOD', - cls.INSTANCEOF: 'INSTANCEOF', - cls.ENUMERATE2: 'ENUMERATE2', - cls.BIT_AND: 'BIT_AND', - cls.BIT_OR: 'BIT_OR', - cls.BIT_XOR: 'BIT_XOR', - cls.BIT_L_SHIFT: 'BIT_L_SHIFT', - cls.BIT_R_SHIFT: 'BIT_R_SHIFT', - cls.BIT_U_R_SHIFT: 'BIT_U_R_SHIFT', - cls.STRICT_EQUALS: 'STRICT_EQUALS', - cls.GREATER: 'GREATER', - cls.EXTENDS: 'EXTENDS', - cls.STORE_REGISTER: 'STORE_REGISTER', - cls.DEFINE_FUNCTION2: 'DEFINE_FUNCTION2', - cls.WITH: 'WITH', - cls.PUSH: 'PUSH', - cls.JUMP: 'JUMP', - cls.GET_URL2: 'GET_URL2', - cls.IF: 'IF', - cls.GOTO_FRAME2: 'GOTO_FRAME2', - cls.GET_TARGET: 'GET_TARGET', - cls.IF2: 'IF2', - cls.STORE_REGISTER2: 'STORE_REGISTER2', - cls.INIT_REGISTER: 'INIT_REGISTER', - cls.ADD_NUM_REGISTER: 'ADD_NUM_REGISTER', - cls.ADD_NUM_VARIABLE: 'ADD_NUM_VARIABLE', + cls.END: "END", + cls.NEXT_FRAME: "NEXT_FRAME", + cls.PREVIOUS_FRAME: "PREVIOUS_FRAME", + cls.PLAY: "PLAY", + cls.STOP: "STOP", + cls.STOP_SOUND: "STOP_SOUND", + cls.SUBTRACT: "SUBTRACT", + cls.MULTIPLY: "MULTIPLY", + cls.DIVIDE: "DIVIDE", + cls.NOT: "NOT", + cls.POP: "POP", + cls.GET_VARIABLE: "GET_VARIABLE", + cls.SET_VARIABLE: "SET_VARIABLE", + cls.GET_PROPERTY: "GET_PROPERTY", + cls.SET_PROPERTY: "SET_PROPERTY", + cls.CLONE_SPRITE: "CLONE_SPRITE", + cls.REMOVE_SPRITE: "REMOVE_SPRITE", + cls.TRACE: "TRACE", + cls.START_DRAG: "START_DRAG", + cls.END_DRAG: "END_DRAG", + cls.THROW: "THROW", + cls.CAST_OP: "CAST_OP", + cls.IMPLEMENTS_OP: "IMPLEMENTS_OP", + cls.GET_TIME: "GET_TIME", + cls.DELETE: "DELETE", + cls.DELETE2: "DELETE2", + cls.DEFINE_LOCAL: "DEFINE_LOCAL", + cls.CALL_FUNCTION: "CALL_FUNCTION", + cls.RETURN: "RETURN", + cls.MODULO: "MODULO", + cls.NEW_OBJECT: "NEW_OBJECT", + cls.DEFINE_LOCAL2: "DEFINE_LOCAL2", + cls.INIT_ARRAY: "INIT_ARRAY", + cls.INIT_OBJECT: "INIT_OBJECT", + cls.TYPEOF: "TYPEOF", + cls.TARGET_PATH: "TARGET_PATH", + cls.ADD2: "ADD2", + cls.LESS2: "LESS2", + cls.EQUALS2: "EQUALS2", + cls.TO_NUMBER: "TO_NUMBER", + cls.TO_STRING: "TO_STRING", + cls.PUSH_DUPLICATE: "PUSH_DUPLICATE", + cls.STACK_SWAP: "STACK_SWAP", + cls.GET_MEMBER: "GET_MEMBER", + cls.SET_MEMBER: "SET_MEMBER", + cls.INCREMENT: "INCREMENT", + cls.DECREMENT: "DECREMENT", + cls.CALL_METHOD: "CALL_METHOD", + cls.NEW_METHOD: "NEW_METHOD", + cls.INSTANCEOF: "INSTANCEOF", + cls.ENUMERATE2: "ENUMERATE2", + cls.BIT_AND: "BIT_AND", + cls.BIT_OR: "BIT_OR", + cls.BIT_XOR: "BIT_XOR", + cls.BIT_L_SHIFT: "BIT_L_SHIFT", + cls.BIT_R_SHIFT: "BIT_R_SHIFT", + cls.BIT_U_R_SHIFT: "BIT_U_R_SHIFT", + cls.STRICT_EQUALS: "STRICT_EQUALS", + cls.GREATER: "GREATER", + cls.EXTENDS: "EXTENDS", + cls.STORE_REGISTER: "STORE_REGISTER", + cls.DEFINE_FUNCTION2: "DEFINE_FUNCTION2", + cls.WITH: "WITH", + cls.PUSH: "PUSH", + cls.JUMP: "JUMP", + cls.GET_URL2: "GET_URL2", + cls.IF: "IF", + cls.GOTO_FRAME2: "GOTO_FRAME2", + cls.GET_TARGET: "GET_TARGET", + cls.IF2: "IF2", + cls.STORE_REGISTER2: "STORE_REGISTER2", + cls.INIT_REGISTER: "INIT_REGISTER", + cls.ADD_NUM_REGISTER: "ADD_NUM_REGISTER", + cls.ADD_NUM_VARIABLE: "ADD_NUM_VARIABLE", } return resources.get(actionid, f"") @@ -706,8 +706,8 @@ class AP2Action: def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { - 'offset': self.offset, - 'action': AP2Action.action_to_name(self.opcode), + "offset": self.offset, + "action": AP2Action.action_to_name(self.opcode), } def __repr__(self) -> str: @@ -715,7 +715,9 @@ class AP2Action: class DefineFunction2Action(AP2Action): - def __init__(self, offset: int, name: Optional[str], flags: int, body: "ByteCode") -> None: + def __init__( + self, offset: int, name: Optional[str], flags: int, body: "ByteCode" + ) -> None: super().__init__(offset, AP2Action.DEFINE_FUNCTION2) self.name = name self.flags = flags @@ -724,19 +726,21 @@ class DefineFunction2Action(AP2Action): def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { **super().as_dict(*args, **kwargs), - 'name': self.name, - 'flags': self.flags, - 'body': self.body.as_dict(*args, **kwargs), + "name": self.name, + "flags": self.flags, + "body": self.body.as_dict(*args, **kwargs), } def __repr__(self) -> str: bytecode = [f" {line}" for line in str(self.body).split(os.linesep)] action_name = AP2Action.action_to_name(self.opcode) - return os.linesep.join([ - f"{self.offset}: {action_name}, Name: {self.name or ''}, Flags: {hex(self.flags)}", - *bytecode, - f"END_{action_name}", - ]) + return os.linesep.join( + [ + f"{self.offset}: {action_name}, Name: {self.name or ''}, Flags: {hex(self.flags)}", + *bytecode, + f"END_{action_name}", + ] + ) class PushAction(AP2Action): @@ -749,17 +753,19 @@ class PushAction(AP2Action): **super().as_dict(*args, **kwargs), # TODO: We need to do better than this when exporting objects, # we should preserve their type. - 'objects': [repr(o) for o in self.objects], + "objects": [repr(o) for o in self.objects], } def __repr__(self) -> str: objects = [f" {repr(obj)}" for obj in self.objects] action_name = AP2Action.action_to_name(self.opcode) - return os.linesep.join([ - f"{self.offset}: {action_name}", - *objects, - f"END_{action_name}", - ]) + return os.linesep.join( + [ + f"{self.offset}: {action_name}", + *objects, + f"END_{action_name}", + ] + ) class InitRegisterAction(AP2Action): @@ -770,21 +776,25 @@ class InitRegisterAction(AP2Action): def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { **super().as_dict(*args, **kwargs), - 'registers': [r.no for r in self.registers], + "registers": [r.no for r in self.registers], } def __repr__(self) -> str: registers = [f" {reg}" for reg in self.registers] action_name = AP2Action.action_to_name(self.opcode) - return os.linesep.join([ - f"{self.offset}: {action_name}", - *registers, - f"END_{action_name}", - ]) + return os.linesep.join( + [ + f"{self.offset}: {action_name}", + *registers, + f"END_{action_name}", + ] + ) class StoreRegisterAction(AP2Action): - def __init__(self, offset: int, registers: List[Register], preserve_stack: bool) -> None: + def __init__( + self, offset: int, registers: List[Register], preserve_stack: bool + ) -> None: super().__init__(offset, AP2Action.STORE_REGISTER) self.registers = registers self.preserve_stack = preserve_stack @@ -792,17 +802,19 @@ class StoreRegisterAction(AP2Action): def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { **super().as_dict(*args, **kwargs), - 'registers': [r.no for r in self.registers], + "registers": [r.no for r in self.registers], } def __repr__(self) -> str: registers = [f" {reg}" for reg in self.registers] action_name = AP2Action.action_to_name(self.opcode) - return os.linesep.join([ - f"{self.offset}: {action_name}, Preserve Stack: {self.preserve_stack}", - *registers, - f"END_{action_name}", - ]) + return os.linesep.join( + [ + f"{self.offset}: {action_name}, Preserve Stack: {self.preserve_stack}", + *registers, + f"END_{action_name}", + ] + ) class IfAction(AP2Action): @@ -848,8 +860,8 @@ class IfAction(AP2Action): def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { **super().as_dict(*args, **kwargs), - 'comparison': IfAction.comparison_to_str(self.comparison), - 'jump_if_true_offset': self.jump_if_true_offset, + "comparison": IfAction.comparison_to_str(self.comparison), + "jump_if_true_offset": self.jump_if_true_offset, } def __repr__(self) -> str: @@ -864,7 +876,7 @@ class JumpAction(AP2Action): def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { **super().as_dict(*args, **kwargs), - 'jump_offset': self.jump_offset, + "jump_offset": self.jump_offset, } def __repr__(self) -> str: @@ -881,7 +893,7 @@ class WithAction(AP2Action): **super().as_dict(*args, **kwargs), # TODO: We need to do better than this, so I guess it comes down to having # a better idea how WITH works. - 'unknown': str(self.unknown), + "unknown": str(self.unknown), } def __repr__(self) -> str: @@ -897,8 +909,8 @@ class GotoFrame2Action(AP2Action): def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { **super().as_dict(*args, **kwargs), - 'additiona_frames': self.additional_frames, - 'stop': self.stop, + "additiona_frames": self.additional_frames, + "stop": self.stop, } def __repr__(self) -> str: @@ -913,7 +925,7 @@ class AddNumVariableAction(AP2Action): def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { **super().as_dict(*args, **kwargs), - 'amount_to_add': self.amount_to_add, + "amount_to_add": self.amount_to_add, } def __repr__(self) -> str: @@ -929,8 +941,8 @@ class AddNumRegisterAction(AP2Action): def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { **super().as_dict(*args, **kwargs), - 'register': self.register.no, - 'amount_to_add': self.amount_to_add, + "register": self.register.no, + "amount_to_add": self.amount_to_add, } def __repr__(self) -> str: @@ -945,7 +957,7 @@ class GetURL2Action(AP2Action): def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { **super().as_dict(*args, **kwargs), - 'action': self.action, + "action": self.action, } def __repr__(self) -> str: @@ -960,7 +972,7 @@ class StartDragAction(AP2Action): def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { **super().as_dict(*args, **kwargs), - 'constrain': self.constrain, + "constrain": self.constrain, } def __repr__(self) -> str: diff --git a/bemani/format/afp/types/expression.py b/bemani/format/afp/types/expression.py index fcc71f2..0bbb132 100644 --- a/bemani/format/afp/types/expression.py +++ b/bemani/format/afp/types/expression.py @@ -11,7 +11,9 @@ class Expression: # Any thing that can be evaluated for a result, such as a variable # reference, function call, or mathematical operation. def render(self, parent_prefix: str, nested: bool = False) -> str: - raise NotImplementedError(f"{self.__class__.__name__} does not implement render()!") + raise NotImplementedError( + f"{self.__class__.__name__} does not implement render()!" + ) class GenericObject(Expression): @@ -28,13 +30,13 @@ class GenericObject(Expression): return self.name -NULL = GenericObject('NULL') -UNDEFINED = GenericObject('UNDEFINED') -THIS = GenericObject('THIS') -ROOT = GenericObject('ROOT') -PARENT = GenericObject('PARENT') -CLIP = GenericObject('CLIP') -GLOBAL = GenericObject('GLOBAL') +NULL = GenericObject("NULL") +UNDEFINED = GenericObject("UNDEFINED") +THIS = GenericObject("THIS") +ROOT = GenericObject("ROOT") +PARENT = GenericObject("PARENT") +CLIP = GenericObject("CLIP") +GLOBAL = GenericObject("GLOBAL") class Register(Expression): @@ -53,1930 +55,1907 @@ class StringConstant(Expression): # Seems to be properties on every object. These also match the original # SWF properties up to 0x115. GET_PROPERTY and SET_PROPERTY use these # values to determine what to look up on an object. - (0x100, '_x'), - (0x101, '_y'), - (0x102, '_xscale'), - (0x103, '_yscale'), - (0x104, '_currentframe'), - (0x105, '_totalframes'), - (0x106, '_alpha'), - (0x107, '_visible'), - (0x108, '_width'), - (0x109, '_height'), - (0x10a, '_rotation'), - (0x10b, '_target'), - (0x10c, '_framesloaded'), - (0x10d, '_name'), - (0x10e, '_droptarget'), - (0x10f, '_url'), - (0x110, '_highquality'), - (0x111, '_focusrect'), - (0x112, '_soundbuftime'), - (0x113, '_quality'), - (0x114, '_xmouse'), - (0x115, '_ymouse'), - (0x116, '_z'), - + (0x100, "_x"), + (0x101, "_y"), + (0x102, "_xscale"), + (0x103, "_yscale"), + (0x104, "_currentframe"), + (0x105, "_totalframes"), + (0x106, "_alpha"), + (0x107, "_visible"), + (0x108, "_width"), + (0x109, "_height"), + (0x10A, "_rotation"), + (0x10B, "_target"), + (0x10C, "_framesloaded"), + (0x10D, "_name"), + (0x10E, "_droptarget"), + (0x10F, "_url"), + (0x110, "_highquality"), + (0x111, "_focusrect"), + (0x112, "_soundbuftime"), + (0x113, "_quality"), + (0x114, "_xmouse"), + (0x115, "_ymouse"), + (0x116, "_z"), # Global properties on every object. - (0x120, 'this'), - (0x121, '_root'), - (0x122, '_parent'), - (0x123, '_global'), - (0x124, 'arguments'), - + (0x120, "this"), + (0x121, "_root"), + (0x122, "_parent"), + (0x123, "_global"), + (0x124, "arguments"), # Object properties? - (0x140, 'blendMode'), - (0x141, 'enabled'), - (0x142, 'hitArea'), - (0x143, '_lockroot'), - (0x144, '$version'), - (0x145, 'numChildren'), - (0x146, 'transform'), - (0x147, 'graphics'), - (0x148, 'loaderInfo'), - (0x149, 'mask'), - (0x14a, 'upState'), - (0x14b, 'overState'), - (0x14c, 'downState'), - (0x14d, 'hitTestState'), - (0x14e, 'doubleClickEnabled'), - (0x14f, 'cacheAsBitmap'), - (0x150, 'scrollRect'), - (0x151, 'opaqueBackground'), - (0x152, 'tabChildren'), - (0x153, 'tabEnabled'), - (0x154, 'tabIndex'), - (0x155, 'mouseEnabled'), - (0x156, 'mouseChildren'), - (0x157, 'buttonMode'), - (0x158, 'useHandCursor'), - + (0x140, "blendMode"), + (0x141, "enabled"), + (0x142, "hitArea"), + (0x143, "_lockroot"), + (0x144, "$version"), + (0x145, "numChildren"), + (0x146, "transform"), + (0x147, "graphics"), + (0x148, "loaderInfo"), + (0x149, "mask"), + (0x14A, "upState"), + (0x14B, "overState"), + (0x14C, "downState"), + (0x14D, "hitTestState"), + (0x14E, "doubleClickEnabled"), + (0x14F, "cacheAsBitmap"), + (0x150, "scrollRect"), + (0x151, "opaqueBackground"), + (0x152, "tabChildren"), + (0x153, "tabEnabled"), + (0x154, "tabIndex"), + (0x155, "mouseEnabled"), + (0x156, "mouseChildren"), + (0x157, "buttonMode"), + (0x158, "useHandCursor"), # Text input properties. - (0x160, 'textWidth'), - (0x161, 'textHeight'), - (0x162, 'text'), - (0x163, 'autoSize'), - (0x164, 'textColor'), - (0x165, 'selectable'), - (0x166, 'multiline'), - (0x167, 'wordWrap'), - (0x168, 'border'), - (0x169, 'borderColor'), - (0x16a, 'background'), - (0x16b, 'backgroundColor'), - (0x16c, 'embedFonts'), - (0x16d, 'defaultTextFormat'), - (0x16e, 'htmlText'), - (0x16f, 'mouseWheelEnabled'), - (0x170, 'maxChars'), - (0x171, 'sharpness'), - (0x172, 'thickness'), - (0x173, 'antiAliasType'), - (0x174, 'gridFitType'), - (0x175, 'maxScrollH'), - (0x176, 'maxScrollV'), - (0x177, 'restrict'), - (0x178, 'numLines'), - + (0x160, "textWidth"), + (0x161, "textHeight"), + (0x162, "text"), + (0x163, "autoSize"), + (0x164, "textColor"), + (0x165, "selectable"), + (0x166, "multiline"), + (0x167, "wordWrap"), + (0x168, "border"), + (0x169, "borderColor"), + (0x16A, "background"), + (0x16B, "backgroundColor"), + (0x16C, "embedFonts"), + (0x16D, "defaultTextFormat"), + (0x16E, "htmlText"), + (0x16F, "mouseWheelEnabled"), + (0x170, "maxChars"), + (0x171, "sharpness"), + (0x172, "thickness"), + (0x173, "antiAliasType"), + (0x174, "gridFitType"), + (0x175, "maxScrollH"), + (0x176, "maxScrollV"), + (0x177, "restrict"), + (0x178, "numLines"), # Color properties? - (0x180, 'ra'), - (0x181, 'rb'), - (0x182, 'ga'), - (0x183, 'gb'), - (0x184, 'ba'), - (0x185, 'bb'), - (0x186, 'aa'), - (0x187, 'ab'), - + (0x180, "ra"), + (0x181, "rb"), + (0x182, "ga"), + (0x183, "gb"), + (0x184, "ba"), + (0x185, "bb"), + (0x186, "aa"), + (0x187, "ab"), # Text properties? - (0x1a0, 'font'), - (0x1a1, 'size'), - (0x1a2, 'color'), - (0x1a3, 'bold'), - (0x1a4, 'italic'), - (0x1a5, 'underline'), - (0x1a6, 'url'), - (0x1a7, 'target'), - (0x1a8, 'align'), - (0x1a9, 'leftMargin'), - (0x1aa, 'rightMargin'), - (0x1ab, 'indent'), - (0x1ac, 'leading'), - (0x1ad, 'letterSpacing'), - + (0x1A0, "font"), + (0x1A1, "size"), + (0x1A2, "color"), + (0x1A3, "bold"), + (0x1A4, "italic"), + (0x1A5, "underline"), + (0x1A6, "url"), + (0x1A7, "target"), + (0x1A8, "align"), + (0x1A9, "leftMargin"), + (0x1AA, "rightMargin"), + (0x1AB, "indent"), + (0x1AC, "leading"), + (0x1AD, "letterSpacing"), # Who the fuck knows... - (0x1c0, 'a'), - (0x1c1, 'b'), - (0x1c2, 'c'), - (0x1c3, 'd'), - (0x1c4, 'e'), - (0x1c5, 'f'), - (0x1c6, 'g'), - (0x1c7, 'h'), - (0x1c8, 'i'), - (0x1c9, 'j'), - (0x1ca, 'k'), - (0x1cb, 'l'), - (0x1cc, 'm'), - (0x1cd, 'n'), - (0x1ce, 'o'), - (0x1cf, 'p'), - (0x1d0, 'q'), - (0x1d1, 'r'), - (0x1d2, 's'), - (0x1d3, 't'), - (0x1d4, 'u'), - (0x1d5, 'v'), - (0x1d6, 'w'), - (0x1d7, 'x'), - (0x1d8, 'y'), - (0x1d9, 'z'), - (0x1da, 'tx'), - (0x1db, 'ty'), - (0x1dc, 'length'), - (0x1dd, 'ignoreWhite'), - (0x1de, 'loaded'), - (0x1df, 'childNodes'), - (0x1e0, 'firstChild'), - (0x1e1, 'nodeValue'), - (0x1e2, 'nextSibling'), - (0x1e3, 'nodeName'), - (0x1e4, 'nodeType'), - (0x1e5, 'attributes'), - (0x1e6, '__count'), - (0x1e7, '__type'), - (0x1e8, 'width'), - (0x1e9, 'height'), - (0x1ea, 'useCodepage'), - (0x1eb, 'duration'), - (0x1ec, 'position'), - (0x1ed, 'matrixType'), - (0x1ee, 'matrix'), - (0x1ef, 'prototype'), - (0x1f0, '__proto__'), - (0x1f1, 'xMin'), - (0x1f2, 'xMax'), - (0x1f3, 'yMin'), - (0x1f4, 'yMax'), - (0x1f5, 'lastChild'), - (0x1f6, 'parentNode'), - (0x1f7, 'previousSibling'), - (0x1f8, 'callee'), - (0x1f9, 'caller'), - (0x1fa, 'colorTransform'), - (0x1fb, 'concatenatedColorTransform'), - (0x1fc, 'concatenatedMatrix'), - (0x1fd, 'pixelBounds'), - (0x1fe, 'matrix3D'), - (0x1ff, 'perspectiveProjection'), - + (0x1C0, "a"), + (0x1C1, "b"), + (0x1C2, "c"), + (0x1C3, "d"), + (0x1C4, "e"), + (0x1C5, "f"), + (0x1C6, "g"), + (0x1C7, "h"), + (0x1C8, "i"), + (0x1C9, "j"), + (0x1CA, "k"), + (0x1CB, "l"), + (0x1CC, "m"), + (0x1CD, "n"), + (0x1CE, "o"), + (0x1CF, "p"), + (0x1D0, "q"), + (0x1D1, "r"), + (0x1D2, "s"), + (0x1D3, "t"), + (0x1D4, "u"), + (0x1D5, "v"), + (0x1D6, "w"), + (0x1D7, "x"), + (0x1D8, "y"), + (0x1D9, "z"), + (0x1DA, "tx"), + (0x1DB, "ty"), + (0x1DC, "length"), + (0x1DD, "ignoreWhite"), + (0x1DE, "loaded"), + (0x1DF, "childNodes"), + (0x1E0, "firstChild"), + (0x1E1, "nodeValue"), + (0x1E2, "nextSibling"), + (0x1E3, "nodeName"), + (0x1E4, "nodeType"), + (0x1E5, "attributes"), + (0x1E6, "__count"), + (0x1E7, "__type"), + (0x1E8, "width"), + (0x1E9, "height"), + (0x1EA, "useCodepage"), + (0x1EB, "duration"), + (0x1EC, "position"), + (0x1ED, "matrixType"), + (0x1EE, "matrix"), + (0x1EF, "prototype"), + (0x1F0, "__proto__"), + (0x1F1, "xMin"), + (0x1F2, "xMax"), + (0x1F3, "yMin"), + (0x1F4, "yMax"), + (0x1F5, "lastChild"), + (0x1F6, "parentNode"), + (0x1F7, "previousSibling"), + (0x1F8, "callee"), + (0x1F9, "caller"), + (0x1FA, "colorTransform"), + (0x1FB, "concatenatedColorTransform"), + (0x1FC, "concatenatedMatrix"), + (0x1FD, "pixelBounds"), + (0x1FE, "matrix3D"), + (0x1FF, "perspectiveProjection"), # Commands and object references? - (0x200, 'FSCommand:fullscreen'), - (0x201, 'FSCommand:showmenu'), - (0x202, 'FSCommand:allowscale'), - (0x203, 'FSCommand:quit'), - (0x204, 'NaN'), - (0x205, 'Infinity'), - (0x206, 'number'), - (0x207, 'boolean'), - (0x208, 'string'), - (0x209, 'object'), - (0x20a, 'movieclip'), - (0x20b, 'null'), - (0x20c, 'undefined'), - (0x20d, 'function'), - (0x20e, 'normal'), - (0x20f, 'layer'), - (0x210, 'darken'), - (0x211, 'multiply'), - (0x212, 'lighten'), - (0x213, 'screen'), - (0x214, 'overlay'), - (0x215, 'hardlight'), - (0x216, 'subtract'), - (0x217, 'difference'), - (0x218, 'invert'), - (0x219, 'alpha'), - (0x21a, 'erase'), - (0x21b, '/'), - (0x21c, '..'), - (0x21d, 'linear'), - (0x21e, 'radial'), - (0x21f, 'none'), - (0x220, 'square'), - (0x221, 'miter'), - (0x222, 'bevel'), - (0x223, 'left'), - (0x224, 'right'), - (0x225, 'center'), - (0x226, 'box'), - (0x227, 'reflect'), - (0x228, 'repeat'), - (0x229, 'RGB'), - (0x22a, 'linearRGB'), - (0x22b, 'justify'), - (0x22c, 'shader'), - (0x22d, 'vertical'), - (0x22e, 'horizontal'), - (0x22f, 'pad'), - (0x230, 'evenOdd'), - (0x231, 'nonZero'), - (0x232, 'negative'), - (0x233, 'positive'), - (0x234, 'xml'), - (0x235, 'B'), - (0x236, 'BL'), - (0x237, 'BR'), - (0x238, 'L'), - (0x239, 'R'), - (0x23a, 'T'), - (0x23b, 'TL'), - (0x23c, 'TR'), - (0x23d, 'exactFit'), - (0x23e, 'noBorder'), - (0x23f, 'noScale'), - (0x240, 'showAll'), - (0x241, 'easeInSine'), - (0x242, 'easeOutSine'), - (0x243, 'easeInOutSine'), - (0x244, 'easeOutInSine'), - (0x245, 'easeInQuad'), - (0x246, 'easeOutQuad'), - (0x247, 'easeInOutQuad'), - (0x248, 'easeOutInQuad'), - (0x249, 'easeInFlash'), - (0x24a, 'easeOutFlash'), - (0x24b, 'element'), - (0x24c, 'dynamic'), - (0x24d, 'binary'), - (0x24e, 'variables'), - (0x24f, 'LB'), - (0x250, 'RB'), - (0x251, 'LT'), - (0x252, 'RT'), - (0x253, ''), - (0x254, 'arrow'), - (0x255, 'auto'), - (0x256, 'button'), - (0x257, 'hand'), - (0x258, 'ibeam'), - (0x259, 'advanced'), - (0x25a, 'pixel'), - (0x25b, 'subpixel'), - (0x25c, 'full'), - (0x25d, 'inner'), - (0x25e, 'outer'), - (0x25f, 'easeInBack'), - (0x260, 'easeOutBack'), - (0x261, 'easeInOutBack'), - (0x262, 'easeOutInBack'), - (0x263, 'registerClassConstructor'), - (0x264, 'setter'), - (0x265, 'getter'), - (0x266, '???'), - (0x267, 'aep_dummy'), - (0x268, 'kind'), - (0x269, '_kind'), - (0x26a, 'org'), - (0x26b, 'flashdevelop'), - (0x26c, 'utils'), - (0x26d, 'FlashConnect'), - (0x26e, 'path'), - (0x26f, 'if'), - (0x270, 'notif'), - (0x271, 'not'), - (0x272, 'A'), - (0x273, 'dmy0273'), - (0x274, 'C'), - (0x275, 'D'), - (0x276, 'dmy0276'), - (0x277, 'F'), - (0x278, 'G'), - (0x279, 'H'), - (0x27a, 'I'), - (0x27b, 'J'), - (0x27c, 'K'), - (0x27d, 'dmy027d'), - (0x27e, 'M'), - (0x27f, 'N'), - (0x280, 'O'), - (0x281, 'P'), - (0x282, 'Q'), - (0x283, 'dmy0283'), - (0x284, 'S'), - (0x285, 'dmy0285'), - (0x286, 'U'), - (0x287, 'V'), - (0x288, 'W'), - (0x289, 'X'), - (0x28a, 'Y'), - (0x28b, 'Z'), - (0x28c, 'fullscreen'), - (0x28d, 'showmenu'), - (0x28e, 'allowscale'), - (0x28f, 'quit'), - (0x290, 'true'), - (0x291, 'false'), - (0x292, 'clamp'), - (0x293, 'ignore'), - (0x294, 'wrap'), - (0x295, 'unknown'), - (0x296, 'bigEndian'), - (0x297, 'littleEndian'), - (0x298, 'fragment'), - (0x299, 'vertex'), - (0x29a, 'bgra'), - (0x29b, 'bgraPacked4444'), - (0x29c, 'bgrPacked565'), - (0x29d, 'compressed'), - (0x29e, 'compressedAlpha'), - (0x29f, 'bytes4'), - (0x2a0, 'float1'), - (0x2a1, 'float2'), - (0x2a2, 'float3'), - (0x2a3, 'float4'), - (0x2a4, 'super'), - (0x2a5, 'axisAngle'), - (0x2a6, 'eulerAngles'), - (0x2a7, 'quaternion'), - (0x2a8, 'orientationStyle'), - + (0x200, "FSCommand:fullscreen"), + (0x201, "FSCommand:showmenu"), + (0x202, "FSCommand:allowscale"), + (0x203, "FSCommand:quit"), + (0x204, "NaN"), + (0x205, "Infinity"), + (0x206, "number"), + (0x207, "boolean"), + (0x208, "string"), + (0x209, "object"), + (0x20A, "movieclip"), + (0x20B, "null"), + (0x20C, "undefined"), + (0x20D, "function"), + (0x20E, "normal"), + (0x20F, "layer"), + (0x210, "darken"), + (0x211, "multiply"), + (0x212, "lighten"), + (0x213, "screen"), + (0x214, "overlay"), + (0x215, "hardlight"), + (0x216, "subtract"), + (0x217, "difference"), + (0x218, "invert"), + (0x219, "alpha"), + (0x21A, "erase"), + (0x21B, "/"), + (0x21C, ".."), + (0x21D, "linear"), + (0x21E, "radial"), + (0x21F, "none"), + (0x220, "square"), + (0x221, "miter"), + (0x222, "bevel"), + (0x223, "left"), + (0x224, "right"), + (0x225, "center"), + (0x226, "box"), + (0x227, "reflect"), + (0x228, "repeat"), + (0x229, "RGB"), + (0x22A, "linearRGB"), + (0x22B, "justify"), + (0x22C, "shader"), + (0x22D, "vertical"), + (0x22E, "horizontal"), + (0x22F, "pad"), + (0x230, "evenOdd"), + (0x231, "nonZero"), + (0x232, "negative"), + (0x233, "positive"), + (0x234, "xml"), + (0x235, "B"), + (0x236, "BL"), + (0x237, "BR"), + (0x238, "L"), + (0x239, "R"), + (0x23A, "T"), + (0x23B, "TL"), + (0x23C, "TR"), + (0x23D, "exactFit"), + (0x23E, "noBorder"), + (0x23F, "noScale"), + (0x240, "showAll"), + (0x241, "easeInSine"), + (0x242, "easeOutSine"), + (0x243, "easeInOutSine"), + (0x244, "easeOutInSine"), + (0x245, "easeInQuad"), + (0x246, "easeOutQuad"), + (0x247, "easeInOutQuad"), + (0x248, "easeOutInQuad"), + (0x249, "easeInFlash"), + (0x24A, "easeOutFlash"), + (0x24B, "element"), + (0x24C, "dynamic"), + (0x24D, "binary"), + (0x24E, "variables"), + (0x24F, "LB"), + (0x250, "RB"), + (0x251, "LT"), + (0x252, "RT"), + (0x253, ""), + (0x254, "arrow"), + (0x255, "auto"), + (0x256, "button"), + (0x257, "hand"), + (0x258, "ibeam"), + (0x259, "advanced"), + (0x25A, "pixel"), + (0x25B, "subpixel"), + (0x25C, "full"), + (0x25D, "inner"), + (0x25E, "outer"), + (0x25F, "easeInBack"), + (0x260, "easeOutBack"), + (0x261, "easeInOutBack"), + (0x262, "easeOutInBack"), + (0x263, "registerClassConstructor"), + (0x264, "setter"), + (0x265, "getter"), + (0x266, "???"), + (0x267, "aep_dummy"), + (0x268, "kind"), + (0x269, "_kind"), + (0x26A, "org"), + (0x26B, "flashdevelop"), + (0x26C, "utils"), + (0x26D, "FlashConnect"), + (0x26E, "path"), + (0x26F, "if"), + (0x270, "notif"), + (0x271, "not"), + (0x272, "A"), + (0x273, "dmy0273"), + (0x274, "C"), + (0x275, "D"), + (0x276, "dmy0276"), + (0x277, "F"), + (0x278, "G"), + (0x279, "H"), + (0x27A, "I"), + (0x27B, "J"), + (0x27C, "K"), + (0x27D, "dmy027d"), + (0x27E, "M"), + (0x27F, "N"), + (0x280, "O"), + (0x281, "P"), + (0x282, "Q"), + (0x283, "dmy0283"), + (0x284, "S"), + (0x285, "dmy0285"), + (0x286, "U"), + (0x287, "V"), + (0x288, "W"), + (0x289, "X"), + (0x28A, "Y"), + (0x28B, "Z"), + (0x28C, "fullscreen"), + (0x28D, "showmenu"), + (0x28E, "allowscale"), + (0x28F, "quit"), + (0x290, "true"), + (0x291, "false"), + (0x292, "clamp"), + (0x293, "ignore"), + (0x294, "wrap"), + (0x295, "unknown"), + (0x296, "bigEndian"), + (0x297, "littleEndian"), + (0x298, "fragment"), + (0x299, "vertex"), + (0x29A, "bgra"), + (0x29B, "bgraPacked4444"), + (0x29C, "bgrPacked565"), + (0x29D, "compressed"), + (0x29E, "compressedAlpha"), + (0x29F, "bytes4"), + (0x2A0, "float1"), + (0x2A1, "float2"), + (0x2A2, "float3"), + (0x2A3, "float4"), + (0x2A4, "super"), + (0x2A5, "axisAngle"), + (0x2A6, "eulerAngles"), + (0x2A7, "quaternion"), + (0x2A8, "orientationStyle"), # Layer depths - (0x2e0, '_level0'), - (0x2e1, '_level1'), - (0x2e2, '_level2'), - (0x2e3, '_level3'), - (0x2e4, '_level4'), - (0x2e5, '_level5'), - (0x2e6, '_level6'), - (0x2e7, '_level7'), - (0x2e8, '_level8'), - (0x2e9, '_level9'), - (0x2ea, '_level10'), - (0x2eb, '_level11'), - (0x2ec, '_level12'), - (0x2ed, '_level13'), - (0x2ee, '_level14'), - (0x2ef, '_level15'), - + (0x2E0, "_level0"), + (0x2E1, "_level1"), + (0x2E2, "_level2"), + (0x2E3, "_level3"), + (0x2E4, "_level4"), + (0x2E5, "_level5"), + (0x2E6, "_level6"), + (0x2E7, "_level7"), + (0x2E8, "_level8"), + (0x2E9, "_level9"), + (0x2EA, "_level10"), + (0x2EB, "_level11"), + (0x2EC, "_level12"), + (0x2ED, "_level13"), + (0x2EE, "_level14"), + (0x2EF, "_level15"), # System objects - (0x300, 'System'), - (0x301, 'Stage'), - (0x302, 'Key'), - (0x303, 'Math'), - (0x304, 'flash'), - (0x305, 'MovieClip'), - (0x306, 'String'), - (0x307, 'TextField'), - (0x308, 'Color'), - (0x309, 'Date'), - (0x30a, 'SharedObject'), - (0x30b, 'Mouse'), - (0x30c, 'Object'), - (0x30d, 'Sound'), - (0x30e, 'Number'), - (0x30f, 'Array'), - (0x310, 'XML'), - (0x311, 'TextFormat'), - (0x312, 'display'), - (0x313, 'geom'), - (0x314, 'Matrix'), - (0x315, 'Point'), - (0x316, 'BitmapData'), - (0x317, 'data'), - (0x318, 'filters'), - (0x319, 'ColorMatrixFilter'), - (0x31a, 'Function'), - (0x31b, 'XMLNode'), - (0x31c, 'aplib'), - (0x31d, 'Transform'), - (0x31e, 'ColorTransform'), - (0x31f, 'Rectangle'), - (0x320, 'asdlib'), - (0x321, 'XMLController'), - (0x322, 'eManager'), - (0x323, 'Error'), - (0x324, 'MovieClipLoader'), - (0x325, 'UndefChecker'), - (0x326, 'int'), - (0x327, 'uint'), - (0x328, 'Vector'), - (0x329, 'Event'), - (0x32a, 'MouseEvent'), - (0x32b, 'Matrix3D'), - (0x32c, 'Keyboard'), - (0x32d, 'DisplayObject'), - (0x32e, 'Dictionary'), - (0x32f, 'BlendMode'), - (0x330, 'DisplayObjectContainer'), - (0x331, 'Class'), - (0x332, 'EventDispatcher'), - (0x333, 'PerspectiveProjection'), - (0x334, 'Vector3D'), - (0x335, 'aplib3'), - (0x336, 'SoundChannel'), - (0x337, 'Loader'), - (0x338, 'URLRequest'), - (0x339, 'Sprite'), - (0x33a, 'KeyboardEvent'), - (0x33b, 'Timer'), - (0x33c, 'TimerEvent'), - (0x33d, 'asdlib3'), - (0x33e, 'eManager3'), - (0x33f, 'LoaderInfo'), - (0x340, 'ProgressEvent'), - (0x341, 'IOErrorEvent'), - (0x342, 'Graphics'), - (0x343, 'LineScaleMode'), - (0x344, 'CapsStyle'), - (0x345, 'JointStyle'), - (0x346, 'GradientType'), - (0x347, 'SpreadMethod'), - (0x348, 'InterpolationMethod'), - (0x349, 'GraphicsPathCommand'), - (0x34a, 'GraphicsPathWinding'), - (0x34b, 'TriangleCulling'), - (0x34c, 'GraphicsBitmapFill'), - (0x34d, 'GraphicsEndFill'), - (0x34e, 'GraphicsGradientFill'), - (0x34f, 'GraphicsPath'), - (0x350, 'GraphicsSolidFill'), - (0x351, 'GraphicsStroke'), - (0x352, 'GraphicsTrianglePath'), - (0x353, 'IGraphicsData'), - (0x354, 'external'), - (0x355, 'ExternalInterface'), - (0x356, 'Scene'), - (0x357, 'FrameLabel'), - (0x358, 'Shape'), - (0x359, 'SimpleButton'), - (0x35a, 'Bitmap'), - (0x35b, 'StageQuality'), - (0x35c, 'InteractiveObject'), - (0x35d, 'MotionBase'), - (0x35e, 'KeyframeBase'), - (0x35f, 'XMLList'), - (0x360, 'StageAlign'), - (0x361, 'StageScaleMode'), - (0x362, 'AnimatorBase'), - (0x363, 'Animator3D'), - (0x364, 'URLLoader'), - (0x365, 'Capabilities'), - (0x366, 'Aweener'), - (0x367, 'Aweener3'), - (0x368, 'SoundTransform'), - (0x369, 'Namespace'), - (0x36a, 'RegExp'), - (0x36b, 'afplib'), - (0x36c, 'afplib3'), - (0x36d, 'ByteArray'), - (0x36e, 'TextFormatAlign'), - (0x36f, 'TextFieldType'), - (0x370, 'TextFieldAutoSize'), - (0x371, 'SecurityErrorEvent'), - (0x372, 'ApplicationDomain'), - (0x373, 'TextEvent'), - (0x374, 'ErrorEvent'), - (0x375, 'LoaderContext'), - (0x376, 'QName'), - (0x377, 'IllegalOperationError'), - (0x378, 'URLLoaderDataFormat'), - (0x379, 'Security'), - (0x37a, 'DropShadowFilter'), - (0x37b, 'ReferenceError'), - (0x37c, 'Proxy'), - (0x37d, 'XMLSocket'), - (0x37e, 'DataEvent'), - (0x37f, 'Font'), - (0x380, 'IEventDispatcher'), - (0x381, 'LocalConnection'), - (0x382, 'ActionScriptVersion'), - (0x383, 'MouseCursor'), - (0x384, 'TypeError'), - (0x385, 'FocusEvent'), - (0x386, 'AntiAliasType'), - (0x387, 'GridFitType'), - (0x388, 'ArgumentError'), - (0x389, 'BitmapFilterType'), - (0x38a, 'BevelFilter'), - (0x38b, 'BitmapFilter'), - (0x38c, 'BitmapFilterQuality'), - (0x38d, 'XMLController3'), - (0x38e, 'URLVariables'), - (0x38f, 'URLRequestMethod'), - (0x390, 'aeplib'), - (0x391, 'BlurFilter'), - (0x392, 'Stage3D'), - (0x393, 'Context3D'), - (0x394, 'Multitouch'), - (0x395, 'Script'), - (0x396, 'AccessibilityProperties'), - (0x397, 'StaticText'), - (0x398, 'MorphShape'), - (0x399, 'BitmapDataChannel'), - (0x39a, 'DisplacementMapFilter'), - (0x39b, 'GlowFilter'), - (0x39c, 'DisplacementMapFilterMode'), - (0x39d, 'AnimatorFactoryBase'), - (0x39e, 'Endian'), - (0x39f, 'IOError'), - (0x3a0, 'EOFError'), - (0x3a1, 'Context3DTextureFormat'), - (0x3a2, 'Context3DProgramType'), - (0x3a3, 'TextureBase'), - (0x3a4, 'VertexBuffer3D'), - (0x3a5, 'IndexBuffer3D'), - (0x3a6, 'Program3D'), - (0x3a7, 'NativeMenuItem'), - (0x3a8, 'ContextMenuItem'), - (0x3a9, 'NativeMenu'), - (0x3aa, 'ContextMenu'), - (0x3ab, 'ContextMenuEvent'), - (0x3ac, 'Context3DVertexBufferFormat'), - (0x3ad, 'TouchEvent'), - (0x3ae, 'b2Vec2'), - (0x3af, 'b2Math'), - (0x3b0, 'b2Transform'), - (0x3b1, 'b2Mat22'), - (0x3b2, 'b2Sweep'), - (0x3b3, 'b2AABB'), - (0x3b4, 'b2Vec3'), - (0x3b5, 'b2Mat33'), - (0x3b6, 'b2DistanceProxy'), - (0x3b7, 'b2Shape'), - (0x3b8, 'b2CircleShape'), - (0x3b9, 'b2PolygonShape'), - (0x3ba, 'b2MassData'), - (0x3bb, 'b2DistanceInput'), - (0x3bc, 'b2DistanceOutput'), - (0x3bd, 'b2SimplexCache'), - (0x3be, 'b2Simplex'), - (0x3bf, 'b2SimplexVertex'), - (0x3c0, 'b2Distance'), - (0x3c1, 'Orientation3D'), - (0x3c2, 'GradientGlowFilter'), - (0x3c3, 'GradientBevelFilter'), - + (0x300, "System"), + (0x301, "Stage"), + (0x302, "Key"), + (0x303, "Math"), + (0x304, "flash"), + (0x305, "MovieClip"), + (0x306, "String"), + (0x307, "TextField"), + (0x308, "Color"), + (0x309, "Date"), + (0x30A, "SharedObject"), + (0x30B, "Mouse"), + (0x30C, "Object"), + (0x30D, "Sound"), + (0x30E, "Number"), + (0x30F, "Array"), + (0x310, "XML"), + (0x311, "TextFormat"), + (0x312, "display"), + (0x313, "geom"), + (0x314, "Matrix"), + (0x315, "Point"), + (0x316, "BitmapData"), + (0x317, "data"), + (0x318, "filters"), + (0x319, "ColorMatrixFilter"), + (0x31A, "Function"), + (0x31B, "XMLNode"), + (0x31C, "aplib"), + (0x31D, "Transform"), + (0x31E, "ColorTransform"), + (0x31F, "Rectangle"), + (0x320, "asdlib"), + (0x321, "XMLController"), + (0x322, "eManager"), + (0x323, "Error"), + (0x324, "MovieClipLoader"), + (0x325, "UndefChecker"), + (0x326, "int"), + (0x327, "uint"), + (0x328, "Vector"), + (0x329, "Event"), + (0x32A, "MouseEvent"), + (0x32B, "Matrix3D"), + (0x32C, "Keyboard"), + (0x32D, "DisplayObject"), + (0x32E, "Dictionary"), + (0x32F, "BlendMode"), + (0x330, "DisplayObjectContainer"), + (0x331, "Class"), + (0x332, "EventDispatcher"), + (0x333, "PerspectiveProjection"), + (0x334, "Vector3D"), + (0x335, "aplib3"), + (0x336, "SoundChannel"), + (0x337, "Loader"), + (0x338, "URLRequest"), + (0x339, "Sprite"), + (0x33A, "KeyboardEvent"), + (0x33B, "Timer"), + (0x33C, "TimerEvent"), + (0x33D, "asdlib3"), + (0x33E, "eManager3"), + (0x33F, "LoaderInfo"), + (0x340, "ProgressEvent"), + (0x341, "IOErrorEvent"), + (0x342, "Graphics"), + (0x343, "LineScaleMode"), + (0x344, "CapsStyle"), + (0x345, "JointStyle"), + (0x346, "GradientType"), + (0x347, "SpreadMethod"), + (0x348, "InterpolationMethod"), + (0x349, "GraphicsPathCommand"), + (0x34A, "GraphicsPathWinding"), + (0x34B, "TriangleCulling"), + (0x34C, "GraphicsBitmapFill"), + (0x34D, "GraphicsEndFill"), + (0x34E, "GraphicsGradientFill"), + (0x34F, "GraphicsPath"), + (0x350, "GraphicsSolidFill"), + (0x351, "GraphicsStroke"), + (0x352, "GraphicsTrianglePath"), + (0x353, "IGraphicsData"), + (0x354, "external"), + (0x355, "ExternalInterface"), + (0x356, "Scene"), + (0x357, "FrameLabel"), + (0x358, "Shape"), + (0x359, "SimpleButton"), + (0x35A, "Bitmap"), + (0x35B, "StageQuality"), + (0x35C, "InteractiveObject"), + (0x35D, "MotionBase"), + (0x35E, "KeyframeBase"), + (0x35F, "XMLList"), + (0x360, "StageAlign"), + (0x361, "StageScaleMode"), + (0x362, "AnimatorBase"), + (0x363, "Animator3D"), + (0x364, "URLLoader"), + (0x365, "Capabilities"), + (0x366, "Aweener"), + (0x367, "Aweener3"), + (0x368, "SoundTransform"), + (0x369, "Namespace"), + (0x36A, "RegExp"), + (0x36B, "afplib"), + (0x36C, "afplib3"), + (0x36D, "ByteArray"), + (0x36E, "TextFormatAlign"), + (0x36F, "TextFieldType"), + (0x370, "TextFieldAutoSize"), + (0x371, "SecurityErrorEvent"), + (0x372, "ApplicationDomain"), + (0x373, "TextEvent"), + (0x374, "ErrorEvent"), + (0x375, "LoaderContext"), + (0x376, "QName"), + (0x377, "IllegalOperationError"), + (0x378, "URLLoaderDataFormat"), + (0x379, "Security"), + (0x37A, "DropShadowFilter"), + (0x37B, "ReferenceError"), + (0x37C, "Proxy"), + (0x37D, "XMLSocket"), + (0x37E, "DataEvent"), + (0x37F, "Font"), + (0x380, "IEventDispatcher"), + (0x381, "LocalConnection"), + (0x382, "ActionScriptVersion"), + (0x383, "MouseCursor"), + (0x384, "TypeError"), + (0x385, "FocusEvent"), + (0x386, "AntiAliasType"), + (0x387, "GridFitType"), + (0x388, "ArgumentError"), + (0x389, "BitmapFilterType"), + (0x38A, "BevelFilter"), + (0x38B, "BitmapFilter"), + (0x38C, "BitmapFilterQuality"), + (0x38D, "XMLController3"), + (0x38E, "URLVariables"), + (0x38F, "URLRequestMethod"), + (0x390, "aeplib"), + (0x391, "BlurFilter"), + (0x392, "Stage3D"), + (0x393, "Context3D"), + (0x394, "Multitouch"), + (0x395, "Script"), + (0x396, "AccessibilityProperties"), + (0x397, "StaticText"), + (0x398, "MorphShape"), + (0x399, "BitmapDataChannel"), + (0x39A, "DisplacementMapFilter"), + (0x39B, "GlowFilter"), + (0x39C, "DisplacementMapFilterMode"), + (0x39D, "AnimatorFactoryBase"), + (0x39E, "Endian"), + (0x39F, "IOError"), + (0x3A0, "EOFError"), + (0x3A1, "Context3DTextureFormat"), + (0x3A2, "Context3DProgramType"), + (0x3A3, "TextureBase"), + (0x3A4, "VertexBuffer3D"), + (0x3A5, "IndexBuffer3D"), + (0x3A6, "Program3D"), + (0x3A7, "NativeMenuItem"), + (0x3A8, "ContextMenuItem"), + (0x3A9, "NativeMenu"), + (0x3AA, "ContextMenu"), + (0x3AB, "ContextMenuEvent"), + (0x3AC, "Context3DVertexBufferFormat"), + (0x3AD, "TouchEvent"), + (0x3AE, "b2Vec2"), + (0x3AF, "b2Math"), + (0x3B0, "b2Transform"), + (0x3B1, "b2Mat22"), + (0x3B2, "b2Sweep"), + (0x3B3, "b2AABB"), + (0x3B4, "b2Vec3"), + (0x3B5, "b2Mat33"), + (0x3B6, "b2DistanceProxy"), + (0x3B7, "b2Shape"), + (0x3B8, "b2CircleShape"), + (0x3B9, "b2PolygonShape"), + (0x3BA, "b2MassData"), + (0x3BB, "b2DistanceInput"), + (0x3BC, "b2DistanceOutput"), + (0x3BD, "b2SimplexCache"), + (0x3BE, "b2Simplex"), + (0x3BF, "b2SimplexVertex"), + (0x3C0, "b2Distance"), + (0x3C1, "Orientation3D"), + (0x3C2, "GradientGlowFilter"), + (0x3C3, "GradientBevelFilter"), # XML functions - (0x400, 'afp_prop_init'), - (0x401, 'afp_prop'), - (0x402, 'afp_prop_dummy'), - (0x403, 'afp_prop_destroy'), - (0x404, 'afp_sync'), - (0x405, 'afp_node_search'), - (0x406, 'afp_node_value'), - (0x407, 'afp_node_array_value'), - (0x408, 'afp_complete'), - (0x409, 'afp_sound_fade_in'), - (0x40a, 'afp_sound_fade_out'), - (0x40b, 'afp_make_gradient_data'), - (0x40c, 'afp_make_alpha_texture'), - (0x40d, 'afp_node_set_value'), - (0x40e, 'afp_node_date_value'), - (0x40f, 'afp_node_num_value'), - (0x410, 'afp_node_array_num_value'), - (0x411, 'afp_node_child'), - (0x412, 'afp_node_parent'), - (0x413, 'afp_node_next'), - (0x414, 'afp_node_prev'), - (0x415, 'afp_node_last'), - (0x416, 'afp_node_first'), - (0x417, 'afp_node_next_same_name'), - (0x418, 'afp_node_prev_same_name'), - (0x419, 'afp_node_name'), - (0x41a, 'afp_node_absolute_path'), - (0x41b, 'afp_node_has_parent'), - (0x41c, 'afp_node_has_child'), - (0x41d, 'afp_node_has_sibling'), - (0x41e, 'afp_node_has_attrib'), - (0x41f, 'afp_node_has_same_name_sibling'), - + (0x400, "afp_prop_init"), + (0x401, "afp_prop"), + (0x402, "afp_prop_dummy"), + (0x403, "afp_prop_destroy"), + (0x404, "afp_sync"), + (0x405, "afp_node_search"), + (0x406, "afp_node_value"), + (0x407, "afp_node_array_value"), + (0x408, "afp_complete"), + (0x409, "afp_sound_fade_in"), + (0x40A, "afp_sound_fade_out"), + (0x40B, "afp_make_gradient_data"), + (0x40C, "afp_make_alpha_texture"), + (0x40D, "afp_node_set_value"), + (0x40E, "afp_node_date_value"), + (0x40F, "afp_node_num_value"), + (0x410, "afp_node_array_num_value"), + (0x411, "afp_node_child"), + (0x412, "afp_node_parent"), + (0x413, "afp_node_next"), + (0x414, "afp_node_prev"), + (0x415, "afp_node_last"), + (0x416, "afp_node_first"), + (0x417, "afp_node_next_same_name"), + (0x418, "afp_node_prev_same_name"), + (0x419, "afp_node_name"), + (0x41A, "afp_node_absolute_path"), + (0x41B, "afp_node_has_parent"), + (0x41C, "afp_node_has_child"), + (0x41D, "afp_node_has_sibling"), + (0x41E, "afp_node_has_attrib"), + (0x41F, "afp_node_has_same_name_sibling"), # System functions - (0x420, 'updateAfterEvent'), - (0x421, 'parseInt'), - (0x422, 'parseFloat'), - (0x423, 'Boolean'), - (0x424, 'setInterval'), - (0x425, 'clearInterval'), - (0x426, 'escape'), - (0x427, 'ASSetPropFlags'), - (0x428, 'unescape'), - (0x429, 'isNaN'), - (0x42a, 'isFinite'), - (0x42b, 'trace'), - (0x42c, 'addFrameScript'), - (0x42d, 'getDefinitionByName'), - (0x42e, 'getTimer'), - (0x42f, 'setTimeout'), - (0x430, 'clearTimeout'), - (0x431, 'escapeMultiByte'), - (0x432, 'unescapeMultiByte'), - (0x433, 'getQualifiedClassName'), - (0x434, 'describeType'), - (0x435, 'decodeURI'), - (0x436, 'encodeURI'), - (0x437, 'decodeURIComponent'), - (0x438, 'encodeURIComponent'), - (0x439, 'registerClassAlias'), - (0x43a, 'getClassByAlias'), - (0x43b, 'getQualifiedSuperclassName'), - (0x43c, 'isXMLName'), - (0x43d, 'fscommand'), - + (0x420, "updateAfterEvent"), + (0x421, "parseInt"), + (0x422, "parseFloat"), + (0x423, "Boolean"), + (0x424, "setInterval"), + (0x425, "clearInterval"), + (0x426, "escape"), + (0x427, "ASSetPropFlags"), + (0x428, "unescape"), + (0x429, "isNaN"), + (0x42A, "isFinite"), + (0x42B, "trace"), + (0x42C, "addFrameScript"), + (0x42D, "getDefinitionByName"), + (0x42E, "getTimer"), + (0x42F, "setTimeout"), + (0x430, "clearTimeout"), + (0x431, "escapeMultiByte"), + (0x432, "unescapeMultiByte"), + (0x433, "getQualifiedClassName"), + (0x434, "describeType"), + (0x435, "decodeURI"), + (0x436, "encodeURI"), + (0x437, "decodeURIComponent"), + (0x438, "encodeURIComponent"), + (0x439, "registerClassAlias"), + (0x43A, "getClassByAlias"), + (0x43B, "getQualifiedSuperclassName"), + (0x43C, "isXMLName"), + (0x43D, "fscommand"), # Current movie manipulation functions. - (0x440, 'stop'), - (0x441, 'play'), - (0x442, 'gotoAndPlay'), - (0x443, 'gotoAndStop'), - (0x444, 'prevFrame'), - (0x445, 'nextFrame'), - (0x446, 'createEmptyMovieClip'), - (0x447, 'duplicateMovieClip'), - (0x448, 'attachMovie'), - (0x449, 'attachBitmap'), - (0x44a, 'removeMovieClip'), - (0x44b, 'unloadMovie'), - (0x44c, 'loadMovie'), - (0x44d, 'loadVariables'), - (0x44e, 'startDrag'), - (0x44f, 'stopDrag'), - (0x450, 'setMask'), - (0x451, 'hitTest'), - (0x452, 'lineStyle'), - (0x453, 'lineGradientStyle'), - (0x454, 'beginFill'), - (0x455, 'beginBitmapFill'), - (0x456, 'endFill'), - (0x457, 'moveTo'), - (0x458, 'lineTo'), - (0x459, 'curveTo'), - (0x45a, 'clear'), - (0x45b, 'getBytesLoaded'), - (0x45c, 'getBytesTotal'), - (0x45d, 'getDepth'), - (0x45e, 'getNextHighestDepth'), - (0x45f, 'swapDepths'), - (0x460, 'localToGlobal'), - (0x461, 'beginGradientFill'), - (0x462, 'getSWFVersion'), - (0x463, 'getRect'), - (0x464, 'getBounds'), - (0x465, 'getInstanceAtDepth'), - (0x466, 'getURL'), - (0x467, 'globalToLocal'), - (0x468, 'nextScene'), - (0x469, 'prevScene'), - (0x46a, 'getChildByName'), - (0x46b, 'getChildIndex'), - (0x46c, 'addChild'), - (0x46d, 'removeChildAt'), - (0x46e, 'getChildAt'), - (0x46f, 'setChildIndex'), - (0x470, 'lineBitmapStyle'), - (0x471, 'hitTestObject'), - (0x472, 'hitTestPoint'), - (0x473, 'addChildAt'), - (0x474, 'removeChild'), - (0x475, 'swapChildren'), - (0x476, 'swapChildrenAt'), - (0x477, 'getObjectsUnderPoint'), - (0x478, 'createTextField'), - (0x479, 'local3DToGlobal'), - (0x47a, 'globalToLocal3D'), - + (0x440, "stop"), + (0x441, "play"), + (0x442, "gotoAndPlay"), + (0x443, "gotoAndStop"), + (0x444, "prevFrame"), + (0x445, "nextFrame"), + (0x446, "createEmptyMovieClip"), + (0x447, "duplicateMovieClip"), + (0x448, "attachMovie"), + (0x449, "attachBitmap"), + (0x44A, "removeMovieClip"), + (0x44B, "unloadMovie"), + (0x44C, "loadMovie"), + (0x44D, "loadVariables"), + (0x44E, "startDrag"), + (0x44F, "stopDrag"), + (0x450, "setMask"), + (0x451, "hitTest"), + (0x452, "lineStyle"), + (0x453, "lineGradientStyle"), + (0x454, "beginFill"), + (0x455, "beginBitmapFill"), + (0x456, "endFill"), + (0x457, "moveTo"), + (0x458, "lineTo"), + (0x459, "curveTo"), + (0x45A, "clear"), + (0x45B, "getBytesLoaded"), + (0x45C, "getBytesTotal"), + (0x45D, "getDepth"), + (0x45E, "getNextHighestDepth"), + (0x45F, "swapDepths"), + (0x460, "localToGlobal"), + (0x461, "beginGradientFill"), + (0x462, "getSWFVersion"), + (0x463, "getRect"), + (0x464, "getBounds"), + (0x465, "getInstanceAtDepth"), + (0x466, "getURL"), + (0x467, "globalToLocal"), + (0x468, "nextScene"), + (0x469, "prevScene"), + (0x46A, "getChildByName"), + (0x46B, "getChildIndex"), + (0x46C, "addChild"), + (0x46D, "removeChildAt"), + (0x46E, "getChildAt"), + (0x46F, "setChildIndex"), + (0x470, "lineBitmapStyle"), + (0x471, "hitTestObject"), + (0x472, "hitTestPoint"), + (0x473, "addChildAt"), + (0x474, "removeChild"), + (0x475, "swapChildren"), + (0x476, "swapChildrenAt"), + (0x477, "getObjectsUnderPoint"), + (0x478, "createTextField"), + (0x479, "local3DToGlobal"), + (0x47A, "globalToLocal3D"), # System object manipulation functions. - (0x480, 'toString'), - (0x481, 'distance'), - (0x482, 'translate'), - (0x483, 'rotate'), - (0x484, 'scale'), - (0x485, 'clone'), - (0x486, 'transformPoint'), - (0x487, 'add'), - (0x488, 'cos'), - (0x489, 'sin'), - (0x48a, 'sqrt'), - (0x48b, 'atan2'), - (0x48c, 'log'), - (0x48d, 'abs'), - (0x48e, 'floor'), - (0x48f, 'ceil'), - (0x490, 'round'), - (0x491, 'pow'), - (0x492, 'max'), - (0x493, 'min'), - (0x494, 'random'), - (0x495, 'acos'), - (0x496, 'asin'), - (0x497, 'atan'), - (0x498, 'tan'), - (0x499, 'exp'), - (0x49a, 'getRGB'), - (0x49b, 'setRGB'), - (0x49c, 'getTransform'), - (0x49d, 'setTransform'), - (0x49e, 'fromCharCode'), - (0x49f, 'substr'), - (0x4a0, 'substring'), - (0x4a1, 'toUpperCase'), - (0x4a2, 'toLowerCase'), - (0x4a3, 'indexOf'), - (0x4a4, 'lastIndexOf'), - (0x4a5, 'charAt'), - (0x4a6, 'charCodeAt'), - (0x4a7, 'split'), - (0x4a8, 'concat'), - (0x4a9, 'getFullYear'), - (0x4aa, 'getUTCFullYear'), - (0x4ab, 'getMonth'), - (0x4ac, 'getUTCMonth'), - (0x4ad, 'getDate'), - (0x4ae, 'getUTCDate'), - (0x4af, 'getDay'), - (0x4b0, 'getHours'), - (0x4b1, 'getUTCHours'), - (0x4b2, 'getMinutes'), - (0x4b3, 'getUTCMinutes'), - (0x4b4, 'getSeconds'), - (0x4b5, 'getUTCSeconds'), - (0x4b6, 'getTime'), - (0x4b7, 'getTimezoneOffset'), - (0x4b8, 'UTC'), - (0x4b9, 'createElement'), - (0x4ba, 'appendChild'), - (0x4bb, 'createTextNode'), - (0x4bc, 'parseXML'), - (0x4bd, 'load'), - (0x4be, 'hasChildNodes'), - (0x4bf, 'cloneNode'), - (0x4c0, 'removeNode'), - (0x4c1, 'loadInAdvance'), - (0x4c2, 'createGradientBox'), - (0x4c3, 'loadBitmap'), - (0x4c4, 'hide'), - (0x4c5, 'show'), - (0x4c6, 'addListener'), - (0x4c7, 'removeListener'), - (0x4c8, 'isDown'), - (0x4c9, 'getCode'), - (0x4ca, 'getAscii'), - (0x4cb, 'attachSound'), - (0x4cc, 'start'), - (0x4cd, 'getVolume'), - (0x4ce, 'setVolume'), - (0x4cf, 'setPan'), - (0x4d0, 'loadSound'), - (0x4d1, 'setTextFormat'), - (0x4d2, 'getTextFormat'), - (0x4d3, 'push'), - (0x4d4, 'pop'), - (0x4d5, 'slice'), - (0x4d6, 'splice'), - (0x4d7, 'reverse'), - (0x4d8, 'sort'), - (0x4d9, 'flush'), - (0x4da, 'getLocal'), - (0x4db, 'shift'), - (0x4dc, 'unshift'), - (0x4dd, 'registerClass'), - (0x4de, 'getUTCDay'), - (0x4df, 'getMilliseconds'), - (0x4e0, 'getUTCMilliseconds'), - (0x4e1, 'addProperty'), - (0x4e2, 'hasOwnProperty'), - (0x4e3, 'isPropertyEnumerable'), - (0x4e4, 'isPrototypeOf'), - (0x4e5, 'unwatch'), - (0x4e6, 'valueOf'), - (0x4e7, 'watch'), - (0x4e8, 'apply'), - (0x4e9, 'call'), - (0x4ea, 'contains'), - (0x4eb, 'containsPoint'), - (0x4ec, 'containsRectangle'), - (0x4ed, 'equals'), - (0x4ee, 'inflate'), - (0x4ef, 'inflatePoint'), - (0x4f0, 'intersection'), - (0x4f1, 'intersects'), - (0x4f2, 'isEmpty'), - (0x4f3, 'offset'), - (0x4f4, 'offsetPoint'), - (0x4f5, 'setEmpty'), - (0x4f6, 'union'), - (0x4f7, 'interpolate'), - (0x4f8, 'join'), - (0x4f9, 'loadClip'), - (0x4fa, 'getProgress'), - (0x4fb, 'unloadClip'), - (0x4fc, 'polar'), - (0x4fd, 'sortOn'), - (0x4fe, 'containsRect'), - (0x4ff, 'getYear'), - + (0x480, "toString"), + (0x481, "distance"), + (0x482, "translate"), + (0x483, "rotate"), + (0x484, "scale"), + (0x485, "clone"), + (0x486, "transformPoint"), + (0x487, "add"), + (0x488, "cos"), + (0x489, "sin"), + (0x48A, "sqrt"), + (0x48B, "atan2"), + (0x48C, "log"), + (0x48D, "abs"), + (0x48E, "floor"), + (0x48F, "ceil"), + (0x490, "round"), + (0x491, "pow"), + (0x492, "max"), + (0x493, "min"), + (0x494, "random"), + (0x495, "acos"), + (0x496, "asin"), + (0x497, "atan"), + (0x498, "tan"), + (0x499, "exp"), + (0x49A, "getRGB"), + (0x49B, "setRGB"), + (0x49C, "getTransform"), + (0x49D, "setTransform"), + (0x49E, "fromCharCode"), + (0x49F, "substr"), + (0x4A0, "substring"), + (0x4A1, "toUpperCase"), + (0x4A2, "toLowerCase"), + (0x4A3, "indexOf"), + (0x4A4, "lastIndexOf"), + (0x4A5, "charAt"), + (0x4A6, "charCodeAt"), + (0x4A7, "split"), + (0x4A8, "concat"), + (0x4A9, "getFullYear"), + (0x4AA, "getUTCFullYear"), + (0x4AB, "getMonth"), + (0x4AC, "getUTCMonth"), + (0x4AD, "getDate"), + (0x4AE, "getUTCDate"), + (0x4AF, "getDay"), + (0x4B0, "getHours"), + (0x4B1, "getUTCHours"), + (0x4B2, "getMinutes"), + (0x4B3, "getUTCMinutes"), + (0x4B4, "getSeconds"), + (0x4B5, "getUTCSeconds"), + (0x4B6, "getTime"), + (0x4B7, "getTimezoneOffset"), + (0x4B8, "UTC"), + (0x4B9, "createElement"), + (0x4BA, "appendChild"), + (0x4BB, "createTextNode"), + (0x4BC, "parseXML"), + (0x4BD, "load"), + (0x4BE, "hasChildNodes"), + (0x4BF, "cloneNode"), + (0x4C0, "removeNode"), + (0x4C1, "loadInAdvance"), + (0x4C2, "createGradientBox"), + (0x4C3, "loadBitmap"), + (0x4C4, "hide"), + (0x4C5, "show"), + (0x4C6, "addListener"), + (0x4C7, "removeListener"), + (0x4C8, "isDown"), + (0x4C9, "getCode"), + (0x4CA, "getAscii"), + (0x4CB, "attachSound"), + (0x4CC, "start"), + (0x4CD, "getVolume"), + (0x4CE, "setVolume"), + (0x4CF, "setPan"), + (0x4D0, "loadSound"), + (0x4D1, "setTextFormat"), + (0x4D2, "getTextFormat"), + (0x4D3, "push"), + (0x4D4, "pop"), + (0x4D5, "slice"), + (0x4D6, "splice"), + (0x4D7, "reverse"), + (0x4D8, "sort"), + (0x4D9, "flush"), + (0x4DA, "getLocal"), + (0x4DB, "shift"), + (0x4DC, "unshift"), + (0x4DD, "registerClass"), + (0x4DE, "getUTCDay"), + (0x4DF, "getMilliseconds"), + (0x4E0, "getUTCMilliseconds"), + (0x4E1, "addProperty"), + (0x4E2, "hasOwnProperty"), + (0x4E3, "isPropertyEnumerable"), + (0x4E4, "isPrototypeOf"), + (0x4E5, "unwatch"), + (0x4E6, "valueOf"), + (0x4E7, "watch"), + (0x4E8, "apply"), + (0x4E9, "call"), + (0x4EA, "contains"), + (0x4EB, "containsPoint"), + (0x4EC, "containsRectangle"), + (0x4ED, "equals"), + (0x4EE, "inflate"), + (0x4EF, "inflatePoint"), + (0x4F0, "intersection"), + (0x4F1, "intersects"), + (0x4F2, "isEmpty"), + (0x4F3, "offset"), + (0x4F4, "offsetPoint"), + (0x4F5, "setEmpty"), + (0x4F6, "union"), + (0x4F7, "interpolate"), + (0x4F8, "join"), + (0x4F9, "loadClip"), + (0x4FA, "getProgress"), + (0x4FB, "unloadClip"), + (0x4FC, "polar"), + (0x4FD, "sortOn"), + (0x4FE, "containsRect"), + (0x4FF, "getYear"), # Event constants - (0x500, 'onKeyDown'), - (0x501, 'onKeyUp'), - (0x502, 'onMouseDown'), - (0x503, 'onMouseUp'), - (0x504, 'onMouseMove'), - (0x505, 'onLoad'), - (0x506, 'onEnterFrame'), - (0x507, 'onUnload'), - (0x508, 'onRollOver'), - (0x509, 'onRollOut'), - (0x50a, 'onPress'), - (0x50b, 'onRelease'), - (0x50c, 'onReleaseOutside'), - (0x50d, 'onData'), - (0x50e, 'onSoundComplete'), - (0x50f, 'onDragOver'), - (0x510, 'onDragOut'), - (0x511, 'onMouseWheel'), - (0x512, 'onLoadError'), - (0x513, 'onLoadComplete'), - (0x514, 'onLoadInit'), - (0x515, 'onLoadProgress'), - (0x516, 'onLoadStart'), - (0x517, 'onComplete'), - (0x518, 'onCompleteParams'), - (0x519, 'ononCompleteScope'), - (0x51a, 'onStart'), - (0x51b, 'onStartParams'), - (0x51c, 'onStartScope'), - (0x51d, 'onUpdate'), - (0x51e, 'onUpdateParams'), - (0x51f, 'onUpdateScope'), - (0x520, 'onKeyPress'), - (0x521, 'onInitialize'), - (0x522, 'onConstruct'), - (0x5c0, 'scaleX'), - (0x5c1, 'scaleY'), - (0x5c2, 'currentFrame'), - (0x5c3, 'totalFrames'), - (0x5c4, 'visible'), - (0x5c5, 'rotation'), - (0x5c6, 'framesLoaded'), - (0x5c7, 'dropTarget'), - (0x5c8, 'focusRect'), - (0x5c9, 'mouseX'), - (0x5ca, 'mouseY'), - (0x5cb, 'root'), - (0x5cc, 'parent'), - (0x5cd, 'stage'), - (0x5ce, 'currentLabel'), - (0x5cf, 'currentLabels'), - (0x5d0, 'currentFrameLabel'), - (0x5d1, 'currentScene'), - (0x5d2, 'scenes'), - (0x5d3, 'rotationX'), - (0x5d4, 'rotationY'), - (0x5d5, 'rotationZ'), - (0x5d6, 'quality'), - (0x5d7, 'skewX'), - (0x5d8, 'skewY'), - (0x5d9, 'rotationConcat'), - (0x5da, 'useRotationConcat'), - (0x5db, 'scaleZ'), - (0x5dc, 'isPlaying'), - + (0x500, "onKeyDown"), + (0x501, "onKeyUp"), + (0x502, "onMouseDown"), + (0x503, "onMouseUp"), + (0x504, "onMouseMove"), + (0x505, "onLoad"), + (0x506, "onEnterFrame"), + (0x507, "onUnload"), + (0x508, "onRollOver"), + (0x509, "onRollOut"), + (0x50A, "onPress"), + (0x50B, "onRelease"), + (0x50C, "onReleaseOutside"), + (0x50D, "onData"), + (0x50E, "onSoundComplete"), + (0x50F, "onDragOver"), + (0x510, "onDragOut"), + (0x511, "onMouseWheel"), + (0x512, "onLoadError"), + (0x513, "onLoadComplete"), + (0x514, "onLoadInit"), + (0x515, "onLoadProgress"), + (0x516, "onLoadStart"), + (0x517, "onComplete"), + (0x518, "onCompleteParams"), + (0x519, "ononCompleteScope"), + (0x51A, "onStart"), + (0x51B, "onStartParams"), + (0x51C, "onStartScope"), + (0x51D, "onUpdate"), + (0x51E, "onUpdateParams"), + (0x51F, "onUpdateScope"), + (0x520, "onKeyPress"), + (0x521, "onInitialize"), + (0x522, "onConstruct"), + (0x5C0, "scaleX"), + (0x5C1, "scaleY"), + (0x5C2, "currentFrame"), + (0x5C3, "totalFrames"), + (0x5C4, "visible"), + (0x5C5, "rotation"), + (0x5C6, "framesLoaded"), + (0x5C7, "dropTarget"), + (0x5C8, "focusRect"), + (0x5C9, "mouseX"), + (0x5CA, "mouseY"), + (0x5CB, "root"), + (0x5CC, "parent"), + (0x5CD, "stage"), + (0x5CE, "currentLabel"), + (0x5CF, "currentLabels"), + (0x5D0, "currentFrameLabel"), + (0x5D1, "currentScene"), + (0x5D2, "scenes"), + (0x5D3, "rotationX"), + (0x5D4, "rotationY"), + (0x5D5, "rotationZ"), + (0x5D6, "quality"), + (0x5D7, "skewX"), + (0x5D8, "skewY"), + (0x5D9, "rotationConcat"), + (0x5DA, "useRotationConcat"), + (0x5DB, "scaleZ"), + (0x5DC, "isPlaying"), # Key constants - (0x600, 'BACKSPACE'), - (0x601, 'CAPSLOCK'), - (0x602, 'CONTROL'), - (0x603, 'DELETEKEY'), - (0x604, 'DOWN'), - (0x605, 'END'), - (0x606, 'ENTER'), - (0x607, 'ESCAPE'), - (0x608, 'HOME'), - (0x609, 'INSERT'), - (0x60a, 'LEFT'), - (0x60b, 'PGDN'), - (0x60c, 'PGUP'), - (0x60d, 'RIGHT'), - (0x60e, 'SHIFT'), - (0x60f, 'SPACE'), - (0x610, 'TAB'), - (0x611, 'UP'), - (0x612, 'ARROW'), - (0x613, 'AUTO'), - (0x614, 'BUTTON'), - (0x615, 'HAND'), - (0x616, 'IBEAM'), - + (0x600, "BACKSPACE"), + (0x601, "CAPSLOCK"), + (0x602, "CONTROL"), + (0x603, "DELETEKEY"), + (0x604, "DOWN"), + (0x605, "END"), + (0x606, "ENTER"), + (0x607, "ESCAPE"), + (0x608, "HOME"), + (0x609, "INSERT"), + (0x60A, "LEFT"), + (0x60B, "PGDN"), + (0x60C, "PGUP"), + (0x60D, "RIGHT"), + (0x60E, "SHIFT"), + (0x60F, "SPACE"), + (0x610, "TAB"), + (0x611, "UP"), + (0x612, "ARROW"), + (0x613, "AUTO"), + (0x614, "BUTTON"), + (0x615, "HAND"), + (0x616, "IBEAM"), # Some sort of sorting constants. - (0x620, 'CASEINSENSITIVE'), - (0x621, 'DESCENDING'), - (0x622, 'UNIQUESORT'), - (0x623, 'RETURNINDEXEDARRAY'), - (0x624, 'NUMERIC'), - (0x640, 'ADD'), - (0x641, 'ALPHA'), - (0x642, 'DARKEN'), - (0x643, 'DIFFERENCE'), - (0x644, 'ERASE'), - (0x645, 'HARDLIGHT'), - (0x646, 'INVERT'), - (0x647, 'LAYER'), - (0x648, 'LIGHTEN'), - (0x649, 'MULTIPLY'), - (0x64a, 'NORMAL'), - (0x64b, 'OVERLAY'), - (0x64c, 'SCREEN'), - (0x64d, 'SHADER'), - (0x64e, 'SUBTRACT'), - (0x660, 'dmy0660'), - (0x661, 'NONE'), - (0x662, 'VERTICAL'), - (0x663, 'HORIZONTAL'), - (0x664, 'ROUND'), - (0x665, 'SQUARE'), - (0x666, 'BEVEL'), - (0x667, 'MITER'), - (0x668, 'LINEAR'), - (0x669, 'RADIAL'), - (0x66a, 'PAD'), - (0x66b, 'REFLECT'), - (0x66c, 'REPEAT'), - (0x66d, 'LINEAR_RGB'), - (0x66e, 'NO_OP'), - (0x66f, 'MOVE_TO'), - (0x670, 'LINE_TO'), - (0x671, 'CURVE_TO'), - (0x672, 'WIDE_MOVE_TO'), - (0x673, 'WIDE_LINE_TO'), - (0x674, 'EVEN_ODD'), - (0x675, 'NON_ZERO'), - (0x676, 'NEGATIVE'), - (0x677, 'POSITIVE'), - (0x678, 'FRAGMENT'), - (0x679, 'VERTEX'), - (0x67a, 'BGRA'), - (0x67b, 'BGRA_PACKED'), - (0x67c, 'BGR_PACKED'), - (0x67d, 'COMPRESSED'), - (0x67e, 'COMPRESSED_ALPHA'), - (0x67f, 'BYTES_4'), - (0x680, 'FLOAT_1'), - (0x681, 'FLOAT_2'), - (0x682, 'FLOAT_3'), - (0x683, 'FLOAT_4'), - (0x684, 'e_unknownShape'), - (0x685, 'e_circleShape'), - (0x686, 'e_polygonShape'), - (0x687, 'e_edgeShape'), - (0x688, 'e_shapeTypeCount'), - (0x689, 'CUBIC_CURVE_TO'), - (0x690, 'BOTTOM'), - (0x691, 'BOTTOM_LEFT'), - (0x692, 'BOTTOM_RIGHT'), - (0x693, 'TOP'), - (0x694, 'TOP_LEFT'), - (0x695, 'TOP_RIGHT'), - (0x696, 'EXACT_FIT'), - (0x697, 'NO_BORDER'), - (0x698, 'NO_SCALE'), - (0x699, 'SHOW_ALL'), - (0x6a0, 'CENTER'), - (0x6a1, 'JUSTIFY'), - (0x6a2, 'dmy06a2'), - (0x6a3, 'dmy06a3'), - (0x6a4, 'dmy06a4'), - (0x6a5, 'dmy06a5'), - (0x6a6, 'dmy06a6'), - (0x6a7, 'dmy06a7'), - (0x6a8, 'dmy06a8'), - (0x6a9, 'DYNAMIC'), - (0x6aa, 'INPUT'), - (0x6ab, 'ADVANCED'), - (0x6ac, 'PIXEL'), - (0x6ad, 'SUBPIXEL'), - (0x6b0, 'BINARY'), - (0x6b1, 'TEXT'), - (0x6b2, 'VARIABLES'), - (0x6c0, 'FULL'), - (0x6c1, 'INNER'), - (0x6c2, 'OUTER'), - (0x6c3, 'RED'), - (0x6c4, 'GREEN'), - (0x6c5, 'BLUE'), - (0x6c6, 'CLAMP'), - (0x6c7, 'COLOR'), - (0x6c8, 'IGNORE'), - (0x6c9, 'WRAP'), - (0x6d0, 'DELETE'), - (0x6d1, 'GET'), - (0x6d2, 'HEAD'), - (0x6d3, 'OPTIONS'), - (0x6d4, 'POST'), - (0x6d5, 'PUT'), - (0x6d6, 'BIG_ENDIAN'), - (0x6d7, 'LITTLE_ENDIAN'), - (0x6d8, 'AXIS_ANGLE'), - (0x6d9, 'EULER_ANGLES'), - (0x6da, 'QUATERNION'), - (0x6f0, 'NUMBER_0'), - (0x6f1, 'NUMBER_1'), - (0x6f2, 'NUMBER_2'), - (0x6f3, 'NUMBER_3'), - (0x6f4, 'NUMBER_4'), - (0x6f5, 'NUMBER_5'), - (0x6f6, 'NUMBER_6'), - (0x6f7, 'NUMBER_7'), - (0x6f8, 'NUMBER_8'), - (0x6f9, 'NUMBER_9'), - + (0x620, "CASEINSENSITIVE"), + (0x621, "DESCENDING"), + (0x622, "UNIQUESORT"), + (0x623, "RETURNINDEXEDARRAY"), + (0x624, "NUMERIC"), + (0x640, "ADD"), + (0x641, "ALPHA"), + (0x642, "DARKEN"), + (0x643, "DIFFERENCE"), + (0x644, "ERASE"), + (0x645, "HARDLIGHT"), + (0x646, "INVERT"), + (0x647, "LAYER"), + (0x648, "LIGHTEN"), + (0x649, "MULTIPLY"), + (0x64A, "NORMAL"), + (0x64B, "OVERLAY"), + (0x64C, "SCREEN"), + (0x64D, "SHADER"), + (0x64E, "SUBTRACT"), + (0x660, "dmy0660"), + (0x661, "NONE"), + (0x662, "VERTICAL"), + (0x663, "HORIZONTAL"), + (0x664, "ROUND"), + (0x665, "SQUARE"), + (0x666, "BEVEL"), + (0x667, "MITER"), + (0x668, "LINEAR"), + (0x669, "RADIAL"), + (0x66A, "PAD"), + (0x66B, "REFLECT"), + (0x66C, "REPEAT"), + (0x66D, "LINEAR_RGB"), + (0x66E, "NO_OP"), + (0x66F, "MOVE_TO"), + (0x670, "LINE_TO"), + (0x671, "CURVE_TO"), + (0x672, "WIDE_MOVE_TO"), + (0x673, "WIDE_LINE_TO"), + (0x674, "EVEN_ODD"), + (0x675, "NON_ZERO"), + (0x676, "NEGATIVE"), + (0x677, "POSITIVE"), + (0x678, "FRAGMENT"), + (0x679, "VERTEX"), + (0x67A, "BGRA"), + (0x67B, "BGRA_PACKED"), + (0x67C, "BGR_PACKED"), + (0x67D, "COMPRESSED"), + (0x67E, "COMPRESSED_ALPHA"), + (0x67F, "BYTES_4"), + (0x680, "FLOAT_1"), + (0x681, "FLOAT_2"), + (0x682, "FLOAT_3"), + (0x683, "FLOAT_4"), + (0x684, "e_unknownShape"), + (0x685, "e_circleShape"), + (0x686, "e_polygonShape"), + (0x687, "e_edgeShape"), + (0x688, "e_shapeTypeCount"), + (0x689, "CUBIC_CURVE_TO"), + (0x690, "BOTTOM"), + (0x691, "BOTTOM_LEFT"), + (0x692, "BOTTOM_RIGHT"), + (0x693, "TOP"), + (0x694, "TOP_LEFT"), + (0x695, "TOP_RIGHT"), + (0x696, "EXACT_FIT"), + (0x697, "NO_BORDER"), + (0x698, "NO_SCALE"), + (0x699, "SHOW_ALL"), + (0x6A0, "CENTER"), + (0x6A1, "JUSTIFY"), + (0x6A2, "dmy06a2"), + (0x6A3, "dmy06a3"), + (0x6A4, "dmy06a4"), + (0x6A5, "dmy06a5"), + (0x6A6, "dmy06a6"), + (0x6A7, "dmy06a7"), + (0x6A8, "dmy06a8"), + (0x6A9, "DYNAMIC"), + (0x6AA, "INPUT"), + (0x6AB, "ADVANCED"), + (0x6AC, "PIXEL"), + (0x6AD, "SUBPIXEL"), + (0x6B0, "BINARY"), + (0x6B1, "TEXT"), + (0x6B2, "VARIABLES"), + (0x6C0, "FULL"), + (0x6C1, "INNER"), + (0x6C2, "OUTER"), + (0x6C3, "RED"), + (0x6C4, "GREEN"), + (0x6C5, "BLUE"), + (0x6C6, "CLAMP"), + (0x6C7, "COLOR"), + (0x6C8, "IGNORE"), + (0x6C9, "WRAP"), + (0x6D0, "DELETE"), + (0x6D1, "GET"), + (0x6D2, "HEAD"), + (0x6D3, "OPTIONS"), + (0x6D4, "POST"), + (0x6D5, "PUT"), + (0x6D6, "BIG_ENDIAN"), + (0x6D7, "LITTLE_ENDIAN"), + (0x6D8, "AXIS_ANGLE"), + (0x6D9, "EULER_ANGLES"), + (0x6DA, "QUATERNION"), + (0x6F0, "NUMBER_0"), + (0x6F1, "NUMBER_1"), + (0x6F2, "NUMBER_2"), + (0x6F3, "NUMBER_3"), + (0x6F4, "NUMBER_4"), + (0x6F5, "NUMBER_5"), + (0x6F6, "NUMBER_6"), + (0x6F7, "NUMBER_7"), + (0x6F8, "NUMBER_8"), + (0x6F9, "NUMBER_9"), # Shape constants? - (0x700, 'redMultiplier'), - (0x701, 'greenMultiplier'), - (0x702, 'blueMultiplier'), - (0x703, 'alphaMultiplier'), - (0x704, 'redOffset'), - (0x705, 'greenOffset'), - (0x706, 'blueOffset'), - (0x707, 'alphaOffset'), - (0x708, 'rgb'), - (0x709, 'bottom'), - (0x70a, 'bottomRight'), - (0x70b, 'top'), - (0x70c, 'topLeft'), - (0x70d, 'LOW'), - (0x70e, 'MEDIUM'), - (0x70f, 'HIGH'), - (0x710, 'BEST'), - (0x711, 'name'), - (0x712, 'message'), - (0x713, 'bytesLoaded'), - (0x714, 'bytesTotal'), - (0x715, 'once'), - (0x716, 'MAX_VALUE'), - (0x717, 'MIN_VALUE'), - (0x718, 'NEGATIVE_INFINITY'), - (0x719, 'POSITIVE_INFINITY'), - (0x71a, 'stageWidth'), - (0x71b, 'stageHeight'), - (0x71c, 'frame'), - (0x71d, 'numFrames'), - (0x71e, 'labels'), - (0x71f, 'currentTarget'), - (0x720, 'void'), - (0x721, 'fixed'), - (0x722, 'rawData'), - (0x723, 'type'), - (0x724, 'focalLength'), - (0x725, 'fieldOfView'), - (0x726, 'projectionCenter'), - (0x727, 'E'), - (0x728, 'LN10'), - (0x729, 'LN2'), - (0x72a, 'LOG10E'), - (0x72b, 'LOG2E'), - (0x72c, 'PI'), - (0x72d, 'SQRT1_2'), - (0x72e, 'SQRT2'), - (0x72f, 'stageX'), - (0x730, 'stageY'), - (0x731, 'localX'), - (0x732, 'localY'), - (0x733, 'tintColor'), - (0x734, 'tintMultiplier'), - (0x735, 'brightness'), - (0x736, 'delay'), - (0x737, 'repeatCount'), - (0x738, 'currentCount'), - (0x739, 'running'), - (0x73a, 'charCode'), - (0x73b, 'keyCode'), - (0x73c, 'altKey'), - (0x73d, 'ctrlKey'), - (0x73e, 'shiftKey'), - (0x73f, 'useCodePage'), - (0x740, 'contentLoaderInfo'), - (0x741, 'loaderURL'), - (0x742, 'loader'), - (0x743, 'fullYear'), - (0x744, 'fullYearUTC'), - (0x745, 'month'), - (0x746, 'monthUTC'), - (0x747, 'date'), - (0x748, 'dateUTC'), - (0x749, 'day'), - (0x74a, 'dayUTC'), - (0x74b, 'hours'), - (0x74c, 'hoursUTC'), - (0x74d, 'minutes'), - (0x74e, 'minutesUTC'), - (0x74f, 'seconds'), - (0x750, 'secondsUTC'), - (0x751, 'milliseconds'), - (0x752, 'millisecondsUTC'), - (0x753, 'timezoneOffset'), - (0x754, 'time'), - (0x755, 'joints'), - (0x756, 'fill'), - (0x757, 'colors'), - (0x758, 'commands'), - (0x759, 'miterLimit'), - (0x75a, 'alphas'), - (0x75b, 'ratios'), - (0x75c, 'bitmapData'), - (0x75d, 'vertices'), - (0x75e, 'uvtData'), - (0x75f, 'indices'), - (0x760, 'parameters'), - (0x761, 'frameRate'), - (0x762, 'low'), - (0x763, 'medium'), - (0x764, 'high'), - (0x765, 'best'), - (0x766, 'index'), - (0x767, 'blank'), - (0x768, 'is3D'), - (0x769, 'scaleMode'), - (0x76a, 'frameEvent'), - (0x76b, 'motion'), - (0x76c, 'transformationPoint'), - (0x76d, 'transformationPointZ'), - (0x76e, 'sceneName'), - (0x76f, 'targetParent'), - (0x770, 'targetName'), - (0x771, 'initialPosition'), - (0x772, 'children'), - (0x773, 'child'), - (0x774, 'playerType'), - (0x775, 'os'), - (0x776, 'capabilities'), - (0x777, 'transition'), - (0x778, 'transitionParameters'), - (0x779, 'useFrames'), - (0x77a, '_color_redMultiplier'), - (0x77b, '_color_redOffset'), - (0x77c, '_color_greenMultiplier'), - (0x77d, '_color_greenOffset'), - (0x77e, '_color_blueMultiplier'), - (0x77f, '_color_blueOffset'), - (0x780, '_color_alphaMultiplier'), - (0x781, '_color_alphaOffset'), - (0x782, '_color'), - (0x783, 'soundTransform'), - (0x784, 'volume'), - (0x785, 'uri'), - (0x786, 'prefix'), - (0x787, 'content'), - (0x788, 'contentType'), - (0x789, 'swfVersion'), - (0x78a, 'input'), - (0x78b, 'source'), - (0x78c, 'lastIndex'), - (0x78d, 'stageFocusRect'), - (0x78e, 'currentDomain'), - (0x78f, 'applicationDomain'), - (0x790, 'parentDomain'), - (0x791, 'dataFormat'), - (0x792, 'digest'), - (0x793, 'errorID'), - (0x794, 'available'), - (0x795, 'client'), - (0x796, 'actionScriptVersion'), - (0x797, 'ACTIONSCRIPT2'), - (0x798, 'ACTIONSCRIPT3'), - (0x799, 'delta'), - (0x79a, 'cursor'), - (0x79b, 'buttonDown'), - (0x79c, 'motionArray'), - (0x79d, 'spanStart'), - (0x79e, 'spanEnd'), - (0x79f, 'placeholderName'), - (0x7a0, 'instanceFactoryClass'), - (0x7a1, 'instance'), - (0x7a2, 'method'), - (0x7a3, 'requestHeaders'), - (0x7a4, 'info'), - (0x7a5, 'dotall'), - (0x7a6, 'extended'), - (0x7a7, 'global'), - (0x7a8, 'ignoreCase'), - (0x7a9, 'X_AXIS'), - (0x7aa, 'Y_AXIS'), - (0x7ab, 'Z_AXIS'), - (0x7ac, 'lengthSquared'), - (0x7ad, 'dps'), - (0x7ae, 'from0'), - (0x7af, '_text'), - (0x7b0, '_text_sound'), - (0x7b1, 'blurX'), - (0x7b2, 'blurY'), - (0x7b3, 'step'), - (0x7b4, 'spc'), - (0x7b5, 'stage3Ds'), - (0x7b6, 'context3D'), - (0x7b7, 'version'), - (0x7b8, 'accessibilityProperties'), - (0x7b9, 'description'), - (0x7ba, 'adjustColorBrightness'), - (0x7bb, 'adjustColorContrast'), - (0x7bc, 'adjustColorSaturation'), - (0x7bd, 'adjustColorHue'), - (0x7be, 'strength'), - (0x7bf, 'angle'), - (0x7c0, 'knockout'), - (0x7c1, 'hideObject'), - (0x7c2, 'manufacturer'), - (0x7c3, 'smoothing'), - (0x7c4, 'rect'), - (0x7c5, 'transparent'), - (0x7c6, 'mapBitmap'), - (0x7c7, 'mapPoint'), - (0x7c8, 'componentX'), - (0x7c9, 'componentY'), - (0x7ca, 'mode'), - (0x7cb, 'highlightColor'), - (0x7cc, 'highlightAlpha'), - (0x7cd, 'shadowColor'), - (0x7ce, 'shadowAlpha'), - (0x7cf, 'endian'), - (0x7d0, '_scale'), - (0x7d1, 'transitionParams'), - (0x7d2, '_text_color'), - (0x7d3, '_text_color_r'), - (0x7d4, '_text_color_g'), - (0x7d5, '_text_color_b'), - (0x7d6, 'cancelable'), - (0x7d7, 'col1'), - (0x7d8, 'col2'), - (0x7d9, 'localCenter'), - (0x7da, 't0'), - (0x7db, 'a0'), - (0x7dc, 'c0'), - (0x7dd, 'lowerBound'), - (0x7de, 'upperBound'), - (0x7df, 'col3'), - (0x7e0, 'mass'), - (0x7e1, 'm_vertices'), - (0x7e2, 'm_vertexCount'), - (0x7e3, 'm_radius'), - (0x7e4, 'm_p'), - (0x7e5, 'm_normals'), - (0x7e6, 'm_type'), - (0x7e7, 'm_centroid'), - (0x7e8, 'proxyA'), - (0x7e9, 'proxyB'), - (0x7ea, 'transformA'), - (0x7eb, 'transformB'), - (0x7ec, 'useRadii'), - (0x7ed, 'pointA'), - (0x7ee, 'pointB'), - (0x7ef, 'iterations'), - (0x7f0, 'count'), - (0x7f1, 'metric'), - (0x7f2, 'indexA'), - (0x7f3, 'indexB'), - (0x7f4, 'determinant'), - + (0x700, "redMultiplier"), + (0x701, "greenMultiplier"), + (0x702, "blueMultiplier"), + (0x703, "alphaMultiplier"), + (0x704, "redOffset"), + (0x705, "greenOffset"), + (0x706, "blueOffset"), + (0x707, "alphaOffset"), + (0x708, "rgb"), + (0x709, "bottom"), + (0x70A, "bottomRight"), + (0x70B, "top"), + (0x70C, "topLeft"), + (0x70D, "LOW"), + (0x70E, "MEDIUM"), + (0x70F, "HIGH"), + (0x710, "BEST"), + (0x711, "name"), + (0x712, "message"), + (0x713, "bytesLoaded"), + (0x714, "bytesTotal"), + (0x715, "once"), + (0x716, "MAX_VALUE"), + (0x717, "MIN_VALUE"), + (0x718, "NEGATIVE_INFINITY"), + (0x719, "POSITIVE_INFINITY"), + (0x71A, "stageWidth"), + (0x71B, "stageHeight"), + (0x71C, "frame"), + (0x71D, "numFrames"), + (0x71E, "labels"), + (0x71F, "currentTarget"), + (0x720, "void"), + (0x721, "fixed"), + (0x722, "rawData"), + (0x723, "type"), + (0x724, "focalLength"), + (0x725, "fieldOfView"), + (0x726, "projectionCenter"), + (0x727, "E"), + (0x728, "LN10"), + (0x729, "LN2"), + (0x72A, "LOG10E"), + (0x72B, "LOG2E"), + (0x72C, "PI"), + (0x72D, "SQRT1_2"), + (0x72E, "SQRT2"), + (0x72F, "stageX"), + (0x730, "stageY"), + (0x731, "localX"), + (0x732, "localY"), + (0x733, "tintColor"), + (0x734, "tintMultiplier"), + (0x735, "brightness"), + (0x736, "delay"), + (0x737, "repeatCount"), + (0x738, "currentCount"), + (0x739, "running"), + (0x73A, "charCode"), + (0x73B, "keyCode"), + (0x73C, "altKey"), + (0x73D, "ctrlKey"), + (0x73E, "shiftKey"), + (0x73F, "useCodePage"), + (0x740, "contentLoaderInfo"), + (0x741, "loaderURL"), + (0x742, "loader"), + (0x743, "fullYear"), + (0x744, "fullYearUTC"), + (0x745, "month"), + (0x746, "monthUTC"), + (0x747, "date"), + (0x748, "dateUTC"), + (0x749, "day"), + (0x74A, "dayUTC"), + (0x74B, "hours"), + (0x74C, "hoursUTC"), + (0x74D, "minutes"), + (0x74E, "minutesUTC"), + (0x74F, "seconds"), + (0x750, "secondsUTC"), + (0x751, "milliseconds"), + (0x752, "millisecondsUTC"), + (0x753, "timezoneOffset"), + (0x754, "time"), + (0x755, "joints"), + (0x756, "fill"), + (0x757, "colors"), + (0x758, "commands"), + (0x759, "miterLimit"), + (0x75A, "alphas"), + (0x75B, "ratios"), + (0x75C, "bitmapData"), + (0x75D, "vertices"), + (0x75E, "uvtData"), + (0x75F, "indices"), + (0x760, "parameters"), + (0x761, "frameRate"), + (0x762, "low"), + (0x763, "medium"), + (0x764, "high"), + (0x765, "best"), + (0x766, "index"), + (0x767, "blank"), + (0x768, "is3D"), + (0x769, "scaleMode"), + (0x76A, "frameEvent"), + (0x76B, "motion"), + (0x76C, "transformationPoint"), + (0x76D, "transformationPointZ"), + (0x76E, "sceneName"), + (0x76F, "targetParent"), + (0x770, "targetName"), + (0x771, "initialPosition"), + (0x772, "children"), + (0x773, "child"), + (0x774, "playerType"), + (0x775, "os"), + (0x776, "capabilities"), + (0x777, "transition"), + (0x778, "transitionParameters"), + (0x779, "useFrames"), + (0x77A, "_color_redMultiplier"), + (0x77B, "_color_redOffset"), + (0x77C, "_color_greenMultiplier"), + (0x77D, "_color_greenOffset"), + (0x77E, "_color_blueMultiplier"), + (0x77F, "_color_blueOffset"), + (0x780, "_color_alphaMultiplier"), + (0x781, "_color_alphaOffset"), + (0x782, "_color"), + (0x783, "soundTransform"), + (0x784, "volume"), + (0x785, "uri"), + (0x786, "prefix"), + (0x787, "content"), + (0x788, "contentType"), + (0x789, "swfVersion"), + (0x78A, "input"), + (0x78B, "source"), + (0x78C, "lastIndex"), + (0x78D, "stageFocusRect"), + (0x78E, "currentDomain"), + (0x78F, "applicationDomain"), + (0x790, "parentDomain"), + (0x791, "dataFormat"), + (0x792, "digest"), + (0x793, "errorID"), + (0x794, "available"), + (0x795, "client"), + (0x796, "actionScriptVersion"), + (0x797, "ACTIONSCRIPT2"), + (0x798, "ACTIONSCRIPT3"), + (0x799, "delta"), + (0x79A, "cursor"), + (0x79B, "buttonDown"), + (0x79C, "motionArray"), + (0x79D, "spanStart"), + (0x79E, "spanEnd"), + (0x79F, "placeholderName"), + (0x7A0, "instanceFactoryClass"), + (0x7A1, "instance"), + (0x7A2, "method"), + (0x7A3, "requestHeaders"), + (0x7A4, "info"), + (0x7A5, "dotall"), + (0x7A6, "extended"), + (0x7A7, "global"), + (0x7A8, "ignoreCase"), + (0x7A9, "X_AXIS"), + (0x7AA, "Y_AXIS"), + (0x7AB, "Z_AXIS"), + (0x7AC, "lengthSquared"), + (0x7AD, "dps"), + (0x7AE, "from0"), + (0x7AF, "_text"), + (0x7B0, "_text_sound"), + (0x7B1, "blurX"), + (0x7B2, "blurY"), + (0x7B3, "step"), + (0x7B4, "spc"), + (0x7B5, "stage3Ds"), + (0x7B6, "context3D"), + (0x7B7, "version"), + (0x7B8, "accessibilityProperties"), + (0x7B9, "description"), + (0x7BA, "adjustColorBrightness"), + (0x7BB, "adjustColorContrast"), + (0x7BC, "adjustColorSaturation"), + (0x7BD, "adjustColorHue"), + (0x7BE, "strength"), + (0x7BF, "angle"), + (0x7C0, "knockout"), + (0x7C1, "hideObject"), + (0x7C2, "manufacturer"), + (0x7C3, "smoothing"), + (0x7C4, "rect"), + (0x7C5, "transparent"), + (0x7C6, "mapBitmap"), + (0x7C7, "mapPoint"), + (0x7C8, "componentX"), + (0x7C9, "componentY"), + (0x7CA, "mode"), + (0x7CB, "highlightColor"), + (0x7CC, "highlightAlpha"), + (0x7CD, "shadowColor"), + (0x7CE, "shadowAlpha"), + (0x7CF, "endian"), + (0x7D0, "_scale"), + (0x7D1, "transitionParams"), + (0x7D2, "_text_color"), + (0x7D3, "_text_color_r"), + (0x7D4, "_text_color_g"), + (0x7D5, "_text_color_b"), + (0x7D6, "cancelable"), + (0x7D7, "col1"), + (0x7D8, "col2"), + (0x7D9, "localCenter"), + (0x7DA, "t0"), + (0x7DB, "a0"), + (0x7DC, "c0"), + (0x7DD, "lowerBound"), + (0x7DE, "upperBound"), + (0x7DF, "col3"), + (0x7E0, "mass"), + (0x7E1, "m_vertices"), + (0x7E2, "m_vertexCount"), + (0x7E3, "m_radius"), + (0x7E4, "m_p"), + (0x7E5, "m_normals"), + (0x7E6, "m_type"), + (0x7E7, "m_centroid"), + (0x7E8, "proxyA"), + (0x7E9, "proxyB"), + (0x7EA, "transformA"), + (0x7EB, "transformB"), + (0x7EC, "useRadii"), + (0x7ED, "pointA"), + (0x7EE, "pointB"), + (0x7EF, "iterations"), + (0x7F0, "count"), + (0x7F1, "metric"), + (0x7F2, "indexA"), + (0x7F3, "indexB"), + (0x7F4, "determinant"), # Some more system functions? - (0x800, 'sound_play'), - (0x801, 'sound_stop'), - (0x802, 'sound_stop_all'), - (0x803, 'set_top_mc'), - (0x804, 'set_controlled_XML'), - (0x805, 'set_config_XML'), - (0x806, 'set_data_top'), - (0x807, 'attach_event'), - (0x808, 'detach_event'), - (0x809, 'set'), - (0x80a, 'get'), - (0x80b, 'ready'), - (0x80c, 'afp_available'), - (0x80d, 'controller_available'), - (0x80e, 'push_state'), - (0x80f, 'pop_state'), - (0x810, 'afp_verbose_action'), - (0x811, 'sound_fadeout'), - (0x812, 'sound_fadeout_all'), - (0x813, 'deepPlay'), - (0x814, 'deepStop'), - (0x815, 'deepGotoAndPlay'), - (0x816, 'deepGotoAndStop'), - (0x817, 'detach_event_all'), - (0x818, 'detach_event_id'), - (0x819, 'detach_event_obj'), - (0x81a, 'get_version'), - (0x81b, 'get_version_str'), - (0x81c, 'addAween'), - (0x81d, 'addCaller'), - (0x81e, 'registerSpecialProperty'), - (0x81f, 'registerSpecialPropertySplitter'), - (0x820, 'registerTransition'), - (0x821, 'removeAllAweens'), - (0x822, 'removeAweens'), - (0x823, 'use_konami_lib'), - (0x824, 'sound_volume'), - (0x825, 'sound_volume_all'), - (0x826, 'afp_verbose_script'), - (0x827, 'afp_node_check_value'), - (0x828, 'afp_node_check'), - (0x829, 'get_version_full_str'), - (0x82a, 'set_debug'), - (0x82b, 'num2str_comma'), - (0x82c, 'areacode2str'), - (0x82d, 'num2str_period'), - (0x82e, 'get_columns'), - (0x82f, 'num2mc'), - (0x830, 'warning'), - (0x831, 'fatal'), - (0x832, 'aep_set_frame_control'), - (0x833, 'aep_set_rect_mask'), - (0x834, 'load_movie'), - (0x835, 'get_movie_clip'), - (0x836, 'aep_set_set_frame'), - (0x837, 'deep_goto_play_label'), - (0x838, 'deep_goto_stop_label'), - (0x839, 'goto_play_label'), - (0x83a, 'goto_stop_label'), - (0x83b, 'goto_play'), - (0x83c, 'goto_stop'), - (0x83d, 'set_text'), - (0x83e, 'get_text_data'), - (0x83f, 'attach_movie'), - (0x840, 'attach_bitmap'), - (0x841, 'create_movie_clip'), - (0x842, 'set_text_scroll'), - (0x843, 'set_stage'), - (0x880, 'flash.system'), - (0x881, 'flash.display'), - (0x882, 'flash.text'), - (0x883, 'fl.motion'), - (0x884, 'flash.net'), - (0x885, 'flash.ui'), - (0x886, 'flash.geom'), - (0x887, 'flash.filters'), - (0x888, 'flash.events'), - (0x889, 'flash.utils'), - (0x88a, 'flash.media'), - (0x88b, 'flash.external'), - (0x88c, 'flash.errors'), - (0x88d, 'flash.xml'), - (0x88e, 'flash.display3D'), - (0x88f, 'flash.accessibility'), - (0x890, 'flash.display3D.textures'), - (0x891, 'Box2D.Common.Math'), - (0x892, 'Box2D.Collision'), - (0x893, 'Box2D.Collision.Shapes'), - + (0x800, "sound_play"), + (0x801, "sound_stop"), + (0x802, "sound_stop_all"), + (0x803, "set_top_mc"), + (0x804, "set_controlled_XML"), + (0x805, "set_config_XML"), + (0x806, "set_data_top"), + (0x807, "attach_event"), + (0x808, "detach_event"), + (0x809, "set"), + (0x80A, "get"), + (0x80B, "ready"), + (0x80C, "afp_available"), + (0x80D, "controller_available"), + (0x80E, "push_state"), + (0x80F, "pop_state"), + (0x810, "afp_verbose_action"), + (0x811, "sound_fadeout"), + (0x812, "sound_fadeout_all"), + (0x813, "deepPlay"), + (0x814, "deepStop"), + (0x815, "deepGotoAndPlay"), + (0x816, "deepGotoAndStop"), + (0x817, "detach_event_all"), + (0x818, "detach_event_id"), + (0x819, "detach_event_obj"), + (0x81A, "get_version"), + (0x81B, "get_version_str"), + (0x81C, "addAween"), + (0x81D, "addCaller"), + (0x81E, "registerSpecialProperty"), + (0x81F, "registerSpecialPropertySplitter"), + (0x820, "registerTransition"), + (0x821, "removeAllAweens"), + (0x822, "removeAweens"), + (0x823, "use_konami_lib"), + (0x824, "sound_volume"), + (0x825, "sound_volume_all"), + (0x826, "afp_verbose_script"), + (0x827, "afp_node_check_value"), + (0x828, "afp_node_check"), + (0x829, "get_version_full_str"), + (0x82A, "set_debug"), + (0x82B, "num2str_comma"), + (0x82C, "areacode2str"), + (0x82D, "num2str_period"), + (0x82E, "get_columns"), + (0x82F, "num2mc"), + (0x830, "warning"), + (0x831, "fatal"), + (0x832, "aep_set_frame_control"), + (0x833, "aep_set_rect_mask"), + (0x834, "load_movie"), + (0x835, "get_movie_clip"), + (0x836, "aep_set_set_frame"), + (0x837, "deep_goto_play_label"), + (0x838, "deep_goto_stop_label"), + (0x839, "goto_play_label"), + (0x83A, "goto_stop_label"), + (0x83B, "goto_play"), + (0x83C, "goto_stop"), + (0x83D, "set_text"), + (0x83E, "get_text_data"), + (0x83F, "attach_movie"), + (0x840, "attach_bitmap"), + (0x841, "create_movie_clip"), + (0x842, "set_text_scroll"), + (0x843, "set_stage"), + (0x880, "flash.system"), + (0x881, "flash.display"), + (0x882, "flash.text"), + (0x883, "fl.motion"), + (0x884, "flash.net"), + (0x885, "flash.ui"), + (0x886, "flash.geom"), + (0x887, "flash.filters"), + (0x888, "flash.events"), + (0x889, "flash.utils"), + (0x88A, "flash.media"), + (0x88B, "flash.external"), + (0x88C, "flash.errors"), + (0x88D, "flash.xml"), + (0x88E, "flash.display3D"), + (0x88F, "flash.accessibility"), + (0x890, "flash.display3D.textures"), + (0x891, "Box2D.Common.Math"), + (0x892, "Box2D.Collision"), + (0x893, "Box2D.Collision.Shapes"), # Lots of generic function names. - (0x900, 'setDate'), - (0x901, 'setUTCDate'), - (0x902, 'setFullYear'), - (0x903, 'setUTCFullYear'), - (0x904, 'setHours'), - (0x905, 'setUTCHours'), - (0x906, 'setMilliseconds'), - (0x907, 'setUTCMilliseconds'), - (0x908, 'setMinutes'), - (0x909, 'setUTCMinutes'), - (0x90a, 'setMonth'), - (0x90b, 'setUTCMonth'), - (0x90c, 'setSeconds'), - (0x90d, 'setUTCSeconds'), - (0x90e, 'setTime'), - (0x90f, 'setYear'), - (0x910, 'addEventListener'), - (0x911, 'removeEventListener'), - (0x912, 'match'), - (0x913, 'replace'), - (0x914, 'search'), - (0x915, 'append'), - (0x916, 'appendRotation'), - (0x917, 'appendScale'), - (0x918, 'appendTranslation'), - (0x919, 'decompose'), - (0x91a, 'deltaTransformVector'), - (0x91b, 'identity'), - (0x91c, 'interpolateTo'), - (0x91d, 'pointAt'), - (0x91e, 'prepend'), - (0x91f, 'prependRotation'), - (0x920, 'prependScale'), - (0x921, 'prependTranslation'), - (0x922, 'recompose'), - (0x923, 'transformVector'), - (0x924, 'transformVectors'), - (0x925, 'transpose'), - (0x926, 'dispatchEvent'), - (0x927, 'toMatrix3D'), - (0x928, 'appendText'), - (0x929, 'getLineText'), - (0x92a, 'replaceText'), - (0x92b, 'propertyIsEnumerable'), - (0x92c, 'setPropertyIsEnumerable'), - (0x92d, 'drawCircle'), - (0x92e, 'drawEllipse'), - (0x92f, 'drawGraphicsData'), - (0x930, 'drawPath'), - (0x931, 'drawRect'), - (0x932, 'drawRoundRect'), - (0x933, 'drawTriangles'), - (0x934, 'copyFrom'), - (0x935, 'addCallback'), - (0x936, 'overrideTargetTransform'), - (0x937, 'addPropertyArray'), - (0x938, 'getCurrentKeyframe'), - (0x939, 'getMatrix3D'), - (0x93a, 'getColorTransform'), - (0x93b, 'getFilters'), - (0x93c, 'getValue'), - (0x93d, 'hasEventListener'), - (0x93e, 'registerParentFrameHandler'), - (0x93f, 'processCurrentFrame'), - (0x940, 'normalize'), - (0x941, 'elements'), - (0x942, 'toXMLString'), - (0x943, 'attribute'), - (0x944, 'localName'), - (0x945, 'nodeKind'), - (0x946, 'exec'), - (0x947, 'toLocaleString'), - (0x948, 'invalidate'), - (0x949, 'getDefinition'), - (0x94a, 'hasDefinition'), - (0x94b, 'descendants'), - (0x94c, 'loadPolicyFile'), - (0x94d, 'reset'), - (0x94e, 'callProperty'), - (0x94f, 'getProperty'), - (0x950, 'setProperty'), - (0x951, 'willTrigger'), - (0x952, 'send'), - (0x953, 'addTargetInfo'), - (0x954, 'drawRoundRectComplex'), - (0x955, 'forEach'), - (0x956, 'filter'), - (0x957, 'every'), - (0x958, 'some'), - (0x959, 'map'), - (0x95a, 'test'), - (0x95b, 'toDateString'), - (0x95c, 'toLocaleDateString'), - (0x95d, 'toLocaleTimeString'), - (0x95e, 'toTimeString'), - (0x95f, 'toUTCString'), - (0x960, 'parse'), - (0x961, 'project'), - (0x962, 'nearEquals'), - (0x963, 'scaleBy'), - (0x964, 'negate'), - (0x965, 'incrementBy'), - (0x966, 'decrementBy'), - (0x967, 'dotProduct'), - (0x968, 'crossProduct'), - (0x969, 'angleBetween'), - (0x96a, 'decode'), - (0x96b, 'copyColumnFrom'), - (0x96c, 'copyColumnTo'), - (0x96d, 'copyRawDataFrom'), - (0x96e, 'copyRawDataTo'), - (0x96f, 'copyRowFrom'), - (0x970, 'copyRowTo'), - (0x971, 'copyToMatrix3D'), - (0x972, 'requestContext3D'), - (0x973, 'initFilters'), - (0x974, 'addFilterPropertyArray'), - (0x975, 'setTo'), - (0x976, 'createBox'), - (0x977, 'deltaTransformPoint'), - (0x978, 'writeByte'), - (0x979, 'writeInt'), - (0x97a, 'readByte'), - (0x97b, 'writeBoolean'), - (0x97c, 'writeDouble'), - (0x97d, 'readBoolean'), - (0x97e, 'readDouble'), - (0x97f, 'writeUnsignedInt'), - (0x980, 'getVector'), - (0x981, 'setVector'), - (0x982, 'unload'), - (0x983, 'unloadAndStop'), - (0x984, 'toExponential'), - (0x985, 'toFixed'), - (0x986, 'toPrecision'), - (0x987, 'getNewTextFormat'), - (0x988, 'SetV'), - (0x989, 'Set'), - (0x98a, 'SetZero'), - (0x98b, 'Make'), - (0x98c, 'Copy'), - (0x98d, 'Length'), - (0x98e, 'LengthSquared'), - (0x98f, 'Normalize'), - (0x990, 'Multiply'), - (0x991, 'GetNegative'), - (0x992, 'NegativeSelf'), - (0x993, 'Clamp'), - (0x994, 'MulX'), - (0x995, 'Dot'), - (0x996, 'SubtractVV'), - (0x997, 'CrossVF'), - (0x998, 'CrossFV'), - (0x999, 'MulTMV'), - (0x99a, 'CrossVV'), - (0x99b, 'Max'), - (0x99c, 'MulMV'), - (0x99d, 'Abs'), - (0x99e, 'MulXT'), - (0x99f, 'SetM'), - (0x9a0, 'AddM'), - (0x9a1, 'Solve'), - (0x9a2, 'Add'), - (0x9a3, 'Min'), - (0x9a4, 'FromAngle'), - (0x9a5, 'GetTransform'), - (0x9a6, 'Advance'), - (0x9a7, 'GetInverse'), - (0x9a8, 'Combine'), - (0x9a9, 'Contains'), - (0x9aa, 'TestOverlap'), - (0x9ab, 'GetCenter'), - (0x9ac, 'Solve22'), - (0x9ad, 'Solve33'), - (0x9ae, 'SetLocalPosition'), - (0x9af, 'ComputeAABB'), - (0x9b0, 'ComputeMass'), - (0x9b1, 'GetType'), - (0x9b2, 'SetAsArray'), - (0x9b3, 'GetVertex'), - (0x9b4, 'GetSupport'), - (0x9b5, 'GetSupportVertex'), - (0x9b6, 'GetVertexCount'), - (0x9b7, 'GetVertices'), - (0x9b8, 'ReadCache'), - (0x9b9, 'GetClosestPoint'), - (0x9ba, 'GetSearchDirection'), - (0x9bb, 'Solve2'), - (0x9bc, 'Solve3'), - (0x9bd, 'GetWitnessPoints'), - (0x9be, 'WriteCache'), - (0x9bf, 'Distance'), - (0x9c0, 'cubicCurveTo'), - (0x9c1, 'wideMoveTo'), - (0x9c2, 'wideLineTo'), - (0x9c3, 'insertAt'), - (0x9c4, 'removeAt'), - + (0x900, "setDate"), + (0x901, "setUTCDate"), + (0x902, "setFullYear"), + (0x903, "setUTCFullYear"), + (0x904, "setHours"), + (0x905, "setUTCHours"), + (0x906, "setMilliseconds"), + (0x907, "setUTCMilliseconds"), + (0x908, "setMinutes"), + (0x909, "setUTCMinutes"), + (0x90A, "setMonth"), + (0x90B, "setUTCMonth"), + (0x90C, "setSeconds"), + (0x90D, "setUTCSeconds"), + (0x90E, "setTime"), + (0x90F, "setYear"), + (0x910, "addEventListener"), + (0x911, "removeEventListener"), + (0x912, "match"), + (0x913, "replace"), + (0x914, "search"), + (0x915, "append"), + (0x916, "appendRotation"), + (0x917, "appendScale"), + (0x918, "appendTranslation"), + (0x919, "decompose"), + (0x91A, "deltaTransformVector"), + (0x91B, "identity"), + (0x91C, "interpolateTo"), + (0x91D, "pointAt"), + (0x91E, "prepend"), + (0x91F, "prependRotation"), + (0x920, "prependScale"), + (0x921, "prependTranslation"), + (0x922, "recompose"), + (0x923, "transformVector"), + (0x924, "transformVectors"), + (0x925, "transpose"), + (0x926, "dispatchEvent"), + (0x927, "toMatrix3D"), + (0x928, "appendText"), + (0x929, "getLineText"), + (0x92A, "replaceText"), + (0x92B, "propertyIsEnumerable"), + (0x92C, "setPropertyIsEnumerable"), + (0x92D, "drawCircle"), + (0x92E, "drawEllipse"), + (0x92F, "drawGraphicsData"), + (0x930, "drawPath"), + (0x931, "drawRect"), + (0x932, "drawRoundRect"), + (0x933, "drawTriangles"), + (0x934, "copyFrom"), + (0x935, "addCallback"), + (0x936, "overrideTargetTransform"), + (0x937, "addPropertyArray"), + (0x938, "getCurrentKeyframe"), + (0x939, "getMatrix3D"), + (0x93A, "getColorTransform"), + (0x93B, "getFilters"), + (0x93C, "getValue"), + (0x93D, "hasEventListener"), + (0x93E, "registerParentFrameHandler"), + (0x93F, "processCurrentFrame"), + (0x940, "normalize"), + (0x941, "elements"), + (0x942, "toXMLString"), + (0x943, "attribute"), + (0x944, "localName"), + (0x945, "nodeKind"), + (0x946, "exec"), + (0x947, "toLocaleString"), + (0x948, "invalidate"), + (0x949, "getDefinition"), + (0x94A, "hasDefinition"), + (0x94B, "descendants"), + (0x94C, "loadPolicyFile"), + (0x94D, "reset"), + (0x94E, "callProperty"), + (0x94F, "getProperty"), + (0x950, "setProperty"), + (0x951, "willTrigger"), + (0x952, "send"), + (0x953, "addTargetInfo"), + (0x954, "drawRoundRectComplex"), + (0x955, "forEach"), + (0x956, "filter"), + (0x957, "every"), + (0x958, "some"), + (0x959, "map"), + (0x95A, "test"), + (0x95B, "toDateString"), + (0x95C, "toLocaleDateString"), + (0x95D, "toLocaleTimeString"), + (0x95E, "toTimeString"), + (0x95F, "toUTCString"), + (0x960, "parse"), + (0x961, "project"), + (0x962, "nearEquals"), + (0x963, "scaleBy"), + (0x964, "negate"), + (0x965, "incrementBy"), + (0x966, "decrementBy"), + (0x967, "dotProduct"), + (0x968, "crossProduct"), + (0x969, "angleBetween"), + (0x96A, "decode"), + (0x96B, "copyColumnFrom"), + (0x96C, "copyColumnTo"), + (0x96D, "copyRawDataFrom"), + (0x96E, "copyRawDataTo"), + (0x96F, "copyRowFrom"), + (0x970, "copyRowTo"), + (0x971, "copyToMatrix3D"), + (0x972, "requestContext3D"), + (0x973, "initFilters"), + (0x974, "addFilterPropertyArray"), + (0x975, "setTo"), + (0x976, "createBox"), + (0x977, "deltaTransformPoint"), + (0x978, "writeByte"), + (0x979, "writeInt"), + (0x97A, "readByte"), + (0x97B, "writeBoolean"), + (0x97C, "writeDouble"), + (0x97D, "readBoolean"), + (0x97E, "readDouble"), + (0x97F, "writeUnsignedInt"), + (0x980, "getVector"), + (0x981, "setVector"), + (0x982, "unload"), + (0x983, "unloadAndStop"), + (0x984, "toExponential"), + (0x985, "toFixed"), + (0x986, "toPrecision"), + (0x987, "getNewTextFormat"), + (0x988, "SetV"), + (0x989, "Set"), + (0x98A, "SetZero"), + (0x98B, "Make"), + (0x98C, "Copy"), + (0x98D, "Length"), + (0x98E, "LengthSquared"), + (0x98F, "Normalize"), + (0x990, "Multiply"), + (0x991, "GetNegative"), + (0x992, "NegativeSelf"), + (0x993, "Clamp"), + (0x994, "MulX"), + (0x995, "Dot"), + (0x996, "SubtractVV"), + (0x997, "CrossVF"), + (0x998, "CrossFV"), + (0x999, "MulTMV"), + (0x99A, "CrossVV"), + (0x99B, "Max"), + (0x99C, "MulMV"), + (0x99D, "Abs"), + (0x99E, "MulXT"), + (0x99F, "SetM"), + (0x9A0, "AddM"), + (0x9A1, "Solve"), + (0x9A2, "Add"), + (0x9A3, "Min"), + (0x9A4, "FromAngle"), + (0x9A5, "GetTransform"), + (0x9A6, "Advance"), + (0x9A7, "GetInverse"), + (0x9A8, "Combine"), + (0x9A9, "Contains"), + (0x9AA, "TestOverlap"), + (0x9AB, "GetCenter"), + (0x9AC, "Solve22"), + (0x9AD, "Solve33"), + (0x9AE, "SetLocalPosition"), + (0x9AF, "ComputeAABB"), + (0x9B0, "ComputeMass"), + (0x9B1, "GetType"), + (0x9B2, "SetAsArray"), + (0x9B3, "GetVertex"), + (0x9B4, "GetSupport"), + (0x9B5, "GetSupportVertex"), + (0x9B6, "GetVertexCount"), + (0x9B7, "GetVertices"), + (0x9B8, "ReadCache"), + (0x9B9, "GetClosestPoint"), + (0x9BA, "GetSearchDirection"), + (0x9BB, "Solve2"), + (0x9BC, "Solve3"), + (0x9BD, "GetWitnessPoints"), + (0x9BE, "WriteCache"), + (0x9BF, "Distance"), + (0x9C0, "cubicCurveTo"), + (0x9C1, "wideMoveTo"), + (0x9C2, "wideLineTo"), + (0x9C3, "insertAt"), + (0x9C4, "removeAt"), # Seems like more event constants. - (0xa00, 'CLICK'), - (0xa01, 'ENTER_FRAME'), - (0xa02, 'ADDED_TO_STAGE'), - (0xa03, 'MOUSE_DOWN'), - (0xa04, 'MOUSE_MOVE'), - (0xa05, 'MOUSE_OUT'), - (0xa06, 'MOUSE_OVER'), - (0xa07, 'MOUSE_UP'), - (0xa08, 'MOUSE_WHEEL'), - (0xa09, 'ROLL_OUT'), - (0xa0a, 'ROLL_OVER'), - (0xa0b, 'KEY_DOWN'), - (0xa0c, 'KEY_UP'), - (0xa0d, 'TIMER'), - (0xa0e, 'COMPLETE'), - (0xa0f, 'SOUND_COMPLETE'), - (0xa10, 'OPEN'), - (0xa11, 'PROGRESS'), - (0xa12, 'INIT'), - (0xa13, 'IO_ERROR'), - (0xa14, 'TIMER_COMPLETE'), - (0xa15, 'REMOVED_FROM_STAGE'), - (0xa16, 'REMOVED'), - (0xa17, 'FRAME_CONSTRUCTED'), - (0xa18, 'DOUBLE_CLICK'), - (0xa19, 'RESIZE'), - (0xa1a, 'ADDED'), - (0xa1b, 'TAB_CHILDREN_CHANGE'), - (0xa1c, 'TAB_ENABLED_CHANGE'), - (0xa1d, 'TAB_INDEX_CHANGE'), - (0xa1e, 'EXIT_FRAME'), - (0xa1f, 'RENDER'), - (0xa20, 'ACTIVATE'), - (0xa21, 'DEACTIVATE'), - (0xa22, 'SECURITY_ERROR'), - (0xa23, 'ERROR'), - (0xa24, 'CLOSE'), - (0xa25, 'DATA'), - (0xa26, 'CONNECT'), - (0xa27, 'MOUSE_LEAVE'), - (0xa28, 'FOCUS_IN'), - (0xa29, 'FOCUS_OUT'), - (0xa2a, 'KEY_FOCUS_CHANGE'), - (0xa2b, 'MOUSE_FOCUS_CHANGE'), - (0xa2c, 'LINK'), - (0xa2d, 'TEXT_INPUT'), - (0xa2e, 'CHANGE'), - (0xa2f, 'SCROLL'), - (0xa30, 'CONTEXT3D_CREATE'), - (0xa31, 'MENU_ITEM_SELECT'), - (0xa32, 'MENU_SELECT'), - (0xa33, 'UNLOAD'), - (0xa34, 'TOUCH_BEGIN'), - (0xa35, 'TOUCH_END'), - (0xa36, 'TOUCH_MOVE'), - (0xa37, 'TOUCH_OUT'), - (0xa38, 'TOUCH_OVER'), - (0xa39, 'TOUCH_ROLL_OUT'), - (0xa3a, 'TOUCH_ROLL_OVER'), - (0xa3b, 'TOUCH_TAP'), - (0xa3c, 'CONTEXT_MENU'), - (0xa3d, 'MIDDLE_CLICK'), - (0xa3e, 'MIDDLE_MOUSE_DOWN'), - (0xa3f, 'MIDDLE_MOUSE_UP'), - (0xa40, 'RELEASE_OUTSIDE'), - (0xa41, 'RIGHT_CLICK'), - (0xa42, 'RIGHT_MOUSE_DOWN'), - (0xa43, 'RIGHT_MOUSE_UP'), - + (0xA00, "CLICK"), + (0xA01, "ENTER_FRAME"), + (0xA02, "ADDED_TO_STAGE"), + (0xA03, "MOUSE_DOWN"), + (0xA04, "MOUSE_MOVE"), + (0xA05, "MOUSE_OUT"), + (0xA06, "MOUSE_OVER"), + (0xA07, "MOUSE_UP"), + (0xA08, "MOUSE_WHEEL"), + (0xA09, "ROLL_OUT"), + (0xA0A, "ROLL_OVER"), + (0xA0B, "KEY_DOWN"), + (0xA0C, "KEY_UP"), + (0xA0D, "TIMER"), + (0xA0E, "COMPLETE"), + (0xA0F, "SOUND_COMPLETE"), + (0xA10, "OPEN"), + (0xA11, "PROGRESS"), + (0xA12, "INIT"), + (0xA13, "IO_ERROR"), + (0xA14, "TIMER_COMPLETE"), + (0xA15, "REMOVED_FROM_STAGE"), + (0xA16, "REMOVED"), + (0xA17, "FRAME_CONSTRUCTED"), + (0xA18, "DOUBLE_CLICK"), + (0xA19, "RESIZE"), + (0xA1A, "ADDED"), + (0xA1B, "TAB_CHILDREN_CHANGE"), + (0xA1C, "TAB_ENABLED_CHANGE"), + (0xA1D, "TAB_INDEX_CHANGE"), + (0xA1E, "EXIT_FRAME"), + (0xA1F, "RENDER"), + (0xA20, "ACTIVATE"), + (0xA21, "DEACTIVATE"), + (0xA22, "SECURITY_ERROR"), + (0xA23, "ERROR"), + (0xA24, "CLOSE"), + (0xA25, "DATA"), + (0xA26, "CONNECT"), + (0xA27, "MOUSE_LEAVE"), + (0xA28, "FOCUS_IN"), + (0xA29, "FOCUS_OUT"), + (0xA2A, "KEY_FOCUS_CHANGE"), + (0xA2B, "MOUSE_FOCUS_CHANGE"), + (0xA2C, "LINK"), + (0xA2D, "TEXT_INPUT"), + (0xA2E, "CHANGE"), + (0xA2F, "SCROLL"), + (0xA30, "CONTEXT3D_CREATE"), + (0xA31, "MENU_ITEM_SELECT"), + (0xA32, "MENU_SELECT"), + (0xA33, "UNLOAD"), + (0xA34, "TOUCH_BEGIN"), + (0xA35, "TOUCH_END"), + (0xA36, "TOUCH_MOVE"), + (0xA37, "TOUCH_OUT"), + (0xA38, "TOUCH_OVER"), + (0xA39, "TOUCH_ROLL_OUT"), + (0xA3A, "TOUCH_ROLL_OVER"), + (0xA3B, "TOUCH_TAP"), + (0xA3C, "CONTEXT_MENU"), + (0xA3D, "MIDDLE_CLICK"), + (0xA3E, "MIDDLE_MOUSE_DOWN"), + (0xA3F, "MIDDLE_MOUSE_UP"), + (0xA40, "RELEASE_OUTSIDE"), + (0xA41, "RIGHT_CLICK"), + (0xA42, "RIGHT_MOUSE_DOWN"), + (0xA43, "RIGHT_MOUSE_UP"), # Seems like methods on objects tied to events. - (0xa80, 'click'), - (0xa81, 'enterFrame'), - (0xa82, 'addedToStage'), - (0xa83, 'mouseDown'), - (0xa84, 'mouseMove'), - (0xa85, 'mouseOut'), - (0xa86, 'mouseOver'), - (0xa87, 'mouseUp'), - (0xa88, 'mouseWheel'), - (0xa89, 'rollOut'), - (0xa8a, 'rollOver'), - (0xa8b, 'keyDown'), - (0xa8c, 'keyUp'), - (0xa8d, 'timer'), - (0xa8e, 'complete'), - (0xa8f, 'soundComplete'), - (0xa90, 'open'), - (0xa91, 'progress'), - (0xa92, 'init'), - (0xa93, 'ioError'), - (0xa94, 'timerComplete'), - (0xa95, 'removedFromStage'), - (0xa96, 'removed'), - (0xa97, 'frameConstructed'), - (0xa98, 'doubleClick'), - (0xa99, 'resize'), - (0xa9a, 'added'), - (0xa9b, 'tabChildrenChange'), - (0xa9c, 'tabEnabledChange'), - (0xa9d, 'tabIndexChange'), - (0xa9e, 'exitFrame'), - (0xa9f, 'render'), - (0xaa0, 'activate'), - (0xaa1, 'deactivate'), - (0xaa2, 'securityError'), - (0xaa3, 'error'), - (0xaa4, 'close'), - (0xaa5, 'udf0aa5'), - (0xaa6, 'connect'), - (0xaa7, 'mouseLeave'), - (0xaa8, 'focusIn'), - (0xaa9, 'focusOut'), - (0xaaa, 'keyFocusChange'), - (0xaab, 'mouseFocusChange'), - (0xaac, 'link'), - (0xaad, 'textInput'), - (0xaae, 'change'), - (0xaaf, 'scroll'), - (0xab0, 'context3DCreate'), - (0xab1, 'menuItemSelect'), - (0xab2, 'menuSelect'), - (0xab3, 'udf0ab3'), - (0xab4, 'touchBegin'), - (0xab5, 'touchEnd'), - (0xab6, 'touchMove'), - (0xab7, 'touchOut'), - (0xab8, 'touchOver'), - (0xab9, 'touchRollOut'), - (0xaba, 'touchRollOver'), - (0xabb, 'touchTap'), - (0xabc, 'contextMenu'), - (0xabd, 'middleClick'), - (0xabe, 'middleMouseDown'), - (0xabf, 'middleMouseUp'), - (0xac0, 'releaseOutside'), - (0xac1, 'rightClick'), - (0xac2, 'rightMouseDown'), - (0xac3, 'rightMouseUp'), - + (0xA80, "click"), + (0xA81, "enterFrame"), + (0xA82, "addedToStage"), + (0xA83, "mouseDown"), + (0xA84, "mouseMove"), + (0xA85, "mouseOut"), + (0xA86, "mouseOver"), + (0xA87, "mouseUp"), + (0xA88, "mouseWheel"), + (0xA89, "rollOut"), + (0xA8A, "rollOver"), + (0xA8B, "keyDown"), + (0xA8C, "keyUp"), + (0xA8D, "timer"), + (0xA8E, "complete"), + (0xA8F, "soundComplete"), + (0xA90, "open"), + (0xA91, "progress"), + (0xA92, "init"), + (0xA93, "ioError"), + (0xA94, "timerComplete"), + (0xA95, "removedFromStage"), + (0xA96, "removed"), + (0xA97, "frameConstructed"), + (0xA98, "doubleClick"), + (0xA99, "resize"), + (0xA9A, "added"), + (0xA9B, "tabChildrenChange"), + (0xA9C, "tabEnabledChange"), + (0xA9D, "tabIndexChange"), + (0xA9E, "exitFrame"), + (0xA9F, "render"), + (0xAA0, "activate"), + (0xAA1, "deactivate"), + (0xAA2, "securityError"), + (0xAA3, "error"), + (0xAA4, "close"), + (0xAA5, "udf0aa5"), + (0xAA6, "connect"), + (0xAA7, "mouseLeave"), + (0xAA8, "focusIn"), + (0xAA9, "focusOut"), + (0xAAA, "keyFocusChange"), + (0xAAB, "mouseFocusChange"), + (0xAAC, "link"), + (0xAAD, "textInput"), + (0xAAE, "change"), + (0xAAF, "scroll"), + (0xAB0, "context3DCreate"), + (0xAB1, "menuItemSelect"), + (0xAB2, "menuSelect"), + (0xAB3, "udf0ab3"), + (0xAB4, "touchBegin"), + (0xAB5, "touchEnd"), + (0xAB6, "touchMove"), + (0xAB7, "touchOut"), + (0xAB8, "touchOver"), + (0xAB9, "touchRollOut"), + (0xABA, "touchRollOver"), + (0xABB, "touchTap"), + (0xABC, "contextMenu"), + (0xABD, "middleClick"), + (0xABE, "middleMouseDown"), + (0xABF, "middleMouseUp"), + (0xAC0, "releaseOutside"), + (0xAC1, "rightClick"), + (0xAC2, "rightMouseDown"), + (0xAC3, "rightMouseUp"), # Seems like debugging information. - (0xb00, 'flash.system.System'), - (0xb01, 'flash.display.Stage'), - (0xb02, 'udf0b02'), - (0xb03, 'sme0b03'), - (0xb04, 'udf0b04'), - (0xb05, 'flash.display.MovieClip'), - (0xb06, 'sme0b06'), - (0xb07, 'flash.text.TextField'), - (0xb08, 'fl.motion.Color'), - (0xb09, 'sme0b09'), - (0xb0a, 'flash.net.SharedObject'), - (0xb0b, 'flash.ui.Mouse'), - (0xb0c, 'sme0b0c'), - (0xb0d, 'flash.media.Sound'), - (0xb0e, 'sme0b0e'), - (0xb0f, 'sme0b0f'), - (0xb10, 'sme0b10'), - (0xb11, 'flash.text.TextFormat'), - (0xb12, 'udf0b12'), - (0xb13, 'udf0b13'), - (0xb14, 'flash.geom.Matrix'), - (0xb15, 'flash.geom.Point'), - (0xb16, 'flash.display.BitmapData'), - (0xb17, 'udf0b17'), - (0xb18, 'udf0b18'), - (0xb19, 'flash.filters.ColorMatrixFilter'), - (0xb1a, 'sme0b1a'), - (0xb1b, 'flash.xml.XMLNode'), - (0xb1c, 'sme0b1c'), - (0xb1d, 'flash.geom.Transform'), - (0xb1e, 'flash.geom.ColorTransform'), - (0xb1f, 'flash.geom.Rectangle'), - (0xb20, 'sme0b20'), - (0xb21, 'sme0b21'), - (0xb22, 'sme0b22'), - (0xb23, 'sme0b23'), - (0xb24, 'udf0b24'), - (0xb25, 'sme0b25'), - (0xb26, 'sme0b26'), - (0xb27, 'sme0b27'), - (0xb28, 'sme0b28'), - (0xb29, 'flash.events.Event'), - (0xb2a, 'flash.events.MouseEvent'), - (0xb2b, 'flash.geom.Matrix3D'), - (0xb2c, 'flash.ui.Keyboard'), - (0xb2d, 'flash.display.DisplayObject'), - (0xb2e, 'flash.utils.Dictionary'), - (0xb2f, 'flash.display.BlendMode'), - (0xb30, 'flash.display.DisplayObjectContainer'), - (0xb31, 'sme0b31'), - (0xb32, 'flash.events.EventDispatcher'), - (0xb33, 'flash.geom.PerspectiveProjection'), - (0xb34, 'flash.geom.Vector3D'), - (0xb35, 'sme0b35'), - (0xb36, 'flash.media.SoundChannel'), - (0xb37, 'flash.display.Loader'), - (0xb38, 'flash.net.URLRequest'), - (0xb39, 'flash.display.Sprite'), - (0xb3a, 'flash.events.KeyboardEvent'), - (0xb3b, 'flash.utils.Timer'), - (0xb3c, 'flash.events.TimerEvent'), - (0xb3d, 'sme0b3d'), - (0xb3e, 'sme0b3e'), - (0xb3f, 'flash.display.LoaderInfo'), - (0xb40, 'flash.events.ProgressEvent'), - (0xb41, 'flash.events.IOErrorEvent'), - (0xb42, 'flash.display.Graphics'), - (0xb43, 'flash.display.LineScaleMode'), - (0xb44, 'flash.display.CapsStyle'), - (0xb45, 'flash.display.JointStyle'), - (0xb46, 'flash.display.GradientType'), - (0xb47, 'flash.display.SpreadMethod'), - (0xb48, 'flash.display.InterpolationMethod'), - (0xb49, 'flash.display.GraphicsPathCommand'), - (0xb4a, 'flash.display.GraphicsPathWinding'), - (0xb4b, 'flash.display.TriangleCulling'), - (0xb4c, 'flash.display.GraphicsBitmapFill'), - (0xb4d, 'flash.display.GraphicsEndFill'), - (0xb4e, 'flash.display.GraphicsGradientFill'), - (0xb4f, 'flash.display.GraphicsPath'), - (0xb50, 'flash.display.GraphicsSolidFill'), - (0xb51, 'flash.display.GraphicsStroke'), - (0xb52, 'flash.display.GraphicsTrianglePath'), - (0xb53, 'flash.display.IGraphicsData'), - (0xb54, 'udf0b54'), - (0xb55, 'flash.external.ExternalInterface'), - (0xb56, 'flash.display.Scene'), - (0xb57, 'flash.display.FrameLabel'), - (0xb58, 'flash.display.Shape'), - (0xb59, 'flash.display.SimpleButton'), - (0xb5a, 'flash.display.Bitmap'), - (0xb5b, 'flash.display.StageQuality'), - (0xb5c, 'flash.display.InteractiveObject'), - (0xb5d, 'fl.motion.MotionBase'), - (0xb5e, 'fl.motion.KeyframeBase'), - (0xb5f, 'sme0b5f'), - (0xb60, 'flash.display.StageAlign'), - (0xb61, 'flash.display.StageScaleMode'), - (0xb62, 'fl.motion.AnimatorBase'), - (0xb63, 'fl.motion.Animator3D'), - (0xb64, 'flash.net.URLLoader'), - (0xb65, 'flash.system.Capabilities'), - (0xb66, 'sme0b66'), - (0xb67, 'sme0b67'), - (0xb68, 'flash.media.SoundTransform'), - (0xb69, 'sme0b69'), - (0xb6a, 'sme0b6a'), - (0xb6b, 'sme0b6b'), - (0xb6c, 'sme0b6c'), - (0xb6d, 'flash.utils.ByteArray'), - (0xb6e, 'flash.text.TextFormatAlign'), - (0xb6f, 'flash.text.TextFieldType'), - (0xb70, 'flash.text.TextFieldAutoSize'), - (0xb71, 'flash.events.SecurityErrorEvent'), - (0xb72, 'flash.system.ApplicationDomain'), - (0xb73, 'flash.events.TextEvent'), - (0xb74, 'flash.events.ErrorEvent'), - (0xb75, 'flash.system.LoaderContext'), - (0xb76, 'sme0b76'), - (0xb77, 'flash.errors.IllegalOperationError'), - (0xb78, 'flash.net.URLLoaderDataFormat'), - (0xb79, 'flash.system.Security'), - (0xb7a, 'flash.filters.DropShadowFilter'), - (0xb7b, 'sme0b7b'), - (0xb7c, 'flash.utils.Proxy'), - (0xb7d, 'flash.net.XMLSocket'), - (0xb7e, 'flash.events.DataEvent'), - (0xb7f, 'flash.text.Font'), - (0xb80, 'flash.events.IEventDispatcher'), - (0xb81, 'flash.net.LocalConnection'), - (0xb82, 'flash.display.ActionScriptVersion'), - (0xb83, 'flash.ui.MouseCursor'), - (0xb84, 'sme0b84'), - (0xb85, 'flash.events.FocusEvent'), - (0xb86, 'flash.text.AntiAliasType'), - (0xb87, 'flash.text.GridFitType'), - (0xb88, 'sme0b88'), - (0xb89, 'flash.filters.BitmapFilterType'), - (0xb8a, 'flash.filters.BevelFilter'), - (0xb8b, 'flash.filters.BitmapFilter'), - (0xb8c, 'flash.filters.BitmapFilterQuality'), - (0xb8d, 'sme0b8d'), - (0xb8e, 'flash.net.URLVariables'), - (0xb8f, 'flash.net.URLRequestMethod'), - (0xb90, 'sme0b90'), - (0xb91, 'flash.filters.BlurFilter'), - (0xb92, 'flash.display.Stage3D'), - (0xb93, 'flash.display3D.Context3D'), - (0xb94, 'flash.ui.Multitouch'), - (0xb95, 'udf0b95'), - (0xb96, 'flash.accessibility.AccessibilityProperties'), - (0xb97, 'flash.text.StaticText'), - (0xb98, 'flash.display.MorphShape'), - (0xb99, 'flash.display.BitmapDataChannel'), - (0xb9a, 'flash.filters.DisplacementMapFilter'), - (0xb9b, 'flash.filters.GlowFilter'), - (0xb9c, 'flash.filters.DisplacementMapFilterMode'), - (0xb9d, 'fl.motion.AnimatorFactoryBase'), - (0xb9e, 'flash.utils.Endian'), - (0xb9f, 'flash.errors.IOError'), - (0xba0, 'flash.errors.EOFError'), - (0xba1, 'flash.display3D.Context3DTextureFormat'), - (0xba2, 'flash.display3D.Context3DProgramType'), - (0xba3, 'flash.display3D.textures.TextureBase'), - (0xba4, 'flash.display3D.VertexBuffer3D'), - (0xba5, 'flash.display3D.IndexBuffer3D'), - (0xba6, 'flash.display3D.Program3D'), - (0xba7, 'flash.display.NativeMenuItem'), - (0xba8, 'flash.ui.ContextMenuItem'), - (0xba9, 'flash.display.NativeMenu'), - (0xbaa, 'flash.ui.ContextMenu'), - (0xbab, 'flash.events.ContextMenuEvent'), - (0xbac, 'flash.display3D.Context3DVertexBufferFormat'), - (0xbad, 'flash.events.TouchEvent'), - (0xbae, 'Box2D.Common.Math.b2Vec2'), - (0xbaf, 'Box2D.Common.Math.b2Math'), - (0xbb0, 'Box2D.Common.Math.b2Transform'), - (0xbb1, 'Box2D.Common.Math.b2Mat22'), - (0xbb2, 'Box2D.Common.Math.b2Sweep'), - (0xbb3, 'Box2D.Collision.b2AABB'), - (0xbb4, 'Box2D.Common.Math.b2Vec3'), - (0xbb5, 'Box2D.Common.Math.b2Mat33'), - (0xbb6, 'Box2D.Collision.b2DistanceProxy'), - (0xbb7, 'Box2D.Collision.Shapes.b2Shape'), - (0xbb8, 'Box2D.Collision.Shapes.b2CircleShape'), - (0xbb9, 'Box2D.Collision.Shapes.b2PolygonShape'), - (0xbba, 'Box2D.Collision.Shapes.b2MassData'), - (0xbbb, 'Box2D.Collision.b2DistanceInput'), - (0xbbc, 'Box2D.Collision.b2DistanceOutput'), - (0xbbd, 'Box2D.Collision.b2SimplexCache'), - (0xbbe, 'Box2D.Collision.b2Simplex'), - (0xbbf, 'Box2D.Collision.b2SimplexVertex'), - (0xbc0, 'Box2D.Collision.b2Distance'), - (0xbc1, 'flash.geom.Orientation3D'), - (0xbc2, 'flash.filters.GradientGlowFilter'), - (0xbc3, 'flash.filters.GradientBevelFilter'), - + (0xB00, "flash.system.System"), + (0xB01, "flash.display.Stage"), + (0xB02, "udf0b02"), + (0xB03, "sme0b03"), + (0xB04, "udf0b04"), + (0xB05, "flash.display.MovieClip"), + (0xB06, "sme0b06"), + (0xB07, "flash.text.TextField"), + (0xB08, "fl.motion.Color"), + (0xB09, "sme0b09"), + (0xB0A, "flash.net.SharedObject"), + (0xB0B, "flash.ui.Mouse"), + (0xB0C, "sme0b0c"), + (0xB0D, "flash.media.Sound"), + (0xB0E, "sme0b0e"), + (0xB0F, "sme0b0f"), + (0xB10, "sme0b10"), + (0xB11, "flash.text.TextFormat"), + (0xB12, "udf0b12"), + (0xB13, "udf0b13"), + (0xB14, "flash.geom.Matrix"), + (0xB15, "flash.geom.Point"), + (0xB16, "flash.display.BitmapData"), + (0xB17, "udf0b17"), + (0xB18, "udf0b18"), + (0xB19, "flash.filters.ColorMatrixFilter"), + (0xB1A, "sme0b1a"), + (0xB1B, "flash.xml.XMLNode"), + (0xB1C, "sme0b1c"), + (0xB1D, "flash.geom.Transform"), + (0xB1E, "flash.geom.ColorTransform"), + (0xB1F, "flash.geom.Rectangle"), + (0xB20, "sme0b20"), + (0xB21, "sme0b21"), + (0xB22, "sme0b22"), + (0xB23, "sme0b23"), + (0xB24, "udf0b24"), + (0xB25, "sme0b25"), + (0xB26, "sme0b26"), + (0xB27, "sme0b27"), + (0xB28, "sme0b28"), + (0xB29, "flash.events.Event"), + (0xB2A, "flash.events.MouseEvent"), + (0xB2B, "flash.geom.Matrix3D"), + (0xB2C, "flash.ui.Keyboard"), + (0xB2D, "flash.display.DisplayObject"), + (0xB2E, "flash.utils.Dictionary"), + (0xB2F, "flash.display.BlendMode"), + (0xB30, "flash.display.DisplayObjectContainer"), + (0xB31, "sme0b31"), + (0xB32, "flash.events.EventDispatcher"), + (0xB33, "flash.geom.PerspectiveProjection"), + (0xB34, "flash.geom.Vector3D"), + (0xB35, "sme0b35"), + (0xB36, "flash.media.SoundChannel"), + (0xB37, "flash.display.Loader"), + (0xB38, "flash.net.URLRequest"), + (0xB39, "flash.display.Sprite"), + (0xB3A, "flash.events.KeyboardEvent"), + (0xB3B, "flash.utils.Timer"), + (0xB3C, "flash.events.TimerEvent"), + (0xB3D, "sme0b3d"), + (0xB3E, "sme0b3e"), + (0xB3F, "flash.display.LoaderInfo"), + (0xB40, "flash.events.ProgressEvent"), + (0xB41, "flash.events.IOErrorEvent"), + (0xB42, "flash.display.Graphics"), + (0xB43, "flash.display.LineScaleMode"), + (0xB44, "flash.display.CapsStyle"), + (0xB45, "flash.display.JointStyle"), + (0xB46, "flash.display.GradientType"), + (0xB47, "flash.display.SpreadMethod"), + (0xB48, "flash.display.InterpolationMethod"), + (0xB49, "flash.display.GraphicsPathCommand"), + (0xB4A, "flash.display.GraphicsPathWinding"), + (0xB4B, "flash.display.TriangleCulling"), + (0xB4C, "flash.display.GraphicsBitmapFill"), + (0xB4D, "flash.display.GraphicsEndFill"), + (0xB4E, "flash.display.GraphicsGradientFill"), + (0xB4F, "flash.display.GraphicsPath"), + (0xB50, "flash.display.GraphicsSolidFill"), + (0xB51, "flash.display.GraphicsStroke"), + (0xB52, "flash.display.GraphicsTrianglePath"), + (0xB53, "flash.display.IGraphicsData"), + (0xB54, "udf0b54"), + (0xB55, "flash.external.ExternalInterface"), + (0xB56, "flash.display.Scene"), + (0xB57, "flash.display.FrameLabel"), + (0xB58, "flash.display.Shape"), + (0xB59, "flash.display.SimpleButton"), + (0xB5A, "flash.display.Bitmap"), + (0xB5B, "flash.display.StageQuality"), + (0xB5C, "flash.display.InteractiveObject"), + (0xB5D, "fl.motion.MotionBase"), + (0xB5E, "fl.motion.KeyframeBase"), + (0xB5F, "sme0b5f"), + (0xB60, "flash.display.StageAlign"), + (0xB61, "flash.display.StageScaleMode"), + (0xB62, "fl.motion.AnimatorBase"), + (0xB63, "fl.motion.Animator3D"), + (0xB64, "flash.net.URLLoader"), + (0xB65, "flash.system.Capabilities"), + (0xB66, "sme0b66"), + (0xB67, "sme0b67"), + (0xB68, "flash.media.SoundTransform"), + (0xB69, "sme0b69"), + (0xB6A, "sme0b6a"), + (0xB6B, "sme0b6b"), + (0xB6C, "sme0b6c"), + (0xB6D, "flash.utils.ByteArray"), + (0xB6E, "flash.text.TextFormatAlign"), + (0xB6F, "flash.text.TextFieldType"), + (0xB70, "flash.text.TextFieldAutoSize"), + (0xB71, "flash.events.SecurityErrorEvent"), + (0xB72, "flash.system.ApplicationDomain"), + (0xB73, "flash.events.TextEvent"), + (0xB74, "flash.events.ErrorEvent"), + (0xB75, "flash.system.LoaderContext"), + (0xB76, "sme0b76"), + (0xB77, "flash.errors.IllegalOperationError"), + (0xB78, "flash.net.URLLoaderDataFormat"), + (0xB79, "flash.system.Security"), + (0xB7A, "flash.filters.DropShadowFilter"), + (0xB7B, "sme0b7b"), + (0xB7C, "flash.utils.Proxy"), + (0xB7D, "flash.net.XMLSocket"), + (0xB7E, "flash.events.DataEvent"), + (0xB7F, "flash.text.Font"), + (0xB80, "flash.events.IEventDispatcher"), + (0xB81, "flash.net.LocalConnection"), + (0xB82, "flash.display.ActionScriptVersion"), + (0xB83, "flash.ui.MouseCursor"), + (0xB84, "sme0b84"), + (0xB85, "flash.events.FocusEvent"), + (0xB86, "flash.text.AntiAliasType"), + (0xB87, "flash.text.GridFitType"), + (0xB88, "sme0b88"), + (0xB89, "flash.filters.BitmapFilterType"), + (0xB8A, "flash.filters.BevelFilter"), + (0xB8B, "flash.filters.BitmapFilter"), + (0xB8C, "flash.filters.BitmapFilterQuality"), + (0xB8D, "sme0b8d"), + (0xB8E, "flash.net.URLVariables"), + (0xB8F, "flash.net.URLRequestMethod"), + (0xB90, "sme0b90"), + (0xB91, "flash.filters.BlurFilter"), + (0xB92, "flash.display.Stage3D"), + (0xB93, "flash.display3D.Context3D"), + (0xB94, "flash.ui.Multitouch"), + (0xB95, "udf0b95"), + (0xB96, "flash.accessibility.AccessibilityProperties"), + (0xB97, "flash.text.StaticText"), + (0xB98, "flash.display.MorphShape"), + (0xB99, "flash.display.BitmapDataChannel"), + (0xB9A, "flash.filters.DisplacementMapFilter"), + (0xB9B, "flash.filters.GlowFilter"), + (0xB9C, "flash.filters.DisplacementMapFilterMode"), + (0xB9D, "fl.motion.AnimatorFactoryBase"), + (0xB9E, "flash.utils.Endian"), + (0xB9F, "flash.errors.IOError"), + (0xBA0, "flash.errors.EOFError"), + (0xBA1, "flash.display3D.Context3DTextureFormat"), + (0xBA2, "flash.display3D.Context3DProgramType"), + (0xBA3, "flash.display3D.textures.TextureBase"), + (0xBA4, "flash.display3D.VertexBuffer3D"), + (0xBA5, "flash.display3D.IndexBuffer3D"), + (0xBA6, "flash.display3D.Program3D"), + (0xBA7, "flash.display.NativeMenuItem"), + (0xBA8, "flash.ui.ContextMenuItem"), + (0xBA9, "flash.display.NativeMenu"), + (0xBAA, "flash.ui.ContextMenu"), + (0xBAB, "flash.events.ContextMenuEvent"), + (0xBAC, "flash.display3D.Context3DVertexBufferFormat"), + (0xBAD, "flash.events.TouchEvent"), + (0xBAE, "Box2D.Common.Math.b2Vec2"), + (0xBAF, "Box2D.Common.Math.b2Math"), + (0xBB0, "Box2D.Common.Math.b2Transform"), + (0xBB1, "Box2D.Common.Math.b2Mat22"), + (0xBB2, "Box2D.Common.Math.b2Sweep"), + (0xBB3, "Box2D.Collision.b2AABB"), + (0xBB4, "Box2D.Common.Math.b2Vec3"), + (0xBB5, "Box2D.Common.Math.b2Mat33"), + (0xBB6, "Box2D.Collision.b2DistanceProxy"), + (0xBB7, "Box2D.Collision.Shapes.b2Shape"), + (0xBB8, "Box2D.Collision.Shapes.b2CircleShape"), + (0xBB9, "Box2D.Collision.Shapes.b2PolygonShape"), + (0xBBA, "Box2D.Collision.Shapes.b2MassData"), + (0xBBB, "Box2D.Collision.b2DistanceInput"), + (0xBBC, "Box2D.Collision.b2DistanceOutput"), + (0xBBD, "Box2D.Collision.b2SimplexCache"), + (0xBBE, "Box2D.Collision.b2Simplex"), + (0xBBF, "Box2D.Collision.b2SimplexVertex"), + (0xBC0, "Box2D.Collision.b2Distance"), + (0xBC1, "flash.geom.Orientation3D"), + (0xBC2, "flash.filters.GradientGlowFilter"), + (0xBC3, "flash.filters.GradientBevelFilter"), # More generic constants. - (0xc00, 'NEARLY_ZERO'), - (0xc01, 'EXACTLY_ZERO'), - (0xc02, 'debug_mode'), - (0xd00, 'm_count'), - (0xd01, 'wA'), - (0xd02, 'wB'), - (0xd03, 'bubbles'), - (0xd04, 'checkPolicyFile'), - (0xd05, 'securityDomain'), - (0xd06, 'spreadMethod'), - (0xd07, 'interpolationMethod'), - (0xd08, 'focalPointRatio'), - (0xd09, 'culling'), - (0xd0a, 'caps'), - (0xd0b, 'winding'), + (0xC00, "NEARLY_ZERO"), + (0xC01, "EXACTLY_ZERO"), + (0xC02, "debug_mode"), + (0xD00, "m_count"), + (0xD01, "wA"), + (0xD02, "wB"), + (0xD03, "bubbles"), + (0xD04, "checkPolicyFile"), + (0xD05, "securityDomain"), + (0xD06, "spreadMethod"), + (0xD07, "interpolationMethod"), + (0xD08, "focalPointRatio"), + (0xD09, "culling"), + (0xD0A, "caps"), + (0xD0B, "winding"), ] @classmethod @@ -2018,7 +1997,7 @@ class ArithmeticExpression(Expression): left = value_ref(self.left, parent_prefix, parens=True) right = value_ref(self.right, parent_prefix, parens=True) - if nested and self.op == '-': + if nested and self.op == "-": return f"({left} {self.op} {right})" else: return f"{left} {self.op} {right}" @@ -2059,7 +2038,10 @@ class Object(Expression): return self.render("") def render(self, parent_prefix: str, nested: bool = False) -> str: - params = [f"{value_ref(key, parent_prefix)}: {value_ref(val, parent_prefix)}" for (key, val) in self.params.items()] + params = [ + f"{value_ref(key, parent_prefix)}: {value_ref(val, parent_prefix)}" + for (key, val) in self.params.items() + ] lpar = "{" rpar = "}" @@ -2068,7 +2050,9 @@ class Object(Expression): class FunctionCall(Expression): # Call a method on an object. - def __init__(self, insertion_ref: int, name: Union[str, StringConstant], params: List[Any]) -> None: + def __init__( + self, insertion_ref: int, name: Union[str, StringConstant], params: List[Any] + ) -> None: self.insertion_ref = insertion_ref self.name = name self.params = params @@ -2096,7 +2080,13 @@ class GetPathFunctionCall(FunctionCall): class MethodCall(Expression): # Call a method on an object. - def __init__(self, insertion_ref: int, objectref: Any, name: Union[str, int, Expression], params: List[Any]) -> None: + def __init__( + self, + insertion_ref: int, + objectref: Any, + name: Union[str, int, Expression], + params: List[Any], + ) -> None: self.insertion_ref = insertion_ref self.objectref = objectref self.name = name @@ -2129,8 +2119,8 @@ class NewFunction(Expression): def render(self, parent_prefix: str, nested: bool = False) -> str: code = self.body.as_string(prefix=parent_prefix + " ") - opar = '{' - cpar = '}' + opar = "{" + cpar = "}" val = f"new Function({hex(self.flags)}, {opar}{os.linesep}{code}{os.linesep}{parent_prefix}{cpar})" if nested: return f"({val})" @@ -2145,7 +2135,7 @@ class NewObject(Expression): self.params = params def __repr__(self) -> str: - return self.render('') + return self.render("") def render(self, parent_prefix: str, nested: bool = False) -> str: objname = name_ref(self.objname, parent_prefix) @@ -2192,7 +2182,9 @@ class Member(Expression): # The following are helpers which facilitate rendering out various parts of expressions. def object_ref(obj: Any, parent_prefix: str) -> str: - if isinstance(obj, (GenericObject, Variable, Member, MethodCall, FunctionCall, Register)): + if isinstance( + obj, (GenericObject, Variable, Member, MethodCall, FunctionCall, Register) + ): return obj.render(parent_prefix, nested=True) else: raise Exception(f"Unsupported objectref {obj} ({type(obj)})") diff --git a/bemani/format/afp/types/generic.py b/bemani/format/afp/types/generic.py index 0da46d7..c4b081a 100644 --- a/bemani/format/afp/types/generic.py +++ b/bemani/format/afp/types/generic.py @@ -15,10 +15,10 @@ class Color: def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { - 'r': self.r, - 'g': self.g, - 'b': self.b, - 'a': self.a, + "r": self.r, + "g": self.g, + "b": self.b, + "a": self.a, } def multiply(self, other: "Color") -> "Color": @@ -63,9 +63,9 @@ class Point: def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { - 'x': self.x, - 'y': self.y, - 'z': self.z, + "x": self.x, + "y": self.y, + "z": self.z, } def as_tuple(self) -> Tuple[int, int, int]: @@ -97,10 +97,10 @@ class Rectangle: def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: return { - 'left': self.left, - 'top': self.top, - 'bottom': self.bottom, - 'right': self.right, + "left": self.left, + "top": self.top, + "bottom": self.bottom, + "right": self.right, } @property @@ -138,11 +138,20 @@ class Matrix: # | tx ty 1 | def __init__( - self, *, - a11: float, a12: float, a13: float, - a21: float, a22: float, a23: float, - a31: float, a32: float, a33: float, - a41: float, a42: float, a43: float, + self, + *, + a11: float, + a12: float, + a13: float, + a21: float, + a22: float, + a23: float, + a31: float, + a32: float, + a33: float, + a41: float, + a42: float, + a43: float, ) -> None: self.__a11 = a11 self.__a12 = a12 @@ -165,10 +174,18 @@ class Matrix: @staticmethod def identity() -> "Matrix": new = Matrix( - a11=1.0, a12=0.0, a13=0.0, - a21=0.0, a22=1.0, a23=0.0, - a31=0.0, a32=0.0, a33=1.0, - a41=0.0, a42=0.0, a43=0.0, + a11=1.0, + a12=0.0, + a13=0.0, + a21=0.0, + a22=1.0, + a23=0.0, + a31=0.0, + a32=0.0, + a33=1.0, + a41=0.0, + a42=0.0, + a43=0.0, ) new.__scale_set = False new.__rotate_set = False @@ -178,49 +195,59 @@ class Matrix: return new @staticmethod - def affine(*, a: float, b: float, c: float, d: float, tx: float, ty: float) -> "Matrix": + def affine( + *, a: float, b: float, c: float, d: float, tx: float, ty: float + ) -> "Matrix": return Matrix( - a11=a, a12=b, a13=0.0, - a21=c, a22=d, a23=0.0, - a31=0.0, a32=0.0, a33=1.0, - a41=tx, a42=ty, a43=0.0, + a11=a, + a12=b, + a13=0.0, + a21=c, + a22=d, + a23=0.0, + a31=0.0, + a32=0.0, + a33=1.0, + a41=tx, + a42=ty, + a43=0.0, ) @property def __is_affine(self) -> bool: return ( - round(abs(self.__a13), 5) == 0.0 and - round(abs(self.__a23), 5) == 0.0 and - round(abs(self.__a31), 5) == 0.0 and - round(abs(self.__a32), 5) == 0.0 and - round(self.__a33, 5) == 1.0 and - round(abs(self.__a43), 5) == 0.0 + round(abs(self.__a13), 5) == 0.0 + and round(abs(self.__a23), 5) == 0.0 + and round(abs(self.__a31), 5) == 0.0 + and round(abs(self.__a32), 5) == 0.0 + and round(self.__a33, 5) == 1.0 + and round(abs(self.__a43), 5) == 0.0 ) def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: if self.__is_affine: return { - 'a': self.__a11, - 'b': self.__a12, - 'c': self.__a21, - 'd': self.__a22, - 'tx': self.__a41, - 'ty': self.__a42, + "a": self.__a11, + "b": self.__a12, + "c": self.__a21, + "d": self.__a22, + "tx": self.__a41, + "ty": self.__a42, } else: return { - 'a11': self.__a11, - 'a12': self.__a12, - 'a13': self.__a13, - 'a21': self.__a21, - 'a22': self.__a22, - 'a23': self.__a23, - 'a31': self.__a31, - 'a32': self.__a32, - 'a33': self.__a33, - 'tx': self.__a41, - 'ty': self.__a42, - 'tz': self.__a43, + "a11": self.__a11, + "a12": self.__a12, + "a13": self.__a13, + "a21": self.__a21, + "a22": self.__a22, + "a23": self.__a23, + "a31": self.__a31, + "a32": self.__a32, + "a33": self.__a33, + "tx": self.__a41, + "ty": self.__a42, + "tz": self.__a43, } def update(self, other: "Matrix") -> "Matrix": @@ -267,15 +294,27 @@ class Matrix: @property def xscale(self) -> float: - return math.sqrt((self.__a11 * self.__a11) + (self.__a12 * self.__a12) + (self.__a13 * self.__a13)) + return math.sqrt( + (self.__a11 * self.__a11) + + (self.__a12 * self.__a12) + + (self.__a13 * self.__a13) + ) @property def yscale(self) -> float: - return math.sqrt((self.__a21 * self.__a21) + (self.__a22 * self.__a22) + (self.__a23 * self.__a23)) + return math.sqrt( + (self.__a21 * self.__a21) + + (self.__a22 * self.__a22) + + (self.__a23 * self.__a23) + ) @property def zscale(self) -> float: - return math.sqrt((self.__a31 * self.__a31) + (self.__a32 * self.__a32) + (self.__a33 * self.__a33)) + return math.sqrt( + (self.__a31 * self.__a31) + + (self.__a32 * self.__a32) + + (self.__a33 * self.__a33) + ) @property def a(self) -> float: @@ -454,9 +493,18 @@ class Matrix: def multiply_point(self, point: Point) -> Point: return Point( - x=(self.__a11 * point.x) + (self.__a21 * point.y) + (self.__a31 * point.z) + self.__a41, - y=(self.__a12 * point.x) + (self.__a22 * point.y) + (self.__a32 * point.z) + self.__a42, - z=(self.__a13 * point.x) + (self.__a23 * point.y) + (self.__a33 * point.z) + self.__a43, + x=(self.__a11 * point.x) + + (self.__a21 * point.y) + + (self.__a31 * point.z) + + self.__a41, + y=(self.__a12 * point.x) + + (self.__a22 * point.y) + + (self.__a32 * point.z) + + self.__a42, + z=(self.__a13 * point.x) + + (self.__a23 * point.y) + + (self.__a33 * point.z) + + self.__a43, ) def translate(self, point: Point) -> "Matrix": @@ -478,21 +526,45 @@ class Matrix: def multiply(self, other: "Matrix") -> "Matrix": return Matrix( - a11=self.__a11 * other.__a11 + self.__a12 * other.__a21 + self.__a13 * other.__a31, - a12=self.__a11 * other.__a12 + self.__a12 * other.__a22 + self.__a13 * other.__a32, - a13=self.__a11 * other.__a13 + self.__a12 * other.__a23 + self.__a13 * other.__a33, - - a21=self.__a21 * other.__a11 + self.__a22 * other.__a21 + self.__a23 * other.__a31, - a22=self.__a21 * other.__a12 + self.__a22 * other.__a22 + self.__a23 * other.__a32, - a23=self.__a21 * other.__a13 + self.__a22 * other.__a23 + self.__a23 * other.__a33, - - a31=self.__a31 * other.__a11 + self.__a32 * other.__a21 + self.__a33 * other.__a31, - a32=self.__a31 * other.__a12 + self.__a32 * other.__a22 + self.__a33 * other.__a32, - a33=self.__a31 * other.__a13 + self.__a32 * other.__a23 + self.__a33 * other.__a33, - - a41=self.__a41 * other.__a11 + self.__a42 * other.__a21 + self.__a43 * other.__a31 + other.__a41, - a42=self.__a41 * other.__a12 + self.__a42 * other.__a22 + self.__a43 * other.__a32 + other.__a42, - a43=self.__a41 * other.__a13 + self.__a42 * other.__a23 + self.__a43 * other.__a33 + other.__a43, + a11=self.__a11 * other.__a11 + + self.__a12 * other.__a21 + + self.__a13 * other.__a31, + a12=self.__a11 * other.__a12 + + self.__a12 * other.__a22 + + self.__a13 * other.__a32, + a13=self.__a11 * other.__a13 + + self.__a12 * other.__a23 + + self.__a13 * other.__a33, + a21=self.__a21 * other.__a11 + + self.__a22 * other.__a21 + + self.__a23 * other.__a31, + a22=self.__a21 * other.__a12 + + self.__a22 * other.__a22 + + self.__a23 * other.__a32, + a23=self.__a21 * other.__a13 + + self.__a22 * other.__a23 + + self.__a23 * other.__a33, + a31=self.__a31 * other.__a11 + + self.__a32 * other.__a21 + + self.__a33 * other.__a31, + a32=self.__a31 * other.__a12 + + self.__a32 * other.__a22 + + self.__a33 * other.__a32, + a33=self.__a31 * other.__a13 + + self.__a32 * other.__a23 + + self.__a33 * other.__a33, + a41=self.__a41 * other.__a11 + + self.__a42 * other.__a21 + + self.__a43 * other.__a31 + + other.__a41, + a42=self.__a41 * other.__a12 + + self.__a42 * other.__a22 + + self.__a43 * other.__a32 + + other.__a42, + a43=self.__a41 * other.__a13 + + self.__a42 * other.__a23 + + self.__a43 * other.__a33 + + other.__a43, ) def inverse(self) -> "Matrix": @@ -512,7 +584,9 @@ class Matrix: [self.__a31, self.__a32, self.__a33, 0.0], [self.__a41, self.__a42, self.__a43, 1.0], ] - inverse: List[List[float]] = [[1 if row == col else 0 for col in range(size)] for row in range(size)] + inverse: List[List[float]] = [ + [1 if row == col else 0 for col in range(size)] for row in range(size) + ] for col in range(size): # First, get upper triangle of the matrix. @@ -566,12 +640,15 @@ class Matrix: m = [ *m[:row], [m[row][i] + m[col][i] * factor for i in range(size)], - *m[(row + 1):], + *m[(row + 1) :], ] inverse = [ *inverse[:row], - [inverse[row][i] + inverse[col][i] * factor for i in range(size)], - *inverse[(row + 1):], + [ + inverse[row][i] + inverse[col][i] * factor + for i in range(size) + ], + *inverse[(row + 1) :], ] # Finally, divide the current column to make it a unit. @@ -579,31 +656,41 @@ class Matrix: m = [ *m[:col], [e * factor for e in m[col]], - *m[(col + 1):], + *m[(col + 1) :], ] inverse = [ *inverse[:col], [e * factor for e in inverse[col]], - *inverse[(col + 1):], + *inverse[(col + 1) :], ] # Technically the rest of the matrix that we don't care about could have values other # than 0.0, 0.0, 0.0, 1.0 but in practice that's because of floating point errors # accumulating so we simply trust the math and discard those values. return Matrix( - a11=inverse[0][0], a12=inverse[0][1], a13=inverse[0][2], - a21=inverse[1][0], a22=inverse[1][1], a23=inverse[1][2], - a31=inverse[2][0], a32=inverse[2][1], a33=inverse[2][2], - a41=inverse[3][0], a42=inverse[3][1], a43=inverse[3][2], + a11=inverse[0][0], + a12=inverse[0][1], + a13=inverse[0][2], + a21=inverse[1][0], + a22=inverse[1][1], + a23=inverse[1][2], + a31=inverse[2][0], + a32=inverse[2][1], + a33=inverse[2][2], + a41=inverse[3][0], + a42=inverse[3][1], + a43=inverse[3][2], ) def __repr__(self) -> str: if self.__is_affine: return f"a: {round(self.__a11, 5)}, b: {round(self.__a12, 5)}, c: {round(self.__a21, 5)}, d: {round(self.__a22, 5)}, tx: {round(self.__a41, 5)}, ty: {round(self.__a42, 5)}" else: - return "; ".join([ - f"a11: {round(self.__a11, 5)}, a12: {round(self.__a12, 5)}, a13: {round(self.__a13, 5)}", - f"a21: {round(self.__a21, 5)}, a22: {round(self.__a22, 5)}, a23: {round(self.__a23, 5)}", - f"a31: {round(self.__a31, 5)}, a32: {round(self.__a32, 5)}, a33: {round(self.__a33, 5)}", - f"tx: {round(self.__a41, 5)}, ty: {round(self.__a42, 5)}, tz: {round(self.__a43, 5)}", - ]) + return "; ".join( + [ + f"a11: {round(self.__a11, 5)}, a12: {round(self.__a12, 5)}, a13: {round(self.__a13, 5)}", + f"a21: {round(self.__a21, 5)}, a22: {round(self.__a22, 5)}, a23: {round(self.__a23, 5)}", + f"a31: {round(self.__a31, 5)}, a32: {round(self.__a32, 5)}, a33: {round(self.__a33, 5)}", + f"tx: {round(self.__a41, 5)}, ty: {round(self.__a42, 5)}, tz: {round(self.__a43, 5)}", + ] + ) diff --git a/bemani/format/afp/types/statement.py b/bemani/format/afp/types/statement.py index fd8b259..8b879e7 100644 --- a/bemani/format/afp/types/statement.py +++ b/bemani/format/afp/types/statement.py @@ -23,7 +23,9 @@ class ConvertedAction: class Statement(ConvertedAction): # This is just a type class for finished statements. def render(self, prefix: str) -> List[str]: - raise NotImplementedError(f"{self.__class__.__name__} does not implement render()!") + raise NotImplementedError( + f"{self.__class__.__name__} does not implement render()!" + ) class DefineLabelStatement(Statement): @@ -59,7 +61,9 @@ class GotoStatement(Statement): # A goto, including the ID of the chunk we want to jump to. def __init__(self, location: int) -> None: if location < 0: - raise Exception(f"Logic error, attempting to go to artificially inserted location {location}!") + raise Exception( + f"Logic error, attempting to go to artificially inserted location {location}!" + ) self.location = location @@ -118,7 +122,9 @@ class NopStatement(Statement): def render(self, prefix: str) -> List[str]: # We should never render this! - raise Exception("Logic error, a NopStatement should never make it to the render stage!") + raise Exception( + "Logic error, a NopStatement should never make it to the render stage!" + ) class ExpressionStatement(Statement): @@ -208,7 +214,12 @@ class GotoFrameStatement(Statement): class CloneSpriteStatement(Statement): # Clone a sprite. - def __init__(self, obj_to_clone: Any, name: Union[str, Expression], depth: Union[int, Expression]) -> None: + def __init__( + self, + obj_to_clone: Any, + name: Union[str, Expression], + depth: Union[int, Expression], + ) -> None: self.obj_to_clone = obj_to_clone self.name = name self.depth = depth @@ -260,7 +271,9 @@ class GetURL2Statement(Statement): class SetMemberStatement(Statement): # Call a method on an object. - def __init__(self, objectref: Any, name: Union[str, int, Expression], valueref: Any) -> None: + def __init__( + self, objectref: Any, name: Union[str, int, Expression], valueref: Any + ) -> None: self.objectref = objectref self.name = name self.valueref = valueref @@ -353,7 +366,7 @@ class StoreRegisterStatement(Statement): self.valueref = valueref def code_equiv(self) -> str: - return self.register.render('') + return self.register.render("") def __repr__(self) -> str: val = value_ref(self.valueref, "") @@ -472,7 +485,9 @@ class AndIf(IfExpr): # for a fact that this if can never be true. self.__false = True else: - self.__false = self.left.is_always_false() or self.right.is_always_false() + self.__false = ( + self.left.is_always_false() or self.right.is_always_false() + ) return self.__false def simplify(self) -> "IfExpr": @@ -547,11 +562,16 @@ class AndIf(IfExpr): def __hash__(self) -> int: if self.__hash is None: - self.__hash = hash("AND:" + ",".join(sorted(str(hash(s)) for s in set(_gather_and(self))))) + self.__hash = hash( + "AND:" + ",".join(sorted(str(hash(s)) for s in set(_gather_and(self)))) + ) return self.__hash def __repr__(self) -> str: - return " && ".join((f"({c!r})" if isinstance(c, (AndIf, OrIf)) else repr(c)) for c in _gather_and(self)) + return " && ".join( + (f"({c!r})" if isinstance(c, (AndIf, OrIf)) else repr(c)) + for c in _gather_and(self) + ) class OrIf(IfExpr): @@ -667,11 +687,16 @@ class OrIf(IfExpr): def __hash__(self) -> int: if self.__hash is None: - self.__hash = hash("OR:" + ",".join(sorted(str(hash(s)) for s in set(_gather_or(self))))) + self.__hash = hash( + "OR:" + ",".join(sorted(str(hash(s)) for s in set(_gather_or(self)))) + ) return self.__hash def __repr__(self) -> str: - return " || ".join((f"({c!r})" if isinstance(c, (AndIf, OrIf)) else repr(c)) for c in _gather_or(self)) + return " || ".join( + (f"({c!r})" if isinstance(c, (AndIf, OrIf)) else repr(c)) + for c in _gather_or(self) + ) def _gather_and(obj: IfExpr) -> List[IfExpr]: @@ -708,7 +733,9 @@ def _factor_and(left: IfExpr, right: IfExpr) -> Optional[IfExpr]: if not left_ors or not right_ors: return _accum_or(commons).simplify() - return OrIf(_accum_or(commons), AndIf(_accum_or(left_ors), _accum_or(right_ors))).simplify() + return OrIf( + _accum_or(commons), AndIf(_accum_or(left_ors), _accum_or(right_ors)) + ).simplify() else: return None @@ -769,7 +796,9 @@ def _factor_or(left: IfExpr, right: IfExpr) -> Optional[IfExpr]: if not left_ands or not right_ands: return _accum_and(commons).simplify() - return AndIf(_accum_and(commons), OrIf(_accum_and(left_ands), _accum_and(right_ands))).simplify() + return AndIf( + _accum_and(commons), OrIf(_accum_and(left_ands), _accum_and(right_ands)) + ).simplify() else: return None @@ -903,9 +932,13 @@ class TwoParameterIf(IfExpr): if self.comp == self.GT_EQUALS: return TwoParameterIf(self.conditional1, self.LT, self.conditional2) if self.comp == self.STRICT_EQUALS: - return TwoParameterIf(self.conditional1, self.STRICT_NOT_EQUALS, self.conditional2) + return TwoParameterIf( + self.conditional1, self.STRICT_NOT_EQUALS, self.conditional2 + ) if self.comp == self.STRICT_NOT_EQUALS: - return TwoParameterIf(self.conditional1, self.STRICT_EQUALS, self.conditional2) + return TwoParameterIf( + self.conditional1, self.STRICT_EQUALS, self.conditional2 + ) raise Exception(f"Cannot invert {self.comp}!") def swap(self) -> "TwoParameterIf": @@ -922,9 +955,13 @@ class TwoParameterIf(IfExpr): if self.comp == self.GT_EQUALS: return TwoParameterIf(self.conditional2, self.LT_EQUALS, self.conditional1) if self.comp == self.STRICT_EQUALS: - return TwoParameterIf(self.conditional2, self.STRICT_EQUALS, self.conditional1) + return TwoParameterIf( + self.conditional2, self.STRICT_EQUALS, self.conditional1 + ) if self.comp == self.STRICT_NOT_EQUALS: - return TwoParameterIf(self.conditional2, self.STRICT_NOT_EQUALS, self.conditional1) + return TwoParameterIf( + self.conditional2, self.STRICT_NOT_EQUALS, self.conditional1 + ) raise Exception(f"Cannot swap {self.comp}!") def __repr__(self) -> str: @@ -934,7 +971,12 @@ class TwoParameterIf(IfExpr): class IfStatement(Statement): - def __init__(self, cond: IfExpr, true_statements: Sequence[Statement], false_statements: Sequence[Statement]) -> None: + def __init__( + self, + cond: IfExpr, + true_statements: Sequence[Statement], + false_statements: Sequence[Statement], + ) -> None: self.cond = cond self.true_statements = list(true_statements) self.false_statements = list(false_statements) @@ -949,19 +991,19 @@ class IfStatement(Statement): false_entries.extend([f" {s}" for s in str(statement).split(os.linesep)]) if false_entries: - return os.linesep.join([ - f"if ({self.cond}) {{", - os.linesep.join(true_entries), - "} else {", - os.linesep.join(false_entries), - "}" - ]) + return os.linesep.join( + [ + f"if ({self.cond}) {{", + os.linesep.join(true_entries), + "} else {", + os.linesep.join(false_entries), + "}", + ] + ) else: - return os.linesep.join([ - f"if ({self.cond}) {{", - os.linesep.join(true_entries), - "}" - ]) + return os.linesep.join( + [f"if ({self.cond}) {{", os.linesep.join(true_entries), "}"] + ) def render(self, prefix: str) -> List[str]: true_entries: List[str] = [] @@ -981,14 +1023,14 @@ class IfStatement(Statement): f"{prefix}else", f"{prefix}{{", *false_entries, - f"{prefix}}}" + f"{prefix}}}", ] else: return [ f"{prefix}if ({self.cond})", f"{prefix}{{", *true_entries, - f"{prefix}}}" + f"{prefix}}}", ] @@ -1001,11 +1043,7 @@ class DoWhileStatement(Statement): for statement in self.body: entries.extend([f" {s}" for s in str(statement).split(os.linesep)]) - return os.linesep.join([ - "do {", - os.linesep.join(entries), - "} while (True)" - ]) + return os.linesep.join(["do {", os.linesep.join(entries), "} while (True)"]) def render(self, prefix: str) -> List[str]: entries: List[str] = [] @@ -1023,7 +1061,15 @@ class DoWhileStatement(Statement): class ForStatement(DoWhileStatement): # Special case of a DoWhileStatement that tracks its own exit condition and increment. - def __init__(self, inc_variable: str, inc_init: Any, cond: IfExpr, inc_assign: Any, body: Sequence[Statement], local: bool = False) -> None: + def __init__( + self, + inc_variable: str, + inc_init: Any, + cond: IfExpr, + inc_assign: Any, + body: Sequence[Statement], + local: bool = False, + ) -> None: super().__init__(body) self.inc_variable = inc_variable self.inc_init = inc_init @@ -1043,11 +1089,13 @@ class ForStatement(DoWhileStatement): else: local = "" - return os.linesep.join([ - f"for ({local}{self.inc_variable} = {inc_init}; {self.cond}; {self.inc_variable} = {inc_assign}) {{", - os.linesep.join(entries), - "}" - ]) + return os.linesep.join( + [ + f"for ({local}{self.inc_variable} = {inc_init}; {self.cond}; {self.inc_variable} = {inc_assign}) {{", + os.linesep.join(entries), + "}", + ] + ) def render(self, prefix: str) -> List[str]: entries: List[str] = [] @@ -1080,11 +1128,9 @@ class WhileStatement(DoWhileStatement): for statement in self.body: entries.extend([f" {s}" for s in str(statement).split(os.linesep)]) - return os.linesep.join([ - f"while ({self.cond}) {{", - os.linesep.join(entries), - "}" - ]) + return os.linesep.join( + [f"while ({self.cond}) {{", os.linesep.join(entries), "}"] + ) def render(self, prefix: str) -> List[str]: entries: List[str] = [] @@ -1111,15 +1157,19 @@ class SwitchCase: if self.const is not None: const = value_ref(self.const, "") - return os.linesep.join([ - f"case {const}:", - os.linesep.join(entries), - ]) + return os.linesep.join( + [ + f"case {const}:", + os.linesep.join(entries), + ] + ) else: - return os.linesep.join([ - "default:", - os.linesep.join(entries), - ]) + return os.linesep.join( + [ + "default:", + os.linesep.join(entries), + ] + ) def render(self, prefix: str) -> List[str]: entries: List[str] = [] @@ -1150,11 +1200,7 @@ class SwitchStatement(Statement): cases.extend([f" {s}" for s in str(case).split(os.linesep)]) check = object_ref(self.check_variable, "") - return os.linesep.join([ - f"switch ({check}) {{", - os.linesep.join(cases), - "}" - ]) + return os.linesep.join([f"switch ({check}) {{", os.linesep.join(cases), "}"]) def render(self, prefix: str) -> List[str]: cases: List[str] = [] @@ -1162,9 +1208,4 @@ class SwitchStatement(Statement): cases.extend(case.render(prefix=prefix + " ")) check = object_ref(self.check_variable, prefix) - return [ - f"{prefix}switch ({check})", - f"{prefix}{{", - *cases, - f"{prefix}}}" - ] + return [f"{prefix}switch ({check})", f"{prefix}{{", *cases, f"{prefix}}}"] diff --git a/bemani/format/afp/util.py b/bemani/format/afp/util.py index 45398cc..be95263 100644 --- a/bemani/format/afp/util.py +++ b/bemani/format/afp/util.py @@ -20,18 +20,18 @@ def descramble_text(text: bytes, obfuscated: bool) -> str: if obfuscated and (text[0] - 0x20) > 0x7F: # Gotta do a weird demangling where we swap the # top bit. - return bytes(((x + 0x80) & 0xFF) for x in text).decode('ascii') + return bytes(((x + 0x80) & 0xFF) for x in text).decode("ascii") else: - return text.decode('ascii') + return text.decode("ascii") else: return "" def scramble_text(text: str, obfuscated: bool) -> bytes: if obfuscated: - return bytes(((x + 0x80) & 0xFF) for x in text.encode('ascii')) + b'\0' + return bytes(((x + 0x80) & 0xFF) for x in text.encode("ascii")) + b"\0" else: - return text.encode('ascii') + b'\0' + return text.encode("ascii") + b"\0" class TrackedCoverageManager: @@ -69,11 +69,21 @@ class TrackedCoverage: raise Exception(f"Already covered {hex(offset)}!") self.coverage[i] = True - def print_coverage(self, req_start: Optional[int] = None, req_end: Optional[int] = None) -> None: + def print_coverage( + self, req_start: Optional[int] = None, req_end: Optional[int] = None + ) -> None: for start, offset in self.get_uncovered_chunks(req_start, req_end): - print(f"Uncovered: {hex(start)} - {hex(offset)} ({offset-start} bytes)", file=sys.stderr) + print( + f"Uncovered: {hex(start)} - {hex(offset)} ({offset-start} bytes)", + file=sys.stderr, + ) - def get_uncovered_chunks(self, req_start: Optional[int] = None, req_end: Optional[int] = None, adjust_offsets: bool = False) -> List[Tuple[int, int]]: + def get_uncovered_chunks( + self, + req_start: Optional[int] = None, + req_end: Optional[int] = None, + adjust_offsets: bool = False, + ) -> List[Tuple[int, int]]: # First offset that is not coverd in a run. start: Optional[int] = None chunks: List[Tuple[int, int]] = [] @@ -117,7 +127,12 @@ class TrackedCoverage: end = req_end if adjust_offsets: - filtered_chunks.append((start - req_start if req_start else 0, end - req_start if req_start else 0)) + filtered_chunks.append( + ( + start - req_start if req_start else 0, + end - req_start if req_start else 0, + ) + ) else: filtered_chunks.append((start, end)) return filtered_chunks @@ -150,7 +165,9 @@ class VerboseOutput: return VerboseOutputManager(self, verbose) def vprint(self, *args: Any, **kwargs: Any) -> None: - should_print = self.verbose or (kwargs.get('component', None) in self.components) - kwargs = {k: v for k, v in kwargs.items() if k != 'component'} + should_print = self.verbose or ( + kwargs.get("component", None) in self.components + ) + kwargs = {k: v for k, v in kwargs.items() if k != "component"} if should_print: print(*args, **kwargs, file=sys.stderr) diff --git a/bemani/format/arc.py b/bemani/format/arc.py index 5c64f1f..130c890 100644 --- a/bemani/format/arc.py +++ b/bemani/format/arc.py @@ -20,19 +20,21 @@ class ARC: def __parse_file(self, data: bytes) -> None: # Check file header if data[0:4] != bytes([0x20, 0x11, 0x75, 0x19]): - raise Exception('Unknown file format!') + raise Exception("Unknown file format!") # Grab header offsets - (_, numfiles, _) = struct.unpack(' Tuple[int, int, int]: # This function converts RGB565 format to raw pixels @@ -37,10 +39,12 @@ class DXTBuffer: def swapbytes(self, data: bytes, swap: bool) -> bytes: if swap: - return b"".join([ - data[(x + 1):(x + 2)] + data[x:(x + 1)] - for x in range(0, len(data), 2) - ]) + return b"".join( + [ + data[(x + 1) : (x + 2)] + data[x : (x + 1)] + for x in range(0, len(data), 2) + ] + ) return data def DXT5Decompress(self, filedata: bytes, swap: bool = False) -> bytes: @@ -49,7 +53,9 @@ class DXTBuffer: for row in range(self.block_county): for col in range(self.block_countx): # Get the alpha values, and color lookup table - a0, a1, acode0, acode1, c0, c1, ctable = struct.unpack(" bytes: # Loop through each block and decompress it @@ -74,7 +80,9 @@ class DXTBuffer: for col in range(self.block_countx): # Color 1 color 2, color look up table - c0, c1, ctable = struct.unpack(" None: - code = (ctable >> (2 * ((4 * j) + i))) & 0x03 # Get the color of the current pixel + code = ( + ctable >> (2 * ((4 * j) + i)) + ) & 0x03 # Get the color of the current pixel pixel_color = None r0, g0, b0 = self.unpackRGB(c0) @@ -116,19 +126,29 @@ class DXTBuffer: pixel_color = (r1, g1, b1, alpha) if code == 2: if c0 > c1: - pixel_color = ((2 * r0 + r1) // 3, (2 * g0 + g1) // 3, (2 * b0 + b1) // 3, alpha) + pixel_color = ( + (2 * r0 + r1) // 3, + (2 * g0 + g1) // 3, + (2 * b0 + b1) // 3, + alpha, + ) else: pixel_color = ((r0 + r1) // 2, (g0 + g1) // 2, (b0 + b1) // 2, alpha) if code == 3: if c0 > c1: - pixel_color = ((r0 + 2 * r1) // 3, (g0 + 2 * g1) // 3, (b0 + 2 * b1) // 3, alpha) + pixel_color = ( + (r0 + 2 * r1) // 3, + (g0 + 2 * g1) // 3, + (b0 + 2 * b1) // 3, + alpha, + ) else: pixel_color = (0, 0, 0, alpha) # While not surpassing the image dimensions, assign pixels the colors. if pixel_color is not None and (x + i) < self.width and (y + j) < self.height: - self.decompressed_buffer[(y + j) * self.width + (x + i)] = ( - struct.pack(' int: diff --git a/bemani/format/ifs.py b/bemani/format/ifs.py index a8a773b..7455286 100644 --- a/bemani/format/ifs.py +++ b/bemani/format/ifs.py @@ -22,10 +22,10 @@ class IFS: def __init__( self, data: bytes, - decode_binxml: bool=False, - decode_textures: bool=False, - keep_hex_names: bool=False, - reference_loader: Optional[Callable[[str], Optional["IFS"]]]=None, + decode_binxml: bool = False, + decode_textures: bool = False, + keep_hex_names: bool = False, + reference_loader: Optional[Callable[[str], Optional["IFS"]]] = None, ) -> None: self.__files: Dict[str, bytes] = {} self.__formats: Dict[str, str] = {} @@ -39,22 +39,29 @@ class IFS: self.__parse_file(data) def __fix_name(self, filename: str) -> str: - if filename[0] == '_' and filename[1].isdigit(): + if filename[0] == "_" and filename[1].isdigit(): filename = filename[1:] - filename = filename.replace('_E', '.') - filename = filename.replace('__', '_') + filename = filename.replace("_E", ".") + filename = filename.replace("__", "_") return filename def __parse_file(self, data: bytes) -> None: # Grab the magic values and make sure this is an IFS - (signature, version, version_crc, pack_time, unpacked_header_size, data_index) = struct.unpack( - '>IHHIII', + ( + signature, + version, + version_crc, + pack_time, + unpacked_header_size, + data_index, + ) = struct.unpack( + ">IHHIII", data[0:20], ) if signature != 0x6CAD8F89: - raise Exception('Invalid IFS file!') + raise Exception("Invalid IFS file!") if version ^ version_crc != 0xFFFF: - raise Exception('Corrupt version in IFS file!') + raise Exception("Corrupt version in IFS file!") if version == 1: # No header MD5 @@ -71,48 +78,55 @@ class IFS: # Now, try as XML xenc = XmlEncoding() header = xenc.decode( - b'' + - data[header_offset:data_index].split(b'\0')[0] + b'' + + data[header_offset:data_index].split(b"\0")[0] ) if header is None: - raise Exception('Invalid IFS file!') + raise Exception("Invalid IFS file!") files: Dict[str, Tuple[int, int, int, Optional[str]]] = {} - if header.name != 'imgfs': - raise Exception('Unknown IFS format!') + if header.name != "imgfs": + raise Exception("Unknown IFS format!") # Grab any super-files that this file might reference. header_md5: Optional[int] = None header_size: Optional[int] = None - supers: List[Tuple[str, bytes]] = [('__INVALID__', b'')] + supers: List[Tuple[str, bytes]] = [("__INVALID__", b"")] for child in header.children: if child.name == "_info_": - header_md5 = child.child_value('md5') # NOQA - header_size = child.child_value('size') # NOQA + header_md5 = child.child_value("md5") # NOQA + header_size = child.child_value("size") # NOQA elif child.name == "_super_": super_name = child.value - super_md5 = child.child_value('md5') + super_md5 = child.child_value("md5") if not isinstance(super_name, str) or not isinstance(super_md5, bytes): - raise Exception(f'Super definition {child} has invalid data!') + raise Exception(f"Super definition {child} has invalid data!") supers.append((super_name, super_md5)) def get_children(parent: str, node: Node) -> None: real_name = self.__fix_name(node.name) - if node.data_type == '3s32': - node_name = os.path.join(parent, real_name).replace(f'{os.sep}imgfs{os.sep}', '') + if node.data_type == "3s32": + node_name = os.path.join(parent, real_name).replace( + f"{os.sep}imgfs{os.sep}", "" + ) ref = None for subnode in node.children: - if subnode.name == 'i': + if subnode.name == "i": super_ref = subnode.value if super_ref > 0 or super_ref < len(supers): ref = supers[super_ref][0] else: ref = supers[0][0] - files[node_name] = (node.value[0] + data_index, node.value[1], node.value[2], ref) + files[node_name] = ( + node.value[0] + data_index, + node.value[1], + node.value[2], + ref, + ) else: for subchild in node.children: get_children(os.path.join(parent, f"{real_name}{os.sep}"), subchild) @@ -133,7 +147,9 @@ class IFS: ifsdata = self.__loader(external_file) if ifsdata is None: - raise Exception(f"Couldn't extract file data for {fn} referencing IFS file {external_file}!") + raise Exception( + f"Couldn't extract file data for {fn} referencing IFS file {external_file}!" + ) else: otherdata[external_file] = ifsdata @@ -142,7 +158,7 @@ class IFS: else: raise Exception(f"{fn} not found in {external_file} IFS!") else: - filedata = data[start:(start + size)] + filedata = data[start : (start + size)] if len(filedata) != size: raise Exception(f"Couldn't extract file data for {fn}!") self.__files[fn] = filedata @@ -163,44 +179,45 @@ class IFS: xenc = XmlEncoding() encoding = "ascii" texdata = xenc.decode( - b'' + - self.__files[filename] + b'' + self.__files[filename] ) if texdata is None: continue else: if benc.encoding is None: - raise Exception("Logic error, expected an encoding from binary decoder!") + raise Exception( + "Logic error, expected an encoding from binary decoder!" + ) encoding = benc.encoding - if texdata.name != 'texturelist': + if texdata.name != "texturelist": raise Exception(f"Unexpected name {texdata.name} in texture list!") - if texdata.attribute('compress') == 'avslz': + if texdata.attribute("compress") == "avslz": compressed = True else: compressed = False for child in texdata.children: - if child.name != 'texture': + if child.name != "texture": continue - textfmt = child.attribute('format') + textfmt = child.attribute("format") if textfmt is None: raise Exception(f"Texture {child} has no texture format!") for subchild in child.children: - if subchild.name != 'image': + if subchild.name != "image": continue - name = subchild.attribute('name') + name = subchild.attribute("name") if name is None: raise Exception(f"Texture entry {subchild} has no name!") - if ' ' in name: + if " " in name: # Certain files that were corrupted on create or copy # seem to have spaces in the name which shouldn't be # allowed. Lob them off. - name = name[:name.find(' ')] + name = name[: name.find(" ")] md5sum = hashlib.md5(name.encode(encoding)).hexdigest() oldname = os.path.join(texdir, md5sum) newname = os.path.join(texdir, name) @@ -225,7 +242,7 @@ class IFS: # Only pop down the format and sizes if we support extracting. self.__formats[newname] = textfmt - rect = subchild.child_value('imgrect') + rect = subchild.child_value("imgrect") if rect is not None: self.__imgsize[newname] = ( rect[0] // 2, @@ -233,7 +250,7 @@ class IFS: rect[2] // 2, rect[3] // 2, ) - rect = subchild.child_value('uvrect') + rect = subchild.child_value("uvrect") if rect is not None: self.__uvsize[newname] = ( rect[0] // 2, @@ -253,28 +270,29 @@ class IFS: if afpdata is None: # Now, try as XML xenc = XmlEncoding() - encoding = 'ascii' + encoding = "ascii" afpdata = xenc.decode( - b'' + - self.__files[filename] + b'' + self.__files[filename] ) if afpdata is None: continue else: if benc.encoding is None: - raise Exception("Logic error, expected an encoding from binary decoder!") + raise Exception( + "Logic error, expected an encoding from binary decoder!" + ) encoding = benc.encoding - if afpdata.name != 'afplist': + if afpdata.name != "afplist": raise Exception(f"Unexpected name {afpdata.name} in afp list!") for child in afpdata.children: - if child.name != 'afp': + if child.name != "afp": continue # First, fix up the afp files themselves. - name = child.attribute('name') + name = child.attribute("name") if name is None: raise Exception("AFP entry {child} has no name!") md5sum = hashlib.md5(name.encode(encoding)).hexdigest() @@ -315,20 +333,25 @@ class IFS: decompress = self.__compressed.get(filename, False) filedata = self.__files[filename] if decompress: - uncompressed_size, compressed_size = struct.unpack('>II', filedata[0:8]) + uncompressed_size, compressed_size = struct.unpack(">II", filedata[0:8]) if len(filedata) == compressed_size + 8: lz77 = Lz77() filedata = lz77.decompress(filedata[8:]) else: filedata = filedata[8:] + filedata[0:8] - if self.__decode_binxml and os.path.splitext(filename)[1] == '.xml': + if self.__decode_binxml and os.path.splitext(filename)[1] == ".xml": benc = BinaryEncoding() filexml = benc.decode(filedata) if filexml is not None: - filedata = str(filexml).encode('utf-8') + filedata = str(filexml).encode("utf-8") - if self.__decode_textures and filename in self.__formats and filename in self.__imgsize and filename in self.__uvsize: + if ( + self.__decode_textures + and filename in self.__formats + and filename in self.__imgsize + and filename in self.__uvsize + ): fmt = self.__formats[filename] img = self.__imgsize[filename] crop = self.__uvsize[filename] @@ -340,36 +363,40 @@ class IFS: if fmt == "argb8888rev": if len(filedata) < (width * height * 4): left = (width * height * 4) - len(filedata) - filedata = filedata + b'\x00' * left - png = Image.frombytes('RGBA', (width, height), filedata, 'raw', 'BGRA') - png = png.crop(( - crop[0] - img[0], - crop[2] - img[2], - crop[1] - img[0], - crop[3] - img[2], - )) + filedata = filedata + b"\x00" * left + png = Image.frombytes("RGBA", (width, height), filedata, "raw", "BGRA") + png = png.crop( + ( + crop[0] - img[0], + crop[2] - img[2], + crop[1] - img[0], + crop[3] - img[2], + ) + ) b = io.BytesIO() - png.save(b, format='PNG') + png.save(b, format="PNG") filedata = b.getvalue() elif fmt == "dxt5": dxt = DXTBuffer(width, height) png = Image.frombuffer( - 'RGBA', + "RGBA", (width, height), dxt.DXT5Decompress(filedata, swap=True), - 'raw', - 'RGBA', + "raw", + "RGBA", 0, 1, ) - png = png.crop(( - crop[0] - img[0], - crop[2] - img[2], - crop[1] - img[0], - crop[3] - img[2], - )) + png = png.crop( + ( + crop[0] - img[0], + crop[2] - img[2], + crop[1] - img[0], + crop[3] - img[2], + ) + ) b = io.BytesIO() - png.save(b, format='PNG') + png.save(b, format="PNG") filedata = b.getvalue() return filedata diff --git a/bemani/format/iidxchart.py b/bemani/format/iidxchart.py index f79bfa4..5c917de 100644 --- a/bemani/format/iidxchart.py +++ b/bemani/format/iidxchart.py @@ -23,7 +23,7 @@ class IIDXChart: def __parse_header(self, data: bytes) -> List[Tuple[int, int]]: header: List[Tuple[int, int]] = [] for i in range(12): - offset, length = struct.unpack(' None: self.__songs: Dict[int, Tuple[IIDXSong, int]] = {} self.__data = data @@ -39,13 +38,13 @@ class IIDXMusicDB: data = copy.deepcopy(self.__data) def format_string(string: str) -> bytes: - bdata = string.encode('shift-jis') + bdata = string.encode("shift-jis") if len(bdata) < 64: - bdata = bdata + (b'\0' * (64 - len(bdata))) + bdata = bdata + (b"\0" * (64 - len(bdata))) return bdata def copy_over(dst: bytes, src: bytes, base: int, offset: int) -> bytes: - return dst[:(base + offset)] + src + dst[(base + offset + len(src)):] + return dst[: (base + offset)] + src + dst[(base + offset + len(src)) :] for mid in self.__songs: song, offset = self.__songs[mid] @@ -66,19 +65,19 @@ class IIDXMusicDB: ) # Offset and difference lookup (not sure this is always right) if data[4] == 0x14: - offset = 0xa420 + offset = 0xA420 leap = 0x320 elif data[4] == 0x15: - offset = 0xabf0 + offset = 0xABF0 leap = 0x320 elif data[4] == 0x16: - offset = 0xb3c0 + offset = 0xB3C0 leap = 0x340 elif data[4] == 0x17: - offset = 0xbb90 + offset = 0xBB90 leap = 0x340 elif data[4] == 0x18: - offset = 0xc360 + offset = 0xC360 leap = 0x340 elif data[4] == 0x19: offset = 0xCB30 @@ -87,8 +86,8 @@ class IIDXMusicDB: offset = 0xD300 leap = 0x344 - if sig[0] != b'IIDX': - raise Exception(f'Invalid signature \'{sig[0]}\' found!') + if sig[0] != b"IIDX": + raise Exception(f"Invalid signature '{sig[0]}' found!") def parse_string(string: bytes) -> str: for i in range(len(string)): @@ -96,7 +95,7 @@ class IIDXMusicDB: string = string[:i] break - return string.decode('shift-jis') + return string.decode("shift-jis") # Load songs while True: @@ -118,16 +117,25 @@ class IIDXMusicDB: parse_string(songdata[1]), parse_string(songdata[2]), parse_string(songdata[3]), - [songdata[5], songdata[6], songdata[7], songdata[8], songdata[9], songdata[10]], + [ + songdata[5], + songdata[6], + songdata[7], + songdata[8], + songdata[9], + songdata[10], + ], songdata[4], ) - if song.artist == 'event_data' and song.genre == 'event_data': + if song.artist == "event_data" and song.genre == "event_data": continue self.__songs[songdata[11]] = (song, songoffset) @property def songs(self) -> List[IIDXSong]: - return sorted([self.__songs[mid][0] for mid in self.__songs], key=lambda song: song.id) + return sorted( + [self.__songs[mid][0] for mid in self.__songs], key=lambda song: song.id + ) @property def songids(self) -> List[int]: diff --git a/bemani/format/twodx.py b/bemani/format/twodx.py index 58c7e8b..d8dc8b6 100644 --- a/bemani/format/twodx.py +++ b/bemani/format/twodx.py @@ -15,43 +15,57 @@ class TwoDX: def __parse_file(self, data: bytes) -> None: # Parse file header - (name, headerSize, numfiles) = struct.unpack('<16sII', data[0:24]) - self.__name = name.split(b'\x00')[0].decode('ascii') + (name, headerSize, numfiles) = struct.unpack("<16sII", data[0:24]) + self.__name = name.split(b"\x00")[0].decode("ascii") if headerSize != (72 + (4 * numfiles)): - raise Exception('Unrecognized 2dx file header!') + raise Exception("Unrecognized 2dx file header!") - fileoffsets = struct.unpack('<' + ''.join(['I' for _ in range(numfiles)]), data[72:(72 + (4 * numfiles))]) + fileoffsets = struct.unpack( + "<" + "".join(["I" for _ in range(numfiles)]), + data[72 : (72 + (4 * numfiles))], + ) fileno = 1 for offset in fileoffsets: - (magic, headerSize, wavSize, _, track, _, attenuation, loop) = struct.unpack( - '<4sIIhhhhi', - data[offset:(offset + 24)], + ( + magic, + headerSize, + wavSize, + _, + track, + _, + attenuation, + loop, + ) = struct.unpack( + "<4sIIhhhhi", + data[offset : (offset + 24)], ) - if magic != b'2DX9': - raise Exception('Unrecognized entry in file!') + if magic != b"2DX9": + raise Exception("Unrecognized entry in file!") if headerSize != 24: - raise Exception('Unrecognized subheader in file!') + raise Exception("Unrecognized subheader in file!") wavOffset = offset + headerSize - wavData = data[wavOffset:(wavOffset + wavSize)] + wavData = data[wavOffset : (wavOffset + wavSize)] - self.__files[f'{self.__name}_{fileno}.wav'] = wavData + self.__files[f"{self.__name}_{fileno}.wav"] = wavData fileno = fileno + 1 @property def name(self) -> str: if self.__name is None: - raise Exception("Logic error, tried to get name of 2dx file before setting it or parsing file!") + raise Exception( + "Logic error, tried to get name of 2dx file before setting it or parsing file!" + ) return self.__name def set_name(self, name: str) -> None: if len(name) <= 16: self.__name = name else: - raise Exception('Name of archive too long!') + raise Exception("Name of archive too long!") @property def filenames(self) -> List[str]: @@ -65,38 +79,40 @@ class TwoDX: def get_new_data(self) -> bytes: if not self.__files: - raise Exception('No files to write!') + raise Exception("No files to write!") if not self.__name: - raise Exception('2dx archive name not set!') + raise Exception("2dx archive name not set!") - name = self.__name.encode('ascii') + name = self.__name.encode("ascii") while len(name) < 16: - name = name + b'\x00' + name = name + b"\x00" filedata = [self.__files[x] for x in self.__files] # Header length is also the base offset for the first file baseoffset = 72 + (4 * len(filedata)) - data = [struct.pack('<16sII', name, baseoffset, len(filedata)) + (b'\x00' * 48)] + data = [struct.pack("<16sII", name, baseoffset, len(filedata)) + (b"\x00" * 48)] # Calculate offset this will go to for bytedata in filedata: # Add where this file will go, then calculate the length - data.append(struct.pack(' Response: - username = request.form['username'] - password = request.form['password'] + username = request.form["username"] + password = request.form["password"] userid = g.data.local.user.from_username(username) if userid is None: - error('Unrecognized username or password!') - return Response(render_template('account/login.html', **{'title': 'Log In', 'show_navigation': False, 'username': username})) + error("Unrecognized username or password!") + return Response( + render_template( + "account/login.html", + **{"title": "Log In", "show_navigation": False, "username": username}, + ) + ) if g.data.local.user.validate_password(userid, password): aes = AESCipher(g.config.secret_key) sessionID = g.data.local.user.create_session(userid, expiration=90 * 86400) - response = make_response(redirect(url_for('home_pages.viewhome'))) + response = make_response(redirect(url_for("home_pages.viewhome"))) response.set_cookie( - 'SessionID', + "SessionID", aes.encrypt(sessionID), expires=Time.now() + (90 * Time.SECONDS_IN_DAY), ) return response else: - error('Unrecognized username or password!') - return Response(render_template('account/login.html', **{'title': 'Log In', 'show_navigation': False, 'username': username})) + error("Unrecognized username or password!") + return Response( + render_template( + "account/login.html", + **{"title": "Log In", "show_navigation": False, "username": username}, + ) + ) -@account_pages.route('/login') +@account_pages.route("/login") @loginprohibited def viewlogin() -> Response: - return Response(render_template('account/login.html', **{'title': 'Log In', 'show_navigation': False})) + return Response( + render_template( + "account/login.html", **{"title": "Log In", "show_navigation": False} + ) + ) def register_display(card_number: str, username: str, email: str) -> Response: - return Response(render_template( - 'account/register.html', - **{ - 'title': 'Register New Account', - 'show_navigation': False, - 'card_number': card_number, - 'username': username, - 'email': email, - }, - )) + return Response( + render_template( + "account/register.html", + **{ + "title": "Register New Account", + "show_navigation": False, + "card_number": card_number, + "username": username, + "email": email, + }, + ) + ) -@account_pages.route('/register', methods=['POST']) +@account_pages.route("/register", methods=["POST"]) @loginprohibited def register() -> Response: - card_number = request.form['card_number'] - pin = request.form['pin'] - username = request.form['username'] - email = request.form['email'] - password1 = request.form['password1'] - password2 = request.form['password2'] + card_number = request.form["card_number"] + pin = request.form["pin"] + username = request.form["username"] + email = request.form["email"] + password1 = request.form["password1"] + password2 = request.form["password2"] # First, try to convert the card to a valid E004 ID try: cardid = CardCipher.decode(card_number) except CardCipherException: - error('Invalid card number!') + error("Invalid card number!") return register_display(card_number, username, email) # Now, see if this card ID exists already userid = g.data.local.user.from_cardid(cardid) if userid is None: - error('This card has not been used on the network yet!') + error("This card has not been used on the network yet!") return register_display(card_number, username, email) # Now, make sure this user doesn't already have an account user = g.data.local.user.get_user(userid) if user.username is not None or user.email is not None: - error('This card is already in use!') + error("This card is already in use!") return register_display(card_number, username, email) # Now, see if the pin is correct if not g.data.local.user.validate_pin(userid, pin): - error('The entered PIN does not match the PIN on the card!') + error("The entered PIN does not match the PIN on the card!") return register_display(card_number, username, email) # Now, see if the username is valid if not valid_username(username): - error('Invalid username!') + error("Invalid username!") return register_display(card_number, username, email) # Now, check whether the username is already in use if g.data.local.user.from_username(username) is not None: - error('The chosen username is already in use!') + error("The chosen username is already in use!") return register_display(card_number, username, email) # Now, see if the email address is valid if not valid_email(email): - error('Invalid email address!') + error("Invalid email address!") return register_display(card_number, username, email) # Now, make sure that the passwords match if password1 != password2: - error('Passwords do not match each other!') + error("Passwords do not match each other!") return register_display(card_number, username, email) # Now, make sure passwords are long enough if len(password1) < 6: - error('Password is not long enough!') + error("Password is not long enough!") return register_display(card_number, username, email) # Now, create the account. @@ -129,91 +163,96 @@ def register() -> Response: # Now, log them into that created account! aes = AESCipher(g.config.secret_key) sessionID = g.data.local.user.create_session(userid) - success('Successfully registered account!') - response = make_response(redirect(url_for('home_pages.viewhome'))) - response.set_cookie('SessionID', aes.encrypt(sessionID)) + success("Successfully registered account!") + response = make_response(redirect(url_for("home_pages.viewhome"))) + response.set_cookie("SessionID", aes.encrypt(sessionID)) return response -@account_pages.route('/register') +@account_pages.route("/register") @loginprohibited def viewregister() -> Response: - return Response(render_template('account/register.html', **{'title': 'Register New Account', 'show_navigation': False})) + return Response( + render_template( + "account/register.html", + **{"title": "Register New Account", "show_navigation": False}, + ) + ) -@account_pages.route('/logout') +@account_pages.route("/logout") @loginrequired def logout() -> Response: g.data.local.user.destroy_session(g.sessionID) - response = make_response(redirect(url_for('account_pages.viewlogin'))) - response.set_cookie('SessionID', '', expires=0) - success('Successfully logged out!') + response = make_response(redirect(url_for("account_pages.viewlogin"))) + response.set_cookie("SessionID", "", expires=0) + success("Successfully logged out!") return response -@account_pages.route('/account') +@account_pages.route("/account") @loginrequired def viewaccount() -> Response: user = g.data.local.user.get_user(g.userID) return render_react( - 'Account Management', - 'account/account.react.js', + "Account Management", + "account/account.react.js", { - 'email': user.email, - 'username': user.username, + "email": user.email, + "username": user.username, }, { - 'updateemail': url_for('account_pages.updateemail'), - 'updatepin': url_for('account_pages.updatepin'), - 'updatepassword': url_for('account_pages.updatepassword'), + "updateemail": url_for("account_pages.updateemail"), + "updatepin": url_for("account_pages.updatepin"), + "updatepassword": url_for("account_pages.updatepassword"), }, ) -@account_pages.route('/account/cards') +@account_pages.route("/account/cards") @loginrequired def viewcards() -> Response: cards = [CardCipher.encode(card) for card in g.data.local.user.get_cards(g.userID)] return render_react( - 'Card Management', - 'account/cards.react.js', + "Card Management", + "account/cards.react.js", { - 'cards': cards, + "cards": cards, }, { - 'addcard': url_for('account_pages.addcard'), - 'removecard': url_for('account_pages.removecard'), - 'listcards': url_for('account_pages.listcards'), + "addcard": url_for("account_pages.addcard"), + "removecard": url_for("account_pages.removecard"), + "listcards": url_for("account_pages.listcards"), }, ) -@account_pages.route('/account/cards/list') +@account_pages.route("/account/cards/list") @jsonify @loginrequired def listcards() -> Dict[str, Any]: # Return new card list cards = [CardCipher.encode(card) for card in g.data.local.user.get_cards(g.userID)] return { - 'cards': cards, + "cards": cards, } -@account_pages.route('/account/cards/add', methods=['POST']) +@account_pages.route("/account/cards/add", methods=["POST"]) @jsonify @loginrequired def addcard() -> Dict[str, Any]: # Grab card, convert it - card = request.get_json()['card'] + card = request.get_json()["card"] try: cardid = CardCipher.decode(card) except CardCipherException: - raise Exception('Invalid card number!') + raise Exception("Invalid card number!") # See if it is already claimed userid = g.data.local.user.from_cardid(cardid) if userid is not None: - raise Exception('This card is already in use!') + raise Exception("This card is already in use!") # Add it to this user's account g.data.local.user.add_card(g.userID, cardid) @@ -221,25 +260,25 @@ def addcard() -> Dict[str, Any]: # Return new card list cards = [CardCipher.encode(card) for card in g.data.local.user.get_cards(g.userID)] return { - 'cards': cards, + "cards": cards, } -@account_pages.route('/account/cards/remove', methods=['POST']) +@account_pages.route("/account/cards/remove", methods=["POST"]) @jsonify @loginrequired def removecard() -> Dict[str, Any]: # Grab card, convert it - card = request.get_json()['card'] + card = request.get_json()["card"] try: cardid = CardCipher.decode(card) except CardCipherException: - raise Exception('Invalid card number!') + raise Exception("Invalid card number!") # Make sure it is our card userid = g.data.local.user.from_cardid(cardid) if userid != g.userID: - raise Exception('This card is not yours to delete!') + raise Exception("This card is not yours to delete!") # Remove it from this user's account g.data.local.user.destroy_card(g.userID, cardid) @@ -247,26 +286,26 @@ def removecard() -> Dict[str, Any]: # Return new card list cards = [CardCipher.encode(card) for card in g.data.local.user.get_cards(g.userID)] return { - 'cards': cards, + "cards": cards, } -@account_pages.route('/account/email/update', methods=['POST']) +@account_pages.route("/account/email/update", methods=["POST"]) @jsonify @loginrequired def updateemail() -> Dict[str, Any]: - email = request.get_json()['email'] - password = request.get_json()['password'] + email = request.get_json()["email"] + password = request.get_json()["password"] user = g.data.local.user.get_user(g.userID) if user is None: - raise Exception('Unable to find user to update!') + raise Exception("Unable to find user to update!") # Make sure current password matches if not g.data.local.user.validate_password(g.userID, password): - raise Exception('Current password is not correct!') + raise Exception("Current password is not correct!") if not valid_email(email): - raise Exception('Invalid email address!') + raise Exception("Invalid email address!") # Update and save user.email = email @@ -274,21 +313,21 @@ def updateemail() -> Dict[str, Any]: # Return updated email return { - 'email': email, + "email": email, } -@account_pages.route('/account/pin/update', methods=['POST']) +@account_pages.route("/account/pin/update", methods=["POST"]) @jsonify @loginrequired def updatepin() -> Dict[str, Any]: - pin = request.get_json()['pin'] + pin = request.get_json()["pin"] user = g.data.local.user.get_user(g.userID) if user is None: - raise Exception('Unable to find user to update!') + raise Exception("Unable to find user to update!") - if not valid_pin(pin, 'card'): - raise Exception('Invalid PIN, must be exactly 4 digits!') + if not valid_pin(pin, "card"): + raise Exception("Invalid PIN, must be exactly 4 digits!") # Update and save g.data.local.user.update_pin(g.userID, pin) @@ -297,28 +336,28 @@ def updatepin() -> Dict[str, Any]: return {} -@account_pages.route('/account/password/update', methods=['POST']) +@account_pages.route("/account/password/update", methods=["POST"]) @jsonify @loginrequired def updatepassword() -> Dict[str, Any]: - old = request.get_json()['old'] - new1 = request.get_json()['new1'] - new2 = request.get_json()['new2'] + old = request.get_json()["old"] + new1 = request.get_json()["new1"] + new2 = request.get_json()["new2"] user = g.data.local.user.get_user(g.userID) if user is None: - raise Exception('Unable to find user to update!') + raise Exception("Unable to find user to update!") # Make sure current password matches if not g.data.local.user.validate_password(g.userID, old): - raise Exception('Current password is not correct!') + raise Exception("Current password is not correct!") # Now, make sure that the passwords match if new1 != new2: - raise Exception('Passwords do not match each other!') + raise Exception("Passwords do not match each other!") # Now, make sure passwords are long enough if len(new1) < 6: - raise Exception('Password is not long enough!') + raise Exception("Password is not long enough!") # Update and save g.data.local.user.update_password(g.userID, new1) diff --git a/bemani/frontend/admin/admin.py b/bemani/frontend/admin/admin.py index cccd1ac..b51b68c 100644 --- a/bemani/frontend/admin/admin.py +++ b/bemani/frontend/admin/admin.py @@ -3,10 +3,23 @@ from typing import Dict, Tuple, Any, Optional from flask import Blueprint, request, Response, render_template, url_for from bemani.backend.base import Base -from bemani.common import CardCipher, CardCipherException, GameConstants, RegionConstants, ValidatedDict +from bemani.common import ( + CardCipher, + CardCipherException, + GameConstants, + RegionConstants, + ValidatedDict, +) from bemani.data import Arcade, Machine, User, UserID, News, Event, Server, Client from bemani.data.api.client import APIClient, NotAuthorizedAPIException, APIException -from bemani.frontend.app import adminrequired, jsonify, valid_email, valid_username, valid_pin, render_react +from bemani.frontend.app import ( + adminrequired, + jsonify, + valid_email, + valid_username, + valid_pin, + render_react, +) from bemani.frontend.gamesettings import get_game_settings from bemani.frontend.iidx.iidx import IIDXFrontend from bemani.frontend.jubeat.jubeat import JubeatFrontend @@ -16,9 +29,9 @@ from bemani.frontend.static import static_location from bemani.frontend.types import g admin_pages = Blueprint( - 'admin_pages', + "admin_pages", __name__, - url_prefix='/admin', + url_prefix="/admin", template_folder=templates_location, static_folder=static_location, ) @@ -31,27 +44,27 @@ def format_arcade(arcade: Arcade) -> Dict[str, Any]: if user is not None: owners.append(user.username) return { - 'id': arcade.id, - 'name': arcade.name, - 'description': arcade.description, - 'region': arcade.region, - 'paseli_enabled': arcade.data.get_bool('paseli_enabled'), - 'paseli_infinite': arcade.data.get_bool('paseli_infinite'), - 'mask_services_url': arcade.data.get_bool('mask_services_url'), - 'owners': owners, + "id": arcade.id, + "name": arcade.name, + "description": arcade.description, + "region": arcade.region, + "paseli_enabled": arcade.data.get_bool("paseli_enabled"), + "paseli_infinite": arcade.data.get_bool("paseli_infinite"), + "mask_services_url": arcade.data.get_bool("mask_services_url"), + "owners": owners, } def format_machine(machine: Machine) -> Dict[str, Any]: return { - 'id': machine.id, - 'pcbid': machine.pcbid, - 'name': machine.name, - 'description': machine.description, - 'arcade': machine.arcade, - 'port': machine.port, - 'game': machine.game.value if machine.game else 'any', - 'version': machine.version, + "id": machine.id, + "pcbid": machine.pcbid, + "name": machine.name, + "description": machine.description, + "arcade": machine.arcade, + "port": machine.port, + "game": machine.game.value if machine.game else "any", + "version": machine.version, } @@ -63,175 +76,210 @@ def format_card(card: Tuple[str, Optional[UserID]]) -> Dict[str, Any]: owner = user.username try: return { - 'number': CardCipher.encode(card[0]), - 'owner': owner, - 'id': card[1], + "number": CardCipher.encode(card[0]), + "owner": owner, + "id": card[1], } except CardCipherException: return { - 'number': '????????????????', - 'owner': owner, - 'id': card[1], + "number": "????????????????", + "owner": owner, + "id": card[1], } def format_user(user: User) -> Dict[str, Any]: return { - 'id': user.id, - 'username': user.username, - 'email': user.email, - 'admin': user.admin, + "id": user.id, + "username": user.username, + "email": user.email, + "admin": user.admin, } def format_news(news: News) -> Dict[str, Any]: return { - 'id': news.id, - 'timestamp': news.timestamp, - 'title': news.title, - 'body': news.body, + "id": news.id, + "timestamp": news.timestamp, + "title": news.title, + "body": news.body, } def format_event(event: Event) -> Dict[str, Any]: return { - 'id': event.id, - 'timestamp': event.timestamp, - 'userid': event.userid, - 'arcadeid': event.arcadeid, - 'type': event.type, - 'data': event.data, + "id": event.id, + "timestamp": event.timestamp, + "userid": event.userid, + "arcadeid": event.arcadeid, + "type": event.type, + "data": event.data, } def format_client(client: Client) -> Dict[str, Any]: return { - 'id': client.id, - 'name': client.name, - 'token': client.token, + "id": client.id, + "name": client.name, + "token": client.token, } def format_server(server: Server) -> Dict[str, Any]: return { - 'id': server.id, - 'uri': server.uri, - 'token': server.token, - 'allow_stats': server.allow_stats, - 'allow_scores': server.allow_scores, + "id": server.id, + "uri": server.uri, + "token": server.token, + "allow_stats": server.allow_stats, + "allow_scores": server.allow_scores, } -@admin_pages.route('/') +@admin_pages.route("/") @adminrequired def viewsettings() -> Response: - return Response(render_template( - 'admin/settings.html', - **{ - 'title': 'Network Settings', - 'config': g.config, - 'region': RegionConstants.LUT, - }, - )) + return Response( + render_template( + "admin/settings.html", + **{ + "title": "Network Settings", + "config": g.config, + "region": RegionConstants.LUT, + }, + ) + ) -@admin_pages.route('/events') +@admin_pages.route("/events") @adminrequired def viewevents() -> Response: iidx = IIDXFrontend(g.data, g.config, g.cache) jubeat = JubeatFrontend(g.data, g.config, g.cache) pnm = PopnMusicFrontend(g.data, g.config, g.cache) return render_react( - 'Events', - 'admin/events.react.js', + "Events", + "admin/events.react.js", { - 'events': [format_event(event) for event in g.data.local.network.get_events(limit=100)], - 'users': {user.id: user.username for user in g.data.local.user.get_all_users()}, - 'arcades': {arcade.id: arcade.name for arcade in g.data.local.machine.get_all_arcades()}, - 'iidxsongs': iidx.get_all_songs(), - 'jubeatsongs': jubeat.get_all_songs(), - 'pnmsongs': pnm.get_all_songs(), - 'iidxversions': {version: name for (game, version, name) in iidx.all_games()}, - 'jubeatversions': {version: name for (game, version, name) in jubeat.all_games()}, - 'pnmversions': {version: name for (game, version, name) in pnm.all_games()}, + "events": [ + format_event(event) + for event in g.data.local.network.get_events(limit=100) + ], + "users": { + user.id: user.username for user in g.data.local.user.get_all_users() + }, + "arcades": { + arcade.id: arcade.name + for arcade in g.data.local.machine.get_all_arcades() + }, + "iidxsongs": iidx.get_all_songs(), + "jubeatsongs": jubeat.get_all_songs(), + "pnmsongs": pnm.get_all_songs(), + "iidxversions": { + version: name for (game, version, name) in iidx.all_games() + }, + "jubeatversions": { + version: name for (game, version, name) in jubeat.all_games() + }, + "pnmversions": {version: name for (game, version, name) in pnm.all_games()}, }, { - 'refresh': url_for('admin_pages.listevents', since=-1), - 'backfill': url_for('admin_pages.backfillevents', until=-1), - 'viewuser': url_for('admin_pages.viewuser', userid=-1), - 'jubeatsong': url_for('jubeat_pages.viewtopscores', musicid=-1) if GameConstants.JUBEAT in g.config.support else None, - 'iidxsong': url_for('iidx_pages.viewtopscores', musicid=-1) if GameConstants.IIDX in g.config.support else None, - 'pnmsong': url_for('popn_pages.viewtopscores', musicid=-1) if GameConstants.POPN_MUSIC in g.config.support else None, + "refresh": url_for("admin_pages.listevents", since=-1), + "backfill": url_for("admin_pages.backfillevents", until=-1), + "viewuser": url_for("admin_pages.viewuser", userid=-1), + "jubeatsong": url_for("jubeat_pages.viewtopscores", musicid=-1) + if GameConstants.JUBEAT in g.config.support + else None, + "iidxsong": url_for("iidx_pages.viewtopscores", musicid=-1) + if GameConstants.IIDX in g.config.support + else None, + "pnmsong": url_for("popn_pages.viewtopscores", musicid=-1) + if GameConstants.POPN_MUSIC in g.config.support + else None, }, ) -@admin_pages.route('/events/backfill/') +@admin_pages.route("/events/backfill/") @jsonify @adminrequired def backfillevents(until: int) -> Dict[str, Any]: return { - 'events': [format_event(event) for event in g.data.local.network.get_events(until_id=until, limit=1000)], + "events": [ + format_event(event) + for event in g.data.local.network.get_events(until_id=until, limit=1000) + ], } -@admin_pages.route('/events/list/') +@admin_pages.route("/events/list/") @jsonify @adminrequired def listevents(since: int) -> Dict[str, Any]: return { - 'events': [format_event(event) for event in g.data.local.network.get_events(since_id=since)], - 'users': {user.id: user.username for user in g.data.local.user.get_all_users()}, - 'arcades': {arcade.id: arcade.name for arcade in g.data.local.machine.get_all_arcades()}, + "events": [ + format_event(event) + for event in g.data.local.network.get_events(since_id=since) + ], + "users": {user.id: user.username for user in g.data.local.user.get_all_users()}, + "arcades": { + arcade.id: arcade.name for arcade in g.data.local.machine.get_all_arcades() + }, } -@admin_pages.route('/api') +@admin_pages.route("/api") @adminrequired def viewapi() -> Response: return render_react( - 'Data API', - 'admin/api.react.js', + "Data API", + "admin/api.react.js", { - 'clients': [format_client(client) for client in g.data.local.api.get_all_clients()], - 'servers': [format_server(server) for server in g.data.local.api.get_all_servers()], + "clients": [ + format_client(client) for client in g.data.local.api.get_all_clients() + ], + "servers": [ + format_server(server) for server in g.data.local.api.get_all_servers() + ], }, { - 'addclient': url_for('admin_pages.addclient'), - 'updateclient': url_for('admin_pages.updateclient'), - 'removeclient': url_for('admin_pages.removeclient'), - 'addserver': url_for('admin_pages.addserver'), - 'updateserver': url_for('admin_pages.updateserver'), - 'removeserver': url_for('admin_pages.removeserver'), - 'queryserver': url_for('admin_pages.queryserver', serverid=-1), + "addclient": url_for("admin_pages.addclient"), + "updateclient": url_for("admin_pages.updateclient"), + "removeclient": url_for("admin_pages.removeclient"), + "addserver": url_for("admin_pages.addserver"), + "updateserver": url_for("admin_pages.updateserver"), + "removeserver": url_for("admin_pages.removeserver"), + "queryserver": url_for("admin_pages.queryserver", serverid=-1), }, ) -@admin_pages.route('/arcades') +@admin_pages.route("/arcades") @adminrequired def viewarcades() -> Response: return render_react( - 'Arcades', - 'admin/arcades.react.js', + "Arcades", + "admin/arcades.react.js", { - 'arcades': [format_arcade(arcade) for arcade in g.data.local.machine.get_all_arcades()], - 'regions': RegionConstants.LUT, - 'usernames': g.data.local.user.get_all_usernames(), - 'paseli_enabled': g.config.paseli.enabled, - 'paseli_infinite': g.config.paseli.infinite, - 'default_region': g.config.server.region, - 'mask_services_url': False, + "arcades": [ + format_arcade(arcade) + for arcade in g.data.local.machine.get_all_arcades() + ], + "regions": RegionConstants.LUT, + "usernames": g.data.local.user.get_all_usernames(), + "paseli_enabled": g.config.paseli.enabled, + "paseli_infinite": g.config.paseli.infinite, + "default_region": g.config.server.region, + "mask_services_url": False, }, { - 'addarcade': url_for('admin_pages.addarcade'), - 'updatearcade': url_for('admin_pages.updatearcade'), - 'removearcade': url_for('admin_pages.removearcade'), + "addarcade": url_for("admin_pages.addarcade"), + "updatearcade": url_for("admin_pages.updatearcade"), + "removearcade": url_for("admin_pages.removearcade"), }, ) -@admin_pages.route('/pcbids') +@admin_pages.route("/pcbids") @adminrequired def viewmachines() -> Response: games: Dict[str, Dict[int, str]] = {} @@ -241,102 +289,110 @@ def viewmachines() -> Response: games[game.value][version] = name return render_react( - 'Machines', - 'admin/machines.react.js', + "Machines", + "admin/machines.react.js", { - 'machines': [format_machine(machine) for machine in g.data.local.machine.get_all_machines()], - 'arcades': {arcade.id: arcade.name for arcade in g.data.local.machine.get_all_arcades()}, - 'series': { - GameConstants.BISHI_BASHI.value: 'BishiBashi', - GameConstants.DDR.value: 'DDR', - GameConstants.IIDX.value: 'IIDX', - GameConstants.JUBEAT.value: 'Jubeat', - GameConstants.MGA.value: 'Metal Gear Arcade', - GameConstants.MUSECA.value: 'MÚSECA', - GameConstants.POPN_MUSIC.value: 'Pop\'n Music', - GameConstants.REFLEC_BEAT.value: 'Reflec Beat', - GameConstants.SDVX.value: 'SDVX', + "machines": [ + format_machine(machine) + for machine in g.data.local.machine.get_all_machines() + ], + "arcades": { + arcade.id: arcade.name + for arcade in g.data.local.machine.get_all_arcades() }, - 'games': games, - 'enforcing': g.config.server.enforce_pcbid, + "series": { + GameConstants.BISHI_BASHI.value: "BishiBashi", + GameConstants.DDR.value: "DDR", + GameConstants.IIDX.value: "IIDX", + GameConstants.JUBEAT.value: "Jubeat", + GameConstants.MGA.value: "Metal Gear Arcade", + GameConstants.MUSECA.value: "MÚSECA", + GameConstants.POPN_MUSIC.value: "Pop'n Music", + GameConstants.REFLEC_BEAT.value: "Reflec Beat", + GameConstants.SDVX.value: "SDVX", + }, + "games": games, + "enforcing": g.config.server.enforce_pcbid, }, { - 'refresh': url_for('admin_pages.listmachines'), - 'generatepcbid': url_for('admin_pages.generatepcbid'), - 'addpcbid': url_for('admin_pages.addpcbid'), - 'updatepcbid': url_for('admin_pages.updatepcbid'), - 'removepcbid': url_for('admin_pages.removepcbid'), + "refresh": url_for("admin_pages.listmachines"), + "generatepcbid": url_for("admin_pages.generatepcbid"), + "addpcbid": url_for("admin_pages.addpcbid"), + "updatepcbid": url_for("admin_pages.updatepcbid"), + "removepcbid": url_for("admin_pages.removepcbid"), }, ) -@admin_pages.route('/cards') +@admin_pages.route("/cards") @adminrequired def viewcards() -> Response: return render_react( - 'Cards', - 'admin/cards.react.js', + "Cards", + "admin/cards.react.js", { - 'cards': [format_card(card) for card in g.data.local.user.get_all_cards()], - 'usernames': g.data.local.user.get_all_usernames(), + "cards": [format_card(card) for card in g.data.local.user.get_all_cards()], + "usernames": g.data.local.user.get_all_usernames(), }, { - 'addcard': url_for('admin_pages.addcard'), - 'removecard': url_for('admin_pages.removecard'), - 'viewuser': url_for('admin_pages.viewuser', userid=-1), + "addcard": url_for("admin_pages.addcard"), + "removecard": url_for("admin_pages.removecard"), + "viewuser": url_for("admin_pages.viewuser", userid=-1), }, ) -@admin_pages.route('/users') +@admin_pages.route("/users") @adminrequired def viewusers() -> Response: return render_react( - 'Users', - 'admin/users.react.js', + "Users", + "admin/users.react.js", { - 'users': [format_user(user) for user in g.data.local.user.get_all_users()], + "users": [format_user(user) for user in g.data.local.user.get_all_users()], }, { - 'searchusers': url_for('admin_pages.searchusers'), - 'viewuser': url_for('admin_pages.viewuser', userid=-1), + "searchusers": url_for("admin_pages.searchusers"), + "viewuser": url_for("admin_pages.viewuser", userid=-1), }, ) -@admin_pages.route('/news') +@admin_pages.route("/news") @adminrequired def viewnews() -> Response: return render_react( - 'News', - 'admin/news.react.js', + "News", + "admin/news.react.js", { - 'news': [format_news(news) for news in g.data.local.network.get_all_news()], + "news": [format_news(news) for news in g.data.local.network.get_all_news()], }, { - 'removenews': url_for('admin_pages.removenews'), - 'addnews': url_for('admin_pages.addnews'), - 'updatenews': url_for('admin_pages.updatenews'), + "removenews": url_for("admin_pages.removenews"), + "addnews": url_for("admin_pages.addnews"), + "updatenews": url_for("admin_pages.updatenews"), }, ) -@admin_pages.route('/gamesettings') +@admin_pages.route("/gamesettings") @adminrequired def viewgamesettings() -> Response: return render_react( - 'Game Settings', - 'admin/gamesettings.react.js', + "Game Settings", + "admin/gamesettings.react.js", { - 'game_settings': get_game_settings(g.data, g.data.local.machine.DEFAULT_SETTINGS_ARCADE), + "game_settings": get_game_settings( + g.data, g.data.local.machine.DEFAULT_SETTINGS_ARCADE + ), }, { - 'update_settings': url_for('admin_pages.updatesettings'), + "update_settings": url_for("admin_pages.updatesettings"), }, ) -@admin_pages.route('/users/') +@admin_pages.route("/users/") @adminrequired def viewuser(userid: int) -> Response: # Cast the userID. @@ -347,37 +403,45 @@ def viewuser(userid: int) -> Response: try: return CardCipher.encode(card) except CardCipherException: - return '????????????????' + return "????????????????" cards = [__format_card(card) for card in g.data.local.user.get_cards(userid)] arcades = g.data.local.machine.get_all_arcades() return render_react( - 'User', - 'admin/user.react.js', + "User", + "admin/user.react.js", { - 'user': { - 'email': user.email, - 'username': user.username, + "user": { + "email": user.email, + "username": user.username, }, - 'cards': cards, - 'arcades': {arcade.id: arcade.name for arcade in arcades}, - 'balances': {arcade.id: g.data.local.user.get_balance(userid, arcade.id) for arcade in arcades}, - 'events': [format_event(event) for event in g.data.local.network.get_events(userid=userid, event='paseli_transaction')], + "cards": cards, + "arcades": {arcade.id: arcade.name for arcade in arcades}, + "balances": { + arcade.id: g.data.local.user.get_balance(userid, arcade.id) + for arcade in arcades + }, + "events": [ + format_event(event) + for event in g.data.local.network.get_events( + userid=userid, event="paseli_transaction" + ) + ], }, { - 'refresh': url_for('admin_pages.listuser', userid=userid), - 'removeusercard': url_for('admin_pages.removeusercard', userid=userid), - 'addusercard': url_for('admin_pages.addusercard', userid=userid), - 'updatebalance': url_for('admin_pages.updatebalance', userid=userid), - 'updateusername': url_for('admin_pages.updateusername', userid=userid), - 'updateemail': url_for('admin_pages.updateemail', userid=userid), - 'updatepin': url_for('admin_pages.updatepin', userid=userid), - 'updatepassword': url_for('admin_pages.updatepassword', userid=userid), + "refresh": url_for("admin_pages.listuser", userid=userid), + "removeusercard": url_for("admin_pages.removeusercard", userid=userid), + "addusercard": url_for("admin_pages.addusercard", userid=userid), + "updatebalance": url_for("admin_pages.updatebalance", userid=userid), + "updateusername": url_for("admin_pages.updateusername", userid=userid), + "updateemail": url_for("admin_pages.updateemail", userid=userid), + "updatepin": url_for("admin_pages.updatepin", userid=userid), + "updatepassword": url_for("admin_pages.updatepassword", userid=userid), }, ) -@admin_pages.route('/users//list') +@admin_pages.route("/users//list") @jsonify @adminrequired def listuser(userid: int) -> Dict[str, Any]: @@ -388,46 +452,59 @@ def listuser(userid: int) -> Dict[str, Any]: try: return CardCipher.encode(card) except CardCipherException: - return '????????????????' + return "????????????????" cards = [__format_card(card) for card in g.data.local.user.get_cards(userid)] arcades = g.data.local.machine.get_all_arcades() return { - 'cards': cards, - 'arcades': {arcade.id: arcade.name for arcade in arcades}, - 'balances': {arcade.id: g.data.local.user.get_balance(userid, arcade.id) for arcade in arcades}, - 'events': [format_event(event) for event in g.data.local.network.get_events(userid=userid, event='paseli_transaction')], + "cards": cards, + "arcades": {arcade.id: arcade.name for arcade in arcades}, + "balances": { + arcade.id: g.data.local.user.get_balance(userid, arcade.id) + for arcade in arcades + }, + "events": [ + format_event(event) + for event in g.data.local.network.get_events( + userid=userid, event="paseli_transaction" + ) + ], } -@admin_pages.route('/arcades/list') +@admin_pages.route("/arcades/list") @jsonify @adminrequired def listmachines() -> Dict[str, Any]: return { - 'machines': [format_machine(machine) for machine in g.data.local.machine.get_all_machines()], - 'arcades': {arcade.id: arcade.name for arcade in g.data.local.machine.get_all_arcades()}, + "machines": [ + format_machine(machine) + for machine in g.data.local.machine.get_all_machines() + ], + "arcades": { + arcade.id: arcade.name for arcade in g.data.local.machine.get_all_arcades() + }, } -@admin_pages.route('/arcades/update', methods=['POST']) +@admin_pages.route("/arcades/update", methods=["POST"]) @jsonify @adminrequired def updatearcade() -> Dict[str, Any]: # Attempt to look this arcade up - new_values = request.get_json()['arcade'] - arcade = g.data.local.machine.get_arcade(new_values['id']) + new_values = request.get_json()["arcade"] + arcade = g.data.local.machine.get_arcade(new_values["id"]) if arcade is None: - raise Exception('Unable to find arcade to update!') + raise Exception("Unable to find arcade to update!") - arcade.name = new_values['name'] - arcade.description = new_values['description'] - arcade.region = new_values['region'] - arcade.data.replace_bool('paseli_enabled', new_values['paseli_enabled']) - arcade.data.replace_bool('paseli_infinite', new_values['paseli_infinite']) - arcade.data.replace_bool('mask_services_url', new_values['mask_services_url']) + arcade.name = new_values["name"] + arcade.description = new_values["description"] + arcade.region = new_values["region"] + arcade.data.replace_bool("paseli_enabled", new_values["paseli_enabled"]) + arcade.data.replace_bool("paseli_infinite", new_values["paseli_infinite"]) + arcade.data.replace_bool("mask_services_url", new_values["mask_services_url"]) owners = [] - for owner in new_values['owners']: + for owner in new_values["owners"]: ownerid = g.data.local.user.from_username(owner) if ownerid is not None: owners.append(ownerid) @@ -437,341 +514,383 @@ def updatearcade() -> Dict[str, Any]: # Just return all arcades for ease of updating return { - 'arcades': [format_arcade(arcade) for arcade in g.data.local.machine.get_all_arcades()], + "arcades": [ + format_arcade(arcade) for arcade in g.data.local.machine.get_all_arcades() + ], } -@admin_pages.route('/arcades/add', methods=['POST']) +@admin_pages.route("/arcades/add", methods=["POST"]) @jsonify @adminrequired def addarcade() -> Dict[str, Any]: # Attempt to look this arcade up - new_values = request.get_json()['arcade'] + new_values = request.get_json()["arcade"] - if len(new_values['name']) == 0: - raise Exception('Please name your new arcade!') - if len(new_values['description']) == 0: - raise Exception('Please describe your new arcade!') + if len(new_values["name"]) == 0: + raise Exception("Please name your new arcade!") + if len(new_values["description"]) == 0: + raise Exception("Please describe your new arcade!") owners = [] - for owner in new_values['owners']: + for owner in new_values["owners"]: ownerid = g.data.local.user.from_username(owner) if ownerid is not None: owners.append(ownerid) owners = list(set(owners)) g.data.local.machine.create_arcade( - new_values['name'], - new_values['description'], - new_values['region'], + new_values["name"], + new_values["description"], + new_values["region"], { - 'paseli_enabled': new_values['paseli_enabled'], - 'paseli_infinite': new_values['paseli_infinite'], - 'mask_services_url': new_values['mask_services_url'], + "paseli_enabled": new_values["paseli_enabled"], + "paseli_infinite": new_values["paseli_infinite"], + "mask_services_url": new_values["mask_services_url"], }, owners, ) # Just return all arcades for ease of updating return { - 'arcades': [format_arcade(arcade) for arcade in g.data.local.machine.get_all_arcades()], + "arcades": [ + format_arcade(arcade) for arcade in g.data.local.machine.get_all_arcades() + ], } -@admin_pages.route('/arcades/remove', methods=['POST']) +@admin_pages.route("/arcades/remove", methods=["POST"]) @jsonify @adminrequired def removearcade() -> Dict[str, Any]: # Attempt to look this arcade up - arcadeid = request.get_json()['arcadeid'] + arcadeid = request.get_json()["arcadeid"] arcade = g.data.local.machine.get_arcade(arcadeid) if arcade is None: - raise Exception('Unable to find arcade to delete!') + raise Exception("Unable to find arcade to delete!") g.data.local.machine.destroy_arcade(arcadeid) # Just return all arcades for ease of updating return { - 'arcades': [format_arcade(arcade) for arcade in g.data.local.machine.get_all_arcades()], + "arcades": [ + format_arcade(arcade) for arcade in g.data.local.machine.get_all_arcades() + ], } -@admin_pages.route('/clients/update', methods=['POST']) +@admin_pages.route("/clients/update", methods=["POST"]) @jsonify @adminrequired def updateclient() -> Dict[str, Any]: # Attempt to look this client up - new_values = request.get_json()['client'] - client = g.data.local.api.get_client(new_values['id']) + new_values = request.get_json()["client"] + client = g.data.local.api.get_client(new_values["id"]) if client is None: - raise Exception('Unable to find client to update!') + raise Exception("Unable to find client to update!") - if len(new_values['name']) == 0: - raise Exception('Client names must be at least one character long!') + if len(new_values["name"]) == 0: + raise Exception("Client names must be at least one character long!") - client.name = new_values['name'] + client.name = new_values["name"] g.data.local.api.put_client(client) # Just return all clients for ease of updating return { - 'clients': [format_client(client) for client in g.data.local.api.get_all_clients()], + "clients": [ + format_client(client) for client in g.data.local.api.get_all_clients() + ], } -@admin_pages.route('/clients/add', methods=['POST']) +@admin_pages.route("/clients/add", methods=["POST"]) @jsonify @adminrequired def addclient() -> Dict[str, Any]: # Attempt to look this client up - new_values = request.get_json()['client'] + new_values = request.get_json()["client"] - if len(new_values['name']) == 0: - raise Exception('Please name your new client!') + if len(new_values["name"]) == 0: + raise Exception("Please name your new client!") g.data.local.api.create_client( - new_values['name'], + new_values["name"], ) # Just return all clientss for ease of updating return { - 'clients': [format_client(client) for client in g.data.local.api.get_all_clients()], + "clients": [ + format_client(client) for client in g.data.local.api.get_all_clients() + ], } -@admin_pages.route('/clients/remove', methods=['POST']) +@admin_pages.route("/clients/remove", methods=["POST"]) @jsonify @adminrequired def removeclient() -> Dict[str, Any]: # Attempt to look this client up - clientid = request.get_json()['clientid'] + clientid = request.get_json()["clientid"] client = g.data.local.api.get_client(clientid) if client is None: - raise Exception('Unable to find client to delete!') + raise Exception("Unable to find client to delete!") g.data.local.api.destroy_client(clientid) # Just return all clients for ease of updating return { - 'clients': [format_client(client) for client in g.data.local.api.get_all_clients()], + "clients": [ + format_client(client) for client in g.data.local.api.get_all_clients() + ], } -@admin_pages.route('/server//info') +@admin_pages.route("/server//info") @jsonify @adminrequired def queryserver(serverid: int) -> Dict[str, Any]: # Attempt to look this server up server = g.data.local.api.get_server(serverid) if server is None: - raise Exception('Unable to find server to query!') + raise Exception("Unable to find server to query!") client = APIClient(server.uri, server.token, False, False) try: serverinfo = client.get_server_info() info = { - 'name': serverinfo['name'], - 'email': serverinfo['email'], + "name": serverinfo["name"], + "email": serverinfo["email"], } - info['status'] = 'ok' if APIClient.API_VERSION in serverinfo['versions'] else 'badversion' + info["status"] = ( + "ok" if APIClient.API_VERSION in serverinfo["versions"] else "badversion" + ) except NotAuthorizedAPIException: info = { - 'name': 'unknown', - 'email': 'unknown', - 'status': 'badauth', + "name": "unknown", + "email": "unknown", + "status": "badauth", } except APIException: info = { - 'name': 'unknown', - 'email': 'unknown', - 'status': 'error', + "name": "unknown", + "email": "unknown", + "status": "error", } return info -@admin_pages.route('/servers/update', methods=['POST']) +@admin_pages.route("/servers/update", methods=["POST"]) @jsonify @adminrequired def updateserver() -> Dict[str, Any]: # Attempt to look this server up - new_values = request.get_json()['server'] - server = g.data.local.api.get_server(new_values['id']) + new_values = request.get_json()["server"] + server = g.data.local.api.get_server(new_values["id"]) if server is None: - raise Exception('Unable to find server to update!') + raise Exception("Unable to find server to update!") - if len(new_values['uri']) == 0: - raise Exception('Please provide a valid connection URI for this server!') - if len(new_values['token']) == 0 or len(new_values['token']) > 64: - raise Exception('Please provide a valid connection token for this server!') + if len(new_values["uri"]) == 0: + raise Exception("Please provide a valid connection URI for this server!") + if len(new_values["token"]) == 0 or len(new_values["token"]) > 64: + raise Exception("Please provide a valid connection token for this server!") - server.uri = new_values['uri'] - server.token = new_values['token'] - server.allow_stats = new_values['allow_stats'] - server.allow_scores = new_values['allow_scores'] + server.uri = new_values["uri"] + server.token = new_values["token"] + server.allow_stats = new_values["allow_stats"] + server.allow_scores = new_values["allow_scores"] g.data.local.api.put_server(server) # Just return all servers for ease of updating return { - 'servers': [format_server(server) for server in g.data.local.api.get_all_servers()], + "servers": [ + format_server(server) for server in g.data.local.api.get_all_servers() + ], } -@admin_pages.route('/servers/add', methods=['POST']) +@admin_pages.route("/servers/add", methods=["POST"]) @jsonify @adminrequired def addserver() -> Dict[str, Any]: # Attempt to look this server up - new_values = request.get_json()['server'] + new_values = request.get_json()["server"] - if len(new_values['uri']) == 0: - raise Exception('Please provide a connection URI for the new server!') - if len(new_values['token']) == 0 or len(new_values['token']) > 64: - raise Exception('Please provide a valid connection token for the new server!') + if len(new_values["uri"]) == 0: + raise Exception("Please provide a connection URI for the new server!") + if len(new_values["token"]) == 0 or len(new_values["token"]) > 64: + raise Exception("Please provide a valid connection token for the new server!") g.data.local.api.create_server( - new_values['uri'], - new_values['token'], + new_values["uri"], + new_values["token"], ) # Just return all serverss for ease of updating return { - 'servers': [format_server(server) for server in g.data.local.api.get_all_servers()], + "servers": [ + format_server(server) for server in g.data.local.api.get_all_servers() + ], } -@admin_pages.route('/servers/remove', methods=['POST']) +@admin_pages.route("/servers/remove", methods=["POST"]) @jsonify @adminrequired def removeserver() -> Dict[str, Any]: # Attempt to look this server up - serverid = request.get_json()['serverid'] + serverid = request.get_json()["serverid"] server = g.data.local.api.get_server(serverid) if server is None: - raise Exception('Unable to find server to delete!') + raise Exception("Unable to find server to delete!") g.data.local.api.destroy_server(serverid) # Just return all servers for ease of updating return { - 'servers': [format_server(server) for server in g.data.local.api.get_all_servers()], + "servers": [ + format_server(server) for server in g.data.local.api.get_all_servers() + ], } -@admin_pages.route('/pcbids/generate', methods=['POST']) +@admin_pages.route("/pcbids/generate", methods=["POST"]) @jsonify @adminrequired def generatepcbid() -> Dict[str, Any]: # Attempt to look this arcade up - new_pcbid = request.get_json()['machine'] - if new_pcbid['arcade'] is not None: - arcade = g.data.local.machine.get_arcade(new_pcbid['arcade']) + new_pcbid = request.get_json()["machine"] + if new_pcbid["arcade"] is not None: + arcade = g.data.local.machine.get_arcade(new_pcbid["arcade"]) if arcade is None: - raise Exception('Unable to find arcade to link PCBID to!') + raise Exception("Unable to find arcade to link PCBID to!") # Will be set by the game on boot. - name: str = 'なし' + name: str = "なし" pcbid: Optional[str] = None while pcbid is None: # Generate a new PCBID, check for uniqueness - potential_pcbid = "01201000000000" + "".join([random.choice("0123456789ABCDEF") for _ in range(6)]) + potential_pcbid = "01201000000000" + "".join( + [random.choice("0123456789ABCDEF") for _ in range(6)] + ) if g.data.local.machine.get_machine(potential_pcbid) is None: pcbid = potential_pcbid - g.data.local.machine.create_machine(pcbid, name, new_pcbid['description'], new_pcbid['arcade']) + g.data.local.machine.create_machine( + pcbid, name, new_pcbid["description"], new_pcbid["arcade"] + ) # Just return all machines for ease of updating return { - 'machines': [format_machine(machine) for machine in g.data.local.machine.get_all_machines()], + "machines": [ + format_machine(machine) + for machine in g.data.local.machine.get_all_machines() + ], } -@admin_pages.route('/pcbids/add', methods=['POST']) +@admin_pages.route("/pcbids/add", methods=["POST"]) @jsonify @adminrequired def addpcbid() -> Dict[str, Any]: # Attempt to look this arcade up - new_pcbid = request.get_json()['machine'] - if new_pcbid['arcade'] is not None: - arcade = g.data.local.machine.get_arcade(new_pcbid['arcade']) + new_pcbid = request.get_json()["machine"] + if new_pcbid["arcade"] is not None: + arcade = g.data.local.machine.get_arcade(new_pcbid["arcade"]) if arcade is None: - raise Exception('Unable to find arcade to link PCBID to!') + raise Exception("Unable to find arcade to link PCBID to!") # Verify that the PCBID is valid - potential_pcbid = "".join([c for c in new_pcbid['pcbid'].upper() if c in "0123456789ABCDEF"]) - if len(potential_pcbid) != len(new_pcbid['pcbid']): + potential_pcbid = "".join( + [c for c in new_pcbid["pcbid"].upper() if c in "0123456789ABCDEF"] + ) + if len(potential_pcbid) != len(new_pcbid["pcbid"]): raise Exception("Invalid characters in PCBID!") if len(potential_pcbid) != 20: raise Exception("PCBID has invalid length!") if g.data.local.machine.get_machine(potential_pcbid) is not None: - raise Exception('PCBID already exists!') + raise Exception("PCBID already exists!") # Will be set by the game on boot. - name = 'なし' - g.data.local.machine.create_machine(potential_pcbid, name, new_pcbid['description'], new_pcbid['arcade']) + name = "なし" + g.data.local.machine.create_machine( + potential_pcbid, name, new_pcbid["description"], new_pcbid["arcade"] + ) # Just return all machines for ease of updating return { - 'machines': [format_machine(machine) for machine in g.data.local.machine.get_all_machines()], + "machines": [ + format_machine(machine) + for machine in g.data.local.machine.get_all_machines() + ], } -@admin_pages.route('/pcbids/update', methods=['POST']) +@admin_pages.route("/pcbids/update", methods=["POST"]) @jsonify @adminrequired def updatepcbid() -> Dict[str, Any]: # Attempt to look this machine up - machine = request.get_json()['machine'] - if machine['arcade'] is not None: - arcade = g.data.local.machine.get_arcade(machine['arcade']) + machine = request.get_json()["machine"] + if machine["arcade"] is not None: + arcade = g.data.local.machine.get_arcade(machine["arcade"]) if arcade is None: - raise Exception('Unable to find arcade to link PCBID to!') + raise Exception("Unable to find arcade to link PCBID to!") # Make sure we don't duplicate port assignments - other_pcbid = g.data.local.machine.from_port(machine['port']) - if other_pcbid is not None and other_pcbid != machine['pcbid']: - raise Exception(f'The specified port is already in use by \'{other_pcbid}\'!') + other_pcbid = g.data.local.machine.from_port(machine["port"]) + if other_pcbid is not None and other_pcbid != machine["pcbid"]: + raise Exception(f"The specified port is already in use by '{other_pcbid}'!") - if machine['port'] < 1 or machine['port'] > 65535: - raise Exception('The specified port is out of range!') + if machine["port"] < 1 or machine["port"] > 65535: + raise Exception("The specified port is out of range!") - current_machine = g.data.local.machine.get_machine(machine['pcbid']) - current_machine.description = machine['description'] - current_machine.arcade = machine['arcade'] - current_machine.port = machine['port'] - current_machine.game = None if machine['game'] == 'any' else GameConstants(machine['game']) - current_machine.version = None if machine['game'] == 'any' else machine['version'] + current_machine = g.data.local.machine.get_machine(machine["pcbid"]) + current_machine.description = machine["description"] + current_machine.arcade = machine["arcade"] + current_machine.port = machine["port"] + current_machine.game = ( + None if machine["game"] == "any" else GameConstants(machine["game"]) + ) + current_machine.version = None if machine["game"] == "any" else machine["version"] g.data.local.machine.put_machine(current_machine) # Just return all machines for ease of updating return { - 'machines': [format_machine(machine) for machine in g.data.local.machine.get_all_machines()], + "machines": [ + format_machine(machine) + for machine in g.data.local.machine.get_all_machines() + ], } -@admin_pages.route('/pcbids/remove', methods=['POST']) +@admin_pages.route("/pcbids/remove", methods=["POST"]) @jsonify @adminrequired def removepcbid() -> Dict[str, Any]: # Attempt to look this machine up - pcbid = request.get_json()['pcbid'] + pcbid = request.get_json()["pcbid"] if g.data.local.machine.get_machine(pcbid) is None: - raise Exception('Unable to find PCBID to delete!') + raise Exception("Unable to find PCBID to delete!") g.data.local.machine.destroy_machine(pcbid) # Just return all machines for ease of updating return { - 'machines': [format_machine(machine) for machine in g.data.local.machine.get_all_machines()], + "machines": [ + format_machine(machine) + for machine in g.data.local.machine.get_all_machines() + ], } -@admin_pages.route('/cards/remove', methods=['POST']) +@admin_pages.route("/cards/remove", methods=["POST"]) @jsonify @adminrequired def removecard() -> Dict[str, Any]: # Grab card, convert it - card = request.get_json()['card'] + card = request.get_json()["card"] try: cardid = CardCipher.decode(card) except CardCipherException: - raise Exception('Invalid card number!') + raise Exception("Invalid card number!") # Make sure it is our card userid = g.data.local.user.from_cardid(cardid) @@ -781,49 +900,49 @@ def removecard() -> Dict[str, Any]: # Return new card list return { - 'cards': [format_card(card) for card in g.data.local.user.get_all_cards()], + "cards": [format_card(card) for card in g.data.local.user.get_all_cards()], } -@admin_pages.route('/cards/add', methods=['POST']) +@admin_pages.route("/cards/add", methods=["POST"]) @jsonify @adminrequired def addcard() -> Dict[str, Any]: # Grab card, convert it - card = request.get_json()['card'] + card = request.get_json()["card"] try: - cardid = CardCipher.decode(card['number']) + cardid = CardCipher.decode(card["number"]) except CardCipherException: - raise Exception('Invalid card number!') + raise Exception("Invalid card number!") # Make sure it is our card - userid = g.data.local.user.from_username(card['owner']) + userid = g.data.local.user.from_username(card["owner"]) if userid is None: - raise Exception('Cannot find user to add card to!') + raise Exception("Cannot find user to add card to!") # See if it is already claimed curuserid = g.data.local.user.from_cardid(cardid) if curuserid is not None: - raise Exception('This card is already in use!') + raise Exception("This card is already in use!") # Add it to the user's account g.data.local.user.add_card(userid, cardid) # Return new card list return { - 'cards': [format_card(card) for card in g.data.local.user.get_all_cards()], + "cards": [format_card(card) for card in g.data.local.user.get_all_cards()], } -@admin_pages.route('/users/search', methods=['POST']) +@admin_pages.route("/users/search", methods=["POST"]) @jsonify @adminrequired def searchusers() -> Dict[str, Any]: # Grab card, convert it - searchdetails = request.get_json()['user_search'] - if len(searchdetails['card']) > 0: + searchdetails = request.get_json()["user_search"] + if len(searchdetails["card"]) > 0: try: - cardid = CardCipher.decode(searchdetails['card']) + cardid = CardCipher.decode(searchdetails["card"]) actual_userid = g.data.local.user.from_cardid(cardid) if actual_userid is None: # Force a non-match below @@ -840,117 +959,129 @@ def searchusers() -> Dict[str, Any]: return True return { - 'users': [format_user(user) for user in g.data.local.user.get_all_users() if match(user)], + "users": [ + format_user(user) + for user in g.data.local.user.get_all_users() + if match(user) + ], } -@admin_pages.route('/users//balance/update', methods=['POST']) +@admin_pages.route("/users//balance/update", methods=["POST"]) @jsonify @adminrequired def updatebalance(userid: int) -> Dict[str, Any]: # Cast the userID. userid = UserID(userid) - credits = request.get_json()['credits'] + credits = request.get_json()["credits"] user = g.data.local.user.get_user(userid) arcades = g.data.local.machine.get_all_arcades() # Make sure the user ID is valid if user is None: - raise Exception('Cannot find user to update!') + raise Exception("Cannot find user to update!") # Update balances for arcadeid in credits: balance = g.data.local.user.update_balance(userid, arcadeid, credits[arcadeid]) if balance is not None: g.data.local.network.put_event( - 'paseli_transaction', + "paseli_transaction", { - 'delta': credits[arcadeid], - 'balance': balance, - 'reason': 'admin adjustment', + "delta": credits[arcadeid], + "balance": balance, + "reason": "admin adjustment", }, userid=userid, arcadeid=arcadeid, ) return { - 'arcades': {arcade.id: arcade.name for arcade in arcades}, - 'balances': {arcade.id: g.data.local.user.get_balance(userid, arcade.id) for arcade in arcades}, - 'events': [format_event(event) for event in g.data.local.network.get_events(userid=userid, event='paseli_transaction')], + "arcades": {arcade.id: arcade.name for arcade in arcades}, + "balances": { + arcade.id: g.data.local.user.get_balance(userid, arcade.id) + for arcade in arcades + }, + "events": [ + format_event(event) + for event in g.data.local.network.get_events( + userid=userid, event="paseli_transaction" + ) + ], } -@admin_pages.route('/users//username/update', methods=['POST']) +@admin_pages.route("/users//username/update", methods=["POST"]) @jsonify @adminrequired def updateusername(userid: int) -> Dict[str, Any]: # Cast the userID. userid = UserID(userid) - username = request.get_json()['username'] + username = request.get_json()["username"] user = g.data.local.user.get_user(userid) # Make sure the user ID is valid if user is None: - raise Exception('Cannot find user to update!') + raise Exception("Cannot find user to update!") if not valid_username(username): - raise Exception('Invalid username!') + raise Exception("Invalid username!") # Make sure this user ID isn't taken potential_userid = g.data.local.user.from_username(username) if potential_userid is not None and potential_userid != userid: - raise Exception('That username is already taken!') + raise Exception("That username is already taken!") # Update the user user.username = username g.data.local.user.put_user(user) return { - 'username': username, + "username": username, } -@admin_pages.route('/users//email/update', methods=['POST']) +@admin_pages.route("/users//email/update", methods=["POST"]) @jsonify @adminrequired def updateemail(userid: int) -> Dict[str, Any]: # Cast the userID. userid = UserID(userid) - email = request.get_json()['email'] + email = request.get_json()["email"] user = g.data.local.user.get_user(userid) # Make sure the user ID is valid if user is None: - raise Exception('Cannot find user to update!') + raise Exception("Cannot find user to update!") if not valid_email(email): - raise Exception('Invalid email!') + raise Exception("Invalid email!") # Update the user user.email = email g.data.local.user.put_user(user) return { - 'email': email, + "email": email, } -@admin_pages.route('/users//pin/update', methods=['POST']) +@admin_pages.route("/users//pin/update", methods=["POST"]) @jsonify @adminrequired def updatepin(userid: int) -> Dict[str, Any]: # Cast the userID. userid = UserID(userid) - pin = request.get_json()['pin'] + pin = request.get_json()["pin"] user = g.data.local.user.get_user(userid) # Make sure the user ID is valid if user is None: - raise Exception('Cannot find user to update!') + raise Exception("Cannot find user to update!") - if not valid_pin(pin, 'card'): - raise Exception('Invalid pin, must be exactly 4 digits!') + if not valid_pin(pin, "card"): + raise Exception("Invalid pin, must be exactly 4 digits!") # Update the user g.data.local.user.update_pin(userid, pin) @@ -958,27 +1089,27 @@ def updatepin(userid: int) -> Dict[str, Any]: return {} -@admin_pages.route('/users//password/update', methods=['POST']) +@admin_pages.route("/users//password/update", methods=["POST"]) @jsonify @adminrequired def updatepassword(userid: int) -> Dict[str, Any]: # Cast the userID. userid = UserID(userid) - new1 = request.get_json()['new1'] - new2 = request.get_json()['new2'] + new1 = request.get_json()["new1"] + new2 = request.get_json()["new2"] user = g.data.local.user.get_user(userid) # Make sure the user ID is valid if user is None: - raise Exception('Cannot find user to update!') + raise Exception("Cannot find user to update!") # Now, make sure that the passwords match if new1 != new2: - raise Exception('Passwords do not match each other!') + raise Exception("Passwords do not match each other!") # Now, make sure passwords are long enough if len(new1) < 6: - raise Exception('Password is not long enough!') + raise Exception("Password is not long enough!") # Update the user g.data.local.user.update_password(userid, new1) @@ -986,7 +1117,7 @@ def updatepassword(userid: int) -> Dict[str, Any]: return {} -@admin_pages.route('/users//cards/remove', methods=['POST']) +@admin_pages.route("/users//cards/remove", methods=["POST"]) @jsonify @adminrequired def removeusercard(userid: int) -> Dict[str, Any]: @@ -994,26 +1125,28 @@ def removeusercard(userid: int) -> Dict[str, Any]: userid = UserID(userid) # Grab card, convert it - card = request.get_json()['card'] + card = request.get_json()["card"] try: cardid = CardCipher.decode(card) except CardCipherException: - raise Exception('Invalid card number!') + raise Exception("Invalid card number!") user = g.data.local.user.get_user(userid) # Make sure the user ID is valid if user is None: - raise Exception('Cannot find user to update!') + raise Exception("Cannot find user to update!") # Remove it from the user's account g.data.local.user.destroy_card(userid, cardid) # Return new card list return { - 'cards': [CardCipher.encode(card) for card in g.data.local.user.get_cards(userid)], + "cards": [ + CardCipher.encode(card) for card in g.data.local.user.get_cards(userid) + ], } -@admin_pages.route('/users//cards/add', methods=['POST']) +@admin_pages.route("/users//cards/add", methods=["POST"]) @jsonify @adminrequired def addusercard(userid: int) -> Dict[str, Any]: @@ -1021,120 +1154,127 @@ def addusercard(userid: int) -> Dict[str, Any]: userid = UserID(userid) # Grab card, convert it - card = request.get_json()['card'] + card = request.get_json()["card"] try: cardid = CardCipher.decode(card) except CardCipherException: - raise Exception('Invalid card number!') + raise Exception("Invalid card number!") user = g.data.local.user.get_user(userid) # Make sure the user ID is valid if user is None: - raise Exception('Cannot find user to update!') + raise Exception("Cannot find user to update!") # See if it is already claimed curuserid = g.data.local.user.from_cardid(cardid) if curuserid is not None: - raise Exception('This card is already in use!') + raise Exception("This card is already in use!") # Add it to the user's account g.data.local.user.add_card(userid, cardid) # Return new card list return { - 'cards': [CardCipher.encode(card) for card in g.data.local.user.get_cards(userid)], + "cards": [ + CardCipher.encode(card) for card in g.data.local.user.get_cards(userid) + ], } -@admin_pages.route('/news/add', methods=['POST']) +@admin_pages.route("/news/add", methods=["POST"]) @jsonify @adminrequired def addnews() -> Dict[str, Any]: - news = request.get_json()['news'] - if len(news['title']) == 0: - raise Exception('Please provide a title!') - if len(news['body']) == 0: - raise Exception('Please provide a body!') + news = request.get_json()["news"] + if len(news["title"]) == 0: + raise Exception("Please provide a title!") + if len(news["body"]) == 0: + raise Exception("Please provide a body!") - g.data.local.network.create_news(news['title'], news['body']) + g.data.local.network.create_news(news["title"], news["body"]) return { - 'news': [format_news(news) for news in g.data.local.network.get_all_news()], + "news": [format_news(news) for news in g.data.local.network.get_all_news()], } -@admin_pages.route('/news/remove', methods=['POST']) +@admin_pages.route("/news/remove", methods=["POST"]) @jsonify @adminrequired def removenews() -> Dict[str, Any]: - newsid = request.get_json()['newsid'] + newsid = request.get_json()["newsid"] if g.data.local.network.get_news(newsid) is None: - raise Exception('Unable to find entry to delete!') + raise Exception("Unable to find entry to delete!") g.data.local.network.destroy_news(newsid) return { - 'news': [format_news(news) for news in g.data.local.network.get_all_news()], + "news": [format_news(news) for news in g.data.local.network.get_all_news()], } -@admin_pages.route('/news/update', methods=['POST']) +@admin_pages.route("/news/update", methods=["POST"]) @jsonify @adminrequired def updatenews() -> Dict[str, Any]: - new_news = request.get_json()['news'] - if g.data.local.network.get_news(new_news['id']) is None: - raise Exception('Unable to find entry to update!') - if len(new_news['title']) == 0: - raise Exception('Please provide a title!') - if len(new_news['body']) == 0: - raise Exception('Please provide a body!') + new_news = request.get_json()["news"] + if g.data.local.network.get_news(new_news["id"]) is None: + raise Exception("Unable to find entry to update!") + if len(new_news["title"]) == 0: + raise Exception("Please provide a title!") + if len(new_news["body"]) == 0: + raise Exception("Please provide a body!") - news = g.data.local.network.get_news(new_news['id']) - news.title = new_news['title'] - news.body = new_news['body'] + news = g.data.local.network.get_news(new_news["id"]) + news.title = new_news["title"] + news.body = new_news["body"] g.data.local.network.put_news(news) return { - 'news': [format_news(news) for news in g.data.local.network.get_all_news()], + "news": [format_news(news) for news in g.data.local.network.get_all_news()], } -@admin_pages.route('/gamesettings/update', methods=['POST']) +@admin_pages.route("/gamesettings/update", methods=["POST"]) @jsonify @adminrequired def updatesettings() -> Dict[str, Any]: # Cast the ID for type safety. arcadeid = g.data.local.machine.DEFAULT_SETTINGS_ARCADE - game = GameConstants(request.get_json()['game']) - version = request.get_json()['version'] + game = GameConstants(request.get_json()["game"]) + version = request.get_json()["version"] for setting_type, update_function in [ - ('bools', 'replace_bool'), - ('ints', 'replace_int'), - ('strs', 'replace_str'), - ('longstrs', 'replace_str'), + ("bools", "replace_bool"), + ("ints", "replace_int"), + ("strs", "replace_str"), + ("longstrs", "replace_str"), ]: for game_setting in request.get_json()[setting_type]: # Grab the value to update - category = game_setting['category'] - setting = game_setting['setting'] - new_value = game_setting['value'] + category = game_setting["category"] + setting = game_setting["setting"] + new_value = game_setting["value"] # Update the value - current_settings = g.data.local.machine.get_settings(arcadeid, game, version, category) + current_settings = g.data.local.machine.get_settings( + arcadeid, game, version, category + ) if current_settings is None: current_settings = ValidatedDict() getattr(current_settings, update_function)(setting, new_value) # Save it back - g.data.local.machine.put_settings(arcadeid, game, version, category, current_settings) + g.data.local.machine.put_settings( + arcadeid, game, version, category, current_settings + ) # Return the updated value return { - 'game_settings': [ - gs for gs in get_game_settings(g.data, arcadeid) - if gs['game'] == game.value and gs['version'] == version + "game_settings": [ + gs + for gs in get_game_settings(g.data, arcadeid) + if gs["game"] == game.value and gs["version"] == version ][0], } diff --git a/bemani/frontend/app.py b/bemani/frontend/app.py index d568e83..c7d4bcf 100644 --- a/bemani/frontend/app.py +++ b/bemani/frontend/app.py @@ -3,7 +3,17 @@ import re import traceback from typing import Callable, Dict, Any, Optional, List from react.jsx import JSXTransformer # type: ignore -from flask import Flask, flash, request, redirect, Response, url_for, render_template, got_request_exception, jsonify as flask_jsonify +from flask import ( + Flask, + flash, + request, + redirect, + Response, + url_for, + render_template, + got_request_exception, + jsonify as flask_jsonify, +) from flask_caching import Cache from functools import wraps @@ -28,11 +38,14 @@ FRONTEND_CACHE_BUST: str = "site.1.0.react.16.14" @app.before_request def before_request() -> None: global config - g.cache = Cache(app, config={ - 'CACHE_TYPE': 'filesystem', - 'CACHE_DIR': config.cache_dir, - }) - if request.endpoint in ['jsx', 'static']: + g.cache = Cache( + app, + config={ + "CACHE_TYPE": "filesystem", + "CACHE_DIR": config.cache_dir, + }, + ) + if request.endpoint in ["jsx", "static"]: # This is just serving cached compiled frontends, skip loading from DB return @@ -42,7 +55,7 @@ def before_request() -> None: g.userID = None try: aes = AESCipher(config.secret_key) - sessionID = aes.decrypt(request.cookies.get('SessionID')) + sessionID = aes.decrypt(request.cookies.get("SessionID")) except Exception: sessionID = None g.sessionID = sessionID @@ -65,7 +78,7 @@ def after_request(response: Response) -> Response: @app.teardown_request def teardown_request(exception: Any) -> None: - data = getattr(g, 'data', None) + data = getattr(g, "data", None) if data is not None: data.close() @@ -74,9 +87,10 @@ def loginrequired(func: Callable) -> Callable: @wraps(func) def decoratedfunction(*args: Any, **kwargs: Any) -> Response: if g.userID is None: - return redirect(url_for('account_pages.viewlogin')) # type: ignore + return redirect(url_for("account_pages.viewlogin")) # type: ignore else: return func(*args, **kwargs) + return decoratedfunction @@ -84,13 +98,16 @@ def adminrequired(func: Callable) -> Callable: @wraps(func) def decoratedfunction(*args: Any, **kwargs: Any) -> Response: if g.userID is None: - return redirect(url_for('account_pages.viewlogin')) # type: ignore + return redirect(url_for("account_pages.viewlogin")) # type: ignore else: user = g.data.local.user.get_user(g.userID) if not user.admin: - return Response(render_template('403.html', **{'title': '403 Forbidden'}), 403) + return Response( + render_template("403.html", **{"title": "403 Forbidden"}), 403 + ) else: return func(*args, **kwargs) + return decoratedfunction @@ -98,9 +115,10 @@ def loginprohibited(func: Callable) -> Callable: @wraps(func) def decoratedfunction(*args: Any, **kwargs: Any) -> Response: if g.userID is not None: - return redirect(url_for('home_pages.viewhome')) # type: ignore + return redirect(url_for("home_pages.viewhome")) # type: ignore else: return func(*args, **kwargs) + return decoratedfunction @@ -111,10 +129,13 @@ def jsonify(func: Callable) -> Callable: return flask_jsonify(func(*args, **kwargs)) except Exception as e: print(traceback.format_exc()) - return flask_jsonify({ - 'error': True, - 'message': str(e), - }) + return flask_jsonify( + { + "error": True, + "message": str(e), + } + ) + return decoratedfunction @@ -125,11 +146,13 @@ def cacheable(max_age: int) -> Callable: response = func(*args, **kwargs) response.cache_control.max_age = max_age return response + return decoratedfunction + return __cache -@app.route('/jsx/') +@app.route("/jsx/") @cacheable(86400) def jsx(filename: str) -> Response: try: @@ -140,27 +163,37 @@ def jsx(filename: str) -> Response: if not normalized_path.startswith(static_location): raise IOError("Path traversal exploit detected!") mtime = os.path.getmtime(jsxfile) - namespace = f'{mtime}.{jsxfile}' + namespace = f"{mtime}.{jsxfile}" jsx = g.cache.get(namespace) if jsx is None: - with open(jsxfile, 'rb') as f: + with open(jsxfile, "rb") as f: transformer = JSXTransformer() - jsx = transformer.transform_string(polyfill_fragments(f.read().decode('utf-8'))) + jsx = transformer.transform_string( + polyfill_fragments(f.read().decode("utf-8")) + ) # Set the cache to one year, since we namespace on this file's update time g.cache.set(namespace, jsx, timeout=86400 * 365) - return Response(jsx, mimetype='application/javascript') + return Response(jsx, mimetype="application/javascript") except Exception as exception: if app.debug: # We should make sure this error shows up on the frontend # much like python or template errors do. - stack = ''.join(traceback.format_exception(type(exception), exception, exception.__traceback__)) + stack = "".join( + traceback.format_exception( + type(exception), exception, exception.__traceback__ + ) + ) stack = stack.replace('"', '\\"') - stack = stack.replace('\r\n', '\\n') - stack = stack.replace('\r', '\\n') - stack = stack.replace('\n', '\\n') + stack = stack.replace("\r\n", "\\n") + stack = stack.replace("\r", "\\n") + stack = stack.replace("\n", "\\n") return Response( - '$("ul.messages").append("
  • JSX transform error in ' + filename + '

    ' + stack + '
  • ");', - mimetype='application/javascript', + '$("ul.messages").append("
  • JSX transform error in ' + + filename + + "

    "
    +                + stack
    +                + '
  • ");', + mimetype="application/javascript", ) else: # Just pass it forward like normal for production. @@ -176,35 +209,39 @@ def polyfill_fragments(jsx: str) -> str: def render_react( title: str, controller: str, - inits: Optional[Dict[str, Any]]=None, - links: Optional[Dict[str, Any]]=None, + inits: Optional[Dict[str, Any]] = None, + links: Optional[Dict[str, Any]] = None, ) -> Response: if links is None: links = {} if inits is None: inits = {} - links['static'] = url_for('static', filename='-1') + links["static"] = url_for("static", filename="-1") - return Response(render_template( - 'react.html', - **{ - 'title': title, - 'reactbase': os.path.join('controllers', controller), - 'inits': inits, - 'links': links, - }, - )) + return Response( + render_template( + "react.html", + **{ + "title": title, + "reactbase": os.path.join("controllers", controller), + "inits": inits, + "links": links, + }, + ) + ) def exception(sender: Any, exception: Exception, **extra: Any) -> None: - stack = ''.join(traceback.format_exception(type(exception), exception, exception.__traceback__)) + stack = "".join( + traceback.format_exception(type(exception), exception, exception.__traceback__) + ) try: g.data.local.network.put_event( - 'exception', + "exception", { - 'service': 'frontend', - 'request': request.url, - 'traceback': stack, + "service": "frontend", + "request": request.url, + "traceback": stack, }, ) except Exception: @@ -216,37 +253,42 @@ got_request_exception.connect(exception, app) @app.errorhandler(403) def forbidden(error: Any) -> Response: - return Response(render_template('403.html', **{'title': '403 Forbidden'}), 403) + return Response(render_template("403.html", **{"title": "403 Forbidden"}), 403) @app.errorhandler(404) def page_not_found(error: Any) -> Response: - return Response(render_template('404.html', **{'title': '404 Not Found'}), 404) + return Response(render_template("404.html", **{"title": "404 Not Found"}), 404) @app.errorhandler(500) def server_error(error: Any) -> Response: - return Response(render_template('500.html', **{'title': '500 Internal Server Error'}), 500) + return Response( + render_template("500.html", **{"title": "500 Internal Server Error"}), 500 + ) def error(msg: str) -> None: - flash(msg, 'error') + flash(msg, "error") def warning(msg: str) -> None: - flash(msg, 'warning') + flash(msg, "warning") def success(msg: str) -> None: - flash(msg, 'success') + flash(msg, "success") def info(msg: str) -> None: - flash(msg, 'info') + flash(msg, "info") def valid_email(email: str) -> bool: - return re.match(r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", email) is not None + return ( + re.match(r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", email) + is not None + ) def valid_username(username: str) -> bool: @@ -254,9 +296,9 @@ def valid_username(username: str) -> bool: def valid_pin(pin: str, type: str) -> bool: - if type == 'card': + if type == "card": return re.match(r"^\d\d\d\d$", pin) is not None - elif type == 'arcade': + elif type == "arcade": return re.match(r"^\d\d\d\d\d\d\d\d$", pin) is not None else: return False @@ -273,16 +315,16 @@ def jinja2_any(lval: Optional[List[Any]], pull: str, equals: str) -> bool: def jinja2_theme(filename: str) -> str: - return url_for('static', filename=f"themes/{config.theme}/{filename}") + return url_for("static", filename=f"themes/{config.theme}/{filename}") @app.context_processor def navigation() -> Dict[str, Any]: # Look up JSX components we should provide for every page load components = [ - os.path.join('components', f) - for f in os.listdir(os.path.join(static_location, 'components')) - if re.search(r'\.react\.js$', f) + os.path.join("components", f) + for f in os.listdir(os.path.join(static_location, "components")) + if re.search(r"\.react\.js$", f) ] # Look up the logged in user ID. @@ -292,20 +334,20 @@ def navigation() -> Dict[str, Any]: profiles = g.data.local.user.get_games_played(g.userID) else: return { - 'components': components, - 'any': jinja2_any, - 'theme_url': jinja2_theme, - 'cache_bust': f"v={FRONTEND_CACHE_BUST}", + "components": components, + "any": jinja2_any, + "theme_url": jinja2_theme, + "cache_bust": f"v={FRONTEND_CACHE_BUST}", } except AttributeError: # If we are trying to render a 500 error and we couldn't even run the # before request, we won't have a userID object on g. So, just give # up and refuse to render any navigation. return { - 'components': components, - 'any': jinja2_any, - 'theme_url': jinja2_theme, - 'cache_bust': f"v={FRONTEND_CACHE_BUST}", + "components": components, + "any": jinja2_any, + "theme_url": jinja2_theme, + "cache_bust": f"v={FRONTEND_CACHE_BUST}", } pages: List[Dict[str, Any]] = [] @@ -313,8 +355,8 @@ def navigation() -> Dict[str, Any]: # Landing page pages.append( { - 'label': 'Home', - 'uri': url_for('home_pages.viewhome'), + "label": "Home", + "uri": url_for("home_pages.viewhome"), }, ) @@ -322,28 +364,32 @@ def navigation() -> Dict[str, Any]: # BishiBashi pages bishi_entries = [] if len([p for p in profiles if p[0] == GameConstants.BISHI_BASHI]) > 0: - bishi_entries.extend([ + bishi_entries.extend( + [ + { + "label": "Game Options", + "uri": url_for("bishi_pages.viewsettings"), + }, + { + "label": "Personal Profile", + "uri": url_for("bishi_pages.viewplayer", userid=g.userID), + }, + ] + ) + bishi_entries.extend( + [ { - 'label': 'Game Options', - 'uri': url_for('bishi_pages.viewsettings'), + "label": "All Players", + "uri": url_for("bishi_pages.viewplayers"), }, - { - 'label': 'Personal Profile', - 'uri': url_for('bishi_pages.viewplayer', userid=g.userID), - }, - ]) - bishi_entries.extend([ - { - 'label': 'All Players', - 'uri': url_for('bishi_pages.viewplayers'), - }, - ]) + ] + ) pages.append( { - 'label': 'BishiBashi', - 'entries': bishi_entries, - 'base_uri': app.blueprints['bishi_pages'].url_prefix, - 'gamecode': GameConstants.BISHI_BASHI.value, + "label": "BishiBashi", + "entries": bishi_entries, + "base_uri": app.blueprints["bishi_pages"].url_prefix, + "gamecode": GameConstants.BISHI_BASHI.value, }, ) @@ -351,48 +397,52 @@ def navigation() -> Dict[str, Any]: # DDR pages ddr_entries = [] if len([p for p in profiles if p[0] == GameConstants.DDR]) > 0: - ddr_entries.extend([ + ddr_entries.extend( + [ + { + "label": "Game Options", + "uri": url_for("ddr_pages.viewsettings"), + }, + { + "label": "Rivals", + "uri": url_for("ddr_pages.viewrivals"), + }, + { + "label": "Personal Profile", + "uri": url_for("ddr_pages.viewplayer", userid=g.userID), + }, + { + "label": "Personal Scores", + "uri": url_for("ddr_pages.viewscores", userid=g.userID), + }, + { + "label": "Personal Records", + "uri": url_for("ddr_pages.viewrecords", userid=g.userID), + }, + ] + ) + ddr_entries.extend( + [ { - 'label': 'Game Options', - 'uri': url_for('ddr_pages.viewsettings'), + "label": "Global Scores", + "uri": url_for("ddr_pages.viewnetworkscores"), }, { - 'label': 'Rivals', - 'uri': url_for('ddr_pages.viewrivals'), + "label": "Global Records", + "uri": url_for("ddr_pages.viewnetworkrecords"), }, { - 'label': 'Personal Profile', - 'uri': url_for('ddr_pages.viewplayer', userid=g.userID), + "label": "All Players", + "uri": url_for("ddr_pages.viewplayers"), }, - { - 'label': 'Personal Scores', - 'uri': url_for('ddr_pages.viewscores', userid=g.userID), - }, - { - 'label': 'Personal Records', - 'uri': url_for('ddr_pages.viewrecords', userid=g.userID), - }, - ]) - ddr_entries.extend([ - { - 'label': 'Global Scores', - 'uri': url_for('ddr_pages.viewnetworkscores'), - }, - { - 'label': 'Global Records', - 'uri': url_for('ddr_pages.viewnetworkrecords'), - }, - { - 'label': 'All Players', - 'uri': url_for('ddr_pages.viewplayers'), - }, - ]) + ] + ) pages.append( { - 'label': 'DDR', - 'entries': ddr_entries, - 'base_uri': app.blueprints['ddr_pages'].url_prefix, - 'gamecode': GameConstants.DDR.value, + "label": "DDR", + "entries": ddr_entries, + "base_uri": app.blueprints["ddr_pages"].url_prefix, + "gamecode": GameConstants.DDR.value, }, ) @@ -400,48 +450,52 @@ def navigation() -> Dict[str, Any]: # IIDX pages iidx_entries = [] if len([p for p in profiles if p[0] == GameConstants.IIDX]) > 0: - iidx_entries.extend([ + iidx_entries.extend( + [ + { + "label": "Game Options", + "uri": url_for("iidx_pages.viewsettings"), + }, + { + "label": "Rivals", + "uri": url_for("iidx_pages.viewrivals"), + }, + { + "label": "Personal Profile", + "uri": url_for("iidx_pages.viewplayer", userid=g.userID), + }, + { + "label": "Personal Scores", + "uri": url_for("iidx_pages.viewscores", userid=g.userID), + }, + { + "label": "Personal Records", + "uri": url_for("iidx_pages.viewrecords", userid=g.userID), + }, + ] + ) + iidx_entries.extend( + [ { - 'label': 'Game Options', - 'uri': url_for('iidx_pages.viewsettings'), + "label": "Global Scores", + "uri": url_for("iidx_pages.viewnetworkscores"), }, { - 'label': 'Rivals', - 'uri': url_for('iidx_pages.viewrivals'), + "label": "Global Records", + "uri": url_for("iidx_pages.viewnetworkrecords"), }, { - 'label': 'Personal Profile', - 'uri': url_for('iidx_pages.viewplayer', userid=g.userID), + "label": "All Players", + "uri": url_for("iidx_pages.viewplayers"), }, - { - 'label': 'Personal Scores', - 'uri': url_for('iidx_pages.viewscores', userid=g.userID), - }, - { - 'label': 'Personal Records', - 'uri': url_for('iidx_pages.viewrecords', userid=g.userID), - }, - ]) - iidx_entries.extend([ - { - 'label': 'Global Scores', - 'uri': url_for('iidx_pages.viewnetworkscores'), - }, - { - 'label': 'Global Records', - 'uri': url_for('iidx_pages.viewnetworkrecords'), - }, - { - 'label': 'All Players', - 'uri': url_for('iidx_pages.viewplayers'), - }, - ]) + ] + ) pages.append( { - 'label': 'IIDX', - 'entries': iidx_entries, - 'base_uri': app.blueprints['iidx_pages'].url_prefix, - 'gamecode': GameConstants.IIDX.value, + "label": "IIDX", + "entries": iidx_entries, + "base_uri": app.blueprints["iidx_pages"].url_prefix, + "gamecode": GameConstants.IIDX.value, }, ) @@ -449,48 +503,52 @@ def navigation() -> Dict[str, Any]: # Jubeat pages jubeat_entries = [] if len([p for p in profiles if p[0] == GameConstants.JUBEAT]) > 0: - jubeat_entries.extend([ + jubeat_entries.extend( + [ + { + "label": "Game Options", + "uri": url_for("jubeat_pages.viewsettings"), + }, + { + "label": "Rivals", + "uri": url_for("jubeat_pages.viewrivals"), + }, + { + "label": "Personal Profile", + "uri": url_for("jubeat_pages.viewplayer", userid=g.userID), + }, + { + "label": "Personal Scores", + "uri": url_for("jubeat_pages.viewscores", userid=g.userID), + }, + { + "label": "Personal Records", + "uri": url_for("jubeat_pages.viewrecords", userid=g.userID), + }, + ] + ) + jubeat_entries.extend( + [ { - 'label': 'Game Options', - 'uri': url_for('jubeat_pages.viewsettings'), + "label": "Global Scores", + "uri": url_for("jubeat_pages.viewnetworkscores"), }, { - 'label': 'Rivals', - 'uri': url_for('jubeat_pages.viewrivals'), + "label": "Global Records", + "uri": url_for("jubeat_pages.viewnetworkrecords"), }, { - 'label': 'Personal Profile', - 'uri': url_for('jubeat_pages.viewplayer', userid=g.userID), + "label": "All Players", + "uri": url_for("jubeat_pages.viewplayers"), }, - { - 'label': 'Personal Scores', - 'uri': url_for('jubeat_pages.viewscores', userid=g.userID), - }, - { - 'label': 'Personal Records', - 'uri': url_for('jubeat_pages.viewrecords', userid=g.userID), - }, - ]) - jubeat_entries.extend([ - { - 'label': 'Global Scores', - 'uri': url_for('jubeat_pages.viewnetworkscores'), - }, - { - 'label': 'Global Records', - 'uri': url_for('jubeat_pages.viewnetworkrecords'), - }, - { - 'label': 'All Players', - 'uri': url_for('jubeat_pages.viewplayers'), - }, - ]) + ] + ) pages.append( { - 'label': 'Jubeat', - 'entries': jubeat_entries, - 'base_uri': app.blueprints['jubeat_pages'].url_prefix, - 'gamecode': GameConstants.JUBEAT.value, + "label": "Jubeat", + "entries": jubeat_entries, + "base_uri": app.blueprints["jubeat_pages"].url_prefix, + "gamecode": GameConstants.JUBEAT.value, }, ) @@ -498,28 +556,32 @@ def navigation() -> Dict[str, Any]: # Metal Gear Arcade pages mga_entries = [] if len([p for p in profiles if p[0] == GameConstants.MGA]) > 0: - mga_entries.extend([ + mga_entries.extend( + [ + { + "label": "Game Options", + "uri": url_for("mga_pages.viewsettings"), + }, + { + "label": "Personal Profile", + "uri": url_for("mga_pages.viewplayer", userid=g.userID), + }, + ] + ) + mga_entries.extend( + [ { - 'label': 'Game Options', - 'uri': url_for('mga_pages.viewsettings'), + "label": "All Players", + "uri": url_for("mga_pages.viewplayers"), }, - { - 'label': 'Personal Profile', - 'uri': url_for('mga_pages.viewplayer', userid=g.userID), - }, - ]) - mga_entries.extend([ - { - 'label': 'All Players', - 'uri': url_for('mga_pages.viewplayers'), - }, - ]) + ] + ) pages.append( { - 'label': 'Metal Gear Arcade', - 'entries': mga_entries, - 'base_uri': app.blueprints['mga_pages'].url_prefix, - 'gamecode': GameConstants.MGA.value, + "label": "Metal Gear Arcade", + "entries": mga_entries, + "base_uri": app.blueprints["mga_pages"].url_prefix, + "gamecode": GameConstants.MGA.value, }, ) @@ -527,44 +589,48 @@ def navigation() -> Dict[str, Any]: # Museca pages museca_entries = [] if len([p for p in profiles if p[0] == GameConstants.MUSECA]) > 0: - museca_entries.extend([ + museca_entries.extend( + [ + { + "label": "Game Options", + "uri": url_for("museca_pages.viewsettings"), + }, + { + "label": "Personal Profile", + "uri": url_for("museca_pages.viewplayer", userid=g.userID), + }, + { + "label": "Personal Scores", + "uri": url_for("museca_pages.viewscores", userid=g.userID), + }, + { + "label": "Personal Records", + "uri": url_for("museca_pages.viewrecords", userid=g.userID), + }, + ] + ) + museca_entries.extend( + [ { - 'label': 'Game Options', - 'uri': url_for('museca_pages.viewsettings'), + "label": "Global Scores", + "uri": url_for("museca_pages.viewnetworkscores"), }, { - 'label': 'Personal Profile', - 'uri': url_for('museca_pages.viewplayer', userid=g.userID), + "label": "Global Records", + "uri": url_for("museca_pages.viewnetworkrecords"), }, { - 'label': 'Personal Scores', - 'uri': url_for('museca_pages.viewscores', userid=g.userID), + "label": "All Players", + "uri": url_for("museca_pages.viewplayers"), }, - { - 'label': 'Personal Records', - 'uri': url_for('museca_pages.viewrecords', userid=g.userID), - }, - ]) - museca_entries.extend([ - { - 'label': 'Global Scores', - 'uri': url_for('museca_pages.viewnetworkscores'), - }, - { - 'label': 'Global Records', - 'uri': url_for('museca_pages.viewnetworkrecords'), - }, - { - 'label': 'All Players', - 'uri': url_for('museca_pages.viewplayers'), - }, - ]) + ] + ) pages.append( { - 'label': 'MÚSECA', - 'entries': museca_entries, - 'base_uri': app.blueprints['museca_pages'].url_prefix, - 'gamecode': GameConstants.MUSECA.value, + "label": "MÚSECA", + "entries": museca_entries, + "base_uri": app.blueprints["museca_pages"].url_prefix, + "gamecode": GameConstants.MUSECA.value, }, ) @@ -572,48 +638,52 @@ def navigation() -> Dict[str, Any]: # Pop'n Music pages popn_entries = [] if len([p for p in profiles if p[0] == GameConstants.POPN_MUSIC]) > 0: - popn_entries.extend([ + popn_entries.extend( + [ + { + "label": "Game Options", + "uri": url_for("popn_pages.viewsettings"), + }, + { + "label": "Rivals", + "uri": url_for("popn_pages.viewrivals"), + }, + { + "label": "Personal Profile", + "uri": url_for("popn_pages.viewplayer", userid=g.userID), + }, + { + "label": "Personal Scores", + "uri": url_for("popn_pages.viewscores", userid=g.userID), + }, + { + "label": "Personal Records", + "uri": url_for("popn_pages.viewrecords", userid=g.userID), + }, + ] + ) + popn_entries.extend( + [ { - 'label': 'Game Options', - 'uri': url_for('popn_pages.viewsettings'), + "label": "Global Scores", + "uri": url_for("popn_pages.viewnetworkscores"), }, { - 'label': 'Rivals', - 'uri': url_for('popn_pages.viewrivals'), + "label": "Global Records", + "uri": url_for("popn_pages.viewnetworkrecords"), }, { - 'label': 'Personal Profile', - 'uri': url_for('popn_pages.viewplayer', userid=g.userID), + "label": "All Players", + "uri": url_for("popn_pages.viewplayers"), }, - { - 'label': 'Personal Scores', - 'uri': url_for('popn_pages.viewscores', userid=g.userID), - }, - { - 'label': 'Personal Records', - 'uri': url_for('popn_pages.viewrecords', userid=g.userID), - }, - ]) - popn_entries.extend([ - { - 'label': 'Global Scores', - 'uri': url_for('popn_pages.viewnetworkscores'), - }, - { - 'label': 'Global Records', - 'uri': url_for('popn_pages.viewnetworkrecords'), - }, - { - 'label': 'All Players', - 'uri': url_for('popn_pages.viewplayers'), - }, - ]) + ] + ) pages.append( { - 'label': 'Pop\'n Music', - 'entries': popn_entries, - 'base_uri': app.blueprints['popn_pages'].url_prefix, - 'gamecode': GameConstants.POPN_MUSIC.value, + "label": "Pop'n Music", + "entries": popn_entries, + "base_uri": app.blueprints["popn_pages"].url_prefix, + "gamecode": GameConstants.POPN_MUSIC.value, }, ) @@ -621,48 +691,52 @@ def navigation() -> Dict[str, Any]: # ReflecBeat pages reflec_entries = [] if len([p for p in profiles if p[0] == GameConstants.REFLEC_BEAT]) > 0: - reflec_entries.extend([ + reflec_entries.extend( + [ + { + "label": "Game Options", + "uri": url_for("reflec_pages.viewsettings"), + }, + { + "label": "Rivals", + "uri": url_for("reflec_pages.viewrivals"), + }, + { + "label": "Personal Profile", + "uri": url_for("reflec_pages.viewplayer", userid=g.userID), + }, + { + "label": "Personal Scores", + "uri": url_for("reflec_pages.viewscores", userid=g.userID), + }, + { + "label": "Personal Records", + "uri": url_for("reflec_pages.viewrecords", userid=g.userID), + }, + ] + ) + reflec_entries.extend( + [ { - 'label': 'Game Options', - 'uri': url_for('reflec_pages.viewsettings'), + "label": "Global Scores", + "uri": url_for("reflec_pages.viewnetworkscores"), }, { - 'label': 'Rivals', - 'uri': url_for('reflec_pages.viewrivals'), + "label": "Global Records", + "uri": url_for("reflec_pages.viewnetworkrecords"), }, { - 'label': 'Personal Profile', - 'uri': url_for('reflec_pages.viewplayer', userid=g.userID), + "label": "All Players", + "uri": url_for("reflec_pages.viewplayers"), }, - { - 'label': 'Personal Scores', - 'uri': url_for('reflec_pages.viewscores', userid=g.userID), - }, - { - 'label': 'Personal Records', - 'uri': url_for('reflec_pages.viewrecords', userid=g.userID), - }, - ]) - reflec_entries.extend([ - { - 'label': 'Global Scores', - 'uri': url_for('reflec_pages.viewnetworkscores'), - }, - { - 'label': 'Global Records', - 'uri': url_for('reflec_pages.viewnetworkrecords'), - }, - { - 'label': 'All Players', - 'uri': url_for('reflec_pages.viewplayers'), - }, - ]) + ] + ) pages.append( { - 'label': 'Reflec Beat', - 'entries': reflec_entries, - 'base_uri': app.blueprints['reflec_pages'].url_prefix, - 'gamecode': GameConstants.REFLEC_BEAT.value, + "label": "Reflec Beat", + "entries": reflec_entries, + "base_uri": app.blueprints["reflec_pages"].url_prefix, + "gamecode": GameConstants.REFLEC_BEAT.value, }, ) @@ -670,48 +744,52 @@ def navigation() -> Dict[str, Any]: # SDVX pages sdvx_entries = [] if len([p for p in profiles if p[0] == GameConstants.SDVX]) > 0: - sdvx_entries.extend([ + sdvx_entries.extend( + [ + { + "label": "Game Options", + "uri": url_for("sdvx_pages.viewsettings"), + }, + { + "label": "Rivals", + "uri": url_for("sdvx_pages.viewrivals"), + }, + { + "label": "Personal Profile", + "uri": url_for("sdvx_pages.viewplayer", userid=g.userID), + }, + { + "label": "Personal Scores", + "uri": url_for("sdvx_pages.viewscores", userid=g.userID), + }, + { + "label": "Personal Records", + "uri": url_for("sdvx_pages.viewrecords", userid=g.userID), + }, + ] + ) + sdvx_entries.extend( + [ { - 'label': 'Game Options', - 'uri': url_for('sdvx_pages.viewsettings'), + "label": "Global Scores", + "uri": url_for("sdvx_pages.viewnetworkscores"), }, { - 'label': 'Rivals', - 'uri': url_for('sdvx_pages.viewrivals'), + "label": "Global Records", + "uri": url_for("sdvx_pages.viewnetworkrecords"), }, { - 'label': 'Personal Profile', - 'uri': url_for('sdvx_pages.viewplayer', userid=g.userID), + "label": "All Players", + "uri": url_for("sdvx_pages.viewplayers"), }, - { - 'label': 'Personal Scores', - 'uri': url_for('sdvx_pages.viewscores', userid=g.userID), - }, - { - 'label': 'Personal Records', - 'uri': url_for('sdvx_pages.viewrecords', userid=g.userID), - }, - ]) - sdvx_entries.extend([ - { - 'label': 'Global Scores', - 'uri': url_for('sdvx_pages.viewnetworkscores'), - }, - { - 'label': 'Global Records', - 'uri': url_for('sdvx_pages.viewnetworkrecords'), - }, - { - 'label': 'All Players', - 'uri': url_for('sdvx_pages.viewplayers'), - }, - ]) + ] + ) pages.append( { - 'label': 'SDVX', - 'entries': sdvx_entries, - 'base_uri': app.blueprints['sdvx_pages'].url_prefix, - 'gamecode': GameConstants.SDVX.value, + "label": "SDVX", + "entries": sdvx_entries, + "base_uri": app.blueprints["sdvx_pages"].url_prefix, + "gamecode": GameConstants.SDVX.value, }, ) @@ -719,44 +797,44 @@ def navigation() -> Dict[str, Any]: if user.admin: pages.append( { - 'label': 'Admin', - 'uri': url_for('admin_pages.viewsettings'), - 'entries': [ + "label": "Admin", + "uri": url_for("admin_pages.viewsettings"), + "entries": [ { - 'label': 'Events', - 'uri': url_for('admin_pages.viewevents'), + "label": "Events", + "uri": url_for("admin_pages.viewevents"), }, { - 'label': 'Data API', - 'uri': url_for('admin_pages.viewapi'), + "label": "Data API", + "uri": url_for("admin_pages.viewapi"), }, { - 'label': 'Arcades', - 'uri': url_for('admin_pages.viewarcades'), + "label": "Arcades", + "uri": url_for("admin_pages.viewarcades"), }, { - 'label': 'PCBIDs', - 'uri': url_for('admin_pages.viewmachines'), + "label": "PCBIDs", + "uri": url_for("admin_pages.viewmachines"), }, { - 'label': 'Game Settings', - 'uri': url_for('admin_pages.viewgamesettings'), + "label": "Game Settings", + "uri": url_for("admin_pages.viewgamesettings"), }, { - 'label': 'Cards', - 'uri': url_for('admin_pages.viewcards'), + "label": "Cards", + "uri": url_for("admin_pages.viewcards"), }, { - 'label': 'Users', - 'uri': url_for('admin_pages.viewusers'), + "label": "Users", + "uri": url_for("admin_pages.viewusers"), }, { - 'label': 'News', - 'uri': url_for('admin_pages.viewnews'), + "label": "News", + "uri": url_for("admin_pages.viewnews"), }, ], - 'base_uri': app.blueprints['admin_pages'].url_prefix, - 'right_justify': True, + "base_uri": app.blueprints["admin_pages"].url_prefix, + "right_justify": True, }, ) @@ -764,58 +842,64 @@ def navigation() -> Dict[str, Any]: arcadeids = g.data.local.machine.from_userid(g.userID) if len(arcadeids) == 1: arcade = g.data.local.machine.get_arcade(arcadeids[0]) - pages.append({ - 'label': arcade.name, - 'uri': url_for('arcade_pages.viewarcade', arcadeid=arcade.id), - 'right_justify': True, - }) + pages.append( + { + "label": arcade.name, + "uri": url_for("arcade_pages.viewarcade", arcadeid=arcade.id), + "right_justify": True, + } + ) elif len(arcadeids) > 1: entries = [] for arcadeid in arcadeids: arcade = g.data.local.machine.get_arcade(arcadeid) - entries.append({ - 'label': arcade.name, - 'uri': url_for('arcade_pages.viewarcade', arcadeid=arcade.id), - }) + entries.append( + { + "label": arcade.name, + "uri": url_for("arcade_pages.viewarcade", arcadeid=arcade.id), + } + ) - pages.append({ - 'label': 'Arcades', - 'entries': entries, - 'base_uri': app.blueprints['arcade_pages'].url_prefix, - 'right_justify': True, - }) + pages.append( + { + "label": "Arcades", + "entries": entries, + "base_uri": app.blueprints["arcade_pages"].url_prefix, + "right_justify": True, + } + ) # User account pages pages.append( { - 'label': 'Account', - 'uri': url_for('account_pages.viewaccount'), - 'entries': [ + "label": "Account", + "uri": url_for("account_pages.viewaccount"), + "entries": [ { - 'label': 'Cards', - 'uri': url_for('account_pages.viewcards'), + "label": "Cards", + "uri": url_for("account_pages.viewcards"), }, ], - 'base_uri': app.blueprints['account_pages'].url_prefix, - 'right_justify': True, + "base_uri": app.blueprints["account_pages"].url_prefix, + "right_justify": True, }, ) # GTFO button pages.append( { - 'label': 'Log Out', - 'uri': url_for('account_pages.logout'), - 'right_justify': True, + "label": "Log Out", + "uri": url_for("account_pages.logout"), + "right_justify": True, }, ) return { - 'current_path': request.path, - 'show_navigation': True, - 'navigation': pages, - 'components': components, - 'any': jinja2_any, - 'theme_url': jinja2_theme, - 'cache_bust': f"v={FRONTEND_CACHE_BUST}", + "current_path": request.path, + "show_navigation": True, + "navigation": pages, + "components": components, + "any": jinja2_any, + "theme_url": jinja2_theme, + "cache_bust": f"v={FRONTEND_CACHE_BUST}", } diff --git a/bemani/frontend/arcade/arcade.py b/bemani/frontend/arcade/arcade.py index 83c1ddb..8af1939 100644 --- a/bemani/frontend/arcade/arcade.py +++ b/bemani/frontend/arcade/arcade.py @@ -3,7 +3,13 @@ from typing import Any, Dict, Optional from flask import Blueprint, request, Response, abort, url_for from bemani.backend.base import Base -from bemani.common import CardCipher, CardCipherException, ValidatedDict, GameConstants, RegionConstants +from bemani.common import ( + CardCipher, + CardCipherException, + ValidatedDict, + GameConstants, + RegionConstants, +) from bemani.data import Arcade, ArcadeID, Event, Machine from bemani.frontend.app import loginrequired, jsonify, render_react, valid_pin from bemani.frontend.gamesettings import get_game_settings @@ -13,9 +19,9 @@ from bemani.frontend.types import g arcade_pages = Blueprint( - 'arcade_pages', + "arcade_pages", __name__, - url_prefix='/arcade', + url_prefix="/arcade", template_folder=templates_location, static_folder=static_location, ) @@ -27,66 +33,68 @@ def is_user_editable(machine: Machine) -> bool: def format_machine(machine: Machine) -> Dict[str, Any]: if machine.game is None: - game = 'any game' + game = "any game" elif machine.version is None: game = { - GameConstants.BISHI_BASHI: 'BishiBashi', - GameConstants.DDR: 'DDR', - GameConstants.IIDX: 'IIDX', - GameConstants.JUBEAT: 'Jubeat', - GameConstants.MGA: 'Metal Gear Arcade', - GameConstants.MUSECA: 'MÚSECA', - GameConstants.POPN_MUSIC: 'Pop\'n Music', - GameConstants.REFLEC_BEAT: 'Reflec Beat', - GameConstants.SDVX: 'SDVX', + GameConstants.BISHI_BASHI: "BishiBashi", + GameConstants.DDR: "DDR", + GameConstants.IIDX: "IIDX", + GameConstants.JUBEAT: "Jubeat", + GameConstants.MGA: "Metal Gear Arcade", + GameConstants.MUSECA: "MÚSECA", + GameConstants.POPN_MUSIC: "Pop'n Music", + GameConstants.REFLEC_BEAT: "Reflec Beat", + GameConstants.SDVX: "SDVX", }.get(machine.game) elif machine.version > 0: game = [ - name for (game, version, name) in Base.all_games() + name + for (game, version, name) in Base.all_games() if game == machine.game and version == machine.version ][0] elif machine.version < 0: game = [ - name for (game, version, name) in Base.all_games() + name + for (game, version, name) in Base.all_games() if game == machine.game and version == -machine.version - ][0] + ' or older' + ][0] + " or older" return { - 'pcbid': machine.pcbid, - 'name': machine.name, - 'description': machine.description, - 'port': machine.port, - 'game': game, - 'editable': is_user_editable(machine), + "pcbid": machine.pcbid, + "name": machine.name, + "description": machine.description, + "port": machine.port, + "game": game, + "editable": is_user_editable(machine), } def format_arcade(arcade: Arcade) -> Dict[str, Any]: return { - 'id': arcade.id, - 'name': arcade.name, - 'description': arcade.description, - 'pin': arcade.pin, - 'region': arcade.region, - 'paseli_enabled': arcade.data.get_bool('paseli_enabled'), - 'paseli_infinite': arcade.data.get_bool('paseli_infinite'), - 'mask_services_url': arcade.data.get_bool('mask_services_url'), - 'owners': arcade.owners, + "id": arcade.id, + "name": arcade.name, + "description": arcade.description, + "pin": arcade.pin, + "region": arcade.region, + "paseli_enabled": arcade.data.get_bool("paseli_enabled"), + "paseli_infinite": arcade.data.get_bool("paseli_infinite"), + "mask_services_url": arcade.data.get_bool("mask_services_url"), + "owners": arcade.owners, } def format_event(event: Event) -> Dict[str, Any]: return { - 'id': event.id, - 'timestamp': event.timestamp, - 'userid': event.userid, - 'arcadeid': event.arcadeid, - 'type': event.type, - 'data': event.data, + "id": event.id, + "timestamp": event.timestamp, + "userid": event.userid, + "arcadeid": event.arcadeid, + "type": event.type, + "data": event.data, } -@arcade_pages.route('/') +@arcade_pages.route("/") @loginrequired def viewarcade(arcadeid: int) -> Response: # Cast the ID for type safety. @@ -96,40 +104,65 @@ def viewarcade(arcadeid: int) -> Response: if arcade is None or g.userID not in arcade.owners: abort(403) machines = [ - format_machine(machine) for machine in g.data.local.machine.get_all_machines(arcade.id) + format_machine(machine) + for machine in g.data.local.machine.get_all_machines(arcade.id) ] return render_react( arcade.name, - 'arcade/arcade.react.js', + "arcade/arcade.react.js", { - 'arcade': format_arcade(arcade), - 'regions': RegionConstants.LUT, - 'machines': machines, - 'game_settings': get_game_settings(g.data, arcadeid), - 'balances': {balance[0]: balance[1] for balance in g.data.local.machine.get_balances(arcadeid)}, - 'users': {user.id: user.username for user in g.data.local.user.get_all_users()}, - 'events': [format_event(event) for event in g.data.local.network.get_events(arcadeid=arcadeid, event='paseli_transaction')], - 'enforcing': g.config.server.enforce_pcbid, - 'max_pcbids': g.config.server.pcbid_self_grant_limit, + "arcade": format_arcade(arcade), + "regions": RegionConstants.LUT, + "machines": machines, + "game_settings": get_game_settings(g.data, arcadeid), + "balances": { + balance[0]: balance[1] + for balance in g.data.local.machine.get_balances(arcadeid) + }, + "users": { + user.id: user.username for user in g.data.local.user.get_all_users() + }, + "events": [ + format_event(event) + for event in g.data.local.network.get_events( + arcadeid=arcadeid, event="paseli_transaction" + ) + ], + "enforcing": g.config.server.enforce_pcbid, + "max_pcbids": g.config.server.pcbid_self_grant_limit, }, { - 'refresh': url_for('arcade_pages.listarcade', arcadeid=arcadeid), - 'paseli_enabled': url_for('arcade_pages.updatearcade', arcadeid=arcadeid, attribute='paseli_enabled'), - 'paseli_infinite': url_for('arcade_pages.updatearcade', arcadeid=arcadeid, attribute='paseli_infinite'), - 'mask_services_url': url_for('arcade_pages.updatearcade', arcadeid=arcadeid, attribute='mask_services_url'), - 'update_settings': url_for('arcade_pages.updatesettings', arcadeid=arcadeid), - 'add_balance': url_for('arcade_pages.addbalance', arcadeid=arcadeid), - 'update_balance': url_for('arcade_pages.updatebalance', arcadeid=arcadeid), - 'update_pin': url_for('arcade_pages.updatepin', arcadeid=arcadeid), - 'update_region': url_for('arcade_pages.updateregion', arcadeid=arcadeid), - 'generatepcbid': url_for('arcade_pages.generatepcbid', arcadeid=arcadeid), - 'updatepcbid': url_for('arcade_pages.updatepcbid', arcadeid=arcadeid), - 'removepcbid': url_for('arcade_pages.removepcbid', arcadeid=arcadeid), + "refresh": url_for("arcade_pages.listarcade", arcadeid=arcadeid), + "paseli_enabled": url_for( + "arcade_pages.updatearcade", + arcadeid=arcadeid, + attribute="paseli_enabled", + ), + "paseli_infinite": url_for( + "arcade_pages.updatearcade", + arcadeid=arcadeid, + attribute="paseli_infinite", + ), + "mask_services_url": url_for( + "arcade_pages.updatearcade", + arcadeid=arcadeid, + attribute="mask_services_url", + ), + "update_settings": url_for( + "arcade_pages.updatesettings", arcadeid=arcadeid + ), + "add_balance": url_for("arcade_pages.addbalance", arcadeid=arcadeid), + "update_balance": url_for("arcade_pages.updatebalance", arcadeid=arcadeid), + "update_pin": url_for("arcade_pages.updatepin", arcadeid=arcadeid), + "update_region": url_for("arcade_pages.updateregion", arcadeid=arcadeid), + "generatepcbid": url_for("arcade_pages.generatepcbid", arcadeid=arcadeid), + "updatepcbid": url_for("arcade_pages.updatepcbid", arcadeid=arcadeid), + "removepcbid": url_for("arcade_pages.removepcbid", arcadeid=arcadeid), }, ) -@arcade_pages.route('//list') +@arcade_pages.route("//list") @jsonify @loginrequired def listarcade(arcadeid: int) -> Dict[str, Any]: @@ -139,32 +172,41 @@ def listarcade(arcadeid: int) -> Dict[str, Any]: # Make sure the arcade is valid arcade = g.data.local.machine.get_arcade(arcadeid) if arcade is None or g.userID not in arcade.owners: - raise Exception('You don\'t own this arcade, refusing to list!') + raise Exception("You don't own this arcade, refusing to list!") machines = [ - format_machine(machine) for machine in g.data.local.machine.get_all_machines(arcade.id) + format_machine(machine) + for machine in g.data.local.machine.get_all_machines(arcade.id) ] return { - 'machines': machines, - 'balances': {balance[0]: balance[1] for balance in g.data.local.machine.get_balances(arcadeid)}, - 'users': {user.id: user.username for user in g.data.local.user.get_all_users()}, - 'events': [format_event(event) for event in g.data.local.network.get_events(arcadeid=arcadeid, event='paseli_transaction')], + "machines": machines, + "balances": { + balance[0]: balance[1] + for balance in g.data.local.machine.get_balances(arcadeid) + }, + "users": {user.id: user.username for user in g.data.local.user.get_all_users()}, + "events": [ + format_event(event) + for event in g.data.local.network.get_events( + arcadeid=arcadeid, event="paseli_transaction" + ) + ], } -@arcade_pages.route('//balance/add', methods=['POST']) +@arcade_pages.route("//balance/add", methods=["POST"]) @jsonify @loginrequired def addbalance(arcadeid: int) -> Dict[str, Any]: # Cast the ID for type safety. arcadeid = ArcadeID(arcadeid) - credits = request.get_json()['credits'] - card = request.get_json()['card'] + credits = request.get_json()["credits"] + card = request.get_json()["card"] # Make sure the arcade is valid arcade = g.data.local.machine.get_arcade(arcadeid) if arcade is None or g.userID not in arcade.owners: - raise Exception('You don\'t own this arcade, refusing to update!') + raise Exception("You don't own this arcade, refusing to update!") try: cardid = CardCipher.decode(card) @@ -173,90 +215,106 @@ def addbalance(arcadeid: int) -> Dict[str, Any]: userid = None if userid is None: - raise Exception('Unable to find user by this card!') + raise Exception("Unable to find user by this card!") # Update balance balance = g.data.local.user.update_balance(userid, arcadeid, credits) if balance is not None: g.data.local.network.put_event( - 'paseli_transaction', + "paseli_transaction", { - 'delta': credits, - 'balance': balance, - 'reason': 'arcade operator adjustment', + "delta": credits, + "balance": balance, + "reason": "arcade operator adjustment", }, userid=userid, arcadeid=arcadeid, ) return { - 'balances': {balance[0]: balance[1] for balance in g.data.local.machine.get_balances(arcadeid)}, - 'users': {user.id: user.username for user in g.data.local.user.get_all_users()}, - 'events': [format_event(event) for event in g.data.local.network.get_events(arcadeid=arcadeid, event='paseli_transaction')], + "balances": { + balance[0]: balance[1] + for balance in g.data.local.machine.get_balances(arcadeid) + }, + "users": {user.id: user.username for user in g.data.local.user.get_all_users()}, + "events": [ + format_event(event) + for event in g.data.local.network.get_events( + arcadeid=arcadeid, event="paseli_transaction" + ) + ], } -@arcade_pages.route('//balance/update', methods=['POST']) +@arcade_pages.route("//balance/update", methods=["POST"]) @jsonify @loginrequired def updatebalance(arcadeid: int) -> Dict[str, Any]: # Cast the ID for type safety. arcadeid = ArcadeID(arcadeid) - credits = request.get_json()['credits'] + credits = request.get_json()["credits"] # Make sure the arcade is valid arcade = g.data.local.machine.get_arcade(arcadeid) if arcade is None or g.userID not in arcade.owners: - raise Exception('You don\'t own this arcade, refusing to update!') + raise Exception("You don't own this arcade, refusing to update!") # Update balances for userid in credits: balance = g.data.local.user.update_balance(userid, arcadeid, credits[userid]) if balance is not None: g.data.local.network.put_event( - 'paseli_transaction', + "paseli_transaction", { - 'delta': credits[userid], - 'balance': balance, - 'reason': 'arcade operator adjustment', + "delta": credits[userid], + "balance": balance, + "reason": "arcade operator adjustment", }, userid=userid, arcadeid=arcadeid, ) return { - 'balances': {balance[0]: balance[1] for balance in g.data.local.machine.get_balances(arcadeid)}, - 'users': {user.id: user.username for user in g.data.local.user.get_all_users()}, - 'events': [format_event(event) for event in g.data.local.network.get_events(arcadeid=arcadeid, event='paseli_transaction')], + "balances": { + balance[0]: balance[1] + for balance in g.data.local.machine.get_balances(arcadeid) + }, + "users": {user.id: user.username for user in g.data.local.user.get_all_users()}, + "events": [ + format_event(event) + for event in g.data.local.network.get_events( + arcadeid=arcadeid, event="paseli_transaction" + ) + ], } -@arcade_pages.route('//pin/update', methods=['POST']) +@arcade_pages.route("//pin/update", methods=["POST"]) @jsonify @loginrequired def updatepin(arcadeid: int) -> Dict[str, Any]: # Cast the ID for type safety. arcadeid = ArcadeID(arcadeid) - pin = request.get_json()['pin'] + pin = request.get_json()["pin"] # Make sure the arcade is valid arcade = g.data.local.machine.get_arcade(arcadeid) if arcade is None or g.userID not in arcade.owners: - raise Exception('You don\'t own this arcade, refusing to update!') + raise Exception("You don't own this arcade, refusing to update!") - if not valid_pin(pin, 'arcade'): - raise Exception('Invalid PIN, must be exactly 8 digits!') + if not valid_pin(pin, "arcade"): + raise Exception("Invalid PIN, must be exactly 8 digits!") # Update and save arcade.pin = pin g.data.local.machine.put_arcade(arcade) # Return nothing - return {'pin': pin} + return {"pin": pin} -@arcade_pages.route('//region/update', methods=['POST']) +@arcade_pages.route("//region/update", methods=["POST"]) @jsonify @loginrequired def updateregion(arcadeid: int) -> Dict[str, Any]: @@ -264,27 +322,29 @@ def updateregion(arcadeid: int) -> Dict[str, Any]: arcadeid = ArcadeID(arcadeid) try: - region = int(request.get_json()['region']) + region = int(request.get_json()["region"]) except Exception: region = 0 # Make sure the arcade is valid arcade = g.data.local.machine.get_arcade(arcadeid) if arcade is None or g.userID not in arcade.owners: - raise Exception('You don\'t own this arcade, refusing to update!') + raise Exception("You don't own this arcade, refusing to update!") - if region not in {RegionConstants.EUROPE, RegionConstants.NO_MAPPING} and (region < RegionConstants.MIN or region > RegionConstants.MAX): - raise Exception('Invalid region!') + if region not in {RegionConstants.EUROPE, RegionConstants.NO_MAPPING} and ( + region < RegionConstants.MIN or region > RegionConstants.MAX + ): + raise Exception("Invalid region!") # Update and save arcade.region = region g.data.local.machine.put_arcade(arcade) # Return nothing - return {'region': region} + return {"region": region} -@arcade_pages.route('//pcbids/generate', methods=['POST']) +@arcade_pages.route("//pcbids/generate", methods=["POST"]) @jsonify @loginrequired def generatepcbid(arcadeid: int) -> Dict[str, Any]: @@ -293,42 +353,53 @@ def generatepcbid(arcadeid: int) -> Dict[str, Any]: # Make sure that arcade owners are allowed to generate PCBIDs in the first place. if g.config.server.pcbid_self_grant_limit <= 0: - raise Exception('You don\'t have permission to generate PCBIDs!') + raise Exception("You don't have permission to generate PCBIDs!") # Make sure the arcade is valid and the current user has permissions to # modify it. arcade = g.data.local.machine.get_arcade(arcadeid) if arcade is None or g.userID not in arcade.owners: - raise Exception('You don\'t own this arcade, refusing to update!') + raise Exception("You don't own this arcade, refusing to update!") # Make sure the user hasn't gone over their limit of PCBIDs. existing_machine_count = len( - [machine for machine in g.data.local.machine.get_all_machines(arcade.id) if is_user_editable(machine)] + [ + machine + for machine in g.data.local.machine.get_all_machines(arcade.id) + if is_user_editable(machine) + ] ) if existing_machine_count >= g.config.server.pcbid_self_grant_limit: - raise Exception('You have hit your limit of allowed PCBIDs!') + raise Exception("You have hit your limit of allowed PCBIDs!") # Will be set by the game on boot. - name: str = 'なし' + name: str = "なし" pcbid: Optional[str] = None - new_machine = request.get_json()['machine'] + new_machine = request.get_json()["machine"] while pcbid is None: # Generate a new PCBID, check for uniqueness - potential_pcbid = "01201000000000" + "".join([random.choice("0123456789ABCDEF") for _ in range(6)]) + potential_pcbid = "01201000000000" + "".join( + [random.choice("0123456789ABCDEF") for _ in range(6)] + ) if g.data.local.machine.get_machine(potential_pcbid) is None: pcbid = potential_pcbid # Finally, add the generated PCBID to the network. - g.data.local.machine.create_machine(pcbid, name, new_machine['description'], arcade.id) + g.data.local.machine.create_machine( + pcbid, name, new_machine["description"], arcade.id + ) # Just return all machines for ease of updating return { - 'machines': [format_machine(machine) for machine in g.data.local.machine.get_all_machines(arcade.id)], + "machines": [ + format_machine(machine) + for machine in g.data.local.machine.get_all_machines(arcade.id) + ], } -@arcade_pages.route('//pcbids/update', methods=['POST']) +@arcade_pages.route("//pcbids/update", methods=["POST"]) @jsonify @loginrequired def updatepcbid(arcadeid: int) -> Dict[str, Any]: @@ -337,50 +408,57 @@ def updatepcbid(arcadeid: int) -> Dict[str, Any]: # Make sure that arcade owners are allowed to edit PCBIDs in the first place. if g.config.server.pcbid_self_grant_limit <= 0: - raise Exception('You don\'t have permission to edit PCBIDs!') + raise Exception("You don't have permission to edit PCBIDs!") # Make sure the arcade is valid and the current user has permissions to # modify it. arcade = g.data.local.machine.get_arcade(arcadeid) if arcade is None or g.userID not in arcade.owners: - raise Exception('You don\'t own this arcade, refusing to update!') + raise Exception("You don't own this arcade, refusing to update!") # Grab the new updates as well as the old values to validate editing permissions. - updated_machine = request.get_json()['machine'] - current_machine = g.data.local.machine.get_machine(updated_machine['pcbid']) + updated_machine = request.get_json()["machine"] + current_machine = g.data.local.machine.get_machine(updated_machine["pcbid"]) # Make sure the PCBID we are trying to modify is actually owned by this arcade. # Also, make sure that the PCBID is actually user-editable. - if current_machine is None or current_machine.arcade != arcadeid or not is_user_editable(current_machine): - raise Exception('You don\'t own this PCBID, refusing to update!') + if ( + current_machine is None + or current_machine.arcade != arcadeid + or not is_user_editable(current_machine) + ): + raise Exception("You don't own this PCBID, refusing to update!") # Make sure the port is actually valid. try: - port = int(updated_machine['port']) + port = int(updated_machine["port"]) except ValueError: port = None if port is None: - raise Exception('The specified port is invalid!') + raise Exception("The specified port is invalid!") if port < 1 or port > 65535: - raise Exception('The specified port is out of range!') + raise Exception("The specified port is out of range!") # Make sure we don't duplicate port assignments. other_pcbid = g.data.local.machine.from_port(port) - if other_pcbid is not None and other_pcbid != updated_machine['pcbid']: - raise Exception('The specified port is already in use!') + if other_pcbid is not None and other_pcbid != updated_machine["pcbid"]: + raise Exception("The specified port is already in use!") # Update the allowed bits of data. - current_machine.description = updated_machine['description'] + current_machine.description = updated_machine["description"] current_machine.port = port g.data.local.machine.put_machine(current_machine) # Just return all machines for ease of updating return { - 'machines': [format_machine(machine) for machine in g.data.local.machine.get_all_machines(arcade.id)], + "machines": [ + format_machine(machine) + for machine in g.data.local.machine.get_all_machines(arcade.id) + ], } -@arcade_pages.route('//pcbids/remove', methods=['POST']) +@arcade_pages.route("//pcbids/remove", methods=["POST"]) @jsonify @loginrequired def removepcbid(arcadeid: int) -> Dict[str, Any]: @@ -389,33 +467,36 @@ def removepcbid(arcadeid: int) -> Dict[str, Any]: # Make sure that arcade owners are allowed to edit PCBIDs in the first place. if g.config.server.pcbid_self_grant_limit <= 0: - raise Exception('You don\'t have permission to edit PCBIDs!') + raise Exception("You don't have permission to edit PCBIDs!") # Make sure the arcade is valid and the current user has permissions to # modify it. arcade = g.data.local.machine.get_arcade(arcadeid) if arcade is None or g.userID not in arcade.owners: - raise Exception('You don\'t own this arcade, refusing to update!') + raise Exception("You don't own this arcade, refusing to update!") # Attempt to look the PCBID we are deleting up to ensure it exists. - pcbid = request.get_json()['pcbid'] + pcbid = request.get_json()["pcbid"] # Make sure the PCBID we are trying to delete is actually owned by this arcade. # Also, make sure that the PCBID is actually user-editable. machine = g.data.local.machine.get_machine(pcbid) if machine is None or machine.arcade != arcadeid or not is_user_editable(machine): - raise Exception('You don\'t own this PCBID, refusing to update!') + raise Exception("You don't own this PCBID, refusing to update!") # Actually delete it. g.data.local.machine.destroy_machine(pcbid) # Just return all machines for ease of updating return { - 'machines': [format_machine(machine) for machine in g.data.local.machine.get_all_machines(arcade.id)], + "machines": [ + format_machine(machine) + for machine in g.data.local.machine.get_all_machines(arcade.id) + ], } -@arcade_pages.route('//update/', methods=['POST']) +@arcade_pages.route("//update/", methods=["POST"]) @jsonify @loginrequired def updatearcade(arcadeid: int, attribute: str) -> Dict[str, Any]: @@ -423,29 +504,29 @@ def updatearcade(arcadeid: int, attribute: str) -> Dict[str, Any]: arcadeid = ArcadeID(arcadeid) # Attempt to look this arcade up - new_value = request.get_json()['value'] + new_value = request.get_json()["value"] arcade = g.data.local.machine.get_arcade(arcadeid) if arcade is None or g.userID not in arcade.owners: - raise Exception('You don\'t own this arcade, refusing to update!') + raise Exception("You don't own this arcade, refusing to update!") - if attribute == 'paseli_enabled': - arcade.data.replace_bool('paseli_enabled', new_value) - elif attribute == 'paseli_infinite': - arcade.data.replace_bool('paseli_infinite', new_value) - elif attribute == 'mask_services_url': - arcade.data.replace_bool('mask_services_url', new_value) + if attribute == "paseli_enabled": + arcade.data.replace_bool("paseli_enabled", new_value) + elif attribute == "paseli_infinite": + arcade.data.replace_bool("paseli_infinite", new_value) + elif attribute == "mask_services_url": + arcade.data.replace_bool("mask_services_url", new_value) else: - raise Exception(f'Unknown attribute {attribute} to update!') + raise Exception(f"Unknown attribute {attribute} to update!") g.data.local.machine.put_arcade(arcade) # Return the updated value return { - 'value': new_value, + "value": new_value, } -@arcade_pages.route('//settings/update', methods=['POST']) +@arcade_pages.route("//settings/update", methods=["POST"]) @jsonify @loginrequired def updatesettings(arcadeid: int) -> Dict[str, Any]: @@ -455,37 +536,42 @@ def updatesettings(arcadeid: int) -> Dict[str, Any]: # Attempt to look this arcade up arcade = g.data.local.machine.get_arcade(arcadeid) if arcade is None or g.userID not in arcade.owners: - raise Exception('You don\'t own this arcade, refusing to update!') + raise Exception("You don't own this arcade, refusing to update!") - game = GameConstants(request.get_json()['game']) - version = request.get_json()['version'] + game = GameConstants(request.get_json()["game"]) + version = request.get_json()["version"] for setting_type, update_function in [ - ('bools', 'replace_bool'), - ('ints', 'replace_int'), - ('strs', 'replace_str'), - ('longstrs', 'replace_str'), + ("bools", "replace_bool"), + ("ints", "replace_int"), + ("strs", "replace_str"), + ("longstrs", "replace_str"), ]: for game_setting in request.get_json()[setting_type]: # Grab the value to update - category = game_setting['category'] - setting = game_setting['setting'] - new_value = game_setting['value'] + category = game_setting["category"] + setting = game_setting["setting"] + new_value = game_setting["value"] # Update the value - current_settings = g.data.local.machine.get_settings(arcadeid, game, version, category) + current_settings = g.data.local.machine.get_settings( + arcadeid, game, version, category + ) if current_settings is None: current_settings = ValidatedDict() getattr(current_settings, update_function)(setting, new_value) # Save it back - g.data.local.machine.put_settings(arcade.id, game, version, category, current_settings) + g.data.local.machine.put_settings( + arcade.id, game, version, category, current_settings + ) # Return the updated value return { - 'game_settings': [ - gs for gs in get_game_settings(g.data, arcadeid) - if gs['game'] == game.value and gs['version'] == version + "game_settings": [ + gs + for gs in get_game_settings(g.data, arcadeid) + if gs["game"] == game.value and gs["version"] == version ][0], } diff --git a/bemani/frontend/base.py b/bemani/frontend/base.py index 714bbef..0c8b44c 100644 --- a/bemani/frontend/base.py +++ b/bemani/frontend/base.py @@ -15,6 +15,7 @@ class FrontendBase(ABC): All subclasses should override this attribute with the string the game series uses in the DB. """ + game: GameConstants """ @@ -40,18 +41,18 @@ class FrontendBase(ABC): self.cache = cache def make_index(self, songid: int, chart: int) -> str: - return f'{songid}-{chart}' + return f"{songid}-{chart}" def get_duplicate_id(self, musicid: int, chart: int) -> Optional[Tuple[int, int]]: return None def format_score(self, userid: UserID, score: Score) -> Dict[str, Any]: return { - 'userid': str(userid), - 'songid': score.id, - 'chart': score.chart, - 'plays': score.plays, - 'points': score.points, + "userid": str(userid), + "songid": score.id, + "chart": score.chart, + "plays": score.plays, + "points": score.points, } def format_top_score(self, userid: UserID, score: Score) -> Dict[str, Any]: @@ -59,34 +60,36 @@ class FrontendBase(ABC): def format_attempt(self, userid: UserID, attempt: Attempt) -> Dict[str, Any]: return { - 'userid': str(userid), - 'songid': attempt.id, - 'chart': attempt.chart, - 'timestamp': attempt.timestamp, - 'raised': attempt.new_record, - 'points': attempt.points, + "userid": str(userid), + "songid": attempt.id, + "chart": attempt.chart, + "timestamp": attempt.timestamp, + "raised": attempt.new_record, + "points": attempt.points, } def format_rival(self, link: Link, profile: Profile) -> Dict[str, Any]: return { - 'type': link.type, - 'userid': str(link.other_userid), - 'remote': RemoteUser.is_remote(link.other_userid), + "type": link.type, + "userid": str(link.other_userid), + "remote": RemoteUser.is_remote(link.other_userid), } - def format_profile(self, profile: Profile, playstats: ValidatedDict) -> Dict[str, Any]: + def format_profile( + self, profile: Profile, playstats: ValidatedDict + ) -> Dict[str, Any]: return { - 'name': profile.get_str('name'), - 'extid': ID.format_extid(profile.extid), - 'first_play_time': playstats.get_int('first_play_timestamp'), - 'last_play_time': playstats.get_int('last_play_timestamp'), + "name": profile.get_str("name"), + "extid": ID.format_extid(profile.extid), + "first_play_time": playstats.get_int("first_play_timestamp"), + "last_play_time": playstats.get_int("last_play_timestamp"), } def format_song(self, song: Song) -> Dict[str, Any]: return { - 'name': song.name, - 'artist': song.artist, - 'genre': song.genre, + "name": song.name, + "artist": song.artist, + "genre": song.genre, } def merge_song(self, existing: Dict[str, Any], new: Song) -> Dict[str, Any]: @@ -97,16 +100,16 @@ class FrontendBase(ABC): if num % 10 == 0: return elems else: - return elems[:-(num % 10)] + return elems[: -(num % 10)] def all_games(self) -> Iterator[Tuple[GameConstants, int, str]]: """ Override this to return an interator based on a game series factory. """ - def get_all_songs(self, force_db_load: bool=False) -> Dict[int, Dict[str, Any]]: + def get_all_songs(self, force_db_load: bool = False) -> Dict[int, Dict[str, Any]]: if not force_db_load: - cached_songs = self.cache.get(f'{self.game.value}.sorted_songs') + cached_songs = self.cache.get(f"{self.game.value}.sorted_songs") if cached_songs is not None: # Not sure why mypy insists that this is a str instead of Any. return cast(Dict[int, Dict[str, Any]], cached_songs) @@ -122,31 +125,44 @@ class FrontendBase(ABC): else: songs[song.id] = self.merge_song(songs[song.id], song) - self.cache.set(f'{self.game.value}.sorted_songs', songs, timeout=600) + self.cache.set(f"{self.game.value}.sorted_songs", songs, timeout=600) return songs - def get_all_player_info(self, userids: List[UserID], limit: Optional[int]=None, allow_remote: bool=False) -> Dict[UserID, Dict[int, Dict[str, Any]]]: + def get_all_player_info( + self, + userids: List[UserID], + limit: Optional[int] = None, + allow_remote: bool = False, + ) -> Dict[UserID, Dict[int, Dict[str, Any]]]: info: Dict[UserID, Dict[int, Dict[str, Any]]] = {} playstats: Dict[UserID, ValidatedDict] = {} # Find all versions of the users' profiles, sorted newest to oldest. - versions = sorted([version for (game, version, name) in self.all_games()], reverse=True) + versions = sorted( + [version for (game, version, name) in self.all_games()], reverse=True + ) for userid in userids: info[userid] = {} userlimit = limit for version in versions: if allow_remote: - profile = self.data.remote.user.get_profile(self.game, version, userid) + profile = self.data.remote.user.get_profile( + self.game, version, userid + ) else: - profile = self.data.local.user.get_profile(self.game, version, userid) + profile = self.data.local.user.get_profile( + self.game, version, userid + ) if profile is not None: if userid not in playstats: stats = self.data.local.game.get_settings(self.game, userid) if stats is None: stats = ValidatedDict() playstats[userid] = stats - info[userid][version] = self.format_profile(profile, playstats[userid]) - info[userid][version]['remote'] = RemoteUser.is_remote(userid) + info[userid][version] = self.format_profile( + profile, playstats[userid] + ) + info[userid][version]["remote"] = RemoteUser.is_remote(userid) # Exit out if we've hit the limit if userlimit is not None: userlimit = userlimit - 1 @@ -155,7 +171,9 @@ class FrontendBase(ABC): return info - def get_latest_player_info(self, userids: List[UserID]) -> Dict[UserID, Dict[str, Any]]: + def get_latest_player_info( + self, userids: List[UserID] + ) -> Dict[UserID, Dict[str, Any]]: # Grab the latest profile for each user all_info = self.get_all_player_info(userids, 1) info = {} @@ -176,12 +194,15 @@ class FrontendBase(ABC): return self.get_latest_player_info(list(userids)) - def get_network_scores(self, limit: Optional[int]=None) -> Dict[str, Any]: + def get_network_scores(self, limit: Optional[int] = None) -> Dict[str, Any]: userids: List[UserID] = [] # Find all attempts across all games attempts = [ - attempt for attempt in self.data.local.music.get_all_attempts(game=self.game, version=self.version, limit=limit) + attempt + for attempt in self.data.local.music.get_all_attempts( + game=self.game, version=self.version, limit=limit + ) if attempt[0] is not None ] for attempt in attempts: @@ -189,12 +210,16 @@ class FrontendBase(ABC): userids.append(attempt[0]) return { - 'attempts': sorted( + "attempts": sorted( [self.format_attempt(attempt[0], attempt[1]) for attempt in attempts], reverse=True, - key=lambda attempt: (attempt['timestamp'], attempt['songid'], attempt['chart']), + key=lambda attempt: ( + attempt["timestamp"], + attempt["songid"], + attempt["chart"], + ), ), - 'players': self.get_latest_player_info(userids), + "players": self.get_latest_player_info(userids), } def get_network_records(self) -> Dict[str, Any]: @@ -202,7 +227,9 @@ class FrontendBase(ABC): userids: List[UserID] = [] # Find all high-scores across all games - highscores = self.data.local.music.get_all_records(game=self.game, version=self.version) + highscores = self.data.local.music.get_all_records( + game=self.game, version=self.version + ) for score in highscores: index = self.make_index(score[1].id, score[1].chart) if index not in records: @@ -221,30 +248,42 @@ class FrontendBase(ABC): records[index] = newscore return { - 'records': [ - self.format_score(records[index][0], records[index][1]) for index in records + "records": [ + self.format_score(records[index][0], records[index][1]) + for index in records ], - 'players': self.get_latest_player_info(userids), + "players": self.get_latest_player_info(userids), } - def get_scores(self, userid: UserID, limit: Optional[int]=None) -> List[Dict[str, Any]]: + def get_scores( + self, userid: UserID, limit: Optional[int] = None + ) -> List[Dict[str, Any]]: # Find all attempts across all games attempts = [ - attempt for attempt in self.data.local.music.get_all_attempts(game=self.game, version=self.version, userid=userid, limit=limit) + attempt + for attempt in self.data.local.music.get_all_attempts( + game=self.game, version=self.version, userid=userid, limit=limit + ) if attempt[0] is not None ] return sorted( [self.format_attempt(None, attempt[1]) for attempt in attempts], reverse=True, - key=lambda attempt: (attempt['timestamp'], attempt['songid'], attempt['chart']), + key=lambda attempt: ( + attempt["timestamp"], + attempt["songid"], + attempt["chart"], + ), ) def get_records(self, userid: UserID) -> List[Dict[str, Any]]: records: Dict[str, Tuple[UserID, Score]] = {} # Find all high-scores across all games - highscores = self.data.local.music.get_all_scores(game=self.game, version=self.version, userid=userid) + highscores = self.data.local.music.get_all_scores( + game=self.game, version=self.version, userid=userid + ) for score in highscores: index = self.make_index(score[1].id, score[1].chart) if index not in records: @@ -263,7 +302,9 @@ class FrontendBase(ABC): # Copy over records to duplicate IDs, such as revivals indexes = [index for index in records] for index in indexes: - alternate = self.get_duplicate_id(records[index][1].id, records[index][1].chart) + alternate = self.get_duplicate_id( + records[index][1].id, records[index][1].chart + ) if alternate is not None: altid, altchart = alternate newindex = self.make_index(altid, altchart) @@ -276,7 +317,9 @@ class FrontendBase(ABC): return [self.format_score(None, records[index][1]) for index in records] def get_top_scores(self, musicid: int) -> Dict[str, Any]: - scores = self.data.local.music.get_all_scores(game=self.game, version=self.version, songid=musicid) + scores = self.data.local.music.get_all_scores( + game=self.game, version=self.version, songid=musicid + ) userids: List[UserID] = [] for score in scores: if score[1].chart not in self.valid_charts: @@ -298,14 +341,19 @@ class FrontendBase(ABC): score[1].chart = oldchart return { - 'topscores': [ - self.format_top_score(score[0], score[1]) for score in scores + "topscores": [ + self.format_top_score(score[0], score[1]) + for score in scores if score[1].chart in self.valid_charts ], - 'players': self.get_latest_player_info(userids), + "players": self.get_latest_player_info(userids), } - def get_rivals(self, userid: UserID) -> Tuple[Dict[int, List[Dict[str, Any]]], Dict[UserID, Dict[int, Dict[str, Any]]]]: + def get_rivals( + self, userid: UserID + ) -> Tuple[ + Dict[int, List[Dict[str, Any]]], Dict[UserID, Dict[int, Dict[str, Any]]] + ]: rivals = {} userids = set() versions = [version for (game, version, name) in self.all_games()] @@ -317,13 +365,20 @@ class FrontendBase(ABC): continue profiles[version] = profile rivals[version] = [ - link for link in self.data.local.user.get_links(self.game, version, userid) + link + for link in self.data.local.user.get_links(self.game, version, userid) if link.type in self.valid_rival_types ] for rival in rivals[version]: userids.add(rival.other_userid) return ( - {version: [self.format_rival(rival, profiles[version]) for rival in rivals[version]] for version in rivals}, + { + version: [ + self.format_rival(rival, profiles[version]) + for rival in rivals[version] + ] + for version in rivals + }, self.get_all_player_info(list(userids), allow_remote=True), ) diff --git a/bemani/frontend/bishi/bishi.py b/bemani/frontend/bishi/bishi.py index 680d3b8..8fd6003 100644 --- a/bemani/frontend/bishi/bishi.py +++ b/bemani/frontend/bishi/bishi.py @@ -23,9 +23,9 @@ class BishiBashiFrontend(FrontendBase): def __update_value(self, oldvalue: str, newvalue: bytes) -> str: try: - newstr = newvalue.decode('shift-jis') + newstr = newvalue.decode("shift-jis") except Exception: - newstr = '' + newstr = "" if len(newstr) == 0: return oldvalue else: @@ -33,43 +33,45 @@ class BishiBashiFrontend(FrontendBase): def sanitize_name(self, name: str) -> str: if len(name) == 0: - return 'なし' + return "なし" return name def update_name(self, profile: Profile, name: str) -> Profile: newprofile = copy.deepcopy(profile) - for i in range(len(newprofile['strdatas'])): - strdata = newprofile['strdatas'][i] + for i in range(len(newprofile["strdatas"])): + strdata = newprofile["strdatas"][i] # Figure out the profile type - csvs = strdata.split(b',') + csvs = strdata.split(b",") if len(csvs) < 2: # Not long enough to care about continue - datatype = csvs[1].decode('ascii') - if datatype != 'IBBDAT00': + datatype = csvs[1].decode("ascii") + if datatype != "IBBDAT00": # Not the right profile type requested continue - csvs[27] = name.encode('shift-jis') - newprofile['strdatas'][i] = b','.join(csvs) + csvs[27] = name.encode("shift-jis") + newprofile["strdatas"][i] = b",".join(csvs) return newprofile - def format_profile(self, profile: Profile, playstats: ValidatedDict) -> Dict[str, Any]: - name = 'なし' # Nothing - shop = '未設定' # Not set - shop_area = '未設定' # Not set + def format_profile( + self, profile: Profile, playstats: ValidatedDict + ) -> Dict[str, Any]: + name = "なし" # Nothing + shop = "未設定" # Not set + shop_area = "未設定" # Not set - for i in range(len(profile['strdatas'])): - strdata = profile['strdatas'][i] + for i in range(len(profile["strdatas"])): + strdata = profile["strdatas"][i] # Figure out the profile type - csvs = strdata.split(b',') + csvs = strdata.split(b",") if len(csvs) < 2: # Not long enough to care about continue - datatype = csvs[1].decode('ascii') - if datatype != 'IBBDAT00': + datatype = csvs[1].decode("ascii") + if datatype != "IBBDAT00": # Not the right profile type requested continue @@ -78,11 +80,11 @@ class BishiBashiFrontend(FrontendBase): shop_area = self.__update_value(shop_area, csvs[31]) return { - 'name': name, - 'extid': ID.format_extid(profile.extid), - 'shop': shop, - 'shop_area': shop_area, - 'first_play_time': playstats.get_int('first_play_timestamp'), - 'last_play_time': playstats.get_int('last_play_timestamp'), - 'plays': playstats.get_int('total_plays'), + "name": name, + "extid": ID.format_extid(profile.extid), + "shop": shop, + "shop_area": shop_area, + "first_play_time": playstats.get_int("first_play_timestamp"), + "last_play_time": playstats.get_int("last_play_timestamp"), + "plays": playstats.get_int("total_plays"), } diff --git a/bemani/frontend/bishi/cache.py b/bemani/frontend/bishi/cache.py index 0e26110..f83869d 100644 --- a/bemani/frontend/bishi/cache.py +++ b/bemani/frontend/bishi/cache.py @@ -2,7 +2,6 @@ from bemani.data import Config, Data class BishiBashiCache: - @classmethod def preload(cls, data: Data, config: Config) -> None: pass diff --git a/bemani/frontend/bishi/endpoints.py b/bemani/frontend/bishi/endpoints.py index b8ea29a..303ec86 100644 --- a/bemani/frontend/bishi/endpoints.py +++ b/bemani/frontend/bishi/endpoints.py @@ -13,42 +13,40 @@ from bemani.frontend.types import g bishi_pages = Blueprint( - 'bishi_pages', + "bishi_pages", __name__, - url_prefix=f'/{GameConstants.BISHI_BASHI.value}', + url_prefix=f"/{GameConstants.BISHI_BASHI.value}", template_folder=templates_location, static_folder=static_location, ) -@bishi_pages.route('/players') +@bishi_pages.route("/players") @loginrequired def viewplayers() -> Response: frontend = BishiBashiFrontend(g.data, g.config, g.cache) return render_react( - 'All BishiBashi Players', - 'bishi/allplayers.react.js', + "All BishiBashi Players", + "bishi/allplayers.react.js", + {"players": frontend.get_all_players()}, { - 'players': frontend.get_all_players() - }, - { - 'refresh': url_for('bishi_pages.listplayers'), - 'player': url_for('bishi_pages.viewplayer', userid=-1), + "refresh": url_for("bishi_pages.listplayers"), + "player": url_for("bishi_pages.viewplayer", userid=-1), }, ) -@bishi_pages.route('/players/list') +@bishi_pages.route("/players/list") @jsonify @loginrequired def listplayers() -> Dict[str, Any]: frontend = BishiBashiFrontend(g.data, g.config, g.cache) return { - 'players': frontend.get_all_players(), + "players": frontend.get_all_players(), } -@bishi_pages.route('/players/') +@bishi_pages.route("/players/") @loginrequired def viewplayer(userid: UserID) -> Response: frontend = BishiBashiFrontend(g.data, g.config, g.cache) @@ -59,20 +57,22 @@ def viewplayer(userid: UserID) -> Response: return render_react( f'{djinfo[latest_version]["name"]}\'s BishiBashi Profile', - 'bishi/player.react.js', + "bishi/player.react.js", { - 'playerid': userid, - 'own_profile': userid == g.userID, - 'player': djinfo, - 'versions': {version: name for (game, version, name) in frontend.all_games()}, + "playerid": userid, + "own_profile": userid == g.userID, + "player": djinfo, + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, }, { - 'refresh': url_for('bishi_pages.listplayer', userid=userid), + "refresh": url_for("bishi_pages.listplayer", userid=userid), }, ) -@bishi_pages.route('/players//list') +@bishi_pages.route("/players//list") @jsonify @loginrequired def listplayer(userid: UserID) -> Dict[str, Any]: @@ -80,11 +80,11 @@ def listplayer(userid: UserID) -> Dict[str, Any]: djinfo = frontend.get_all_player_info([userid])[userid] return { - 'player': djinfo, + "player": djinfo, } -@bishi_pages.route('/options') +@bishi_pages.route("/options") @loginrequired def viewsettings() -> Response: frontend = BishiBashiFrontend(g.data, g.config, g.cache) @@ -94,35 +94,37 @@ def viewsettings() -> Response: abort(404) return render_react( - 'BishiBashi Game Settings', - 'bishi/settings.react.js', + "BishiBashi Game Settings", + "bishi/settings.react.js", { - 'player': djinfo, - 'versions': {version: name for (game, version, name) in frontend.all_games()}, + "player": djinfo, + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, }, { - 'updatename': url_for('bishi_pages.updatename'), + "updatename": url_for("bishi_pages.updatename"), }, ) -@bishi_pages.route('/options/name/update', methods=['POST']) +@bishi_pages.route("/options/name/update", methods=["POST"]) @jsonify @loginrequired def updatename() -> Dict[str, Any]: frontend = BishiBashiFrontend(g.data, g.config, g.cache) - version = int(request.get_json()['version']) - name = request.get_json()['name'] + version = int(request.get_json()["version"]) + name = request.get_json()["name"] user = g.data.local.user.get_user(g.userID) if user is None: - raise Exception('Unable to find user to update!') + raise Exception("Unable to find user to update!") # Grab profile and update dj name profile = g.data.local.user.get_profile(GameConstants.BISHI_BASHI, version, user.id) if profile is None: - raise Exception('Unable to find profile to update!') + raise Exception("Unable to find profile to update!") if len(name) == 0 or len(name) > 6: - raise Exception('Invalid profile name!') + raise Exception("Invalid profile name!") # Convert lowercase to uppercase. We allow lowercase widetext in # the JS frontend to allow for Windows IME input of hiragana/katakana. @@ -132,44 +134,48 @@ def updatename() -> Dict[str, Any]: return chr(i - (0xFF41 - 0xFF21)) else: return char - name = ''.join([conv(a) for a in name]) - if re.match( - "^[" + - "\uFF20-\uFF3A" + # widetext A-Z, @ - "\uFF10-\uFF19" + # widetext 0-9 - "\u3041-\u308D\u308F\u3092\u3093" + # hiragana - "\u30A1-\u30ED\u30EF\u30F2\u30F3\u30FC" + # katakana - "\u3000" + # widetext blank space - "\u301C" + # widetext ~ - "\u30FB" + # widetext middot - "\u30FC" + # widetext long dash - "\u2212" + # widetext short dash - "\u2605" + # widetext heavy star - "\uFF01" + # widetext ! - "\uFF03" + # widetext # - "\uFF04" + # widetext $ - "\uFF05" + # widetext % - "\uFF06" + # widetext & - "\uFF08" + # widetext ( - "\uFF09" + # widetext ) - "\uFF0A" + # widetext * - "\uFF0B" + # widetext + - "\uFF0F" + # widetext / - "\uFF1C" + # widetext < - "\uFF1D" + # widetext = - "\uFF1E" + # widetext > - "\uFF1F" + # widetext ? - "\uFFE5" + # widetext Yen symbol - "]*$", - name, - ) is None: - raise Exception('Invalid profile name!') + name = "".join([conv(a) for a in name]) + + if ( + re.match( + "^[" + + "\uFF20-\uFF3A" + + "\uFF10-\uFF19" # widetext A-Z, @ + + "\u3041-\u308D\u308F\u3092\u3093" # widetext 0-9 + + "\u30A1-\u30ED\u30EF\u30F2\u30F3\u30FC" # hiragana + + "\u3000" # katakana + + "\u301C" # widetext blank space + + "\u30FB" # widetext ~ + + "\u30FC" # widetext middot + + "\u2212" # widetext long dash + + "\u2605" # widetext short dash + + "\uFF01" # widetext heavy star + + "\uFF03" # widetext ! + + "\uFF04" # widetext # + + "\uFF05" # widetext $ + + "\uFF06" # widetext % + + "\uFF08" # widetext & + + "\uFF09" # widetext ( + + "\uFF0A" # widetext ) + + "\uFF0B" # widetext * + + "\uFF0F" # widetext + + + "\uFF1C" # widetext / + + "\uFF1D" # widetext < + + "\uFF1E" # widetext = + + "\uFF1F" # widetext > + + "\uFFE5" # widetext ? + + "]*$", # widetext Yen symbol + name, + ) + is None + ): + raise Exception("Invalid profile name!") profile = frontend.update_name(profile, name) g.data.local.user.put_profile(GameConstants.BISHI_BASHI, version, user.id, profile) # Return that we updated return { - 'version': version, - 'name': frontend.sanitize_name(name), + "version": version, + "name": frontend.sanitize_name(name), } diff --git a/bemani/frontend/ddr/cache.py b/bemani/frontend/ddr/cache.py index 486cb0c..b339a7e 100644 --- a/bemani/frontend/ddr/cache.py +++ b/bemani/frontend/ddr/cache.py @@ -6,12 +6,14 @@ from bemani.frontend.ddr.ddr import DDRFrontend class DDRCache: - @classmethod def preload(cls, data: Data, config: Config) -> None: - cache = Cache(app, config={ - 'CACHE_TYPE': 'filesystem', - 'CACHE_DIR': config.cache_dir, - }) + cache = Cache( + app, + config={ + "CACHE_TYPE": "filesystem", + "CACHE_DIR": config.cache_dir, + }, + ) frontend = DDRFrontend(data, config, cache) frontend.get_all_songs(force_db_load=True) diff --git a/bemani/frontend/ddr/ddr.py b/bemani/frontend/ddr/ddr.py index cde6576..a8c96b9 100644 --- a/bemani/frontend/ddr/ddr.py +++ b/bemani/frontend/ddr/ddr.py @@ -26,7 +26,7 @@ class DDRFrontend(FrontendBase): DDRBase.CHART_DOUBLE_CHALLENGE, ] - valid_rival_types: List[str] = [f'friend_{i}' for i in range(10)] + valid_rival_types: List[str] = [f"friend_{i}" for i in range(10)] max_active_rivals: Dict[int, int] = { VersionConstants.DDR_X2: 1, @@ -42,89 +42,99 @@ class DDRFrontend(FrontendBase): def update_name(self, profile: Profile, name: str) -> Profile: newprofile = copy.deepcopy(profile) - newprofile.replace_str('name', name) + newprofile.replace_str("name", name) return newprofile def update_weight(self, profile: Profile, weight: int, enabled: bool) -> Profile: newprofile = copy.deepcopy(profile) if newprofile.version in (VersionConstants.DDR_ACE, VersionConstants.DDR_A20): if enabled: - newprofile.replace_int('weight', weight) - newprofile.replace_bool('workout_mode', True) + newprofile.replace_int("weight", weight) + newprofile.replace_bool("workout_mode", True) else: - newprofile.replace_int('weight', 0) - newprofile.replace_bool('workout_mode', False) + newprofile.replace_int("weight", 0) + newprofile.replace_bool("workout_mode", False) else: if enabled: - newprofile.replace_int('weight', weight) + newprofile.replace_int("weight", weight) else: - if 'weight' in newprofile: - del newprofile['weight'] + if "weight" in newprofile: + del newprofile["weight"] return newprofile def update_early_late(self, profile: Profile, display_early_late: bool) -> Profile: newprofile = copy.deepcopy(profile) - newprofile.replace_int('early_late', 1 if display_early_late else 0) + newprofile.replace_int("early_late", 1 if display_early_late else 0) return newprofile - def update_background_combo(self, profile: Profile, background_combo: bool) -> Profile: + def update_background_combo( + self, profile: Profile, background_combo: bool + ) -> Profile: newprofile = copy.deepcopy(profile) - newprofile.replace_int('combo', 1 if background_combo else 0) + newprofile.replace_int("combo", 1 if background_combo else 0) return newprofile - def update_settings(self, profile: Profile, new_settings: Dict[str, Any]) -> Profile: + def update_settings( + self, profile: Profile, new_settings: Dict[str, Any] + ) -> Profile: newprofile = copy.deepcopy(profile) if newprofile.version in (VersionConstants.DDR_ACE, VersionConstants.DDR_A20): - newprofile.replace_int('arrowskin', new_settings['arrowskin']) - newprofile.replace_int('guidelines', new_settings['guidelines']) - newprofile.replace_int('filter', new_settings['filter']) - newprofile.replace_int('character', new_settings['character']) + newprofile.replace_int("arrowskin", new_settings["arrowskin"]) + newprofile.replace_int("guidelines", new_settings["guidelines"]) + newprofile.replace_int("filter", new_settings["filter"]) + newprofile.replace_int("character", new_settings["character"]) else: # No other versions have extra options yet. pass return newprofile - def format_profile(self, profile: Profile, playstats: ValidatedDict) -> Dict[str, Any]: + def format_profile( + self, profile: Profile, playstats: ValidatedDict + ) -> Dict[str, Any]: formatted_profile = super().format_profile(profile, playstats) if profile.version in (VersionConstants.DDR_ACE, VersionConstants.DDR_A20): - formatted_profile.update({ - 'sp': playstats.get_int('single_plays'), - 'dp': playstats.get_int('double_plays'), - 'early_late': profile.get_int('early_late') != 0, - 'background_combo': profile.get_int('combo') != 0, - 'workout_mode': profile.get_bool('workout_mode'), - 'weight': profile.get_int('weight'), - 'settings': { - 'arrowskin': profile.get_int('arrowskin'), - 'guidelines': profile.get_int('guidelines'), - 'filter': profile.get_int('filter'), - 'character': profile.get_int('character'), - }, - }) + formatted_profile.update( + { + "sp": playstats.get_int("single_plays"), + "dp": playstats.get_int("double_plays"), + "early_late": profile.get_int("early_late") != 0, + "background_combo": profile.get_int("combo") != 0, + "workout_mode": profile.get_bool("workout_mode"), + "weight": profile.get_int("weight"), + "settings": { + "arrowskin": profile.get_int("arrowskin"), + "guidelines": profile.get_int("guidelines"), + "filter": profile.get_int("filter"), + "character": profile.get_int("character"), + }, + } + ) else: - formatted_profile.update({ - 'sp': playstats.get_int('single_plays'), - 'dp': playstats.get_int('double_plays'), - 'early_late': profile.get_int('early_late') != 0, - 'background_combo': profile.get_int('combo') != 0, - 'workout_mode': 'weight' in profile, - 'weight': profile.get_int('weight'), - }) + formatted_profile.update( + { + "sp": playstats.get_int("single_plays"), + "dp": playstats.get_int("double_plays"), + "early_late": profile.get_int("early_late") != 0, + "background_combo": profile.get_int("combo") != 0, + "workout_mode": "weight" in profile, + "weight": profile.get_int("weight"), + } + ) return formatted_profile def format_score(self, userid: UserID, score: Score) -> Dict[str, Any]: formatted_score = super().format_score(userid, score) - formatted_score['combo'] = score.data.get_int('combo', -1) - formatted_score['lamp'] = score.data.get_int('halo') - formatted_score['halo'] = { + formatted_score["combo"] = score.data.get_int("combo", -1) + formatted_score["lamp"] = score.data.get_int("halo") + formatted_score["halo"] = { DDRBase.HALO_NONE: None, DDRBase.HALO_GOOD_FULL_COMBO: "GOOD FULL COMBO", DDRBase.HALO_GREAT_FULL_COMBO: "GREAT FULL COMBO", DDRBase.HALO_PERFECT_FULL_COMBO: "PERFECT FULL COMBO", DDRBase.HALO_MARVELOUS_FULL_COMBO: "MARVELOUS FULL COMBO", - }.get(score.data.get_int('halo')) - formatted_score['status'] = score.data.get_int('rank') - formatted_score['rank'] = { + }.get(score.data.get_int("halo")) + formatted_score["status"] = score.data.get_int("rank") + formatted_score["rank"] = { DDRBase.RANK_AAA: "AAA", DDRBase.RANK_AA_PLUS: "AA+", DDRBase.RANK_AA: "AA", @@ -141,20 +151,20 @@ class DDRFrontend(FrontendBase): DDRBase.RANK_D_PLUS: "D+", DDRBase.RANK_D: "D", DDRBase.RANK_E: "E", - }.get(score.data.get_int('rank'), 'NO PLAY') + }.get(score.data.get_int("rank"), "NO PLAY") return formatted_score def format_attempt(self, userid: UserID, attempt: Attempt) -> Dict[str, Any]: formatted_attempt = super().format_attempt(userid, attempt) - formatted_attempt['combo'] = attempt.data.get_int('combo', -1) - formatted_attempt['halo'] = { + formatted_attempt["combo"] = attempt.data.get_int("combo", -1) + formatted_attempt["halo"] = { DDRBase.HALO_NONE: None, DDRBase.HALO_GOOD_FULL_COMBO: "GOOD FULL COMBO", DDRBase.HALO_GREAT_FULL_COMBO: "GREAT FULL COMBO", DDRBase.HALO_PERFECT_FULL_COMBO: "PERFECT FULL COMBO", DDRBase.HALO_MARVELOUS_FULL_COMBO: "MARVELOUS FULL COMBO", - }.get(attempt.data.get_int('halo')) - formatted_attempt['rank'] = { + }.get(attempt.data.get_int("halo")) + formatted_attempt["rank"] = { DDRBase.RANK_AAA: "AAA", DDRBase.RANK_AA_PLUS: "AA+", DDRBase.RANK_AA: "AA", @@ -171,96 +181,109 @@ class DDRFrontend(FrontendBase): DDRBase.RANK_D_PLUS: "D+", DDRBase.RANK_D: "D", DDRBase.RANK_E: "E", - }.get(attempt.data.get_int('rank'), 'NO PLAY') + }.get(attempt.data.get_int("rank"), "NO PLAY") return formatted_attempt def format_song(self, song: Song) -> Dict[str, Any]: difficulties = [0] * 10 - difficulties[song.chart] = song.data.get_int('difficulty', 20) + difficulties[song.chart] = song.data.get_int("difficulty", 20) formatted_song = super().format_song(song) - formatted_song['bpm_min'] = song.data.get_int('bpm_min', 120) - formatted_song['bpm_max'] = song.data.get_int('bpm_max', 120) - formatted_song['category'] = song.data.get_int('category', 0) - formatted_song['groove'] = song.data.get_dict('groove', { - 'voltage': 0, - 'stream': 0, - 'air': 0, - 'chaos': 0, - 'freeze': 0, - }) - formatted_song['difficulties'] = difficulties + formatted_song["bpm_min"] = song.data.get_int("bpm_min", 120) + formatted_song["bpm_max"] = song.data.get_int("bpm_max", 120) + formatted_song["category"] = song.data.get_int("category", 0) + formatted_song["groove"] = song.data.get_dict( + "groove", + { + "voltage": 0, + "stream": 0, + "air": 0, + "chaos": 0, + "freeze": 0, + }, + ) + formatted_song["difficulties"] = difficulties return formatted_song def merge_song(self, existing: Dict[str, Any], new: Song) -> Dict[str, Any]: new_song = super().merge_song(existing, new) - if ( - existing['difficulties'][new.chart] == 0 - ): - new_song['difficulties'][new.chart] = new.data.get_int('difficulty', 20) + if existing["difficulties"][new.chart] == 0: + new_song["difficulties"][new.chart] = new.data.get_int("difficulty", 20) return new_song def activate_rival(self, profile: Profile, position: int) -> Profile: newprofile = copy.deepcopy(profile) if newprofile.version == VersionConstants.DDR_X2: # X2 only has one active rival - lastdict = newprofile.get_dict('last') - lastdict.replace_int('fri', position + 1) - newprofile.replace_dict('last', lastdict) - elif newprofile.version in [VersionConstants.DDR_X3_VS_2NDMIX, VersionConstants.DDR_2013, VersionConstants.DDR_2014, VersionConstants.DDR_ACE, VersionConstants.DDR_A20]: + lastdict = newprofile.get_dict("last") + lastdict.replace_int("fri", position + 1) + newprofile.replace_dict("last", lastdict) + elif newprofile.version in [ + VersionConstants.DDR_X3_VS_2NDMIX, + VersionConstants.DDR_2013, + VersionConstants.DDR_2014, + VersionConstants.DDR_ACE, + VersionConstants.DDR_A20, + ]: # X3 has 3 active rivals, put this in the first open slot - lastdict = newprofile.get_dict('last') - if lastdict.get_int('rival1') < 1: - lastdict.replace_int('rival1', position + 1) - elif lastdict.get_int('rival2') < 1: - lastdict.replace_int('rival2', position + 1) - elif lastdict.get_int('rival3') < 1: - lastdict.replace_int('rival3', position + 1) - newprofile.replace_dict('last', lastdict) + lastdict = newprofile.get_dict("last") + if lastdict.get_int("rival1") < 1: + lastdict.replace_int("rival1", position + 1) + elif lastdict.get_int("rival2") < 1: + lastdict.replace_int("rival2", position + 1) + elif lastdict.get_int("rival3") < 1: + lastdict.replace_int("rival3", position + 1) + newprofile.replace_dict("last", lastdict) return newprofile def deactivate_rival(self, profile: Profile, position: int) -> Profile: newprofile = copy.deepcopy(profile) if newprofile.version == VersionConstants.DDR_X2: # X2 only has one active rival - lastdict = newprofile.get_dict('last') - if lastdict.get_int('fri') == position + 1: - lastdict.replace_int('fri', 0) - newprofile.replace_dict('last', lastdict) - elif newprofile.version in [VersionConstants.DDR_X3_VS_2NDMIX, VersionConstants.DDR_2013, VersionConstants.DDR_2014, VersionConstants.DDR_ACE, VersionConstants.DDR_A20]: + lastdict = newprofile.get_dict("last") + if lastdict.get_int("fri") == position + 1: + lastdict.replace_int("fri", 0) + newprofile.replace_dict("last", lastdict) + elif newprofile.version in [ + VersionConstants.DDR_X3_VS_2NDMIX, + VersionConstants.DDR_2013, + VersionConstants.DDR_2014, + VersionConstants.DDR_ACE, + VersionConstants.DDR_A20, + ]: # X3 has 3 active rivals, put this in the first open slot - lastdict = newprofile.get_dict('last') - if lastdict.get_int('rival1') == position + 1: - lastdict.replace_int('rival1', 0) - elif lastdict.get_int('rival2') == position + 1: - lastdict.replace_int('rival2', 0) - elif lastdict.get_int('rival3') == position + 1: - lastdict.replace_int('rival3', 0) - newprofile.replace_dict('last', lastdict) + lastdict = newprofile.get_dict("last") + if lastdict.get_int("rival1") == position + 1: + lastdict.replace_int("rival1", 0) + elif lastdict.get_int("rival2") == position + 1: + lastdict.replace_int("rival2", 0) + elif lastdict.get_int("rival3") == position + 1: + lastdict.replace_int("rival3", 0) + newprofile.replace_dict("last", lastdict) return newprofile def format_rival(self, link: Link, profile: Profile) -> Dict[str, Any]: pos = int(link.type[7:]) if profile.version == VersionConstants.DDR_X2: - active = pos == (profile.get_dict('last').get_int('fri') - 1) + active = pos == (profile.get_dict("last").get_int("fri") - 1) elif profile.version in { VersionConstants.DDR_X3_VS_2NDMIX, VersionConstants.DDR_2013, VersionConstants.DDR_2014, VersionConstants.DDR_ACE, - VersionConstants.DDR_A20 + VersionConstants.DDR_A20, }: actives = [ - profile.get_dict('last').get_int('rival1') - 1, - profile.get_dict('last').get_int('rival2') - 1, - profile.get_dict('last').get_int('rival3') - 1, + profile.get_dict("last").get_int("rival1") - 1, + profile.get_dict("last").get_int("rival2") - 1, + profile.get_dict("last").get_int("rival3") - 1, ] active = pos in actives else: active = False return { - 'position': pos, - 'active': active, - 'userid': str(link.other_userid), - 'remote': RemoteUser.is_remote(link.other_userid), + "position": pos, + "active": active, + "userid": str(link.other_userid), + "remote": RemoteUser.is_remote(link.other_userid), } diff --git a/bemani/frontend/ddr/endpoints.py b/bemani/frontend/ddr/endpoints.py index f32ac83..6eda798 100644 --- a/bemani/frontend/ddr/endpoints.py +++ b/bemani/frontend/ddr/endpoints.py @@ -13,43 +13,45 @@ from bemani.frontend.types import g ddr_pages = Blueprint( - 'ddr_pages', + "ddr_pages", __name__, - url_prefix=f'/{GameConstants.DDR.value}', + url_prefix=f"/{GameConstants.DDR.value}", template_folder=templates_location, static_folder=static_location, ) -@ddr_pages.route('/scores') +@ddr_pages.route("/scores") @loginrequired def viewnetworkscores() -> Response: # Only load the last 100 results for the initial fetch, so we can render faster frontend = DDRFrontend(g.data, g.config, g.cache) network_scores = frontend.get_network_scores(limit=100) - if len(network_scores['attempts']) > 10: - network_scores['attempts'] = frontend.round_to_ten(network_scores['attempts']) + if len(network_scores["attempts"]) > 10: + network_scores["attempts"] = frontend.round_to_ten(network_scores["attempts"]) return render_react( - 'Global DDR Scores', - 'ddr/scores.react.js', + "Global DDR Scores", + "ddr/scores.react.js", { - 'attempts': network_scores['attempts'], - 'songs': frontend.get_all_songs(), - 'players': network_scores['players'], - 'versions': {version: name for (game, version, name) in frontend.all_games()}, - 'shownames': True, - 'shownewrecords': False, + "attempts": network_scores["attempts"], + "songs": frontend.get_all_songs(), + "players": network_scores["players"], + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, + "shownames": True, + "shownewrecords": False, }, { - 'refresh': url_for('ddr_pages.listnetworkscores'), - 'player': url_for('ddr_pages.viewplayer', userid=-1), - 'individual_score': url_for('ddr_pages.viewtopscores', musicid=-1), + "refresh": url_for("ddr_pages.listnetworkscores"), + "player": url_for("ddr_pages.viewplayer", userid=-1), + "individual_score": url_for("ddr_pages.viewtopscores", musicid=-1), }, ) -@ddr_pages.route('/scores/list') +@ddr_pages.route("/scores/list") @jsonify @loginrequired def listnetworkscores() -> Dict[str, Any]: @@ -57,7 +59,7 @@ def listnetworkscores() -> Dict[str, Any]: return frontend.get_network_scores() -@ddr_pages.route('/scores/') +@ddr_pages.route("/scores/") @loginrequired def viewscores(userid: UserID) -> Response: frontend = DDRFrontend(g.data, g.config, g.cache) @@ -71,61 +73,65 @@ def viewscores(userid: UserID) -> Response: return render_react( f'{info["name"]}\'s DDR Scores', - 'ddr/scores.react.js', + "ddr/scores.react.js", { - 'attempts': scores, - 'songs': frontend.get_all_songs(), - 'players': {}, - 'versions': {version: name for (game, version, name) in frontend.all_games()}, - 'shownames': False, - 'shownewrecords': True, + "attempts": scores, + "songs": frontend.get_all_songs(), + "players": {}, + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, + "shownames": False, + "shownewrecords": True, }, { - 'refresh': url_for('ddr_pages.listscores', userid=userid), - 'player': url_for('ddr_pages.viewplayer', userid=-1), - 'individual_score': url_for('ddr_pages.viewtopscores', musicid=-1), + "refresh": url_for("ddr_pages.listscores", userid=userid), + "player": url_for("ddr_pages.viewplayer", userid=-1), + "individual_score": url_for("ddr_pages.viewtopscores", musicid=-1), }, ) -@ddr_pages.route('/scores//list') +@ddr_pages.route("/scores//list") @jsonify @loginrequired def listscores(userid: UserID) -> Dict[str, Any]: frontend = DDRFrontend(g.data, g.config, g.cache) return { - 'attempts': frontend.get_scores(userid), - 'players': {}, + "attempts": frontend.get_scores(userid), + "players": {}, } -@ddr_pages.route('/records') +@ddr_pages.route("/records") @loginrequired def viewnetworkrecords() -> Response: frontend = DDRFrontend(g.data, g.config, g.cache) network_records = frontend.get_network_records() return render_react( - 'Global DDR Records', - 'ddr/records.react.js', + "Global DDR Records", + "ddr/records.react.js", { - 'records': network_records['records'], - 'songs': frontend.get_all_songs(), - 'players': network_records['players'], - 'versions': {version: name for (game, version, name) in frontend.all_games()}, - 'shownames': True, - 'showpersonalsort': False, - 'filterempty': False, + "records": network_records["records"], + "songs": frontend.get_all_songs(), + "players": network_records["players"], + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, + "shownames": True, + "showpersonalsort": False, + "filterempty": False, }, { - 'refresh': url_for('ddr_pages.listnetworkrecords'), - 'player': url_for('ddr_pages.viewplayer', userid=-1), - 'individual_score': url_for('ddr_pages.viewtopscores', musicid=-1), + "refresh": url_for("ddr_pages.listnetworkrecords"), + "player": url_for("ddr_pages.viewplayer", userid=-1), + "individual_score": url_for("ddr_pages.viewtopscores", musicid=-1), }, ) -@ddr_pages.route('/records/list') +@ddr_pages.route("/records/list") @jsonify @loginrequired def listnetworkrecords() -> Dict[str, Any]: @@ -133,7 +139,7 @@ def listnetworkrecords() -> Dict[str, Any]: return frontend.get_network_records() -@ddr_pages.route('/records/') +@ddr_pages.route("/records/") @loginrequired def viewrecords(userid: UserID) -> Response: frontend = DDRFrontend(g.data, g.config, g.cache) @@ -143,36 +149,38 @@ def viewrecords(userid: UserID) -> Response: return render_react( f'{info["name"]}\'s DDR Records', - 'ddr/records.react.js', + "ddr/records.react.js", { - 'records': frontend.get_records(userid), - 'songs': frontend.get_all_songs(), - 'players': {}, - 'versions': {version: name for (game, version, name) in frontend.all_games()}, - 'shownames': False, - 'showpersonalsort': True, - 'filterempty': True, + "records": frontend.get_records(userid), + "songs": frontend.get_all_songs(), + "players": {}, + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, + "shownames": False, + "showpersonalsort": True, + "filterempty": True, }, { - 'refresh': url_for('ddr_pages.listrecords', userid=userid), - 'player': url_for('ddr_pages.viewplayer', userid=-1), - 'individual_score': url_for('ddr_pages.viewtopscores', musicid=-1), + "refresh": url_for("ddr_pages.listrecords", userid=userid), + "player": url_for("ddr_pages.viewplayer", userid=-1), + "individual_score": url_for("ddr_pages.viewtopscores", musicid=-1), }, ) -@ddr_pages.route('/records//list') +@ddr_pages.route("/records//list") @jsonify @loginrequired def listrecords(userid: UserID) -> Dict[str, Any]: frontend = DDRFrontend(g.data, g.config, g.cache) return { - 'records': frontend.get_records(userid), - 'players': {}, + "records": frontend.get_records(userid), + "players": {}, } -@ddr_pages.route('/topscores/') +@ddr_pages.route("/topscores/") @loginrequired def viewtopscores(musicid: int) -> Response: # We just want to find the latest mix that this song exists in @@ -189,8 +197,8 @@ def viewtopscores(musicid: int) -> Response: name = details.name artist = details.artist genre = details.genre - difficulties[chart] = details.data.get_int('difficulty', 13) - groove[chart] = details.data.get_dict('groove') + difficulties[chart] = details.data.get_int("difficulty", 13) + groove[chart] = details.data.get_dict("groove") if name is None: # Not a real song! @@ -199,25 +207,25 @@ def viewtopscores(musicid: int) -> Response: top_scores = frontend.get_top_scores(musicid) return render_react( - f'Top DDR Scores for {artist} - {name}', - 'ddr/topscores.react.js', + f"Top DDR Scores for {artist} - {name}", + "ddr/topscores.react.js", { - 'name': name, - 'artist': artist, - 'genre': genre, - 'difficulties': difficulties, - 'groove': groove, - 'players': top_scores['players'], - 'topscores': top_scores['topscores'], + "name": name, + "artist": artist, + "genre": genre, + "difficulties": difficulties, + "groove": groove, + "players": top_scores["players"], + "topscores": top_scores["topscores"], }, { - 'refresh': url_for('ddr_pages.listtopscores', musicid=musicid), - 'player': url_for('ddr_pages.viewplayer', userid=-1), + "refresh": url_for("ddr_pages.listtopscores", musicid=musicid), + "player": url_for("ddr_pages.viewplayer", userid=-1), }, ) -@ddr_pages.route('/topscores//list') +@ddr_pages.route("/topscores//list") @jsonify @loginrequired def listtopscores(musicid: int) -> Dict[str, Any]: @@ -225,34 +233,32 @@ def listtopscores(musicid: int) -> Dict[str, Any]: return frontend.get_top_scores(musicid) -@ddr_pages.route('/players') +@ddr_pages.route("/players") @loginrequired def viewplayers() -> Response: frontend = DDRFrontend(g.data, g.config, g.cache) return render_react( - 'All DDR Players', - 'ddr/allplayers.react.js', + "All DDR Players", + "ddr/allplayers.react.js", + {"players": frontend.get_all_players()}, { - 'players': frontend.get_all_players() - }, - { - 'refresh': url_for('ddr_pages.listplayers'), - 'player': url_for('ddr_pages.viewplayer', userid=-1), + "refresh": url_for("ddr_pages.listplayers"), + "player": url_for("ddr_pages.viewplayer", userid=-1), }, ) -@ddr_pages.route('/players/list') +@ddr_pages.route("/players/list") @jsonify @loginrequired def listplayers() -> Dict[str, Any]: frontend = DDRFrontend(g.data, g.config, g.cache) return { - 'players': frontend.get_all_players(), + "players": frontend.get_all_players(), } -@ddr_pages.route('/players/') +@ddr_pages.route("/players/") @loginrequired def viewplayer(userid: UserID) -> Response: frontend = DDRFrontend(g.data, g.config, g.cache) @@ -263,22 +269,24 @@ def viewplayer(userid: UserID) -> Response: return render_react( f'{info[latest_version]["name"]}\'s DDR Profile', - 'ddr/player.react.js', + "ddr/player.react.js", { - 'playerid': userid, - 'own_profile': userid == g.userID, - 'player': info, - 'versions': {version: name for (game, version, name) in frontend.all_games()}, + "playerid": userid, + "own_profile": userid == g.userID, + "player": info, + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, }, { - 'refresh': url_for('ddr_pages.listplayer', userid=userid), - 'records': url_for('ddr_pages.viewrecords', userid=userid), - 'scores': url_for('ddr_pages.viewscores', userid=userid), + "refresh": url_for("ddr_pages.listplayer", userid=userid), + "records": url_for("ddr_pages.viewrecords", userid=userid), + "scores": url_for("ddr_pages.viewscores", userid=userid), }, ) -@ddr_pages.route('/players//list') +@ddr_pages.route("/players//list") @jsonify @loginrequired def listplayer(userid: UserID) -> Dict[str, Any]: @@ -286,11 +294,11 @@ def listplayer(userid: UserID) -> Dict[str, Any]: info = frontend.get_all_player_info([userid])[userid] return { - 'player': info, + "player": info, } -@ddr_pages.route('/options') +@ddr_pages.route("/options") @loginrequired def viewsettings() -> Response: frontend = DDRFrontend(g.data, g.config, g.cache) @@ -300,186 +308,190 @@ def viewsettings() -> Response: abort(404) return render_react( - 'DDR Game Settings', - 'ddr/settings.react.js', + "DDR Game Settings", + "ddr/settings.react.js", { - 'player': info, - 'versions': {version: name for (game, version, name) in frontend.all_games()}, + "player": info, + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, }, { - 'updatename': url_for('ddr_pages.updatename'), - 'updateweight': url_for('ddr_pages.updateweight'), - 'updateearlylate': url_for('ddr_pages.updateearlylate'), - 'updatebackgroundcombo': url_for('ddr_pages.updatebackgroundcombo'), - 'updatesettings': url_for('ddr_pages.updatesettings'), + "updatename": url_for("ddr_pages.updatename"), + "updateweight": url_for("ddr_pages.updateweight"), + "updateearlylate": url_for("ddr_pages.updateearlylate"), + "updatebackgroundcombo": url_for("ddr_pages.updatebackgroundcombo"), + "updatesettings": url_for("ddr_pages.updatesettings"), }, ) -@ddr_pages.route('/options/name/update', methods=['POST']) +@ddr_pages.route("/options/name/update", methods=["POST"]) @jsonify @loginrequired def updatename() -> Dict[str, Any]: frontend = DDRFrontend(g.data, g.config, g.cache) - version = int(request.get_json()['version']) - name = request.get_json()['name'] + version = int(request.get_json()["version"]) + name = request.get_json()["name"] user = g.data.local.user.get_user(g.userID) if user is None: - raise Exception('Unable to find user to update!') + raise Exception("Unable to find user to update!") # Grab profile and update name profile = g.data.local.user.get_profile(GameConstants.DDR, version, user.id) if profile is None: - raise Exception('Unable to find profile to update!') + raise Exception("Unable to find profile to update!") if len(name) == 0 or len(name) > 8: - raise Exception('Invalid profile name!') - if re.match(r'^[-&$\\.\\?!A-Z0-9 ]*$', name) is None: - raise Exception('Invalid profile name!') + raise Exception("Invalid profile name!") + if re.match(r"^[-&$\\.\\?!A-Z0-9 ]*$", name) is None: + raise Exception("Invalid profile name!") profile = frontend.update_name(profile, name) g.data.local.user.put_profile(GameConstants.DDR, version, user.id, profile) # Return that we updated return { - 'version': version, - 'name': name, + "version": version, + "name": name, } -@ddr_pages.route('/options/weight/update', methods=['POST']) +@ddr_pages.route("/options/weight/update", methods=["POST"]) @jsonify @loginrequired def updateweight() -> Dict[str, Any]: frontend = DDRFrontend(g.data, g.config, g.cache) - version = int(request.get_json()['version']) - weight = int(float(request.get_json()['weight']) * 10) - enabled = request.get_json()['enabled'] + version = int(request.get_json()["version"]) + weight = int(float(request.get_json()["weight"]) * 10) + enabled = request.get_json()["enabled"] user = g.data.local.user.get_user(g.userID) if user is None: - raise Exception('Unable to find user to update!') + raise Exception("Unable to find user to update!") # Grab profile and update weight profile = g.data.local.user.get_profile(GameConstants.DDR, version, user.id) if profile is None: - raise Exception('Unable to find profile to update!') + raise Exception("Unable to find profile to update!") if enabled: if weight <= 0 or weight > 9999: - raise Exception('Invalid weight!') + raise Exception("Invalid weight!") profile = frontend.update_weight(profile, weight, enabled) g.data.local.user.put_profile(GameConstants.DDR, version, user.id, profile) # Return that we updated return { - 'version': version, - 'weight': weight, - 'enabled': enabled, + "version": version, + "weight": weight, + "enabled": enabled, } -@ddr_pages.route('/options/earlylate/update', methods=['POST']) +@ddr_pages.route("/options/earlylate/update", methods=["POST"]) @jsonify @loginrequired def updateearlylate() -> Dict[str, Any]: frontend = DDRFrontend(g.data, g.config, g.cache) - version = int(request.get_json()['version']) - value = request.get_json()['value'] + version = int(request.get_json()["version"]) + value = request.get_json()["value"] user = g.data.local.user.get_user(g.userID) if user is None: - raise Exception('Unable to find user to update!') + raise Exception("Unable to find user to update!") # Grab profile and update early/late indicator profile = g.data.local.user.get_profile(GameConstants.DDR, version, user.id) if profile is None: - raise Exception('Unable to find profile to update!') + raise Exception("Unable to find profile to update!") profile = frontend.update_early_late(profile, value) g.data.local.user.put_profile(GameConstants.DDR, version, user.id, profile) # Return that we updated return { - 'version': version, - 'value': value != 0, + "version": version, + "value": value != 0, } -@ddr_pages.route('/options/backgroundcombo/update', methods=['POST']) +@ddr_pages.route("/options/backgroundcombo/update", methods=["POST"]) @jsonify @loginrequired def updatebackgroundcombo() -> Dict[str, Any]: frontend = DDRFrontend(g.data, g.config, g.cache) - version = int(request.get_json()['version']) - value = request.get_json()['value'] + version = int(request.get_json()["version"]) + value = request.get_json()["value"] user = g.data.local.user.get_user(g.userID) if user is None: - raise Exception('Unable to find user to update!') + raise Exception("Unable to find user to update!") # Grab profile and update combo position profile = g.data.local.user.get_profile(GameConstants.DDR, version, user.id) if profile is None: - raise Exception('Unable to find profile to update!') + raise Exception("Unable to find profile to update!") profile = frontend.update_background_combo(profile, value) g.data.local.user.put_profile(GameConstants.DDR, version, user.id, profile) # Return that we updated return { - 'version': version, - 'value': value != 0, + "version": version, + "value": value != 0, } -@ddr_pages.route('/options/settings/update', methods=['POST']) +@ddr_pages.route("/options/settings/update", methods=["POST"]) @jsonify @loginrequired def updatesettings() -> Dict[str, Any]: frontend = DDRFrontend(g.data, g.config, g.cache) - settings = request.get_json()['settings'] - version = int(request.get_json()['version']) + settings = request.get_json()["settings"] + version = int(request.get_json()["version"]) user = g.data.local.user.get_user(g.userID) if user is None: - raise Exception('Unable to find user to update!') + raise Exception("Unable to find user to update!") # Grab profile and settings dict that needs updating profile = g.data.local.user.get_profile(GameConstants.DDR, version, user.id) if profile is None: - raise Exception('Unable to find profile to update!') + raise Exception("Unable to find profile to update!") profile = frontend.update_settings(profile, settings) g.data.local.user.put_profile(GameConstants.DDR, version, user.id, profile) # Return updated settings info = frontend.get_all_player_info([user.id])[user.id] return { - 'player': info, - 'version': version, + "player": info, + "version": version, } -@ddr_pages.route('/rivals') +@ddr_pages.route("/rivals") @loginrequired def viewrivals() -> Response: frontend = DDRFrontend(g.data, g.config, g.cache) rivals, info = frontend.get_rivals(g.userID) return render_react( - 'DDR Rivals', - 'ddr/rivals.react.js', + "DDR Rivals", + "ddr/rivals.react.js", { - 'userid': str(g.userID), - 'rivals': rivals, - 'max_active_rivals': frontend.max_active_rivals, - 'players': info, - 'versions': {version: name for (game, version, name) in frontend.all_games()}, + "userid": str(g.userID), + "rivals": rivals, + "max_active_rivals": frontend.max_active_rivals, + "players": info, + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, }, { - 'refresh': url_for('ddr_pages.listrivals'), - 'search': url_for('ddr_pages.searchrivals'), - 'player': url_for('ddr_pages.viewplayer', userid=-1), - 'addrival': url_for('ddr_pages.addrival'), - 'removerival': url_for('ddr_pages.removerival'), - 'setactiverival': url_for('ddr_pages.setactiverival'), - 'setinactiverival': url_for('ddr_pages.setinactiverival'), + "refresh": url_for("ddr_pages.listrivals"), + "search": url_for("ddr_pages.searchrivals"), + "player": url_for("ddr_pages.viewplayer", userid=-1), + "addrival": url_for("ddr_pages.addrival"), + "removerival": url_for("ddr_pages.removerival"), + "setactiverival": url_for("ddr_pages.setactiverival"), + "setinactiverival": url_for("ddr_pages.setinactiverival"), }, ) -@ddr_pages.route('/rivals/list') +@ddr_pages.route("/rivals/list") @jsonify @loginrequired def listrivals() -> Dict[str, Any]: @@ -487,18 +499,18 @@ def listrivals() -> Dict[str, Any]: rivals, info = frontend.get_rivals(g.userID) return { - 'rivals': rivals, - 'players': info, + "rivals": rivals, + "players": info, } -@ddr_pages.route('/rivals/search', methods=['POST']) +@ddr_pages.route("/rivals/search", methods=["POST"]) @jsonify @loginrequired def searchrivals() -> Dict[str, Any]: frontend = DDRFrontend(g.data, g.config, g.cache) - version = int(request.get_json()['version']) - name = request.get_json()['term'] + version = int(request.get_json()["version"]) + name = request.get_json()["term"] # Try to treat the term as an extid extid = ID.parse_extid(name) @@ -506,28 +518,28 @@ def searchrivals() -> Dict[str, Any]: matches = set() profiles = g.data.remote.user.get_all_profiles(GameConstants.DDR, version) for (userid, profile) in profiles: - if profile.extid == extid or profile.get_str('name').lower() == name.lower(): + if profile.extid == extid or profile.get_str("name").lower() == name.lower(): matches.add(userid) info = frontend.get_all_player_info(list(matches), allow_remote=True) return { - 'results': info, + "results": info, } -@ddr_pages.route('/rivals/add', methods=['POST']) +@ddr_pages.route("/rivals/add", methods=["POST"]) @jsonify @loginrequired def addrival() -> Dict[str, Any]: frontend = DDRFrontend(g.data, g.config, g.cache) - version = int(request.get_json()['version']) - other_userid = UserID(int(request.get_json()['userid'])) + version = int(request.get_json()["version"]) + other_userid = UserID(int(request.get_json()["userid"])) userid = g.userID # Find a slot to put the rival into occupied: List[Optional[Link]] = [None] * 10 for link in g.data.local.user.get_links(GameConstants.DDR, version, userid): - if link.type[:7] != 'friend_': + if link.type[:7] != "friend_": continue pos = int(link.type[7:]) @@ -542,18 +554,18 @@ def addrival() -> Dict[str, Any]: break if newrivalpos == -1: - raise Exception('No room for another rival!') + raise Exception("No room for another rival!") # Add this rival link profile = g.data.remote.user.get_profile(GameConstants.DDR, version, other_userid) if profile is None: - raise Exception('Unable to find profile for rival!') + raise Exception("Unable to find profile for rival!") g.data.local.user.put_link( GameConstants.DDR, version, userid, - f'friend_{newrivalpos}', + f"friend_{newrivalpos}", other_userid, {}, ) @@ -562,19 +574,19 @@ def addrival() -> Dict[str, Any]: rivals, info = frontend.get_rivals(userid) return { - 'rivals': rivals, - 'players': info, + "rivals": rivals, + "players": info, } -@ddr_pages.route('/rivals/remove', methods=['POST']) +@ddr_pages.route("/rivals/remove", methods=["POST"]) @jsonify @loginrequired def removerival() -> Dict[str, Any]: frontend = DDRFrontend(g.data, g.config, g.cache) - version = int(request.get_json()['version']) - other_userid = UserID(int(request.get_json()['userid'])) - position = int(request.get_json()['position']) + version = int(request.get_json()["version"]) + other_userid = UserID(int(request.get_json()["userid"])) + position = int(request.get_json()["position"]) userid = g.userID # Remove this rival link @@ -582,13 +594,13 @@ def removerival() -> Dict[str, Any]: GameConstants.DDR, version, userid, - f'friend_{position}', + f"friend_{position}", other_userid, ) profile = g.data.local.user.get_profile(GameConstants.DDR, version, userid) if profile is None: - raise Exception('Unable to find profile to update!') + raise Exception("Unable to find profile to update!") profile = frontend.deactivate_rival(profile, position) g.data.local.user.put_profile(GameConstants.DDR, version, userid, profile) @@ -596,23 +608,23 @@ def removerival() -> Dict[str, Any]: rivals, info = frontend.get_rivals(userid) return { - 'rivals': rivals, - 'players': info, + "rivals": rivals, + "players": info, } -@ddr_pages.route('/rivals/activate', methods=['POST']) +@ddr_pages.route("/rivals/activate", methods=["POST"]) @jsonify @loginrequired def setactiverival() -> Dict[str, Any]: frontend = DDRFrontend(g.data, g.config, g.cache) - version = int(request.get_json()['version']) - position = int(request.get_json()['position']) + version = int(request.get_json()["version"]) + position = int(request.get_json()["position"]) userid = g.userID profile = g.data.local.user.get_profile(GameConstants.DDR, version, userid) if profile is None: - raise Exception('Unable to find profile to update!') + raise Exception("Unable to find profile to update!") profile = frontend.activate_rival(profile, position) g.data.local.user.put_profile(GameConstants.DDR, version, userid, profile) @@ -620,23 +632,23 @@ def setactiverival() -> Dict[str, Any]: rivals, info = frontend.get_rivals(userid) return { - 'rivals': rivals, - 'players': info, + "rivals": rivals, + "players": info, } -@ddr_pages.route('/rivals/inactivate', methods=['POST']) +@ddr_pages.route("/rivals/inactivate", methods=["POST"]) @jsonify @loginrequired def setinactiverival() -> Dict[str, Any]: frontend = DDRFrontend(g.data, g.config, g.cache) - version = int(request.get_json()['version']) - position = int(request.get_json()['position']) + version = int(request.get_json()["version"]) + position = int(request.get_json()["position"]) userid = g.userID profile = g.data.local.user.get_profile(GameConstants.DDR, version, userid) if profile is None: - raise Exception('Unable to find profile to update!') + raise Exception("Unable to find profile to update!") profile = frontend.deactivate_rival(profile, position) g.data.local.user.put_profile(GameConstants.DDR, version, userid, profile) @@ -644,6 +656,6 @@ def setinactiverival() -> Dict[str, Any]: rivals, info = frontend.get_rivals(userid) return { - 'rivals': rivals, - 'players': info, + "rivals": rivals, + "players": info, } diff --git a/bemani/frontend/gamesettings.py b/bemani/frontend/gamesettings.py index a15475c..3ded481 100644 --- a/bemani/frontend/gamesettings.py +++ b/bemani/frontend/gamesettings.py @@ -23,31 +23,35 @@ def get_game_settings(data: Data, arcadeid: ArcadeID) -> List[Dict[str, Any]]: # First, set up the basics game_settings: Dict[str, Any] = { - 'game': game.value, - 'version': version, - 'name': game_lut[game][version], - 'bools': [], - 'ints': [], - 'strs': [], - 'longstrs': [], + "game": game.value, + "version": version, + "name": game_lut[game][version], + "bools": [], + "ints": [], + "strs": [], + "longstrs": [], } # Now, look up the current setting for each returned setting for setting_type, setting_unpacker in [ - ('bools', "get_bool"), - ('ints', "get_int"), - ('strs', "get_str"), - ('longstrs', "get_str"), + ("bools", "get_bool"), + ("ints", "get_int"), + ("strs", "get_str"), + ("longstrs", "get_str"), ]: for setting in settings.get(setting_type, []): - if setting['category'] not in settings_lut[game][version]: - cached_setting = data.local.machine.get_settings(arcadeid, game, version, setting['category']) + if setting["category"] not in settings_lut[game][version]: + cached_setting = data.local.machine.get_settings( + arcadeid, game, version, setting["category"] + ) if cached_setting is None: cached_setting = ValidatedDict() - settings_lut[game][version][setting['category']] = cached_setting + settings_lut[game][version][setting["category"]] = cached_setting - current_settings = settings_lut[game][version][setting['category']] - setting['value'] = getattr(current_settings, setting_unpacker)(setting['setting']) + current_settings = settings_lut[game][version][setting["category"]] + setting["value"] = getattr(current_settings, setting_unpacker)( + setting["setting"] + ) game_settings[setting_type].append(setting) # Now, include it! @@ -55,5 +59,5 @@ def get_game_settings(data: Data, arcadeid: ArcadeID) -> List[Dict[str, Any]]: return sorted( all_settings, - key=lambda setting: (setting['game'], setting['version']), + key=lambda setting: (setting["game"], setting["version"]), ) diff --git a/bemani/frontend/home/home.py b/bemani/frontend/home/home.py index 72574ea..c7f01cb 100644 --- a/bemani/frontend/home/home.py +++ b/bemani/frontend/home/home.py @@ -9,7 +9,7 @@ from bemani.frontend.types import g home_pages = Blueprint( - 'home_pages', + "home_pages", __name__, template_folder=templates_location, static_folder=static_location, @@ -18,19 +18,19 @@ home_pages = Blueprint( def format_news(news: News) -> Dict[str, Any]: return { - 'timestamp': news.timestamp, - 'title': news.title, - 'body': news.body, + "timestamp": news.timestamp, + "title": news.title, + "body": news.body, } -@home_pages.route('/') +@home_pages.route("/") @loginrequired def viewhome() -> Response: return render_react( - g.config.get('name', 'e-AMUSEMENT Network'), - 'home.react.js', + g.config.get("name", "e-AMUSEMENT Network"), + "home.react.js", { - 'news': [format_news(news) for news in g.data.local.network.get_all_news()], - } + "news": [format_news(news) for news in g.data.local.network.get_all_news()], + }, ) diff --git a/bemani/frontend/iidx/cache.py b/bemani/frontend/iidx/cache.py index 32c2156..0d5099a 100644 --- a/bemani/frontend/iidx/cache.py +++ b/bemani/frontend/iidx/cache.py @@ -6,12 +6,14 @@ from bemani.frontend.iidx.iidx import IIDXFrontend class IIDXCache: - @classmethod def preload(cls, data: Data, config: Config) -> None: - cache = Cache(app, config={ - 'CACHE_TYPE': 'filesystem', - 'CACHE_DIR': config.cache_dir, - }) + cache = Cache( + app, + config={ + "CACHE_TYPE": "filesystem", + "CACHE_DIR": config.cache_dir, + }, + ) frontend = IIDXFrontend(data, config, cache) frontend.get_all_songs(force_db_load=True) diff --git a/bemani/frontend/iidx/endpoints.py b/bemani/frontend/iidx/endpoints.py index b9f2aa1..f5a6ac5 100644 --- a/bemani/frontend/iidx/endpoints.py +++ b/bemani/frontend/iidx/endpoints.py @@ -12,43 +12,45 @@ from bemani.frontend.static import static_location from bemani.frontend.types import g iidx_pages = Blueprint( - 'iidx_pages', + "iidx_pages", __name__, - url_prefix=f'/{GameConstants.IIDX.value}', + url_prefix=f"/{GameConstants.IIDX.value}", template_folder=templates_location, static_folder=static_location, ) -@iidx_pages.route('/scores') +@iidx_pages.route("/scores") @loginrequired def viewnetworkscores() -> Response: # Only load the last 100 results for the initial fetch, so we can render faster frontend = IIDXFrontend(g.data, g.config, g.cache) network_scores = frontend.get_network_scores(limit=100) - if len(network_scores['attempts']) > 10: - network_scores['attempts'] = frontend.round_to_ten(network_scores['attempts']) + if len(network_scores["attempts"]) > 10: + network_scores["attempts"] = frontend.round_to_ten(network_scores["attempts"]) return render_react( - 'Global IIDX Scores', - 'iidx/scores.react.js', + "Global IIDX Scores", + "iidx/scores.react.js", { - 'attempts': network_scores['attempts'], - 'songs': frontend.get_all_songs(), - 'players': network_scores['players'], - 'versions': {version: name for (game, version, name) in frontend.all_games()}, - 'showdjnames': True, - 'shownewrecords': False, + "attempts": network_scores["attempts"], + "songs": frontend.get_all_songs(), + "players": network_scores["players"], + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, + "showdjnames": True, + "shownewrecords": False, }, { - 'refresh': url_for('iidx_pages.listnetworkscores'), - 'player': url_for('iidx_pages.viewplayer', userid=-1), - 'individual_score': url_for('iidx_pages.viewtopscores', musicid=-1), + "refresh": url_for("iidx_pages.listnetworkscores"), + "player": url_for("iidx_pages.viewplayer", userid=-1), + "individual_score": url_for("iidx_pages.viewtopscores", musicid=-1), }, ) -@iidx_pages.route('/scores/list') +@iidx_pages.route("/scores/list") @jsonify @loginrequired def listnetworkscores() -> Dict[str, Any]: @@ -56,7 +58,7 @@ def listnetworkscores() -> Dict[str, Any]: return frontend.get_network_scores() -@iidx_pages.route('/scores/') +@iidx_pages.route("/scores/") @loginrequired def viewscores(userid: UserID) -> Response: frontend = IIDXFrontend(g.data, g.config, g.cache) @@ -70,61 +72,65 @@ def viewscores(userid: UserID) -> Response: return render_react( f'dj {djinfo["name"]}\'s IIDX Scores', - 'iidx/scores.react.js', + "iidx/scores.react.js", { - 'attempts': scores, - 'songs': frontend.get_all_songs(), - 'players': {}, - 'versions': {version: name for (game, version, name) in frontend.all_games()}, - 'showdjnames': False, - 'shownewrecords': True, + "attempts": scores, + "songs": frontend.get_all_songs(), + "players": {}, + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, + "showdjnames": False, + "shownewrecords": True, }, { - 'refresh': url_for('iidx_pages.listscores', userid=userid), - 'player': url_for('iidx_pages.viewplayer', userid=-1), - 'individual_score': url_for('iidx_pages.viewtopscores', musicid=-1), + "refresh": url_for("iidx_pages.listscores", userid=userid), + "player": url_for("iidx_pages.viewplayer", userid=-1), + "individual_score": url_for("iidx_pages.viewtopscores", musicid=-1), }, ) -@iidx_pages.route('/scores//list') +@iidx_pages.route("/scores//list") @jsonify @loginrequired def listscores(userid: UserID) -> Dict[str, Any]: frontend = IIDXFrontend(g.data, g.config, g.cache) return { - 'attempts': frontend.get_scores(userid), - 'players': {}, + "attempts": frontend.get_scores(userid), + "players": {}, } -@iidx_pages.route('/records') +@iidx_pages.route("/records") @loginrequired def viewnetworkrecords() -> Response: frontend = IIDXFrontend(g.data, g.config, g.cache) network_records = frontend.get_network_records() return render_react( - 'Global IIDX Records', - 'iidx/records.react.js', + "Global IIDX Records", + "iidx/records.react.js", { - 'records': network_records['records'], - 'songs': frontend.get_all_songs(), - 'players': network_records['players'], - 'versions': {version: name for (game, version, name) in frontend.all_games()}, - 'showdjnames': True, - 'showpersonalsort': False, - 'filterempty': False, + "records": network_records["records"], + "songs": frontend.get_all_songs(), + "players": network_records["players"], + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, + "showdjnames": True, + "showpersonalsort": False, + "filterempty": False, }, { - 'refresh': url_for('iidx_pages.listnetworkrecords'), - 'player': url_for('iidx_pages.viewplayer', userid=-1), - 'individual_score': url_for('iidx_pages.viewtopscores', musicid=-1), + "refresh": url_for("iidx_pages.listnetworkrecords"), + "player": url_for("iidx_pages.viewplayer", userid=-1), + "individual_score": url_for("iidx_pages.viewtopscores", musicid=-1), }, ) -@iidx_pages.route('/records/list') +@iidx_pages.route("/records/list") @jsonify @loginrequired def listnetworkrecords() -> Dict[str, Any]: @@ -132,7 +138,7 @@ def listnetworkrecords() -> Dict[str, Any]: return frontend.get_network_records() -@iidx_pages.route('/records/') +@iidx_pages.route("/records/") @loginrequired def viewrecords(userid: UserID) -> Response: frontend = IIDXFrontend(g.data, g.config, g.cache) @@ -142,36 +148,38 @@ def viewrecords(userid: UserID) -> Response: return render_react( f'dj {djinfo["name"]}\'s IIDX Records', - 'iidx/records.react.js', + "iidx/records.react.js", { - 'records': frontend.get_records(userid), - 'songs': frontend.get_all_songs(), - 'players': {}, - 'versions': {version: name for (game, version, name) in frontend.all_games()}, - 'showdjnames': False, - 'showpersonalsort': True, - 'filterempty': True, + "records": frontend.get_records(userid), + "songs": frontend.get_all_songs(), + "players": {}, + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, + "showdjnames": False, + "showpersonalsort": True, + "filterempty": True, }, { - 'refresh': url_for('iidx_pages.listrecords', userid=userid), - 'player': url_for('iidx_pages.viewplayer', userid=-1), - 'individual_score': url_for('iidx_pages.viewtopscores', musicid=-1), + "refresh": url_for("iidx_pages.listrecords", userid=userid), + "player": url_for("iidx_pages.viewplayer", userid=-1), + "individual_score": url_for("iidx_pages.viewtopscores", musicid=-1), }, ) -@iidx_pages.route('/records//list') +@iidx_pages.route("/records//list") @jsonify @loginrequired def listrecords(userid: UserID) -> Dict[str, Any]: frontend = IIDXFrontend(g.data, g.config, g.cache) return { - 'records': frontend.get_records(userid), - 'players': {}, + "records": frontend.get_records(userid), + "players": {}, } -@iidx_pages.route('/topscores/') +@iidx_pages.route("/topscores/") @loginrequired def viewtopscores(musicid: int) -> Response: # We just want to find the latest mix that this song exists in @@ -189,13 +197,15 @@ def viewtopscores(musicid: int) -> Response: for version in versions: for omniadd in [0, 10000]: for chart in [0, 1, 2, 3, 4, 5]: - details = g.data.local.music.get_song(GameConstants.IIDX, version + omniadd, musicid, chart) + details = g.data.local.music.get_song( + GameConstants.IIDX, version + omniadd, musicid, chart + ) if details is not None: name = details.name artist = details.artist genre = details.genre - difficulties[chart] = details.data.get_int('difficulty', 13) - notecounts[chart] = details.data.get_int('notecount', 5730) + difficulties[chart] = details.data.get_int("difficulty", 13) + notecounts[chart] = details.data.get_int("notecount", 5730) if name is None: # Not a real song! @@ -204,25 +214,25 @@ def viewtopscores(musicid: int) -> Response: top_scores = frontend.get_top_scores(musicid) return render_react( - f'Top IIDX Scores for {artist} - {name}', - 'iidx/topscores.react.js', + f"Top IIDX Scores for {artist} - {name}", + "iidx/topscores.react.js", { - 'name': name, - 'artist': artist, - 'genre': genre, - 'difficulties': difficulties, - 'notecounts': notecounts, - 'players': top_scores['players'], - 'topscores': top_scores['topscores'], + "name": name, + "artist": artist, + "genre": genre, + "difficulties": difficulties, + "notecounts": notecounts, + "players": top_scores["players"], + "topscores": top_scores["topscores"], }, { - 'refresh': url_for('iidx_pages.listtopscores', musicid=musicid), - 'player': url_for('iidx_pages.viewplayer', userid=-1), + "refresh": url_for("iidx_pages.listtopscores", musicid=musicid), + "player": url_for("iidx_pages.viewplayer", userid=-1), }, ) -@iidx_pages.route('/topscores//list') +@iidx_pages.route("/topscores//list") @jsonify @loginrequired def listtopscores(musicid: int) -> Dict[str, Any]: @@ -230,34 +240,32 @@ def listtopscores(musicid: int) -> Dict[str, Any]: return frontend.get_top_scores(musicid) -@iidx_pages.route('/players') +@iidx_pages.route("/players") @loginrequired def viewplayers() -> Response: frontend = IIDXFrontend(g.data, g.config, g.cache) return render_react( - 'All IIDX Players', - 'iidx/allplayers.react.js', + "All IIDX Players", + "iidx/allplayers.react.js", + {"players": frontend.get_all_players()}, { - 'players': frontend.get_all_players() - }, - { - 'refresh': url_for('iidx_pages.listplayers'), - 'player': url_for('iidx_pages.viewplayer', userid=-1), + "refresh": url_for("iidx_pages.listplayers"), + "player": url_for("iidx_pages.viewplayer", userid=-1), }, ) -@iidx_pages.route('/players/list') +@iidx_pages.route("/players/list") @jsonify @loginrequired def listplayers() -> Dict[str, Any]: frontend = IIDXFrontend(g.data, g.config, g.cache) return { - 'players': frontend.get_all_players(), + "players": frontend.get_all_players(), } -@iidx_pages.route('/players/') +@iidx_pages.route("/players/") @loginrequired def viewplayer(userid: UserID) -> Response: frontend = IIDXFrontend(g.data, g.config, g.cache) @@ -267,31 +275,37 @@ def viewplayer(userid: UserID) -> Response: latest_version = sorted(djinfo.keys(), reverse=True)[0] for version in djinfo: - sp_rival = g.data.local.user.get_link(GameConstants.IIDX, version, g.userID, 'sp_rival', userid) - dp_rival = g.data.local.user.get_link(GameConstants.IIDX, version, g.userID, 'dp_rival', userid) - djinfo[version]['sp_rival'] = sp_rival is not None - djinfo[version]['dp_rival'] = dp_rival is not None + sp_rival = g.data.local.user.get_link( + GameConstants.IIDX, version, g.userID, "sp_rival", userid + ) + dp_rival = g.data.local.user.get_link( + GameConstants.IIDX, version, g.userID, "dp_rival", userid + ) + djinfo[version]["sp_rival"] = sp_rival is not None + djinfo[version]["dp_rival"] = dp_rival is not None return render_react( f'dj {djinfo[latest_version]["name"]}\'s IIDX Profile', - 'iidx/player.react.js', + "iidx/player.react.js", { - 'playerid': userid, - 'own_profile': userid == g.userID, - 'player': djinfo, - 'versions': {version: name for (game, version, name) in frontend.all_games()}, + "playerid": userid, + "own_profile": userid == g.userID, + "player": djinfo, + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, }, { - 'refresh': url_for('iidx_pages.listplayer', userid=userid), - 'records': url_for('iidx_pages.viewrecords', userid=userid), - 'scores': url_for('iidx_pages.viewscores', userid=userid), - 'addrival': url_for('iidx_pages.addrival'), - 'removerival': url_for('iidx_pages.removerival'), + "refresh": url_for("iidx_pages.listplayer", userid=userid), + "records": url_for("iidx_pages.viewrecords", userid=userid), + "scores": url_for("iidx_pages.viewscores", userid=userid), + "addrival": url_for("iidx_pages.addrival"), + "removerival": url_for("iidx_pages.removerival"), }, ) -@iidx_pages.route('/players//list') +@iidx_pages.route("/players//list") @jsonify @loginrequired def listplayer(userid: UserID) -> Dict[str, Any]: @@ -299,17 +313,21 @@ def listplayer(userid: UserID) -> Dict[str, Any]: djinfo = frontend.get_all_player_info([userid])[userid] for version in djinfo: - sp_rival = g.data.local.user.get_link(GameConstants.IIDX, version, g.userID, 'sp_rival', userid) - dp_rival = g.data.local.user.get_link(GameConstants.IIDX, version, g.userID, 'dp_rival', userid) - djinfo[version]['sp_rival'] = sp_rival is not None - djinfo[version]['dp_rival'] = dp_rival is not None + sp_rival = g.data.local.user.get_link( + GameConstants.IIDX, version, g.userID, "sp_rival", userid + ) + dp_rival = g.data.local.user.get_link( + GameConstants.IIDX, version, g.userID, "dp_rival", userid + ) + djinfo[version]["sp_rival"] = sp_rival is not None + djinfo[version]["dp_rival"] = dp_rival is not None return { - 'player': djinfo, + "player": djinfo, } -@iidx_pages.route('/options') +@iidx_pages.route("/options") @loginrequired def viewsettings() -> Response: frontend = IIDXFrontend(g.data, g.config, g.cache) @@ -322,236 +340,246 @@ def viewsettings() -> Response: reverse=True, ) return render_react( - 'IIDX Game Settings', - 'iidx/settings.react.js', + "IIDX Game Settings", + "iidx/settings.react.js", { - 'player': djinfo, - 'regions': RegionConstants.LUT, - 'versions': {version: name for (game, version, name) in frontend.all_games()}, - 'qpros': frontend.get_all_items(versions), + "player": djinfo, + "regions": RegionConstants.LUT, + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, + "qpros": frontend.get_all_items(versions), }, { - 'updateqpro': url_for('iidx_pages.updateqpro'), - 'updateflags': url_for('iidx_pages.updateflags'), - 'updatesettings': url_for('iidx_pages.updatesettings'), - 'updatename': url_for('iidx_pages.updatename'), - 'updateprefecture': url_for('iidx_pages.updateprefecture'), - 'leavearcade': url_for('iidx_pages.leavearcade'), + "updateqpro": url_for("iidx_pages.updateqpro"), + "updateflags": url_for("iidx_pages.updateflags"), + "updatesettings": url_for("iidx_pages.updatesettings"), + "updatename": url_for("iidx_pages.updatename"), + "updateprefecture": url_for("iidx_pages.updateprefecture"), + "leavearcade": url_for("iidx_pages.leavearcade"), }, ) -@iidx_pages.route('/options/flags/update', methods=['POST']) +@iidx_pages.route("/options/flags/update", methods=["POST"]) @jsonify @loginrequired def updateflags() -> Dict[str, Any]: frontend = IIDXFrontend(g.data, g.config, g.cache) - flags = request.get_json()['flags'] - version = int(request.get_json()['version']) + flags = request.get_json()["flags"] + version = int(request.get_json()["version"]) user = g.data.local.user.get_user(g.userID) if user is None: - raise Exception('Unable to find user to update!') + raise Exception("Unable to find user to update!") # Grab profile and settings dict that needs updating profile = g.data.local.user.get_profile(GameConstants.IIDX, version, user.id) if profile is None: - raise Exception('Unable to find profile to update!') - settings_dict = profile.get_dict('settings') + raise Exception("Unable to find profile to update!") + settings_dict = profile.get_dict("settings") # Set bits for flags based on frontend flagint = 0 - flagint += 0x001 if flags['grade'] else 0 - flagint += 0x002 if flags['status'] else 0 - flagint += 0x004 if flags['difficulty'] else 0 - flagint += 0x008 if flags['alphabet'] else 0 - flagint += 0x010 if flags['rival_played'] else 0 - flagint += 0x040 if flags['rival_win_lose'] else 0 - flagint += 0x080 if flags['rival_info'] else 0 - flagint += 0x100 if flags['hide_play_count'] else 0 - flagint += 0x200 if flags['disable_graph_cutin'] else 0 - flagint += 0x400 if flags['classic_hispeed'] else 0 - flagint += 0x1000 if flags['hide_iidx_id'] else 0 - settings_dict.replace_int('flags', flagint) + flagint += 0x001 if flags["grade"] else 0 + flagint += 0x002 if flags["status"] else 0 + flagint += 0x004 if flags["difficulty"] else 0 + flagint += 0x008 if flags["alphabet"] else 0 + flagint += 0x010 if flags["rival_played"] else 0 + flagint += 0x040 if flags["rival_win_lose"] else 0 + flagint += 0x080 if flags["rival_info"] else 0 + flagint += 0x100 if flags["hide_play_count"] else 0 + flagint += 0x200 if flags["disable_graph_cutin"] else 0 + flagint += 0x400 if flags["classic_hispeed"] else 0 + flagint += 0x1000 if flags["hide_iidx_id"] else 0 + settings_dict.replace_int("flags", flagint) # Update special case flags - settings_dict.replace_int('disable_song_preview', 1 if flags['disable_song_preview'] else 0) - settings_dict.replace_int('effector_lock', 1 if flags['effector_lock'] else 0) - settings_dict.replace_int('disable_hcn_color', 1 if flags['disable_hcn_color'] else 0) + settings_dict.replace_int( + "disable_song_preview", 1 if flags["disable_song_preview"] else 0 + ) + settings_dict.replace_int("effector_lock", 1 if flags["effector_lock"] else 0) + settings_dict.replace_int( + "disable_hcn_color", 1 if flags["disable_hcn_color"] else 0 + ) # Update the settings dict - profile.replace_dict('settings', settings_dict) + profile.replace_dict("settings", settings_dict) g.data.local.user.put_profile(GameConstants.IIDX, version, user.id, profile) # Return updated flags return { - 'flags': frontend.format_flags(settings_dict), - 'version': version, + "flags": frontend.format_flags(settings_dict), + "version": version, } -@iidx_pages.route('/options/qpro/update', methods=['POST']) +@iidx_pages.route("/options/qpro/update", methods=["POST"]) @jsonify @loginrequired def updateqpro() -> Dict[str, Any]: frontend = IIDXFrontend(g.data, g.config, g.cache) - qpros = request.get_json()['qpro'] - version = int(request.get_json()['version']) + qpros = request.get_json()["qpro"] + version = int(request.get_json()["version"]) user = g.data.local.user.get_user(g.userID) if user is None: - raise Exception('Unable to find user to update!') + raise Exception("Unable to find user to update!") # Grab profile and qpro dict that needs updating profile = g.data.local.user.get_profile(GameConstants.IIDX, version, user.id) if profile is None: - raise Exception('Unable to find profile to update!') - qpro_dict = profile.get_dict('qpro') + raise Exception("Unable to find profile to update!") + qpro_dict = profile.get_dict("qpro") for qpro in qpros: qpro_dict.replace_int(qpro, qpros[qpro]) # Update the qpro dict - profile.replace_dict('qpro', qpro_dict) + profile.replace_dict("qpro", qpro_dict) g.data.local.user.put_profile(GameConstants.IIDX, version, user.id, profile) # Return updated qpro return { - 'qpro': frontend.format_qpro(qpro_dict), - 'version': version, + "qpro": frontend.format_qpro(qpro_dict), + "version": version, } -@iidx_pages.route('/options/settings/update', methods=['POST']) +@iidx_pages.route("/options/settings/update", methods=["POST"]) @jsonify @loginrequired def updatesettings() -> Dict[str, Any]: frontend = IIDXFrontend(g.data, g.config, g.cache) - settings = request.get_json()['settings'] - version = int(request.get_json()['version']) + settings = request.get_json()["settings"] + version = int(request.get_json()["version"]) user = g.data.local.user.get_user(g.userID) if user is None: - raise Exception('Unable to find user to update!') + raise Exception("Unable to find user to update!") # Grab profile and settings dict that needs updating profile = g.data.local.user.get_profile(GameConstants.IIDX, version, user.id) if profile is None: - raise Exception('Unable to find profile to update!') - settings_dict = profile.get_dict('settings') + raise Exception("Unable to find profile to update!") + settings_dict = profile.get_dict("settings") for setting in settings: settings_dict.replace_int(setting, settings[setting]) # Update the settings dict - profile.replace_dict('settings', settings_dict) + profile.replace_dict("settings", settings_dict) g.data.local.user.put_profile(GameConstants.IIDX, version, user.id, profile) # Return updated settings return { - 'settings': frontend.format_settings(settings_dict), - 'version': version, + "settings": frontend.format_settings(settings_dict), + "version": version, } -@iidx_pages.route('/options/arcade/leave', methods=['POST']) +@iidx_pages.route("/options/arcade/leave", methods=["POST"]) @jsonify @loginrequired def leavearcade() -> Dict[str, Any]: - version = int(request.get_json()['version']) + version = int(request.get_json()["version"]) user = g.data.local.user.get_user(g.userID) if user is None: - raise Exception('Unable to find user to update!') + raise Exception("Unable to find user to update!") # Grab profile and nuke the shop location profile = g.data.local.user.get_profile(GameConstants.IIDX, version, user.id) if profile is None: - raise Exception('Unable to find profile to update!') - if 'shop_location' in profile: - del profile['shop_location'] + raise Exception("Unable to find profile to update!") + if "shop_location" in profile: + del profile["shop_location"] g.data.local.user.put_profile(GameConstants.IIDX, version, user.id, profile) # Return that we updated return { - 'version': version, + "version": version, } -@iidx_pages.route('/options/name/update', methods=['POST']) +@iidx_pages.route("/options/name/update", methods=["POST"]) @jsonify @loginrequired def updatename() -> Dict[str, Any]: - version = int(request.get_json()['version']) - name = request.get_json()['name'] + version = int(request.get_json()["version"]) + name = request.get_json()["name"] user = g.data.local.user.get_user(g.userID) if user is None: - raise Exception('Unable to find user to update!') + raise Exception("Unable to find user to update!") # Grab profile and update dj name profile = g.data.local.user.get_profile(GameConstants.IIDX, version, user.id) if profile is None: - raise Exception('Unable to find profile to update!') + raise Exception("Unable to find profile to update!") if len(name) == 0 or len(name) > 6: - raise Exception('Invalid profile name!') - if re.match(r'^[-&$#\.\?\*!A-Z0-9]*$', name) is None: - raise Exception('Invalid profile name!') - profile.replace_str('name', name) + raise Exception("Invalid profile name!") + if re.match(r"^[-&$#\.\?\*!A-Z0-9]*$", name) is None: + raise Exception("Invalid profile name!") + profile.replace_str("name", name) g.data.local.user.put_profile(GameConstants.IIDX, version, user.id, profile) # Return that we updated return { - 'version': version, - 'name': name, + "version": version, + "name": name, } -@iidx_pages.route('/options/prefecture/update', methods=['POST']) +@iidx_pages.route("/options/prefecture/update", methods=["POST"]) @jsonify @loginrequired def updateprefecture() -> Dict[str, Any]: - version = int(request.get_json()['version']) - prefecture = int(request.get_json()['prefecture']) + version = int(request.get_json()["version"]) + prefecture = int(request.get_json()["prefecture"]) user = g.data.local.user.get_user(g.userID) if user is None: - raise Exception('Unable to find user to update!') + raise Exception("Unable to find user to update!") # Grab profile and update prefecture profile = g.data.local.user.get_profile(GameConstants.IIDX, version, user.id) if profile is None: - raise Exception('Unable to find profile to update!') - profile.replace_int('pid', RegionConstants.db_to_game_region(version >= 25, prefecture)) + raise Exception("Unable to find profile to update!") + profile.replace_int( + "pid", RegionConstants.db_to_game_region(version >= 25, prefecture) + ) g.data.local.user.put_profile(GameConstants.IIDX, version, user.id, profile) # Return that we updated return { - 'version': version, - 'prefecture': prefecture, + "version": version, + "prefecture": prefecture, } -@iidx_pages.route('/rivals') +@iidx_pages.route("/rivals") @loginrequired def viewrivals() -> Response: frontend = IIDXFrontend(g.data, g.config, g.cache) rivals, djinfo = frontend.get_rivals(g.userID) return render_react( - 'IIDX Rivals', - 'iidx/rivals.react.js', + "IIDX Rivals", + "iidx/rivals.react.js", { - 'userid': str(g.userID), - 'rivals': rivals, - 'players': djinfo, - 'versions': {version: name for (game, version, name) in frontend.all_games()}, + "userid": str(g.userID), + "rivals": rivals, + "players": djinfo, + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, }, { - 'refresh': url_for('iidx_pages.listrivals'), - 'search': url_for('iidx_pages.searchrivals'), - 'player': url_for('iidx_pages.viewplayer', userid=-1), - 'addrival': url_for('iidx_pages.addrival'), - 'removerival': url_for('iidx_pages.removerival'), + "refresh": url_for("iidx_pages.listrivals"), + "search": url_for("iidx_pages.searchrivals"), + "player": url_for("iidx_pages.viewplayer", userid=-1), + "addrival": url_for("iidx_pages.addrival"), + "removerival": url_for("iidx_pages.removerival"), }, ) -@iidx_pages.route('/rivals/list') +@iidx_pages.route("/rivals/list") @jsonify @loginrequired def listrivals() -> Dict[str, Any]: @@ -559,18 +587,18 @@ def listrivals() -> Dict[str, Any]: rivals, djinfo = frontend.get_rivals(g.userID) return { - 'rivals': rivals, - 'players': djinfo, + "rivals": rivals, + "players": djinfo, } -@iidx_pages.route('/rivals/search', methods=['POST']) +@iidx_pages.route("/rivals/search", methods=["POST"]) @jsonify @loginrequired def searchrivals() -> Dict[str, Any]: frontend = IIDXFrontend(g.data, g.config, g.cache) - version = int(request.get_json()['version']) - djname = request.get_json()['term'] + version = int(request.get_json()["version"]) + djname = request.get_json()["term"] # Try to treat the term as an extid extid = ID.parse_extid(djname) @@ -578,31 +606,31 @@ def searchrivals() -> Dict[str, Any]: matches = set() profiles = g.data.remote.user.get_all_profiles(GameConstants.IIDX, version) for (userid, profile) in profiles: - if profile.extid == extid or profile.get_str('name').lower() == djname.lower(): + if profile.extid == extid or profile.get_str("name").lower() == djname.lower(): matches.add(userid) djinfo = frontend.get_all_player_info(list(matches), allow_remote=True) return { - 'results': djinfo, + "results": djinfo, } -@iidx_pages.route('/rivals/add', methods=['POST']) +@iidx_pages.route("/rivals/add", methods=["POST"]) @jsonify @loginrequired def addrival() -> Dict[str, Any]: frontend = IIDXFrontend(g.data, g.config, g.cache) - version = int(request.get_json()['version']) - rivaltype = request.get_json()['type'] - other_userid = UserID(int(request.get_json()['userid'])) + version = int(request.get_json()["version"]) + rivaltype = request.get_json()["type"] + other_userid = UserID(int(request.get_json()["userid"])) userid = g.userID # Add this rival link - if rivaltype != 'sp_rival' and rivaltype != 'dp_rival': - raise Exception(f'Invalid rival type {rivaltype}!') + if rivaltype != "sp_rival" and rivaltype != "dp_rival": + raise Exception(f"Invalid rival type {rivaltype}!") profile = g.data.remote.user.get_profile(GameConstants.IIDX, version, other_userid) if profile is None: - raise Exception('Unable to find profile for rival!') + raise Exception("Unable to find profile for rival!") g.data.local.user.put_link( GameConstants.IIDX, @@ -617,24 +645,24 @@ def addrival() -> Dict[str, Any]: rivals, djinfo = frontend.get_rivals(userid) return { - 'rivals': rivals, - 'players': djinfo, + "rivals": rivals, + "players": djinfo, } -@iidx_pages.route('/rivals/remove', methods=['POST']) +@iidx_pages.route("/rivals/remove", methods=["POST"]) @jsonify @loginrequired def removerival() -> Dict[str, Any]: frontend = IIDXFrontend(g.data, g.config, g.cache) - version = int(request.get_json()['version']) - rivaltype = request.get_json()['type'] - other_userid = UserID(int(request.get_json()['userid'])) + version = int(request.get_json()["version"]) + rivaltype = request.get_json()["type"] + other_userid = UserID(int(request.get_json()["userid"])) userid = g.userID # Remove this rival link - if rivaltype != 'sp_rival' and rivaltype != 'dp_rival': - raise Exception(f'Invalid rival type {rivaltype}!') + if rivaltype != "sp_rival" and rivaltype != "dp_rival": + raise Exception(f"Invalid rival type {rivaltype}!") g.data.local.user.destroy_link( GameConstants.IIDX, @@ -648,6 +676,6 @@ def removerival() -> Dict[str, Any]: rivals, djinfo = frontend.get_rivals(userid) return { - 'rivals': rivals, - 'players': djinfo, + "rivals": rivals, + "players": djinfo, } diff --git a/bemani/frontend/iidx/iidx.py b/bemani/frontend/iidx/iidx.py index 20cf137..e0881c9 100644 --- a/bemani/frontend/iidx/iidx.py +++ b/bemani/frontend/iidx/iidx.py @@ -23,8 +23,8 @@ class IIDXFrontend(FrontendBase): ] valid_rival_types: List[str] = [ - 'sp_rival', - 'dp_rival', + "sp_rival", + "dp_rival", ] def __init__(self, data: Data, config: Config, cache: Cache) -> None: @@ -90,82 +90,82 @@ class IIDXFrontend(FrontendBase): def format_dan_rank(self, rank: int) -> str: if rank == -1: - return '--' + return "--" return { - IIDXBase.DAN_RANK_7_KYU: '七級', - IIDXBase.DAN_RANK_6_KYU: '六級', - IIDXBase.DAN_RANK_5_KYU: '五級', - IIDXBase.DAN_RANK_4_KYU: '四級', - IIDXBase.DAN_RANK_3_KYU: '三級', - IIDXBase.DAN_RANK_2_KYU: '二級', - IIDXBase.DAN_RANK_1_KYU: '一級', - IIDXBase.DAN_RANK_1_DAN: '初段', - IIDXBase.DAN_RANK_2_DAN: '二段', - IIDXBase.DAN_RANK_3_DAN: '三段', - IIDXBase.DAN_RANK_4_DAN: '四段', - IIDXBase.DAN_RANK_5_DAN: '五段', - IIDXBase.DAN_RANK_6_DAN: '六段', - IIDXBase.DAN_RANK_7_DAN: '七段', - IIDXBase.DAN_RANK_8_DAN: '八段', - IIDXBase.DAN_RANK_9_DAN: '九段', - IIDXBase.DAN_RANK_10_DAN: '十段', - IIDXBase.DAN_RANK_CHUDEN: '中伝', - IIDXBase.DAN_RANK_KAIDEN: '皆伝', + IIDXBase.DAN_RANK_7_KYU: "七級", + IIDXBase.DAN_RANK_6_KYU: "六級", + IIDXBase.DAN_RANK_5_KYU: "五級", + IIDXBase.DAN_RANK_4_KYU: "四級", + IIDXBase.DAN_RANK_3_KYU: "三級", + IIDXBase.DAN_RANK_2_KYU: "二級", + IIDXBase.DAN_RANK_1_KYU: "一級", + IIDXBase.DAN_RANK_1_DAN: "初段", + IIDXBase.DAN_RANK_2_DAN: "二段", + IIDXBase.DAN_RANK_3_DAN: "三段", + IIDXBase.DAN_RANK_4_DAN: "四段", + IIDXBase.DAN_RANK_5_DAN: "五段", + IIDXBase.DAN_RANK_6_DAN: "六段", + IIDXBase.DAN_RANK_7_DAN: "七段", + IIDXBase.DAN_RANK_8_DAN: "八段", + IIDXBase.DAN_RANK_9_DAN: "九段", + IIDXBase.DAN_RANK_10_DAN: "十段", + IIDXBase.DAN_RANK_CHUDEN: "中伝", + IIDXBase.DAN_RANK_KAIDEN: "皆伝", }[rank] def format_flags(self, settings_dict: ValidatedDict) -> Dict[str, Any]: - flags = settings_dict.get_int('flags') + flags = settings_dict.get_int("flags") return { - 'grade': (flags & 0x001) != 0, - 'status': (flags & 0x002) != 0, - 'difficulty': (flags & 0x004) != 0, - 'alphabet': (flags & 0x008) != 0, - 'rival_played': (flags & 0x010) != 0, - 'rival_win_lose': (flags & 0x040) != 0, - 'rival_info': (flags & 0x080) != 0, - 'hide_play_count': (flags & 0x100) != 0, - 'disable_graph_cutin': (flags & 0x200) != 0, - 'classic_hispeed': (flags & 0x400) != 0, - 'hide_iidx_id': (flags & 0x1000) != 0, - 'disable_song_preview': settings_dict.get_int('disable_song_preview') != 0, - 'effector_lock': settings_dict.get_int('effector_lock') != 0, - 'disable_hcn_color': settings_dict.get_int('disable_hcn_color') != 0, + "grade": (flags & 0x001) != 0, + "status": (flags & 0x002) != 0, + "difficulty": (flags & 0x004) != 0, + "alphabet": (flags & 0x008) != 0, + "rival_played": (flags & 0x010) != 0, + "rival_win_lose": (flags & 0x040) != 0, + "rival_info": (flags & 0x080) != 0, + "hide_play_count": (flags & 0x100) != 0, + "disable_graph_cutin": (flags & 0x200) != 0, + "classic_hispeed": (flags & 0x400) != 0, + "hide_iidx_id": (flags & 0x1000) != 0, + "disable_song_preview": settings_dict.get_int("disable_song_preview") != 0, + "effector_lock": settings_dict.get_int("effector_lock") != 0, + "disable_hcn_color": settings_dict.get_int("disable_hcn_color") != 0, } def format_settings(self, settings_dict: ValidatedDict) -> Dict[str, Any]: return { - 'frame': settings_dict.get_int('frame'), - 'turntable': settings_dict.get_int('turntable'), - 'burst': settings_dict.get_int('burst'), - 'bgm': settings_dict.get_int('bgm'), - 'towel': settings_dict.get_int('towel'), - 'judge_pos': settings_dict.get_int('judge_pos'), - 'voice': settings_dict.get_int('voice'), - 'noteskin': settings_dict.get_int('noteskin'), - 'full_combo': settings_dict.get_int('full_combo'), - 'beam': settings_dict.get_int('beam'), - 'judge': settings_dict.get_int('judge'), - 'pacemaker': settings_dict.get_int('pacemaker'), - 'effector_preset': settings_dict.get_int('effector_preset'), - 'explosion_size': settings_dict.get_int('explosion_size'), - 'note_preview': settings_dict.get_int('note_preview'), + "frame": settings_dict.get_int("frame"), + "turntable": settings_dict.get_int("turntable"), + "burst": settings_dict.get_int("burst"), + "bgm": settings_dict.get_int("bgm"), + "towel": settings_dict.get_int("towel"), + "judge_pos": settings_dict.get_int("judge_pos"), + "voice": settings_dict.get_int("voice"), + "noteskin": settings_dict.get_int("noteskin"), + "full_combo": settings_dict.get_int("full_combo"), + "beam": settings_dict.get_int("beam"), + "judge": settings_dict.get_int("judge"), + "pacemaker": settings_dict.get_int("pacemaker"), + "effector_preset": settings_dict.get_int("effector_preset"), + "explosion_size": settings_dict.get_int("explosion_size"), + "note_preview": settings_dict.get_int("note_preview"), } def format_qpro(self, qpro_dict: ValidatedDict) -> Dict[str, Any]: return { - 'body': qpro_dict.get_int('body'), - 'face': qpro_dict.get_int('face'), - 'hair': qpro_dict.get_int('hair'), - 'hand': qpro_dict.get_int('hand'), - 'head': qpro_dict.get_int('head'), + "body": qpro_dict.get_int("body"), + "face": qpro_dict.get_int("face"), + "hair": qpro_dict.get_int("hair"), + "hand": qpro_dict.get_int("hand"), + "head": qpro_dict.get_int("head"), } def get_all_items(self, versions: list) -> Dict[str, List[Dict[str, Any]]]: result = {} for version in versions: qpro = self.__format_iidx_extras(version) - result[version] = qpro['qpros'] + result[version] = qpro["qpros"] return result def __format_iidx_extras(self, version: int) -> Dict[str, List[Dict[str, Any]]]: @@ -181,106 +181,113 @@ class IIDXFrontend(FrontendBase): "type": item.type[3:], } for item in items - if item.type in ['qp_body', 'qp_face', 'qp_hair', 'qp_hand', 'qp_head'] + if item.type in ["qp_body", "qp_face", "qp_hair", "qp_hand", "qp_head"] ], } - def format_profile(self, profile: Profile, playstats: ValidatedDict) -> Dict[str, Any]: + def format_profile( + self, profile: Profile, playstats: ValidatedDict + ) -> Dict[str, Any]: formatted_profile = super().format_profile(profile, playstats) - formatted_profile.update({ - 'arcade': "", - 'prefecture': RegionConstants.game_to_db_region(profile.version >= 25, profile.get_int('pid', self.config.server.region)), - 'settings': self.format_settings(profile.get_dict('settings')), - 'flags': self.format_flags(profile.get_dict('settings')), - 'sdjp': playstats.get_int('single_dj_points'), - 'ddjp': playstats.get_int('double_dj_points'), - 'sp': playstats.get_int('single_plays'), - 'dp': playstats.get_int('double_plays'), - 'sdan': self.format_dan_rank(profile.get_int('sgrade', -1)), - 'ddan': self.format_dan_rank(profile.get_int('dgrade', -1)), - 'srank': profile.get_int('sgrade', -1), - 'drank': profile.get_int('dgrade', -1), - 'qpro': self.format_qpro(profile.get_dict('qpro')), - }) - if 'shop_location' in profile: - shop_id = profile.get_int('shop_location') + formatted_profile.update( + { + "arcade": "", + "prefecture": RegionConstants.game_to_db_region( + profile.version >= 25, + profile.get_int("pid", self.config.server.region), + ), + "settings": self.format_settings(profile.get_dict("settings")), + "flags": self.format_flags(profile.get_dict("settings")), + "sdjp": playstats.get_int("single_dj_points"), + "ddjp": playstats.get_int("double_dj_points"), + "sp": playstats.get_int("single_plays"), + "dp": playstats.get_int("double_plays"), + "sdan": self.format_dan_rank(profile.get_int("sgrade", -1)), + "ddan": self.format_dan_rank(profile.get_int("dgrade", -1)), + "srank": profile.get_int("sgrade", -1), + "drank": profile.get_int("dgrade", -1), + "qpro": self.format_qpro(profile.get_dict("qpro")), + } + ) + if "shop_location" in profile: + shop_id = profile.get_int("shop_location") if shop_id in self.machines: - formatted_profile['arcade'] = self.machines[shop_id] + formatted_profile["arcade"] = self.machines[shop_id] else: pcbid = self.data.local.machine.from_machine_id(shop_id) if pcbid is not None: machine = self.data.local.machine.get_machine(pcbid) self.machines[shop_id] = machine.name - formatted_profile['arcade'] = machine.name + formatted_profile["arcade"] = machine.name return formatted_profile def format_score(self, userid: UserID, score: Score) -> Dict[str, Any]: formatted_score = super().format_score(userid, score) - formatted_score['miss_count'] = score.data.get_int('miss_count', -1) - formatted_score['lamp'] = score.data.get_int('clear_status') - formatted_score['status'] = { - IIDXBase.CLEAR_STATUS_NO_PLAY: 'NO PLAY', - IIDXBase.CLEAR_STATUS_FAILED: 'FAILED', - IIDXBase.CLEAR_STATUS_ASSIST_CLEAR: 'ASSIST CLEAR', - IIDXBase.CLEAR_STATUS_EASY_CLEAR: 'EASY CLEAR', - IIDXBase.CLEAR_STATUS_CLEAR: 'CLEAR', - IIDXBase.CLEAR_STATUS_HARD_CLEAR: 'HARD CLEAR', - IIDXBase.CLEAR_STATUS_EX_HARD_CLEAR: 'EX HARD CLEAR', - IIDXBase.CLEAR_STATUS_FULL_COMBO: 'FULL COMBO', - }.get(score.data.get_int('clear_status'), 'NO PLAY') + formatted_score["miss_count"] = score.data.get_int("miss_count", -1) + formatted_score["lamp"] = score.data.get_int("clear_status") + formatted_score["status"] = { + IIDXBase.CLEAR_STATUS_NO_PLAY: "NO PLAY", + IIDXBase.CLEAR_STATUS_FAILED: "FAILED", + IIDXBase.CLEAR_STATUS_ASSIST_CLEAR: "ASSIST CLEAR", + IIDXBase.CLEAR_STATUS_EASY_CLEAR: "EASY CLEAR", + IIDXBase.CLEAR_STATUS_CLEAR: "CLEAR", + IIDXBase.CLEAR_STATUS_HARD_CLEAR: "HARD CLEAR", + IIDXBase.CLEAR_STATUS_EX_HARD_CLEAR: "EX HARD CLEAR", + IIDXBase.CLEAR_STATUS_FULL_COMBO: "FULL COMBO", + }.get(score.data.get_int("clear_status"), "NO PLAY") return formatted_score def format_top_score(self, userid: UserID, score: Score) -> Dict[str, Any]: formatted_score = super().format_score(userid, score) - formatted_score['miss_count'] = score.data.get_int('miss_count', -1) - formatted_score['lamp'] = score.data.get_int('clear_status') - formatted_score['ghost'] = [x for x in (score.data.get_bytes('ghost') or b'')] - formatted_score['status'] = { - IIDXBase.CLEAR_STATUS_NO_PLAY: 'NO PLAY', - IIDXBase.CLEAR_STATUS_FAILED: 'FAILED', - IIDXBase.CLEAR_STATUS_ASSIST_CLEAR: 'ASSIST CLEAR', - IIDXBase.CLEAR_STATUS_EASY_CLEAR: 'EASY CLEAR', - IIDXBase.CLEAR_STATUS_CLEAR: 'CLEAR', - IIDXBase.CLEAR_STATUS_HARD_CLEAR: 'HARD CLEAR', - IIDXBase.CLEAR_STATUS_EX_HARD_CLEAR: 'EX HARD CLEAR', - IIDXBase.CLEAR_STATUS_FULL_COMBO: 'FULL COMBO', - }.get(score.data.get_int('clear_status'), 'NO PLAY') + formatted_score["miss_count"] = score.data.get_int("miss_count", -1) + formatted_score["lamp"] = score.data.get_int("clear_status") + formatted_score["ghost"] = [x for x in (score.data.get_bytes("ghost") or b"")] + formatted_score["status"] = { + IIDXBase.CLEAR_STATUS_NO_PLAY: "NO PLAY", + IIDXBase.CLEAR_STATUS_FAILED: "FAILED", + IIDXBase.CLEAR_STATUS_ASSIST_CLEAR: "ASSIST CLEAR", + IIDXBase.CLEAR_STATUS_EASY_CLEAR: "EASY CLEAR", + IIDXBase.CLEAR_STATUS_CLEAR: "CLEAR", + IIDXBase.CLEAR_STATUS_HARD_CLEAR: "HARD CLEAR", + IIDXBase.CLEAR_STATUS_EX_HARD_CLEAR: "EX HARD CLEAR", + IIDXBase.CLEAR_STATUS_FULL_COMBO: "FULL COMBO", + }.get(score.data.get_int("clear_status"), "NO PLAY") return formatted_score def format_attempt(self, userid: UserID, attempt: Attempt) -> Dict[str, Any]: formatted_attempt = super().format_attempt(userid, attempt) - formatted_attempt['miss_count'] = attempt.data.get_int('miss_count', -1) - formatted_attempt['status'] = { - IIDXBase.CLEAR_STATUS_NO_PLAY: 'NO PLAY', - IIDXBase.CLEAR_STATUS_FAILED: 'FAILED', - IIDXBase.CLEAR_STATUS_ASSIST_CLEAR: 'ASSIST CLEAR', - IIDXBase.CLEAR_STATUS_EASY_CLEAR: 'EASY CLEAR', - IIDXBase.CLEAR_STATUS_CLEAR: 'CLEAR', - IIDXBase.CLEAR_STATUS_HARD_CLEAR: 'HARD CLEAR', - IIDXBase.CLEAR_STATUS_EX_HARD_CLEAR: 'EX HARD CLEAR', - IIDXBase.CLEAR_STATUS_FULL_COMBO: 'FULL COMBO', - }.get(attempt.data.get_int('clear_status'), 'NO PLAY') + formatted_attempt["miss_count"] = attempt.data.get_int("miss_count", -1) + formatted_attempt["status"] = { + IIDXBase.CLEAR_STATUS_NO_PLAY: "NO PLAY", + IIDXBase.CLEAR_STATUS_FAILED: "FAILED", + IIDXBase.CLEAR_STATUS_ASSIST_CLEAR: "ASSIST CLEAR", + IIDXBase.CLEAR_STATUS_EASY_CLEAR: "EASY CLEAR", + IIDXBase.CLEAR_STATUS_CLEAR: "CLEAR", + IIDXBase.CLEAR_STATUS_HARD_CLEAR: "HARD CLEAR", + IIDXBase.CLEAR_STATUS_EX_HARD_CLEAR: "EX HARD CLEAR", + IIDXBase.CLEAR_STATUS_FULL_COMBO: "FULL COMBO", + }.get(attempt.data.get_int("clear_status"), "NO PLAY") return formatted_attempt def format_song(self, song: Song) -> Dict[str, Any]: difficulties = [0, 0, 0, 0, 0, 0] notecounts = [0, 0, 0, 0, 0, 0] - difficulties[song.chart] = song.data.get_int('difficulty', 13) - notecounts[song.chart] = song.data.get_int('notecount', 5730) + difficulties[song.chart] = song.data.get_int("difficulty", 13) + notecounts[song.chart] = song.data.get_int("notecount", 5730) formatted_song = super().format_song(song) - formatted_song['bpm_min'] = song.data.get_int('bpm_min', 120) - formatted_song['bpm_max'] = song.data.get_int('bpm_max', 120) - formatted_song['difficulties'] = difficulties - formatted_song['notecounts'] = notecounts + formatted_song["bpm_min"] = song.data.get_int("bpm_min", 120) + formatted_song["bpm_max"] = song.data.get_int("bpm_max", 120) + formatted_song["difficulties"] = difficulties + formatted_song["notecounts"] = notecounts return formatted_song def merge_song(self, existing: Dict[str, Any], new: Song) -> Dict[str, Any]: new_song = super().merge_song(existing, new) if ( - existing['difficulties'][new.chart] == 0 or - existing['notecounts'][new.chart] == 0 + existing["difficulties"][new.chart] == 0 + or existing["notecounts"][new.chart] == 0 ): - new_song['difficulties'][new.chart] = new.data.get_int('difficulty', 13) - new_song['notecounts'][new.chart] = new.data.get_int('notecount', 5730) + new_song["difficulties"][new.chart] = new.data.get_int("difficulty", 13) + new_song["notecounts"][new.chart] = new.data.get_int("notecount", 5730) return new_song diff --git a/bemani/frontend/jubeat/cache.py b/bemani/frontend/jubeat/cache.py index 8236fed..7ebe1aa 100644 --- a/bemani/frontend/jubeat/cache.py +++ b/bemani/frontend/jubeat/cache.py @@ -6,12 +6,14 @@ from bemani.frontend.jubeat.jubeat import JubeatFrontend class JubeatCache: - @classmethod def preload(cls, data: Data, config: Config) -> None: - cache = Cache(app, config={ - 'CACHE_TYPE': 'filesystem', - 'CACHE_DIR': config.cache_dir, - }) + cache = Cache( + app, + config={ + "CACHE_TYPE": "filesystem", + "CACHE_DIR": config.cache_dir, + }, + ) frontend = JubeatFrontend(data, config, cache) frontend.get_all_songs(force_db_load=True) diff --git a/bemani/frontend/jubeat/endpoints.py b/bemani/frontend/jubeat/endpoints.py index 6e26b90..4b3ac09 100644 --- a/bemani/frontend/jubeat/endpoints.py +++ b/bemani/frontend/jubeat/endpoints.py @@ -12,43 +12,45 @@ from bemani.frontend.static import static_location from bemani.frontend.types import g jubeat_pages = Blueprint( - 'jubeat_pages', + "jubeat_pages", __name__, - url_prefix=f'/{GameConstants.JUBEAT.value}', + url_prefix=f"/{GameConstants.JUBEAT.value}", template_folder=templates_location, static_folder=static_location, ) -@jubeat_pages.route('/scores') +@jubeat_pages.route("/scores") @loginrequired def viewnetworkscores() -> Response: # Only load the last 100 results for the initial fetch, so we can render faster frontend = JubeatFrontend(g.data, g.config, g.cache) network_scores = frontend.get_network_scores(limit=100) - if len(network_scores['attempts']) > 10: - network_scores['attempts'] = frontend.round_to_ten(network_scores['attempts']) + if len(network_scores["attempts"]) > 10: + network_scores["attempts"] = frontend.round_to_ten(network_scores["attempts"]) return render_react( - 'Global Jubeat Scores', - 'jubeat/scores.react.js', + "Global Jubeat Scores", + "jubeat/scores.react.js", { - 'attempts': network_scores['attempts'], - 'songs': frontend.get_all_songs(), - 'players': network_scores['players'], - 'versions': {version: name for (game, version, name) in frontend.sanitized_games()}, - 'shownames': True, - 'shownewrecords': False, + "attempts": network_scores["attempts"], + "songs": frontend.get_all_songs(), + "players": network_scores["players"], + "versions": { + version: name for (game, version, name) in frontend.sanitized_games() + }, + "shownames": True, + "shownewrecords": False, }, { - 'refresh': url_for('jubeat_pages.listnetworkscores'), - 'player': url_for('jubeat_pages.viewplayer', userid=-1), - 'individual_score': url_for('jubeat_pages.viewtopscores', musicid=-1), + "refresh": url_for("jubeat_pages.listnetworkscores"), + "player": url_for("jubeat_pages.viewplayer", userid=-1), + "individual_score": url_for("jubeat_pages.viewtopscores", musicid=-1), }, ) -@jubeat_pages.route('/scores/list') +@jubeat_pages.route("/scores/list") @jsonify @loginrequired def listnetworkscores() -> Dict[str, Any]: @@ -56,7 +58,7 @@ def listnetworkscores() -> Dict[str, Any]: return frontend.get_network_scores() -@jubeat_pages.route('/scores/') +@jubeat_pages.route("/scores/") @loginrequired def viewscores(userid: UserID) -> Response: frontend = JubeatFrontend(g.data, g.config, g.cache) @@ -70,61 +72,65 @@ def viewscores(userid: UserID) -> Response: return render_react( f'{info["name"]}\'s Jubeat Scores', - 'jubeat/scores.react.js', + "jubeat/scores.react.js", { - 'attempts': scores, - 'songs': frontend.get_all_songs(), - 'players': {}, - 'versions': {version: name for (game, version, name) in frontend.sanitized_games()}, - 'shownames': False, - 'shownewrecords': True, + "attempts": scores, + "songs": frontend.get_all_songs(), + "players": {}, + "versions": { + version: name for (game, version, name) in frontend.sanitized_games() + }, + "shownames": False, + "shownewrecords": True, }, { - 'refresh': url_for('jubeat_pages.listscores', userid=userid), - 'player': url_for('jubeat_pages.viewplayer', userid=-1), - 'individual_score': url_for('jubeat_pages.viewtopscores', musicid=-1), + "refresh": url_for("jubeat_pages.listscores", userid=userid), + "player": url_for("jubeat_pages.viewplayer", userid=-1), + "individual_score": url_for("jubeat_pages.viewtopscores", musicid=-1), }, ) -@jubeat_pages.route('/scores//list') +@jubeat_pages.route("/scores//list") @jsonify @loginrequired def listscores(userid: UserID) -> Dict[str, Any]: frontend = JubeatFrontend(g.data, g.config, g.cache) return { - 'attempts': frontend.get_scores(userid), - 'players': {}, + "attempts": frontend.get_scores(userid), + "players": {}, } -@jubeat_pages.route('/records') +@jubeat_pages.route("/records") @loginrequired def viewnetworkrecords() -> Response: frontend = JubeatFrontend(g.data, g.config, g.cache) network_records = frontend.get_network_records() return render_react( - 'Global Jubeat Records', - 'jubeat/records.react.js', + "Global Jubeat Records", + "jubeat/records.react.js", { - 'records': network_records['records'], - 'songs': frontend.get_all_songs(), - 'players': network_records['players'], - 'versions': {version: name for (game, version, name) in frontend.sanitized_games()}, - 'shownames': True, - 'showpersonalsort': False, - 'filterempty': False, + "records": network_records["records"], + "songs": frontend.get_all_songs(), + "players": network_records["players"], + "versions": { + version: name for (game, version, name) in frontend.sanitized_games() + }, + "shownames": True, + "showpersonalsort": False, + "filterempty": False, }, { - 'refresh': url_for('jubeat_pages.listnetworkrecords'), - 'player': url_for('jubeat_pages.viewplayer', userid=-1), - 'individual_score': url_for('jubeat_pages.viewtopscores', musicid=-1), + "refresh": url_for("jubeat_pages.listnetworkrecords"), + "player": url_for("jubeat_pages.viewplayer", userid=-1), + "individual_score": url_for("jubeat_pages.viewtopscores", musicid=-1), }, ) -@jubeat_pages.route('/records/list') +@jubeat_pages.route("/records/list") @jsonify @loginrequired def listnetworkrecords() -> Dict[str, Any]: @@ -132,7 +138,7 @@ def listnetworkrecords() -> Dict[str, Any]: return frontend.get_network_records() -@jubeat_pages.route('/records/') +@jubeat_pages.route("/records/") @loginrequired def viewrecords(userid: UserID) -> Response: frontend = JubeatFrontend(g.data, g.config, g.cache) @@ -142,36 +148,38 @@ def viewrecords(userid: UserID) -> Response: return render_react( f'{info["name"]}\'s Jubeat Records', - 'jubeat/records.react.js', + "jubeat/records.react.js", { - 'records': frontend.get_records(userid), - 'songs': frontend.get_all_songs(), - 'players': {}, - 'versions': {version: name for (game, version, name) in frontend.sanitized_games()}, - 'shownames': False, - 'showpersonalsort': True, - 'filterempty': True, + "records": frontend.get_records(userid), + "songs": frontend.get_all_songs(), + "players": {}, + "versions": { + version: name for (game, version, name) in frontend.sanitized_games() + }, + "shownames": False, + "showpersonalsort": True, + "filterempty": True, }, { - 'refresh': url_for('jubeat_pages.listrecords', userid=userid), - 'player': url_for('jubeat_pages.viewplayer', userid=-1), - 'individual_score': url_for('jubeat_pages.viewtopscores', musicid=-1), + "refresh": url_for("jubeat_pages.listrecords", userid=userid), + "player": url_for("jubeat_pages.viewplayer", userid=-1), + "individual_score": url_for("jubeat_pages.viewtopscores", musicid=-1), }, ) -@jubeat_pages.route('/records//list') +@jubeat_pages.route("/records//list") @jsonify @loginrequired def listrecords(userid: UserID) -> Dict[str, Any]: frontend = JubeatFrontend(g.data, g.config, g.cache) return { - 'records': frontend.get_records(userid), - 'players': {}, + "records": frontend.get_records(userid), + "players": {}, } -@jubeat_pages.route('/topscores/') +@jubeat_pages.route("/topscores/") @loginrequired def viewtopscores(musicid: int) -> Response: # We just want to find the latest mix that this song exists in @@ -188,7 +196,9 @@ def viewtopscores(musicid: int) -> Response: for version in versions: for chart in [0, 1, 2, 3, 4, 5]: - details = g.data.local.music.get_song(GameConstants.JUBEAT, version, musicid, chart) + details = g.data.local.music.get_song( + GameConstants.JUBEAT, version, musicid, chart + ) if details is not None: name = details.name artist = details.artist @@ -196,9 +206,11 @@ def viewtopscores(musicid: int) -> Response: if category < version: category = version if difficulties[chart] == 0.0: - difficulties[chart] = details.data.get_float('difficulty', 13) + difficulties[chart] = details.data.get_float("difficulty", 13) if difficulties[chart] >= 13.0: - difficulties[chart] = float(details.data.get_int('difficulty', 13)) + difficulties[chart] = float( + details.data.get_int("difficulty", 13) + ) if name is None: # Not a real song! @@ -207,25 +219,25 @@ def viewtopscores(musicid: int) -> Response: top_scores = frontend.get_top_scores(musicid) return render_react( - f'Top Jubeat Scores for {artist} - {name}', - 'jubeat/topscores.react.js', + f"Top Jubeat Scores for {artist} - {name}", + "jubeat/topscores.react.js", { - 'name': name, - 'artist': artist, - 'genre': genre, - 'difficulties': difficulties, - 'new_rating': category >= VersionConstants.JUBEAT_FESTO, - 'players': top_scores['players'], - 'topscores': top_scores['topscores'], + "name": name, + "artist": artist, + "genre": genre, + "difficulties": difficulties, + "new_rating": category >= VersionConstants.JUBEAT_FESTO, + "players": top_scores["players"], + "topscores": top_scores["topscores"], }, { - 'refresh': url_for('jubeat_pages.listtopscores', musicid=musicid), - 'player': url_for('jubeat_pages.viewplayer', userid=-1), + "refresh": url_for("jubeat_pages.listtopscores", musicid=musicid), + "player": url_for("jubeat_pages.viewplayer", userid=-1), }, ) -@jubeat_pages.route('/topscores//list') +@jubeat_pages.route("/topscores//list") @jsonify @loginrequired def listtopscores(musicid: int) -> Dict[str, Any]: @@ -233,34 +245,32 @@ def listtopscores(musicid: int) -> Dict[str, Any]: return frontend.get_top_scores(musicid) -@jubeat_pages.route('/players') +@jubeat_pages.route("/players") @loginrequired def viewplayers() -> Response: frontend = JubeatFrontend(g.data, g.config, g.cache) return render_react( - 'All Jubeat Players', - 'jubeat/allplayers.react.js', + "All Jubeat Players", + "jubeat/allplayers.react.js", + {"players": frontend.get_all_players()}, { - 'players': frontend.get_all_players() - }, - { - 'refresh': url_for('jubeat_pages.listplayers'), - 'player': url_for('jubeat_pages.viewplayer', userid=-1), + "refresh": url_for("jubeat_pages.listplayers"), + "player": url_for("jubeat_pages.viewplayer", userid=-1), }, ) -@jubeat_pages.route('/players/list') +@jubeat_pages.route("/players/list") @jsonify @loginrequired def listplayers() -> Dict[str, Any]: frontend = JubeatFrontend(g.data, g.config, g.cache) return { - 'players': frontend.get_all_players(), + "players": frontend.get_all_players(), } -@jubeat_pages.route('/players/') +@jubeat_pages.route("/players/") @loginrequired def viewplayer(userid: UserID) -> Response: frontend = JubeatFrontend(g.data, g.config, g.cache) @@ -271,23 +281,25 @@ def viewplayer(userid: UserID) -> Response: return render_react( f'{info[latest_version]["name"]}\'s Jubeat Profile', - 'jubeat/player.react.js', + "jubeat/player.react.js", { - 'playerid': userid, - 'own_profile': userid == g.userID, - 'player': info, - 'versions': {version: name for (game, version, name) in frontend.all_games()}, + "playerid": userid, + "own_profile": userid == g.userID, + "player": info, + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, }, { - 'refresh': url_for('jubeat_pages.listplayer', userid=userid), - 'records': url_for('jubeat_pages.viewrecords', userid=userid), - 'scores': url_for('jubeat_pages.viewscores', userid=userid), - 'jubility': url_for('jubeat_pages.showjubility', userid=userid), + "refresh": url_for("jubeat_pages.listplayer", userid=userid), + "records": url_for("jubeat_pages.viewrecords", userid=userid), + "scores": url_for("jubeat_pages.viewscores", userid=userid), + "jubility": url_for("jubeat_pages.showjubility", userid=userid), }, ) -@jubeat_pages.route('/players//list') +@jubeat_pages.route("/players//list") @jsonify @loginrequired def listplayer(userid: UserID) -> Dict[str, Any]: @@ -295,11 +307,11 @@ def listplayer(userid: UserID) -> Dict[str, Any]: info = frontend.get_all_player_info([userid])[userid] return { - 'player': info, + "player": info, } -@jubeat_pages.route('/players//jubility') +@jubeat_pages.route("/players//jubility") @loginrequired def showjubility(userid: UserID) -> Response: frontend = JubeatFrontend(g.data, g.config, g.cache) @@ -310,22 +322,24 @@ def showjubility(userid: UserID) -> Response: return render_react( f'{info[latest_version]["name"]}\'s Jubility Breakdown', - 'jubeat/jubility.react.js', + "jubeat/jubility.react.js", { - 'playerid': userid, - 'player': info, - 'songs': frontend.get_all_songs(), - 'versions': {version: name for (game, version, name) in frontend.all_games()}, + "playerid": userid, + "player": info, + "songs": frontend.get_all_songs(), + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, }, { - 'refresh': url_for('jubeat_pages.listplayer', userid=userid), - 'individual_score': url_for('jubeat_pages.viewtopscores', musicid=-1), - 'profile': url_for('jubeat_pages.viewplayer', userid=userid), + "refresh": url_for("jubeat_pages.listplayer", userid=userid), + "individual_score": url_for("jubeat_pages.viewtopscores", musicid=-1), + "profile": url_for("jubeat_pages.viewplayer", userid=userid), }, ) -@jubeat_pages.route('/options') +@jubeat_pages.route("/options") @loginrequired def viewsettings() -> Response: frontend = JubeatFrontend(g.data, g.config, g.cache) @@ -340,112 +354,116 @@ def viewsettings() -> Response: all_emblems = frontend.get_all_items(versions) return render_react( - 'Jubeat Game Settings', - 'jubeat/settings.react.js', + "Jubeat Game Settings", + "jubeat/settings.react.js", { - 'player': info, - 'versions': {version: name for (game, version, name) in frontend.all_games()}, - 'emblems': all_emblems, + "player": info, + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, + "emblems": all_emblems, }, { - 'updatename': url_for('jubeat_pages.updatename'), - 'updateemblem': url_for('jubeat_pages.updateemblem') + "updatename": url_for("jubeat_pages.updatename"), + "updateemblem": url_for("jubeat_pages.updateemblem"), }, ) -@jubeat_pages.route('/options/emblem/update', methods=['POST']) +@jubeat_pages.route("/options/emblem/update", methods=["POST"]) @jsonify @loginrequired def updateemblem() -> Dict[str, Any]: frontend = JubeatFrontend(g.data, g.config, g.cache) - version = int(request.get_json()['version']) - emblem = request.get_json()['emblem'] + version = int(request.get_json()["version"]) + emblem = request.get_json()["emblem"] user = g.data.local.user.get_user(g.userID) if user is None: - raise Exception('Unable to find user to update!') + raise Exception("Unable to find user to update!") # Grab profile and update emblem profile = g.data.local.user.get_profile(GameConstants.JUBEAT, version, user.id) if profile is None: - raise Exception('Unable to find profile to update!') + raise Exception("Unable to find profile to update!") # Making emblem arr for update emblem_arr = [ - emblem['background'], - emblem['main'], - emblem['ornament'], - emblem['effect'], - emblem['speech_bubble'], + emblem["background"], + emblem["main"], + emblem["ornament"], + emblem["effect"], + emblem["speech_bubble"], ] # Grab last dict from profile for updating emblem - last_dict = profile.get_dict('last') - last_dict.replace_int_array('emblem', 5, emblem_arr) + last_dict = profile.get_dict("last") + last_dict.replace_int_array("emblem", 5, emblem_arr) # Replace last dict that replaced int arr - profile.replace_dict('last', last_dict) + profile.replace_dict("last", last_dict) g.data.local.user.put_profile(GameConstants.JUBEAT, version, user.id, profile) return { - 'version': version, - 'emblem': frontend.format_emblem(emblem_arr), + "version": version, + "emblem": frontend.format_emblem(emblem_arr), } -@jubeat_pages.route('/options/name/update', methods=['POST']) +@jubeat_pages.route("/options/name/update", methods=["POST"]) @jsonify @loginrequired def updatename() -> Dict[str, Any]: - version = int(request.get_json()['version']) - name = request.get_json()['name'] + version = int(request.get_json()["version"]) + name = request.get_json()["name"] user = g.data.local.user.get_user(g.userID) if user is None: - raise Exception('Unable to find user to update!') + raise Exception("Unable to find user to update!") # Grab profile and update name profile = g.data.local.user.get_profile(GameConstants.JUBEAT, version, user.id) if profile is None: - raise Exception('Unable to find profile to update!') + raise Exception("Unable to find profile to update!") if len(name) == 0 or len(name) > 8: - raise Exception('Invalid profile name!') - if re.match(r'^[ -&\.\*A-Z0-9]*$', name) is None: - raise Exception('Invalid profile name!') - profile.replace_str('name', name) + raise Exception("Invalid profile name!") + if re.match(r"^[ -&\.\*A-Z0-9]*$", name) is None: + raise Exception("Invalid profile name!") + profile.replace_str("name", name) g.data.local.user.put_profile(GameConstants.JUBEAT, version, user.id, profile) # Return that we updated return { - 'version': version, - 'name': name, + "version": version, + "name": name, } -@jubeat_pages.route('/rivals') +@jubeat_pages.route("/rivals") @loginrequired def viewrivals() -> Response: frontend = JubeatFrontend(g.data, g.config, g.cache) rivals, playerinfo = frontend.get_rivals(g.userID) return render_react( - 'Jubeat Rivals', - 'jubeat/rivals.react.js', + "Jubeat Rivals", + "jubeat/rivals.react.js", { - 'userid': str(g.userID), - 'rivals': rivals, - 'players': playerinfo, - 'versions': {version: name for (game, version, name) in frontend.all_games()}, + "userid": str(g.userID), + "rivals": rivals, + "players": playerinfo, + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, }, { - 'refresh': url_for('jubeat_pages.listrivals'), - 'search': url_for('jubeat_pages.searchrivals'), - 'player': url_for('jubeat_pages.viewplayer', userid=-1), - 'addrival': url_for('jubeat_pages.addrival'), - 'removerival': url_for('jubeat_pages.removerival'), + "refresh": url_for("jubeat_pages.listrivals"), + "search": url_for("jubeat_pages.searchrivals"), + "player": url_for("jubeat_pages.viewplayer", userid=-1), + "addrival": url_for("jubeat_pages.addrival"), + "removerival": url_for("jubeat_pages.removerival"), }, ) -@jubeat_pages.route('/rivals/list') +@jubeat_pages.route("/rivals/list") @jsonify @loginrequired def listrivals() -> Dict[str, Any]: @@ -453,18 +471,18 @@ def listrivals() -> Dict[str, Any]: rivals, playerinfo = frontend.get_rivals(g.userID) return { - 'rivals': rivals, - 'players': playerinfo, + "rivals": rivals, + "players": playerinfo, } -@jubeat_pages.route('/rivals/search', methods=['POST']) +@jubeat_pages.route("/rivals/search", methods=["POST"]) @jsonify @loginrequired def searchrivals() -> Dict[str, Any]: frontend = JubeatFrontend(g.data, g.config, g.cache) - version = int(request.get_json()['version']) - name = request.get_json()['term'] + version = int(request.get_json()["version"]) + name = request.get_json()["term"] # Try to treat the term as an extid extid = ID.parse_extid(name) @@ -472,34 +490,36 @@ def searchrivals() -> Dict[str, Any]: matches = set() profiles = g.data.remote.user.get_all_profiles(GameConstants.JUBEAT, version) for (userid, profile) in profiles: - if profile.extid == extid or profile.get_str('name').lower() == name.lower(): + if profile.extid == extid or profile.get_str("name").lower() == name.lower(): matches.add(userid) playerinfo = frontend.get_all_player_info(list(matches), allow_remote=True) return { - 'results': playerinfo, + "results": playerinfo, } -@jubeat_pages.route('/rivals/add', methods=['POST']) +@jubeat_pages.route("/rivals/add", methods=["POST"]) @jsonify @loginrequired def addrival() -> Dict[str, Any]: frontend = JubeatFrontend(g.data, g.config, g.cache) - version = int(request.get_json()['version']) - other_userid = UserID(int(request.get_json()['userid'])) + version = int(request.get_json()["version"]) + other_userid = UserID(int(request.get_json()["userid"])) userid = g.userID # Add this rival link - profile = g.data.remote.user.get_profile(GameConstants.JUBEAT, version, other_userid) + profile = g.data.remote.user.get_profile( + GameConstants.JUBEAT, version, other_userid + ) if profile is None: - raise Exception('Unable to find profile for rival!') + raise Exception("Unable to find profile for rival!") g.data.local.user.put_link( GameConstants.JUBEAT, version, userid, - 'rival', + "rival", other_userid, {}, ) @@ -508,18 +528,18 @@ def addrival() -> Dict[str, Any]: rivals, playerinfo = frontend.get_rivals(userid) return { - 'rivals': rivals, - 'players': playerinfo, + "rivals": rivals, + "players": playerinfo, } -@jubeat_pages.route('/rivals/remove', methods=['POST']) +@jubeat_pages.route("/rivals/remove", methods=["POST"]) @jsonify @loginrequired def removerival() -> Dict[str, Any]: frontend = JubeatFrontend(g.data, g.config, g.cache) - version = int(request.get_json()['version']) - other_userid = UserID(int(request.get_json()['userid'])) + version = int(request.get_json()["version"]) + other_userid = UserID(int(request.get_json()["userid"])) userid = g.userID # Remove this rival link @@ -527,7 +547,7 @@ def removerival() -> Dict[str, Any]: GameConstants.JUBEAT, version, userid, - 'rival', + "rival", other_userid, ) @@ -535,6 +555,6 @@ def removerival() -> Dict[str, Any]: rivals, playerinfo = frontend.get_rivals(userid) return { - 'rivals': rivals, - 'players': playerinfo, + "rivals": rivals, + "players": playerinfo, } diff --git a/bemani/frontend/jubeat/jubeat.py b/bemani/frontend/jubeat/jubeat.py index 3af48a1..0420f45 100644 --- a/bemani/frontend/jubeat/jubeat.py +++ b/bemani/frontend/jubeat/jubeat.py @@ -20,7 +20,7 @@ class JubeatFrontend(FrontendBase): JubeatBase.CHART_TYPE_HARD_EXTREME, ] - valid_rival_types: List[str] = ['rival'] + valid_rival_types: List[str] = ["rival"] def all_games(self) -> Iterator[Tuple[GameConstants, int, str]]: yield from JubeatFactory.all_games() @@ -63,7 +63,7 @@ class JubeatFrontend(FrontendBase): result = {} for version in versions: emblem = self.__format_jubeat_extras(version) - result[version] = emblem['emblems'] + result[version] = emblem["emblems"] return result def __format_jubeat_extras(self, version: int) -> Dict[str, List[Dict[str, Any]]]: @@ -96,87 +96,104 @@ class JubeatFrontend(FrontendBase): def format_score(self, userid: UserID, score: Score) -> Dict[str, Any]: formatted_score = super().format_score(userid, score) - formatted_score['combo'] = score.data.get_int('combo', -1) - formatted_score['music_rate'] = score.data.get_int('music_rate', -1) - if formatted_score['music_rate'] >= 0: - formatted_score['music_rate'] /= 10 - formatted_score['medal'] = score.data.get_int('medal') - formatted_score['status'] = { + formatted_score["combo"] = score.data.get_int("combo", -1) + formatted_score["music_rate"] = score.data.get_int("music_rate", -1) + if formatted_score["music_rate"] >= 0: + formatted_score["music_rate"] /= 10 + formatted_score["medal"] = score.data.get_int("medal") + formatted_score["status"] = { JubeatBase.PLAY_MEDAL_FAILED: "FAILED", JubeatBase.PLAY_MEDAL_CLEARED: "CLEARED", JubeatBase.PLAY_MEDAL_NEARLY_FULL_COMBO: "NEARLY FULL COMBO", JubeatBase.PLAY_MEDAL_FULL_COMBO: "FULL COMBO", JubeatBase.PLAY_MEDAL_NEARLY_EXCELLENT: "NEARLY EXCELLENT", JubeatBase.PLAY_MEDAL_EXCELLENT: "EXCELLENT", - }.get(score.data.get_int('medal'), 'NO PLAY') - formatted_score['clear_cnt'] = score.data.get_int('clear_count', 0) - formatted_score['stats'] = score.data.get_dict('stats') + }.get(score.data.get_int("medal"), "NO PLAY") + formatted_score["clear_cnt"] = score.data.get_int("clear_count", 0) + formatted_score["stats"] = score.data.get_dict("stats") return formatted_score def format_attempt(self, userid: UserID, attempt: Attempt) -> Dict[str, Any]: formatted_attempt = super().format_attempt(userid, attempt) - formatted_attempt['combo'] = attempt.data.get_int('combo', -1) - formatted_attempt['medal'] = attempt.data.get_int('medal') - formatted_attempt['music_rate'] = attempt.data.get_int('music_rate', -1) - if formatted_attempt['music_rate'] >= 0: - formatted_attempt['music_rate'] /= 10 - formatted_attempt['status'] = { + formatted_attempt["combo"] = attempt.data.get_int("combo", -1) + formatted_attempt["medal"] = attempt.data.get_int("medal") + formatted_attempt["music_rate"] = attempt.data.get_int("music_rate", -1) + if formatted_attempt["music_rate"] >= 0: + formatted_attempt["music_rate"] /= 10 + formatted_attempt["status"] = { JubeatBase.PLAY_MEDAL_FAILED: "FAILED", JubeatBase.PLAY_MEDAL_CLEARED: "CLEARED", JubeatBase.PLAY_MEDAL_NEARLY_FULL_COMBO: "NEARLY FULL COMBO", JubeatBase.PLAY_MEDAL_FULL_COMBO: "FULL COMBO", JubeatBase.PLAY_MEDAL_NEARLY_EXCELLENT: "NEARLY EXCELLENT", JubeatBase.PLAY_MEDAL_EXCELLENT: "EXCELLENT", - }.get(attempt.data.get_int('medal'), 'NO PLAY') - formatted_attempt['stats'] = attempt.data.get_dict('stats') + }.get(attempt.data.get_int("medal"), "NO PLAY") + formatted_attempt["stats"] = attempt.data.get_dict("stats") return formatted_attempt def format_emblem(self, emblem: list) -> Dict[str, Any]: return { - 'background': emblem[0], - 'main': emblem[1], - 'ornament': emblem[2], - 'effect': emblem[3], - 'speech_bubble': emblem[4], + "background": emblem[0], + "main": emblem[1], + "ornament": emblem[2], + "effect": emblem[3], + "speech_bubble": emblem[4], } - def format_profile(self, profile: Profile, playstats: ValidatedDict) -> Dict[str, Any]: + def format_profile( + self, profile: Profile, playstats: ValidatedDict + ) -> Dict[str, Any]: formatted_profile = super().format_profile(profile, playstats) - formatted_profile['plays'] = playstats.get_int('total_plays') - formatted_profile['emblem'] = self.format_emblem(profile.get_dict('last').get_int_array('emblem', 5)) - formatted_profile['jubility'] = ( - profile.get_int('jubility') - if profile.version not in {VersionConstants.JUBEAT_PROP, VersionConstants.JUBEAT_QUBELL, VersionConstants.JUBEAT_FESTO} + formatted_profile["plays"] = playstats.get_int("total_plays") + formatted_profile["emblem"] = self.format_emblem( + profile.get_dict("last").get_int_array("emblem", 5) + ) + formatted_profile["jubility"] = ( + profile.get_int("jubility") + if profile.version + not in { + VersionConstants.JUBEAT_PROP, + VersionConstants.JUBEAT_QUBELL, + VersionConstants.JUBEAT_FESTO, + } else 0 ) - formatted_profile['pick_up_jubility'] = ( - profile.get_float('pick_up_jubility') + formatted_profile["pick_up_jubility"] = ( + profile.get_float("pick_up_jubility") if profile.version == VersionConstants.JUBEAT_FESTO else 0 ) - formatted_profile['common_jubility'] = ( - profile.get_float('common_jubility') + formatted_profile["common_jubility"] = ( + profile.get_float("common_jubility") if profile.version == VersionConstants.JUBEAT_FESTO else 0 ) if profile.version == VersionConstants.JUBEAT_FESTO: # Only reason this is a dictionary of dictionaries is because ValidatedDict doesn't support a list of dictionaries. # Probably intentionally lol. Just listify the pickup/common charts. - formatted_profile['pick_up_chart'] = list(profile.get_dict('pick_up_chart').values()) - formatted_profile['common_chart'] = list(profile.get_dict('common_chart').values()) + formatted_profile["pick_up_chart"] = list( + profile.get_dict("pick_up_chart").values() + ) + formatted_profile["common_chart"] = list( + profile.get_dict("common_chart").values() + ) elif profile.version == VersionConstants.JUBEAT_CLAN: # Look up achievements which is where jubility was stored. This is a bit of a hack # due to the fact that this could be formatting remote profiles, but then they should # have no achievements. - userid = self.data.local.user.from_refid(profile.game, profile.version, profile.refid) + userid = self.data.local.user.from_refid( + profile.game, profile.version, profile.refid + ) if userid is not None: - achievements = self.data.local.user.get_achievements(profile.game, profile.version, userid) + achievements = self.data.local.user.get_achievements( + profile.game, profile.version, userid + ) else: achievements = [] jubeat_entries: List[ValidatedDict] = [] for achievement in achievements: - if achievement.type != 'jubility': + if achievement.type != "jubility": continue # Figure out for each song, what's the highest value jubility and @@ -189,31 +206,33 @@ class JubeatFrontend(FrontendBase): bestentry.replace_int("songid", achievement.id) bestentry.replace_int("chart", chart) jubeat_entries.append(bestentry) - jubeat_entries = sorted(jubeat_entries, key=lambda entry: entry.get_int("value"), reverse=True)[:30] - formatted_profile['chart'] = jubeat_entries + jubeat_entries = sorted( + jubeat_entries, key=lambda entry: entry.get_int("value"), reverse=True + )[:30] + formatted_profile["chart"] = jubeat_entries - formatted_profile['ex_count'] = profile.get_int('ex_cnt') - formatted_profile['fc_count'] = profile.get_int('fc_cnt') + formatted_profile["ex_count"] = profile.get_int("ex_cnt") + formatted_profile["fc_count"] = profile.get_int("fc_cnt") return formatted_profile def format_song(self, song: Song) -> Dict[str, Any]: difficulties = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] - difficulties[song.chart] = song.data.get_float('difficulty', 13) + difficulties[song.chart] = song.data.get_float("difficulty", 13) if difficulties[song.chart] == 13.0: - difficulties[song.chart] = float(song.data.get_int('difficulty', 13)) + difficulties[song.chart] = float(song.data.get_int("difficulty", 13)) formatted_song = super().format_song(song) - formatted_song['bpm_min'] = song.data.get_int('bpm_min', 120) - formatted_song['bpm_max'] = song.data.get_int('bpm_max', 120) - formatted_song['difficulties'] = difficulties - version = song.data.get_int('version', 0) + formatted_song["bpm_min"] = song.data.get_int("bpm_min", 120) + formatted_song["bpm_max"] = song.data.get_int("bpm_max", 120) + formatted_song["difficulties"] = difficulties + version = song.data.get_int("version", 0) if version == 0: # The default here is a nasty hack for installations that existed prior to importing # version using read.py. This ensures that not importing again won't break existing # installations. - formatted_song['version'] = int(song.id / 10000000) + formatted_song["version"] = int(song.id / 10000000) else: - formatted_song['version'] = { + formatted_song["version"] = { VersionConstants.JUBEAT: 1, VersionConstants.JUBEAT_RIPPLES: 2, VersionConstants.JUBEAT_RIPPLES_APPEND: 2, @@ -232,8 +251,10 @@ class JubeatFrontend(FrontendBase): def merge_song(self, existing: Dict[str, Any], new: Song) -> Dict[str, Any]: new_song = super().merge_song(existing, new) - if existing['difficulties'][new.chart] == 0.0: - new_song['difficulties'][new.chart] = new.data.get_float('difficulty', 13) - if new_song['difficulties'][new.chart] == 13.0: - new_song['difficulties'][new.chart] = float(new.data.get_int('difficulty', 13)) + if existing["difficulties"][new.chart] == 0.0: + new_song["difficulties"][new.chart] = new.data.get_float("difficulty", 13) + if new_song["difficulties"][new.chart] == 13.0: + new_song["difficulties"][new.chart] = float( + new.data.get_int("difficulty", 13) + ) return new_song diff --git a/bemani/frontend/mga/cache.py b/bemani/frontend/mga/cache.py index 42fb6a2..d6aa747 100644 --- a/bemani/frontend/mga/cache.py +++ b/bemani/frontend/mga/cache.py @@ -2,7 +2,6 @@ from bemani.data import Config, Data class MetalGearArcadeCache: - @classmethod def preload(cls, data: Data, config: Config) -> None: pass diff --git a/bemani/frontend/mga/endpoints.py b/bemani/frontend/mga/endpoints.py index 48b0dca..8e993dc 100644 --- a/bemani/frontend/mga/endpoints.py +++ b/bemani/frontend/mga/endpoints.py @@ -13,42 +13,40 @@ from bemani.frontend.types import g mga_pages = Blueprint( - 'mga_pages', + "mga_pages", __name__, - url_prefix=f'/{GameConstants.MGA.value}', + url_prefix=f"/{GameConstants.MGA.value}", template_folder=templates_location, static_folder=static_location, ) -@mga_pages.route('/players') +@mga_pages.route("/players") @loginrequired def viewplayers() -> Response: frontend = MetalGearArcadeFrontend(g.data, g.config, g.cache) return render_react( - 'All MGA Players', - 'mga/allplayers.react.js', + "All MGA Players", + "mga/allplayers.react.js", + {"players": frontend.get_all_players()}, { - 'players': frontend.get_all_players() - }, - { - 'refresh': url_for('mga_pages.listplayers'), - 'player': url_for('mga_pages.viewplayer', userid=-1), + "refresh": url_for("mga_pages.listplayers"), + "player": url_for("mga_pages.viewplayer", userid=-1), }, ) -@mga_pages.route('/players/list') +@mga_pages.route("/players/list") @jsonify @loginrequired def listplayers() -> Dict[str, Any]: frontend = MetalGearArcadeFrontend(g.data, g.config, g.cache) return { - 'players': frontend.get_all_players(), + "players": frontend.get_all_players(), } -@mga_pages.route('/players/') +@mga_pages.route("/players/") @loginrequired def viewplayer(userid: UserID) -> Response: frontend = MetalGearArcadeFrontend(g.data, g.config, g.cache) @@ -59,20 +57,22 @@ def viewplayer(userid: UserID) -> Response: return render_react( f'{djinfo[latest_version]["name"]}\'s MGA Profile', - 'mga/player.react.js', + "mga/player.react.js", { - 'playerid': userid, - 'own_profile': userid == g.userID, - 'player': djinfo, - 'versions': {version: name for (game, version, name) in frontend.all_games()}, + "playerid": userid, + "own_profile": userid == g.userID, + "player": djinfo, + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, }, { - 'refresh': url_for('mga_pages.listplayer', userid=userid), + "refresh": url_for("mga_pages.listplayer", userid=userid), }, ) -@mga_pages.route('/players//list') +@mga_pages.route("/players//list") @jsonify @loginrequired def listplayer(userid: UserID) -> Dict[str, Any]: @@ -80,11 +80,11 @@ def listplayer(userid: UserID) -> Dict[str, Any]: djinfo = frontend.get_all_player_info([userid])[userid] return { - 'player': djinfo, + "player": djinfo, } -@mga_pages.route('/options') +@mga_pages.route("/options") @loginrequired def viewsettings() -> Response: frontend = MetalGearArcadeFrontend(g.data, g.config, g.cache) @@ -94,51 +94,51 @@ def viewsettings() -> Response: abort(404) return render_react( - 'Metal Gear Arcade Game Settings', - 'mga/settings.react.js', + "Metal Gear Arcade Game Settings", + "mga/settings.react.js", { - 'player': djinfo, - 'versions': {version: name for (game, version, name) in frontend.all_games()}, + "player": djinfo, + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, }, { - 'updatename': url_for('mga_pages.updatename'), + "updatename": url_for("mga_pages.updatename"), }, ) -@mga_pages.route('/options/name/update', methods=['POST']) +@mga_pages.route("/options/name/update", methods=["POST"]) @jsonify @loginrequired def updatename() -> Dict[str, Any]: frontend = MetalGearArcadeFrontend(g.data, g.config, g.cache) - version = int(request.get_json()['version']) - name = request.get_json()['name'] + version = int(request.get_json()["version"]) + name = request.get_json()["name"] user = g.data.local.user.get_user(g.userID) if user is None: - raise Exception('Unable to find user to update!') + raise Exception("Unable to find user to update!") # Grab profile and update dj name profile = g.data.local.user.get_profile(GameConstants.MGA, version, user.id) if profile is None: - raise Exception('Unable to find profile to update!') + raise Exception("Unable to find profile to update!") if len(name) == 0 or len(name) > 8: - raise Exception('Invalid profile name!') + raise Exception("Invalid profile name!") - if re.match( - "^[" + - "a-z" + - "A-Z" + - "0-9" + - "@!?/=():*^[\\]#;\\-_{}$.+" + - "]*$", - name, - ) is None: - raise Exception('Invalid profile name!') + if ( + re.match( + "^[" + "a-z" + "A-Z" + "0-9" + "@!?/=():*^[\\]#;\\-_{}$.+" + "]*$", + name, + ) + is None + ): + raise Exception("Invalid profile name!") profile = frontend.update_name(profile, name) g.data.local.user.put_profile(GameConstants.MGA, version, user.id, profile) # Return that we updated return { - 'version': version, - 'name': frontend.sanitize_name(name), + "version": version, + "name": frontend.sanitize_name(name), } diff --git a/bemani/frontend/mga/mga.py b/bemani/frontend/mga/mga.py index 202a7cb..9e7cccf 100644 --- a/bemani/frontend/mga/mga.py +++ b/bemani/frontend/mga/mga.py @@ -23,9 +23,9 @@ class MetalGearArcadeFrontend(FrontendBase): def __update_value(self, oldvalue: str, newvalue: bytes) -> str: try: - newstr = newvalue.decode('shift-jis') + newstr = newvalue.decode("shift-jis") except Exception: - newstr = '' + newstr = "" if len(newstr) == 0: return oldvalue else: @@ -33,43 +33,45 @@ class MetalGearArcadeFrontend(FrontendBase): def sanitize_name(self, name: str) -> str: if len(name) == 0: - return 'なし' + return "なし" return name def update_name(self, profile: Profile, name: str) -> Profile: newprofile = copy.deepcopy(profile) - for i in range(len(newprofile['strdatas'])): - strdata = newprofile['strdatas'][i] + for i in range(len(newprofile["strdatas"])): + strdata = newprofile["strdatas"][i] # Figure out the profile type - csvs = strdata.split(b',') + csvs = strdata.split(b",") if len(csvs) < 2: # Not long enough to care about continue - datatype = csvs[1].decode('ascii') - if datatype != 'PLAYDATA': + datatype = csvs[1].decode("ascii") + if datatype != "PLAYDATA": # Not the right profile type requested continue - csvs[27] = name.encode('shift-jis') - newprofile['strdatas'][i] = b','.join(csvs) + csvs[27] = name.encode("shift-jis") + newprofile["strdatas"][i] = b",".join(csvs) return newprofile - def format_profile(self, profile: Profile, playstats: ValidatedDict) -> Dict[str, Any]: - name = 'なし' # Nothing - shop = '未設定' # Not set - shop_area = '未設定' # Not set + def format_profile( + self, profile: Profile, playstats: ValidatedDict + ) -> Dict[str, Any]: + name = "なし" # Nothing + shop = "未設定" # Not set + shop_area = "未設定" # Not set - for i in range(len(profile['strdatas'])): - strdata = profile['strdatas'][i] + for i in range(len(profile["strdatas"])): + strdata = profile["strdatas"][i] # Figure out the profile type - csvs = strdata.split(b',') + csvs = strdata.split(b",") if len(csvs) < 2: # Not long enough to care about continue - datatype = csvs[1].decode('ascii') - if datatype != 'PLAYDATA': + datatype = csvs[1].decode("ascii") + if datatype != "PLAYDATA": # Not the right profile type requested continue @@ -78,11 +80,11 @@ class MetalGearArcadeFrontend(FrontendBase): shop_area = self.__update_value(shop_area, csvs[31]) return { - 'name': name, - 'extid': ID.format_extid(profile.extid), - 'shop': shop, - 'shop_area': shop_area, - 'first_play_time': playstats.get_int('first_play_timestamp'), - 'last_play_time': playstats.get_int('last_play_timestamp'), - 'plays': playstats.get_int('total_plays'), + "name": name, + "extid": ID.format_extid(profile.extid), + "shop": shop, + "shop_area": shop_area, + "first_play_time": playstats.get_int("first_play_timestamp"), + "last_play_time": playstats.get_int("last_play_timestamp"), + "plays": playstats.get_int("total_plays"), } diff --git a/bemani/frontend/museca/cache.py b/bemani/frontend/museca/cache.py index 633133e..c6e6518 100644 --- a/bemani/frontend/museca/cache.py +++ b/bemani/frontend/museca/cache.py @@ -6,12 +6,14 @@ from bemani.frontend.museca.museca import MusecaFrontend class MusecaCache: - @classmethod def preload(cls, data: Data, config: Config) -> None: - cache = Cache(app, config={ - 'CACHE_TYPE': 'filesystem', - 'CACHE_DIR': config.cache_dir, - }) + cache = Cache( + app, + config={ + "CACHE_TYPE": "filesystem", + "CACHE_DIR": config.cache_dir, + }, + ) frontend = MusecaFrontend(data, config, cache) frontend.get_all_songs(force_db_load=True) diff --git a/bemani/frontend/museca/endpoints.py b/bemani/frontend/museca/endpoints.py index 938441e..41c531e 100644 --- a/bemani/frontend/museca/endpoints.py +++ b/bemani/frontend/museca/endpoints.py @@ -13,42 +13,42 @@ from bemani.frontend.types import g museca_pages = Blueprint( - 'museca_pages', + "museca_pages", __name__, - url_prefix=f'/{GameConstants.MUSECA.value}', + url_prefix=f"/{GameConstants.MUSECA.value}", template_folder=templates_location, static_folder=static_location, ) -@museca_pages.route('/scores') +@museca_pages.route("/scores") @loginrequired def viewnetworkscores() -> Response: # Only load the last 100 results for the initial fetch, so we can render faster frontend = MusecaFrontend(g.data, g.config, g.cache) network_scores = frontend.get_network_scores(limit=100) - if len(network_scores['attempts']) > 10: - network_scores['attempts'] = frontend.round_to_ten(network_scores['attempts']) + if len(network_scores["attempts"]) > 10: + network_scores["attempts"] = frontend.round_to_ten(network_scores["attempts"]) return render_react( - 'Global MÚSECA Scores', - 'museca/scores.react.js', + "Global MÚSECA Scores", + "museca/scores.react.js", { - 'attempts': network_scores['attempts'], - 'songs': frontend.get_all_songs(), - 'players': network_scores['players'], - 'shownames': True, - 'shownewrecords': False, + "attempts": network_scores["attempts"], + "songs": frontend.get_all_songs(), + "players": network_scores["players"], + "shownames": True, + "shownewrecords": False, }, { - 'refresh': url_for('museca_pages.listnetworkscores'), - 'player': url_for('museca_pages.viewplayer', userid=-1), - 'individual_score': url_for('museca_pages.viewtopscores', musicid=-1), + "refresh": url_for("museca_pages.listnetworkscores"), + "player": url_for("museca_pages.viewplayer", userid=-1), + "individual_score": url_for("museca_pages.viewtopscores", musicid=-1), }, ) -@museca_pages.route('/scores/list') +@museca_pages.route("/scores/list") @jsonify @loginrequired def listnetworkscores() -> Dict[str, Any]: @@ -56,7 +56,7 @@ def listnetworkscores() -> Dict[str, Any]: return frontend.get_network_scores() -@museca_pages.route('/scores/') +@museca_pages.route("/scores/") @loginrequired def viewscores(userid: UserID) -> Response: frontend = MusecaFrontend(g.data, g.config, g.cache) @@ -70,34 +70,34 @@ def viewscores(userid: UserID) -> Response: return render_react( f'{info["name"]}\'s MÚSECA Scores', - 'museca/scores.react.js', + "museca/scores.react.js", { - 'attempts': scores, - 'songs': frontend.get_all_songs(), - 'players': {}, - 'shownames': False, - 'shownewrecords': True, + "attempts": scores, + "songs": frontend.get_all_songs(), + "players": {}, + "shownames": False, + "shownewrecords": True, }, { - 'refresh': url_for('museca_pages.listscores', userid=userid), - 'player': url_for('museca_pages.viewplayer', userid=-1), - 'individual_score': url_for('museca_pages.viewtopscores', musicid=-1), + "refresh": url_for("museca_pages.listscores", userid=userid), + "player": url_for("museca_pages.viewplayer", userid=-1), + "individual_score": url_for("museca_pages.viewtopscores", musicid=-1), }, ) -@museca_pages.route('/scores//list') +@museca_pages.route("/scores//list") @jsonify @loginrequired def listscores(userid: UserID) -> Dict[str, Any]: frontend = MusecaFrontend(g.data, g.config, g.cache) return { - 'attempts': frontend.get_scores(userid), - 'players': {}, + "attempts": frontend.get_scores(userid), + "players": {}, } -@museca_pages.route('/records') +@museca_pages.route("/records") @loginrequired def viewnetworkrecords() -> Response: frontend = MusecaFrontend(g.data, g.config, g.cache) @@ -105,26 +105,26 @@ def viewnetworkrecords() -> Response: versions = {version: name for (game, version, name) in frontend.all_games()} return render_react( - 'Global MÚSECA Records', - 'museca/records.react.js', + "Global MÚSECA Records", + "museca/records.react.js", { - 'records': network_records['records'], - 'songs': frontend.get_all_songs(), - 'players': network_records['players'], - 'versions': versions, - 'shownames': True, - 'showpersonalsort': False, - 'filterempty': False, + "records": network_records["records"], + "songs": frontend.get_all_songs(), + "players": network_records["players"], + "versions": versions, + "shownames": True, + "showpersonalsort": False, + "filterempty": False, }, { - 'refresh': url_for('museca_pages.listnetworkrecords'), - 'player': url_for('museca_pages.viewplayer', userid=-1), - 'individual_score': url_for('museca_pages.viewtopscores', musicid=-1), + "refresh": url_for("museca_pages.listnetworkrecords"), + "player": url_for("museca_pages.viewplayer", userid=-1), + "individual_score": url_for("museca_pages.viewtopscores", musicid=-1), }, ) -@museca_pages.route('/records/list') +@museca_pages.route("/records/list") @jsonify @loginrequired def listnetworkrecords() -> Dict[str, Any]: @@ -132,7 +132,7 @@ def listnetworkrecords() -> Dict[str, Any]: return frontend.get_network_records() -@museca_pages.route('/records/') +@museca_pages.route("/records/") @loginrequired def viewrecords(userid: UserID) -> Response: frontend = MusecaFrontend(g.data, g.config, g.cache) @@ -143,36 +143,36 @@ def viewrecords(userid: UserID) -> Response: return render_react( f'{info["name"]}\'s MÚSECA Records', - 'museca/records.react.js', + "museca/records.react.js", { - 'records': frontend.get_records(userid), - 'songs': frontend.get_all_songs(), - 'players': {}, - 'versions': versions, - 'shownames': False, - 'showpersonalsort': True, - 'filterempty': True, + "records": frontend.get_records(userid), + "songs": frontend.get_all_songs(), + "players": {}, + "versions": versions, + "shownames": False, + "showpersonalsort": True, + "filterempty": True, }, { - 'refresh': url_for('museca_pages.listrecords', userid=userid), - 'player': url_for('museca_pages.viewplayer', userid=-1), - 'individual_score': url_for('museca_pages.viewtopscores', musicid=-1), + "refresh": url_for("museca_pages.listrecords", userid=userid), + "player": url_for("museca_pages.viewplayer", userid=-1), + "individual_score": url_for("museca_pages.viewtopscores", musicid=-1), }, ) -@museca_pages.route('/records//list') +@museca_pages.route("/records//list") @jsonify @loginrequired def listrecords(userid: UserID) -> Dict[str, Any]: frontend = MusecaFrontend(g.data, g.config, g.cache) return { - 'records': frontend.get_records(userid), - 'players': {}, + "records": frontend.get_records(userid), + "players": {}, } -@museca_pages.route('/topscores/') +@museca_pages.route("/topscores/") @loginrequired def viewtopscores(musicid: int) -> Response: # We just want to find the latest mix that this song exists in @@ -188,14 +188,16 @@ def viewtopscores(musicid: int) -> Response: for version in versions: for omniadd in [0, 10000]: for chart in [0, 1, 2, 3, 4]: - details = g.data.local.music.get_song(GameConstants.MUSECA, version + omniadd, musicid, chart) + details = g.data.local.music.get_song( + GameConstants.MUSECA, version + omniadd, musicid, chart + ) if details is not None: if name is None: name = details.name if artist is None: artist = details.artist if difficulties[chart] == 0: - difficulties[chart] = details.data.get_int('difficulty') + difficulties[chart] = details.data.get_int("difficulty") if name is None: # Not a real song! @@ -204,23 +206,23 @@ def viewtopscores(musicid: int) -> Response: top_scores = frontend.get_top_scores(musicid) return render_react( - f'Top MÚSECA Scores for {artist} - {name}', - 'museca/topscores.react.js', + f"Top MÚSECA Scores for {artist} - {name}", + "museca/topscores.react.js", { - 'name': name, - 'artist': artist, - 'difficulties': difficulties, - 'players': top_scores['players'], - 'topscores': top_scores['topscores'], + "name": name, + "artist": artist, + "difficulties": difficulties, + "players": top_scores["players"], + "topscores": top_scores["topscores"], }, { - 'refresh': url_for('museca_pages.listtopscores', musicid=musicid), - 'player': url_for('museca_pages.viewplayer', userid=-1), + "refresh": url_for("museca_pages.listtopscores", musicid=musicid), + "player": url_for("museca_pages.viewplayer", userid=-1), }, ) -@museca_pages.route('/topscores//list') +@museca_pages.route("/topscores//list") @jsonify @loginrequired def listtopscores(musicid: int) -> Dict[str, Any]: @@ -228,34 +230,32 @@ def listtopscores(musicid: int) -> Dict[str, Any]: return frontend.get_top_scores(musicid) -@museca_pages.route('/players') +@museca_pages.route("/players") @loginrequired def viewplayers() -> Response: frontend = MusecaFrontend(g.data, g.config, g.cache) return render_react( - 'All MÚSECA Players', - 'museca/allplayers.react.js', + "All MÚSECA Players", + "museca/allplayers.react.js", + {"players": frontend.get_all_players()}, { - 'players': frontend.get_all_players() - }, - { - 'refresh': url_for('museca_pages.listplayers'), - 'player': url_for('museca_pages.viewplayer', userid=-1), + "refresh": url_for("museca_pages.listplayers"), + "player": url_for("museca_pages.viewplayer", userid=-1), }, ) -@museca_pages.route('/players/list') +@museca_pages.route("/players/list") @jsonify @loginrequired def listplayers() -> Dict[str, Any]: frontend = MusecaFrontend(g.data, g.config, g.cache) return { - 'players': frontend.get_all_players(), + "players": frontend.get_all_players(), } -@museca_pages.route('/players/') +@museca_pages.route("/players/") @loginrequired def viewplayer(userid: UserID) -> Response: frontend = MusecaFrontend(g.data, g.config, g.cache) @@ -266,22 +266,24 @@ def viewplayer(userid: UserID) -> Response: return render_react( f'{info[latest_version]["name"]}\'s MÚSECA Profile', - 'museca/player.react.js', + "museca/player.react.js", { - 'playerid': userid, - 'own_profile': userid == g.userID, - 'player': info, - 'versions': {version: name for (game, version, name) in frontend.all_games()}, + "playerid": userid, + "own_profile": userid == g.userID, + "player": info, + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, }, { - 'refresh': url_for('museca_pages.listplayer', userid=userid), - 'records': url_for('museca_pages.viewrecords', userid=userid), - 'scores': url_for('museca_pages.viewscores', userid=userid), + "refresh": url_for("museca_pages.listplayer", userid=userid), + "records": url_for("museca_pages.viewrecords", userid=userid), + "scores": url_for("museca_pages.viewscores", userid=userid), }, ) -@museca_pages.route('/players//list') +@museca_pages.route("/players//list") @jsonify @loginrequired def listplayer(userid: UserID) -> Dict[str, Any]: @@ -289,11 +291,11 @@ def listplayer(userid: UserID) -> Dict[str, Any]: info = frontend.get_all_player_info([userid])[userid] return { - 'player': info, + "player": info, } -@museca_pages.route('/options') +@museca_pages.route("/options") @loginrequired def viewsettings() -> Response: frontend = MusecaFrontend(g.data, g.config, g.cache) @@ -303,48 +305,49 @@ def viewsettings() -> Response: abort(404) return render_react( - 'MÚSECA Game Settings', - 'museca/settings.react.js', + "MÚSECA Game Settings", + "museca/settings.react.js", { - 'player': info, - 'versions': {version: name for (game, version, name) in frontend.all_games()}, + "player": info, + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, }, { - 'updatename': url_for('museca_pages.updatename'), + "updatename": url_for("museca_pages.updatename"), }, ) -@museca_pages.route('/options/name/update', methods=['POST']) +@museca_pages.route("/options/name/update", methods=["POST"]) @jsonify @loginrequired def updatename() -> Dict[str, Any]: - version = int(request.get_json()['version']) - name = request.get_json()['name'] + version = int(request.get_json()["version"]) + name = request.get_json()["name"] user = g.data.local.user.get_user(g.userID) if user is None: - raise Exception('Unable to find user to update!') + raise Exception("Unable to find user to update!") # Grab profile and update name profile = g.data.local.user.get_profile(GameConstants.MUSECA, version, user.id) if profile is None: - raise Exception('Unable to find profile to update!') + raise Exception("Unable to find profile to update!") if len(name) == 0 or len(name) > 8: - raise Exception('Invalid profile name!') - if re.match( - "^[" + - "0-9" + - "A-Z" + - "!?#$&*-. " + - "]*$", - name, - ) is None: - raise Exception('Invalid profile name!') - profile.replace_str('name', name) + raise Exception("Invalid profile name!") + if ( + re.match( + "^[" + "0-9" + "A-Z" + "!?#$&*-. " + "]*$", + name, + ) + is None + ): + raise Exception("Invalid profile name!") + profile.replace_str("name", name) g.data.local.user.put_profile(GameConstants.MUSECA, version, user.id, profile) # Return that we updated return { - 'version': version, - 'name': name, + "version": version, + "name": name, } diff --git a/bemani/frontend/museca/museca.py b/bemani/frontend/museca/museca.py index cf029c8..39cdb9e 100644 --- a/bemani/frontend/museca/museca.py +++ b/bemani/frontend/museca/museca.py @@ -4,7 +4,13 @@ from typing import Any, Dict, Iterator, List, Tuple from flask_caching import Cache from bemani.backend.museca import MusecaFactory, MusecaBase -from bemani.common import GameConstants, VersionConstants, DBConstants, Profile, ValidatedDict +from bemani.common import ( + GameConstants, + VersionConstants, + DBConstants, + Profile, + ValidatedDict, +) from bemani.data import Attempt, Data, Config, Score, Song, UserID from bemani.frontend.base import FrontendBase @@ -27,74 +33,76 @@ class MusecaFrontend(FrontendBase): yield ( GameConstants.MUSECA, VersionConstants.MUSECA_1_PLUS + DBConstants.OMNIMIX_VERSION_BUMP, - 'MÚSECA PLUS', + "MÚSECA PLUS", ) # Hard code entry for MÚSECA PLUS since entries will go in blank category otherwise def format_score(self, userid: UserID, score: Score) -> Dict[str, Any]: formatted_score = super().format_score(userid, score) - formatted_score['combo'] = score.data.get_int('combo', -1) - formatted_score['grade'] = { - MusecaBase.GRADE_DEATH: 'Death (没)', - MusecaBase.GRADE_POOR: 'Poor (拙)', - MusecaBase.GRADE_MEDIOCRE: 'Mediocre (凡)', - MusecaBase.GRADE_GOOD: 'Good (佳)', - MusecaBase.GRADE_GREAT: 'Great (良)', - MusecaBase.GRADE_EXCELLENT: 'Excellent (優)', - MusecaBase.GRADE_SUPERB: 'Superb (秀)', - MusecaBase.GRADE_MASTERPIECE: 'Masterpiece (傑)', - MusecaBase.GRADE_PERFECT: 'Perfect (傑)', - }.get(score.data.get_int('grade'), 'No Play') - formatted_score['clear_type'] = { - MusecaBase.CLEAR_TYPE_FAILED: 'FAILED', - MusecaBase.CLEAR_TYPE_CLEARED: 'CLEARED', - MusecaBase.CLEAR_TYPE_FULL_COMBO: 'FULL COMBO', - }.get(score.data.get_int('clear_type'), 'FAILED') - formatted_score['medal'] = score.data.get_int('clear_type') - formatted_score['stats'] = score.data.get_dict('stats') + formatted_score["combo"] = score.data.get_int("combo", -1) + formatted_score["grade"] = { + MusecaBase.GRADE_DEATH: "Death (没)", + MusecaBase.GRADE_POOR: "Poor (拙)", + MusecaBase.GRADE_MEDIOCRE: "Mediocre (凡)", + MusecaBase.GRADE_GOOD: "Good (佳)", + MusecaBase.GRADE_GREAT: "Great (良)", + MusecaBase.GRADE_EXCELLENT: "Excellent (優)", + MusecaBase.GRADE_SUPERB: "Superb (秀)", + MusecaBase.GRADE_MASTERPIECE: "Masterpiece (傑)", + MusecaBase.GRADE_PERFECT: "Perfect (傑)", + }.get(score.data.get_int("grade"), "No Play") + formatted_score["clear_type"] = { + MusecaBase.CLEAR_TYPE_FAILED: "FAILED", + MusecaBase.CLEAR_TYPE_CLEARED: "CLEARED", + MusecaBase.CLEAR_TYPE_FULL_COMBO: "FULL COMBO", + }.get(score.data.get_int("clear_type"), "FAILED") + formatted_score["medal"] = score.data.get_int("clear_type") + formatted_score["stats"] = score.data.get_dict("stats") return formatted_score def format_attempt(self, userid: UserID, attempt: Attempt) -> Dict[str, Any]: formatted_attempt = super().format_attempt(userid, attempt) - formatted_attempt['combo'] = attempt.data.get_int('combo', -1) - formatted_attempt['grade'] = { - MusecaBase.GRADE_DEATH: 'Death (没)', - MusecaBase.GRADE_POOR: 'Poor (拙)', - MusecaBase.GRADE_MEDIOCRE: 'Mediocre (凡)', - MusecaBase.GRADE_GOOD: 'Good (佳)', - MusecaBase.GRADE_GREAT: 'Great (良)', - MusecaBase.GRADE_EXCELLENT: 'Excellent (優)', - MusecaBase.GRADE_SUPERB: 'Superb (秀)', - MusecaBase.GRADE_MASTERPIECE: 'Masterpiece (傑)', - MusecaBase.GRADE_PERFECT: 'Perfect (傑)', - }.get(attempt.data.get_int('grade'), 'No Play') - formatted_attempt['clear_type'] = { - MusecaBase.CLEAR_TYPE_FAILED: 'FAILED', - MusecaBase.CLEAR_TYPE_CLEARED: 'CLEARED', - MusecaBase.CLEAR_TYPE_FULL_COMBO: 'FULL COMBO', - }.get(attempt.data.get_int('clear_type'), 'FAILED') - formatted_attempt['medal'] = attempt.data.get_int('clear_type') - formatted_attempt['stats'] = attempt.data.get_dict('stats') + formatted_attempt["combo"] = attempt.data.get_int("combo", -1) + formatted_attempt["grade"] = { + MusecaBase.GRADE_DEATH: "Death (没)", + MusecaBase.GRADE_POOR: "Poor (拙)", + MusecaBase.GRADE_MEDIOCRE: "Mediocre (凡)", + MusecaBase.GRADE_GOOD: "Good (佳)", + MusecaBase.GRADE_GREAT: "Great (良)", + MusecaBase.GRADE_EXCELLENT: "Excellent (優)", + MusecaBase.GRADE_SUPERB: "Superb (秀)", + MusecaBase.GRADE_MASTERPIECE: "Masterpiece (傑)", + MusecaBase.GRADE_PERFECT: "Perfect (傑)", + }.get(attempt.data.get_int("grade"), "No Play") + formatted_attempt["clear_type"] = { + MusecaBase.CLEAR_TYPE_FAILED: "FAILED", + MusecaBase.CLEAR_TYPE_CLEARED: "CLEARED", + MusecaBase.CLEAR_TYPE_FULL_COMBO: "FULL COMBO", + }.get(attempt.data.get_int("clear_type"), "FAILED") + formatted_attempt["medal"] = attempt.data.get_int("clear_type") + formatted_attempt["stats"] = attempt.data.get_dict("stats") return formatted_attempt - def format_profile(self, profile: Profile, playstats: ValidatedDict) -> Dict[str, Any]: + def format_profile( + self, profile: Profile, playstats: ValidatedDict + ) -> Dict[str, Any]: formatted_profile = super().format_profile(profile, playstats) - formatted_profile['plays'] = playstats.get_int('total_plays') + formatted_profile["plays"] = playstats.get_int("total_plays") return formatted_profile def format_song(self, song: Song) -> Dict[str, Any]: difficulties = [0, 0, 0] - difficulties[song.chart] = song.data.get_int('difficulty', 21) + difficulties[song.chart] = song.data.get_int("difficulty", 21) formatted_song = super().format_song(song) - formatted_song['difficulties'] = difficulties - formatted_song['category'] = song.version + formatted_song["difficulties"] = difficulties + formatted_song["category"] = song.version return formatted_song def merge_song(self, existing: Dict[str, Any], new: Song) -> Dict[str, Any]: new_song = super().merge_song(existing, new) - if existing['difficulties'][new.chart] == 0: - new_song['difficulties'][new.chart] = new.data.get_int('difficulty', 21) + if existing["difficulties"][new.chart] == 0: + new_song["difficulties"][new.chart] = new.data.get_int("difficulty", 21) # Set the category to the earliest seen version of this song - if existing['category'] > new.version: - new_song['category'] = new.version + if existing["category"] > new.version: + new_song["category"] = new.version return new_song diff --git a/bemani/frontend/popn/cache.py b/bemani/frontend/popn/cache.py index e849f5f..ab5d924 100644 --- a/bemani/frontend/popn/cache.py +++ b/bemani/frontend/popn/cache.py @@ -6,12 +6,14 @@ from bemani.frontend.popn.popn import PopnMusicFrontend class PopnMusicCache: - @classmethod def preload(cls, data: Data, config: Config) -> None: - cache = Cache(app, config={ - 'CACHE_TYPE': 'filesystem', - 'CACHE_DIR': config.cache_dir, - }) + cache = Cache( + app, + config={ + "CACHE_TYPE": "filesystem", + "CACHE_DIR": config.cache_dir, + }, + ) frontend = PopnMusicFrontend(data, config, cache) frontend.get_all_songs(force_db_load=True) diff --git a/bemani/frontend/popn/endpoints.py b/bemani/frontend/popn/endpoints.py index c9601bf..3ec68e9 100644 --- a/bemani/frontend/popn/endpoints.py +++ b/bemani/frontend/popn/endpoints.py @@ -13,42 +13,42 @@ from bemani.frontend.types import g popn_pages = Blueprint( - 'popn_pages', + "popn_pages", __name__, - url_prefix=f'/{GameConstants.POPN_MUSIC.value}', + url_prefix=f"/{GameConstants.POPN_MUSIC.value}", template_folder=templates_location, static_folder=static_location, ) -@popn_pages.route('/scores') +@popn_pages.route("/scores") @loginrequired def viewnetworkscores() -> Response: # Only load the last 100 results for the initial fetch, so we can render faster frontend = PopnMusicFrontend(g.data, g.config, g.cache) network_scores = frontend.get_network_scores(limit=100) - if len(network_scores['attempts']) > 10: - network_scores['attempts'] = frontend.round_to_ten(network_scores['attempts']) + if len(network_scores["attempts"]) > 10: + network_scores["attempts"] = frontend.round_to_ten(network_scores["attempts"]) return render_react( - 'Global Pop\'n Music Scores', - 'popn/scores.react.js', + "Global Pop'n Music Scores", + "popn/scores.react.js", { - 'attempts': network_scores['attempts'], - 'songs': frontend.get_all_songs(), - 'players': network_scores['players'], - 'shownames': True, - 'shownewrecords': False, + "attempts": network_scores["attempts"], + "songs": frontend.get_all_songs(), + "players": network_scores["players"], + "shownames": True, + "shownewrecords": False, }, { - 'refresh': url_for('popn_pages.listnetworkscores'), - 'player': url_for('popn_pages.viewplayer', userid=-1), - 'individual_score': url_for('popn_pages.viewtopscores', musicid=-1), + "refresh": url_for("popn_pages.listnetworkscores"), + "player": url_for("popn_pages.viewplayer", userid=-1), + "individual_score": url_for("popn_pages.viewtopscores", musicid=-1), }, ) -@popn_pages.route('/scores/list') +@popn_pages.route("/scores/list") @jsonify @loginrequired def listnetworkscores() -> Dict[str, Any]: @@ -56,7 +56,7 @@ def listnetworkscores() -> Dict[str, Any]: return frontend.get_network_scores() -@popn_pages.route('/scores/') +@popn_pages.route("/scores/") @loginrequired def viewscores(userid: UserID) -> Response: frontend = PopnMusicFrontend(g.data, g.config, g.cache) @@ -70,34 +70,34 @@ def viewscores(userid: UserID) -> Response: return render_react( f'{info["name"]}\'s Pop\'n Music Scores', - 'popn/scores.react.js', + "popn/scores.react.js", { - 'attempts': scores, - 'songs': frontend.get_all_songs(), - 'players': {}, - 'shownames': False, - 'shownewrecords': True, + "attempts": scores, + "songs": frontend.get_all_songs(), + "players": {}, + "shownames": False, + "shownewrecords": True, }, { - 'refresh': url_for('popn_pages.listscores', userid=userid), - 'player': url_for('popn_pages.viewplayer', userid=-1), - 'individual_score': url_for('popn_pages.viewtopscores', musicid=-1), + "refresh": url_for("popn_pages.listscores", userid=userid), + "player": url_for("popn_pages.viewplayer", userid=-1), + "individual_score": url_for("popn_pages.viewtopscores", musicid=-1), }, ) -@popn_pages.route('/scores//list') +@popn_pages.route("/scores//list") @jsonify @loginrequired def listscores(userid: UserID) -> Dict[str, Any]: frontend = PopnMusicFrontend(g.data, g.config, g.cache) return { - 'attempts': frontend.get_scores(userid), - 'players': {}, + "attempts": frontend.get_scores(userid), + "players": {}, } -@popn_pages.route('/records') +@popn_pages.route("/records") @loginrequired def viewnetworkrecords() -> Response: frontend = PopnMusicFrontend(g.data, g.config, g.cache) @@ -105,26 +105,26 @@ def viewnetworkrecords() -> Response: versions = {version: name for (game, version, name) in frontend.all_games()} return render_react( - 'Global Pop\'n Music Records', - 'popn/records.react.js', + "Global Pop'n Music Records", + "popn/records.react.js", { - 'records': network_records['records'], - 'songs': frontend.get_all_songs(), - 'players': network_records['players'], - 'versions': versions, - 'shownames': True, - 'showpersonalsort': False, - 'filterempty': False, + "records": network_records["records"], + "songs": frontend.get_all_songs(), + "players": network_records["players"], + "versions": versions, + "shownames": True, + "showpersonalsort": False, + "filterempty": False, }, { - 'refresh': url_for('popn_pages.listnetworkrecords'), - 'player': url_for('popn_pages.viewplayer', userid=-1), - 'individual_score': url_for('popn_pages.viewtopscores', musicid=-1), + "refresh": url_for("popn_pages.listnetworkrecords"), + "player": url_for("popn_pages.viewplayer", userid=-1), + "individual_score": url_for("popn_pages.viewtopscores", musicid=-1), }, ) -@popn_pages.route('/records/list') +@popn_pages.route("/records/list") @jsonify @loginrequired def listnetworkrecords() -> Dict[str, Any]: @@ -132,7 +132,7 @@ def listnetworkrecords() -> Dict[str, Any]: return frontend.get_network_records() -@popn_pages.route('/records/') +@popn_pages.route("/records/") @loginrequired def viewrecords(userid: UserID) -> Response: frontend = PopnMusicFrontend(g.data, g.config, g.cache) @@ -143,36 +143,36 @@ def viewrecords(userid: UserID) -> Response: return render_react( f'{info["name"]}\'s Pop\'n Music Records', - 'popn/records.react.js', + "popn/records.react.js", { - 'records': frontend.get_records(userid), - 'songs': frontend.get_all_songs(), - 'players': {}, - 'versions': versions, - 'shownames': False, - 'showpersonalsort': True, - 'filterempty': True, + "records": frontend.get_records(userid), + "songs": frontend.get_all_songs(), + "players": {}, + "versions": versions, + "shownames": False, + "showpersonalsort": True, + "filterempty": True, }, { - 'refresh': url_for('popn_pages.listrecords', userid=userid), - 'player': url_for('popn_pages.viewplayer', userid=-1), - 'individual_score': url_for('popn_pages.viewtopscores', musicid=-1), + "refresh": url_for("popn_pages.listrecords", userid=userid), + "player": url_for("popn_pages.viewplayer", userid=-1), + "individual_score": url_for("popn_pages.viewtopscores", musicid=-1), }, ) -@popn_pages.route('/records//list') +@popn_pages.route("/records//list") @jsonify @loginrequired def listrecords(userid: UserID) -> Dict[str, Any]: frontend = PopnMusicFrontend(g.data, g.config, g.cache) return { - 'records': frontend.get_records(userid), - 'players': {}, + "records": frontend.get_records(userid), + "players": {}, } -@popn_pages.route('/topscores/') +@popn_pages.route("/topscores/") @loginrequired def viewtopscores(musicid: int) -> Response: # We just want to find the latest mix that this song exists in @@ -188,7 +188,9 @@ def viewtopscores(musicid: int) -> Response: for version in versions: for chart in [0, 1, 2, 3]: - details = g.data.local.music.get_song(GameConstants.POPN_MUSIC, version, musicid, chart) + details = g.data.local.music.get_song( + GameConstants.POPN_MUSIC, version, musicid, chart + ) if details is not None: if name is None: name = details.name @@ -197,7 +199,7 @@ def viewtopscores(musicid: int) -> Response: if genre is None: genre = details.genre if difficulties[chart] == 0: - difficulties[chart] = details.data.get_int('difficulty') + difficulties[chart] = details.data.get_int("difficulty") if name is None: # Not a real song! @@ -206,24 +208,24 @@ def viewtopscores(musicid: int) -> Response: top_scores = frontend.get_top_scores(musicid) return render_react( - f'Top Pop\'n Music Scores for {artist} - {name}', - 'popn/topscores.react.js', + f"Top Pop'n Music Scores for {artist} - {name}", + "popn/topscores.react.js", { - 'name': name, - 'artist': artist, - 'genre': genre, - 'difficulties': difficulties, - 'players': top_scores['players'], - 'topscores': top_scores['topscores'], + "name": name, + "artist": artist, + "genre": genre, + "difficulties": difficulties, + "players": top_scores["players"], + "topscores": top_scores["topscores"], }, { - 'refresh': url_for('popn_pages.listtopscores', musicid=musicid), - 'player': url_for('popn_pages.viewplayer', userid=-1), + "refresh": url_for("popn_pages.listtopscores", musicid=musicid), + "player": url_for("popn_pages.viewplayer", userid=-1), }, ) -@popn_pages.route('/topscores//list') +@popn_pages.route("/topscores//list") @jsonify @loginrequired def listtopscores(musicid: int) -> Dict[str, Any]: @@ -231,34 +233,32 @@ def listtopscores(musicid: int) -> Dict[str, Any]: return frontend.get_top_scores(musicid) -@popn_pages.route('/players') +@popn_pages.route("/players") @loginrequired def viewplayers() -> Response: frontend = PopnMusicFrontend(g.data, g.config, g.cache) return render_react( - 'All Pop\'n Music Players', - 'popn/allplayers.react.js', + "All Pop'n Music Players", + "popn/allplayers.react.js", + {"players": frontend.get_all_players()}, { - 'players': frontend.get_all_players() - }, - { - 'refresh': url_for('popn_pages.listplayers'), - 'player': url_for('popn_pages.viewplayer', userid=-1), + "refresh": url_for("popn_pages.listplayers"), + "player": url_for("popn_pages.viewplayer", userid=-1), }, ) -@popn_pages.route('/players/list') +@popn_pages.route("/players/list") @jsonify @loginrequired def listplayers() -> Dict[str, Any]: frontend = PopnMusicFrontend(g.data, g.config, g.cache) return { - 'players': frontend.get_all_players(), + "players": frontend.get_all_players(), } -@popn_pages.route('/players/') +@popn_pages.route("/players/") @loginrequired def viewplayer(userid: UserID) -> Response: frontend = PopnMusicFrontend(g.data, g.config, g.cache) @@ -269,22 +269,24 @@ def viewplayer(userid: UserID) -> Response: return render_react( f'{info[latest_version]["name"]}\'s Pop\'n Music Profile', - 'popn/player.react.js', + "popn/player.react.js", { - 'playerid': userid, - 'own_profile': userid == g.userID, - 'player': info, - 'versions': {version: name for (game, version, name) in frontend.all_games()}, + "playerid": userid, + "own_profile": userid == g.userID, + "player": info, + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, }, { - 'refresh': url_for('popn_pages.listplayer', userid=userid), - 'records': url_for('popn_pages.viewrecords', userid=userid), - 'scores': url_for('popn_pages.viewscores', userid=userid), + "refresh": url_for("popn_pages.listplayer", userid=userid), + "records": url_for("popn_pages.viewrecords", userid=userid), + "scores": url_for("popn_pages.viewscores", userid=userid), }, ) -@popn_pages.route('/players//list') +@popn_pages.route("/players//list") @jsonify @loginrequired def listplayer(userid: UserID) -> Dict[str, Any]: @@ -292,11 +294,11 @@ def listplayer(userid: UserID) -> Dict[str, Any]: info = frontend.get_all_player_info([userid])[userid] return { - 'player': info, + "player": info, } -@popn_pages.route('/options') +@popn_pages.route("/options") @loginrequired def viewsettings() -> Response: frontend = PopnMusicFrontend(g.data, g.config, g.cache) @@ -306,57 +308,62 @@ def viewsettings() -> Response: abort(404) return render_react( - 'Pop\'n Music Game Settings', - 'popn/settings.react.js', + "Pop'n Music Game Settings", + "popn/settings.react.js", { - 'player': info, - 'versions': {version: name for (game, version, name) in frontend.all_games()}, + "player": info, + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, }, { - 'updatename': url_for('popn_pages.updatename'), + "updatename": url_for("popn_pages.updatename"), }, ) -@popn_pages.route('/options/name/update', methods=['POST']) +@popn_pages.route("/options/name/update", methods=["POST"]) @jsonify @loginrequired def updatename() -> Dict[str, Any]: - version = int(request.get_json()['version']) - name = request.get_json()['name'] + version = int(request.get_json()["version"]) + name = request.get_json()["name"] user = g.data.local.user.get_user(g.userID) if user is None: - raise Exception('Unable to find user to update!') + raise Exception("Unable to find user to update!") # Grab profile and update name profile = g.data.local.user.get_profile(GameConstants.POPN_MUSIC, version, user.id) if profile is None: - raise Exception('Unable to find profile to update!') + raise Exception("Unable to find profile to update!") if len(name) == 0 or len(name) > 6: - raise Exception('Invalid profile name!') - if re.match( - "^[" + - "\uFF20-\uFF3A" + # widetext A-Z and @ - "\uFF41-\uFF5A" + # widetext a-z - "\uFF10-\uFF19" + # widetext 0-9 - "\uFF0C\uFF0E\uFF3F" + # widetext ,._ - "\u3041-\u308D\u308F\u3092\u3093" + # hiragana - "\u30A1-\u30ED\u30EF\u30F2\u30F3\u30FC" + # katakana - "]*$", - name, - ) is None: - raise Exception('Invalid profile name!') - profile.replace_str('name', name) + raise Exception("Invalid profile name!") + if ( + re.match( + "^[" + + "\uFF20-\uFF3A" + + "\uFF41-\uFF5A" # widetext A-Z and @ + + "\uFF10-\uFF19" # widetext a-z + + "\uFF0C\uFF0E\uFF3F" # widetext 0-9 + + "\u3041-\u308D\u308F\u3092\u3093" # widetext ,._ + + "\u30A1-\u30ED\u30EF\u30F2\u30F3\u30FC" # hiragana + + "]*$", # katakana + name, + ) + is None + ): + raise Exception("Invalid profile name!") + profile.replace_str("name", name) g.data.local.user.put_profile(GameConstants.POPN_MUSIC, version, user.id, profile) # Return that we updated return { - 'version': version, - 'name': name, + "version": version, + "name": name, } -@popn_pages.route('/rivals') +@popn_pages.route("/rivals") @loginrequired def viewrivals() -> Response: frontend = PopnMusicFrontend(g.data, g.config, g.cache) @@ -367,26 +374,28 @@ def viewrivals() -> Response: del rivals[VersionConstants.POPN_MUSIC_TUNE_STREET] return render_react( - 'Pop\'n Music Rivals', - 'popn/rivals.react.js', + "Pop'n Music Rivals", + "popn/rivals.react.js", { - 'userid': str(g.userID), - 'rivals': rivals, - 'max_active_rivals': frontend.max_active_rivals, - 'players': playerinfo, - 'versions': {version: name for (game, version, name) in frontend.all_games()}, + "userid": str(g.userID), + "rivals": rivals, + "max_active_rivals": frontend.max_active_rivals, + "players": playerinfo, + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, }, { - 'refresh': url_for('popn_pages.listrivals'), - 'search': url_for('popn_pages.searchrivals'), - 'player': url_for('popn_pages.viewplayer', userid=-1), - 'addrival': url_for('popn_pages.addrival'), - 'removerival': url_for('popn_pages.removerival'), + "refresh": url_for("popn_pages.listrivals"), + "search": url_for("popn_pages.searchrivals"), + "player": url_for("popn_pages.viewplayer", userid=-1), + "addrival": url_for("popn_pages.addrival"), + "removerival": url_for("popn_pages.removerival"), }, ) -@popn_pages.route('/rivals/list') +@popn_pages.route("/rivals/list") @jsonify @loginrequired def listrivals() -> Dict[str, Any]: @@ -398,18 +407,18 @@ def listrivals() -> Dict[str, Any]: del rivals[VersionConstants.POPN_MUSIC_TUNE_STREET] return { - 'rivals': rivals, - 'players': playerinfo, + "rivals": rivals, + "players": playerinfo, } -@popn_pages.route('/rivals/search', methods=['POST']) +@popn_pages.route("/rivals/search", methods=["POST"]) @jsonify @loginrequired def searchrivals() -> Dict[str, Any]: frontend = PopnMusicFrontend(g.data, g.config, g.cache) - version = int(request.get_json()['version']) - name = request.get_json()['term'] + version = int(request.get_json()["version"]) + name = request.get_json()["term"] print(name) # Try to treat the term as an extid @@ -418,34 +427,36 @@ def searchrivals() -> Dict[str, Any]: matches = set() profiles = g.data.remote.user.get_all_profiles(GameConstants.POPN_MUSIC, version) for (userid, profile) in profiles: - if profile.extid == extid or profile.get_str('name').lower() == name.lower(): + if profile.extid == extid or profile.get_str("name").lower() == name.lower(): matches.add(userid) playerinfo = frontend.get_all_player_info(list(matches), allow_remote=True) return { - 'results': playerinfo, + "results": playerinfo, } -@popn_pages.route('/rivals/add', methods=['POST']) +@popn_pages.route("/rivals/add", methods=["POST"]) @jsonify @loginrequired def addrival() -> Dict[str, Any]: frontend = PopnMusicFrontend(g.data, g.config, g.cache) - version = int(request.get_json()['version']) - other_userid = UserID(int(request.get_json()['userid'])) + version = int(request.get_json()["version"]) + other_userid = UserID(int(request.get_json()["userid"])) userid = g.userID # Add this rival link - profile = g.data.remote.user.get_profile(GameConstants.POPN_MUSIC, version, other_userid) + profile = g.data.remote.user.get_profile( + GameConstants.POPN_MUSIC, version, other_userid + ) if profile is None: - raise Exception('Unable to find profile for rival!') + raise Exception("Unable to find profile for rival!") g.data.local.user.put_link( GameConstants.POPN_MUSIC, version, userid, - 'rival', + "rival", other_userid, {}, ) @@ -454,18 +465,18 @@ def addrival() -> Dict[str, Any]: rivals, playerinfo = frontend.get_rivals(userid) return { - 'rivals': rivals, - 'players': playerinfo, + "rivals": rivals, + "players": playerinfo, } -@popn_pages.route('/rivals/remove', methods=['POST']) +@popn_pages.route("/rivals/remove", methods=["POST"]) @jsonify @loginrequired def removerival() -> Dict[str, Any]: frontend = PopnMusicFrontend(g.data, g.config, g.cache) - version = int(request.get_json()['version']) - other_userid = UserID(int(request.get_json()['userid'])) + version = int(request.get_json()["version"]) + other_userid = UserID(int(request.get_json()["userid"])) userid = g.userID # Remove this rival link @@ -473,7 +484,7 @@ def removerival() -> Dict[str, Any]: GameConstants.POPN_MUSIC, version, userid, - 'rival', + "rival", other_userid, ) @@ -481,6 +492,6 @@ def removerival() -> Dict[str, Any]: rivals, playerinfo = frontend.get_rivals(userid) return { - 'rivals': rivals, - 'players': playerinfo, + "rivals": rivals, + "players": playerinfo, } diff --git a/bemani/frontend/popn/popn.py b/bemani/frontend/popn/popn.py index 71f81be..ee92e53 100644 --- a/bemani/frontend/popn/popn.py +++ b/bemani/frontend/popn/popn.py @@ -18,7 +18,7 @@ class PopnMusicFrontend(FrontendBase): PopnMusicBase.CHART_TYPE_EX, ] - valid_rival_types: List[str] = ['rival'] + valid_rival_types: List[str] = ["rival"] max_active_rivals: Dict[int, int] = { # Technically there is support for Rivals in Tune Street but I @@ -33,14 +33,18 @@ class PopnMusicFrontend(FrontendBase): def all_games(self) -> Iterator[Tuple[GameConstants, int, str]]: yield from PopnMusicFactory.all_games() - yield (GameConstants.POPN_MUSIC, 0, 'CS and Licenses') # Folder that doesn't belong to any specific game + yield ( + GameConstants.POPN_MUSIC, + 0, + "CS and Licenses", + ) # Folder that doesn't belong to any specific game def format_score(self, userid: UserID, score: Score) -> Dict[str, Any]: formatted_score = super().format_score(userid, score) - formatted_score['combo'] = score.data.get_int('combo', -1) - formatted_score['medal'] = score.data.get_int('medal') - formatted_score['stats'] = score.data.get_dict('stats') - formatted_score['status'] = { + formatted_score["combo"] = score.data.get_int("combo", -1) + formatted_score["medal"] = score.data.get_int("medal") + formatted_score["stats"] = score.data.get_dict("stats") + formatted_score["status"] = { PopnMusicBase.PLAY_MEDAL_NO_PLAY: "NO PLAY", PopnMusicBase.PLAY_MEDAL_CIRCLE_FAILED: "○ FAILED", PopnMusicBase.PLAY_MEDAL_DIAMOND_FAILED: "◇ FAILED", @@ -53,15 +57,15 @@ class PopnMusicFrontend(FrontendBase): PopnMusicBase.PLAY_MEDAL_DIAMOND_FULL_COMBO: "◇ FULL COMBO", PopnMusicBase.PLAY_MEDAL_STAR_FULL_COMBO: "☆ FULL COMBO", PopnMusicBase.PLAY_MEDAL_PERFECT: "PERFECT", - }.get(score.data.get_int('medal'), 'NO PLAY') + }.get(score.data.get_int("medal"), "NO PLAY") return formatted_score def format_attempt(self, userid: UserID, attempt: Attempt) -> Dict[str, Any]: formatted_attempt = super().format_attempt(userid, attempt) - formatted_attempt['combo'] = attempt.data.get_int('combo', -1) - formatted_attempt['medal'] = attempt.data.get_int('medal') - formatted_attempt['stats'] = attempt.data.get_dict('stats') - formatted_attempt['status'] = { + formatted_attempt["combo"] = attempt.data.get_int("combo", -1) + formatted_attempt["medal"] = attempt.data.get_int("medal") + formatted_attempt["stats"] = attempt.data.get_dict("stats") + formatted_attempt["status"] = { PopnMusicBase.PLAY_MEDAL_CIRCLE_FAILED: "○ FAILED", PopnMusicBase.PLAY_MEDAL_DIAMOND_FAILED: "◇ FAILED", PopnMusicBase.PLAY_MEDAL_STAR_FAILED: "☆ FAILED", @@ -73,25 +77,27 @@ class PopnMusicFrontend(FrontendBase): PopnMusicBase.PLAY_MEDAL_DIAMOND_FULL_COMBO: "◇ FULL COMBO", PopnMusicBase.PLAY_MEDAL_STAR_FULL_COMBO: "☆ FULL COMBO", PopnMusicBase.PLAY_MEDAL_PERFECT: "PERFECT", - }.get(attempt.data.get_int('medal'), 'NO PLAY') + }.get(attempt.data.get_int("medal"), "NO PLAY") return formatted_attempt - def format_profile(self, profile: Profile, playstats: ValidatedDict) -> Dict[str, Any]: + def format_profile( + self, profile: Profile, playstats: ValidatedDict + ) -> Dict[str, Any]: formatted_profile = super().format_profile(profile, playstats) - formatted_profile['plays'] = playstats.get_int('total_plays') + formatted_profile["plays"] = playstats.get_int("total_plays") return formatted_profile def format_song(self, song: Song) -> Dict[str, Any]: difficulties = [0, 0, 0, 0] - difficulties[song.chart] = song.data.get_int('difficulty', 51) + difficulties[song.chart] = song.data.get_int("difficulty", 51) formatted_song = super().format_song(song) - formatted_song['category'] = song.data.get_str('category') - formatted_song['difficulties'] = difficulties + formatted_song["category"] = song.data.get_str("category") + formatted_song["difficulties"] = difficulties return formatted_song def merge_song(self, existing: Dict[str, Any], new: Song) -> Dict[str, Any]: new_song = super().merge_song(existing, new) - if existing['difficulties'][new.chart] == 0: - new_song['difficulties'][new.chart] = new.data.get_int('difficulty', 51) + if existing["difficulties"][new.chart] == 0: + new_song["difficulties"][new.chart] = new.data.get_int("difficulty", 51) return new_song diff --git a/bemani/frontend/reflec/cache.py b/bemani/frontend/reflec/cache.py index 7807fd8..55f56c7 100644 --- a/bemani/frontend/reflec/cache.py +++ b/bemani/frontend/reflec/cache.py @@ -6,12 +6,14 @@ from bemani.frontend.reflec.reflec import ReflecBeatFrontend class ReflecBeatCache: - @classmethod def preload(cls, data: Data, config: Config) -> None: - cache = Cache(app, config={ - 'CACHE_TYPE': 'filesystem', - 'CACHE_DIR': config.cache_dir, - }) + cache = Cache( + app, + config={ + "CACHE_TYPE": "filesystem", + "CACHE_DIR": config.cache_dir, + }, + ) frontend = ReflecBeatFrontend(data, config, cache) frontend.get_all_songs(force_db_load=True) diff --git a/bemani/frontend/reflec/endpoints.py b/bemani/frontend/reflec/endpoints.py index 6dd3f31..c2dce75 100644 --- a/bemani/frontend/reflec/endpoints.py +++ b/bemani/frontend/reflec/endpoints.py @@ -14,9 +14,9 @@ from bemani.frontend.types import g reflec_pages = Blueprint( - 'reflec_pages', + "reflec_pages", __name__, - url_prefix=f'/{GameConstants.REFLEC_BEAT.value}', + url_prefix=f"/{GameConstants.REFLEC_BEAT.value}", template_folder=templates_location, static_folder=static_location, ) @@ -24,34 +24,34 @@ reflec_pages = Blueprint( NO_RIVAL_SUPPORT: Final[List[int]] = [1] -@reflec_pages.route('/scores') +@reflec_pages.route("/scores") @loginrequired def viewnetworkscores() -> Response: # Only load the last 100 results for the initial fetch, so we can render faster frontend = ReflecBeatFrontend(g.data, g.config, g.cache) network_scores = frontend.get_network_scores(limit=100) - if len(network_scores['attempts']) > 10: - network_scores['attempts'] = frontend.round_to_ten(network_scores['attempts']) + if len(network_scores["attempts"]) > 10: + network_scores["attempts"] = frontend.round_to_ten(network_scores["attempts"]) return render_react( - 'Global Reflec Beat Scores', - 'reflec/scores.react.js', + "Global Reflec Beat Scores", + "reflec/scores.react.js", { - 'attempts': network_scores['attempts'], - 'songs': frontend.get_all_songs(), - 'players': network_scores['players'], - 'shownames': True, - 'shownewrecords': False, + "attempts": network_scores["attempts"], + "songs": frontend.get_all_songs(), + "players": network_scores["players"], + "shownames": True, + "shownewrecords": False, }, { - 'refresh': url_for('reflec_pages.listnetworkscores'), - 'player': url_for('reflec_pages.viewplayer', userid=-1), - 'individual_score': url_for('reflec_pages.viewtopscores', musicid=-1), + "refresh": url_for("reflec_pages.listnetworkscores"), + "player": url_for("reflec_pages.viewplayer", userid=-1), + "individual_score": url_for("reflec_pages.viewtopscores", musicid=-1), }, ) -@reflec_pages.route('/scores/list') +@reflec_pages.route("/scores/list") @jsonify @loginrequired def listnetworkscores() -> Dict[str, Any]: @@ -59,7 +59,7 @@ def listnetworkscores() -> Dict[str, Any]: return frontend.get_network_scores() -@reflec_pages.route('/scores/') +@reflec_pages.route("/scores/") @loginrequired def viewscores(userid: UserID) -> Response: frontend = ReflecBeatFrontend(g.data, g.config, g.cache) @@ -73,62 +73,62 @@ def viewscores(userid: UserID) -> Response: return render_react( f'{info["name"]}\'s Reflec Beat Scores', - 'reflec/scores.react.js', + "reflec/scores.react.js", { - 'attempts': scores, - 'songs': frontend.get_all_songs(), - 'players': {}, - 'shownames': False, - 'shownewrecords': True, + "attempts": scores, + "songs": frontend.get_all_songs(), + "players": {}, + "shownames": False, + "shownewrecords": True, }, { - 'refresh': url_for('reflec_pages.listscores', userid=userid), - 'player': url_for('reflec_pages.viewplayer', userid=-1), - 'individual_score': url_for('reflec_pages.viewtopscores', musicid=-1), + "refresh": url_for("reflec_pages.listscores", userid=userid), + "player": url_for("reflec_pages.viewplayer", userid=-1), + "individual_score": url_for("reflec_pages.viewtopscores", musicid=-1), }, ) -@reflec_pages.route('/scores//list') +@reflec_pages.route("/scores//list") @jsonify @loginrequired def listscores(userid: UserID) -> Dict[str, Any]: frontend = ReflecBeatFrontend(g.data, g.config, g.cache) return { - 'attempts': frontend.get_scores(userid), - 'players': {}, + "attempts": frontend.get_scores(userid), + "players": {}, } -@reflec_pages.route('/records') +@reflec_pages.route("/records") @loginrequired def viewnetworkrecords() -> Response: frontend = ReflecBeatFrontend(g.data, g.config, g.cache) network_records = frontend.get_network_records() versions = {version: name for (game, version, name) in frontend.all_games()} - versions[0] = 'CS and Licenses' + versions[0] = "CS and Licenses" return render_react( - 'Global Reflec Beat Records', - 'reflec/records.react.js', + "Global Reflec Beat Records", + "reflec/records.react.js", { - 'records': network_records['records'], - 'songs': frontend.get_all_songs(), - 'players': network_records['players'], - 'versions': versions, - 'shownames': True, - 'showpersonalsort': False, - 'filterempty': False, + "records": network_records["records"], + "songs": frontend.get_all_songs(), + "players": network_records["players"], + "versions": versions, + "shownames": True, + "showpersonalsort": False, + "filterempty": False, }, { - 'refresh': url_for('reflec_pages.listnetworkrecords'), - 'player': url_for('reflec_pages.viewplayer', userid=-1), - 'individual_score': url_for('reflec_pages.viewtopscores', musicid=-1), + "refresh": url_for("reflec_pages.listnetworkrecords"), + "player": url_for("reflec_pages.viewplayer", userid=-1), + "individual_score": url_for("reflec_pages.viewtopscores", musicid=-1), }, ) -@reflec_pages.route('/records/list') +@reflec_pages.route("/records/list") @jsonify @loginrequired def listnetworkrecords() -> Dict[str, Any]: @@ -136,7 +136,7 @@ def listnetworkrecords() -> Dict[str, Any]: return frontend.get_network_records() -@reflec_pages.route('/records/') +@reflec_pages.route("/records/") @loginrequired def viewrecords(userid: UserID) -> Response: frontend = ReflecBeatFrontend(g.data, g.config, g.cache) @@ -147,36 +147,36 @@ def viewrecords(userid: UserID) -> Response: return render_react( f'{info["name"]}\'s Reflec Beat Records', - 'reflec/records.react.js', + "reflec/records.react.js", { - 'records': frontend.get_records(userid), - 'songs': frontend.get_all_songs(), - 'players': {}, - 'versions': versions, - 'shownames': False, - 'showpersonalsort': True, - 'filterempty': True, + "records": frontend.get_records(userid), + "songs": frontend.get_all_songs(), + "players": {}, + "versions": versions, + "shownames": False, + "showpersonalsort": True, + "filterempty": True, }, { - 'refresh': url_for('reflec_pages.listrecords', userid=userid), - 'player': url_for('reflec_pages.viewplayer', userid=-1), - 'individual_score': url_for('reflec_pages.viewtopscores', musicid=-1), + "refresh": url_for("reflec_pages.listrecords", userid=userid), + "player": url_for("reflec_pages.viewplayer", userid=-1), + "individual_score": url_for("reflec_pages.viewtopscores", musicid=-1), }, ) -@reflec_pages.route('/records//list') +@reflec_pages.route("/records//list") @jsonify @loginrequired def listrecords(userid: UserID) -> Dict[str, Any]: frontend = ReflecBeatFrontend(g.data, g.config, g.cache) return { - 'records': frontend.get_records(userid), - 'players': {}, + "records": frontend.get_records(userid), + "players": {}, } -@reflec_pages.route('/topscores/') +@reflec_pages.route("/topscores/") @loginrequired def viewtopscores(musicid: int) -> Response: # We just want to find the latest mix that this song exists in @@ -186,14 +186,16 @@ def viewtopscores(musicid: int) -> Response: difficulties = [0, 0, 0, 0] for chart in [0, 1, 2, 3]: - details = g.data.local.music.get_song(GameConstants.REFLEC_BEAT, 0, musicid, chart) + details = g.data.local.music.get_song( + GameConstants.REFLEC_BEAT, 0, musicid, chart + ) if details is not None: if name is None: name = details.name if artist is None: artist = details.artist if difficulties[chart] == 0: - difficulties[chart] = details.data.get_int('difficulty') + difficulties[chart] = details.data.get_int("difficulty") if name is None: # Not a real song! @@ -202,23 +204,23 @@ def viewtopscores(musicid: int) -> Response: top_scores = frontend.get_top_scores(musicid) return render_react( - f'Top Reflec Beat Scores for {artist} - {name}', - 'reflec/topscores.react.js', + f"Top Reflec Beat Scores for {artist} - {name}", + "reflec/topscores.react.js", { - 'name': name, - 'artist': artist, - 'difficulties': difficulties, - 'players': top_scores['players'], - 'topscores': top_scores['topscores'], + "name": name, + "artist": artist, + "difficulties": difficulties, + "players": top_scores["players"], + "topscores": top_scores["topscores"], }, { - 'refresh': url_for('reflec_pages.listtopscores', musicid=musicid), - 'player': url_for('reflec_pages.viewplayer', userid=-1), + "refresh": url_for("reflec_pages.listtopscores", musicid=musicid), + "player": url_for("reflec_pages.viewplayer", userid=-1), }, ) -@reflec_pages.route('/topscores//list') +@reflec_pages.route("/topscores//list") @jsonify @loginrequired def listtopscores(musicid: int) -> Dict[str, Any]: @@ -226,34 +228,32 @@ def listtopscores(musicid: int) -> Dict[str, Any]: return frontend.get_top_scores(musicid) -@reflec_pages.route('/players') +@reflec_pages.route("/players") @loginrequired def viewplayers() -> Response: frontend = ReflecBeatFrontend(g.data, g.config, g.cache) return render_react( - 'All Reflec Beat Players', - 'reflec/allplayers.react.js', + "All Reflec Beat Players", + "reflec/allplayers.react.js", + {"players": frontend.get_all_players()}, { - 'players': frontend.get_all_players() - }, - { - 'refresh': url_for('reflec_pages.listplayers'), - 'player': url_for('reflec_pages.viewplayer', userid=-1), + "refresh": url_for("reflec_pages.listplayers"), + "player": url_for("reflec_pages.viewplayer", userid=-1), }, ) -@reflec_pages.route('/players/list') +@reflec_pages.route("/players/list") @jsonify @loginrequired def listplayers() -> Dict[str, Any]: frontend = ReflecBeatFrontend(g.data, g.config, g.cache) return { - 'players': frontend.get_all_players(), + "players": frontend.get_all_players(), } -@reflec_pages.route('/players/') +@reflec_pages.route("/players/") @loginrequired def viewplayer(userid: UserID) -> Response: frontend = ReflecBeatFrontend(g.data, g.config, g.cache) @@ -264,22 +264,24 @@ def viewplayer(userid: UserID) -> Response: return render_react( f'{info[latest_version]["name"]}\'s Reflec Beat Profile', - 'reflec/player.react.js', + "reflec/player.react.js", { - 'playerid': userid, - 'own_profile': userid == g.userID, - 'player': info, - 'versions': {version: name for (game, version, name) in frontend.all_games()}, + "playerid": userid, + "own_profile": userid == g.userID, + "player": info, + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, }, { - 'refresh': url_for('reflec_pages.listplayer', userid=userid), - 'records': url_for('reflec_pages.viewrecords', userid=userid), - 'scores': url_for('reflec_pages.viewscores', userid=userid), + "refresh": url_for("reflec_pages.listplayer", userid=userid), + "records": url_for("reflec_pages.viewrecords", userid=userid), + "scores": url_for("reflec_pages.viewscores", userid=userid), }, ) -@reflec_pages.route('/players//list') +@reflec_pages.route("/players//list") @jsonify @loginrequired def listplayer(userid: UserID) -> Dict[str, Any]: @@ -287,11 +289,11 @@ def listplayer(userid: UserID) -> Dict[str, Any]: info = frontend.get_all_player_info([userid])[userid] return { - 'player': info, + "player": info, } -@reflec_pages.route('/options') +@reflec_pages.route("/options") @loginrequired def viewsettings() -> Response: frontend = ReflecBeatFrontend(g.data, g.config, g.cache) @@ -301,83 +303,91 @@ def viewsettings() -> Response: abort(404) return render_react( - 'Reflec Beat Game Settings', - 'reflec/settings.react.js', + "Reflec Beat Game Settings", + "reflec/settings.react.js", { - 'player': info, - 'versions': {version: name for (game, version, name) in frontend.all_games()}, + "player": info, + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, }, { - 'updatename': url_for('reflec_pages.updatename'), + "updatename": url_for("reflec_pages.updatename"), }, ) -@reflec_pages.route('/options/name/update', methods=['POST']) +@reflec_pages.route("/options/name/update", methods=["POST"]) @jsonify @loginrequired def updatename() -> Dict[str, Any]: - version = int(request.get_json()['version']) - name = request.get_json()['name'] + version = int(request.get_json()["version"]) + name = request.get_json()["name"] user = g.data.local.user.get_user(g.userID) if user is None: - raise Exception('Unable to find user to update!') + raise Exception("Unable to find user to update!") # Grab profile and update name profile = g.data.local.user.get_profile(GameConstants.REFLEC_BEAT, version, user.id) if profile is None: - raise Exception('Unable to find profile to update!') + raise Exception("Unable to find profile to update!") if len(name) == 0 or len(name) > 8: - raise Exception('Invalid profile name!') + raise Exception("Invalid profile name!") if version <= 3: # Older reflec didn't allow for lowercase - if re.match( - "^[" + - "\uFF21-\uFF3A" + # widetext A-Z - "\uFF10-\uFF19" + # widetext 0-9 - "\uFF0E\u2212\uFF3F\u30FB" + - "\uFF06\uFF01\uFF1F\uFF0F" + - "\uFF0A\uFF03\u266D\u2605" + - "\uFF20\u266A\u2193\u2191" + - "\u2192\u2190\uFF08\uFF09" + - "\u221E\u25C6\u25CF\u25BC" + - "\uFFE5\uFF3E\u2200\uFF05" + - "\u3000" + # widetext space - "]*$", - name, - ) is None: - raise Exception('Invalid profile name!') + if ( + re.match( + "^[" + + "\uFF21-\uFF3A" + + "\uFF10-\uFF19" # widetext A-Z + + "\uFF0E\u2212\uFF3F\u30FB" # widetext 0-9 + + "\uFF06\uFF01\uFF1F\uFF0F" + + "\uFF0A\uFF03\u266D\u2605" + + "\uFF20\u266A\u2193\u2191" + + "\u2192\u2190\uFF08\uFF09" + + "\u221E\u25C6\u25CF\u25BC" + + "\uFFE5\uFF3E\u2200\uFF05" + + "\u3000" + + "]*$", # widetext space + name, + ) + is None + ): + raise Exception("Invalid profile name!") else: # Newer reflec allows the same as older but # also allows for lowercase widetext. - if re.match( - "^[" + - "\uFF21-\uFF3A" + # widetext A-Z - "\uFF41-\uFF5A" + # widetext a-z - "\uFF10-\uFF19" + # widetext 0-9 - "\uFF0E\u2212\uFF3F\u30FB" + - "\uFF06\uFF01\uFF1F\uFF0F" + - "\uFF0A\uFF03\u266D\u2605" + - "\uFF20\u266A\u2193\u2191" + - "\u2192\u2190\uFF08\uFF09" + - "\u221E\u25C6\u25CF\u25BC" + - "\uFFE5\uFF3E\u2200\uFF05" + - "\u3000" + # widetext space - "]*$", - name, - ) is None: - raise Exception('Invalid profile name!') - profile.replace_str('name', name) + if ( + re.match( + "^[" + + "\uFF21-\uFF3A" + + "\uFF41-\uFF5A" # widetext A-Z + + "\uFF10-\uFF19" # widetext a-z + + "\uFF0E\u2212\uFF3F\u30FB" # widetext 0-9 + + "\uFF06\uFF01\uFF1F\uFF0F" + + "\uFF0A\uFF03\u266D\u2605" + + "\uFF20\u266A\u2193\u2191" + + "\u2192\u2190\uFF08\uFF09" + + "\u221E\u25C6\u25CF\u25BC" + + "\uFFE5\uFF3E\u2200\uFF05" + + "\u3000" + + "]*$", # widetext space + name, + ) + is None + ): + raise Exception("Invalid profile name!") + profile.replace_str("name", name) g.data.local.user.put_profile(GameConstants.REFLEC_BEAT, version, user.id, profile) # Return that we updated return { - 'version': version, - 'name': name, + "version": version, + "name": name, } -@reflec_pages.route('/rivals') +@reflec_pages.route("/rivals") @loginrequired def viewrivals() -> Response: frontend = ReflecBeatFrontend(g.data, g.config, g.cache) @@ -389,25 +399,29 @@ def viewrivals() -> Response: del rivals[no_rivals_support] return render_react( - 'Reflec Beat Rivals', - 'reflec/rivals.react.js', + "Reflec Beat Rivals", + "reflec/rivals.react.js", { - 'userid': str(g.userID), - 'rivals': rivals, - 'players': playerinfo, - 'versions': {version: name for (game, version, name) in frontend.all_games() if version not in NO_RIVAL_SUPPORT}, + "userid": str(g.userID), + "rivals": rivals, + "players": playerinfo, + "versions": { + version: name + for (game, version, name) in frontend.all_games() + if version not in NO_RIVAL_SUPPORT + }, }, { - 'refresh': url_for('reflec_pages.listrivals'), - 'search': url_for('reflec_pages.searchrivals'), - 'player': url_for('reflec_pages.viewplayer', userid=-1), - 'addrival': url_for('reflec_pages.addrival'), - 'removerival': url_for('reflec_pages.removerival'), + "refresh": url_for("reflec_pages.listrivals"), + "search": url_for("reflec_pages.searchrivals"), + "player": url_for("reflec_pages.viewplayer", userid=-1), + "addrival": url_for("reflec_pages.addrival"), + "removerival": url_for("reflec_pages.removerival"), }, ) -@reflec_pages.route('/rivals/list') +@reflec_pages.route("/rivals/list") @jsonify @loginrequired def listrivals() -> Dict[str, Any]: @@ -420,18 +434,18 @@ def listrivals() -> Dict[str, Any]: del rivals[no_rivals_support] return { - 'rivals': rivals, - 'players': playerinfo, + "rivals": rivals, + "players": playerinfo, } -@reflec_pages.route('/rivals/search', methods=['POST']) +@reflec_pages.route("/rivals/search", methods=["POST"]) @jsonify @loginrequired def searchrivals() -> Dict[str, Any]: frontend = ReflecBeatFrontend(g.data, g.config, g.cache) - version = int(request.get_json()['version']) - name = request.get_json()['term'] + version = int(request.get_json()["version"]) + name = request.get_json()["term"] # Try to treat the term as an extid extid = ID.parse_extid(name) @@ -439,34 +453,36 @@ def searchrivals() -> Dict[str, Any]: matches = set() profiles = g.data.remote.user.get_all_profiles(GameConstants.REFLEC_BEAT, version) for (userid, profile) in profiles: - if profile.extid == extid or profile.get_str('name').lower() == name.lower(): + if profile.extid == extid or profile.get_str("name").lower() == name.lower(): matches.add(userid) playerinfo = frontend.get_all_player_info(list(matches), allow_remote=True) return { - 'results': playerinfo, + "results": playerinfo, } -@reflec_pages.route('/rivals/add', methods=['POST']) +@reflec_pages.route("/rivals/add", methods=["POST"]) @jsonify @loginrequired def addrival() -> Dict[str, Any]: frontend = ReflecBeatFrontend(g.data, g.config, g.cache) - version = int(request.get_json()['version']) - other_userid = UserID(int(request.get_json()['userid'])) + version = int(request.get_json()["version"]) + other_userid = UserID(int(request.get_json()["userid"])) userid = g.userID # Add this rival link - profile = g.data.remote.user.get_profile(GameConstants.REFLEC_BEAT, version, other_userid) + profile = g.data.remote.user.get_profile( + GameConstants.REFLEC_BEAT, version, other_userid + ) if profile is None: - raise Exception('Unable to find profile for rival!') + raise Exception("Unable to find profile for rival!") g.data.local.user.put_link( GameConstants.REFLEC_BEAT, version, userid, - 'rival', + "rival", other_userid, {}, ) @@ -475,18 +491,18 @@ def addrival() -> Dict[str, Any]: rivals, playerinfo = frontend.get_rivals(userid) return { - 'rivals': rivals, - 'players': playerinfo, + "rivals": rivals, + "players": playerinfo, } -@reflec_pages.route('/rivals/remove', methods=['POST']) +@reflec_pages.route("/rivals/remove", methods=["POST"]) @jsonify @loginrequired def removerival() -> Dict[str, Any]: frontend = ReflecBeatFrontend(g.data, g.config, g.cache) - version = int(request.get_json()['version']) - other_userid = UserID(int(request.get_json()['userid'])) + version = int(request.get_json()["version"]) + other_userid = UserID(int(request.get_json()["userid"])) userid = g.userID # Remove this rival link @@ -494,7 +510,7 @@ def removerival() -> Dict[str, Any]: GameConstants.REFLEC_BEAT, version, userid, - 'rival', + "rival", other_userid, ) @@ -502,6 +518,6 @@ def removerival() -> Dict[str, Any]: rivals, playerinfo = frontend.get_rivals(userid) return { - 'rivals': rivals, - 'players': playerinfo, + "rivals": rivals, + "players": playerinfo, } diff --git a/bemani/frontend/reflec/reflec.py b/bemani/frontend/reflec/reflec.py index e5ca28e..b6bc487 100644 --- a/bemani/frontend/reflec/reflec.py +++ b/bemani/frontend/reflec/reflec.py @@ -23,7 +23,7 @@ class ReflecBeatFrontend(FrontendBase): ] valid_rival_types: List[str] = [ - 'rival', + "rival", ] def __init__(self, data: Data, config: Config, cache: Cache) -> None: @@ -34,64 +34,72 @@ class ReflecBeatFrontend(FrontendBase): def format_score(self, userid: UserID, score: Score) -> Dict[str, Any]: formatted_score = super().format_score(userid, score) - formatted_score['combo'] = score.data.get_int('combo', -1) - formatted_score['achievement_rate'] = score.data.get_int('achievement_rate', -1) - formatted_score['miss_count'] = score.data.get_int('miss_count', -1) - formatted_score['clear_type'] = { - ReflecBeatBase.CLEAR_TYPE_NO_PLAY: 'NO PLAY', - ReflecBeatBase.CLEAR_TYPE_FAILED: 'FAILED', - ReflecBeatBase.CLEAR_TYPE_CLEARED: 'CLEARED', - ReflecBeatBase.CLEAR_TYPE_HARD_CLEARED: 'HARD CLEARED', - ReflecBeatBase.CLEAR_TYPE_S_HARD_CLEARED: 'S-HARD CLEARED', - }.get(score.data.get_int('clear_type'), 'FAILED') - formatted_score['combo_type'] = { - ReflecBeatBase.COMBO_TYPE_NONE: '', - ReflecBeatBase.COMBO_TYPE_ALMOST_COMBO: 'ALMOST FULL COMBO', - ReflecBeatBase.COMBO_TYPE_FULL_COMBO: 'FULL COMBO', - ReflecBeatBase.COMBO_TYPE_FULL_COMBO_ALL_JUST: 'FULL COMBO + ALL JUST', - }.get(score.data.get_int('combo_type'), '') - formatted_score['medal'] = score.data.get_int('combo_type') * 1000 + score.data.get_int('clear_type') + formatted_score["combo"] = score.data.get_int("combo", -1) + formatted_score["achievement_rate"] = score.data.get_int("achievement_rate", -1) + formatted_score["miss_count"] = score.data.get_int("miss_count", -1) + formatted_score["clear_type"] = { + ReflecBeatBase.CLEAR_TYPE_NO_PLAY: "NO PLAY", + ReflecBeatBase.CLEAR_TYPE_FAILED: "FAILED", + ReflecBeatBase.CLEAR_TYPE_CLEARED: "CLEARED", + ReflecBeatBase.CLEAR_TYPE_HARD_CLEARED: "HARD CLEARED", + ReflecBeatBase.CLEAR_TYPE_S_HARD_CLEARED: "S-HARD CLEARED", + }.get(score.data.get_int("clear_type"), "FAILED") + formatted_score["combo_type"] = { + ReflecBeatBase.COMBO_TYPE_NONE: "", + ReflecBeatBase.COMBO_TYPE_ALMOST_COMBO: "ALMOST FULL COMBO", + ReflecBeatBase.COMBO_TYPE_FULL_COMBO: "FULL COMBO", + ReflecBeatBase.COMBO_TYPE_FULL_COMBO_ALL_JUST: "FULL COMBO + ALL JUST", + }.get(score.data.get_int("combo_type"), "") + formatted_score["medal"] = score.data.get_int( + "combo_type" + ) * 1000 + score.data.get_int("clear_type") return formatted_score def format_attempt(self, userid: UserID, attempt: Attempt) -> Dict[str, Any]: formatted_attempt = super().format_attempt(userid, attempt) - formatted_attempt['combo'] = attempt.data.get_int('combo', -1) - formatted_attempt['achievement_rate'] = attempt.data.get_int('achievement_rate', -1) - formatted_attempt['miss_count'] = attempt.data.get_int('miss_count', -1) - formatted_attempt['clear_type'] = { - ReflecBeatBase.CLEAR_TYPE_NO_PLAY: 'NO PLAY', - ReflecBeatBase.CLEAR_TYPE_FAILED: 'FAILED', - ReflecBeatBase.CLEAR_TYPE_CLEARED: 'CLEARED', - ReflecBeatBase.CLEAR_TYPE_HARD_CLEARED: 'HARD CLEARED', - ReflecBeatBase.CLEAR_TYPE_S_HARD_CLEARED: 'S-HARD CLEARED', - }.get(attempt.data.get_int('clear_type'), 'FAILED') - formatted_attempt['combo_type'] = { - ReflecBeatBase.COMBO_TYPE_NONE: '', - ReflecBeatBase.COMBO_TYPE_ALMOST_COMBO: 'ALMOST FULL COMBO', - ReflecBeatBase.COMBO_TYPE_FULL_COMBO: 'FULL COMBO', - ReflecBeatBase.COMBO_TYPE_FULL_COMBO_ALL_JUST: 'FULL COMBO + ALL JUST', - }.get(attempt.data.get_int('combo_type'), '') - formatted_attempt['medal'] = attempt.data.get_int('combo_type') * 1000 + attempt.data.get_int('clear_type') + formatted_attempt["combo"] = attempt.data.get_int("combo", -1) + formatted_attempt["achievement_rate"] = attempt.data.get_int( + "achievement_rate", -1 + ) + formatted_attempt["miss_count"] = attempt.data.get_int("miss_count", -1) + formatted_attempt["clear_type"] = { + ReflecBeatBase.CLEAR_TYPE_NO_PLAY: "NO PLAY", + ReflecBeatBase.CLEAR_TYPE_FAILED: "FAILED", + ReflecBeatBase.CLEAR_TYPE_CLEARED: "CLEARED", + ReflecBeatBase.CLEAR_TYPE_HARD_CLEARED: "HARD CLEARED", + ReflecBeatBase.CLEAR_TYPE_S_HARD_CLEARED: "S-HARD CLEARED", + }.get(attempt.data.get_int("clear_type"), "FAILED") + formatted_attempt["combo_type"] = { + ReflecBeatBase.COMBO_TYPE_NONE: "", + ReflecBeatBase.COMBO_TYPE_ALMOST_COMBO: "ALMOST FULL COMBO", + ReflecBeatBase.COMBO_TYPE_FULL_COMBO: "FULL COMBO", + ReflecBeatBase.COMBO_TYPE_FULL_COMBO_ALL_JUST: "FULL COMBO + ALL JUST", + }.get(attempt.data.get_int("combo_type"), "") + formatted_attempt["medal"] = attempt.data.get_int( + "combo_type" + ) * 1000 + attempt.data.get_int("clear_type") return formatted_attempt - def format_profile(self, profile: Profile, playstats: ValidatedDict) -> Dict[str, Any]: + def format_profile( + self, profile: Profile, playstats: ValidatedDict + ) -> Dict[str, Any]: formatted_profile = super().format_profile(profile, playstats) - formatted_profile['plays'] = playstats.get_int('total_plays') + formatted_profile["plays"] = playstats.get_int("total_plays") return formatted_profile def format_song(self, song: Song) -> Dict[str, Any]: difficulties = [0, 0, 0, 0] - difficulties[song.chart] = song.data.get_int('difficulty', 16) + difficulties[song.chart] = song.data.get_int("difficulty", 16) formatted_song = super().format_song(song) - formatted_song['difficulties'] = difficulties - formatted_song['category'] = song.data.get_int('folder', 1) + formatted_song["difficulties"] = difficulties + formatted_song["category"] = song.data.get_int("folder", 1) return formatted_song def merge_song(self, existing: Dict[str, Any], new: Song) -> Dict[str, Any]: new_song = super().merge_song(existing, new) - if existing['difficulties'][new.chart] == 0: - new_song['difficulties'][new.chart] = new.data.get_int('difficulty', 16) - if existing['category'] == 0: - new_song['category'] = new.data.get_int('folder', 1) + if existing["difficulties"][new.chart] == 0: + new_song["difficulties"][new.chart] = new.data.get_int("difficulty", 16) + if existing["category"] == 0: + new_song["category"] = new.data.get_int("folder", 1) return new_song diff --git a/bemani/frontend/sdvx/cache.py b/bemani/frontend/sdvx/cache.py index f93df91..656e786 100644 --- a/bemani/frontend/sdvx/cache.py +++ b/bemani/frontend/sdvx/cache.py @@ -6,12 +6,14 @@ from bemani.frontend.sdvx.sdvx import SoundVoltexFrontend class SoundVoltexCache: - @classmethod def preload(cls, data: Data, config: Config) -> None: - cache = Cache(app, config={ - 'CACHE_TYPE': 'filesystem', - 'CACHE_DIR': config.cache_dir, - }) + cache = Cache( + app, + config={ + "CACHE_TYPE": "filesystem", + "CACHE_DIR": config.cache_dir, + }, + ) frontend = SoundVoltexFrontend(data, config, cache) frontend.get_all_songs(force_db_load=True) diff --git a/bemani/frontend/sdvx/endpoints.py b/bemani/frontend/sdvx/endpoints.py index 37bf4d0..c11a2cd 100644 --- a/bemani/frontend/sdvx/endpoints.py +++ b/bemani/frontend/sdvx/endpoints.py @@ -13,42 +13,42 @@ from bemani.frontend.types import g sdvx_pages = Blueprint( - 'sdvx_pages', + "sdvx_pages", __name__, - url_prefix=f'/{GameConstants.SDVX.value}', + url_prefix=f"/{GameConstants.SDVX.value}", template_folder=templates_location, static_folder=static_location, ) -@sdvx_pages.route('/scores') +@sdvx_pages.route("/scores") @loginrequired def viewnetworkscores() -> Response: # Only load the last 100 results for the initial fetch, so we can render faster frontend = SoundVoltexFrontend(g.data, g.config, g.cache) network_scores = frontend.get_network_scores(limit=100) - if len(network_scores['attempts']) > 10: - network_scores['attempts'] = frontend.round_to_ten(network_scores['attempts']) + if len(network_scores["attempts"]) > 10: + network_scores["attempts"] = frontend.round_to_ten(network_scores["attempts"]) return render_react( - 'Global SDVX Scores', - 'sdvx/scores.react.js', + "Global SDVX Scores", + "sdvx/scores.react.js", { - 'attempts': network_scores['attempts'], - 'songs': frontend.get_all_songs(), - 'players': network_scores['players'], - 'shownames': True, - 'shownewrecords': False, + "attempts": network_scores["attempts"], + "songs": frontend.get_all_songs(), + "players": network_scores["players"], + "shownames": True, + "shownewrecords": False, }, { - 'refresh': url_for('sdvx_pages.listnetworkscores'), - 'player': url_for('sdvx_pages.viewplayer', userid=-1), - 'individual_score': url_for('sdvx_pages.viewtopscores', musicid=-1), + "refresh": url_for("sdvx_pages.listnetworkscores"), + "player": url_for("sdvx_pages.viewplayer", userid=-1), + "individual_score": url_for("sdvx_pages.viewtopscores", musicid=-1), }, ) -@sdvx_pages.route('/scores/list') +@sdvx_pages.route("/scores/list") @jsonify @loginrequired def listnetworkscores() -> Dict[str, Any]: @@ -56,7 +56,7 @@ def listnetworkscores() -> Dict[str, Any]: return frontend.get_network_scores() -@sdvx_pages.route('/scores/') +@sdvx_pages.route("/scores/") @loginrequired def viewscores(userid: UserID) -> Response: frontend = SoundVoltexFrontend(g.data, g.config, g.cache) @@ -70,62 +70,62 @@ def viewscores(userid: UserID) -> Response: return render_react( f'{info["name"]}\'s SDVX Scores', - 'sdvx/scores.react.js', + "sdvx/scores.react.js", { - 'attempts': scores, - 'songs': frontend.get_all_songs(), - 'players': {}, - 'shownames': False, - 'shownewrecords': True, + "attempts": scores, + "songs": frontend.get_all_songs(), + "players": {}, + "shownames": False, + "shownewrecords": True, }, { - 'refresh': url_for('sdvx_pages.listscores', userid=userid), - 'player': url_for('sdvx_pages.viewplayer', userid=-1), - 'individual_score': url_for('sdvx_pages.viewtopscores', musicid=-1), + "refresh": url_for("sdvx_pages.listscores", userid=userid), + "player": url_for("sdvx_pages.viewplayer", userid=-1), + "individual_score": url_for("sdvx_pages.viewtopscores", musicid=-1), }, ) -@sdvx_pages.route('/scores//list') +@sdvx_pages.route("/scores//list") @jsonify @loginrequired def listscores(userid: UserID) -> Dict[str, Any]: frontend = SoundVoltexFrontend(g.data, g.config, g.cache) return { - 'attempts': frontend.get_scores(userid), - 'players': {}, + "attempts": frontend.get_scores(userid), + "players": {}, } -@sdvx_pages.route('/records') +@sdvx_pages.route("/records") @loginrequired def viewnetworkrecords() -> Response: frontend = SoundVoltexFrontend(g.data, g.config, g.cache) network_records = frontend.get_network_records() versions = {version: name for (game, version, name) in frontend.all_games()} - versions[0] = 'CS and Licenses' + versions[0] = "CS and Licenses" return render_react( - 'Global SDVX Records', - 'sdvx/records.react.js', + "Global SDVX Records", + "sdvx/records.react.js", { - 'records': network_records['records'], - 'songs': frontend.get_all_songs(), - 'players': network_records['players'], - 'versions': versions, - 'shownames': True, - 'showpersonalsort': False, - 'filterempty': False, + "records": network_records["records"], + "songs": frontend.get_all_songs(), + "players": network_records["players"], + "versions": versions, + "shownames": True, + "showpersonalsort": False, + "filterempty": False, }, { - 'refresh': url_for('sdvx_pages.listnetworkrecords'), - 'player': url_for('sdvx_pages.viewplayer', userid=-1), - 'individual_score': url_for('sdvx_pages.viewtopscores', musicid=-1), + "refresh": url_for("sdvx_pages.listnetworkrecords"), + "player": url_for("sdvx_pages.viewplayer", userid=-1), + "individual_score": url_for("sdvx_pages.viewtopscores", musicid=-1), }, ) -@sdvx_pages.route('/records/list') +@sdvx_pages.route("/records/list") @jsonify @loginrequired def listnetworkrecords() -> Dict[str, Any]: @@ -133,7 +133,7 @@ def listnetworkrecords() -> Dict[str, Any]: return frontend.get_network_records() -@sdvx_pages.route('/records/') +@sdvx_pages.route("/records/") @loginrequired def viewrecords(userid: UserID) -> Response: frontend = SoundVoltexFrontend(g.data, g.config, g.cache) @@ -144,36 +144,36 @@ def viewrecords(userid: UserID) -> Response: return render_react( f'{info["name"]}\'s SDVX Records', - 'sdvx/records.react.js', + "sdvx/records.react.js", { - 'records': frontend.get_records(userid), - 'songs': frontend.get_all_songs(), - 'players': {}, - 'versions': versions, - 'shownames': False, - 'showpersonalsort': True, - 'filterempty': True, + "records": frontend.get_records(userid), + "songs": frontend.get_all_songs(), + "players": {}, + "versions": versions, + "shownames": False, + "showpersonalsort": True, + "filterempty": True, }, { - 'refresh': url_for('sdvx_pages.listrecords', userid=userid), - 'player': url_for('sdvx_pages.viewplayer', userid=-1), - 'individual_score': url_for('sdvx_pages.viewtopscores', musicid=-1), + "refresh": url_for("sdvx_pages.listrecords", userid=userid), + "player": url_for("sdvx_pages.viewplayer", userid=-1), + "individual_score": url_for("sdvx_pages.viewtopscores", musicid=-1), }, ) -@sdvx_pages.route('/records//list') +@sdvx_pages.route("/records//list") @jsonify @loginrequired def listrecords(userid: UserID) -> Dict[str, Any]: frontend = SoundVoltexFrontend(g.data, g.config, g.cache) return { - 'records': frontend.get_records(userid), - 'players': {}, + "records": frontend.get_records(userid), + "players": {}, } -@sdvx_pages.route('/topscores/') +@sdvx_pages.route("/topscores/") @loginrequired def viewtopscores(musicid: int) -> Response: # We just want to find the latest mix that this song exists in @@ -188,14 +188,16 @@ def viewtopscores(musicid: int) -> Response: for version in versions: for chart in [0, 1, 2, 3, 4]: - details = g.data.local.music.get_song(GameConstants.SDVX, version, musicid, chart) + details = g.data.local.music.get_song( + GameConstants.SDVX, version, musicid, chart + ) if details is not None: if name is None: name = details.name if artist is None: artist = details.artist if difficulties[chart] == 0: - difficulties[chart] = details.data.get_int('difficulty') + difficulties[chart] = details.data.get_int("difficulty") if name is None: # Not a real song! @@ -204,23 +206,23 @@ def viewtopscores(musicid: int) -> Response: top_scores = frontend.get_top_scores(musicid) return render_react( - f'Top SDVX Scores for {artist} - {name}', - 'sdvx/topscores.react.js', + f"Top SDVX Scores for {artist} - {name}", + "sdvx/topscores.react.js", { - 'name': name, - 'artist': artist, - 'difficulties': difficulties, - 'players': top_scores['players'], - 'topscores': top_scores['topscores'], + "name": name, + "artist": artist, + "difficulties": difficulties, + "players": top_scores["players"], + "topscores": top_scores["topscores"], }, { - 'refresh': url_for('sdvx_pages.listtopscores', musicid=musicid), - 'player': url_for('sdvx_pages.viewplayer', userid=-1), + "refresh": url_for("sdvx_pages.listtopscores", musicid=musicid), + "player": url_for("sdvx_pages.viewplayer", userid=-1), }, ) -@sdvx_pages.route('/topscores//list') +@sdvx_pages.route("/topscores//list") @jsonify @loginrequired def listtopscores(musicid: int) -> Dict[str, Any]: @@ -228,34 +230,32 @@ def listtopscores(musicid: int) -> Dict[str, Any]: return frontend.get_top_scores(musicid) -@sdvx_pages.route('/players') +@sdvx_pages.route("/players") @loginrequired def viewplayers() -> Response: frontend = SoundVoltexFrontend(g.data, g.config, g.cache) return render_react( - 'All SDVX Players', - 'sdvx/allplayers.react.js', + "All SDVX Players", + "sdvx/allplayers.react.js", + {"players": frontend.get_all_players()}, { - 'players': frontend.get_all_players() - }, - { - 'refresh': url_for('sdvx_pages.listplayers'), - 'player': url_for('sdvx_pages.viewplayer', userid=-1), + "refresh": url_for("sdvx_pages.listplayers"), + "player": url_for("sdvx_pages.viewplayer", userid=-1), }, ) -@sdvx_pages.route('/players/list') +@sdvx_pages.route("/players/list") @jsonify @loginrequired def listplayers() -> Dict[str, Any]: frontend = SoundVoltexFrontend(g.data, g.config, g.cache) return { - 'players': frontend.get_all_players(), + "players": frontend.get_all_players(), } -@sdvx_pages.route('/players/') +@sdvx_pages.route("/players/") @loginrequired def viewplayer(userid: UserID) -> Response: frontend = SoundVoltexFrontend(g.data, g.config, g.cache) @@ -266,22 +266,24 @@ def viewplayer(userid: UserID) -> Response: return render_react( f'{info[latest_version]["name"]}\'s SDVX Profile', - 'sdvx/player.react.js', + "sdvx/player.react.js", { - 'playerid': userid, - 'own_profile': userid == g.userID, - 'player': info, - 'versions': {version: name for (game, version, name) in frontend.all_games()}, + "playerid": userid, + "own_profile": userid == g.userID, + "player": info, + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, }, { - 'refresh': url_for('sdvx_pages.listplayer', userid=userid), - 'records': url_for('sdvx_pages.viewrecords', userid=userid), - 'scores': url_for('sdvx_pages.viewscores', userid=userid), + "refresh": url_for("sdvx_pages.listplayer", userid=userid), + "records": url_for("sdvx_pages.viewrecords", userid=userid), + "scores": url_for("sdvx_pages.viewscores", userid=userid), }, ) -@sdvx_pages.route('/players//list') +@sdvx_pages.route("/players//list") @jsonify @loginrequired def listplayer(userid: UserID) -> Dict[str, Any]: @@ -289,11 +291,11 @@ def listplayer(userid: UserID) -> Dict[str, Any]: info = frontend.get_all_player_info([userid])[userid] return { - 'player': info, + "player": info, } -@sdvx_pages.route('/options') +@sdvx_pages.route("/options") @loginrequired def viewsettings() -> Response: frontend = SoundVoltexFrontend(g.data, g.config, g.cache) @@ -303,54 +305,55 @@ def viewsettings() -> Response: abort(404) return render_react( - 'SDVX Game Settings', - 'sdvx/settings.react.js', + "SDVX Game Settings", + "sdvx/settings.react.js", { - 'player': info, - 'versions': {version: name for (game, version, name) in frontend.all_games()}, + "player": info, + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, }, { - 'updatename': url_for('sdvx_pages.updatename'), + "updatename": url_for("sdvx_pages.updatename"), }, ) -@sdvx_pages.route('/options/name/update', methods=['POST']) +@sdvx_pages.route("/options/name/update", methods=["POST"]) @jsonify @loginrequired def updatename() -> Dict[str, Any]: - version = int(request.get_json()['version']) - name = request.get_json()['name'] + version = int(request.get_json()["version"]) + name = request.get_json()["name"] user = g.data.local.user.get_user(g.userID) if user is None: - raise Exception('Unable to find user to update!') + raise Exception("Unable to find user to update!") # Grab profile and update name profile = g.data.local.user.get_profile(GameConstants.SDVX, version, user.id) if profile is None: - raise Exception('Unable to find profile to update!') + raise Exception("Unable to find profile to update!") if len(name) == 0 or len(name) > 8: - raise Exception('Invalid profile name!') - if re.match( - "^[" + - "0-9" + - "A-Z" + - "!?#$&*-. " + - "]*$", - name, - ) is None: - raise Exception('Invalid profile name!') - profile.replace_str('name', name) + raise Exception("Invalid profile name!") + if ( + re.match( + "^[" + "0-9" + "A-Z" + "!?#$&*-. " + "]*$", + name, + ) + is None + ): + raise Exception("Invalid profile name!") + profile.replace_str("name", name) g.data.local.user.put_profile(GameConstants.SDVX, version, user.id, profile) # Return that we updated return { - 'version': version, - 'name': name, + "version": version, + "name": name, } -@sdvx_pages.route('/rivals') +@sdvx_pages.route("/rivals") @loginrequired def viewrivals() -> Response: frontend = SoundVoltexFrontend(g.data, g.config, g.cache) @@ -363,25 +366,27 @@ def viewrivals() -> Response: del rivals[VersionConstants.SDVX_INFINITE_INFECTION] return render_react( - 'SDVX Rivals', - 'sdvx/rivals.react.js', + "SDVX Rivals", + "sdvx/rivals.react.js", { - 'userid': str(g.userID), - 'rivals': rivals, - 'players': playerinfo, - 'versions': {version: name for (game, version, name) in frontend.all_games()}, + "userid": str(g.userID), + "rivals": rivals, + "players": playerinfo, + "versions": { + version: name for (game, version, name) in frontend.all_games() + }, }, { - 'refresh': url_for('sdvx_pages.listrivals'), - 'search': url_for('sdvx_pages.searchrivals'), - 'player': url_for('sdvx_pages.viewplayer', userid=-1), - 'addrival': url_for('sdvx_pages.addrival'), - 'removerival': url_for('sdvx_pages.removerival'), + "refresh": url_for("sdvx_pages.listrivals"), + "search": url_for("sdvx_pages.searchrivals"), + "player": url_for("sdvx_pages.viewplayer", userid=-1), + "addrival": url_for("sdvx_pages.addrival"), + "removerival": url_for("sdvx_pages.removerival"), }, ) -@sdvx_pages.route('/rivals/list') +@sdvx_pages.route("/rivals/list") @jsonify @loginrequired def listrivals() -> Dict[str, Any]: @@ -395,18 +400,18 @@ def listrivals() -> Dict[str, Any]: del rivals[VersionConstants.SDVX_INFINITE_INFECTION] return { - 'rivals': rivals, - 'players': playerinfo, + "rivals": rivals, + "players": playerinfo, } -@sdvx_pages.route('/rivals/search', methods=['POST']) +@sdvx_pages.route("/rivals/search", methods=["POST"]) @jsonify @loginrequired def searchrivals() -> Dict[str, Any]: frontend = SoundVoltexFrontend(g.data, g.config, g.cache) - version = int(request.get_json()['version']) - name = request.get_json()['term'] + version = int(request.get_json()["version"]) + name = request.get_json()["term"] # Try to treat the term as an extid extid = ID.parse_extid(name) @@ -414,34 +419,34 @@ def searchrivals() -> Dict[str, Any]: matches = set() profiles = g.data.remote.user.get_all_profiles(GameConstants.SDVX, version) for (userid, profile) in profiles: - if profile.extid == extid or profile.get_str('name').lower() == name.lower(): + if profile.extid == extid or profile.get_str("name").lower() == name.lower(): matches.add(userid) playerinfo = frontend.get_all_player_info(list(matches), allow_remote=True) return { - 'results': playerinfo, + "results": playerinfo, } -@sdvx_pages.route('/rivals/add', methods=['POST']) +@sdvx_pages.route("/rivals/add", methods=["POST"]) @jsonify @loginrequired def addrival() -> Dict[str, Any]: frontend = SoundVoltexFrontend(g.data, g.config, g.cache) - version = int(request.get_json()['version']) - other_userid = UserID(int(request.get_json()['userid'])) + version = int(request.get_json()["version"]) + other_userid = UserID(int(request.get_json()["userid"])) userid = g.userID # Add this rival link profile = g.data.remote.user.get_profile(GameConstants.SDVX, version, other_userid) if profile is None: - raise Exception('Unable to find profile for rival!') + raise Exception("Unable to find profile for rival!") g.data.local.user.put_link( GameConstants.SDVX, version, userid, - 'rival', + "rival", other_userid, {}, ) @@ -450,18 +455,18 @@ def addrival() -> Dict[str, Any]: rivals, playerinfo = frontend.get_rivals(userid) return { - 'rivals': rivals, - 'players': playerinfo, + "rivals": rivals, + "players": playerinfo, } -@sdvx_pages.route('/rivals/remove', methods=['POST']) +@sdvx_pages.route("/rivals/remove", methods=["POST"]) @jsonify @loginrequired def removerival() -> Dict[str, Any]: frontend = SoundVoltexFrontend(g.data, g.config, g.cache) - version = int(request.get_json()['version']) - other_userid = UserID(int(request.get_json()['userid'])) + version = int(request.get_json()["version"]) + other_userid = UserID(int(request.get_json()["userid"])) userid = g.userID # Remove this rival link @@ -469,7 +474,7 @@ def removerival() -> Dict[str, Any]: GameConstants.SDVX, version, userid, - 'rival', + "rival", other_userid, ) @@ -477,6 +482,6 @@ def removerival() -> Dict[str, Any]: rivals, playerinfo = frontend.get_rivals(userid) return { - 'rivals': rivals, - 'players': playerinfo, + "rivals": rivals, + "players": playerinfo, } diff --git a/bemani/frontend/sdvx/sdvx.py b/bemani/frontend/sdvx/sdvx.py index 1c6ab24..2477248 100644 --- a/bemani/frontend/sdvx/sdvx.py +++ b/bemani/frontend/sdvx/sdvx.py @@ -22,7 +22,7 @@ class SoundVoltexFrontend(FrontendBase): ] valid_rival_types: List[str] = [ - 'rival', + "rival", ] def __init__(self, data: Data, config: Config, cache: Cache) -> None: @@ -33,79 +33,81 @@ class SoundVoltexFrontend(FrontendBase): def format_score(self, userid: UserID, score: Score) -> Dict[str, Any]: formatted_score = super().format_score(userid, score) - formatted_score['combo'] = score.data.get_int('combo', -1) - formatted_score['grade'] = { - SoundVoltexBase.GRADE_NO_PLAY: '-', - SoundVoltexBase.GRADE_D: 'D', - SoundVoltexBase.GRADE_C: 'C', - SoundVoltexBase.GRADE_B: 'B', - SoundVoltexBase.GRADE_A: 'A', - SoundVoltexBase.GRADE_A_PLUS: 'A+', - SoundVoltexBase.GRADE_AA: 'AA', - SoundVoltexBase.GRADE_AA_PLUS: 'AA+', - SoundVoltexBase.GRADE_AAA: 'AAA', - SoundVoltexBase.GRADE_AAA_PLUS: 'AAA+', - SoundVoltexBase.GRADE_S: 'S', - }.get(score.data.get_int('grade'), 'No Play') - formatted_score['clear_type'] = { - SoundVoltexBase.CLEAR_TYPE_NO_PLAY: 'NO PLAY', - SoundVoltexBase.CLEAR_TYPE_FAILED: 'FAILED', - SoundVoltexBase.CLEAR_TYPE_CLEAR: 'CLEARED', - SoundVoltexBase.CLEAR_TYPE_HARD_CLEAR: 'HARD CLEARED', - SoundVoltexBase.CLEAR_TYPE_ULTIMATE_CHAIN: 'ULTIMATE CHAIN', - SoundVoltexBase.CLEAR_TYPE_PERFECT_ULTIMATE_CHAIN: 'PERFECT ULTIMATE CHAIN', - }.get(score.data.get_int('clear_type'), 'FAILED') - formatted_score['medal'] = score.data.get_int('clear_type') - formatted_score['stats'] = score.data.get_dict('stats') + formatted_score["combo"] = score.data.get_int("combo", -1) + formatted_score["grade"] = { + SoundVoltexBase.GRADE_NO_PLAY: "-", + SoundVoltexBase.GRADE_D: "D", + SoundVoltexBase.GRADE_C: "C", + SoundVoltexBase.GRADE_B: "B", + SoundVoltexBase.GRADE_A: "A", + SoundVoltexBase.GRADE_A_PLUS: "A+", + SoundVoltexBase.GRADE_AA: "AA", + SoundVoltexBase.GRADE_AA_PLUS: "AA+", + SoundVoltexBase.GRADE_AAA: "AAA", + SoundVoltexBase.GRADE_AAA_PLUS: "AAA+", + SoundVoltexBase.GRADE_S: "S", + }.get(score.data.get_int("grade"), "No Play") + formatted_score["clear_type"] = { + SoundVoltexBase.CLEAR_TYPE_NO_PLAY: "NO PLAY", + SoundVoltexBase.CLEAR_TYPE_FAILED: "FAILED", + SoundVoltexBase.CLEAR_TYPE_CLEAR: "CLEARED", + SoundVoltexBase.CLEAR_TYPE_HARD_CLEAR: "HARD CLEARED", + SoundVoltexBase.CLEAR_TYPE_ULTIMATE_CHAIN: "ULTIMATE CHAIN", + SoundVoltexBase.CLEAR_TYPE_PERFECT_ULTIMATE_CHAIN: "PERFECT ULTIMATE CHAIN", + }.get(score.data.get_int("clear_type"), "FAILED") + formatted_score["medal"] = score.data.get_int("clear_type") + formatted_score["stats"] = score.data.get_dict("stats") return formatted_score def format_attempt(self, userid: UserID, attempt: Attempt) -> Dict[str, Any]: formatted_attempt = super().format_attempt(userid, attempt) - formatted_attempt['combo'] = attempt.data.get_int('combo', -1) - formatted_attempt['grade'] = { - SoundVoltexBase.GRADE_NO_PLAY: '-', - SoundVoltexBase.GRADE_D: 'D', - SoundVoltexBase.GRADE_C: 'C', - SoundVoltexBase.GRADE_B: 'B', - SoundVoltexBase.GRADE_A: 'A', - SoundVoltexBase.GRADE_A_PLUS: 'A+', - SoundVoltexBase.GRADE_AA: 'AA', - SoundVoltexBase.GRADE_AA_PLUS: 'AA+', - SoundVoltexBase.GRADE_AAA: 'AAA', - SoundVoltexBase.GRADE_AAA_PLUS: 'AAA+', - SoundVoltexBase.GRADE_S: 'S', - }.get(attempt.data.get_int('grade'), 'No Play') - formatted_attempt['clear_type'] = { - SoundVoltexBase.CLEAR_TYPE_NO_PLAY: 'NO PLAY', - SoundVoltexBase.CLEAR_TYPE_FAILED: 'FAILED', - SoundVoltexBase.CLEAR_TYPE_CLEAR: 'CLEARED', - SoundVoltexBase.CLEAR_TYPE_HARD_CLEAR: 'HARD CLEARED', - SoundVoltexBase.CLEAR_TYPE_ULTIMATE_CHAIN: 'ULTIMATE CHAIN', - SoundVoltexBase.CLEAR_TYPE_PERFECT_ULTIMATE_CHAIN: 'PERFECT ULTIMATE CHAIN', - }.get(attempt.data.get_int('clear_type'), 'FAILED') - formatted_attempt['medal'] = attempt.data.get_int('clear_type') - formatted_attempt['stats'] = attempt.data.get_dict('stats') + formatted_attempt["combo"] = attempt.data.get_int("combo", -1) + formatted_attempt["grade"] = { + SoundVoltexBase.GRADE_NO_PLAY: "-", + SoundVoltexBase.GRADE_D: "D", + SoundVoltexBase.GRADE_C: "C", + SoundVoltexBase.GRADE_B: "B", + SoundVoltexBase.GRADE_A: "A", + SoundVoltexBase.GRADE_A_PLUS: "A+", + SoundVoltexBase.GRADE_AA: "AA", + SoundVoltexBase.GRADE_AA_PLUS: "AA+", + SoundVoltexBase.GRADE_AAA: "AAA", + SoundVoltexBase.GRADE_AAA_PLUS: "AAA+", + SoundVoltexBase.GRADE_S: "S", + }.get(attempt.data.get_int("grade"), "No Play") + formatted_attempt["clear_type"] = { + SoundVoltexBase.CLEAR_TYPE_NO_PLAY: "NO PLAY", + SoundVoltexBase.CLEAR_TYPE_FAILED: "FAILED", + SoundVoltexBase.CLEAR_TYPE_CLEAR: "CLEARED", + SoundVoltexBase.CLEAR_TYPE_HARD_CLEAR: "HARD CLEARED", + SoundVoltexBase.CLEAR_TYPE_ULTIMATE_CHAIN: "ULTIMATE CHAIN", + SoundVoltexBase.CLEAR_TYPE_PERFECT_ULTIMATE_CHAIN: "PERFECT ULTIMATE CHAIN", + }.get(attempt.data.get_int("clear_type"), "FAILED") + formatted_attempt["medal"] = attempt.data.get_int("clear_type") + formatted_attempt["stats"] = attempt.data.get_dict("stats") return formatted_attempt - def format_profile(self, profile: Profile, playstats: ValidatedDict) -> Dict[str, Any]: + def format_profile( + self, profile: Profile, playstats: ValidatedDict + ) -> Dict[str, Any]: formatted_profile = super().format_profile(profile, playstats) - formatted_profile['plays'] = playstats.get_int('total_plays') + formatted_profile["plays"] = playstats.get_int("total_plays") return formatted_profile def format_song(self, song: Song) -> Dict[str, Any]: difficulties = [0, 0, 0, 0, 0] - difficulties[song.chart] = song.data.get_int('difficulty', 21) + difficulties[song.chart] = song.data.get_int("difficulty", 21) formatted_song = super().format_song(song) - formatted_song['difficulties'] = difficulties - formatted_song['category'] = song.version + formatted_song["difficulties"] = difficulties + formatted_song["category"] = song.version return formatted_song def merge_song(self, existing: Dict[str, Any], new: Song) -> Dict[str, Any]: new_song = super().merge_song(existing, new) - if existing['difficulties'][new.chart] == 0: - new_song['difficulties'][new.chart] = new.data.get_int('difficulty', 21) + if existing["difficulties"][new.chart] == 0: + new_song["difficulties"][new.chart] = new.data.get_int("difficulty", 21) # Set the category to the earliest seen version of this song - if existing['category'] > new.version: - new_song['category'] = new.version + if existing["category"] > new.version: + new_song["category"] = new.version return new_song diff --git a/bemani/protocol/binary.py b/bemani/protocol/binary.py index 7aa3abd..2912b77 100644 --- a/bemani/protocol/binary.py +++ b/bemani/protocol/binary.py @@ -41,7 +41,7 @@ class PackedOrdering: 1 4 0 0 2 2 2 2 2 2 2 0 3 3 0 0 """ - def __init__(self, size: int, allow_expansion: bool=False) -> None: + def __init__(self, size: int, allow_expansion: bool = False) -> None: """ Initialize with a known size. If this is to be used to create a packing instead of deduce a packing, then allow_expansion should be set to true and new holes will be created when @@ -66,7 +66,7 @@ class PackedOrdering: self.order.append(None) self.__orderlen = self.__orderlen + 1 - def mark_used(self, size: int, offset: int, round_to: int=1) -> None: + def mark_used(self, size: int, offset: int, round_to: int = 1) -> None: """ Mark size bytes at offset as being used. If needed, round to the nearest byte/half/integer. @@ -187,7 +187,9 @@ class PackedOrdering: return None @staticmethod - def node_to_body_ordering(node: Node, include_children: bool=True, include_void: bool=False) -> List[Dict[str, Any]]: + def node_to_body_ordering( + node: Node, include_children: bool = True, include_void: bool = False + ) -> List[Dict[str, Any]]: """ Walk this node, attributes and children in the correct order to create a node ordering for the purpose of mapping Node objects to their actual data @@ -220,21 +222,25 @@ class PackedOrdering: # Take care of 64 bit integers that are 32 bit aligned alignment = 4 - ordering.append({ - 'type': 'value', - 'node': node, - 'name': node.name, - 'alignment': alignment, - }) + ordering.append( + { + "type": "value", + "node": node, + "name": node.name, + "alignment": alignment, + } + ) order = sorted(node.attributes.keys()) for attr in order: - ordering.append({ - 'type': 'attribute', - 'node': node, - 'name': attr, - 'alignment': 4, - }) + ordering.append( + { + "type": "attribute", + "node": node, + "name": attr, + "alignment": 4, + } + ) if include_children: for child in node.children: @@ -272,26 +278,36 @@ class BinaryDecoder: """ length = self.stream.read_int() if length is None: - raise BinaryEncodingException("Ran out of data when attempting to read node name length!") + raise BinaryEncodingException( + "Ran out of data when attempting to read node name length!" + ) if not self.compressed: if length < 0x40: - raise BinaryEncodingException("Node name length under decompressed minimum") + raise BinaryEncodingException( + "Node name length under decompressed minimum" + ) elif length < 0x80: - length -= 0x3f + length -= 0x3F else: length_ex = self.stream.read_int() if length_ex is None: - raise BinaryEncodingException("Ran out of data when attempting to read node name length!") + raise BinaryEncodingException( + "Ran out of data when attempting to read node name length!" + ) length = (length << 8) | length_ex - length -= 0x7fbf + length -= 0x7FBF if length > BinaryEncoding.NAME_MAX_DECOMPRESSED: - raise BinaryEncodingException("Node name length over decompressed limit") + raise BinaryEncodingException( + "Node name length over decompressed limit" + ) name = self.stream.read_blob(length) if name is None: - raise BinaryEncodingException("Ran out of data when attempting to read node name!") + raise BinaryEncodingException( + "Ran out of data when attempting to read node name!" + ) return name.decode(self.encoding) @@ -303,19 +319,21 @@ class BinaryDecoder: def int_to_bin(integer: int) -> str: val = bin(integer)[2:] while len(val) < 8: - val = '0' + val + val = "0" + val return val - data = '' + data = "" for _ in range(binary_length): next_byte = self.stream.read_int() if next_byte is None: - raise BinaryEncodingException("Ran out of data when attempting to read node name!") + raise BinaryEncodingException( + "Ran out of data when attempting to read node name!" + ) data = data + int_to_bin(next_byte) - data_str = [data[i:(i + 6)] for i in range(0, len(data), 6)] + data_str = [data[i : (i + 6)] for i in range(0, len(data), 6)] data_int = [int(val, 2) for val in data_str] - ret = ''.join([Node.NODE_NAME_CHARS[val] for val in data_int]) + ret = "".join([Node.NODE_NAME_CHARS[val] for val in data_int]) ret = ret[:length] return ret @@ -334,7 +352,9 @@ class BinaryDecoder: while True: child_type = self.stream.read_int() if child_type is None: - raise BinaryEncodingException("Ran out of data when attempting to read node type!") + raise BinaryEncodingException( + "Ran out of data when attempting to read node type!" + ) if child_type == Node.END_OF_NODE: return node @@ -354,22 +374,28 @@ class BinaryDecoder: Node object """ if self.executed: - raise BinaryEncodingException("Logic error, should only call this once per instance") + raise BinaryEncodingException( + "Logic error, should only call this once per instance" + ) self.executed = True # Read the header first header_length = self.stream.read_int(4) if header_length is None: - raise BinaryEncodingException("Ran out of data when attempting to read header length!") + raise BinaryEncodingException( + "Ran out of data when attempting to read header length!" + ) node_type = self.stream.read_int() if node_type is None: - raise BinaryEncodingException("Ran out of data when attempting to read root node type!") + raise BinaryEncodingException( + "Ran out of data when attempting to read root node type!" + ) root = self.__read_node(node_type) eod = self.stream.read_int() if eod != Node.END_OF_DOCUMENT: - raise BinaryEncodingException(f'Unknown node type {eod} at end of document') + raise BinaryEncodingException(f"Unknown node type {eod} at end of document") # Skip by any padding while self.stream.pos < header_length + 4: @@ -382,19 +408,19 @@ class BinaryDecoder: # We have a body body = self.stream.read_blob(body_length) if body is None: - raise BinaryEncodingException('Body has insufficient data') + raise BinaryEncodingException("Body has insufficient data") ordering = PackedOrdering(body_length) values = PackedOrdering.node_to_body_ordering(root) for value in values: - node = value['node'] + node = value["node"] - if value['type'] == 'attribute': + if value["type"] == "attribute": size = None - enc = 's' - dtype = 'str' + enc = "s" + dtype = "str" array = False composite = False else: @@ -405,11 +431,11 @@ class BinaryDecoder: composite = node.is_composite if composite and array: - raise Exception('Logic error, no support for composite arrays!') + raise Exception("Logic error, no support for composite arrays!") if not array: # Scalar value - alignment = value['alignment'] + alignment = value["alignment"] if alignment == 1: loc = ordering.get_next_byte() @@ -418,59 +444,65 @@ class BinaryDecoder: elif alignment == 4: loc = ordering.get_next_int() if loc is None: - raise BinaryEncodingException("Ran out of data when attempting to read node data location!") + raise BinaryEncodingException( + "Ran out of data when attempting to read node data location!" + ) if size is None: # The size should be read from the first 4 bytes - size = struct.unpack('>I', body[loc:(loc + 4)])[0] + size = struct.unpack(">I", body[loc : (loc + 4)])[0] ordering.mark_used(size + 4, loc, round_to=4) loc = loc + 4 - decode_data = body[loc:(loc + size)] - decode_value = f'>{size}{enc}' + decode_data = body[loc : (loc + size)] + decode_value = f">{size}{enc}" else: # The size is built-in ordering.mark_used(size, loc) - decode_data = body[loc:(loc + size)] - decode_value = f'>{enc}' + decode_data = body[loc : (loc + size)] + decode_value = f">{enc}" if composite: val_list = list(struct.unpack(decode_value, decode_data)) - if value['type'] == 'attribute': - raise Exception('Logic error, shouldn\'t have composite attribute type!') + if value["type"] == "attribute": + raise Exception( + "Logic error, shouldn't have composite attribute type!" + ) node.set_value(val_list) continue val = struct.unpack(decode_value, decode_data)[0] - if dtype == 'str': + if dtype == "str": # Need to convert this from encoding to standard string. # Also, need to lob off the trailing null. try: - val = val[:-1].decode(self.encoding, 'replace') + val = val[:-1].decode(self.encoding, "replace") except UnicodeDecodeError: # Nothing we can do here pass - if value['type'] == 'attribute': - node.set_attribute(value['name'], val) + if value["type"] == "attribute": + node.set_attribute(value["name"], val) else: node.set_value(val) else: # Array value loc = ordering.get_next_int() if loc is None: - raise BinaryEncodingException("Ran out of data when attempting to read array length location!") + raise BinaryEncodingException( + "Ran out of data when attempting to read array length location!" + ) # The raw size in bytes - length = struct.unpack('>I', body[loc:(loc + 4)])[0] + length = struct.unpack(">I", body[loc : (loc + 4)])[0] elems = int(length / size) ordering.mark_used(length + 4, loc, round_to=4) loc = loc + 4 - decode_data = body[loc:(loc + length)] - decode_value = f'>{enc * elems}' + decode_data = body[loc : (loc + length)] + decode_value = f">{enc * elems}" val = struct.unpack(decode_value, decode_data) node.set_value([v for v in val]) @@ -483,7 +515,7 @@ class BinaryEncoder: A class capable of taking a Node tree and encoding it into a binary format. """ - def __init__(self, tree: Node, encoding: str, compressed: bool=True) -> None: + def __init__(self, tree: Node, encoding: str, compressed: bool = True) -> None: """ Initialize the object. @@ -518,14 +550,16 @@ class BinaryEncoder: length = len(encoded) if length > BinaryEncoding.NAME_MAX_DECOMPRESSED: - raise BinaryEncodingException("Node name length over decompressed limit") + raise BinaryEncodingException( + "Node name length over decompressed limit" + ) if length < 64: - self.stream.write_int(length + 0x3f) + self.stream.write_int(length + 0x3F) else: - length += 0x7fbf - self.stream.write_int((length >> 8) & 0xff) - self.stream.write_int(length & 0xff) + length += 0x7FBF + self.stream.write_int((length >> 8) & 0xFF) + self.stream.write_int(length & 0xFF) self.stream.write_blob(encoded) return @@ -534,20 +568,20 @@ class BinaryEncoder: val = bin(index)[2:] while len(val) < 6: - val = '0' + val + val = "0" + val return val[-6:] # Convert to six bit bytes length = len(name) - data = ''.join([char_to_bin(c) for c in name]) + data = "".join([char_to_bin(c) for c in name]) # Pad out the rest with zeros while (len(data) & 0x7) != 0: - data = data + '0' + data = data + "0" # Convert to 8-bit bytes - data_chunks = [data[i:(i + 8)] for i in range(0, len(data), 8)] + data_chunks = [data[i : (i + 8)] for i in range(0, len(data), 8)] data_int = [int(val, 2) for val in data_chunks] # Output @@ -564,15 +598,17 @@ class BinaryEncoder: Parameters: node - A Node which should be encoded. """ - to_write = PackedOrdering.node_to_body_ordering(node, include_children=False, include_void=True) + to_write = PackedOrdering.node_to_body_ordering( + node, include_children=False, include_void=True + ) for thing in to_write: # First, write the type of this node out - if thing['type'] == 'value': - self.stream.write_int(thing['node'].type) + if thing["type"] == "value": + self.stream.write_int(thing["node"].type) else: self.stream.write_int(Node.ATTR_TYPE) # Now, write the name out - self.__write_node_name(thing['name']) + self.__write_node_name(thing["name"]) # Now, write out the children for child in node.children: @@ -630,15 +666,15 @@ class BinaryEncoder: ordering = PackedOrdering(0, allow_expansion=True) for value in values: - node = value['node'] + node = value["node"] - if value['type'] == 'attribute': + if value["type"] == "attribute": size = None - enc = 's' - dtype = 'str' + enc = "s" + dtype = "str" array = False composite = False - val = node.attribute(value['name']) + val = node.attribute(value["name"]) else: size = node.data_length enc = node.data_encoding @@ -654,7 +690,7 @@ class BinaryEncoder: if not array: # Scalar value - alignment = value['alignment'] + alignment = value["alignment"] if alignment == 1: loc = ordering.get_next_byte() @@ -663,9 +699,11 @@ class BinaryEncoder: elif alignment == 4: loc = ordering.get_next_int() if loc is None: - raise BinaryEncodingException("Ran out of data when attempting to allocate node location!") + raise BinaryEncodingException( + "Ran out of data when attempting to allocate node location!" + ) - if dtype == 'str': + if dtype == "str": # Need to convert this to encoding from standard string. # Also, need to lob off the trailing null. if not isinstance(val, str): @@ -674,21 +712,23 @@ class BinaryEncoder: ) try: - valbytes = val.encode(self.encoding) + b'\0' + valbytes = val.encode(self.encoding) + b"\0" except UnicodeEncodeError: raise BinaryEncodingException( f'Node \'{value["name"]}\' has un-encodable string value \'{val}\'' ) size = len(valbytes) - self.__add_data(struct.pack('>I', size) + valbytes, size + 4, loc) + self.__add_data( + struct.pack(">I", size) + valbytes, size + 4, loc + ) ordering.mark_used(size + 4, loc, round_to=4) # We took care of this one continue - elif dtype == 'bin': + elif dtype == "bin": # Store raw binary size = len(val) - self.__add_data(struct.pack('>I', size) + val, size + 4, loc) + self.__add_data(struct.pack(">I", size) + val, size + 4, loc) ordering.mark_used(size + 4, loc, round_to=4) # We took care of this one @@ -696,43 +736,51 @@ class BinaryEncoder: elif composite: # Array, but not, somewhat silly if size is None: - raise Exception("Logic error, node size not set yet this is not an attribute!") + raise Exception( + "Logic error, node size not set yet this is not an attribute!" + ) - encode_value = f'>{enc}' + encode_value = f">{enc}" self.__add_data(struct.pack(encode_value, *val), size, loc) ordering.mark_used(size, loc) # We took care of this one continue - elif dtype == 'bool': + elif dtype == "bool": val = 1 if val else 0 # The size is built-in, emit it if size is None: - raise Exception("Logic error, node size not set yet this is not an attribute!") + raise Exception( + "Logic error, node size not set yet this is not an attribute!" + ) - encode_value = f'>{enc}' + encode_value = f">{enc}" self.__add_data(struct.pack(encode_value, val), size, loc) ordering.mark_used(size, loc) else: # Array value loc = ordering.get_next_int() if loc is None: - raise BinaryEncodingException("Ran out of data when attempting allocate array location!") + raise BinaryEncodingException( + "Ran out of data when attempting allocate array location!" + ) if size is None: - raise Exception("Logic error, node size not set yet this is not an attribute!") + raise Exception( + "Logic error, node size not set yet this is not an attribute!" + ) # The raw size in bytes elems = len(val) length = elems * size # Write out the header (number of bytes taken up) - data = struct.pack('>I', length) - encode_value = f'>{enc}' + data = struct.pack(">I", length) + encode_value = f">{enc}" # Write out data one element at a time for v in val: - if dtype == 'bool': + if dtype == "bool": data = data + struct.pack(encode_value, 1 if v else 0) else: data = data + struct.pack(encode_value, v) @@ -740,18 +788,21 @@ class BinaryEncoder: self.__add_data(data, length + 4, loc) ordering.mark_used(length + 4, loc, round_to=4) - return b''.join([ - struct.pack('>I', header_length), - header, - struct.pack('>I', self.__body_len), - bytes(self.__body), - ]) + return b"".join( + [ + struct.pack(">I", header_length), + header, + struct.pack(">I", self.__body_len), + bytes(self.__body), + ] + ) class BinaryEncoding: """ Wrapper class representing a Binary Encoding. """ + MAGIC: Final[int] = 0xA0 COMPRESSED_WITH_DATA: Final[int] = 0x42 @@ -794,7 +845,7 @@ class BinaryEncoding: return "shift-jis" return enc - def decode(self, data: bytes, skip_on_exceptions: bool=False) -> Optional[Node]: + def decode(self, data: bytes, skip_on_exceptions: bool = False) -> Optional[Node]: """ Given a data blob, decode the data with the current encoding. Will also set the class property value 'encoding' to the encoding used @@ -808,7 +859,9 @@ class BinaryEncoding: if we couldn't decode the object for some reason. """ try: - data_magic, contents, encoding_raw, encoding_swapped = struct.unpack(">BBBB", data[0:4]) + data_magic, contents, encoding_raw, encoding_swapped = struct.unpack( + ">BBBB", data[0:4] + ) except struct.error: # Couldn't even parse magic return None @@ -819,10 +872,12 @@ class BinaryEncoding: return None self.compressed = contents in [ - BinaryEncoding.COMPRESSED_WITH_DATA, BinaryEncoding.COMPRESSED_WITHOUT_DATA + BinaryEncoding.COMPRESSED_WITH_DATA, + BinaryEncoding.COMPRESSED_WITHOUT_DATA, ] if not self.compressed and contents not in [ - BinaryEncoding.DECOMPRESSED_WITH_DATA, BinaryEncoding.DECOMPRESSED_WITHOUT_DATA + BinaryEncoding.DECOMPRESSED_WITH_DATA, + BinaryEncoding.DECOMPRESSED_WITHOUT_DATA, ]: return None @@ -843,7 +898,9 @@ class BinaryEncoding: else: return None - def encode(self, tree: Node, encoding: Optional[str]=None, compressed: bool=True) -> bytes: + def encode( + self, tree: Node, encoding: Optional[str] = None, compressed: bool = True + ) -> bytes: """ Given a tree of Node objects, encode the data with the current encoding. @@ -858,7 +915,7 @@ class BinaryEncoding: if encoding is None: encoding = self.encoding if encoding is None: - raise BinaryEncodingException('Unknown encoding') + raise BinaryEncodingException("Unknown encoding") encoding_magic = None for magic, encstr in BinaryEncoding.ENCODINGS.items(): @@ -871,10 +928,15 @@ class BinaryEncoding: encoder = BinaryEncoder(tree, self.__sanitize_encoding(encoding), compressed) data = encoder.get_data() - return struct.pack( - ">BBBB", - BinaryEncoding.MAGIC, - BinaryEncoding.COMPRESSED_WITH_DATA if compressed else BinaryEncoding.DECOMPRESSED_WITH_DATA, - encoding_magic, - (~encoding_magic & 0xFF) - ) + data + return ( + struct.pack( + ">BBBB", + BinaryEncoding.MAGIC, + BinaryEncoding.COMPRESSED_WITH_DATA + if compressed + else BinaryEncoding.DECOMPRESSED_WITH_DATA, + encoding_magic, + (~encoding_magic & 0xFF), + ) + + data + ) diff --git a/bemani/protocol/lz77.py b/bemani/protocol/lz77.py index cd1be36..7218a03 100644 --- a/bemani/protocol/lz77.py +++ b/bemani/protocol/lz77.py @@ -11,12 +11,26 @@ from .. import package_root try: clib = None clib_path = os.path.join(package_root, "protocol") - files = [f for f in os.listdir(clib_path) if f.startswith("lz77cpp") and f.endswith(".so")] + files = [ + f + for f in os.listdir(clib_path) + if f.startswith("lz77cpp") and f.endswith(".so") + ] if len(files) > 0: clib = ctypes.cdll.LoadLibrary(os.path.join(clib_path, files[0])) - clib.decompress.argtypes = (ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_int) + clib.decompress.argtypes = ( + ctypes.c_char_p, + ctypes.c_int, + ctypes.c_char_p, + ctypes.c_int, + ) clib.decompress.restype = ctypes.c_int - clib.compress.argtypes = (ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_int) + clib.compress.argtypes = ( + ctypes.c_char_p, + ctypes.c_int, + ctypes.c_char_p, + ctypes.c_int, + ) clib.compress.restype = ctypes.c_int except Exception: clib = None @@ -35,6 +49,7 @@ class Lz77Decompress: over-the-wire compression of XML data, as well as compression inside a decent amount of file formats found in various Konami games. """ + RING_LENGTH: Final[int] = 0x1000 FLAG_COPY: Final[int] = 1 @@ -57,7 +72,7 @@ class Lz77Decompress: self.pending_copy_pos: int = 0 self.pending_copy_max: int = 0 self.ringlength: int = backref or self.RING_LENGTH - self.ring: bytes = b'\x00' * self.ringlength + self.ring: bytes = b"\x00" * self.ringlength def _ring_read(self, copy_pos: int, copy_len: int) -> Generator[bytes, None, None]: """ @@ -74,7 +89,7 @@ class Lz77Decompress: # Copy the whole thing out, we have enough space to do so amount = copy_len - ret = self.ring[copy_pos:(copy_pos + amount)] + ret = self.ring[copy_pos : (copy_pos + amount)] self._ring_write(ret) yield ret @@ -95,7 +110,11 @@ class Lz77Decompress: if amount > (self.ringlength - self.write_pos): amount = self.ringlength - self.write_pos - self.ring = self.ring[:self.write_pos] + bytedata[:amount] + self.ring[(self.write_pos + amount):] + self.ring = ( + self.ring[: self.write_pos] + + bytedata[:amount] + + self.ring[(self.write_pos + amount) :] + ) bytedata = bytedata[amount:] self.write_pos = (self.write_pos + amount) % self.ringlength @@ -151,7 +170,7 @@ class Lz77Decompress: amount += 1 # Grab chunk right out of the data source - b = self.data[self.read_pos:(self.read_pos + amount)] + b = self.data[self.read_pos : (self.read_pos + amount)] self._ring_write(b) yield b @@ -179,7 +198,7 @@ class Lz77Decompress: self.eof = True return if self.left == 1: - raise LzException('Unexpected EOF mid-backref') + raise LzException("Unexpected EOF mid-backref") hi = self.data[self.read_pos] lo = self.data[self.read_pos + 1] @@ -203,7 +222,7 @@ class Lz77Decompress: # Only copy the available bytes copy_len = copy_pos - copy_pos = (self.write_pos - copy_pos) + copy_pos = self.write_pos - copy_pos while copy_pos < 0: copy_pos += self.ringlength copy_pos = copy_pos % self.ringlength @@ -322,7 +341,7 @@ class Lz77Compress: # a backref. flags |= self.FLAG_COPY << flagpos - chunk = self.data[self.read_pos:(self.read_pos + 1)] + chunk = self.data[self.read_pos : (self.read_pos + 1)] data[flagpos] = chunk self._ring_write(chunk) @@ -335,19 +354,22 @@ class Lz77Compress: # Iterate over all spots where the first byte equals, and is in range. earliest = max(0, self.bytes_written - (self.ringlength - 1)) - index = self.data[self.read_pos:(self.read_pos + 3)] + index = self.data[self.read_pos : (self.read_pos + 3)] updated_backref_locations: Set[int] = set( - absolute_pos for absolute_pos in self.starts[index] + absolute_pos + for absolute_pos in self.starts[index] if absolute_pos >= earliest ) self.starts[index] = updated_backref_locations - possible_backref_locations: List[int] = list(updated_backref_locations) + possible_backref_locations: List[int] = list( + updated_backref_locations + ) # Output the data as a copy if we couldn't find a backref if not possible_backref_locations: flags |= self.FLAG_COPY << flagpos - chunk = self.data[self.read_pos:(self.read_pos + 1)] + chunk = self.data[self.read_pos : (self.read_pos + 1)] data[flagpos] = chunk self._ring_write(chunk) @@ -363,10 +385,15 @@ class Lz77Compress: copy_amount = 3 while copy_amount < backref_amount: # First, let's see if we have any 3-wide chunks to consume. - index = self.data[(self.read_pos + copy_amount):(self.read_pos + copy_amount + 3)] + index = self.data[ + (self.read_pos + copy_amount) : ( + self.read_pos + copy_amount + 3 + ) + ] locations = self.starts[index] new_backref_locations: List[int] = [ - absolute_pos for absolute_pos in possible_backref_locations + absolute_pos + for absolute_pos in possible_backref_locations if absolute_pos + copy_amount in locations ] @@ -379,9 +406,12 @@ class Lz77Compress: # Check our existing locations to figure out if we still have # longest prefixes of 1 or 2 left. while copy_amount < backref_amount: - locations = self.locations[self.data[self.read_pos + copy_amount]] + locations = self.locations[ + self.data[self.read_pos + copy_amount] + ] new_backref_locations = [ - absolute_pos for absolute_pos in possible_backref_locations + absolute_pos + for absolute_pos in possible_backref_locations if absolute_pos + copy_amount in locations ] @@ -391,7 +421,13 @@ class Lz77Compress: break # Mark that we're copying an extra byte from the backref. - self._ring_write(self.data[(self.read_pos + copy_amount):(self.read_pos + copy_amount + 1)]) + self._ring_write( + self.data[ + (self.read_pos + copy_amount) : ( + self.read_pos + copy_amount + 1 + ) + ] + ) copy_amount += 1 possible_backref_locations = new_backref_locations @@ -455,7 +491,7 @@ class Lz77: raise LzException("Unknown exception in C++ code!") else: lz = Lz77Decompress(data, backref=self.backref) - return b''.join(lz.decompress_bytes()) + return b"".join(lz.decompress_bytes()) def compress(self, data: bytes) -> bytes: """ @@ -485,4 +521,4 @@ class Lz77: raise LzException("Unknown exception in C++ code!") else: lz = Lz77Compress(data, backref=self.backref) - return b''.join(lz.compress_bytes()) + return b"".join(lz.compress_bytes()) diff --git a/bemani/protocol/node.py b/bemani/protocol/node.py index dbbc381..7413c18 100644 --- a/bemani/protocol/node.py +++ b/bemani/protocol/node.py @@ -21,7 +21,10 @@ class Node: supported for a node to not have a value or children. This also includes a decent amount of constructor helper classmethods to make constructing a tree from source code easier. """ - NODE_NAME_CHARS: Final[str] = "0123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz" + + NODE_NAME_CHARS: Final[ + str + ] = "0123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz" NODE_TYPE_VOID: Final[int] = 1 NODE_TYPE_S8: Final[int] = 2 @@ -76,280 +79,280 @@ class Node: NODE_TYPES: Final[Dict[int, Dict[str, Any]]] = { NODE_TYPE_VOID: { - 'name': 'void', - 'enc': '', - 'int': False, - 'composite': False, + "name": "void", + "enc": "", + "int": False, + "composite": False, }, NODE_TYPE_S8: { - 'name': 's8', - 'enc': 'b', - 'int': True, - 'composite': False, + "name": "s8", + "enc": "b", + "int": True, + "composite": False, }, NODE_TYPE_U8: { - 'name': 'u8', - 'enc': 'B', - 'int': True, - 'composite': False, + "name": "u8", + "enc": "B", + "int": True, + "composite": False, }, NODE_TYPE_S16: { - 'name': 's16', - 'enc': 'h', - 'int': True, - 'composite': False, + "name": "s16", + "enc": "h", + "int": True, + "composite": False, }, NODE_TYPE_U16: { - 'name': 'u16', - 'enc': 'H', - 'int': True, - 'composite': False, + "name": "u16", + "enc": "H", + "int": True, + "composite": False, }, NODE_TYPE_S32: { - 'name': 's32', - 'enc': 'i', - 'int': True, - 'composite': False, + "name": "s32", + "enc": "i", + "int": True, + "composite": False, }, NODE_TYPE_U32: { - 'name': 'u32', - 'enc': 'I', - 'int': True, - 'composite': False, + "name": "u32", + "enc": "I", + "int": True, + "composite": False, }, NODE_TYPE_S64: { - 'name': 's64', - 'enc': 'q', - 'int': True, - 'composite': False, + "name": "s64", + "enc": "q", + "int": True, + "composite": False, }, NODE_TYPE_U64: { - 'name': 'u64', - 'enc': 'Q', - 'int': True, - 'composite': False, + "name": "u64", + "enc": "Q", + "int": True, + "composite": False, }, NODE_TYPE_BIN: { - 'name': 'bin', - 'enc': 's', - 'int': False, - 'composite': False, + "name": "bin", + "enc": "s", + "int": False, + "composite": False, }, NODE_TYPE_STR: { - 'name': 'str', - 'enc': 's', - 'int': False, - 'composite': False, + "name": "str", + "enc": "s", + "int": False, + "composite": False, }, NODE_TYPE_IP4: { - 'name': 'ip4', - 'enc': '4s', - 'int': False, - 'composite': False, + "name": "ip4", + "enc": "4s", + "int": False, + "composite": False, }, NODE_TYPE_TIME: { - 'name': 'time', - 'enc': 'I', - 'int': True, - 'composite': False, + "name": "time", + "enc": "I", + "int": True, + "composite": False, }, NODE_TYPE_FLOAT: { - 'name': 'float', - 'enc': 'f', - 'int': False, - 'composite': False, + "name": "float", + "enc": "f", + "int": False, + "composite": False, }, NODE_TYPE_DOUBLE: { - 'name': 'double', - 'enc': 'd', - 'int': False, - 'composite': False, + "name": "double", + "enc": "d", + "int": False, + "composite": False, }, NODE_TYPE_2S8: { - 'name': '2s8', - 'enc': 'bb', - 'int': True, - 'composite': True, + "name": "2s8", + "enc": "bb", + "int": True, + "composite": True, }, NODE_TYPE_2U8: { - 'name': '2u8', - 'enc': 'BB', - 'int': True, - 'composite': True, + "name": "2u8", + "enc": "BB", + "int": True, + "composite": True, }, NODE_TYPE_2S16: { - 'name': '2s16', - 'enc': 'hh', - 'int': True, - 'composite': True, + "name": "2s16", + "enc": "hh", + "int": True, + "composite": True, }, NODE_TYPE_2U16: { - 'name': '2u16', - 'enc': 'HH', - 'int': True, - 'composite': True, + "name": "2u16", + "enc": "HH", + "int": True, + "composite": True, }, NODE_TYPE_2S32: { - 'name': '2s32', - 'enc': 'ii', - 'int': True, - 'composite': True, + "name": "2s32", + "enc": "ii", + "int": True, + "composite": True, }, NODE_TYPE_2U32: { - 'name': '2u32', - 'enc': 'II', - 'int': True, - 'composite': True, + "name": "2u32", + "enc": "II", + "int": True, + "composite": True, }, NODE_TYPE_2S64: { - 'name': '2s64', - 'enc': 'qq', - 'int': True, - 'composite': True, + "name": "2s64", + "enc": "qq", + "int": True, + "composite": True, }, NODE_TYPE_2U64: { - 'name': '2u64', - 'enc': 'QQ', - 'int': True, - 'composite': True, + "name": "2u64", + "enc": "QQ", + "int": True, + "composite": True, }, NODE_TYPE_2FLOAT: { - 'name': '2float', - 'enc': 'ff', - 'int': False, - 'composite': True, + "name": "2float", + "enc": "ff", + "int": False, + "composite": True, }, NODE_TYPE_2DOUBLE: { - 'name': '2double', - 'enc': 'dd', - 'int': False, - 'composite': True, + "name": "2double", + "enc": "dd", + "int": False, + "composite": True, }, NODE_TYPE_3S8: { - 'name': '3s8', - 'enc': 'bbb', - 'int': True, - 'composite': True, + "name": "3s8", + "enc": "bbb", + "int": True, + "composite": True, }, NODE_TYPE_3U8: { - 'name': '3u8', - 'enc': 'BBB', - 'int': True, - 'composite': True, + "name": "3u8", + "enc": "BBB", + "int": True, + "composite": True, }, NODE_TYPE_3S16: { - 'name': '3s16', - 'enc': 'hhh', - 'int': True, - 'composite': True, + "name": "3s16", + "enc": "hhh", + "int": True, + "composite": True, }, NODE_TYPE_3U16: { - 'name': '3u16', - 'enc': 'HHH', - 'int': True, - 'composite': True, + "name": "3u16", + "enc": "HHH", + "int": True, + "composite": True, }, NODE_TYPE_3S32: { - 'name': '3s32', - 'enc': 'iii', - 'int': True, - 'composite': True, + "name": "3s32", + "enc": "iii", + "int": True, + "composite": True, }, NODE_TYPE_3U32: { - 'name': '3u32', - 'enc': 'III', - 'int': True, - 'composite': True, + "name": "3u32", + "enc": "III", + "int": True, + "composite": True, }, NODE_TYPE_3S64: { - 'name': '3s64', - 'enc': 'qqq', - 'int': True, - 'composite': True, + "name": "3s64", + "enc": "qqq", + "int": True, + "composite": True, }, NODE_TYPE_3U64: { - 'name': '3u64', - 'enc': 'QQQ', - 'int': True, - 'composite': True, + "name": "3u64", + "enc": "QQQ", + "int": True, + "composite": True, }, NODE_TYPE_3FLOAT: { - 'name': '3float', - 'enc': 'fff', - 'int': False, - 'composite': True, + "name": "3float", + "enc": "fff", + "int": False, + "composite": True, }, NODE_TYPE_3DOUBLE: { - 'name': '3double', - 'enc': 'ddd', - 'int': False, - 'composite': True, + "name": "3double", + "enc": "ddd", + "int": False, + "composite": True, }, NODE_TYPE_4U8: { - 'name': '4u8', - 'enc': 'BBBB', - 'int': True, - 'composite': True, + "name": "4u8", + "enc": "BBBB", + "int": True, + "composite": True, }, NODE_TYPE_4S8: { - 'name': '4s8', - 'enc': 'bbbb', - 'int': True, - 'composite': True, + "name": "4s8", + "enc": "bbbb", + "int": True, + "composite": True, }, NODE_TYPE_4U16: { - 'name': '4u16', - 'enc': 'HHHH', - 'int': True, - 'composite': True, + "name": "4u16", + "enc": "HHHH", + "int": True, + "composite": True, }, NODE_TYPE_4S16: { - 'name': '4s16', - 'enc': 'hhhh', - 'int': True, - 'composite': True, + "name": "4s16", + "enc": "hhhh", + "int": True, + "composite": True, }, NODE_TYPE_4S32: { - 'name': '4s32', - 'enc': 'iiii', - 'int': True, - 'composite': True, + "name": "4s32", + "enc": "iiii", + "int": True, + "composite": True, }, NODE_TYPE_4U32: { - 'name': '4u32', - 'enc': 'IIII', - 'int': True, - 'composite': True, + "name": "4u32", + "enc": "IIII", + "int": True, + "composite": True, }, NODE_TYPE_4S64: { - 'name': '4s64', - 'enc': 'qqqq', - 'int': True, - 'composite': True, + "name": "4s64", + "enc": "qqqq", + "int": True, + "composite": True, }, NODE_TYPE_4U64: { - 'name': '4u64', - 'enc': 'QQQQ', - 'int': True, - 'composite': True, + "name": "4u64", + "enc": "QQQQ", + "int": True, + "composite": True, }, NODE_TYPE_4FLOAT: { - 'name': '4float', - 'enc': 'ffff', - 'int': False, - 'composite': True, + "name": "4float", + "enc": "ffff", + "int": False, + "composite": True, }, NODE_TYPE_4DOUBLE: { - 'name': '4double', - 'enc': 'dddd', - 'int': False, - 'composite': True, + "name": "4double", + "enc": "dddd", + "int": False, + "composite": True, }, NODE_TYPE_BOOL: { - 'name': 'bool', - 'enc': 'b', - 'int': False, - 'composite': False, + "name": "bool", + "enc": "b", + "int": False, + "composite": False, }, } ARRAY_BIT: Final[int] = 0x40 @@ -358,135 +361,135 @@ class Node: END_OF_DOCUMENT: Final[int] = 0xFF @staticmethod - def void(name: str) -> 'Node': + def void(name: str) -> "Node": return Node(name=name, type=Node.NODE_TYPE_VOID) @staticmethod - def string(name: str, value: str) -> 'Node': + def string(name: str, value: str) -> "Node": return Node(name=name, type=Node.NODE_TYPE_STR, value=value) @staticmethod - def binary(name: str, value: bytes) -> 'Node': + def binary(name: str, value: bytes) -> "Node": return Node(name=name, type=Node.NODE_TYPE_BIN, value=value) @staticmethod - def float(name: str, value: _renamed_float) -> 'Node': + def float(name: str, value: _renamed_float) -> "Node": return Node(name=name, type=Node.NODE_TYPE_FLOAT, value=value) @staticmethod - def bool(name: str, value: _renamed_bool) -> 'Node': + def bool(name: str, value: _renamed_bool) -> "Node": return Node(name=name, type=Node.NODE_TYPE_BOOL, value=value) @staticmethod - def ipv4(name: str, value: str) -> 'Node': + def ipv4(name: str, value: str) -> "Node": return Node(name=name, type=Node.NODE_TYPE_IP4, value=value) @staticmethod - def time(name: str, value: int) -> 'Node': + def time(name: str, value: int) -> "Node": return Node(name=name, type=Node.NODE_TYPE_TIME, value=value) @staticmethod - def u8(name: str, value: int) -> 'Node': + def u8(name: str, value: int) -> "Node": Node.__validate(Node.NODE_TYPE_U8, name, value) return Node(name=name, type=Node.NODE_TYPE_U8, value=value) @staticmethod - def s8(name: str, value: int) -> 'Node': + def s8(name: str, value: int) -> "Node": Node.__validate(Node.NODE_TYPE_S8, name, value) return Node(name=name, type=Node.NODE_TYPE_S8, value=value) @staticmethod - def u16(name: str, value: int) -> 'Node': + def u16(name: str, value: int) -> "Node": Node.__validate(Node.NODE_TYPE_U16, name, value) return Node(name=name, type=Node.NODE_TYPE_U16, value=value) @staticmethod - def s16(name: str, value: int) -> 'Node': + def s16(name: str, value: int) -> "Node": Node.__validate(Node.NODE_TYPE_S16, name, value) return Node(name=name, type=Node.NODE_TYPE_S16, value=value) @staticmethod - def u32(name: str, value: int) -> 'Node': + def u32(name: str, value: int) -> "Node": Node.__validate(Node.NODE_TYPE_U32, name, value) return Node(name=name, type=Node.NODE_TYPE_U32, value=value) @staticmethod - def s32(name: str, value: int) -> 'Node': + def s32(name: str, value: int) -> "Node": Node.__validate(Node.NODE_TYPE_S32, name, value) return Node(name=name, type=Node.NODE_TYPE_S32, value=value) @staticmethod - def u64(name: str, value: int) -> 'Node': + def u64(name: str, value: int) -> "Node": Node.__validate(Node.NODE_TYPE_U64, name, value) return Node(name=name, type=Node.NODE_TYPE_U64, value=value) @staticmethod - def s64(name: str, value: int) -> 'Node': + def s64(name: str, value: int) -> "Node": Node.__validate(Node.NODE_TYPE_S64, name, value) return Node(name=name, type=Node.NODE_TYPE_S64, value=value) @staticmethod - def time_array(name: str, values: List[int]) -> 'Node': + def time_array(name: str, values: List[int]) -> "Node": return Node(name=name, type=Node.NODE_TYPE_TIME, array=True, value=values) @staticmethod - def float_array(name: str, values: List[_renamed_float]) -> 'Node': + def float_array(name: str, values: List[_renamed_float]) -> "Node": return Node(name=name, type=Node.NODE_TYPE_FLOAT, array=True, value=values) @staticmethod - def bool_array(name: str, values: List[_renamed_bool]) -> 'Node': + def bool_array(name: str, values: List[_renamed_bool]) -> "Node": return Node(name=name, type=Node.NODE_TYPE_BOOL, array=True, value=values) @staticmethod - def u8_array(name: str, values: List[int]) -> 'Node': + def u8_array(name: str, values: List[int]) -> "Node": for value in values: Node.__validate(Node.NODE_TYPE_U8, name, value) return Node(name=name, type=Node.NODE_TYPE_U8, array=True, value=values) @staticmethod - def s8_array(name: str, values: List[int]) -> 'Node': + def s8_array(name: str, values: List[int]) -> "Node": for value in values: Node.__validate(Node.NODE_TYPE_S8, name, value) return Node(name=name, type=Node.NODE_TYPE_S8, array=True, value=values) @staticmethod - def u16_array(name: str, values: List[int]) -> 'Node': + def u16_array(name: str, values: List[int]) -> "Node": for value in values: Node.__validate(Node.NODE_TYPE_U16, name, value) return Node(name=name, type=Node.NODE_TYPE_U16, array=True, value=values) @staticmethod - def s16_array(name: str, values: List[int]) -> 'Node': + def s16_array(name: str, values: List[int]) -> "Node": for value in values: Node.__validate(Node.NODE_TYPE_S16, name, value) return Node(name=name, type=Node.NODE_TYPE_S16, array=True, value=values) @staticmethod - def u32_array(name: str, values: List[int]) -> 'Node': + def u32_array(name: str, values: List[int]) -> "Node": for value in values: Node.__validate(Node.NODE_TYPE_U32, name, value) return Node(name=name, type=Node.NODE_TYPE_U32, array=True, value=values) @staticmethod - def s32_array(name: str, values: List[int]) -> 'Node': + def s32_array(name: str, values: List[int]) -> "Node": for value in values: Node.__validate(Node.NODE_TYPE_S32, name, value) return Node(name=name, type=Node.NODE_TYPE_S32, array=True, value=values) @staticmethod - def u64_array(name: str, values: List[int]) -> 'Node': + def u64_array(name: str, values: List[int]) -> "Node": for value in values: Node.__validate(Node.NODE_TYPE_U64, name, value) return Node(name=name, type=Node.NODE_TYPE_U64, array=True, value=values) @staticmethod - def s64_array(name: str, values: List[int]) -> 'Node': + def s64_array(name: str, values: List[int]) -> "Node": for value in values: Node.__validate(Node.NODE_TYPE_S64, name, value) return Node(name=name, type=Node.NODE_TYPE_S64, array=True, value=values) @staticmethod - def fouru8(name: str, values: List[int]) -> 'Node': + def fouru8(name: str, values: List[int]) -> "Node": for value in values: Node.__validate(Node.NODE_TYPE_U8, name, value) return Node(name=name, type=Node.NODE_TYPE_4U8, value=values) @@ -505,7 +508,7 @@ class Node: An integer specifying the node type or None if not found. """ for nodetype in Node.NODE_TYPES: - if typename.lower() == Node.NODE_TYPES[nodetype]['name']: + if typename.lower() == Node.NODE_TYPES[nodetype]["name"]: return nodetype return None @@ -514,30 +517,36 @@ class Node: def __validate(nodetype: int, name: str, value: int) -> None: if nodetype == Node.NODE_TYPE_U8: if value < 0 or value > 255: - raise NodeException(f'Invalid value {value} for u8 {name}') + raise NodeException(f"Invalid value {value} for u8 {name}") elif nodetype == Node.NODE_TYPE_S8: if value < -128 or value > 127: - raise NodeException(f'Invalid value {value} for s8 {name}') + raise NodeException(f"Invalid value {value} for s8 {name}") elif nodetype == Node.NODE_TYPE_U16: if value < 0 or value > 65535: - raise NodeException(f'Invalid value {value} for u16 {name}') + raise NodeException(f"Invalid value {value} for u16 {name}") elif nodetype == Node.NODE_TYPE_S16: if value < -32768 or value > 32767: - raise NodeException(f'Invalid value {value} for s16 {name}') + raise NodeException(f"Invalid value {value} for s16 {name}") elif nodetype == Node.NODE_TYPE_U32: if value < 0 or value > 4294967295: - raise NodeException(f'Invalid value {value} for u32 {name}') + raise NodeException(f"Invalid value {value} for u32 {name}") elif nodetype == Node.NODE_TYPE_S32: if value < -2147483648 or value > 2147483647: - raise NodeException(f'Invalid value {value} for s32 {name}') + raise NodeException(f"Invalid value {value} for s32 {name}") elif nodetype == Node.NODE_TYPE_U64: if value < 0 or value > 18446744073709551615: - raise NodeException(f'Invalid value {value} for u64 {name}') + raise NodeException(f"Invalid value {value} for u64 {name}") elif nodetype == Node.NODE_TYPE_S64: if value < -9223372036854775808 or value > 9223372036854775807: - raise NodeException(f'Invalid value {value} for s32 {name}') + raise NodeException(f"Invalid value {value} for s32 {name}") - def __init__(self, name: Optional[str]=None, type: Optional[int]=None, array: Optional[_renamed_bool]=None, value: Optional[Any]=None) -> None: + def __init__( + self, + name: Optional[str] = None, + type: Optional[int] = None, + array: Optional[_renamed_bool] = None, + value: Optional[Any] = None, + ) -> None: """ Initialize a node, with an optional name and type. @@ -578,7 +587,7 @@ class Node: # Ensure it isn't a violation for char in name: if char not in Node.NODE_NAME_CHARS: - raise NodeException(f'Invalid node name {name}') + raise NodeException(f"Invalid node name {name}") self.__name = name @@ -591,10 +600,10 @@ class Node: A string node name. """ if self.__name is None: - raise Exception('Logic error, tried to fetch name before setting!') + raise Exception("Logic error, tried to fetch name before setting!") return self.__name - def set_type(self, type: int, array: Optional[_renamed_bool]=None) -> None: + def set_type(self, type: int, array: Optional[_renamed_bool] = None) -> None: """ Set the type of the node to a new integer type, as specified in Node.NODE_TYPES. @@ -616,7 +625,7 @@ class Node: self.__translated_type = Node.NODE_TYPES[type & (~Node.ARRAY_BIT)] self.__type = type except KeyError: - raise NodeException(f'Unknown node type {type} on node name {self.__name}') + raise NodeException(f"Unknown node type {type} on node name {self.__name}") @property def type(self) -> int: @@ -628,7 +637,7 @@ class Node: bit ARRAY_BIT might be set. """ if self.__type is None: - raise Exception('Logic error, tried to fetch type before setting!') + raise Exception("Logic error, tried to fetch type before setting!") return self.__type @property @@ -640,8 +649,10 @@ class Node: A string data type name. This string can be fed to typename_to_type to get the original type back. """ if self.__translated_type is None: - raise Exception('Logic error, tried to fetch data type before setting type!') - return self.__translated_type['name'] + raise Exception( + "Logic error, tried to fetch data type before setting type!" + ) + return self.__translated_type["name"] @property def data_length(self) -> Optional[int]: @@ -653,10 +664,12 @@ class Node: An integer data length, or None if this node's element has variable length. """ if self.__translated_type is None: - raise Exception('Logic error, tried to fetch data length before setting type!') - if self.__translated_type['name'] in {'bin', 'str'}: + raise Exception( + "Logic error, tried to fetch data length before setting type!" + ) + if self.__translated_type["name"] in {"bin", "str"}: return None - return struct.calcsize(self.__translated_type['enc']) + return struct.calcsize(self.__translated_type["enc"]) @property def data_encoding(self) -> str: @@ -667,10 +680,12 @@ class Node: A character that can be passed to struct.pack or struct.unpack. """ if self.__translated_type is None: - raise Exception('Logic error, tried to fetch data encoding before setting type!') - return self.__translated_type['enc'] + raise Exception( + "Logic error, tried to fetch data encoding before setting type!" + ) + return self.__translated_type["enc"] - def set_attribute(self, attr: str, val: str='') -> None: + def set_attribute(self, attr: str, val: str = "") -> None: """ Set an attribute to a particular string value on this node. @@ -681,7 +696,7 @@ class Node: """ self.__attrs[attr] = val - def attribute(self, attr: str, default: Optional[str]=None) -> Optional[str]: + def attribute(self, attr: str, default: Optional[str] = None) -> Optional[str]: """ Get an attribute based on a string, or None if nonexistent. @@ -693,7 +708,7 @@ class Node: """ return self.__attrs.get(attr, default) - def add_child(self, child: 'Node') -> None: + def add_child(self, child: "Node") -> None: """ Add a child Node to this node. @@ -701,11 +716,11 @@ class Node: child - A Node to set as a child to this node. """ if not isinstance(child, Node): - raise NodeException('Invalid child') + raise NodeException("Invalid child") self.__children.append(child) - def child(self, name: str) -> Optional['Node']: + def child(self, name: str) -> Optional["Node"]: """ Find a child by name. @@ -717,7 +732,7 @@ class Node: Returns: A Node if a child was found by name, or None if not. """ - tree = name.split('/', 1) + tree = name.split("/", 1) for child in self.__children: if child.name == tree[0]: if len(tree) == 1: @@ -748,7 +763,7 @@ class Node: return child.value @property - def children(self) -> List['Node']: + def children(self) -> List["Node"]: """ Wrapper for accessing children. @@ -787,8 +802,10 @@ class Node: True if this Node is a composite type, False otherwise. """ if self.__translated_type is None: - raise Exception('Logic error, tried to fetch composite determination before setting type!') - return self.__translated_type['composite'] + raise Exception( + "Logic error, tried to fetch composite determination before setting type!" + ) + return self.__translated_type["composite"] def set_value(self, val: Any) -> None: """ @@ -801,50 +818,54 @@ class Node: is_array = isinstance(val, (list, tuple)) if self.__translated_type is None: - raise Exception('Logic error, tried to set value before setting type!') + raise Exception("Logic error, tried to set value before setting type!") translated_type: Dict[str, Any] = self.__translated_type # Handle composite types - if translated_type['composite']: + if translated_type["composite"]: if not is_array: - raise NodeException('Input is not array, expected array') - if len(val) != len(translated_type['enc']): - raise NodeException(f'Input array for {translated_type["name"]} expected to be {len(translated_type["enc"])} elements!') + raise NodeException("Input is not array, expected array") + if len(val) != len(translated_type["enc"]): + raise NodeException( + f'Input array for {translated_type["name"]} expected to be {len(translated_type["enc"])} elements!' + ) is_array = False if is_array != self.__array: - raise NodeException(f'Input {"is" if is_array else "is not"} array, expected {"array" if self.__array else "scalar"}') + raise NodeException( + f'Input {"is" if is_array else "is not"} array, expected {"array" if self.__array else "scalar"}' + ) def val_to_str(val: Any) -> Union[str, bytes]: - if translated_type['name'] == 'bool': + if translated_type["name"] == "bool": # Support user-built boolean types if val is True: - return 'true' + return "true" if val is False: - return 'false' + return "false" # Support construction from binary - return 'true' if val != 0 else 'false' - elif translated_type['name'] == 'float': + return "true" if val != 0 else "false" + elif translated_type["name"] == "float": return str(val) - elif translated_type['name'] == 'ip4': + elif translated_type["name"] == "ip4": try: # Support construction from binary - ip = struct.unpack('BBBB', val) - return f'{ip[0]}.{ip[1]}.{ip[2]}.{ip[3]}' + ip = struct.unpack("BBBB", val) + return f"{ip[0]}.{ip[1]}.{ip[2]}.{ip[3]}" except (struct.error, TypeError): # Assume that its user-built string? if isinstance(val, str): - if len(val.split('.')) == 4: + if len(val.split(".")) == 4: return val - raise NodeException(f'Invalid value {val} for IP4 type') - elif translated_type['int']: + raise NodeException(f"Invalid value {val} for IP4 type") + elif translated_type["int"]: return str(val) else: # This could return either a string or bytes. return val - if is_array or translated_type['composite']: + if is_array or translated_type["composite"]: self.__value = [val_to_str(v) for v in val] else: self.__value = val_to_str(val) @@ -858,26 +879,26 @@ class Node: A mixed value corresponding to this node's value. The returned value will be of the correct data type. """ if self.__translated_type is None: - raise Exception('Logic error, tried to get value before setting type!') + raise Exception("Logic error, tried to get value before setting type!") translated_type: Dict[str, Any] = self.__translated_type def str_to_val(string: Union[str, bytes]) -> Any: - if translated_type['name'] == 'bool': - return True if string == 'true' else False - elif translated_type['name'] == 'float': + if translated_type["name"] == "bool": + return True if string == "true" else False + elif translated_type["name"] == "float": return float(string) - elif translated_type['name'] == 'ip4': + elif translated_type["name"] == "ip4": if not isinstance(string, str): - raise Exception('Logic error, expected a string!') - ip = [int(tup) for tup in string.split('.')] - return struct.pack('BBBB', ip[0], ip[1], ip[2], ip[3]) - elif translated_type['int']: + raise Exception("Logic error, expected a string!") + ip = [int(tup) for tup in string.split(".")] + return struct.pack("BBBB", ip[0], ip[1], ip[2], ip[3]) + elif translated_type["int"]: return int(string) else: # At this point, we could be a string or bytes. return string - if self.__array or translated_type['composite']: + if self.__array or translated_type["composite"]: return [str_to_val(v) for v in self.__value] else: return str_to_val(self.__value) @@ -894,7 +915,9 @@ class Node: A string representing the XML-like data for this node and all children. """ if self.__translated_type is None: - raise Exception('Logic error, tried to get XML representation before setting type!') + raise Exception( + "Logic error, tried to get XML representation before setting type!" + ) translated_type: Dict[str, Any] = self.__translated_type attrs_dict = copy.deepcopy(self.__attrs) @@ -903,50 +926,52 @@ class Node: # Represent type and length if self.__array: if self.__value is None: - attrs_dict['__count'] = '0' + attrs_dict["__count"] = "0" else: - attrs_dict['__count'] = str(len(self.__value)) - order.insert(0, '__count') - attrs_dict['__type'] = translated_type['name'] - order.insert(0, '__type') + attrs_dict["__count"] = str(len(self.__value)) + order.insert(0, "__count") + attrs_dict["__type"] = translated_type["name"] + order.insert(0, "__type") - def escape(val: Any, attr: _renamed_bool=False) -> str: + def escape(val: Any, attr: _renamed_bool = False) -> str: if isinstance(val, str): - val = val.replace('&', '&') - val = val.replace('<', '<') - val = val.replace('>', '>') - val = val.replace('\'', ''') - val = val.replace('\"', '"') + val = val.replace("&", "&") + val = val.replace("<", "<") + val = val.replace(">", ">") + val = val.replace("'", "'") + val = val.replace('"', """) if attr: - val = val.replace('\r', ' ') - val = val.replace('\n', ' ') + val = val.replace("\r", " ") + val = val.replace("\n", " ") return val else: return str(val) if attrs_dict: - attrs = ' ' + ' '.join([f'{attr}="{escape(attrs_dict[attr], attr=True)}"' for attr in order]) + attrs = " " + " ".join( + [f'{attr}="{escape(attrs_dict[attr], attr=True)}"' for attr in order] + ) else: - attrs = '' + attrs = "" def get_val() -> str: - if self.__array or translated_type['composite']: + if self.__array or translated_type["composite"]: if self.__value is None: - vals = '' + vals = "" else: - vals = ' '.join([val for val in self.__value]) - elif translated_type['name'] == 'str': + vals = " ".join([val for val in self.__value]) + elif translated_type["name"] == "str": vals = escape(self.__value) - elif translated_type['name'] == 'bin': + elif translated_type["name"] == "bin": # Convert to a hex string def bin_to_hex(binary: int) -> str: val = hex(binary)[2:] while len(val) < 2: - val = '0' + val + val = "0" + val return val - vals = ''.join([bin_to_hex(v) for v in self.__value]) + vals = "".join([bin_to_hex(v) for v in self.__value]) else: vals = str(self.__value) return vals diff --git a/bemani/protocol/protocol.py b/bemani/protocol/protocol.py index f111c0c..2377f62 100644 --- a/bemani/protocol/protocol.py +++ b/bemani/protocol/protocol.py @@ -19,7 +19,10 @@ class EAmuseProtocol: """ A wrapper object that encapsulates encoding/decoding the E-Amusement protocol by Konami. """ - SHARED_SECRET: Final[bytes] = b'\x69\xD7\x46\x27\xD9\x85\xEE\x21\x87\x16\x15\x70\xD0\x8D\x93\xB1\x24\x55\x03\x5B\x6D\xF0\xD8\x20\x5D\xF5' + + SHARED_SECRET: Final[ + bytes + ] = b"\x69\xD7\x46\x27\xD9\x85\xEE\x21\x87\x16\x15\x70\xD0\x8D\x93\xB1\x24\x55\x03\x5B\x6D\xF0\xD8\x20\x5D\xF5" XML: Final[int] = 1 BINARY: Final[int] = 2 @@ -62,7 +65,7 @@ class EAmuseProtocol: i = j = 0 for char in data: i = (i + 1) & 0xFF - j = (j + S[i]) & 0XFF + j = (j + S[i]) & 0xFF S[i], S[j] = S[j], S[i] out.append(char ^ S[(S[i] + S[j]) & 0xFF]) @@ -84,8 +87,11 @@ class EAmuseProtocol: key: Optional[bytes] = None if encryption_key: # Key is concatenated with the shared secret above - version, first, second = encryption_key.split('-') - key = binascii.unhexlify((first + second).encode('ascii')) + EAmuseProtocol.SHARED_SECRET + version, first, second = encryption_key.split("-") + key = ( + binascii.unhexlify((first + second).encode("ascii")) + + EAmuseProtocol.SHARED_SECRET + ) # Next, key is sent through MD5 to derive the real key m = hashlib.md5() @@ -128,15 +134,15 @@ class EAmuseProtocol: Returns: binary string representing transformed data """ - if compression is None or compression == 'none': + if compression is None or compression == "none": # This isn't compressed return data - elif compression == 'lz77': + elif compression == "lz77": # This is a compressed new-style packet lz = Lz77() return lz.decompress(data) else: - raise EAmuseException(f'Unknown compression {compression}') + raise EAmuseException(f"Unknown compression {compression}") def __compress(self, compression: Optional[str], data: bytes) -> bytes: """ @@ -151,15 +157,15 @@ class EAmuseProtocol: Returns: binary string representing transformed data """ - if compression is None or compression == 'none': + if compression is None or compression == "none": # This isn't compressed return data - elif compression == 'lz77': + elif compression == "lz77": # This is a compressed new-style packet lz = Lz77() return lz.compress(data) else: - raise EAmuseException(f'Unknown compression {compression}') + raise EAmuseException(f"Unknown compression {compression}") def __decode(self, data: bytes) -> Node: """ @@ -194,7 +200,7 @@ class EAmuseProtocol: return ret # Couldn't decode - raise EAmuseException('Unknown packet encoding') + raise EAmuseException("Unknown packet encoding") def __encode(self, tree: Node, text_encoding: str, packet_encoding: int) -> bytes: """ @@ -226,7 +232,9 @@ class EAmuseProtocol: else: raise EAmuseException(f"Invalid packet encoding {packet_encoding}") - def decode(self, compression: Optional[str], encryption: Optional[str], data: bytes) -> Node: + def decode( + self, compression: Optional[str], encryption: Optional[str], data: bytes + ) -> Node: """ Given a request with optional compression and encryption set, decrypt, decompress and decode the data, returning a parsed tree. @@ -249,8 +257,8 @@ class EAmuseProtocol: compression: Optional[str], encryption: Optional[str], tree: Node, - text_encoding: Optional[str]=None, - packet_encoding: Optional[int]=None, + text_encoding: Optional[str] = None, + packet_encoding: Optional[int] = None, ) -> bytes: """ Given a response with optional compression and encryption set, encode, compress diff --git a/bemani/protocol/stream.py b/bemani/protocol/stream.py index acf3fcf..e43e1dc 100644 --- a/bemani/protocol/stream.py +++ b/bemani/protocol/stream.py @@ -42,7 +42,7 @@ class InputStream: if blob_size <= 0: return None if blob_size <= self.left: - bytedata = self.data[self.pos:(self.pos + blob_size)] + bytedata = self.data[self.pos : (self.pos + blob_size)] self.pos += blob_size self.left -= blob_size return bytedata @@ -58,7 +58,7 @@ class InputStream: """ return self.read_blob(1) - def read_int(self, size: int=1, is_unsigned: bool=True) -> Optional[int]: + def read_int(self, size: int = 1, is_unsigned: bool = True) -> Optional[int]: """ Grab the next integer of size 'size' at the current position. If not enough bytes are available to decode this integer, return None. @@ -82,27 +82,27 @@ class InputStream: # Fastpath, just use python's own decoder return data[0] else: - return struct.unpack('>b', data)[0] + return struct.unpack(">b", data)[0] elif size == 2: data = self.read_blob(2) if data is None: return None if is_unsigned: - return struct.unpack('>H', data)[0] + return struct.unpack(">H", data)[0] else: - return struct.unpack('>h', data)[0] + return struct.unpack(">h", data)[0] elif size == 4: data = self.read_blob(4) if data is None: return None if is_unsigned: - return struct.unpack('>I', data)[0] + return struct.unpack(">I", data)[0] else: - return struct.unpack('>i', data)[0] + return struct.unpack(">i", data)[0] else: - raise StreamError(f'Unsupported size {size}') + raise StreamError(f"Unsupported size {size}") class OutputStream: @@ -125,7 +125,7 @@ class OutputStream: @property def data(self) -> bytes: if self.__formatted_data is None: - self.__formatted_data = b''.join(self.__data) + self.__formatted_data = b"".join(self.__data) return self.__formatted_data def write_blob(self, blob: bytes) -> int: @@ -153,7 +153,7 @@ class OutputStream: self.__data_len = self.__data_len + 1 self.__formatted_data = None - def write_int(self, integer: int, size: int=1, is_unsigned: bool=True) -> None: + def write_int(self, integer: int, size: int = 1, is_unsigned: bool = True) -> None: """ Write an integer to the end of the output stream. @@ -166,24 +166,24 @@ class OutputStream: """ if size == 1: if is_unsigned: - self.__data.append(struct.pack('>B', integer)) + self.__data.append(struct.pack(">B", integer)) else: - self.__data.append(struct.pack('>b', integer)) + self.__data.append(struct.pack(">b", integer)) self.__data_len = self.__data_len + 1 elif size == 2: if is_unsigned: - self.__data.append(struct.pack('>H', integer)) + self.__data.append(struct.pack(">H", integer)) else: - self.__data.append(struct.pack('>h', integer)) + self.__data.append(struct.pack(">h", integer)) self.__data_len = self.__data_len + 2 elif size == 4: if is_unsigned: - self.__data.append(struct.pack('>I', integer)) + self.__data.append(struct.pack(">I", integer)) else: - self.__data.append(struct.pack('>i', integer)) + self.__data.append(struct.pack(">i", integer)) self.__data_len = self.__data_len + 4 else: - raise StreamError(f'Unsupported size {size}') + raise StreamError(f"Unsupported size {size}") self.__formatted_data = None def write_pad(self, pad_to: int) -> None: @@ -197,6 +197,6 @@ class OutputStream: be placed on a boundary compatible with the pad_to parameter. """ while (self.__data_len & (pad_to - 1)) != 0: - self.__data.append(b'\0') + self.__data.append(b"\0") self.__data_len = self.__data_len + 1 self.__formatted_data = None diff --git a/bemani/protocol/xml.py b/bemani/protocol/xml.py index f47bcb1..1f31d55 100644 --- a/bemani/protocol/xml.py +++ b/bemani/protocol/xml.py @@ -45,9 +45,9 @@ class XmlDecoder: attribute values. This attribute values should already be decoded from the XML's encoding. """ - data_type = attributes.get('__type') + data_type = attributes.get("__type") - array_str = attributes.get('__count') + array_str = attributes.get("__count") if array_str is not None: array = True else: @@ -55,18 +55,20 @@ class XmlDecoder: if data_type is None: # Special case for nodes that don't have a type - node = Node(name=tag.decode('ascii'), type=Node.NODE_TYPE_VOID) + node = Node(name=tag.decode("ascii"), type=Node.NODE_TYPE_VOID) else: # Get the data value type_int = Node.typename_to_type(data_type) if type_int is None: - raise XmlEncodingException(f'Invalid node type {data_type} for node {tag.decode("ascii")}') + raise XmlEncodingException( + f'Invalid node type {data_type} for node {tag.decode("ascii")}' + ) - node = Node(name=tag.decode('ascii'), type=type_int, array=array) + node = Node(name=tag.decode("ascii"), type=type_int, array=array) # Now, do the attributes for attr in attributes: - if attr == '__type' or attr == '__count': + if attr == "__type" or attr == "__count": # Skip these, handled continue else: @@ -84,8 +86,10 @@ class XmlDecoder: """ node = self.current.pop() - if node.name != tag.decode('ascii'): - raise Exception(f'Logic error, expected {tag.decode("ascii")} but got {node.name}') + if node.name != tag.decode("ascii"): + raise Exception( + f'Logic error, expected {tag.decode("ascii")} but got {node.name}' + ) if len(self.current) == 0: self.root = node @@ -94,13 +98,13 @@ class XmlDecoder: parent.add_child(node) def __yield_values(self, text: str) -> Iterator[str]: - value = '' + value = "" for c in text: if c.isspace(): if len(value) > 0: yield value - value = '' + value = "" else: value = value + c @@ -118,62 +122,84 @@ class XmlDecoder: try: value = text.decode(self.encoding) except UnicodeDecodeError: - raise XmlEncodingException('Failed to decode text node with given encoding') + raise XmlEncodingException("Failed to decode text node with given encoding") if len(self.current) > 0: data_type = self.current[-1].data_type composite = self.current[-1].is_composite array = self.current[-1].is_array - if data_type == 'void': + if data_type == "void": # We can't handle this return - if data_type == 'str': + if data_type == "str": # Do nothing, already fine - value = value.replace('&', '&') - value = value.replace('<', '<') - value = value.replace('>', '>') - value = value.replace(''', '\'') - value = value.replace('"', '\"') + value = value.replace("&", "&") + value = value.replace("<", "<") + value = value.replace(">", ">") + value = value.replace("'", "'") + value = value.replace(""", '"') if self.current[-1].value is None: self.current[-1].set_value(value) else: self.current[-1].set_value(self.current[-1].value + value) - elif data_type == 'bin': + elif data_type == "bin": # Convert from a hex string def hex_to_bin(hexval: str) -> bytes: intval = int(hexval, 16) - return struct.pack('>B', intval) + return struct.pack(">B", intval) # Remove any spaces first - value = ''.join([c for c in value if not c.isspace()]) + value = "".join([c for c in value if not c.isspace()]) if self.current[-1].value is None: - self.current[-1].set_value(b''.join([hex_to_bin(value[i:(i + 2)]) for i in range(0, len(value), 2)])) + self.current[-1].set_value( + b"".join( + [ + hex_to_bin(value[i : (i + 2)]) + for i in range(0, len(value), 2) + ] + ) + ) else: - self.current[-1].set_value(self.current[-1].value + b''.join([hex_to_bin(value[i:(i + 2)]) for i in range(0, len(value), 2)])) - elif data_type == 'ip4': + self.current[-1].set_value( + self.current[-1].value + + b"".join( + [ + hex_to_bin(value[i : (i + 2)]) + for i in range(0, len(value), 2) + ] + ) + ) + elif data_type == "ip4": # Do nothing, already fine self.current[-1].set_value(value) - elif data_type == 'bool': + elif data_type == "bool": + def conv_bool(val: str) -> bool: - if val and val.lower() in ['0', 'false']: + if val and val.lower() in ["0", "false"]: return False else: return True if array or composite: - self.current[-1].set_value([conv_bool(v) for v in self.__yield_values(value)]) + self.current[-1].set_value( + [conv_bool(v) for v in self.__yield_values(value)] + ) else: self.current[-1].set_value(conv_bool(value)) - elif data_type == 'float': + elif data_type == "float": if array or composite: - self.current[-1].set_value([float(v) for v in self.__yield_values(value)]) + self.current[-1].set_value( + [float(v) for v in self.__yield_values(value)] + ) else: self.current[-1].set_value(float(value)) else: if array or composite: - self.current[-1].set_value([int(v) for v in self.__yield_values(value)]) + self.current[-1].set_value( + [int(v) for v in self.__yield_values(value)] + ) else: self.current[-1].set_value(int(value)) @@ -188,59 +214,59 @@ class XmlDecoder: """ attr_stream = InputStream(attributes) parsed_attrs: Dict[str, str] = {} - state = 'space' - attr = b'' - val = b'' + state = "space" + attr = b"" + val = b"" def unescape(value: bytes) -> str: val = value.decode(self.encoding) - val = val.replace('&', '&') - val = val.replace('<', '<') - val = val.replace('>', '>') - val = val.replace(''', '\'') - val = val.replace('"', '\"') - val = val.replace(' ', '\r') - return val.replace(' ', '\n') + val = val.replace("&", "&") + val = val.replace("<", "<") + val = val.replace(">", ">") + val = val.replace("'", "'") + val = val.replace(""", '"') + val = val.replace(" ", "\r") + return val.replace(" ", "\n") while True: c = attr_stream.read_byte() if c is None: return parsed_attrs - if state == 'space': + if state == "space": if not c.isspace(): - state = 'attr' + state = "attr" attr = c - elif state == 'attr': - if c == b'=': + elif state == "attr": + if c == b"=": attr = attr.strip() - state = 'valstart' + state = "valstart" else: attr = attr + c - elif state == 'valstart': + elif state == "valstart": if c == b'"': - state = 'valdouble' - val = b'' - elif c == b'\'': - state = 'valsingle' - val = b'' - elif state == 'valdouble': + state = "valdouble" + val = b"" + elif c == b"'": + state = "valsingle" + val = b"" + elif state == "valdouble": if c == b'"': - state = 'space' - parsed_attrs[attr.decode('ascii')] = unescape(val) + state = "space" + parsed_attrs[attr.decode("ascii")] = unescape(val) else: val = val + c - elif state == 'valsingle': - if c == b'\'': - state = 'space' - parsed_attrs[attr.decode('ascii')] = unescape(val) + elif state == "valsingle": + if c == b"'": + state = "space" + parsed_attrs[attr.decode("ascii")] = unescape(val) else: val = val + c def __split_node(self, content: bytes) -> Tuple[bytes, bytes]: node_stream = InputStream(content) - tag = b'' - attributes = b'' + tag = b"" + attributes = b"" state = "tag" while True: @@ -272,21 +298,21 @@ class XmlDecoder: The node contents, minus the < and > characters. This will be encoded in the XML document's encoding. """ - if content[:1] == b'?' and content[-1:] == b'?': + if content[:1] == b"?" and content[-1:] == b"?": # Special node, parse to get the encoding. tag, attributes = self.__split_node(content[1:-1]) - if tag == b'xml': + if tag == b"xml": attributes_dict = self.__parse_attributes(attributes) - if 'encoding' in attributes_dict: - self.encoding = attributes_dict['encoding'] + if "encoding" in attributes_dict: + self.encoding = attributes_dict["encoding"] return - if content[:1] == b'/': + if content[:1] == b"/": # We got an element end self.__end_element(content[1:]) else: # We got a start element - if content[-1:] == b'/': + if content[-1:] == b"/": # This is an empty element empty = True content = content[:-1] @@ -306,27 +332,27 @@ class XmlDecoder: Returns: A Node object representing the root of the XML document. """ - state = 'text' - text = b'' - node = b'' + state = "text" + text = b"" + node = b"" while True: c = self.stream.read_byte() if c is None: return self.root - elif state == 'text': - if c == b'<': + elif state == "text": + if c == b"<": self.__text(text) - state = 'node' - node = b'' + state = "node" + node = b"" else: text = text + c - elif state == 'node': - if c == b'>': + elif state == "node": + if c == b">": self.__handle_node(node) - state = 'text' - text = b'' + state = "text" + text = b"" else: node = node + c @@ -345,7 +371,7 @@ class XmlEncoder: self.encoding = encoding def get_data(self) -> bytes: - magic = f''.encode('ascii') + magic = f''.encode("ascii") payload = self.to_xml(self.tree) return magic + payload @@ -366,98 +392,118 @@ class XmlEncoder: # Represent type and length if node.is_array: if node.value is None: - attrs_dict['__count'] = '0' + attrs_dict["__count"] = "0" else: - attrs_dict['__count'] = str(len(node.value)) - order.insert(0, '__count') - attrs_dict['__type'] = node.data_type - order.insert(0, '__type') + attrs_dict["__count"] = str(len(node.value)) + order.insert(0, "__count") + attrs_dict["__type"] = node.data_type + order.insert(0, "__type") - def escape(val: Any, attr: bool=False) -> bytes: + def escape(val: Any, attr: bool = False) -> bytes: if isinstance(val, str): - val = val.replace('&', '&') - val = val.replace('<', '<') - val = val.replace('>', '>') - val = val.replace('\'', ''') - val = val.replace('\"', '"') + val = val.replace("&", "&") + val = val.replace("<", "<") + val = val.replace(">", ">") + val = val.replace("'", "'") + val = val.replace('"', """) if attr: - val = val.replace('\r', ' ') - val = val.replace('\n', ' ') + val = val.replace("\r", " ") + val = val.replace("\n", " ") return val.encode(self.encoding) else: - return str(val).encode('ascii') + return str(val).encode("ascii") if attrs_dict: - attrs = b' ' + b' '.join([b''.join([attr.encode('ascii'), b'="', escape(attrs_dict[attr], attr=True), b'"']) for attr in order]) + attrs = b" " + b" ".join( + [ + b"".join( + [ + attr.encode("ascii"), + b'="', + escape(attrs_dict[attr], attr=True), + b'"', + ] + ) + for attr in order + ] + ) else: - attrs = b'' + attrs = b"" if node.children: # Has children nodes children = [self.to_xml(child) for child in node.children] - string = b''.join([ - b'<', - node.name.encode('ascii'), - attrs, - b'>', - b''.join(children), - b'', - ]) + string = b"".join( + [ + b"<", + node.name.encode("ascii"), + attrs, + b">", + b"".join(children), + b"", + ] + ) else: # Doesn't have children nodes if node.data_length == 0: # Void node - string = b''.join([ - b'<', - node.name.encode('ascii'), - attrs, - b'/>', - ]) + string = b"".join( + [ + b"<", + node.name.encode("ascii"), + attrs, + b"/>", + ] + ) else: # Node with values if node.is_array or node.is_composite: if node.value is None: - vals = '' + vals = "" else: - if node.data_type == 'bool': - vals = ' '.join([('1' if val else '0') for val in node.value]) + if node.data_type == "bool": + vals = " ".join( + [("1" if val else "0") for val in node.value] + ) else: - vals = ' '.join([str(val) for val in node.value]) - binary = vals.encode('ascii') - elif node.data_type == 'str': + vals = " ".join([str(val) for val in node.value]) + binary = vals.encode("ascii") + elif node.data_type == "str": binary = escape(node.value) - elif node.data_type == 'bool': - binary = b'1' if node.value else b'0' - elif node.data_type == 'ip4': - vals = '.'.join([str(val) for val in node.value]) - binary = vals.encode('ascii') - elif node.data_type == 'bin': + elif node.data_type == "bool": + binary = b"1" if node.value else b"0" + elif node.data_type == "ip4": + vals = ".".join([str(val) for val in node.value]) + binary = vals.encode("ascii") + elif node.data_type == "bin": # Convert to a hex string def bin_to_hex(binary: int) -> str: val = hex(binary)[2:] while len(val) < 2: - val = '0' + val + val = "0" + val return val - vals = ''.join([bin_to_hex(v) for v in node.value]) - binary = vals.encode('ascii') + vals = "".join([bin_to_hex(v) for v in node.value]) + binary = vals.encode("ascii") else: vals = str(node.value) - binary = vals.encode('ascii') + binary = vals.encode("ascii") - string = b''.join([ - b'<', - node.name.encode('ascii'), - attrs, - b'>', - binary, - b'', - ]) + string = b"".join( + [ + b"<", + node.name.encode("ascii"), + attrs, + b">", + binary, + b"", + ] + ) return string @@ -466,10 +512,11 @@ class XmlEncoding: """ Wrapper class representing an XML encoding. """ + # The string values should match the constants in EAmuseProtocol. # I have no better way to link these than to write this comment, # as otherwise we would have a circular dependency. - ACCEPTED_ENCODINGS: Final[List[str]] = ['shift-jis', 'euc-jp', 'utf-8', 'ascii'] + ACCEPTED_ENCODINGS: Final[List[str]] = ["shift-jis", "euc-jp", "utf-8", "ascii"] def __init__(self) -> None: """ @@ -489,10 +536,10 @@ class XmlEncoding: A new encoding string that is equivalent but normalized. """ encoding = encoding.lower() - encoding = encoding.replace('_', '-') + encoding = encoding.replace("_", "-") return encoding - def decode(self, data: bytes, skip_on_exceptions: bool=False) -> Optional[Node]: + def decode(self, data: bytes, skip_on_exceptions: bool = False) -> Optional[Node]: """ Given a data blob, decode the data with the current encoding. Will set the class property value 'encoding' to the encoding used on the last @@ -506,7 +553,7 @@ class XmlEncoding: if we couldn't decode the object for some reason. """ # Always assume this, unless we get told otherwise in the XML - self.encoding = 'shift-jis' + self.encoding = "shift-jis" # Decode property/value try: @@ -520,7 +567,7 @@ class XmlEncoding: else: raise - def encode(self, tree: Node, encoding: Optional[str]=None) -> bytes: + def encode(self, tree: Node, encoding: Optional[str] = None) -> bytes: """ Given a tree of Node objects, encode the data with the current encoding. @@ -536,7 +583,7 @@ class XmlEncoding: if encoding is None: encoding = self.encoding if encoding is None: - raise XmlEncodingException('Unknown encoding') + raise XmlEncodingException("Unknown encoding") encoding = self.__fix_encoding(encoding) if encoding not in XmlEncoding.ACCEPTED_ENCODINGS: diff --git a/bemani/sniff/sniff.py b/bemani/sniff/sniff.py index d7ab641..f67f2c0 100644 --- a/bemani/sniff/sniff.py +++ b/bemani/sniff/sniff.py @@ -24,18 +24,19 @@ class TCPStream: SYN -> SYNACK -> ACK flow followed by some data followed by a FIN -> FINACK -> ACK flow. Luckily, this is exactly what most HTTP requests look like. """ - INBOUND: Final[str] = 'inbound' - OUTBOUND: Final[str] = 'outbound' + + INBOUND: Final[str] = "inbound" + OUTBOUND: Final[str] = "outbound" def __init__(self, packet: Dict[str, Any]) -> None: """ Initialize a new stream with a packet that belongs in this stream. Expects a packet dictionary as returned by Sniffer.recv_raw(). """ - self.source_address = packet['ip_header']['source_address'] - self.source_port = packet['tcp_header']['source_port'] - self.destination_address = packet['ip_header']['destination_address'] - self.destination_port = packet['tcp_header']['destination_port'] + self.source_address = packet["ip_header"]["source_address"] + self.source_port = packet["tcp_header"]["source_port"] + self.destination_address = packet["ip_header"]["destination_address"] + self.destination_port = packet["tcp_header"]["destination_port"] self.packets = [(TCPStream.INBOUND, packet)] @@ -49,20 +50,19 @@ class TCPStream: False - If this packet doesn't belong and should go elsewhere. """ if ( - packet['tcp_header']['source_port'] == self.source_port and - packet['tcp_header']['destination_port'] == self.destination_port and - packet['ip_header']['source_address'] == self.source_address and - packet['ip_header']['destination_address'] == self.destination_address + packet["tcp_header"]["source_port"] == self.source_port + and packet["tcp_header"]["destination_port"] == self.destination_port + and packet["ip_header"]["source_address"] == self.source_address + and packet["ip_header"]["destination_address"] == self.destination_address ): self.packets.append((TCPStream.INBOUND, packet)) return True if ( - packet['tcp_header']['source_port'] == self.destination_port and - packet['tcp_header']['destination_port'] == self.source_port and - packet['ip_header']['source_address'] == self.destination_address and - packet['ip_header']['destination_address'] == self.source_address - + packet["tcp_header"]["source_port"] == self.destination_port + and packet["tcp_header"]["destination_port"] == self.source_port + and packet["ip_header"]["source_address"] == self.destination_address + and packet["ip_header"]["destination_address"] == self.source_address ): self.packets.append((TCPStream.OUTBOUND, packet)) return True @@ -88,13 +88,13 @@ class TCPStream: # This is really crude, just make sure that we get a SYN -> SYN/AC -> ACK, then a FIN -> FIN/ACK -> ACK state: Dict[str, Dict[str, Optional[str]]] = { TCPStream.INBOUND: { - 'syn': None, - 'fin': None, + "syn": None, + "fin": None, }, TCPStream.OUTBOUND: { - 'syn': None, - 'fin': None, - } + "syn": None, + "fin": None, + }, } sequence = { TCPStream.INBOUND: 0, @@ -112,34 +112,34 @@ class TCPStream: for packet in self.packets: direction = packet[0] other = other_direction(direction) - syn = packet[1]['tcp_header']['flags']['syn'] - fin = packet[1]['tcp_header']['flags']['fin'] - ack = packet[1]['tcp_header']['flags']['ack'] - seq = packet[1]['tcp_header']['sequence'] + syn = packet[1]["tcp_header"]["flags"]["syn"] + fin = packet[1]["tcp_header"]["flags"]["fin"] + ack = packet[1]["tcp_header"]["flags"]["ack"] + seq = packet[1]["tcp_header"]["sequence"] if syn: - if state[direction]['syn'] is None: - state[direction]['syn'] = 'sent' + if state[direction]["syn"] is None: + state[direction]["syn"] = "sent" sequence[direction] = seq if fin: - if state[direction]['fin'] is None: - state[direction]['fin'] = 'sent' + if state[direction]["fin"] is None: + state[direction]["fin"] = "sent" if ack: - if state[other]['syn'] == 'sent': - state[other]['syn'] = 'ackd' - if state[other]['fin'] == 'sent': - state[other]['fin'] = 'ackd' + if state[other]["syn"] == "sent": + state[other]["syn"] = "ackd" + if state[other]["fin"] == "sent": + state[other]["fin"] = "ackd" if ( - state[TCPStream.INBOUND]['syn'] == 'ackd' and - state[TCPStream.INBOUND]['fin'] == 'ackd' and - state[TCPStream.OUTBOUND]['syn'] == 'ackd' and - state[TCPStream.OUTBOUND]['fin'] == 'ackd' + state[TCPStream.INBOUND]["syn"] == "ackd" + and state[TCPStream.INBOUND]["fin"] == "ackd" + and state[TCPStream.OUTBOUND]["syn"] == "ackd" + and state[TCPStream.OUTBOUND]["fin"] == "ackd" ): # This stream is finished, can be reassembled data = { - TCPStream.INBOUND: b'', - TCPStream.OUTBOUND: b'', + TCPStream.INBOUND: b"", + TCPStream.OUTBOUND: b"", } def add_data(packet: bytes, data: bytes, offset: int) -> bytes: @@ -147,7 +147,7 @@ class TCPStream: if len(packet) < offset: # Pad out, then add - packet = packet + b'\0' * (offset - len(packet)) + packet = packet + b"\0" * (offset - len(packet)) return packet + data if len(packet) == offset: # Add to end @@ -158,33 +158,33 @@ class TCPStream: return packet + data if len(packet) > (offset + length): before = packet[:offset] - after = packet[offset + length:] + after = packet[offset + length :] return before + data + after - raise Exception('Logic error!') + raise Exception("Logic error!") for packet in self.packets: dir = packet[0] - syn = packet[1]['tcp_header']['flags']['syn'] - fin = packet[1]['tcp_header']['flags']['fin'] - ack = packet[1]['tcp_header']['flags']['ack'] - seq = packet[1]['tcp_header']['sequence'] + syn = packet[1]["tcp_header"]["flags"]["syn"] + fin = packet[1]["tcp_header"]["flags"]["fin"] + ack = packet[1]["tcp_header"]["flags"]["ack"] + seq = packet[1]["tcp_header"]["sequence"] if syn: continue # Figure out what this packet has - length = len(packet[1]['data']) + length = len(packet[1]["data"]) position = seq - sequence[dir] - 1 if length > 0: - data[dir] = add_data(data[dir], packet[1]['data'], position) + data[dir] = add_data(data[dir], packet[1]["data"], position) return { - 'source_address': self.source_address, - 'destination_address': self.destination_address, - 'source_port': self.source_port, - 'destination_port': self.destination_port, + "source_address": self.source_address, + "destination_address": self.destination_address, + "source_port": self.source_port, + "destination_port": self.destination_port, TCPStream.INBOUND: data[TCPStream.INBOUND], TCPStream.OUTBOUND: data[TCPStream.OUTBOUND], } @@ -197,12 +197,15 @@ class Sniffer: A generic python sniffer. Listens to all raw traffic on the machine and parses packets down to TCP chunks to be reassembled. """ + RECEIVE_SIZE: Final[int] = 1048576 ETH_HEADER_LENGTH: Final[int] = 14 IP_HEADER_LENGTH: Final[int] = 20 TCP_HEADER_LENGTH: Final[int] = 20 - def __init__(self, address: Optional[str]=None, port: Optional[int]=None) -> None: + def __init__( + self, address: Optional[str] = None, port: Optional[int] = None + ) -> None: """ Initialize the sniffer. Can be told to filter by address, port or both. If address or port is not provided, it defaults to all addresses or ports. @@ -233,12 +236,12 @@ class Sniffer: - header_length - The actual length in bytes of this header as an integer - protocol - An integer representing the protocol encapsulated in this header """ - eth = struct.unpack('!6s6sH', eth_header) + eth = struct.unpack("!6s6sH", eth_header) eth_protocol = socket.ntohs(eth[2]) return { - 'header_length': Sniffer.ETH_HEADER_LENGTH, - 'protocol': eth_protocol, + "header_length": Sniffer.ETH_HEADER_LENGTH, + "protocol": eth_protocol, } def __process_ipframe(self, ip_header: bytes) -> Dict[str, Any]: @@ -259,7 +262,7 @@ class Sniffer: - destination_address - A string representing the destination IPv4 address """ # Extract the 20 bytes IP header, ignoring the IP options - iph = struct.unpack('!BBHHHBBH4s4s', ip_header) + iph = struct.unpack("!BBHHHBBH4s4s", ip_header) version = (iph[0] >> 4) & 0xF length = iph[2] ihl = (iph[0] & 0xF) * 4 @@ -268,25 +271,25 @@ class Sniffer: if ihl < Sniffer.IP_HEADER_LENGTH: raise InvalidPacketException( - f'Invalid IP length {ihl}', + f"Invalid IP length {ihl}", ) if version != 4: raise UnknownPacketException( - f'Unknown IP version {version}', + f"Unknown IP version {version}", ) s_addr = socket.inet_ntoa(iph[8]) d_addr = socket.inet_ntoa(iph[9]) return { - 'header_length': ihl, - 'version': version, - 'length': length, - 'ttl': ttl, - 'protocol': proto, - 'source_address': s_addr, - 'destination_address': d_addr, + "header_length": ihl, + "version": version, + "length": length, + "ttl": ttl, + "protocol": proto, + "source_address": s_addr, + "destination_address": d_addr, } def __process_flags(self, flags: int) -> Dict[str, bool]: @@ -295,18 +298,20 @@ class Sniffer: by the flag name who's value is True if set and False otherwise. """ return { - 'ns': True if flags & 0x100 else False, - 'cwr': True if flags & 0x080 else False, - 'ece': True if flags & 0x040 else False, - 'urg': True if flags & 0x020 else False, - 'ack': True if flags & 0x010 else False, - 'psh': True if flags & 0x008 else False, - 'rst': True if flags & 0x004 else False, - 'syn': True if flags & 0x002 else False, - 'fin': True if flags & 0x001 else False, + "ns": True if flags & 0x100 else False, + "cwr": True if flags & 0x080 else False, + "ece": True if flags & 0x040 else False, + "urg": True if flags & 0x020 else False, + "ack": True if flags & 0x010 else False, + "psh": True if flags & 0x008 else False, + "rst": True if flags & 0x004 else False, + "syn": True if flags & 0x002 else False, + "fin": True if flags & 0x001 else False, } - def __process_address(self, address: Tuple[int, int, int, int, int]) -> Dict[str, int]: + def __process_address( + self, address: Tuple[int, int, int, int, int] + ) -> Dict[str, int]: """ Given an address tuple from Linux's recvfrom syscall, return a dict which represents this address. @@ -323,11 +328,11 @@ class Sniffer: - address - The hardware address that received this """ return { - 'interface': address[0], - 'protocol': address[1], - 'type': address[2], - 'hardware_type': address[3], - 'address': address[4], + "interface": address[0], + "protocol": address[1], + "type": address[2], + "hardware_type": address[3], + "address": address[4], } def __process_tcpframe(self, tcp_header: bytes) -> Dict[str, Any]: @@ -346,7 +351,7 @@ class Sniffer: - acknowledgement - An integer representing the current acknowledgement of this packet - flags - A dictionary as defined in Sniffer.__process_flags() """ - tcph = struct.unpack('!HHLLBBHHH', tcp_header) + tcph = struct.unpack("!HHLLBBHHH", tcp_header) # Normal stuff source_port = tcph[0] @@ -359,12 +364,12 @@ class Sniffer: flags = ((tcph[4] & 1) << 8) | tcph[5] return { - 'header_length': tcphl, - 'source_port': source_port, - 'destination_port': dest_port, - 'sequence': sequence, - 'acknowledgement': acknowledgement, - 'flags': self.__process_flags(flags), + "header_length": tcphl, + "source_port": source_port, + "destination_port": dest_port, + "sequence": sequence, + "acknowledgement": acknowledgement, + "flags": self.__process_flags(flags), } def __recv_frame(self) -> Dict[str, Any]: @@ -386,40 +391,48 @@ class Sniffer: offset = 0 # Make sure its a valid packet - eth_header = self.__process_ethframe(packet[offset:(offset + Sniffer.ETH_HEADER_LENGTH)]) - offset = offset + eth_header['header_length'] + eth_header = self.__process_ethframe( + packet[offset : (offset + Sniffer.ETH_HEADER_LENGTH)] + ) + offset = offset + eth_header["header_length"] - if eth_header['protocol'] != 8: + if eth_header["protocol"] != 8: # Not IP - raise UnknownPacketException( - f'Unknown frame {eth_header["protocol"]}' - ) + raise UnknownPacketException(f'Unknown frame {eth_header["protocol"]}') # Get the IP header - ip_header = self.__process_ipframe(packet[offset:(offset + Sniffer.IP_HEADER_LENGTH)]) - offset = offset + ip_header['header_length'] + ip_header = self.__process_ipframe( + packet[offset : (offset + Sniffer.IP_HEADER_LENGTH)] + ) + offset = offset + ip_header["header_length"] - if ip_header['protocol'] != 6: + if ip_header["protocol"] != 6: # Not TCP raise UnknownPacketException( f'Unknown protocol {ip_header["protocol"]}', ) # Get TCP header - tcp_header = self.__process_tcpframe(packet[offset:(offset + Sniffer.TCP_HEADER_LENGTH)]) - offset = offset + tcp_header['header_length'] + tcp_header = self.__process_tcpframe( + packet[offset : (offset + Sniffer.TCP_HEADER_LENGTH)] + ) + offset = offset + tcp_header["header_length"] # Get payload length - payload_length = ip_header['length'] - ip_header['header_length'] - tcp_header['header_length'] + payload_length = ( + ip_header["length"] + - ip_header["header_length"] + - tcp_header["header_length"] + ) # Get payload - data = packet[offset:offset + payload_length] + data = packet[offset : offset + payload_length] return { - 'ip_header': ip_header, - 'tcp_header': tcp_header, - 'data': data, - 'address': address, + "ip_header": ip_header, + "tcp_header": tcp_header, + "data": data, + "address": address, } def recv_raw(self) -> Dict[str, Any]: @@ -436,30 +449,33 @@ class Sniffer: continue # Hack for sniffing on localhost - if packet['address']['interface'] == 'lo' and packet['address']['type'] != 4: + if ( + packet["address"]["interface"] == "lo" + and packet["address"]["type"] != 4 + ): continue if self.address and self.port: if ( - packet['ip_header']['source_address'] == self.address and - packet['tcp_header']['source_port'] == self.port + packet["ip_header"]["source_address"] == self.address + and packet["tcp_header"]["source_port"] == self.port ): return packet if ( - packet['ip_header']['destination_address'] == self.address and - packet['tcp_header']['destination_port'] == self.port + packet["ip_header"]["destination_address"] == self.address + and packet["tcp_header"]["destination_port"] == self.port ): return packet elif self.address: if ( - packet['ip_header']['source_address'] == self.address or - packet['ip_header']['destination_address'] == self.address + packet["ip_header"]["source_address"] == self.address + or packet["ip_header"]["destination_address"] == self.address ): return packet elif self.port: if ( - packet['tcp_header']['source_port'] == self.port or - packet['tcp_header']['destination_port'] == self.port + packet["tcp_header"]["source_port"] == self.port + or packet["tcp_header"]["destination_port"] == self.port ): return packet else: diff --git a/bemani/tests/helpers.py b/bemani/tests/helpers.py index eefe214..9f3f313 100644 --- a/bemani/tests/helpers.py +++ b/bemani/tests/helpers.py @@ -20,15 +20,16 @@ class ExtendedTestCase(unittest.TestCase): self.assertEqual(a_items, b_items) -class FakeCursor(): - +class FakeCursor: def __init__(self, rows: List[Dict[str, Any]]) -> None: self.__rows = rows self.rowcount = len(rows) def fetchone(self) -> Dict[str, Any]: if len(self.__rows) != 1: - raise Exception(f'Tried to fetch one row and there are {len(self.__rows)} rows!') + raise Exception( + f"Tried to fetch one row and there are {len(self.__rows)} rows!" + ) return self.__rows[0] def fetchall(self) -> List[Dict[str, Any]]: diff --git a/bemani/tests/test_AES.py b/bemani/tests/test_AES.py index 97c865b..e57a9c8 100644 --- a/bemani/tests/test_AES.py +++ b/bemani/tests/test_AES.py @@ -5,21 +5,22 @@ from bemani.common import AESCipher class TestAESCipher(unittest.TestCase): - def test_pad(self) -> None: - aes = AESCipher('a wonderful key') - self.assertEqual(aes._pad(''), '0.--------------') - self.assertEqual(aes._unpad(aes._pad('')), '') - self.assertEqual(aes._pad('1337'), '4.1337----------') - self.assertEqual(aes._unpad(aes._pad('1337')), '1337') - self.assertEqual(aes._pad('aaaaaaaaaaaaaaaa'), '16.aaaaaaaaaaaaaaaa-------------') - self.assertEqual(aes._unpad(aes._pad('aaaaaaaaaaaaaaaa')), 'aaaaaaaaaaaaaaaa') - self.assertEqual(aes._pad('aaaaaaaaaaaaa'), '13.aaaaaaaaaaaaa') - self.assertEqual(aes._unpad(aes._pad('aaaaaaaaaaaaa')), 'aaaaaaaaaaaaa') + aes = AESCipher("a wonderful key") + self.assertEqual(aes._pad(""), "0.--------------") + self.assertEqual(aes._unpad(aes._pad("")), "") + self.assertEqual(aes._pad("1337"), "4.1337----------") + self.assertEqual(aes._unpad(aes._pad("1337")), "1337") + self.assertEqual( + aes._pad("aaaaaaaaaaaaaaaa"), "16.aaaaaaaaaaaaaaaa-------------" + ) + self.assertEqual(aes._unpad(aes._pad("aaaaaaaaaaaaaaaa")), "aaaaaaaaaaaaaaaa") + self.assertEqual(aes._pad("aaaaaaaaaaaaa"), "13.aaaaaaaaaaaaa") + self.assertEqual(aes._unpad(aes._pad("aaaaaaaaaaaaa")), "aaaaaaaaaaaaa") def test_crypto(self) -> None: - aes = AESCipher('a wonderful key') - ciphertext = aes.encrypt('testing') + aes = AESCipher("a wonderful key") + ciphertext = aes.encrypt("testing") plaintext = aes.decrypt(ciphertext) - self.assertEqual(plaintext, 'testing') + self.assertEqual(plaintext, "testing") self.assertNotEqual(ciphertext, plaintext) diff --git a/bemani/tests/test_AFPUtils.py b/bemani/tests/test_AFPUtils.py index 8019473..e0317a1 100644 --- a/bemani/tests/test_AFPUtils.py +++ b/bemani/tests/test_AFPUtils.py @@ -5,7 +5,6 @@ from bemani.utils.afputils import parse_intlist, adjust_background_loop class TestAFPUtils(unittest.TestCase): - def test_parse_intlist(self) -> None: # Simple self.assertEqual( diff --git a/bemani/tests/test_APIClient.py b/bemani/tests/test_APIClient.py index bb1ec08..1f3f4f3 100644 --- a/bemani/tests/test_APIClient.py +++ b/bemani/tests/test_APIClient.py @@ -5,17 +5,18 @@ from bemani.data.api.client import APIClient class TestAPIClient(unittest.TestCase): - def test_content_type(self) -> None: - client = APIClient('https://127.0.0.1', 'token', False, False) - self.assertFalse(client._content_type_valid('application/text')) - self.assertFalse(client._content_type_valid('application/json')) - self.assertFalse(client._content_type_valid('application/json; charset=shift-jis')) - self.assertTrue(client._content_type_valid('application/json; charset=utf-8')) - self.assertTrue(client._content_type_valid('application/json;charset=utf-8')) - self.assertTrue(client._content_type_valid('application/json;charset = utf-8')) - self.assertTrue(client._content_type_valid('application/json; charset = utf-8')) - self.assertTrue(client._content_type_valid('application/json; charset=UTF-8')) - self.assertTrue(client._content_type_valid('application/json;charset=UTF-8')) - self.assertTrue(client._content_type_valid('application/json;charset = UTF-8')) - self.assertTrue(client._content_type_valid('application/json; charset = UTF-8')) + client = APIClient("https://127.0.0.1", "token", False, False) + self.assertFalse(client._content_type_valid("application/text")) + self.assertFalse(client._content_type_valid("application/json")) + self.assertFalse( + client._content_type_valid("application/json; charset=shift-jis") + ) + self.assertTrue(client._content_type_valid("application/json; charset=utf-8")) + self.assertTrue(client._content_type_valid("application/json;charset=utf-8")) + self.assertTrue(client._content_type_valid("application/json;charset = utf-8")) + self.assertTrue(client._content_type_valid("application/json; charset = utf-8")) + self.assertTrue(client._content_type_valid("application/json; charset=UTF-8")) + self.assertTrue(client._content_type_valid("application/json;charset=UTF-8")) + self.assertTrue(client._content_type_valid("application/json;charset = UTF-8")) + self.assertTrue(client._content_type_valid("application/json; charset = UTF-8")) diff --git a/bemani/tests/test_BaseData.py b/bemani/tests/test_BaseData.py index 9e13233..a6ef808 100644 --- a/bemani/tests/test_BaseData.py +++ b/bemani/tests/test_BaseData.py @@ -6,19 +6,18 @@ from bemani.data.mysql.base import BaseData class TestBaseData(unittest.TestCase): - def test_basic_serialize(self) -> None: data = BaseData(Mock(), None) testdict = { - 'test1': 1, - 'test2': '2', - 'test3': 3.3, - 'test4': [1, 2, 3, 4], - 'test5': { - 'a': 'b', + "test1": 1, + "test2": "2", + "test3": 3.3, + "test4": [1, 2, 3, 4], + "test5": { + "a": "b", }, - 'testempty': [], + "testempty": [], } self.assertEqual(data.deserialize(data.serialize(testdict)), testdict) @@ -27,7 +26,7 @@ class TestBaseData(unittest.TestCase): data = BaseData(Mock(), None) testdict = { - 'bytes': b'\x01\x02\x03\x04\x05', + "bytes": b"\x01\x02\x03\x04\x05", } serialized = data.serialize(testdict) @@ -38,12 +37,12 @@ class TestBaseData(unittest.TestCase): data = BaseData(Mock(), None) testdict = { - 'sentinal': True, - 'test': { - 'sentinal': False, - 'bytes': b'\x01\x02\x03\x04\x05', - 'bytes2': b'', - } + "sentinal": True, + "test": { + "sentinal": False, + "bytes": b"\x01\x02\x03\x04\x05", + "bytes2": b"", + }, } self.assertEqual(data.deserialize(data.serialize(testdict)), testdict) diff --git a/bemani/tests/test_CardCipher.py b/bemani/tests/test_CardCipher.py index 2809e56..b290ec9 100644 --- a/bemani/tests/test_CardCipher.py +++ b/bemani/tests/test_CardCipher.py @@ -5,20 +5,29 @@ from bemani.common import CardCipher class TestCardCipher(unittest.TestCase): - def test_internal_cipher(self) -> None: test_ciphers = [ - ([0x68, 0xFC, 0xA5, 0x27, 0x00, 0x01, 0x04, 0xE0], [0xC7, 0xD0, 0xB3, 0x85, 0xAD, 0x1F, 0xD9, 0x49]), - ([0x2C, 0x10, 0xA6, 0x27, 0x00, 0x01, 0x04, 0xE0], [0x33, 0xC6, 0xE6, 0x2E, 0x6E, 0x33, 0x38, 0x74]), + ( + [0x68, 0xFC, 0xA5, 0x27, 0x00, 0x01, 0x04, 0xE0], + [0xC7, 0xD0, 0xB3, 0x85, 0xAD, 0x1F, 0xD9, 0x49], + ), + ( + [0x2C, 0x10, 0xA6, 0x27, 0x00, 0x01, 0x04, 0xE0], + [0x33, 0xC6, 0xE6, 0x2E, 0x6E, 0x33, 0x38, 0x74], + ), ] for pair in test_ciphers: inp = bytes(pair[0]) out = bytes(pair[1]) encoded = CardCipher._encode(inp) - self.assertEqual(encoded, out, f"Card encode {encoded!r} doesn't match expected {out!r}") + self.assertEqual( + encoded, out, f"Card encode {encoded!r} doesn't match expected {out!r}" + ) decoded = CardCipher._decode(out) - self.assertEqual(decoded, inp, f"Card decode {decoded!r} doesn't match expected {inp!r}") + self.assertEqual( + decoded, inp, f"Card decode {decoded!r} doesn't match expected {inp!r}" + ) def test_external_cipher(self) -> None: test_cards = [ @@ -30,6 +39,10 @@ class TestCardCipher(unittest.TestCase): back = card[0] db = card[1] decoded = CardCipher.decode(back) - self.assertEqual(decoded, db, f"Card DB {decoded} doesn't match expected {db}") + self.assertEqual( + decoded, db, f"Card DB {decoded} doesn't match expected {db}" + ) encoded = CardCipher.encode(db) - self.assertEqual(encoded, back, f"Card back {encoded} doesn't match expected {back}") + self.assertEqual( + encoded, back, f"Card back {encoded} doesn't match expected {back}" + ) diff --git a/bemani/tests/test_GameData.py b/bemani/tests/test_GameData.py index d4df9d5..05a75b4 100644 --- a/bemani/tests/test_GameData.py +++ b/bemani/tests/test_GameData.py @@ -8,26 +8,50 @@ from bemani.tests.helpers import FakeCursor class TestGameData(unittest.TestCase): - def test_put_time_sensitive_settings(self) -> None: game = GameData(Mock(), None) # Verify that we catch incorrect input order with self.assertRaises(Exception) as context: - game.put_time_sensitive_settings(GameConstants.BISHI_BASHI, 1, 'work', {'start_time': 12345, 'end_time': 12340}) - self.assertTrue('Start time is greater than end time!' in str(context.exception)) + game.put_time_sensitive_settings( + GameConstants.BISHI_BASHI, + 1, + "work", + {"start_time": 12345, "end_time": 12340}, + ) + self.assertTrue( + "Start time is greater than end time!" in str(context.exception) + ) # Verify that we catch events spanning no time with self.assertRaises(Exception) as context: - game.put_time_sensitive_settings(GameConstants.BISHI_BASHI, 1, 'work', {'start_time': 12345, 'end_time': 12345}) - self.assertTrue('This setting spans zero seconds!' in str(context.exception)) + game.put_time_sensitive_settings( + GameConstants.BISHI_BASHI, + 1, + "work", + {"start_time": 12345, "end_time": 12345}, + ) + self.assertTrue("This setting spans zero seconds!" in str(context.exception)) # Verify that we ignore finding ourselves before updating - game.execute = Mock(return_value=FakeCursor([{'start_time': 12345, 'end_time': 12346}])) # type: ignore - game.put_time_sensitive_settings(GameConstants.BISHI_BASHI, 1, 'work', {'start_time': 12345, 'end_time': 12346}) + game.execute = Mock(return_value=FakeCursor([{"start_time": 12345, "end_time": 12346}])) # type: ignore + game.put_time_sensitive_settings( + GameConstants.BISHI_BASHI, + 1, + "work", + {"start_time": 12345, "end_time": 12346}, + ) # Verify that we catch events overlapping other events in the DB - game.execute = Mock(return_value=FakeCursor([{'start_time': 12345, 'end_time': 12350}])) # type: ignore + game.execute = Mock(return_value=FakeCursor([{"start_time": 12345, "end_time": 12350}])) # type: ignore with self.assertRaises(Exception) as context: - game.put_time_sensitive_settings(GameConstants.BISHI_BASHI, 1, 'work', {'start_time': 12347, 'end_time': 12355}) - self.assertTrue('This event overlaps an existing one with start time 12345 and end time 12350' in str(context.exception)) + game.put_time_sensitive_settings( + GameConstants.BISHI_BASHI, + 1, + "work", + {"start_time": 12347, "end_time": 12355}, + ) + self.assertTrue( + "This event overlaps an existing one with start time 12345 and end time 12350" + in str(context.exception) + ) diff --git a/bemani/tests/test_ID.py b/bemani/tests/test_ID.py index a9932b5..4b2972a 100644 --- a/bemani/tests/test_ID.py +++ b/bemani/tests/test_ID.py @@ -5,15 +5,14 @@ from bemani.common import ID class TestID(unittest.TestCase): - def test_format_extid(self) -> None: - self.assertEqual(ID.format_extid(12345678), '1234-5678') - self.assertEqual(ID.parse_extid('1234-5678'), 12345678) - self.assertEqual(ID.parse_extid('bla'), None) - self.assertEqual(ID.parse_extid('blah-blah'), None) + self.assertEqual(ID.format_extid(12345678), "1234-5678") + self.assertEqual(ID.parse_extid("1234-5678"), 12345678) + self.assertEqual(ID.parse_extid("bla"), None) + self.assertEqual(ID.parse_extid("blah-blah"), None) def test_format_machine_id(self) -> None: - self.assertEqual(ID.format_machine_id(123), 'US-123') - self.assertEqual(ID.parse_machine_id('US-123'), 123) - self.assertEqual(ID.parse_machine_id('bla'), None) - self.assertEqual(ID.parse_machine_id('US-blah'), None) + self.assertEqual(ID.format_machine_id(123), "US-123") + self.assertEqual(ID.parse_machine_id("US-123"), 123) + self.assertEqual(ID.parse_machine_id("bla"), None) + self.assertEqual(ID.parse_machine_id("US-blah"), None) diff --git a/bemani/tests/test_IIDXPendual.py b/bemani/tests/test_IIDXPendual.py index 8bcc5ec..d7edf7d 100644 --- a/bemani/tests/test_IIDXPendual.py +++ b/bemani/tests/test_IIDXPendual.py @@ -10,7 +10,6 @@ from bemani.data import Score class TestIIDXPendual(unittest.TestCase): - def __make_score(self, ghost: List[int]) -> Score: return Score( 0, @@ -22,7 +21,7 @@ class TestIIDXPendual(unittest.TestCase): 0, 1, { - 'ghost': bytes(ghost), + "ghost": bytes(ghost), }, ) @@ -36,37 +35,49 @@ class TestIIDXPendual(unittest.TestCase): def test_average_identity(self) -> None: base = IIDXPendual(Mock(), Mock(), Mock()) self.assertEqual( - base.delta_score([ - self.__make_score([10, 20, 30]), - ], 3), - (60, struct.pack('bbb', *[-10, 0, 10])), + base.delta_score( + [ + self.__make_score([10, 20, 30]), + ], + 3, + ), + (60, struct.pack("bbb", *[-10, 0, 10])), ) def test_average_basic(self) -> None: base = IIDXPendual(Mock(), Mock(), Mock()) self.assertEqual( - base.delta_score([ - self.__make_score([10, 20, 30]), - self.__make_score([0, 0, 0]), - ], 3), - (30, struct.pack('bbb', *[-5, 0, 5])), + base.delta_score( + [ + self.__make_score([10, 20, 30]), + self.__make_score([0, 0, 0]), + ], + 3, + ), + (30, struct.pack("bbb", *[-5, 0, 5])), ) def test_average_complex(self) -> None: base = IIDXPendual(Mock(), Mock(), Mock()) self.assertEqual( - base.delta_score([ - self.__make_score([10, 20, 30]), - self.__make_score([20, 30, 40]), - self.__make_score([30, 40, 50]), - ], 3), - (90, struct.pack('bbb', *[-10, 0, 10])), + base.delta_score( + [ + self.__make_score([10, 20, 30]), + self.__make_score([20, 30, 40]), + self.__make_score([30, 40, 50]), + ], + 3, + ), + (90, struct.pack("bbb", *[-10, 0, 10])), ) def test_average_always_zero(self) -> None: base = IIDXPendual(Mock(), Mock(), Mock()) - ex_score, ghost = base.delta_score([ - self.__make_score([random.randint(0, 10) for _ in range(64)]), - self.__make_score([random.randint(0, 10) for _ in range(64)]), - ], 64) - self.assertEqual(sum(struct.unpack('b' * 64, ghost)), 0) + ex_score, ghost = base.delta_score( + [ + self.__make_score([random.randint(0, 10) for _ in range(64)]), + self.__make_score([random.randint(0, 10) for _ in range(64)]), + ], + 64, + ) + self.assertEqual(sum(struct.unpack("b" * 64, ghost)), 0) diff --git a/bemani/tests/test_JubeatProp.py b/bemani/tests/test_JubeatProp.py index 6b1c399..63650c8 100644 --- a/bemani/tests/test_JubeatProp.py +++ b/bemani/tests/test_JubeatProp.py @@ -8,7 +8,6 @@ from bemani.data.types import Achievement, UserID class TestJubeatProp(unittest.TestCase): - def test_increment_class(self) -> None: # Verify normal increase self.assertEqual(JubeatProp._increment_class(1, 5), (1, 4)) @@ -154,11 +153,7 @@ class TestJubeatProp(unittest.TestCase): # Test correct behavior on empty input self.assertEqual( - JubeatProp._get_league_scores( - None, - 999, - [] - ), + JubeatProp._get_league_scores(None, 999, []), ( [], [], @@ -166,7 +161,7 @@ class TestJubeatProp(unittest.TestCase): ) # Test that we can load last week's score if it exists for a user - data.local.user.get_achievement = Mock(return_value={'score': [123, 456, 789]}) + data.local.user.get_achievement = Mock(return_value={"score": [123, 456, 789]}) self.assertEqual( JubeatProp._get_league_scores( data, @@ -183,7 +178,7 @@ class TestJubeatProp(unittest.TestCase): JubeatProp.version, 1337, 998, - 'league', + "league", ) data.local.user.get_achievement.reset_mock() @@ -205,7 +200,7 @@ class TestJubeatProp(unittest.TestCase): JubeatProp.version, 1337, 998, - 'league', + "league", ) data.local.user.get_achievement.reset_mock() @@ -242,9 +237,11 @@ class TestJubeatProp(unittest.TestCase): data.local.user.get_achievements.reset_mock() # Test that a user who only skipped last week doesn't get flagged absentee - data.local.user.get_achievements = Mock(return_value=[ - Achievement(997, 'league', None, {}), - ]) + data.local.user.get_achievements = Mock( + return_value=[ + Achievement(997, "league", None, {}), + ] + ) self.assertEqual( JubeatProp._get_league_absentees( data, @@ -261,9 +258,11 @@ class TestJubeatProp(unittest.TestCase): data.local.user.get_achievements.reset_mock() # Test that a user who skipped last two week gets flagged absentee - data.local.user.get_achievements = Mock(return_value=[ - Achievement(996, 'league', None, {}), - ]) + data.local.user.get_achievements = Mock( + return_value=[ + Achievement(996, "league", None, {}), + ] + ) self.assertEqual( JubeatProp._get_league_absentees( data, @@ -281,9 +280,11 @@ class TestJubeatProp(unittest.TestCase): # Test that a user who skipped last three week doesn't get flagged # (they got flagged last week) - data.local.user.get_achievements = Mock(return_value=[ - Achievement(995, 'league', None, {}), - ]) + data.local.user.get_achievements = Mock( + return_value=[ + Achievement(995, "league", None, {}), + ] + ) self.assertEqual( JubeatProp._get_league_absentees( data, @@ -300,9 +301,11 @@ class TestJubeatProp(unittest.TestCase): data.local.user.get_achievements.reset_mock() # Test that a user who skipped last four week gets flagged absentee - data.local.user.get_achievements = Mock(return_value=[ - Achievement(994, 'league', None, {}), - ]) + data.local.user.get_achievements = Mock( + return_value=[ + Achievement(994, "league", None, {}), + ] + ) self.assertEqual( JubeatProp._get_league_absentees( data, @@ -324,139 +327,149 @@ class TestJubeatProp(unittest.TestCase): data.local.user = Mock() # Test demoting a user at the bottom does nothing. - data.local.user.get_profile = Mock(return_value=Profile( - JubeatProp.game, - JubeatProp.version, - "", - 0, - { - 'league_class': 1, - 'league_subclass': 5, - }, - )) + data.local.user.get_profile = Mock( + return_value=Profile( + JubeatProp.game, + JubeatProp.version, + "", + 0, + { + "league_class": 1, + "league_subclass": 5, + }, + ) + ) JubeatProp._modify_profile( data, UserID(1337), - 'demote', + "demote", ) self.assertFalse(data.local.user.put_profile.called) # Test promoting a user at the top does nothing. - data.local.user.get_profile = Mock(return_value=Profile( - JubeatProp.game, - JubeatProp.version, - "", - 0, - { - 'league_class': 4, - 'league_subclass': 1, - }, - )) + data.local.user.get_profile = Mock( + return_value=Profile( + JubeatProp.game, + JubeatProp.version, + "", + 0, + { + "league_class": 4, + "league_subclass": 1, + }, + ) + ) JubeatProp._modify_profile( data, UserID(1337), - 'promote', + "promote", ) self.assertFalse(data.local.user.put_profile.called) # Test regular promotion updates profile properly - data.local.user.get_profile = Mock(return_value=Profile( - JubeatProp.game, - JubeatProp.version, - "", - 0, - { - 'league_class': 1, - 'league_subclass': 5, - 'league_is_checked': True, - }, - )) + data.local.user.get_profile = Mock( + return_value=Profile( + JubeatProp.game, + JubeatProp.version, + "", + 0, + { + "league_class": 1, + "league_subclass": 5, + "league_is_checked": True, + }, + ) + ) JubeatProp._modify_profile( data, UserID(1337), - 'promote', + "promote", ) data.local.user.put_profile.assert_called_once_with( JubeatProp.game, JubeatProp.version, UserID(1337), { - 'league_class': 1, - 'league_subclass': 4, - 'league_is_checked': False, - 'last': { - 'league_class': 1, - 'league_subclass': 5, + "league_class": 1, + "league_subclass": 4, + "league_is_checked": False, + "last": { + "league_class": 1, + "league_subclass": 5, }, }, ) data.local.user.put_profile.reset_mock() # Test regular demote updates profile properly - data.local.user.get_profile = Mock(return_value=Profile( - JubeatProp.game, - JubeatProp.version, - "", - 0, - { - 'league_class': 1, - 'league_subclass': 3, - 'league_is_checked': True, - }, - )) + data.local.user.get_profile = Mock( + return_value=Profile( + JubeatProp.game, + JubeatProp.version, + "", + 0, + { + "league_class": 1, + "league_subclass": 3, + "league_is_checked": True, + }, + ) + ) JubeatProp._modify_profile( data, UserID(1337), - 'demote', + "demote", ) data.local.user.put_profile.assert_called_once_with( JubeatProp.game, JubeatProp.version, UserID(1337), { - 'league_class': 1, - 'league_subclass': 4, - 'league_is_checked': False, - 'last': { - 'league_class': 1, - 'league_subclass': 3, + "league_class": 1, + "league_subclass": 4, + "league_is_checked": False, + "last": { + "league_class": 1, + "league_subclass": 3, }, }, ) data.local.user.put_profile.reset_mock() # Test demotion after not checking doesn't update old values - data.local.user.get_profile = Mock(return_value=Profile( - JubeatProp.game, - JubeatProp.version, - "", - 0, - { - 'league_class': 1, - 'league_subclass': 4, - 'league_is_checked': False, - 'last': { - 'league_class': 1, - 'league_subclass': 3, + data.local.user.get_profile = Mock( + return_value=Profile( + JubeatProp.game, + JubeatProp.version, + "", + 0, + { + "league_class": 1, + "league_subclass": 4, + "league_is_checked": False, + "last": { + "league_class": 1, + "league_subclass": 3, + }, }, - }, - )) + ) + ) JubeatProp._modify_profile( data, UserID(1337), - 'demote', + "demote", ) data.local.user.put_profile.assert_called_once_with( JubeatProp.game, JubeatProp.version, UserID(1337), { - 'league_class': 1, - 'league_subclass': 5, - 'league_is_checked': False, - 'last': { - 'league_class': 1, - 'league_subclass': 3, + "league_class": 1, + "league_subclass": 5, + "league_is_checked": False, + "last": { + "league_class": 1, + "league_subclass": 3, }, }, ) diff --git a/bemani/tests/test_NetworkData.py b/bemani/tests/test_NetworkData.py index 800b3dc..c2ec437 100644 --- a/bemani/tests/test_NetworkData.py +++ b/bemani/tests/test_NetworkData.py @@ -9,44 +9,55 @@ from bemani.tests.helpers import FakeCursor class TestNetworkData(unittest.TestCase): - def test_get_schedule_type(self) -> None: network = NetworkData(Mock(), None) - with freeze_time('2016-01-01 12:00'): + with freeze_time("2016-01-01 12:00"): # Check daily schedule self.assertEqual( # 2016-01-01 -> 2016-01-02 (1451606400, 1451692800), - network.get_schedule_duration('daily'), + network.get_schedule_duration("daily"), ) # Check weekly schedule (weeks start on monday in python lol) self.assertEqual( # 2015-12-27 -> 2916-01-03 (1451260800, 1451865600), - network.get_schedule_duration('weekly'), + network.get_schedule_duration("weekly"), ) def test_should_schedule(self) -> None: network = NetworkData(Mock(), None) - with freeze_time('2016-01-01'): + with freeze_time("2016-01-01"): # Check for should schedule if nothing in DB network.execute = Mock(return_value=FakeCursor([])) # type: ignore - self.assertTrue(network.should_schedule(GameConstants.BISHI_BASHI, 1, 'work', 'daily')) - self.assertTrue(network.should_schedule(GameConstants.BISHI_BASHI, 1, 'work', 'weekly')) + self.assertTrue( + network.should_schedule(GameConstants.BISHI_BASHI, 1, "work", "daily") + ) + self.assertTrue( + network.should_schedule(GameConstants.BISHI_BASHI, 1, "work", "weekly") + ) # Check for don't schedule if DB time is our current time - network.execute = Mock(return_value=FakeCursor([{'year': 2016, 'day': 1}])) # type: ignore - self.assertFalse(network.should_schedule(GameConstants.BISHI_BASHI, 1, 'work', 'daily')) + network.execute = Mock(return_value=FakeCursor([{"year": 2016, "day": 1}])) # type: ignore + self.assertFalse( + network.should_schedule(GameConstants.BISHI_BASHI, 1, "work", "daily") + ) - network.execute = Mock(return_value=FakeCursor([{'year': None, 'day': 16797}])) # type: ignore - self.assertFalse(network.should_schedule(GameConstants.BISHI_BASHI, 1, 'work', 'weekly')) + network.execute = Mock(return_value=FakeCursor([{"year": None, "day": 16797}])) # type: ignore + self.assertFalse( + network.should_schedule(GameConstants.BISHI_BASHI, 1, "work", "weekly") + ) # Check for do schedule if DB time is older than our current time - network.execute = Mock(return_value=FakeCursor([{'year': 2015, 'day': 365}])) # type: ignore - self.assertTrue(network.should_schedule(GameConstants.BISHI_BASHI, 1, 'work', 'daily')) + network.execute = Mock(return_value=FakeCursor([{"year": 2015, "day": 365}])) # type: ignore + self.assertTrue( + network.should_schedule(GameConstants.BISHI_BASHI, 1, "work", "daily") + ) - network.execute = Mock(return_value=FakeCursor([{'year': None, 'day': 16790}])) # type: ignore - self.assertTrue(network.should_schedule(GameConstants.BISHI_BASHI, 1, 'work', 'weekly')) + network.execute = Mock(return_value=FakeCursor([{"year": None, "day": 16790}])) # type: ignore + self.assertTrue( + network.should_schedule(GameConstants.BISHI_BASHI, 1, "work", "weekly") + ) diff --git a/bemani/tests/test_Parallel.py b/bemani/tests/test_Parallel.py index b10e65f..19c8d84 100644 --- a/bemani/tests/test_Parallel.py +++ b/bemani/tests/test_Parallel.py @@ -6,7 +6,6 @@ from bemani.common import Parallel class TestParallel(unittest.TestCase): - def test_empty(self) -> None: results = Parallel.execute([]) self.assertEqual(results, []) @@ -18,26 +17,30 @@ class TestParallel(unittest.TestCase): self.assertEqual(results, []) def test_basic(self) -> None: - results = Parallel.execute([ - lambda: 1, - lambda: 2, - lambda: 3, - lambda: 4, - lambda: 5, - ]) + results = Parallel.execute( + [ + lambda: 1, + lambda: 2, + lambda: 3, + lambda: 4, + lambda: 5, + ] + ) self.assertEqual(results, [1, 2, 3, 4, 5]) def test_function(self) -> None: def fun(x: int) -> int: return -x - results = Parallel.execute([ - lambda: fun(1), - lambda: fun(2), - lambda: fun(3), - lambda: fun(4), - lambda: fun(5), - ]) + results = Parallel.execute( + [ + lambda: fun(1), + lambda: fun(2), + lambda: fun(3), + lambda: fun(4), + lambda: fun(5), + ] + ) self.assertEqual(results, [-1, -2, -3, -4, -5]) def test_map(self) -> None: diff --git a/bemani/tests/test_PlayStats.py b/bemani/tests/test_PlayStats.py index 36060c0..963067c 100644 --- a/bemani/tests/test_PlayStats.py +++ b/bemani/tests/test_PlayStats.py @@ -21,7 +21,9 @@ def mock_stats(existing_value: Dict[str, Any]) -> Mock: data = Mock() data.local = Mock() data.local.game = Mock() - data.local.game.get_settings = Mock(return_value=ValidatedDict(existing_value) if existing_value else None) + data.local.game.get_settings = Mock( + return_value=ValidatedDict(existing_value) if existing_value else None + ) data.local.game.put_settings = Mock() return data @@ -31,9 +33,8 @@ def saved_stats(mock: Mock) -> ValidatedDict: class TestPlayStats(unittest.TestCase): - def test_get_brand_new_profile(self) -> None: - with freeze_time('2021-08-24'): + with freeze_time("2021-08-24"): stats = None data = mock_stats(stats) base = InstantiableBase(data, Mock(), Mock()) @@ -49,7 +50,7 @@ class TestPlayStats(unittest.TestCase): self.assertEqual(settings.last_play_timestamp, Time.now()) def test_put_brand_new_profile(self) -> None: - with freeze_time('2021-08-24'): + with freeze_time("2021-08-24"): stats = None data = mock_stats(stats) base = InstantiableBase(data, Mock(), Mock()) @@ -58,25 +59,27 @@ class TestPlayStats(unittest.TestCase): base.update_play_statistics(UserID(1337), settings) new_settings = saved_stats(data) - self.assertEqual(new_settings.get_int('total_plays'), 1) - self.assertEqual(new_settings.get_int('today_plays'), 1) - self.assertEqual(new_settings.get_int('total_days'), 1) - self.assertEqual(new_settings.get_int('consecutive_days'), 1) - self.assertEqual(new_settings.get_int('first_play_timestamp'), Time.now()) - self.assertEqual(new_settings.get_int('last_play_timestamp'), Time.now()) - self.assertEqual(new_settings.get_int_array('last_play_date', 3), Time.todays_date()) + self.assertEqual(new_settings.get_int("total_plays"), 1) + self.assertEqual(new_settings.get_int("today_plays"), 1) + self.assertEqual(new_settings.get_int("total_days"), 1) + self.assertEqual(new_settings.get_int("consecutive_days"), 1) + self.assertEqual(new_settings.get_int("first_play_timestamp"), Time.now()) + self.assertEqual(new_settings.get_int("last_play_timestamp"), Time.now()) + self.assertEqual( + new_settings.get_int_array("last_play_date", 3), Time.todays_date() + ) def test_get_played_today(self) -> None: - with freeze_time('2021-08-24'): + with freeze_time("2021-08-24"): play_date = Time.todays_date() stats = { - 'total_plays': 1234, - 'today_plays': 420, - 'total_days': 10, - 'first_play_timestamp': 1234567890, - 'last_play_timestamp': 9876543210, - 'consecutive_days': 69, - 'last_play_date': [play_date[0], play_date[1], play_date[2]], + "total_plays": 1234, + "today_plays": 420, + "total_days": 10, + "first_play_timestamp": 1234567890, + "last_play_timestamp": 9876543210, + "consecutive_days": 69, + "last_play_date": [play_date[0], play_date[1], play_date[2]], } data = mock_stats(stats) base = InstantiableBase(data, Mock(), Mock()) @@ -92,16 +95,16 @@ class TestPlayStats(unittest.TestCase): self.assertEqual(settings.last_play_timestamp, 9876543210) def test_put_played_today(self) -> None: - with freeze_time('2021-08-24'): + with freeze_time("2021-08-24"): play_date = Time.todays_date() stats = { - 'total_plays': 1234, - 'today_plays': 420, - 'total_days': 10, - 'first_play_timestamp': 1234567890, - 'last_play_timestamp': 1234567890, - 'consecutive_days': 69, - 'last_play_date': [play_date[0], play_date[1], play_date[2]], + "total_plays": 1234, + "today_plays": 420, + "total_days": 10, + "first_play_timestamp": 1234567890, + "last_play_timestamp": 1234567890, + "consecutive_days": 69, + "last_play_date": [play_date[0], play_date[1], play_date[2]], } data = mock_stats(stats) base = InstantiableBase(data, Mock(), Mock()) @@ -110,25 +113,27 @@ class TestPlayStats(unittest.TestCase): base.update_play_statistics(UserID(1337), settings) new_settings = saved_stats(data) - self.assertEqual(new_settings.get_int('total_plays'), 1235) - self.assertEqual(new_settings.get_int('today_plays'), 421) - self.assertEqual(new_settings.get_int('total_days'), 10) - self.assertEqual(new_settings.get_int('consecutive_days'), 69) - self.assertEqual(new_settings.get_int('first_play_timestamp'), 1234567890) - self.assertEqual(new_settings.get_int('last_play_timestamp'), Time.now()) - self.assertEqual(new_settings.get_int_array('last_play_date', 3), Time.todays_date()) + self.assertEqual(new_settings.get_int("total_plays"), 1235) + self.assertEqual(new_settings.get_int("today_plays"), 421) + self.assertEqual(new_settings.get_int("total_days"), 10) + self.assertEqual(new_settings.get_int("consecutive_days"), 69) + self.assertEqual(new_settings.get_int("first_play_timestamp"), 1234567890) + self.assertEqual(new_settings.get_int("last_play_timestamp"), Time.now()) + self.assertEqual( + new_settings.get_int_array("last_play_date", 3), Time.todays_date() + ) def test_get_played_yesterday(self) -> None: - with freeze_time('2021-08-24'): + with freeze_time("2021-08-24"): play_date = Time.yesterdays_date() stats = { - 'total_plays': 1234, - 'today_plays': 420, - 'total_days': 10, - 'first_play_timestamp': 1234567890, - 'last_play_timestamp': 9876543210, - 'consecutive_days': 69, - 'last_play_date': [play_date[0], play_date[1], play_date[2]], + "total_plays": 1234, + "today_plays": 420, + "total_days": 10, + "first_play_timestamp": 1234567890, + "last_play_timestamp": 9876543210, + "consecutive_days": 69, + "last_play_date": [play_date[0], play_date[1], play_date[2]], } data = mock_stats(stats) base = InstantiableBase(data, Mock(), Mock()) @@ -144,16 +149,16 @@ class TestPlayStats(unittest.TestCase): self.assertEqual(settings.last_play_timestamp, 9876543210) def test_put_played_yesterday(self) -> None: - with freeze_time('2021-08-24'): + with freeze_time("2021-08-24"): play_date = Time.yesterdays_date() stats = { - 'total_plays': 1234, - 'today_plays': 420, - 'total_days': 10, - 'first_play_timestamp': 1234567890, - 'last_play_timestamp': 1234567890, - 'consecutive_days': 69, - 'last_play_date': [play_date[0], play_date[1], play_date[2]], + "total_plays": 1234, + "today_plays": 420, + "total_days": 10, + "first_play_timestamp": 1234567890, + "last_play_timestamp": 1234567890, + "consecutive_days": 69, + "last_play_date": [play_date[0], play_date[1], play_date[2]], } data = mock_stats(stats) base = InstantiableBase(data, Mock(), Mock()) @@ -162,24 +167,26 @@ class TestPlayStats(unittest.TestCase): base.update_play_statistics(UserID(1337), settings) new_settings = saved_stats(data) - self.assertEqual(new_settings.get_int('total_plays'), 1235) - self.assertEqual(new_settings.get_int('today_plays'), 1) - self.assertEqual(new_settings.get_int('total_days'), 11) - self.assertEqual(new_settings.get_int('consecutive_days'), 70) - self.assertEqual(new_settings.get_int('first_play_timestamp'), 1234567890) - self.assertEqual(new_settings.get_int('last_play_timestamp'), Time.now()) - self.assertEqual(new_settings.get_int_array('last_play_date', 3), Time.todays_date()) + self.assertEqual(new_settings.get_int("total_plays"), 1235) + self.assertEqual(new_settings.get_int("today_plays"), 1) + self.assertEqual(new_settings.get_int("total_days"), 11) + self.assertEqual(new_settings.get_int("consecutive_days"), 70) + self.assertEqual(new_settings.get_int("first_play_timestamp"), 1234567890) + self.assertEqual(new_settings.get_int("last_play_timestamp"), Time.now()) + self.assertEqual( + new_settings.get_int_array("last_play_date", 3), Time.todays_date() + ) def test_get_played_awhile_ago(self) -> None: - with freeze_time('2021-08-24'): + with freeze_time("2021-08-24"): stats = { - 'total_plays': 1234, - 'today_plays': 420, - 'total_days': 10, - 'first_play_timestamp': 1234567890, - 'last_play_timestamp': 9876543210, - 'consecutive_days': 69, - 'last_play_date': [2010, 4, 20], + "total_plays": 1234, + "today_plays": 420, + "total_days": 10, + "first_play_timestamp": 1234567890, + "last_play_timestamp": 9876543210, + "consecutive_days": 69, + "last_play_date": [2010, 4, 20], } data = mock_stats(stats) base = InstantiableBase(data, Mock(), Mock()) @@ -195,15 +202,15 @@ class TestPlayStats(unittest.TestCase): self.assertEqual(settings.last_play_timestamp, 9876543210) def test_put_played_awhile_ago(self) -> None: - with freeze_time('2021-08-24'): + with freeze_time("2021-08-24"): stats = { - 'total_plays': 1234, - 'today_plays': 420, - 'total_days': 10, - 'first_play_timestamp': 1234567890, - 'last_play_timestamp': 1234567890, - 'consecutive_days': 69, - 'last_play_date': [2010, 4, 20], + "total_plays": 1234, + "today_plays": 420, + "total_days": 10, + "first_play_timestamp": 1234567890, + "last_play_timestamp": 1234567890, + "consecutive_days": 69, + "last_play_date": [2010, 4, 20], } data = mock_stats(stats) base = InstantiableBase(data, Mock(), Mock()) @@ -212,49 +219,51 @@ class TestPlayStats(unittest.TestCase): base.update_play_statistics(UserID(1337), settings) new_settings = saved_stats(data) - self.assertEqual(new_settings.get_int('total_plays'), 1235) - self.assertEqual(new_settings.get_int('today_plays'), 1) - self.assertEqual(new_settings.get_int('total_days'), 11) - self.assertEqual(new_settings.get_int('consecutive_days'), 1) - self.assertEqual(new_settings.get_int('first_play_timestamp'), 1234567890) - self.assertEqual(new_settings.get_int('last_play_timestamp'), Time.now()) - self.assertEqual(new_settings.get_int_array('last_play_date', 3), Time.todays_date()) + self.assertEqual(new_settings.get_int("total_plays"), 1235) + self.assertEqual(new_settings.get_int("today_plays"), 1) + self.assertEqual(new_settings.get_int("total_days"), 11) + self.assertEqual(new_settings.get_int("consecutive_days"), 1) + self.assertEqual(new_settings.get_int("first_play_timestamp"), 1234567890) + self.assertEqual(new_settings.get_int("last_play_timestamp"), Time.now()) + self.assertEqual( + new_settings.get_int_array("last_play_date", 3), Time.todays_date() + ) def test_get_extra_settings(self) -> None: - with freeze_time('2021-08-24'): + with freeze_time("2021-08-24"): stats = { - 'total_plays': 1234, - 'key': 'value', - 'int': 1337, + "total_plays": 1234, + "key": "value", + "int": 1337, } data = mock_stats(stats) base = InstantiableBase(data, Mock(), Mock()) settings = base.get_play_statistics(UserID(1337)) - self.assertEqual(settings.get_int('int'), 1337) - self.assertEqual(settings.get_str('key'), 'value') - self.assertEqual(settings.get_int('total_plays'), 0) + self.assertEqual(settings.get_int("int"), 1337) + self.assertEqual(settings.get_str("key"), "value") + self.assertEqual(settings.get_int("total_plays"), 0) def test_put_extra_settings(self) -> None: - with freeze_time('2021-08-24'): + with freeze_time("2021-08-24"): stats = { - 'total_plays': 1234, - 'key': 'value', - 'int': 1337, + "total_plays": 1234, + "key": "value", + "int": 1337, } data = mock_stats(stats) base = InstantiableBase(data, Mock(), Mock()) settings = base.get_play_statistics(UserID(1337)) - settings.replace_int('int', 420) - settings.replace_int('int2', 69) - settings.replace_int('total_plays', 37) + settings.replace_int("int", 420) + settings.replace_int("int2", 69) + settings.replace_int("total_plays", 37) base.update_play_statistics(UserID(1337), settings) new_settings = saved_stats(data) - self.assertEqual(new_settings.get_int('int'), 420) - self.assertEqual(new_settings.get_str('key'), 'value') - self.assertEqual(new_settings.get_int('int2'), 69) - self.assertEqual(new_settings.get_int('total_plays'), 1235) + self.assertEqual(new_settings.get_int("int"), 420) + self.assertEqual(new_settings.get_str("key"), "value") + self.assertEqual(new_settings.get_int("int2"), 69) + self.assertEqual(new_settings.get_int("total_plays"), 1235) diff --git a/bemani/tests/test_RC4.py b/bemani/tests/test_RC4.py index 7ec2fc0..53262ea 100644 --- a/bemani/tests/test_RC4.py +++ b/bemani/tests/test_RC4.py @@ -6,11 +6,10 @@ from bemani.protocol.protocol import EAmuseProtocol class TestRC4Cipher(unittest.TestCase): - def test_crypto(self) -> None: - key = b'12345' - data = b'This is a wonderful text string to cypher.' - encrypted = b'\x04]Q\x11\x0cw\x7fO\xfa\x03\xa3\xdf\xb6\x02\xb7d\x9f\x13U\x19\xc9-j\x96\x15yl\x98\xee_<\xfa\x9b\x8f\xbe}\xf4\x05l5\x0e\xd6' + key = b"12345" + data = b"This is a wonderful text string to cypher." + encrypted = b"\x04]Q\x11\x0cw\x7fO\xfa\x03\xa3\xdf\xb6\x02\xb7d\x9f\x13U\x19\xc9-j\x96\x15yl\x98\xee_<\xfa\x9b\x8f\xbe}\xf4\x05l5\x0e\xd6" proto = EAmuseProtocol() cyphertext = proto._rc4_crypt(data, key) diff --git a/bemani/tests/test_RemoteUser.py b/bemani/tests/test_RemoteUser.py index d7ca307..fcc2a66 100644 --- a/bemani/tests/test_RemoteUser.py +++ b/bemani/tests/test_RemoteUser.py @@ -6,7 +6,6 @@ from bemani.data.types import UserID class TestRemoteUser(unittest.TestCase): - def test_id_mangling(self) -> None: card = "E0040100DEADBEEF" userid = RemoteUser.card_to_userid(card) diff --git a/bemani/tests/test_Time.py b/bemani/tests/test_Time.py index 6e228ea..57db363 100644 --- a/bemani/tests/test_Time.py +++ b/bemani/tests/test_Time.py @@ -6,36 +6,35 @@ from bemani.common.time import Time class TestTime(unittest.TestCase): - def test_days_since_epoch(self) -> None: # Verify that we just get the right number of days. - with freeze_time('2016-01-01'): + with freeze_time("2016-01-01"): self.assertEqual(16797, Time.week_in_days_since_epoch()) # Verify that adding a day doesn't change the value, because it rounds to nearest week. - with freeze_time('2016-01-02'): + with freeze_time("2016-01-02"): self.assertEqual(16797, Time.week_in_days_since_epoch()) # Verify that adding a week makes the value go up by 7 days. - with freeze_time('2016-01-08'): + with freeze_time("2016-01-08"): self.assertEqual(16804, Time.week_in_days_since_epoch()) def test_end_of_this_week(self) -> None: # Verify that we can detect the end of the month properly - with freeze_time('2017-10-16'): + with freeze_time("2017-10-16"): self.assertEqual(1508716800, Time.end_of_this_week()) def test_beginning_of_this_week(self) -> None: # Verify that we can detect the end of the month properly - with freeze_time('2017-10-16'): + with freeze_time("2017-10-16"): self.assertEqual(1508112000, Time.beginning_of_this_week()) def test_end_of_this_month(self) -> None: # Verify that we can detect the end of the month properly - with freeze_time('2017-10-16'): + with freeze_time("2017-10-16"): self.assertEqual(1509494400, Time.end_of_this_month()) def test_beginning_of_this_month(self) -> None: # Verify that we can detect the end of the month properly - with freeze_time('2017-10-16'): + with freeze_time("2017-10-16"): self.assertEqual(1506816000, Time.beginning_of_this_month()) diff --git a/bemani/tests/test_ValidatedDict.py b/bemani/tests/test_ValidatedDict.py index c806960..e00e9a6 100644 --- a/bemani/tests/test_ValidatedDict.py +++ b/bemani/tests/test_ValidatedDict.py @@ -5,252 +5,288 @@ from bemani.common import ValidatedDict, intish class TestIntish(unittest.TestCase): - def test_none(self) -> None: self.assertEqual(intish(None), None) def test_int(self) -> None: - self.assertEqual(intish('3'), 3) + self.assertEqual(intish("3"), 3) def test_str(self) -> None: - self.assertEqual(intish('str'), None) + self.assertEqual(intish("str"), None) class TestValidatedDict(unittest.TestCase): - def test_empty_dict(self) -> None: # Empty dictionary gets validict = ValidatedDict() - self.assertEqual(validict.get_int('int'), 0) - self.assertEqual(validict.get_int('int', 2), 2) - self.assertEqual(validict.get_float('float'), 0.0) - self.assertEqual(validict.get_float('float', 2.0), 2.0) - self.assertEqual(validict.get_bool('bool'), False) - self.assertEqual(validict.get_bool('bool', True), True) - self.assertEqual(validict.get_str('str'), '') - self.assertEqual(validict.get_str('str', 'test'), 'test') - self.assertEqual(validict.get_bytes('bytes'), b'') - self.assertEqual(validict.get_bytes('bytes', b'test'), b'test') - self.assertEqual(validict.get_int_array('int_array', 3), [0, 0, 0]) - self.assertEqual(validict.get_int_array('int_array', 3, [1, 2, 3]), [1, 2, 3]) - self.assertEqual(validict.get_bool_array('bool_array', 2), [False, False]) - self.assertEqual(validict.get_bool_array('bool_array', 2, [False, True]), [False, True]) - self.assertEqual(validict.get_str_array('str_array', 3), ['', '', '']) - self.assertEqual(validict.get_str_array('str_array', 3, ['1', '2', '3']), ['1', '2', '3']) - self.assertEqual(validict.get_bytes_array('bytes_array', 3), [b'', b'', b'']) - self.assertEqual(validict.get_bytes_array('bytes_array', 3, [b'1', b'2', b'3']), [b'1', b'2', b'3']) - self.assertTrue(isinstance(validict.get_dict('dict'), dict)) - self.assertEqual(validict.get_dict('dict').get_int('test'), 0) + self.assertEqual(validict.get_int("int"), 0) + self.assertEqual(validict.get_int("int", 2), 2) + self.assertEqual(validict.get_float("float"), 0.0) + self.assertEqual(validict.get_float("float", 2.0), 2.0) + self.assertEqual(validict.get_bool("bool"), False) + self.assertEqual(validict.get_bool("bool", True), True) + self.assertEqual(validict.get_str("str"), "") + self.assertEqual(validict.get_str("str", "test"), "test") + self.assertEqual(validict.get_bytes("bytes"), b"") + self.assertEqual(validict.get_bytes("bytes", b"test"), b"test") + self.assertEqual(validict.get_int_array("int_array", 3), [0, 0, 0]) + self.assertEqual(validict.get_int_array("int_array", 3, [1, 2, 3]), [1, 2, 3]) + self.assertEqual(validict.get_bool_array("bool_array", 2), [False, False]) + self.assertEqual( + validict.get_bool_array("bool_array", 2, [False, True]), [False, True] + ) + self.assertEqual(validict.get_str_array("str_array", 3), ["", "", ""]) + self.assertEqual( + validict.get_str_array("str_array", 3, ["1", "2", "3"]), ["1", "2", "3"] + ) + self.assertEqual(validict.get_bytes_array("bytes_array", 3), [b"", b"", b""]) + self.assertEqual( + validict.get_bytes_array("bytes_array", 3, [b"1", b"2", b"3"]), + [b"1", b"2", b"3"], + ) + self.assertTrue(isinstance(validict.get_dict("dict"), dict)) + self.assertEqual(validict.get_dict("dict").get_int("test"), 0) def test_normal_dict(self) -> None: # Existing info gets - validict = ValidatedDict({ - 'int': 5, - 'float': 5.5, - 'bool': True, - 'str': 'foobar', - 'bytes': b'foobar', - 'int_array': [3, 2, 1], - 'bool_array': [True, False], - 'str_array': ['3', '4', '5'], - 'bytes_array': [b'3', b'5', b'7'], - 'dict': {'test': 123}, - }) - self.assertEqual(validict.get_int('int'), 5) - self.assertEqual(validict.get_int('int', 2), 5) - self.assertEqual(validict.get_float('float'), 5.5) - self.assertEqual(validict.get_float('float', 2.0), 5.5) - self.assertEqual(validict.get_bool('bool'), True) - self.assertEqual(validict.get_bool('bool', False), True) - self.assertEqual(validict.get_str('str'), 'foobar') - self.assertEqual(validict.get_str('str', 'test'), 'foobar') - self.assertEqual(validict.get_bytes('bytes'), b'foobar') - self.assertEqual(validict.get_bytes('bytes', b'test'), b'foobar') - self.assertEqual(validict.get_int_array('int_array', 3), [3, 2, 1]) - self.assertEqual(validict.get_int_array('int_array', 3, [1, 2, 3]), [3, 2, 1]) - self.assertEqual(validict.get_bool_array('bool_array', 2), [True, False]) - self.assertEqual(validict.get_bool_array('bool_array', 2, [False, True]), [True, False]) - self.assertEqual(validict.get_str_array('str_array', 3), ['3', '4', '5']) - self.assertEqual(validict.get_str_array('str_array', 3, ['1', '2', '3']), ['3', '4', '5']) - self.assertEqual(validict.get_bytes_array('bytes_array', 3), [b'3', b'5', b'7']) - self.assertEqual(validict.get_bytes_array('bytes_array', 3, [b'1', b'2', b'3']), [b'3', b'5', b'7']) - self.assertTrue(isinstance(validict.get_dict('dict'), dict)) - self.assertEqual(validict.get_dict('dict').get_int('test'), 123) + validict = ValidatedDict( + { + "int": 5, + "float": 5.5, + "bool": True, + "str": "foobar", + "bytes": b"foobar", + "int_array": [3, 2, 1], + "bool_array": [True, False], + "str_array": ["3", "4", "5"], + "bytes_array": [b"3", b"5", b"7"], + "dict": {"test": 123}, + } + ) + self.assertEqual(validict.get_int("int"), 5) + self.assertEqual(validict.get_int("int", 2), 5) + self.assertEqual(validict.get_float("float"), 5.5) + self.assertEqual(validict.get_float("float", 2.0), 5.5) + self.assertEqual(validict.get_bool("bool"), True) + self.assertEqual(validict.get_bool("bool", False), True) + self.assertEqual(validict.get_str("str"), "foobar") + self.assertEqual(validict.get_str("str", "test"), "foobar") + self.assertEqual(validict.get_bytes("bytes"), b"foobar") + self.assertEqual(validict.get_bytes("bytes", b"test"), b"foobar") + self.assertEqual(validict.get_int_array("int_array", 3), [3, 2, 1]) + self.assertEqual(validict.get_int_array("int_array", 3, [1, 2, 3]), [3, 2, 1]) + self.assertEqual(validict.get_bool_array("bool_array", 2), [True, False]) + self.assertEqual( + validict.get_bool_array("bool_array", 2, [False, True]), [True, False] + ) + self.assertEqual(validict.get_str_array("str_array", 3), ["3", "4", "5"]) + self.assertEqual( + validict.get_str_array("str_array", 3, ["1", "2", "3"]), ["3", "4", "5"] + ) + self.assertEqual(validict.get_bytes_array("bytes_array", 3), [b"3", b"5", b"7"]) + self.assertEqual( + validict.get_bytes_array("bytes_array", 3, [b"1", b"2", b"3"]), + [b"3", b"5", b"7"], + ) + self.assertTrue(isinstance(validict.get_dict("dict"), dict)) + self.assertEqual(validict.get_dict("dict").get_int("test"), 123) def test_default_on_invalid(self) -> None: # Default on invalid info stored - validict = ValidatedDict({ - 'int': 'five', - 'float': 'five', - 'bool': 'true', - 'str': 123, - 'bytes': 'str', - 'int_array': [3, 2, 1, 0], - 'bool_array': [True, False], - 'str_array': ['3', '2', '1', '0'], - 'bytes_array': [b'3', b'2', b'1', b'0'], - 'dict': 'not_a_dict', - }) - self.assertEqual(validict.get_int('int'), 0) - self.assertEqual(validict.get_int('int', 2), 2) - self.assertEqual(validict.get_float('float'), 0.0) - self.assertEqual(validict.get_float('float', 2.0), 2.0) - self.assertEqual(validict.get_bool('bool'), False) - self.assertEqual(validict.get_bool('bool', True), True) - self.assertEqual(validict.get_str('str'), '') - self.assertEqual(validict.get_str('str', 'test'), 'test') - self.assertEqual(validict.get_bytes('bytes'), b'') - self.assertEqual(validict.get_bytes('bytes', b'test'), b'test') - self.assertEqual(validict.get_int_array('int_array', 3), [0, 0, 0]) - self.assertEqual(validict.get_int_array('int_array', 3, [1, 2, 3]), [1, 2, 3]) - self.assertEqual(validict.get_bool_array('bool_array', 3), [False, False, False]) - self.assertEqual(validict.get_bool_array('bool_array', 3, [False, True, True]), [False, True, True]) - self.assertEqual(validict.get_str_array('str_array', 3), ['', '', '']) - self.assertEqual(validict.get_str_array('str_array', 3, ['1', '2', '3']), ['1', '2', '3']) - self.assertEqual(validict.get_bytes_array('bytes_array', 3), [b'', b'', b'']) - self.assertEqual(validict.get_bytes_array('bytes_array', 3, [b'1', b'2', b'3']), [b'1', b'2', b'3']) - self.assertTrue(isinstance(validict.get_dict('dict'), dict)) - self.assertEqual(validict.get_dict('dict').get_int('test'), 0) + validict = ValidatedDict( + { + "int": "five", + "float": "five", + "bool": "true", + "str": 123, + "bytes": "str", + "int_array": [3, 2, 1, 0], + "bool_array": [True, False], + "str_array": ["3", "2", "1", "0"], + "bytes_array": [b"3", b"2", b"1", b"0"], + "dict": "not_a_dict", + } + ) + self.assertEqual(validict.get_int("int"), 0) + self.assertEqual(validict.get_int("int", 2), 2) + self.assertEqual(validict.get_float("float"), 0.0) + self.assertEqual(validict.get_float("float", 2.0), 2.0) + self.assertEqual(validict.get_bool("bool"), False) + self.assertEqual(validict.get_bool("bool", True), True) + self.assertEqual(validict.get_str("str"), "") + self.assertEqual(validict.get_str("str", "test"), "test") + self.assertEqual(validict.get_bytes("bytes"), b"") + self.assertEqual(validict.get_bytes("bytes", b"test"), b"test") + self.assertEqual(validict.get_int_array("int_array", 3), [0, 0, 0]) + self.assertEqual(validict.get_int_array("int_array", 3, [1, 2, 3]), [1, 2, 3]) + self.assertEqual( + validict.get_bool_array("bool_array", 3), [False, False, False] + ) + self.assertEqual( + validict.get_bool_array("bool_array", 3, [False, True, True]), + [False, True, True], + ) + self.assertEqual(validict.get_str_array("str_array", 3), ["", "", ""]) + self.assertEqual( + validict.get_str_array("str_array", 3, ["1", "2", "3"]), ["1", "2", "3"] + ) + self.assertEqual(validict.get_bytes_array("bytes_array", 3), [b"", b"", b""]) + self.assertEqual( + validict.get_bytes_array("bytes_array", 3, [b"1", b"2", b"3"]), + [b"1", b"2", b"3"], + ) + self.assertTrue(isinstance(validict.get_dict("dict"), dict)) + self.assertEqual(validict.get_dict("dict").get_int("test"), 0) def test_replace_int(self) -> None: # Verify replace int - validict = ValidatedDict({ - 'int': 5, - }) - validict.replace_int('int', 3) - self.assertEqual(validict.get_int('int'), 3) - validict.replace_int('int', None) - self.assertEqual(validict.get_int('int'), 3) - validict.replace_int('int', 'three') - self.assertEqual(validict.get_int('int'), 3) + validict = ValidatedDict( + { + "int": 5, + } + ) + validict.replace_int("int", 3) + self.assertEqual(validict.get_int("int"), 3) + validict.replace_int("int", None) + self.assertEqual(validict.get_int("int"), 3) + validict.replace_int("int", "three") + self.assertEqual(validict.get_int("int"), 3) def test_replace_float(self) -> None: # Verify replace float - validict = ValidatedDict({ - 'float': 5.0, - }) - validict.replace_float('float', 3.0) - self.assertEqual(validict.get_float('float'), 3.0) - validict.replace_float('float', None) - self.assertEqual(validict.get_float('float'), 3.0) - validict.replace_float('float', 'three') - self.assertEqual(validict.get_float('float'), 3.0) + validict = ValidatedDict( + { + "float": 5.0, + } + ) + validict.replace_float("float", 3.0) + self.assertEqual(validict.get_float("float"), 3.0) + validict.replace_float("float", None) + self.assertEqual(validict.get_float("float"), 3.0) + validict.replace_float("float", "three") + self.assertEqual(validict.get_float("float"), 3.0) def test_replace_bool(self) -> None: # Verify replace bool - validict = ValidatedDict({ - 'bool': False, - }) - validict.replace_bool('bool', True) - self.assertEqual(validict.get_bool('bool'), True) - validict.replace_bool('bool', None) - self.assertEqual(validict.get_bool('bool'), True) - validict.replace_bool('bool', 'three') - self.assertEqual(validict.get_bool('bool'), True) + validict = ValidatedDict( + { + "bool": False, + } + ) + validict.replace_bool("bool", True) + self.assertEqual(validict.get_bool("bool"), True) + validict.replace_bool("bool", None) + self.assertEqual(validict.get_bool("bool"), True) + validict.replace_bool("bool", "three") + self.assertEqual(validict.get_bool("bool"), True) def test_replace_str(self) -> None: # Verify replace str - validict = ValidatedDict({ - 'str': 'blah', - }) - validict.replace_str('str', 'foobar') - self.assertEqual(validict.get_str('str'), 'foobar') - validict.replace_str('str', None) - self.assertEqual(validict.get_str('str'), 'foobar') - validict.replace_str('str', 5) - self.assertEqual(validict.get_str('str'), 'foobar') + validict = ValidatedDict( + { + "str": "blah", + } + ) + validict.replace_str("str", "foobar") + self.assertEqual(validict.get_str("str"), "foobar") + validict.replace_str("str", None) + self.assertEqual(validict.get_str("str"), "foobar") + validict.replace_str("str", 5) + self.assertEqual(validict.get_str("str"), "foobar") def test_replace_bytes(self) -> None: # Verify replace bytes - validict = ValidatedDict({ - 'bytes': 'blah', - }) - validict.replace_bytes('bytes', b'foobar') - self.assertEqual(validict.get_bytes('bytes'), b'foobar') - validict.replace_bytes('bytes', None) - self.assertEqual(validict.get_bytes('bytes'), b'foobar') - validict.replace_bytes('bytes', 5) - self.assertEqual(validict.get_bytes('bytes'), b'foobar') + validict = ValidatedDict( + { + "bytes": "blah", + } + ) + validict.replace_bytes("bytes", b"foobar") + self.assertEqual(validict.get_bytes("bytes"), b"foobar") + validict.replace_bytes("bytes", None) + self.assertEqual(validict.get_bytes("bytes"), b"foobar") + validict.replace_bytes("bytes", 5) + self.assertEqual(validict.get_bytes("bytes"), b"foobar") def test_replace_int_array(self) -> None: # Verify replace int_array - validict = ValidatedDict({ - 'int_array': [1, 2, 3] - }) - validict.replace_int_array('int_array', 3, [3, 2, 1]) - self.assertEqual(validict.get_int_array('int_array', 3), [3, 2, 1]) - validict.replace_int_array('int_array', 3, None) - self.assertEqual(validict.get_int_array('int_array', 3), [3, 2, 1]) - validict.replace_int_array('int_array', 3, 'bla') - self.assertEqual(validict.get_int_array('int_array', 3), [3, 2, 1]) - validict.replace_int_array('int_array', 3, [3, 2, 1, 0]) - self.assertEqual(validict.get_int_array('int_array', 3), [3, 2, 1]) + validict = ValidatedDict({"int_array": [1, 2, 3]}) + validict.replace_int_array("int_array", 3, [3, 2, 1]) + self.assertEqual(validict.get_int_array("int_array", 3), [3, 2, 1]) + validict.replace_int_array("int_array", 3, None) + self.assertEqual(validict.get_int_array("int_array", 3), [3, 2, 1]) + validict.replace_int_array("int_array", 3, "bla") + self.assertEqual(validict.get_int_array("int_array", 3), [3, 2, 1]) + validict.replace_int_array("int_array", 3, [3, 2, 1, 0]) + self.assertEqual(validict.get_int_array("int_array", 3), [3, 2, 1]) def test_replace_bool_array(self) -> None: # Verify replace bool_array - validict = ValidatedDict({ - 'bool_array': [False, True], - }) - validict.replace_bool_array('bool_array', 2, [True, False]) - self.assertEqual(validict.get_bool_array('bool_array', 2), [True, False]) - validict.replace_bool_array('bool_array', 2, None) - self.assertEqual(validict.get_bool_array('bool_array', 2), [True, False]) - validict.replace_bool_array('bool_array', 2, 'bla') - self.assertEqual(validict.get_bool_array('bool_array', 2), [True, False]) - validict.replace_bool_array('bool_array', 2, [True, True, True]) - self.assertEqual(validict.get_bool_array('bool_array', 2), [True, False]) + validict = ValidatedDict( + { + "bool_array": [False, True], + } + ) + validict.replace_bool_array("bool_array", 2, [True, False]) + self.assertEqual(validict.get_bool_array("bool_array", 2), [True, False]) + validict.replace_bool_array("bool_array", 2, None) + self.assertEqual(validict.get_bool_array("bool_array", 2), [True, False]) + validict.replace_bool_array("bool_array", 2, "bla") + self.assertEqual(validict.get_bool_array("bool_array", 2), [True, False]) + validict.replace_bool_array("bool_array", 2, [True, True, True]) + self.assertEqual(validict.get_bool_array("bool_array", 2), [True, False]) def test_replace_str_array(self) -> None: # Verify replace str_array - validict = ValidatedDict({ - 'str_array': ['1', '2', '3'] - }) - validict.replace_str_array('str_array', 3, ['3', '2', '1']) - self.assertEqual(validict.get_str_array('str_array', 3), ['3', '2', '1']) - validict.replace_str_array('str_array', 3, None) - self.assertEqual(validict.get_str_array('str_array', 3), ['3', '2', '1']) - validict.replace_str_array('str_array', 3, 'bla') - self.assertEqual(validict.get_str_array('str_array', 3), ['3', '2', '1']) - validict.replace_str_array('str_array', 3, ['3', '2', '1', '0']) - self.assertEqual(validict.get_str_array('str_array', 3), ['3', '2', '1']) + validict = ValidatedDict({"str_array": ["1", "2", "3"]}) + validict.replace_str_array("str_array", 3, ["3", "2", "1"]) + self.assertEqual(validict.get_str_array("str_array", 3), ["3", "2", "1"]) + validict.replace_str_array("str_array", 3, None) + self.assertEqual(validict.get_str_array("str_array", 3), ["3", "2", "1"]) + validict.replace_str_array("str_array", 3, "bla") + self.assertEqual(validict.get_str_array("str_array", 3), ["3", "2", "1"]) + validict.replace_str_array("str_array", 3, ["3", "2", "1", "0"]) + self.assertEqual(validict.get_str_array("str_array", 3), ["3", "2", "1"]) def test_replace_bytes_array(self) -> None: # Verify replace bytes_array - validict = ValidatedDict({ - 'bytes_array': [b'1', b'2', b'3'] - }) - validict.replace_bytes_array('bytes_array', 3, [b'3', b'2', b'1']) - self.assertEqual(validict.get_bytes_array('bytes_array', 3), [b'3', b'2', b'1']) - validict.replace_bytes_array('bytes_array', 3, None) - self.assertEqual(validict.get_bytes_array('bytes_array', 3), [b'3', b'2', b'1']) - validict.replace_bytes_array('bytes_array', 3, 'bla') - self.assertEqual(validict.get_bytes_array('bytes_array', 3), [b'3', b'2', b'1']) - validict.replace_bytes_array('bytes_array', 3, [b'3', b'2', b'1', b'0']) - self.assertEqual(validict.get_bytes_array('bytes_array', 3), [b'3', b'2', b'1']) + validict = ValidatedDict({"bytes_array": [b"1", b"2", b"3"]}) + validict.replace_bytes_array("bytes_array", 3, [b"3", b"2", b"1"]) + self.assertEqual(validict.get_bytes_array("bytes_array", 3), [b"3", b"2", b"1"]) + validict.replace_bytes_array("bytes_array", 3, None) + self.assertEqual(validict.get_bytes_array("bytes_array", 3), [b"3", b"2", b"1"]) + validict.replace_bytes_array("bytes_array", 3, "bla") + self.assertEqual(validict.get_bytes_array("bytes_array", 3), [b"3", b"2", b"1"]) + validict.replace_bytes_array("bytes_array", 3, [b"3", b"2", b"1", b"0"]) + self.assertEqual(validict.get_bytes_array("bytes_array", 3), [b"3", b"2", b"1"]) def test_replace_dict(self) -> None: # Verify replace dict - validict = ValidatedDict({ - 'dict': {}, - }) - validict.replace_dict('dict', {'yay': 'bla'}) - self.assertTrue(isinstance(validict.get_dict('dict'), dict)) - self.assertEqual(validict.get_dict('dict').get_str('yay'), 'bla') - validict.replace_dict('dict', None) - self.assertEqual(validict.get_dict('dict').get_str('yay'), 'bla') - validict.replace_dict('dict', 'three') - self.assertEqual(validict.get_dict('dict').get_str('yay'), 'bla') + validict = ValidatedDict( + { + "dict": {}, + } + ) + validict.replace_dict("dict", {"yay": "bla"}) + self.assertTrue(isinstance(validict.get_dict("dict"), dict)) + self.assertEqual(validict.get_dict("dict").get_str("yay"), "bla") + validict.replace_dict("dict", None) + self.assertEqual(validict.get_dict("dict").get_str("yay"), "bla") + validict.replace_dict("dict", "three") + self.assertEqual(validict.get_dict("dict").get_str("yay"), "bla") def test_increment_int(self) -> None: # Verify increment_int - validict = ValidatedDict({ - 'int': 5, - 'int2': 'str', - }) - validict.increment_int('int') - self.assertEqual(validict.get_int('int'), 6) - validict.increment_int('int2') - self.assertEqual(validict.get_int('int2'), 1) - validict.increment_int('int3') - self.assertEqual(validict.get_int('int3'), 1) + validict = ValidatedDict( + { + "int": 5, + "int2": "str", + } + ) + validict.increment_int("int") + self.assertEqual(validict.get_int("int"), 6) + validict.increment_int("int2") + self.assertEqual(validict.get_int("int2"), 1) + validict.increment_int("int3") + self.assertEqual(validict.get_int("int3"), 1) diff --git a/bemani/tests/test_XmlDecoder.py b/bemani/tests/test_XmlDecoder.py index 1fdab5c..272e080 100644 --- a/bemani/tests/test_XmlDecoder.py +++ b/bemani/tests/test_XmlDecoder.py @@ -5,110 +5,111 @@ from bemani.protocol.xml import XmlDecoder class TestXmlDecoder(unittest.TestCase): - def test_detect_encoding(self) -> None: - xml = XmlDecoder(b'', 'ascii') + xml = XmlDecoder(b'', "ascii") tree = xml.get_tree() - self.assertEqual(xml.encoding, 'utf-8') + self.assertEqual(xml.encoding, "utf-8") self.assertEqual(tree, None) - xml = XmlDecoder(b'', 'ascii') + xml = XmlDecoder(b'', "ascii") tree = xml.get_tree() - self.assertEqual(xml.encoding, 'utf-8') + self.assertEqual(xml.encoding, "utf-8") self.assertEqual(tree, None) def test_decode_void(self) -> None: - xml = XmlDecoder(b'', 'ascii') + xml = XmlDecoder(b"", "ascii") tree = xml.get_tree() self.assertEqual(tree.children, []) - self.assertEqual(tree.name, 'node') + self.assertEqual(tree.name, "node") self.assertEqual(tree.attributes, {}) - self.assertEqual(tree.data_type, 'void') + self.assertEqual(tree.data_type, "void") self.assertEqual(tree.value, None) - xml = XmlDecoder(b'', 'ascii') + xml = XmlDecoder(b"", "ascii") tree = xml.get_tree() self.assertEqual(tree.children, []) - self.assertEqual(tree.name, 'node') + self.assertEqual(tree.name, "node") self.assertEqual(tree.attributes, {}) - self.assertEqual(tree.data_type, 'void') + self.assertEqual(tree.data_type, "void") self.assertEqual(tree.value, None) def test_decode_attributes(self) -> None: - xml = XmlDecoder(b'', 'ascii') + xml = XmlDecoder(b'', "ascii") tree = xml.get_tree() self.assertEqual(tree.children, []) - self.assertEqual(tree.name, 'node') - self.assertEqual(tree.attributes, {'attr1': 'foo', 'attr2': 'bar'}) - self.assertEqual(tree.data_type, 'void') + self.assertEqual(tree.name, "node") + self.assertEqual(tree.attributes, {"attr1": "foo", "attr2": "bar"}) + self.assertEqual(tree.data_type, "void") self.assertEqual(tree.value, None) - xml = XmlDecoder(b'', 'ascii') + xml = XmlDecoder(b'', "ascii") tree = xml.get_tree() self.assertEqual(tree.children, []) - self.assertEqual(tree.name, 'node') - self.assertEqual(tree.attributes, {'attr1': 'foo', 'attr2': 'bar'}) - self.assertEqual(tree.data_type, 'void') + self.assertEqual(tree.name, "node") + self.assertEqual(tree.attributes, {"attr1": "foo", "attr2": "bar"}) + self.assertEqual(tree.data_type, "void") self.assertEqual(tree.value, None) - xml = XmlDecoder(b'', 'ascii') + xml = XmlDecoder(b'', "ascii") tree = xml.get_tree() self.assertEqual(tree.children, []) - self.assertEqual(tree.name, 'node') - self.assertEqual(tree.attributes, {'attr1': 'foo', 'attr2': 'bar'}) - self.assertEqual(tree.data_type, 'void') + self.assertEqual(tree.name, "node") + self.assertEqual(tree.attributes, {"attr1": "foo", "attr2": "bar"}) + self.assertEqual(tree.data_type, "void") self.assertEqual(tree.value, None) def test_decode_bin(self) -> None: - xml = XmlDecoder(b'DEADBEEF', 'ascii') + xml = XmlDecoder(b'DEADBEEF', "ascii") tree = xml.get_tree() self.assertEqual(tree.children, []) - self.assertEqual(tree.name, 'node') + self.assertEqual(tree.name, "node") self.assertEqual(tree.attributes, {}) - self.assertEqual(tree.data_type, 'bin') - self.assertEqual(tree.value, b'\xDE\xAD\xBE\xEF') + self.assertEqual(tree.data_type, "bin") + self.assertEqual(tree.value, b"\xDE\xAD\xBE\xEF") - xml = XmlDecoder(b'\nDEADBEEF\n', 'ascii') + xml = XmlDecoder(b'\nDEADBEEF\n', "ascii") tree = xml.get_tree() self.assertEqual(tree.children, []) - self.assertEqual(tree.name, 'node') + self.assertEqual(tree.name, "node") self.assertEqual(tree.attributes, {}) - self.assertEqual(tree.data_type, 'bin') - self.assertEqual(tree.value, b'\xDE\xAD\xBE\xEF') + self.assertEqual(tree.data_type, "bin") + self.assertEqual(tree.value, b"\xDE\xAD\xBE\xEF") - xml = XmlDecoder(b' D E A D B E E F ', 'ascii') + xml = XmlDecoder(b' D E A D B E E F ', "ascii") tree = xml.get_tree() self.assertEqual(tree.children, []) - self.assertEqual(tree.name, 'node') + self.assertEqual(tree.name, "node") self.assertEqual(tree.attributes, {}) - self.assertEqual(tree.data_type, 'bin') - self.assertEqual(tree.value, b'\xDE\xAD\xBE\xEF') + self.assertEqual(tree.data_type, "bin") + self.assertEqual(tree.value, b"\xDE\xAD\xBE\xEF") def test_decode_array(self) -> None: - xml = XmlDecoder(b'1 2 3 4', 'ascii') + xml = XmlDecoder(b'1 2 3 4', "ascii") tree = xml.get_tree() self.assertEqual(tree.children, []) - self.assertEqual(tree.name, 'node') + self.assertEqual(tree.name, "node") self.assertEqual(tree.attributes, {}) - self.assertEqual(tree.data_type, 'u32') + self.assertEqual(tree.data_type, "u32") self.assertEqual(tree.value, [1, 2, 3, 4]) - xml = XmlDecoder(b'\n1\n2\n3\n4\n', 'ascii') + xml = XmlDecoder( + b'\n1\n2\n3\n4\n', "ascii" + ) tree = xml.get_tree() self.assertEqual(tree.children, []) - self.assertEqual(tree.name, 'node') + self.assertEqual(tree.name, "node") self.assertEqual(tree.attributes, {}) - self.assertEqual(tree.data_type, 'u32') + self.assertEqual(tree.data_type, "u32") self.assertEqual(tree.value, [1, 2, 3, 4]) diff --git a/bemani/tests/test_afp_decompile.py b/bemani/tests/test_afp_decompile.py index 0fbd47b..1aa07e0 100644 --- a/bemani/tests/test_afp_decompile.py +++ b/bemani/tests/test_afp_decompile.py @@ -4,7 +4,13 @@ import unittest from typing import Dict, List, Sequence, Tuple, Union from bemani.tests.helpers import ExtendedTestCase -from bemani.format.afp.decompile import ByteCodeDecompiler, ByteCode, BitVector, ByteCodeChunk, ControlFlow +from bemani.format.afp.decompile import ( + ByteCodeDecompiler, + ByteCode, + BitVector, + ByteCodeChunk, + ControlFlow, +) from bemani.format.afp.types import ( AP2Action, IfAction, @@ -38,7 +44,6 @@ CLOSE_BRACKET = "}" class TestAFPBitVector(unittest.TestCase): - def test_simple(self) -> None: bv = BitVector(5) @@ -150,7 +155,9 @@ class TestAFPControlGraph(ExtendedTestCase): actions[-1].offset + 1, ) - def __call_graph(self, bytecode: ByteCode) -> Tuple[Dict[int, ByteCodeChunk], Dict[int, int]]: + def __call_graph( + self, bytecode: ByteCode + ) -> Tuple[Dict[int, ByteCodeChunk], Dict[int, int]]: # Just create a dummy compiler so we can access the internal method for testing. bcd = ByteCodeDecompiler(bytecode, optimize=True) @@ -158,16 +165,20 @@ class TestAFPControlGraph(ExtendedTestCase): chunks, offset_map = bcd._graph_control_flow(bytecode) return {chunk.id: chunk for chunk in chunks}, offset_map - def __equiv(self, bytecode: Union[ByteCode, ByteCodeChunk, List[AP2Action]]) -> List[str]: + def __equiv( + self, bytecode: Union[ByteCode, ByteCodeChunk, List[AP2Action]] + ) -> List[str]: if isinstance(bytecode, (ByteCode, ByteCodeChunk)): return [str(x) for x in bytecode.actions] else: return [str(x) for x in bytecode] def test_simple_bytecode(self) -> None: - bytecode = self.__make_bytecode([ - AP2Action(100, AP2Action.STOP), - ]) + bytecode = self.__make_bytecode( + [ + AP2Action(100, AP2Action.STOP), + ] + ) chunks_by_id, offset_map = self.__call_graph(bytecode) self.assertEqual(offset_map, {100: 0, 101: 1}) self.assertItemsEqual(chunks_by_id.keys(), {0, 1}) @@ -181,18 +192,23 @@ class TestAFPControlGraph(ExtendedTestCase): self.assertEqual(self.__equiv(chunks_by_id[1]), []) def test_jump_handling(self) -> None: - bytecode = self.__make_bytecode([ - JumpAction(100, 102), - JumpAction(101, 104), - JumpAction(102, 101), - JumpAction(103, 106), - JumpAction(104, 103), - JumpAction(105, 107), - JumpAction(106, 105), - AP2Action(107, AP2Action.STOP), - ]) + bytecode = self.__make_bytecode( + [ + JumpAction(100, 102), + JumpAction(101, 104), + JumpAction(102, 101), + JumpAction(103, 106), + JumpAction(104, 103), + JumpAction(105, 107), + JumpAction(106, 105), + AP2Action(107, AP2Action.STOP), + ] + ) chunks_by_id, offset_map = self.__call_graph(bytecode) - self.assertEqual(offset_map, {100: 0, 101: 1, 102: 2, 103: 3, 104: 4, 105: 5, 106: 6, 107: 7, 108: 8}) + self.assertEqual( + offset_map, + {100: 0, 101: 1, 102: 2, 103: 3, 104: 4, 105: 5, 106: 6, 107: 7, 108: 8}, + ) self.assertItemsEqual(chunks_by_id.keys(), {0, 1, 2, 3, 4, 5, 6, 7, 8}) self.assertItemsEqual(chunks_by_id[0].previous_chunks, []) self.assertItemsEqual(chunks_by_id[0].next_chunks, [2]) @@ -214,24 +230,40 @@ class TestAFPControlGraph(ExtendedTestCase): self.assertItemsEqual(chunks_by_id[8].next_chunks, []) # Also verify the code - self.assertEqual(self.__equiv(chunks_by_id[0]), ["100: JUMP, Offset To Jump To: 102"]) - self.assertEqual(self.__equiv(chunks_by_id[1]), ["101: JUMP, Offset To Jump To: 104"]) - self.assertEqual(self.__equiv(chunks_by_id[2]), ["102: JUMP, Offset To Jump To: 101"]) - self.assertEqual(self.__equiv(chunks_by_id[3]), ["103: JUMP, Offset To Jump To: 106"]) - self.assertEqual(self.__equiv(chunks_by_id[4]), ["104: JUMP, Offset To Jump To: 103"]) - self.assertEqual(self.__equiv(chunks_by_id[5]), ["105: JUMP, Offset To Jump To: 107"]) - self.assertEqual(self.__equiv(chunks_by_id[6]), ["106: JUMP, Offset To Jump To: 105"]) + self.assertEqual( + self.__equiv(chunks_by_id[0]), ["100: JUMP, Offset To Jump To: 102"] + ) + self.assertEqual( + self.__equiv(chunks_by_id[1]), ["101: JUMP, Offset To Jump To: 104"] + ) + self.assertEqual( + self.__equiv(chunks_by_id[2]), ["102: JUMP, Offset To Jump To: 101"] + ) + self.assertEqual( + self.__equiv(chunks_by_id[3]), ["103: JUMP, Offset To Jump To: 106"] + ) + self.assertEqual( + self.__equiv(chunks_by_id[4]), ["104: JUMP, Offset To Jump To: 103"] + ) + self.assertEqual( + self.__equiv(chunks_by_id[5]), ["105: JUMP, Offset To Jump To: 107"] + ) + self.assertEqual( + self.__equiv(chunks_by_id[6]), ["106: JUMP, Offset To Jump To: 105"] + ) self.assertEqual(self.__equiv(chunks_by_id[7]), ["107: STOP"]) self.assertEqual(self.__equiv(chunks_by_id[8]), []) def test_dead_code_elimination_jump(self) -> None: # Jump case - bytecode = self.__make_bytecode([ - AP2Action(100, AP2Action.STOP), - JumpAction(101, 103), - AP2Action(102, AP2Action.PLAY), - AP2Action(103, AP2Action.STOP), - ]) + bytecode = self.__make_bytecode( + [ + AP2Action(100, AP2Action.STOP), + JumpAction(101, 103), + AP2Action(102, AP2Action.PLAY), + AP2Action(103, AP2Action.STOP), + ] + ) chunks_by_id, offset_map = self.__call_graph(bytecode) self.assertEqual(offset_map, {100: 0, 103: 1, 104: 2}) self.assertItemsEqual(chunks_by_id.keys(), {0, 1, 2}) @@ -243,17 +275,22 @@ class TestAFPControlGraph(ExtendedTestCase): self.assertItemsEqual(chunks_by_id[2].next_chunks, []) # Also verify the code - self.assertEqual(self.__equiv(chunks_by_id[0]), ["100: STOP", "101: JUMP, Offset To Jump To: 103"]) + self.assertEqual( + self.__equiv(chunks_by_id[0]), + ["100: STOP", "101: JUMP, Offset To Jump To: 103"], + ) self.assertEqual(self.__equiv(chunks_by_id[1]), ["103: STOP"]) self.assertEqual(self.__equiv(chunks_by_id[2]), []) def test_dead_code_elimination_return(self) -> None: # Return case - bytecode = self.__make_bytecode([ - AP2Action(100, AP2Action.STOP), - AP2Action(101, AP2Action.RETURN), - AP2Action(102, AP2Action.STOP), - ]) + bytecode = self.__make_bytecode( + [ + AP2Action(100, AP2Action.STOP), + AP2Action(101, AP2Action.RETURN), + AP2Action(102, AP2Action.STOP), + ] + ) chunks_by_id, offset_map = self.__call_graph(bytecode) self.assertEqual(offset_map, {100: 0, 103: 1}) self.assertItemsEqual(chunks_by_id.keys(), {0, 1}) @@ -268,11 +305,13 @@ class TestAFPControlGraph(ExtendedTestCase): def test_dead_code_elimination_end(self) -> None: # Return case - bytecode = self.__make_bytecode([ - AP2Action(100, AP2Action.STOP), - AP2Action(101, AP2Action.END), - AP2Action(102, AP2Action.END), - ]) + bytecode = self.__make_bytecode( + [ + AP2Action(100, AP2Action.STOP), + AP2Action(101, AP2Action.END), + AP2Action(102, AP2Action.END), + ] + ) chunks_by_id, offset_map = self.__call_graph(bytecode) self.assertEqual(offset_map, {100: 0, 103: 1}) self.assertItemsEqual(chunks_by_id.keys(), {0, 1}) @@ -287,11 +326,13 @@ class TestAFPControlGraph(ExtendedTestCase): def test_dead_code_elimination_throw(self) -> None: # Throw case - bytecode = self.__make_bytecode([ - PushAction(100, ["exception"]), - AP2Action(101, AP2Action.THROW), - AP2Action(102, AP2Action.STOP), - ]) + bytecode = self.__make_bytecode( + [ + PushAction(100, ["exception"]), + AP2Action(101, AP2Action.THROW), + AP2Action(102, AP2Action.STOP), + ] + ) chunks_by_id, offset_map = self.__call_graph(bytecode) self.assertEqual(offset_map, {100: 0, 103: 1}) self.assertItemsEqual(chunks_by_id.keys(), {0, 1}) @@ -301,20 +342,25 @@ class TestAFPControlGraph(ExtendedTestCase): self.assertItemsEqual(chunks_by_id[1].next_chunks, []) # Also verify the code - self.assertEqual(self.__equiv(chunks_by_id[0]), [f"100: PUSH{os.linesep} 'exception'{os.linesep}END_PUSH", "101: THROW"]) + self.assertEqual( + self.__equiv(chunks_by_id[0]), + [f"100: PUSH{os.linesep} 'exception'{os.linesep}END_PUSH", "101: THROW"], + ) self.assertEqual(self.__equiv(chunks_by_id[1]), []) def test_if_handling_basic(self) -> None: # If by itself case. - bytecode = self.__make_bytecode([ - # Beginning of the if statement. - PushAction(100, [True]), - IfAction(101, IfAction.COMP_IS_FALSE, 103), - # False case (fall through from if). - AP2Action(102, AP2Action.PLAY), - # Line after the if statement. - AP2Action(103, AP2Action.END), - ]) + bytecode = self.__make_bytecode( + [ + # Beginning of the if statement. + PushAction(100, [True]), + IfAction(101, IfAction.COMP_IS_FALSE, 103), + # False case (fall through from if). + AP2Action(102, AP2Action.PLAY), + # Line after the if statement. + AP2Action(103, AP2Action.END), + ] + ) chunks_by_id, offset_map = self.__call_graph(bytecode) self.assertEqual(offset_map, {100: 0, 102: 1, 103: 2, 104: 3}) self.assertItemsEqual(chunks_by_id.keys(), {0, 1, 2, 3}) @@ -328,22 +374,30 @@ class TestAFPControlGraph(ExtendedTestCase): self.assertItemsEqual(chunks_by_id[3].next_chunks, []) # Also verify the code - self.assertEqual(self.__equiv(chunks_by_id[0]), [f"100: PUSH{os.linesep} True{os.linesep}END_PUSH", "101: IF, Comparison: IS FALSE, Offset To Jump To If True: 103"]) + self.assertEqual( + self.__equiv(chunks_by_id[0]), + [ + f"100: PUSH{os.linesep} True{os.linesep}END_PUSH", + "101: IF, Comparison: IS FALSE, Offset To Jump To If True: 103", + ], + ) self.assertEqual(self.__equiv(chunks_by_id[1]), ["102: PLAY"]) self.assertEqual(self.__equiv(chunks_by_id[2]), ["103: END"]) self.assertEqual(self.__equiv(chunks_by_id[3]), []) def test_if_handling_basic_jump_to_end(self) -> None: # If by itself case. - bytecode = self.__make_bytecode([ - # Beginning of the if statement. - PushAction(100, [True]), - IfAction(101, IfAction.COMP_IS_FALSE, 103), - # False case (fall through from if). - AP2Action(102, AP2Action.PLAY), - # Some code will jump to the end offset as a way of - # "returning" early from a function. - ]) + bytecode = self.__make_bytecode( + [ + # Beginning of the if statement. + PushAction(100, [True]), + IfAction(101, IfAction.COMP_IS_FALSE, 103), + # False case (fall through from if). + AP2Action(102, AP2Action.PLAY), + # Some code will jump to the end offset as a way of + # "returning" early from a function. + ] + ) chunks_by_id, offset_map = self.__call_graph(bytecode) self.assertEqual(offset_map, {100: 0, 102: 1, 103: 2}) self.assertItemsEqual(chunks_by_id.keys(), {0, 1, 2}) @@ -355,24 +409,32 @@ class TestAFPControlGraph(ExtendedTestCase): self.assertItemsEqual(chunks_by_id[2].next_chunks, []) # Also verify the code - self.assertEqual(self.__equiv(chunks_by_id[0]), [f"100: PUSH{os.linesep} True{os.linesep}END_PUSH", "101: IF, Comparison: IS FALSE, Offset To Jump To If True: 103"]) + self.assertEqual( + self.__equiv(chunks_by_id[0]), + [ + f"100: PUSH{os.linesep} True{os.linesep}END_PUSH", + "101: IF, Comparison: IS FALSE, Offset To Jump To If True: 103", + ], + ) self.assertEqual(self.__equiv(chunks_by_id[1]), ["102: PLAY"]) self.assertEqual(self.__equiv(chunks_by_id[2]), []) def test_if_handling_diamond(self) -> None: # If true-false diamond case. - bytecode = self.__make_bytecode([ - # Beginning of the if statement. - PushAction(100, [True]), - IfAction(101, IfAction.COMP_IS_TRUE, 104), - # False case (fall through from if). - AP2Action(102, AP2Action.STOP), - JumpAction(103, 105), - # True case. - AP2Action(104, AP2Action.PLAY), - # Line after the if statement. - AP2Action(105, AP2Action.END), - ]) + bytecode = self.__make_bytecode( + [ + # Beginning of the if statement. + PushAction(100, [True]), + IfAction(101, IfAction.COMP_IS_TRUE, 104), + # False case (fall through from if). + AP2Action(102, AP2Action.STOP), + JumpAction(103, 105), + # True case. + AP2Action(104, AP2Action.PLAY), + # Line after the if statement. + AP2Action(105, AP2Action.END), + ] + ) chunks_by_id, offset_map = self.__call_graph(bytecode) self.assertEqual(offset_map, {100: 0, 102: 1, 104: 2, 105: 3, 106: 4}) self.assertItemsEqual(chunks_by_id.keys(), {0, 1, 2, 3, 4}) @@ -388,24 +450,35 @@ class TestAFPControlGraph(ExtendedTestCase): self.assertItemsEqual(chunks_by_id[4].next_chunks, []) # Also verify the code - self.assertEqual(self.__equiv(chunks_by_id[0]), [f"100: PUSH{os.linesep} True{os.linesep}END_PUSH", "101: IF, Comparison: IS TRUE, Offset To Jump To If True: 104"]) - self.assertEqual(self.__equiv(chunks_by_id[1]), ["102: STOP", "103: JUMP, Offset To Jump To: 105"]) + self.assertEqual( + self.__equiv(chunks_by_id[0]), + [ + f"100: PUSH{os.linesep} True{os.linesep}END_PUSH", + "101: IF, Comparison: IS TRUE, Offset To Jump To If True: 104", + ], + ) + self.assertEqual( + self.__equiv(chunks_by_id[1]), + ["102: STOP", "103: JUMP, Offset To Jump To: 105"], + ) self.assertEqual(self.__equiv(chunks_by_id[2]), ["104: PLAY"]) self.assertEqual(self.__equiv(chunks_by_id[3]), ["105: END"]) self.assertEqual(self.__equiv(chunks_by_id[4]), []) def test_if_handling_diamond_jump_to_end(self) -> None: # If true-false diamond case. - bytecode = self.__make_bytecode([ - # Beginning of the if statement. - PushAction(100, [True]), - IfAction(101, IfAction.COMP_IS_TRUE, 104), - # False case (fall through from if). - AP2Action(102, AP2Action.STOP), - JumpAction(103, 105), - # True case. - AP2Action(104, AP2Action.PLAY), - ]) + bytecode = self.__make_bytecode( + [ + # Beginning of the if statement. + PushAction(100, [True]), + IfAction(101, IfAction.COMP_IS_TRUE, 104), + # False case (fall through from if). + AP2Action(102, AP2Action.STOP), + JumpAction(103, 105), + # True case. + AP2Action(104, AP2Action.PLAY), + ] + ) chunks_by_id, offset_map = self.__call_graph(bytecode) self.assertEqual(offset_map, {100: 0, 102: 1, 104: 2, 105: 3}) self.assertItemsEqual(chunks_by_id.keys(), {0, 1, 2, 3}) @@ -419,24 +492,35 @@ class TestAFPControlGraph(ExtendedTestCase): self.assertItemsEqual(chunks_by_id[3].next_chunks, []) # Also verify the code - self.assertEqual(self.__equiv(chunks_by_id[0]), [f"100: PUSH{os.linesep} True{os.linesep}END_PUSH", "101: IF, Comparison: IS TRUE, Offset To Jump To If True: 104"]) - self.assertEqual(self.__equiv(chunks_by_id[1]), ["102: STOP", "103: JUMP, Offset To Jump To: 105"]) + self.assertEqual( + self.__equiv(chunks_by_id[0]), + [ + f"100: PUSH{os.linesep} True{os.linesep}END_PUSH", + "101: IF, Comparison: IS TRUE, Offset To Jump To If True: 104", + ], + ) + self.assertEqual( + self.__equiv(chunks_by_id[1]), + ["102: STOP", "103: JUMP, Offset To Jump To: 105"], + ) self.assertEqual(self.__equiv(chunks_by_id[2]), ["104: PLAY"]) self.assertEqual(self.__equiv(chunks_by_id[3]), []) def test_if_handling_diamond_return_to_end(self) -> None: # If true-false diamond case but the cases never converge. - bytecode = self.__make_bytecode([ - # Beginning of the if statement. - PushAction(100, [True]), - IfAction(101, IfAction.COMP_IS_TRUE, 104), - # False case (fall through from if). - PushAction(102, ['b']), - AP2Action(103, AP2Action.RETURN), - # True case. - PushAction(104, ['a']), - AP2Action(105, AP2Action.RETURN), - ]) + bytecode = self.__make_bytecode( + [ + # Beginning of the if statement. + PushAction(100, [True]), + IfAction(101, IfAction.COMP_IS_TRUE, 104), + # False case (fall through from if). + PushAction(102, ["b"]), + AP2Action(103, AP2Action.RETURN), + # True case. + PushAction(104, ["a"]), + AP2Action(105, AP2Action.RETURN), + ] + ) chunks_by_id, offset_map = self.__call_graph(bytecode) self.assertEqual(offset_map, {100: 0, 102: 1, 104: 2, 106: 3}) self.assertItemsEqual(chunks_by_id.keys(), {0, 1, 2, 3}) @@ -450,43 +534,56 @@ class TestAFPControlGraph(ExtendedTestCase): self.assertItemsEqual(chunks_by_id[3].next_chunks, []) # Also verify the code - self.assertEqual(self.__equiv(chunks_by_id[0]), [f"100: PUSH{os.linesep} True{os.linesep}END_PUSH", "101: IF, Comparison: IS TRUE, Offset To Jump To If True: 104"]) - self.assertEqual(self.__equiv(chunks_by_id[1]), [f"102: PUSH{os.linesep} 'b'{os.linesep}END_PUSH", "103: RETURN"]) - self.assertEqual(self.__equiv(chunks_by_id[2]), [f"104: PUSH{os.linesep} 'a'{os.linesep}END_PUSH", "105: RETURN"]) + self.assertEqual( + self.__equiv(chunks_by_id[0]), + [ + f"100: PUSH{os.linesep} True{os.linesep}END_PUSH", + "101: IF, Comparison: IS TRUE, Offset To Jump To If True: 104", + ], + ) + self.assertEqual( + self.__equiv(chunks_by_id[1]), + [f"102: PUSH{os.linesep} 'b'{os.linesep}END_PUSH", "103: RETURN"], + ) + self.assertEqual( + self.__equiv(chunks_by_id[2]), + [f"104: PUSH{os.linesep} 'a'{os.linesep}END_PUSH", "105: RETURN"], + ) self.assertEqual(self.__equiv(chunks_by_id[3]), []) def test_if_handling_switch(self) -> None: # Series of ifs (basically a switch statement). - bytecode = self.__make_bytecode([ - # Beginning of the first if statement. - PushAction(100, [Register(0), 1]), - IfAction(101, IfAction.COMP_NOT_EQUALS, 104), - # False case (fall through from if). - PushAction(102, ['a']), - JumpAction(103, 113), - - # Beginning of the second if statement. - PushAction(104, [Register(0), 2]), - IfAction(105, IfAction.COMP_NOT_EQUALS, 108), - # False case (fall through from if). - PushAction(106, ['b']), - JumpAction(107, 113), - - # Beginning of the third if statement. - PushAction(108, [Register(0), 3]), - IfAction(109, IfAction.COMP_NOT_EQUALS, 112), - # False case (fall through from if). - PushAction(110, ['c']), - JumpAction(111, 113), - - # Beginning of default case. - PushAction(112, ['d']), - - # Line after the switch statement. - AP2Action(113, AP2Action.END), - ]) + bytecode = self.__make_bytecode( + [ + # Beginning of the first if statement. + PushAction(100, [Register(0), 1]), + IfAction(101, IfAction.COMP_NOT_EQUALS, 104), + # False case (fall through from if). + PushAction(102, ["a"]), + JumpAction(103, 113), + # Beginning of the second if statement. + PushAction(104, [Register(0), 2]), + IfAction(105, IfAction.COMP_NOT_EQUALS, 108), + # False case (fall through from if). + PushAction(106, ["b"]), + JumpAction(107, 113), + # Beginning of the third if statement. + PushAction(108, [Register(0), 3]), + IfAction(109, IfAction.COMP_NOT_EQUALS, 112), + # False case (fall through from if). + PushAction(110, ["c"]), + JumpAction(111, 113), + # Beginning of default case. + PushAction(112, ["d"]), + # Line after the switch statement. + AP2Action(113, AP2Action.END), + ] + ) chunks_by_id, offset_map = self.__call_graph(bytecode) - self.assertEqual(offset_map, {100: 0, 102: 1, 104: 2, 106: 3, 108: 4, 110: 5, 112: 6, 113: 7, 114: 8}) + self.assertEqual( + offset_map, + {100: 0, 102: 1, 104: 2, 106: 3, 108: 4, 110: 5, 112: 6, 113: 7, 114: 8}, + ) self.assertItemsEqual(chunks_by_id.keys(), {0, 1, 2, 3, 4, 5, 6, 7, 8}) self.assertItemsEqual(chunks_by_id[0].previous_chunks, []) self.assertItemsEqual(chunks_by_id[0].next_chunks, [1, 2]) @@ -508,29 +605,70 @@ class TestAFPControlGraph(ExtendedTestCase): self.assertItemsEqual(chunks_by_id[8].next_chunks, []) # Also verify the code - self.assertEqual(self.__equiv(chunks_by_id[0]), [f"100: PUSH{os.linesep} Register(0){os.linesep} 1{os.linesep}END_PUSH", "101: IF, Comparison: !=, Offset To Jump To If True: 104"]) - self.assertEqual(self.__equiv(chunks_by_id[1]), [f"102: PUSH{os.linesep} 'a'{os.linesep}END_PUSH", "103: JUMP, Offset To Jump To: 113"]) - self.assertEqual(self.__equiv(chunks_by_id[2]), [f"104: PUSH{os.linesep} Register(0){os.linesep} 2{os.linesep}END_PUSH", "105: IF, Comparison: !=, Offset To Jump To If True: 108"]) - self.assertEqual(self.__equiv(chunks_by_id[3]), [f"106: PUSH{os.linesep} 'b'{os.linesep}END_PUSH", "107: JUMP, Offset To Jump To: 113"]) - self.assertEqual(self.__equiv(chunks_by_id[4]), [f"108: PUSH{os.linesep} Register(0){os.linesep} 3{os.linesep}END_PUSH", "109: IF, Comparison: !=, Offset To Jump To If True: 112"]) - self.assertEqual(self.__equiv(chunks_by_id[5]), [f"110: PUSH{os.linesep} 'c'{os.linesep}END_PUSH", "111: JUMP, Offset To Jump To: 113"]) - self.assertEqual(self.__equiv(chunks_by_id[6]), [f"112: PUSH{os.linesep} 'd'{os.linesep}END_PUSH"]) + self.assertEqual( + self.__equiv(chunks_by_id[0]), + [ + f"100: PUSH{os.linesep} Register(0){os.linesep} 1{os.linesep}END_PUSH", + "101: IF, Comparison: !=, Offset To Jump To If True: 104", + ], + ) + self.assertEqual( + self.__equiv(chunks_by_id[1]), + [ + f"102: PUSH{os.linesep} 'a'{os.linesep}END_PUSH", + "103: JUMP, Offset To Jump To: 113", + ], + ) + self.assertEqual( + self.__equiv(chunks_by_id[2]), + [ + f"104: PUSH{os.linesep} Register(0){os.linesep} 2{os.linesep}END_PUSH", + "105: IF, Comparison: !=, Offset To Jump To If True: 108", + ], + ) + self.assertEqual( + self.__equiv(chunks_by_id[3]), + [ + f"106: PUSH{os.linesep} 'b'{os.linesep}END_PUSH", + "107: JUMP, Offset To Jump To: 113", + ], + ) + self.assertEqual( + self.__equiv(chunks_by_id[4]), + [ + f"108: PUSH{os.linesep} Register(0){os.linesep} 3{os.linesep}END_PUSH", + "109: IF, Comparison: !=, Offset To Jump To If True: 112", + ], + ) + self.assertEqual( + self.__equiv(chunks_by_id[5]), + [ + f"110: PUSH{os.linesep} 'c'{os.linesep}END_PUSH", + "111: JUMP, Offset To Jump To: 113", + ], + ) + self.assertEqual( + self.__equiv(chunks_by_id[6]), + [f"112: PUSH{os.linesep} 'd'{os.linesep}END_PUSH"], + ) self.assertEqual(self.__equiv(chunks_by_id[7]), ["113: END"]) self.assertEqual(self.__equiv(chunks_by_id[8]), []) def test_if_handling_diamond_end_both_sides(self) -> None: # If true-false diamond case but the cases never converge. - bytecode = self.__make_bytecode([ - # Beginning of the if statement. - PushAction(100, [True]), - IfAction(101, IfAction.COMP_IS_TRUE, 104), - # False case (fall through from if). - PushAction(102, ['b']), - AP2Action(103, AP2Action.END), - # True case. - PushAction(104, ['a']), - AP2Action(105, AP2Action.END), - ]) + bytecode = self.__make_bytecode( + [ + # Beginning of the if statement. + PushAction(100, [True]), + IfAction(101, IfAction.COMP_IS_TRUE, 104), + # False case (fall through from if). + PushAction(102, ["b"]), + AP2Action(103, AP2Action.END), + # True case. + PushAction(104, ["a"]), + AP2Action(105, AP2Action.END), + ] + ) chunks_by_id, offset_map = self.__call_graph(bytecode) self.assertEqual(offset_map, {100: 0, 102: 1, 104: 2, 106: 3}) self.assertItemsEqual(chunks_by_id.keys(), {0, 1, 2, 3}) @@ -544,9 +682,21 @@ class TestAFPControlGraph(ExtendedTestCase): self.assertItemsEqual(chunks_by_id[3].next_chunks, []) # Also verify the code - self.assertEqual(self.__equiv(chunks_by_id[0]), [f"100: PUSH{os.linesep} True{os.linesep}END_PUSH", "101: IF, Comparison: IS TRUE, Offset To Jump To If True: 104"]) - self.assertEqual(self.__equiv(chunks_by_id[1]), [f"102: PUSH{os.linesep} 'b'{os.linesep}END_PUSH", "103: END"]) - self.assertEqual(self.__equiv(chunks_by_id[2]), [f"104: PUSH{os.linesep} 'a'{os.linesep}END_PUSH", "105: END"]) + self.assertEqual( + self.__equiv(chunks_by_id[0]), + [ + f"100: PUSH{os.linesep} True{os.linesep}END_PUSH", + "101: IF, Comparison: IS TRUE, Offset To Jump To If True: 104", + ], + ) + self.assertEqual( + self.__equiv(chunks_by_id[1]), + [f"102: PUSH{os.linesep} 'b'{os.linesep}END_PUSH", "103: END"], + ) + self.assertEqual( + self.__equiv(chunks_by_id[2]), + [f"104: PUSH{os.linesep} 'a'{os.linesep}END_PUSH", "105: END"], + ) self.assertEqual(self.__equiv(chunks_by_id[3]), []) @@ -573,390 +723,498 @@ class TestAFPDecompile(ExtendedTestCase): return [str(x) for x in statements] def test_simple_bytecode(self) -> None: - bytecode = self.__make_bytecode([ - AP2Action(100, AP2Action.STOP), - ]) + bytecode = self.__make_bytecode( + [ + AP2Action(100, AP2Action.STOP), + ] + ) statements = self.__call_decompile(bytecode) - self.assertEqual(self.__equiv(statements), ['builtin_StopPlaying()']) + self.assertEqual(self.__equiv(statements), ["builtin_StopPlaying()"]) def test_jump_handling(self) -> None: - bytecode = self.__make_bytecode([ - JumpAction(100, 102), - JumpAction(101, 104), - JumpAction(102, 101), - JumpAction(103, 106), - JumpAction(104, 103), - JumpAction(105, 107), - JumpAction(106, 105), - AP2Action(107, AP2Action.STOP), - ]) + bytecode = self.__make_bytecode( + [ + JumpAction(100, 102), + JumpAction(101, 104), + JumpAction(102, 101), + JumpAction(103, 106), + JumpAction(104, 103), + JumpAction(105, 107), + JumpAction(106, 105), + AP2Action(107, AP2Action.STOP), + ] + ) statements = self.__call_decompile(bytecode) - self.assertEqual(self.__equiv(statements), ['builtin_StopPlaying()']) + self.assertEqual(self.__equiv(statements), ["builtin_StopPlaying()"]) def test_dead_code_elimination_jump(self) -> None: # Jump case - bytecode = self.__make_bytecode([ - AP2Action(100, AP2Action.STOP), - JumpAction(101, 103), - AP2Action(102, AP2Action.PLAY), - AP2Action(103, AP2Action.STOP), - ]) + bytecode = self.__make_bytecode( + [ + AP2Action(100, AP2Action.STOP), + JumpAction(101, 103), + AP2Action(102, AP2Action.PLAY), + AP2Action(103, AP2Action.STOP), + ] + ) statements = self.__call_decompile(bytecode) - self.assertEqual(self.__equiv(statements), ['builtin_StopPlaying()', 'builtin_StopPlaying()']) + self.assertEqual( + self.__equiv(statements), ["builtin_StopPlaying()", "builtin_StopPlaying()"] + ) def test_dead_code_elimination_return(self) -> None: # Return case - bytecode = self.__make_bytecode([ - PushAction(100, ["strval"]), - AP2Action(101, AP2Action.RETURN), - AP2Action(102, AP2Action.STOP), - ]) + bytecode = self.__make_bytecode( + [ + PushAction(100, ["strval"]), + AP2Action(101, AP2Action.RETURN), + AP2Action(102, AP2Action.STOP), + ] + ) statements = self.__call_decompile(bytecode) self.assertEqual(self.__equiv(statements), ["return 'strval'"]) def test_dead_code_elimination_end(self) -> None: # Return case - bytecode = self.__make_bytecode([ - AP2Action(100, AP2Action.STOP), - AP2Action(101, AP2Action.END), - AP2Action(102, AP2Action.END), - ]) + bytecode = self.__make_bytecode( + [ + AP2Action(100, AP2Action.STOP), + AP2Action(101, AP2Action.END), + AP2Action(102, AP2Action.END), + ] + ) statements = self.__call_decompile(bytecode) - self.assertEqual(self.__equiv(statements), ['builtin_StopPlaying()']) + self.assertEqual(self.__equiv(statements), ["builtin_StopPlaying()"]) def test_dead_code_elimination_throw(self) -> None: # Throw case - bytecode = self.__make_bytecode([ - PushAction(100, ["exception"]), - AP2Action(101, AP2Action.THROW), - AP2Action(102, AP2Action.STOP), - ]) + bytecode = self.__make_bytecode( + [ + PushAction(100, ["exception"]), + AP2Action(101, AP2Action.THROW), + AP2Action(102, AP2Action.STOP), + ] + ) statements = self.__call_decompile(bytecode) self.assertEqual(self.__equiv(statements), ["throw 'exception'"]) def test_if_handling_basic_flow_to_end(self) -> None: # If by itself case. - bytecode = self.__make_bytecode([ - # Beginning of the if statement. - PushAction(100, [True]), - IfAction(101, IfAction.COMP_IS_FALSE, 103), - # False case (fall through from if). - AP2Action(102, AP2Action.PLAY), - # Line after the if statement. - AP2Action(103, AP2Action.END), - ]) + bytecode = self.__make_bytecode( + [ + # Beginning of the if statement. + PushAction(100, [True]), + IfAction(101, IfAction.COMP_IS_FALSE, 103), + # False case (fall through from if). + AP2Action(102, AP2Action.PLAY), + # Line after the if statement. + AP2Action(103, AP2Action.END), + ] + ) statements = self.__call_decompile(bytecode) - self.assertEqual(self.__equiv(statements), [f"if (True) {OPEN_BRACKET}{os.linesep} builtin_StartPlaying(){os.linesep}{CLOSE_BRACKET}"]) + self.assertEqual( + self.__equiv(statements), + [ + f"if (True) {OPEN_BRACKET}{os.linesep} builtin_StartPlaying(){os.linesep}{CLOSE_BRACKET}" + ], + ) def test_if_handling_basic_jump_to_end(self) -> None: # If by itself case. - bytecode = self.__make_bytecode([ - # Beginning of the if statement. - PushAction(100, [True]), - IfAction(101, IfAction.COMP_IS_FALSE, 103), - # False case (fall through from if). - AP2Action(102, AP2Action.PLAY), - # Some code will jump to the end offset as a way of - # "returning" early from a function. - ]) + bytecode = self.__make_bytecode( + [ + # Beginning of the if statement. + PushAction(100, [True]), + IfAction(101, IfAction.COMP_IS_FALSE, 103), + # False case (fall through from if). + AP2Action(102, AP2Action.PLAY), + # Some code will jump to the end offset as a way of + # "returning" early from a function. + ] + ) statements = self.__call_decompile(bytecode) - self.assertEqual(self.__equiv(statements), [f"if (True) {OPEN_BRACKET}{os.linesep} builtin_StartPlaying(){os.linesep}{CLOSE_BRACKET}"]) + self.assertEqual( + self.__equiv(statements), + [ + f"if (True) {OPEN_BRACKET}{os.linesep} builtin_StartPlaying(){os.linesep}{CLOSE_BRACKET}" + ], + ) def test_if_handling_diamond(self) -> None: # If true-false diamond case. - bytecode = self.__make_bytecode([ - # Beginning of the if statement. - PushAction(100, [True]), - IfAction(101, IfAction.COMP_IS_TRUE, 104), - # False case (fall through from if). - AP2Action(102, AP2Action.STOP), - JumpAction(103, 105), - # True case. - AP2Action(104, AP2Action.PLAY), - # Line after the if statement. - AP2Action(105, AP2Action.END), - ]) + bytecode = self.__make_bytecode( + [ + # Beginning of the if statement. + PushAction(100, [True]), + IfAction(101, IfAction.COMP_IS_TRUE, 104), + # False case (fall through from if). + AP2Action(102, AP2Action.STOP), + JumpAction(103, 105), + # True case. + AP2Action(104, AP2Action.PLAY), + # Line after the if statement. + AP2Action(105, AP2Action.END), + ] + ) statements = self.__call_decompile(bytecode) - self.assertEqual(self.__equiv(statements), [ - f"if (True) {OPEN_BRACKET}{os.linesep} builtin_StartPlaying(){os.linesep}{CLOSE_BRACKET} else {OPEN_BRACKET}{os.linesep} builtin_StopPlaying(){os.linesep}{CLOSE_BRACKET}" - ]) + self.assertEqual( + self.__equiv(statements), + [ + f"if (True) {OPEN_BRACKET}{os.linesep} builtin_StartPlaying(){os.linesep}{CLOSE_BRACKET} else {OPEN_BRACKET}{os.linesep} builtin_StopPlaying(){os.linesep}{CLOSE_BRACKET}" + ], + ) def test_if_handling_diamond_jump_to_end(self) -> None: # If true-false diamond case. - bytecode = self.__make_bytecode([ - # Beginning of the if statement. - PushAction(100, [True]), - IfAction(101, IfAction.COMP_IS_TRUE, 104), - # False case (fall through from if). - AP2Action(102, AP2Action.STOP), - JumpAction(103, 105), - # True case. - AP2Action(104, AP2Action.PLAY), - ]) + bytecode = self.__make_bytecode( + [ + # Beginning of the if statement. + PushAction(100, [True]), + IfAction(101, IfAction.COMP_IS_TRUE, 104), + # False case (fall through from if). + AP2Action(102, AP2Action.STOP), + JumpAction(103, 105), + # True case. + AP2Action(104, AP2Action.PLAY), + ] + ) statements = self.__call_decompile(bytecode) - self.assertEqual(self.__equiv(statements), [ - f"if (True) {OPEN_BRACKET}{os.linesep} builtin_StartPlaying(){os.linesep}{CLOSE_BRACKET} else {OPEN_BRACKET}{os.linesep} builtin_StopPlaying(){os.linesep}{CLOSE_BRACKET}" - ]) + self.assertEqual( + self.__equiv(statements), + [ + f"if (True) {OPEN_BRACKET}{os.linesep} builtin_StartPlaying(){os.linesep}{CLOSE_BRACKET} else {OPEN_BRACKET}{os.linesep} builtin_StopPlaying(){os.linesep}{CLOSE_BRACKET}" + ], + ) def test_if_handling_diamond_return_to_end(self) -> None: # If true-false diamond case but the cases never converge. - bytecode = self.__make_bytecode([ - # Beginning of the if statement. - PushAction(100, [True]), - IfAction(101, IfAction.COMP_IS_TRUE, 104), - # False case (fall through from if). - PushAction(102, ['b']), - AP2Action(103, AP2Action.RETURN), - # True case. - PushAction(104, ['a']), - AP2Action(105, AP2Action.RETURN), - ]) + bytecode = self.__make_bytecode( + [ + # Beginning of the if statement. + PushAction(100, [True]), + IfAction(101, IfAction.COMP_IS_TRUE, 104), + # False case (fall through from if). + PushAction(102, ["b"]), + AP2Action(103, AP2Action.RETURN), + # True case. + PushAction(104, ["a"]), + AP2Action(105, AP2Action.RETURN), + ] + ) statements = self.__call_decompile(bytecode) - self.assertEqual(self.__equiv(statements), [ - f"if (True) {OPEN_BRACKET}{os.linesep} return 'a'{os.linesep}{CLOSE_BRACKET} else {OPEN_BRACKET}{os.linesep} return 'b'{os.linesep}{CLOSE_BRACKET}" - ]) + self.assertEqual( + self.__equiv(statements), + [ + f"if (True) {OPEN_BRACKET}{os.linesep} return 'a'{os.linesep}{CLOSE_BRACKET} else {OPEN_BRACKET}{os.linesep} return 'b'{os.linesep}{CLOSE_BRACKET}" + ], + ) def test_if_handling_switch(self) -> None: # Series of ifs (basically a switch statement). - bytecode = self.__make_bytecode([ - # Beginning of the first if statement. - PushAction(100, [Register(0), 1]), - IfAction(101, IfAction.COMP_NOT_EQUALS, 104), - # False case (fall through from if). - PushAction(102, ['a']), - JumpAction(103, 113), - - # Beginning of the second if statement. - PushAction(104, [Register(0), 2]), - IfAction(105, IfAction.COMP_NOT_EQUALS, 108), - # False case (fall through from if). - PushAction(106, ['b']), - JumpAction(107, 113), - - # Beginning of the third if statement. - PushAction(108, [Register(0), 3]), - IfAction(109, IfAction.COMP_NOT_EQUALS, 112), - # False case (fall through from if). - PushAction(110, ['c']), - JumpAction(111, 113), - - # Beginning of default case. - PushAction(112, ['d']), - - # Line after the switch statement. - AP2Action(113, AP2Action.RETURN), - ]) + bytecode = self.__make_bytecode( + [ + # Beginning of the first if statement. + PushAction(100, [Register(0), 1]), + IfAction(101, IfAction.COMP_NOT_EQUALS, 104), + # False case (fall through from if). + PushAction(102, ["a"]), + JumpAction(103, 113), + # Beginning of the second if statement. + PushAction(104, [Register(0), 2]), + IfAction(105, IfAction.COMP_NOT_EQUALS, 108), + # False case (fall through from if). + PushAction(106, ["b"]), + JumpAction(107, 113), + # Beginning of the third if statement. + PushAction(108, [Register(0), 3]), + IfAction(109, IfAction.COMP_NOT_EQUALS, 112), + # False case (fall through from if). + PushAction(110, ["c"]), + JumpAction(111, 113), + # Beginning of default case. + PushAction(112, ["d"]), + # Line after the switch statement. + AP2Action(113, AP2Action.RETURN), + ] + ) statements = self.__call_decompile(bytecode) - self.assertEqual(self.__equiv(statements), [ - f"switch (registers[0]) {OPEN_BRACKET}{os.linesep}" - f" case 1:{os.linesep}" - f" tempvar_0 = 'a'{os.linesep}" - f" break{os.linesep}" - f" case 2:{os.linesep}" - f" tempvar_0 = 'b'{os.linesep}" - f" break{os.linesep}" - f" case 3:{os.linesep}" - f" tempvar_0 = 'c'{os.linesep}" - f" break{os.linesep}" - f" default:{os.linesep}" - f" tempvar_0 = 'd'{os.linesep}" - f" break{os.linesep}" - "}", - "return tempvar_0" - ]) + self.assertEqual( + self.__equiv(statements), + [ + f"switch (registers[0]) {OPEN_BRACKET}{os.linesep}" + f" case 1:{os.linesep}" + f" tempvar_0 = 'a'{os.linesep}" + f" break{os.linesep}" + f" case 2:{os.linesep}" + f" tempvar_0 = 'b'{os.linesep}" + f" break{os.linesep}" + f" case 3:{os.linesep}" + f" tempvar_0 = 'c'{os.linesep}" + f" break{os.linesep}" + f" default:{os.linesep}" + f" tempvar_0 = 'd'{os.linesep}" + f" break{os.linesep}" + "}", + "return tempvar_0", + ], + ) def test_if_handling_diamond_end_both_sides(self) -> None: # If true-false diamond case but the cases never converge. - bytecode = self.__make_bytecode([ - # Beginning of the if statement. - PushAction(100, [True]), - IfAction(101, IfAction.COMP_IS_TRUE, 104), - # False case (fall through from if). - AP2Action(102, AP2Action.STOP), - AP2Action(103, AP2Action.END), - # True case. - AP2Action(104, AP2Action.PLAY), - AP2Action(105, AP2Action.END), - ]) + bytecode = self.__make_bytecode( + [ + # Beginning of the if statement. + PushAction(100, [True]), + IfAction(101, IfAction.COMP_IS_TRUE, 104), + # False case (fall through from if). + AP2Action(102, AP2Action.STOP), + AP2Action(103, AP2Action.END), + # True case. + AP2Action(104, AP2Action.PLAY), + AP2Action(105, AP2Action.END), + ] + ) statements = self.__call_decompile(bytecode) - self.assertEqual(self.__equiv(statements), [ - f"if (True) {OPEN_BRACKET}{os.linesep} builtin_StartPlaying(){os.linesep}{CLOSE_BRACKET} else {OPEN_BRACKET}{os.linesep} builtin_StopPlaying(){os.linesep}{CLOSE_BRACKET}" - ]) + self.assertEqual( + self.__equiv(statements), + [ + f"if (True) {OPEN_BRACKET}{os.linesep} builtin_StartPlaying(){os.linesep}{CLOSE_BRACKET} else {OPEN_BRACKET}{os.linesep} builtin_StopPlaying(){os.linesep}{CLOSE_BRACKET}" + ], + ) def test_if_handling_or(self) -> None: # Two ifs that together make an or (if register == 1 or register == 3) - bytecode = self.__make_bytecode([ - # Beginning of the first if statement. - PushAction(100, [Register(0), 1]), - IfAction(101, IfAction.COMP_EQUALS, 104), - # False case (circuit not broken, register is not equal to 1) - PushAction(102, [Register(0), 2]), - IfAction(103, IfAction.COMP_NOT_EQUALS, 106), - # This is the true case - AP2Action(104, AP2Action.PLAY), - JumpAction(105, 107), - # This is the false case - AP2Action(106, AP2Action.STOP), - # This is the fall-through after the if. - PushAction(107, ['strval']), - AP2Action(108, AP2Action.RETURN), - ]) + bytecode = self.__make_bytecode( + [ + # Beginning of the first if statement. + PushAction(100, [Register(0), 1]), + IfAction(101, IfAction.COMP_EQUALS, 104), + # False case (circuit not broken, register is not equal to 1) + PushAction(102, [Register(0), 2]), + IfAction(103, IfAction.COMP_NOT_EQUALS, 106), + # This is the true case + AP2Action(104, AP2Action.PLAY), + JumpAction(105, 107), + # This is the false case + AP2Action(106, AP2Action.STOP), + # This is the fall-through after the if. + PushAction(107, ["strval"]), + AP2Action(108, AP2Action.RETURN), + ] + ) statements = self.__call_decompile(bytecode) - self.assertEqual(self.__equiv(statements), [ - f"if (registers[0] == 1 || registers[0] == 2) {OPEN_BRACKET}{os.linesep}" - f" builtin_StartPlaying(){os.linesep}" - f"{CLOSE_BRACKET} else {OPEN_BRACKET}{os.linesep}" - f" builtin_StopPlaying(){os.linesep}" - f"{CLOSE_BRACKET}", - "return 'strval'" - ]) + self.assertEqual( + self.__equiv(statements), + [ + f"if (registers[0] == 1 || registers[0] == 2) {OPEN_BRACKET}{os.linesep}" + f" builtin_StartPlaying(){os.linesep}" + f"{CLOSE_BRACKET} else {OPEN_BRACKET}{os.linesep}" + f" builtin_StopPlaying(){os.linesep}" + f"{CLOSE_BRACKET}", + "return 'strval'", + ], + ) def test_basic_while(self) -> None: # A basic while statement. - bytecode = self.__make_bytecode([ - # Define exit condition variable. - PushAction(100, ["finished", False]), - AP2Action(101, AP2Action.DEFINE_LOCAL), - # Check exit condition. - PushAction(102, ["finished"]), - AP2Action(103, AP2Action.GET_VARIABLE), - IfAction(104, IfAction.COMP_IS_TRUE, 107), - # Loop code. - AP2Action(105, AP2Action.NEXT_FRAME), - # Loop finished jump back to beginning. - JumpAction(106, 102), - # End of loop. - AP2Action(107, AP2Action.END), - ]) + bytecode = self.__make_bytecode( + [ + # Define exit condition variable. + PushAction(100, ["finished", False]), + AP2Action(101, AP2Action.DEFINE_LOCAL), + # Check exit condition. + PushAction(102, ["finished"]), + AP2Action(103, AP2Action.GET_VARIABLE), + IfAction(104, IfAction.COMP_IS_TRUE, 107), + # Loop code. + AP2Action(105, AP2Action.NEXT_FRAME), + # Loop finished jump back to beginning. + JumpAction(106, 102), + # End of loop. + AP2Action(107, AP2Action.END), + ] + ) statements = self.__call_decompile(bytecode) - self.assertEqual(self.__equiv(statements), [ - "local finished = False", - f"while (not finished) {OPEN_BRACKET}{os.linesep}" - f" builtin_GotoNextFrame(){os.linesep}" - "}" - ]) + self.assertEqual( + self.__equiv(statements), + [ + "local finished = False", + f"while (not finished) {OPEN_BRACKET}{os.linesep}" + f" builtin_GotoNextFrame(){os.linesep}" + "}", + ], + ) def test_advanced_while(self) -> None: # A basic while statement. - bytecode = self.__make_bytecode([ - # Define exit condition variable. - PushAction(100, ["finished", False]), - AP2Action(101, AP2Action.DEFINE_LOCAL), - # Check exit condition. - PushAction(102, ["finished"]), - AP2Action(103, AP2Action.GET_VARIABLE), - IfAction(104, IfAction.COMP_IS_TRUE, 112), - # Loop code with a continue statement. - PushAction(105, ["some_condition"]), - AP2Action(106, AP2Action.GET_VARIABLE), - IfAction(107, IfAction.COMP_IS_FALSE, 110), - AP2Action(108, AP2Action.NEXT_FRAME), - # Continue statement. - JumpAction(109, 102), - # Exit early. - AP2Action(110, AP2Action.STOP), - # Break statement. - JumpAction(111, 112), - # End of loop. - AP2Action(112, AP2Action.END), - ]) + bytecode = self.__make_bytecode( + [ + # Define exit condition variable. + PushAction(100, ["finished", False]), + AP2Action(101, AP2Action.DEFINE_LOCAL), + # Check exit condition. + PushAction(102, ["finished"]), + AP2Action(103, AP2Action.GET_VARIABLE), + IfAction(104, IfAction.COMP_IS_TRUE, 112), + # Loop code with a continue statement. + PushAction(105, ["some_condition"]), + AP2Action(106, AP2Action.GET_VARIABLE), + IfAction(107, IfAction.COMP_IS_FALSE, 110), + AP2Action(108, AP2Action.NEXT_FRAME), + # Continue statement. + JumpAction(109, 102), + # Exit early. + AP2Action(110, AP2Action.STOP), + # Break statement. + JumpAction(111, 112), + # End of loop. + AP2Action(112, AP2Action.END), + ] + ) statements = self.__call_decompile(bytecode) - self.assertEqual(self.__equiv(statements), [ - "local finished = False", - f"while (not finished) {OPEN_BRACKET}{os.linesep}" - f" if (not some_condition) {OPEN_BRACKET}{os.linesep}" - f" builtin_StopPlaying(){os.linesep}" - f" break{os.linesep}" - f" {CLOSE_BRACKET}{os.linesep}" - f" builtin_GotoNextFrame(){os.linesep}" - "}" - ]) + self.assertEqual( + self.__equiv(statements), + [ + "local finished = False", + f"while (not finished) {OPEN_BRACKET}{os.linesep}" + f" if (not some_condition) {OPEN_BRACKET}{os.linesep}" + f" builtin_StopPlaying(){os.linesep}" + f" break{os.linesep}" + f" {CLOSE_BRACKET}{os.linesep}" + f" builtin_GotoNextFrame(){os.linesep}" + "}", + ], + ) def test_basic_for(self) -> None: # A basic for statement. - bytecode = self.__make_bytecode([ - # Define exit condition variable. - PushAction(100, ["i", 0]), - AP2Action(101, AP2Action.DEFINE_LOCAL), - # Check exit condition. - PushAction(102, [10, "i"]), - AP2Action(103, AP2Action.GET_VARIABLE), - IfAction(104, IfAction.COMP_LT_EQUALS, 109), - # Loop code. - AP2Action(105, AP2Action.NEXT_FRAME), - # Increment, also the continue point. - PushAction(106, ["i"]), - AddNumVariableAction(107, 1), - # Loop finished jump back to beginning. - JumpAction(108, 102), - # End of loop. - AP2Action(109, AP2Action.END), - ]) + bytecode = self.__make_bytecode( + [ + # Define exit condition variable. + PushAction(100, ["i", 0]), + AP2Action(101, AP2Action.DEFINE_LOCAL), + # Check exit condition. + PushAction(102, [10, "i"]), + AP2Action(103, AP2Action.GET_VARIABLE), + IfAction(104, IfAction.COMP_LT_EQUALS, 109), + # Loop code. + AP2Action(105, AP2Action.NEXT_FRAME), + # Increment, also the continue point. + PushAction(106, ["i"]), + AddNumVariableAction(107, 1), + # Loop finished jump back to beginning. + JumpAction(108, 102), + # End of loop. + AP2Action(109, AP2Action.END), + ] + ) statements = self.__call_decompile(bytecode) - self.assertEqual(self.__equiv(statements), [ - f"for (local i = 0; i < 10; i = i + 1) {OPEN_BRACKET}{os.linesep}" - f" builtin_GotoNextFrame(){os.linesep}" - "}" - ]) + self.assertEqual( + self.__equiv(statements), + [ + f"for (local i = 0; i < 10; i = i + 1) {OPEN_BRACKET}{os.linesep}" + f" builtin_GotoNextFrame(){os.linesep}" + "}" + ], + ) def test_advanced_for(self) -> None: # A basic for statement. - bytecode = self.__make_bytecode([ - # Define exit condition variable. - PushAction(100, ["i", 0]), - AP2Action(101, AP2Action.DEFINE_LOCAL), - # Check exit condition. - PushAction(102, [10, "i"]), - AP2Action(103, AP2Action.GET_VARIABLE), - IfAction(104, IfAction.COMP_LT_EQUALS, 115), - # Loop code with a continue statement. - PushAction(105, ["some_condition"]), - AP2Action(106, AP2Action.GET_VARIABLE), - IfAction(107, IfAction.COMP_IS_FALSE, 110), - AP2Action(108, AP2Action.NEXT_FRAME), - # Continue statement. - JumpAction(109, 112), - # Exit early. - AP2Action(110, AP2Action.STOP), - # Break statement. - JumpAction(111, 115), - # Increment, also the continue point. - PushAction(112, ["i"]), - AddNumVariableAction(113, 1), - # Loop finished jump back to beginning. - JumpAction(114, 102), - # End of loop. - AP2Action(115, AP2Action.END), - ]) + bytecode = self.__make_bytecode( + [ + # Define exit condition variable. + PushAction(100, ["i", 0]), + AP2Action(101, AP2Action.DEFINE_LOCAL), + # Check exit condition. + PushAction(102, [10, "i"]), + AP2Action(103, AP2Action.GET_VARIABLE), + IfAction(104, IfAction.COMP_LT_EQUALS, 115), + # Loop code with a continue statement. + PushAction(105, ["some_condition"]), + AP2Action(106, AP2Action.GET_VARIABLE), + IfAction(107, IfAction.COMP_IS_FALSE, 110), + AP2Action(108, AP2Action.NEXT_FRAME), + # Continue statement. + JumpAction(109, 112), + # Exit early. + AP2Action(110, AP2Action.STOP), + # Break statement. + JumpAction(111, 115), + # Increment, also the continue point. + PushAction(112, ["i"]), + AddNumVariableAction(113, 1), + # Loop finished jump back to beginning. + JumpAction(114, 102), + # End of loop. + AP2Action(115, AP2Action.END), + ] + ) statements = self.__call_decompile(bytecode) - self.assertEqual(self.__equiv(statements), [ - f"for (local i = 0; i < 10; i = i + 1) {OPEN_BRACKET}{os.linesep}" - f" if (not some_condition) {OPEN_BRACKET}{os.linesep}" - f" builtin_StopPlaying(){os.linesep}" - f" break{os.linesep}" - f" {CLOSE_BRACKET}{os.linesep}" - f" builtin_GotoNextFrame(){os.linesep}" - "}" - ]) + self.assertEqual( + self.__equiv(statements), + [ + f"for (local i = 0; i < 10; i = i + 1) {OPEN_BRACKET}{os.linesep}" + f" if (not some_condition) {OPEN_BRACKET}{os.linesep}" + f" builtin_StopPlaying(){os.linesep}" + f" break{os.linesep}" + f" {CLOSE_BRACKET}{os.linesep}" + f" builtin_GotoNextFrame(){os.linesep}" + "}" + ], + ) class TestIfExprs(ExtendedTestCase): def test_simple(self) -> None: - self.assertEqual(str(IsUndefinedIf(Variable('a'))), "a is UNDEFINED") - self.assertEqual(str(IsBooleanIf(Variable('a'))), "a") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.EQUALS, Variable("b"))), "a == b") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.NOT_EQUALS, Variable("b"))), "a != b") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b"))), "a < b") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.GT, Variable("b"))), "a > b") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.LT_EQUALS, Variable("b"))), "a <= b") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.GT_EQUALS, Variable("b"))), "a >= b") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.STRICT_EQUALS, Variable("b"))), "a === b") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.STRICT_NOT_EQUALS, Variable("b"))), "a !== b") + self.assertEqual(str(IsUndefinedIf(Variable("a"))), "a is UNDEFINED") + self.assertEqual(str(IsBooleanIf(Variable("a"))), "a") + self.assertEqual( + str(TwoParameterIf(Variable("a"), TwoParameterIf.EQUALS, Variable("b"))), + "a == b", + ) + self.assertEqual( + str( + TwoParameterIf(Variable("a"), TwoParameterIf.NOT_EQUALS, Variable("b")) + ), + "a != b", + ) + self.assertEqual( + str(TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b"))), + "a < b", + ) + self.assertEqual( + str(TwoParameterIf(Variable("a"), TwoParameterIf.GT, Variable("b"))), + "a > b", + ) + self.assertEqual( + str(TwoParameterIf(Variable("a"), TwoParameterIf.LT_EQUALS, Variable("b"))), + "a <= b", + ) + self.assertEqual( + str(TwoParameterIf(Variable("a"), TwoParameterIf.GT_EQUALS, Variable("b"))), + "a >= b", + ) + self.assertEqual( + str( + TwoParameterIf( + Variable("a"), TwoParameterIf.STRICT_EQUALS, Variable("b") + ) + ), + "a === b", + ) + self.assertEqual( + str( + TwoParameterIf( + Variable("a"), TwoParameterIf.STRICT_NOT_EQUALS, Variable("b") + ) + ), + "a !== b", + ) self.assertEqual( str( AndIf( - TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b")), - TwoParameterIf(Variable('c'), TwoParameterIf.GT, Variable("d")), + TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b")), + TwoParameterIf(Variable("c"), TwoParameterIf.GT, Variable("d")), ) ), "a < b && c > d", @@ -964,29 +1222,83 @@ class TestIfExprs(ExtendedTestCase): self.assertEqual( str( OrIf( - TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b")), - TwoParameterIf(Variable('c'), TwoParameterIf.GT, Variable("d")), + TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b")), + TwoParameterIf(Variable("c"), TwoParameterIf.GT, Variable("d")), ) ), "a < b || c > d", ) def test_invert_simple(self) -> None: - self.assertEqual(str(IsUndefinedIf(Variable('a')).invert()), "a is not UNDEFINED") - self.assertEqual(str(IsBooleanIf(Variable('a')).invert()), "not a") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.EQUALS, Variable("b")).invert()), "a != b") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.NOT_EQUALS, Variable("b")).invert()), "a == b") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b")).invert()), "a >= b") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.GT, Variable("b")).invert()), "a <= b") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.LT_EQUALS, Variable("b")).invert()), "a > b") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.GT_EQUALS, Variable("b")).invert()), "a < b") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.STRICT_EQUALS, Variable("b")).invert()), "a !== b") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.STRICT_NOT_EQUALS, Variable("b")).invert()), "a === b") + self.assertEqual( + str(IsUndefinedIf(Variable("a")).invert()), "a is not UNDEFINED" + ) + self.assertEqual(str(IsBooleanIf(Variable("a")).invert()), "not a") + self.assertEqual( + str( + TwoParameterIf( + Variable("a"), TwoParameterIf.EQUALS, Variable("b") + ).invert() + ), + "a != b", + ) + self.assertEqual( + str( + TwoParameterIf( + Variable("a"), TwoParameterIf.NOT_EQUALS, Variable("b") + ).invert() + ), + "a == b", + ) + self.assertEqual( + str( + TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b")).invert() + ), + "a >= b", + ) + self.assertEqual( + str( + TwoParameterIf(Variable("a"), TwoParameterIf.GT, Variable("b")).invert() + ), + "a <= b", + ) + self.assertEqual( + str( + TwoParameterIf( + Variable("a"), TwoParameterIf.LT_EQUALS, Variable("b") + ).invert() + ), + "a > b", + ) + self.assertEqual( + str( + TwoParameterIf( + Variable("a"), TwoParameterIf.GT_EQUALS, Variable("b") + ).invert() + ), + "a < b", + ) + self.assertEqual( + str( + TwoParameterIf( + Variable("a"), TwoParameterIf.STRICT_EQUALS, Variable("b") + ).invert() + ), + "a !== b", + ) + self.assertEqual( + str( + TwoParameterIf( + Variable("a"), TwoParameterIf.STRICT_NOT_EQUALS, Variable("b") + ).invert() + ), + "a === b", + ) self.assertEqual( str( AndIf( - TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b")), - TwoParameterIf(Variable('c'), TwoParameterIf.GT, Variable("d")), + TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b")), + TwoParameterIf(Variable("c"), TwoParameterIf.GT, Variable("d")), ).invert() ), "a >= b || c <= d", @@ -994,59 +1306,173 @@ class TestIfExprs(ExtendedTestCase): self.assertEqual( str( OrIf( - TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b")), - TwoParameterIf(Variable('c'), TwoParameterIf.GT, Variable("d")), + TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b")), + TwoParameterIf(Variable("c"), TwoParameterIf.GT, Variable("d")), ).invert(), ), "a >= b && c <= d", ) def test_invert_double(self) -> None: - self.assertEqual(str(IsUndefinedIf(Variable('a')).invert().invert()), "a is UNDEFINED") - self.assertEqual(str(IsBooleanIf(Variable('a')).invert().invert()), "a") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.EQUALS, Variable("b")).invert().invert()), "a == b") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.NOT_EQUALS, Variable("b")).invert().invert()), "a != b") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b")).invert().invert()), "a < b") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.GT, Variable("b")).invert().invert()), "a > b") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.LT_EQUALS, Variable("b")).invert().invert()), "a <= b") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.GT_EQUALS, Variable("b")).invert().invert()), "a >= b") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.STRICT_EQUALS, Variable("b")).invert().invert()), "a === b") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.STRICT_NOT_EQUALS, Variable("b")).invert().invert()), "a !== b") + self.assertEqual( + str(IsUndefinedIf(Variable("a")).invert().invert()), "a is UNDEFINED" + ) + self.assertEqual(str(IsBooleanIf(Variable("a")).invert().invert()), "a") + self.assertEqual( + str( + TwoParameterIf(Variable("a"), TwoParameterIf.EQUALS, Variable("b")) + .invert() + .invert() + ), + "a == b", + ) + self.assertEqual( + str( + TwoParameterIf(Variable("a"), TwoParameterIf.NOT_EQUALS, Variable("b")) + .invert() + .invert() + ), + "a != b", + ) + self.assertEqual( + str( + TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b")) + .invert() + .invert() + ), + "a < b", + ) + self.assertEqual( + str( + TwoParameterIf(Variable("a"), TwoParameterIf.GT, Variable("b")) + .invert() + .invert() + ), + "a > b", + ) + self.assertEqual( + str( + TwoParameterIf(Variable("a"), TwoParameterIf.LT_EQUALS, Variable("b")) + .invert() + .invert() + ), + "a <= b", + ) + self.assertEqual( + str( + TwoParameterIf(Variable("a"), TwoParameterIf.GT_EQUALS, Variable("b")) + .invert() + .invert() + ), + "a >= b", + ) + self.assertEqual( + str( + TwoParameterIf( + Variable("a"), TwoParameterIf.STRICT_EQUALS, Variable("b") + ) + .invert() + .invert() + ), + "a === b", + ) + self.assertEqual( + str( + TwoParameterIf( + Variable("a"), TwoParameterIf.STRICT_NOT_EQUALS, Variable("b") + ) + .invert() + .invert() + ), + "a !== b", + ) self.assertEqual( str( AndIf( - TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b")), - TwoParameterIf(Variable('c'), TwoParameterIf.GT, Variable("d")), - ).invert().invert() + TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b")), + TwoParameterIf(Variable("c"), TwoParameterIf.GT, Variable("d")), + ) + .invert() + .invert() ), "a < b && c > d", ) self.assertEqual( str( OrIf( - TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b")), - TwoParameterIf(Variable('c'), TwoParameterIf.GT, Variable("d")), - ).invert().invert() + TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b")), + TwoParameterIf(Variable("c"), TwoParameterIf.GT, Variable("d")), + ) + .invert() + .invert() ), "a < b || c > d", ) def test_swap_simple(self) -> None: - self.assertEqual(str(IsUndefinedIf(Variable('a')).swap()), "a is UNDEFINED") - self.assertEqual(str(IsBooleanIf(Variable('a')).swap()), "a") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.EQUALS, Variable("b")).swap()), "b == a") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.NOT_EQUALS, Variable("b")).swap()), "b != a") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b")).swap()), "b > a") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.GT, Variable("b")).swap()), "b < a") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.LT_EQUALS, Variable("b")).swap()), "b >= a") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.GT_EQUALS, Variable("b")).swap()), "b <= a") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.STRICT_EQUALS, Variable("b")).swap()), "b === a") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.STRICT_NOT_EQUALS, Variable("b")).swap()), "b !== a") + self.assertEqual(str(IsUndefinedIf(Variable("a")).swap()), "a is UNDEFINED") + self.assertEqual(str(IsBooleanIf(Variable("a")).swap()), "a") + self.assertEqual( + str( + TwoParameterIf( + Variable("a"), TwoParameterIf.EQUALS, Variable("b") + ).swap() + ), + "b == a", + ) + self.assertEqual( + str( + TwoParameterIf( + Variable("a"), TwoParameterIf.NOT_EQUALS, Variable("b") + ).swap() + ), + "b != a", + ) + self.assertEqual( + str(TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b")).swap()), + "b > a", + ) + self.assertEqual( + str(TwoParameterIf(Variable("a"), TwoParameterIf.GT, Variable("b")).swap()), + "b < a", + ) + self.assertEqual( + str( + TwoParameterIf( + Variable("a"), TwoParameterIf.LT_EQUALS, Variable("b") + ).swap() + ), + "b >= a", + ) + self.assertEqual( + str( + TwoParameterIf( + Variable("a"), TwoParameterIf.GT_EQUALS, Variable("b") + ).swap() + ), + "b <= a", + ) + self.assertEqual( + str( + TwoParameterIf( + Variable("a"), TwoParameterIf.STRICT_EQUALS, Variable("b") + ).swap() + ), + "b === a", + ) + self.assertEqual( + str( + TwoParameterIf( + Variable("a"), TwoParameterIf.STRICT_NOT_EQUALS, Variable("b") + ).swap() + ), + "b !== a", + ) self.assertEqual( str( AndIf( - TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b")), - TwoParameterIf(Variable('c'), TwoParameterIf.GT, Variable("d")), + TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b")), + TwoParameterIf(Variable("c"), TwoParameterIf.GT, Variable("d")), ).swap() ), "c > d && a < b", @@ -1054,53 +1480,121 @@ class TestIfExprs(ExtendedTestCase): self.assertEqual( str( OrIf( - TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b")), - TwoParameterIf(Variable('c'), TwoParameterIf.GT, Variable("d")), + TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b")), + TwoParameterIf(Variable("c"), TwoParameterIf.GT, Variable("d")), ).swap(), ), "c > d || a < b", ) def test_swap_double(self) -> None: - self.assertEqual(str(IsUndefinedIf(Variable('a')).swap().swap()), "a is UNDEFINED") - self.assertEqual(str(IsBooleanIf(Variable('a')).swap().swap()), "a") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.EQUALS, Variable("b")).swap().swap()), "a == b") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.NOT_EQUALS, Variable("b")).swap().swap()), "a != b") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b")).swap().swap()), "a < b") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.GT, Variable("b")).swap().swap()), "a > b") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.LT_EQUALS, Variable("b")).swap().swap()), "a <= b") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.GT_EQUALS, Variable("b")).swap().swap()), "a >= b") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.STRICT_EQUALS, Variable("b")).swap().swap()), "a === b") - self.assertEqual(str(TwoParameterIf(Variable('a'), TwoParameterIf.STRICT_NOT_EQUALS, Variable("b")).swap().swap()), "a !== b") + self.assertEqual( + str(IsUndefinedIf(Variable("a")).swap().swap()), "a is UNDEFINED" + ) + self.assertEqual(str(IsBooleanIf(Variable("a")).swap().swap()), "a") + self.assertEqual( + str( + TwoParameterIf(Variable("a"), TwoParameterIf.EQUALS, Variable("b")) + .swap() + .swap() + ), + "a == b", + ) + self.assertEqual( + str( + TwoParameterIf(Variable("a"), TwoParameterIf.NOT_EQUALS, Variable("b")) + .swap() + .swap() + ), + "a != b", + ) + self.assertEqual( + str( + TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b")) + .swap() + .swap() + ), + "a < b", + ) + self.assertEqual( + str( + TwoParameterIf(Variable("a"), TwoParameterIf.GT, Variable("b")) + .swap() + .swap() + ), + "a > b", + ) + self.assertEqual( + str( + TwoParameterIf(Variable("a"), TwoParameterIf.LT_EQUALS, Variable("b")) + .swap() + .swap() + ), + "a <= b", + ) + self.assertEqual( + str( + TwoParameterIf(Variable("a"), TwoParameterIf.GT_EQUALS, Variable("b")) + .swap() + .swap() + ), + "a >= b", + ) + self.assertEqual( + str( + TwoParameterIf( + Variable("a"), TwoParameterIf.STRICT_EQUALS, Variable("b") + ) + .swap() + .swap() + ), + "a === b", + ) + self.assertEqual( + str( + TwoParameterIf( + Variable("a"), TwoParameterIf.STRICT_NOT_EQUALS, Variable("b") + ) + .swap() + .swap() + ), + "a !== b", + ) self.assertEqual( str( AndIf( - TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b")), - TwoParameterIf(Variable('c'), TwoParameterIf.GT, Variable("d")), - ).swap().swap() + TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b")), + TwoParameterIf(Variable("c"), TwoParameterIf.GT, Variable("d")), + ) + .swap() + .swap() ), "a < b && c > d", ) self.assertEqual( str( OrIf( - TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b")), - TwoParameterIf(Variable('c'), TwoParameterIf.GT, Variable("d")), - ).swap().swap() + TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b")), + TwoParameterIf(Variable("c"), TwoParameterIf.GT, Variable("d")), + ) + .swap() + .swap() ), "a < b || c > d", ) def test_simplify_noop(self) -> None: - self.assertEqual(str(IsUndefinedIf(Variable('a')).simplify()), "a is UNDEFINED") - self.assertEqual(str(IsUndefinedIf(Variable('a')).invert().simplify()), "a is not UNDEFINED") - self.assertEqual(str(IsBooleanIf(Variable('a')).simplify()), "a") - self.assertEqual(str(IsBooleanIf(Variable('a')).invert().simplify()), "not a") + self.assertEqual(str(IsUndefinedIf(Variable("a")).simplify()), "a is UNDEFINED") + self.assertEqual( + str(IsUndefinedIf(Variable("a")).invert().simplify()), "a is not UNDEFINED" + ) + self.assertEqual(str(IsBooleanIf(Variable("a")).simplify()), "a") + self.assertEqual(str(IsBooleanIf(Variable("a")).invert().simplify()), "not a") self.assertEqual( str( AndIf( - TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b")), - TwoParameterIf(Variable('c'), TwoParameterIf.GT, Variable("d")), + TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b")), + TwoParameterIf(Variable("c"), TwoParameterIf.GT, Variable("d")), ).simplify() ), "a < b && c > d", @@ -1108,8 +1602,8 @@ class TestIfExprs(ExtendedTestCase): self.assertEqual( str( OrIf( - TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b")), - TwoParameterIf(Variable('c'), TwoParameterIf.GT, Variable("d")), + TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b")), + TwoParameterIf(Variable("c"), TwoParameterIf.GT, Variable("d")), ).simplify() ), "a < b || c > d", @@ -1117,8 +1611,10 @@ class TestIfExprs(ExtendedTestCase): self.assertEqual( str( AndIf( - TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b")), - TwoParameterIf(Variable('a'), TwoParameterIf.GT_EQUALS, Variable("c")), + TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b")), + TwoParameterIf( + Variable("a"), TwoParameterIf.GT_EQUALS, Variable("c") + ), ).simplify() ), "a < b && a >= c", @@ -1126,8 +1622,8 @@ class TestIfExprs(ExtendedTestCase): self.assertEqual( str( OrIf( - TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b")), - TwoParameterIf(Variable('a'), TwoParameterIf.GT, Variable("b")), + TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b")), + TwoParameterIf(Variable("a"), TwoParameterIf.GT, Variable("b")), ).simplify() ), "a < b || a > b", @@ -1145,7 +1641,7 @@ class TestIfExprs(ExtendedTestCase): self.assertEqual( str( AndIf( - TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b")), + TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b")), IsBooleanIf(True), ).simplify() ), @@ -1154,7 +1650,7 @@ class TestIfExprs(ExtendedTestCase): self.assertEqual( str( AndIf( - TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b")), + TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b")), IsBooleanIf(False), ).simplify() ), @@ -1164,7 +1660,7 @@ class TestIfExprs(ExtendedTestCase): str( AndIf( IsBooleanIf(True), - TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b")), + TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b")), ).simplify() ), "a < b", @@ -1173,7 +1669,7 @@ class TestIfExprs(ExtendedTestCase): str( AndIf( IsBooleanIf(False), - TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b")), + TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b")), ).simplify() ), "False", @@ -1181,7 +1677,7 @@ class TestIfExprs(ExtendedTestCase): self.assertEqual( str( OrIf( - TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b")), + TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b")), IsBooleanIf(True), ).simplify() ), @@ -1190,7 +1686,7 @@ class TestIfExprs(ExtendedTestCase): self.assertEqual( str( OrIf( - TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b")), + TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b")), IsBooleanIf(False), ).simplify() ), @@ -1200,7 +1696,7 @@ class TestIfExprs(ExtendedTestCase): str( OrIf( IsBooleanIf(True), - TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b")), + TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b")), ).simplify() ), "True", @@ -1209,7 +1705,7 @@ class TestIfExprs(ExtendedTestCase): str( OrIf( IsBooleanIf(False), - TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b")), + TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b")), ).simplify() ), "a < b", @@ -1219,8 +1715,10 @@ class TestIfExprs(ExtendedTestCase): self.assertEqual( str( AndIf( - TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b")), - TwoParameterIf(Variable('a'), TwoParameterIf.GT_EQUALS, Variable("b")), + TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b")), + TwoParameterIf( + Variable("a"), TwoParameterIf.GT_EQUALS, Variable("b") + ), ).simplify() ), "False", @@ -1228,8 +1726,10 @@ class TestIfExprs(ExtendedTestCase): self.assertEqual( str( OrIf( - TwoParameterIf(Variable('a'), TwoParameterIf.LT, Variable("b")), - TwoParameterIf(Variable('a'), TwoParameterIf.GT_EQUALS, Variable("b")), + TwoParameterIf(Variable("a"), TwoParameterIf.LT, Variable("b")), + TwoParameterIf( + Variable("a"), TwoParameterIf.GT_EQUALS, Variable("b") + ), ).simplify() ), "True", @@ -1238,56 +1738,56 @@ class TestIfExprs(ExtendedTestCase): def test_equals_associativity(self) -> None: self.assertEqual( AndIf( - IsBooleanIf(Variable('a')), + IsBooleanIf(Variable("a")), AndIf( - IsBooleanIf(Variable('b')), - IsBooleanIf(Variable('c')), + IsBooleanIf(Variable("b")), + IsBooleanIf(Variable("c")), ), ), AndIf( AndIf( - IsBooleanIf(Variable('a')), - IsBooleanIf(Variable('b')), + IsBooleanIf(Variable("a")), + IsBooleanIf(Variable("b")), ), - IsBooleanIf(Variable('c')), + IsBooleanIf(Variable("c")), ), ) self.assertEqual( OrIf( - IsBooleanIf(Variable('a')), + IsBooleanIf(Variable("a")), OrIf( - IsBooleanIf(Variable('b')), - IsBooleanIf(Variable('c')), + IsBooleanIf(Variable("b")), + IsBooleanIf(Variable("c")), ), ), OrIf( OrIf( - IsBooleanIf(Variable('a')), - IsBooleanIf(Variable('b')), + IsBooleanIf(Variable("a")), + IsBooleanIf(Variable("b")), ), - IsBooleanIf(Variable('c')), + IsBooleanIf(Variable("c")), ), ) def test_equals_commutativity(self) -> None: self.assertEqual( AndIf( - IsBooleanIf(Variable('a')), - IsBooleanIf(Variable('b')), + IsBooleanIf(Variable("a")), + IsBooleanIf(Variable("b")), ), AndIf( - IsBooleanIf(Variable('b')), - IsBooleanIf(Variable('a')), + IsBooleanIf(Variable("b")), + IsBooleanIf(Variable("a")), ), ) self.assertEqual( OrIf( - IsBooleanIf(Variable('a')), - IsBooleanIf(Variable('b')), + IsBooleanIf(Variable("a")), + IsBooleanIf(Variable("b")), ), OrIf( - IsBooleanIf(Variable('b')), - IsBooleanIf(Variable('a')), + IsBooleanIf(Variable("b")), + IsBooleanIf(Variable("a")), ), ) @@ -1295,7 +1795,7 @@ class TestIfExprs(ExtendedTestCase): self.assertEqual( str( AndIf( - IsBooleanIf(Variable('a')), + IsBooleanIf(Variable("a")), IsBooleanIf(True), ).simplify(), ), @@ -1304,7 +1804,7 @@ class TestIfExprs(ExtendedTestCase): self.assertEqual( str( OrIf( - IsBooleanIf(Variable('a')), + IsBooleanIf(Variable("a")), IsBooleanIf(False), ).simplify(), ), @@ -1315,7 +1815,7 @@ class TestIfExprs(ExtendedTestCase): self.assertEqual( str( AndIf( - IsBooleanIf(Variable('a')), + IsBooleanIf(Variable("a")), IsBooleanIf(False), ).simplify(), ), @@ -1324,7 +1824,7 @@ class TestIfExprs(ExtendedTestCase): self.assertEqual( str( OrIf( - IsBooleanIf(Variable('a')), + IsBooleanIf(Variable("a")), IsBooleanIf(True), ).simplify(), ), @@ -1335,8 +1835,8 @@ class TestIfExprs(ExtendedTestCase): self.assertEqual( str( AndIf( - IsBooleanIf(Variable('a')), - IsBooleanIf(Variable('a')), + IsBooleanIf(Variable("a")), + IsBooleanIf(Variable("a")), ).simplify(), ), "a", @@ -1344,8 +1844,8 @@ class TestIfExprs(ExtendedTestCase): self.assertEqual( str( OrIf( - IsBooleanIf(Variable('a')), - IsBooleanIf(Variable('a')), + IsBooleanIf(Variable("a")), + IsBooleanIf(Variable("a")), ).simplify(), ), "a", @@ -1355,8 +1855,8 @@ class TestIfExprs(ExtendedTestCase): self.assertEqual( str( AndIf( - IsBooleanIf(Variable('a')), - IsBooleanIf(Variable('a')).invert(), + IsBooleanIf(Variable("a")), + IsBooleanIf(Variable("a")).invert(), ).simplify(), ), "False", @@ -1364,8 +1864,8 @@ class TestIfExprs(ExtendedTestCase): self.assertEqual( str( OrIf( - IsBooleanIf(Variable('a')), - IsBooleanIf(Variable('a')).invert(), + IsBooleanIf(Variable("a")), + IsBooleanIf(Variable("a")).invert(), ).simplify(), ), "True", @@ -1376,12 +1876,12 @@ class TestIfExprs(ExtendedTestCase): str( OrIf( AndIf( - IsBooleanIf(Variable('x')), - IsBooleanIf(Variable('y')), + IsBooleanIf(Variable("x")), + IsBooleanIf(Variable("y")), ), AndIf( - IsBooleanIf(Variable('x')), - IsBooleanIf(Variable('y')).invert(), + IsBooleanIf(Variable("x")), + IsBooleanIf(Variable("y")).invert(), ), ).simplify(), ), @@ -1391,12 +1891,12 @@ class TestIfExprs(ExtendedTestCase): str( AndIf( OrIf( - IsBooleanIf(Variable('x')), - IsBooleanIf(Variable('y')), + IsBooleanIf(Variable("x")), + IsBooleanIf(Variable("y")), ), OrIf( - IsBooleanIf(Variable('x')), - IsBooleanIf(Variable('y')).invert(), + IsBooleanIf(Variable("x")), + IsBooleanIf(Variable("y")).invert(), ), ).simplify(), ), @@ -1407,10 +1907,10 @@ class TestIfExprs(ExtendedTestCase): self.assertEqual( str( AndIf( - IsBooleanIf(Variable('x')), + IsBooleanIf(Variable("x")), OrIf( - IsBooleanIf(Variable('x')), - IsBooleanIf(Variable('y')), + IsBooleanIf(Variable("x")), + IsBooleanIf(Variable("y")), ), ).simplify(), ), @@ -1419,10 +1919,10 @@ class TestIfExprs(ExtendedTestCase): self.assertEqual( str( OrIf( - IsBooleanIf(Variable('x')), + IsBooleanIf(Variable("x")), AndIf( - IsBooleanIf(Variable('x')), - IsBooleanIf(Variable('y')), + IsBooleanIf(Variable("x")), + IsBooleanIf(Variable("y")), ), ).simplify(), ), @@ -1433,10 +1933,10 @@ class TestIfExprs(ExtendedTestCase): self.assertEqual( str( AndIf( - IsBooleanIf(Variable('x')), + IsBooleanIf(Variable("x")), OrIf( - IsBooleanIf(Variable('x')).invert(), - IsBooleanIf(Variable('y')), + IsBooleanIf(Variable("x")).invert(), + IsBooleanIf(Variable("y")), ), ).simplify(), ), @@ -1445,10 +1945,10 @@ class TestIfExprs(ExtendedTestCase): self.assertEqual( str( OrIf( - IsBooleanIf(Variable('x')), + IsBooleanIf(Variable("x")), AndIf( - IsBooleanIf(Variable('x')).invert(), - IsBooleanIf(Variable('y')), + IsBooleanIf(Variable("x")).invert(), + IsBooleanIf(Variable("y")), ), ).simplify(), ), @@ -1464,10 +1964,12 @@ class TestAFPOptimize(ExtendedTestCase): [], -1, ), - optimize=True + optimize=True, ) with bcd.debugging(verbose=self.verbose): - return bcd._pretty_print(bcd._optimize_code(statements), prefix="").split(os.linesep) + return bcd._pretty_print(bcd._optimize_code(statements), prefix="").split( + os.linesep + ) def test_no_flow(self) -> None: statements: List[Statement] = [ @@ -1478,9 +1980,9 @@ class TestAFPOptimize(ExtendedTestCase): self.assertEqual( self.__optimize_code(statements), [ - 'builtin_StartPlaying();', - 'builtin_StopPlaying();', - ] + "builtin_StartPlaying();", + "builtin_StopPlaying();", + ], ) def test_basic_flow(self) -> None: @@ -1488,7 +1990,7 @@ class TestAFPOptimize(ExtendedTestCase): PlayMovieStatement(), IfStatement( IsBooleanIf( - Variable('a'), + Variable("a"), ), [ NextFrameStatement(), @@ -1503,38 +2005,38 @@ class TestAFPOptimize(ExtendedTestCase): self.assertEqual( self.__optimize_code(statements), [ - 'builtin_StartPlaying();', - 'if (a)', - '{', - ' builtin_GotoNextFrame();', - '}', - 'else', - '{', - ' builtin_GotoPreviousFrame();', - '}', - 'builtin_StopPlaying();', - ] + "builtin_StartPlaying();", + "if (a)", + "{", + " builtin_GotoNextFrame();", + "}", + "else", + "{", + " builtin_GotoPreviousFrame();", + "}", + "builtin_StopPlaying();", + ], ) def test_compound_or_basic(self) -> None: statements: List[Statement] = [ IfStatement( TwoParameterIf( - Variable('a'), + Variable("a"), TwoParameterIf.NOT_EQUALS, 1, ), [ IfStatement( TwoParameterIf( - Variable('a'), + Variable("a"), TwoParameterIf.NOT_EQUALS, 2, ), [ StopMovieStatement(), DefineLabelStatement(1000), - ReturnStatement('strval'), + ReturnStatement("strval"), ], [], ), @@ -1548,30 +2050,30 @@ class TestAFPOptimize(ExtendedTestCase): self.assertEqual( self.__optimize_code(statements), [ - 'if (a == 1 || a == 2)', - '{', - ' builtin_StartPlaying();', - '}', - 'else', - '{', - ' builtin_StopPlaying();', - '}', + "if (a == 1 || a == 2)", + "{", + " builtin_StartPlaying();", + "}", + "else", + "{", + " builtin_StopPlaying();", + "}", "return 'strval';", - ] + ], ) def test_compound_or_alternate(self) -> None: statements: List[Statement] = [ IfStatement( TwoParameterIf( - Variable('a'), + Variable("a"), TwoParameterIf.NOT_EQUALS, 1, ), [ IfStatement( TwoParameterIf( - Variable('a'), + Variable("a"), TwoParameterIf.NOT_EQUALS, 2, ), @@ -1586,48 +2088,48 @@ class TestAFPOptimize(ExtendedTestCase): ), PlayMovieStatement(), DefineLabelStatement(1000), - ReturnStatement('strval'), + ReturnStatement("strval"), ] self.assertEqual( self.__optimize_code(statements), [ - 'if (a == 1 || a == 2)', - '{', - ' builtin_StartPlaying();', - '}', - 'else', - '{', - ' builtin_StopPlaying();', - '}', + "if (a == 1 || a == 2)", + "{", + " builtin_StartPlaying();", + "}", + "else", + "{", + " builtin_StopPlaying();", + "}", "return 'strval';", - ] + ], ) def test_compound_or_inside_if(self) -> None: statements: List[Statement] = [ IfStatement( IsBooleanIf( - Variable('debug'), + Variable("debug"), ).invert(), [ IfStatement( TwoParameterIf( - Variable('a'), + Variable("a"), TwoParameterIf.NOT_EQUALS, 1, ), [ IfStatement( TwoParameterIf( - Variable('a'), + Variable("a"), TwoParameterIf.NOT_EQUALS, 2, ), [ StopMovieStatement(), DefineLabelStatement(1000), - ReturnStatement('strval'), + ReturnStatement("strval"), ], [], ), @@ -1644,19 +2146,19 @@ class TestAFPOptimize(ExtendedTestCase): self.assertEqual( self.__optimize_code(statements), [ - 'if (not debug)', - '{', - ' if (a == 1 || a == 2)', - ' {', - ' builtin_StartPlaying();', - ' }', - ' else', - ' {', - ' builtin_StopPlaying();', - ' }', + "if (not debug)", + "{", + " if (a == 1 || a == 2)", + " {", + " builtin_StartPlaying();", + " }", + " else", + " {", + " builtin_StopPlaying();", + " }", " return 'strval';", - '}', - ] + "}", + ], ) def test_compound_or_inside_while(self) -> None: @@ -1665,33 +2167,33 @@ class TestAFPOptimize(ExtendedTestCase): "x", 0, TwoParameterIf( - Variable('x'), + Variable("x"), TwoParameterIf.LT, 10, ), ArithmeticExpression( - Variable('x'), - '+', + Variable("x"), + "+", 1, ), [ IfStatement( TwoParameterIf( - Variable('a'), + Variable("a"), TwoParameterIf.NOT_EQUALS, 1, ), [ IfStatement( TwoParameterIf( - Variable('a'), + Variable("a"), TwoParameterIf.NOT_EQUALS, 2, ), [ StopMovieStatement(), DefineLabelStatement(1000), - ReturnStatement('strval'), + ReturnStatement("strval"), ], [], ), @@ -1708,40 +2210,40 @@ class TestAFPOptimize(ExtendedTestCase): self.assertEqual( self.__optimize_code(statements), [ - 'for (local x = 0; x < 10; x = x + 1)', - '{', - ' if (a == 1 || a == 2)', - ' {', - ' builtin_StartPlaying();', - ' }', - ' else', - ' {', - ' builtin_StopPlaying();', - ' }', + "for (local x = 0; x < 10; x = x + 1)", + "{", + " if (a == 1 || a == 2)", + " {", + " builtin_StartPlaying();", + " }", + " else", + " {", + " builtin_StopPlaying();", + " }", " return 'strval';", - '}', - ] + "}", + ], ) def test_compound_or_with_inner_if(self) -> None: statements: List[Statement] = [ IfStatement( TwoParameterIf( - Variable('a'), + Variable("a"), TwoParameterIf.NOT_EQUALS, 1, ), [ IfStatement( TwoParameterIf( - Variable('a'), + Variable("a"), TwoParameterIf.NOT_EQUALS, 2, ), [ IfStatement( TwoParameterIf( - Variable('x'), + Variable("x"), TwoParameterIf.EQUALS, 5, ), @@ -1751,7 +2253,7 @@ class TestAFPOptimize(ExtendedTestCase): [], ), DefineLabelStatement(1000), - ReturnStatement('strval'), + ReturnStatement("strval"), ], [], ), @@ -1760,7 +2262,7 @@ class TestAFPOptimize(ExtendedTestCase): ), IfStatement( TwoParameterIf( - Variable('x'), + Variable("x"), TwoParameterIf.EQUALS, 10, ), @@ -1775,50 +2277,50 @@ class TestAFPOptimize(ExtendedTestCase): self.assertEqual( self.__optimize_code(statements), [ - 'if (a == 1 || a == 2)', - '{', - ' if (x == 10)', - ' {', - ' builtin_StartPlaying();', - ' }', - '}', - 'else', - '{', - ' if (x == 5)', - ' {', - ' builtin_StopPlaying();', - ' }', - '}', + "if (a == 1 || a == 2)", + "{", + " if (x == 10)", + " {", + " builtin_StartPlaying();", + " }", + "}", + "else", + "{", + " if (x == 5)", + " {", + " builtin_StopPlaying();", + " }", + "}", "return 'strval';", - ] + ], ) def test_compound_or_with_inner_compound_or(self) -> None: statements: List[Statement] = [ IfStatement( TwoParameterIf( - Variable('a'), + Variable("a"), TwoParameterIf.NOT_EQUALS, 1, ), [ IfStatement( TwoParameterIf( - Variable('a'), + Variable("a"), TwoParameterIf.NOT_EQUALS, 2, ), [ IfStatement( TwoParameterIf( - Variable('x'), + Variable("x"), TwoParameterIf.NOT_EQUALS, 10, ), [ IfStatement( TwoParameterIf( - Variable('x'), + Variable("x"), TwoParameterIf.NOT_EQUALS, 20, ), @@ -1842,58 +2344,58 @@ class TestAFPOptimize(ExtendedTestCase): NextFrameStatement(), PreviousFrameStatement(), DefineLabelStatement(1000), - ReturnStatement('strval'), + ReturnStatement("strval"), ] self.assertEqual( self.__optimize_code(statements), [ - 'if (a == 1 || a == 2)', - '{', - ' builtin_GotoNextFrame();', - ' builtin_GotoPreviousFrame();', - '}', - 'else', - '{', - ' if (x == 10 || x == 20)', - ' {', - ' builtin_StartPlaying();', - ' }', - ' else', - ' {', - ' builtin_StopPlaying();', - ' }', - '}', + "if (a == 1 || a == 2)", + "{", + " builtin_GotoNextFrame();", + " builtin_GotoPreviousFrame();", + "}", + "else", + "{", + " if (x == 10 || x == 20)", + " {", + " builtin_StartPlaying();", + " }", + " else", + " {", + " builtin_StopPlaying();", + " }", + "}", "return 'strval';", - ] + ], ) def test_compound_or_triple(self) -> None: statements: List[Statement] = [ IfStatement( TwoParameterIf( - Variable('a'), + Variable("a"), TwoParameterIf.NOT_EQUALS, 1, ), [ IfStatement( TwoParameterIf( - Variable('a'), + Variable("a"), TwoParameterIf.NOT_EQUALS, 2, ), [ IfStatement( TwoParameterIf( - Variable('a'), + Variable("a"), TwoParameterIf.NOT_EQUALS, 3, ), [ StopMovieStatement(), DefineLabelStatement(1000), - ReturnStatement('strval'), + ReturnStatement("strval"), ], [], ), @@ -1910,51 +2412,51 @@ class TestAFPOptimize(ExtendedTestCase): self.assertEqual( self.__optimize_code(statements), [ - 'if (a == 1 || a == 2 || a == 3)', - '{', - ' builtin_StartPlaying();', - '}', - 'else', - '{', - ' builtin_StopPlaying();', - '}', + "if (a == 1 || a == 2 || a == 3)", + "{", + " builtin_StartPlaying();", + "}", + "else", + "{", + " builtin_StopPlaying();", + "}", "return 'strval';", - ] + ], ) def test_compound_or_quad(self) -> None: statements: List[Statement] = [ IfStatement( TwoParameterIf( - Variable('a'), + Variable("a"), TwoParameterIf.NOT_EQUALS, 1, ), [ IfStatement( TwoParameterIf( - Variable('a'), + Variable("a"), TwoParameterIf.NOT_EQUALS, 2, ), [ IfStatement( TwoParameterIf( - Variable('a'), + Variable("a"), TwoParameterIf.NOT_EQUALS, 3, ), [ IfStatement( TwoParameterIf( - Variable('a'), + Variable("a"), TwoParameterIf.NOT_EQUALS, 4, ), [ StopMovieStatement(), DefineLabelStatement(1000), - ReturnStatement('strval'), + ReturnStatement("strval"), ], [], ), @@ -1974,16 +2476,16 @@ class TestAFPOptimize(ExtendedTestCase): self.assertEqual( self.__optimize_code(statements), [ - 'if (a == 1 || a == 2 || a == 3 || a == 4)', - '{', - ' builtin_StartPlaying();', - '}', - 'else', - '{', - ' builtin_StopPlaying();', - '}', + "if (a == 1 || a == 2 || a == 3 || a == 4)", + "{", + " builtin_StartPlaying();", + "}", + "else", + "{", + " builtin_StopPlaying();", + "}", "return 'strval';", - ] + ], ) def test_compound_or_quint(self) -> None: @@ -1991,42 +2493,42 @@ class TestAFPOptimize(ExtendedTestCase): statements: List[Statement] = [ IfStatement( TwoParameterIf( - Variable('a'), + Variable("a"), TwoParameterIf.NOT_EQUALS, 1, ), [ IfStatement( TwoParameterIf( - Variable('a'), + Variable("a"), TwoParameterIf.NOT_EQUALS, 2, ), [ IfStatement( TwoParameterIf( - Variable('a'), + Variable("a"), TwoParameterIf.NOT_EQUALS, 3, ), [ IfStatement( TwoParameterIf( - Variable('a'), + Variable("a"), TwoParameterIf.NOT_EQUALS, 4, ), [ IfStatement( TwoParameterIf( - Variable('a'), + Variable("a"), TwoParameterIf.NOT_EQUALS, 5, ), [ StopMovieStatement(), DefineLabelStatement(1000), - ReturnStatement('strval'), + ReturnStatement("strval"), ], [], ), @@ -2049,14 +2551,14 @@ class TestAFPOptimize(ExtendedTestCase): self.assertEqual( self.__optimize_code(statements), [ - 'if (a == 1 || a == 2 || a == 3 || a == 4 || a == 5)', - '{', - ' builtin_StartPlaying();', - '}', - 'else', - '{', - ' builtin_StopPlaying();', - '}', + "if (a == 1 || a == 2 || a == 3 || a == 4 || a == 5)", + "{", + " builtin_StartPlaying();", + "}", + "else", + "{", + " builtin_StopPlaying();", + "}", "return 'strval';", - ] + ], ) diff --git a/bemani/tests/test_lz77.py b/bemani/tests/test_lz77.py index 08b5818..79d1d92 100644 --- a/bemani/tests/test_lz77.py +++ b/bemani/tests/test_lz77.py @@ -9,7 +9,7 @@ from bemani.tests.helpers import get_fixture class TestLZ77Decompressor(unittest.TestCase): def test_ringbuffer_fuzz(self) -> None: - dec = Lz77Decompress(b'') + dec = Lz77Decompress(b"") for _ in range(100): amount = random.randint(1, Lz77Decompress.RING_LENGTH) @@ -20,7 +20,7 @@ class TestLZ77Decompressor(unittest.TestCase): dec._ring_write(data) # Read a chunk of data back from that buffer, see its the same - newdata = b''.join(dec._ring_read(readpos, amount)) + newdata = b"".join(dec._ring_read(readpos, amount)) self.assertEqual(data, newdata) # Verify integrity of ringbuffer diff --git a/bemani/tests/test_protocol.py b/bemani/tests/test_protocol.py index 4e652eb..70cb7f4 100644 --- a/bemani/tests/test_protocol.py +++ b/bemani/tests/test_protocol.py @@ -11,7 +11,11 @@ class TestProtocol(unittest.TestCase): def assertLoopback(self, root: Node) -> None: proto = EAmuseProtocol() - for encoding in [EAmuseProtocol.BINARY, EAmuseProtocol.BINARY_DECOMPRESSED, EAmuseProtocol.XML]: + for encoding in [ + EAmuseProtocol.BINARY, + EAmuseProtocol.BINARY_DECOMPRESSED, + EAmuseProtocol.XML, + ]: if encoding == EAmuseProtocol.BINARY: loop_name = "binary" elif encoding == EAmuseProtocol.BINARY_DECOMPRESSED: @@ -21,357 +25,409 @@ class TestProtocol(unittest.TestCase): else: raise Exception("Logic error!") - binary = proto.encode(None, None, root, text_encoding=EAmuseProtocol.SHIFT_JIS, packet_encoding=encoding) + binary = proto.encode( + None, + None, + root, + text_encoding=EAmuseProtocol.SHIFT_JIS, + packet_encoding=encoding, + ) newroot = proto.decode(None, None, binary) - self.assertEqual(newroot, root, f"Round trip with {loop_name} and no encryption/compression doesn't match!") + self.assertEqual( + newroot, + root, + f"Round trip with {loop_name} and no encryption/compression doesn't match!", + ) - binary = proto.encode(None, '1-abcdef-0123', root, text_encoding=EAmuseProtocol.SHIFT_JIS, packet_encoding=encoding) - newroot = proto.decode(None, '1-abcdef-0123', binary) - self.assertEqual(newroot, root, f"Round trip with {loop_name}, encryption and no compression doesn't match!") + binary = proto.encode( + None, + "1-abcdef-0123", + root, + text_encoding=EAmuseProtocol.SHIFT_JIS, + packet_encoding=encoding, + ) + newroot = proto.decode(None, "1-abcdef-0123", binary) + self.assertEqual( + newroot, + root, + f"Round trip with {loop_name}, encryption and no compression doesn't match!", + ) - binary = proto.encode('none', None, root, text_encoding=EAmuseProtocol.SHIFT_JIS, packet_encoding=encoding) - newroot = proto.decode('none', None, binary) - self.assertEqual(newroot, root, f"Round trip with {loop_name}, encryption and no compression doesn't match!") + binary = proto.encode( + "none", + None, + root, + text_encoding=EAmuseProtocol.SHIFT_JIS, + packet_encoding=encoding, + ) + newroot = proto.decode("none", None, binary) + self.assertEqual( + newroot, + root, + f"Round trip with {loop_name}, encryption and no compression doesn't match!", + ) - binary = proto.encode('lz77', None, root, text_encoding=EAmuseProtocol.SHIFT_JIS, packet_encoding=encoding) - newroot = proto.decode('lz77', None, binary) - self.assertEqual(newroot, root, f"Round trip with {loop_name}, no encryption and lz77 compression doesn't match!") + binary = proto.encode( + "lz77", + None, + root, + text_encoding=EAmuseProtocol.SHIFT_JIS, + packet_encoding=encoding, + ) + newroot = proto.decode("lz77", None, binary) + self.assertEqual( + newroot, + root, + f"Round trip with {loop_name}, no encryption and lz77 compression doesn't match!", + ) def test_game_packet1(self) -> None: - root = Node.void('call') - root.set_attribute('model', 'M39:J:B:A:2014061900') - root.set_attribute('srcid', '012010000000DEADBEEF') - root.set_attribute('tag', '1d0cbcd5') + root = Node.void("call") + root.set_attribute("model", "M39:J:B:A:2014061900") + root.set_attribute("srcid", "012010000000DEADBEEF") + root.set_attribute("tag", "1d0cbcd5") - pcbevent = Node.void('pcbevent') + pcbevent = Node.void("pcbevent") root.add_child(pcbevent) - pcbevent.set_attribute('method', 'put') + pcbevent.set_attribute("method", "put") - pcbevent.add_child(Node.time('time', 1438375918)) - pcbevent.add_child(Node.u32('seq', value=0)) + pcbevent.add_child(Node.time("time", 1438375918)) + pcbevent.add_child(Node.u32("seq", value=0)) - item = Node.void('item') + item = Node.void("item") pcbevent.add_child(item) - item.add_child(Node.string('name', 'boot')) - item.add_child(Node.s32('value', 1)) - item.add_child(Node.time('time', 1438375959)) + item.add_child(Node.string("name", "boot")) + item.add_child(Node.s32("value", 1)) + item.add_child(Node.time("time", 1438375959)) self.assertLoopback(root) def test_game_packet2(self) -> None: - root = Node.void('call') - root.set_attribute('model', 'LDJ:A:A:A:2015060700') - root.set_attribute('srcid', '012010000000DEADBEEF') - root.set_attribute('tag', '9yU+HH4q') + root = Node.void("call") + root.set_attribute("model", "LDJ:A:A:A:2015060700") + root.set_attribute("srcid", "012010000000DEADBEEF") + root.set_attribute("tag", "9yU+HH4q") - eacoin = Node.void('eacoin') + eacoin = Node.void("eacoin") root.add_child(eacoin) - eacoin.set_attribute('esdate', '2015-08-01T02:09:23') - eacoin.set_attribute('esid', '177baae4bdf0085f1f3da9b6fed02223ee9b482f62b83a28af704a9c7893a370') - eacoin.set_attribute('method', 'consume') + eacoin.set_attribute("esdate", "2015-08-01T02:09:23") + eacoin.set_attribute( + "esid", "177baae4bdf0085f1f3da9b6fed02223ee9b482f62b83a28af704a9c7893a370" + ) + eacoin.set_attribute("method", "consume") - eacoin.add_child(Node.string('sessid', '5666-5524')) - eacoin.add_child(Node.s16('sequence', 0)) - eacoin.add_child(Node.s32('payment', 420)) - eacoin.add_child(Node.s32('service', 0)) - eacoin.add_child(Node.string('itemtype', '0')) - eacoin.add_child(Node.string('detail', '/eacoin/premium_free_1p_3')) + eacoin.add_child(Node.string("sessid", "5666-5524")) + eacoin.add_child(Node.s16("sequence", 0)) + eacoin.add_child(Node.s32("payment", 420)) + eacoin.add_child(Node.s32("service", 0)) + eacoin.add_child(Node.string("itemtype", "0")) + eacoin.add_child(Node.string("detail", "/eacoin/premium_free_1p_3")) self.assertLoopback(root) def test_game_packet3(self) -> None: - root = Node.void('response') - root.add_child(Node.void('music')) + root = Node.void("response") + root.add_child(Node.void("music")) self.assertLoopback(root) def test_game_packet4(self) -> None: - root = Node.void('response') - game = Node.void('game') + root = Node.void("response") + game = Node.void("game") root.add_child(game) - game.set_attribute('image_no', '1') - game.set_attribute('no', '1') + game.set_attribute("image_no", "1") + game.set_attribute("no", "1") - game.add_child(Node.s32('ir_phase', 0)) + game.add_child(Node.s32("ir_phase", 0)) - game.add_child(Node.s32('personal_event_phase', 10)) - game.add_child(Node.s32('shop_event_phase', 6)) - game.add_child(Node.s32('netvs_phase', 0)) - game.add_child(Node.s32('card_phase', 9)) - game.add_child(Node.s32('other_phase', 9)) - game.add_child(Node.s32('music_open_phase', 8)) - game.add_child(Node.s32('collabo_phase', 8)) - game.add_child(Node.s32('local_matching_enable', 1)) - game.add_child(Node.s32('n_maching_sec', 60)) - game.add_child(Node.s32('l_matching_sec', 60)) - game.add_child(Node.s32('is_check_cpu', 0)) - game.add_child(Node.s32('week_no', 0)) - game.add_child(Node.s16_array('sel_ranking', [-1, -1, -1, -1, -1])) - game.add_child(Node.s16_array('up_ranking', [-1, -1, -1, -1, -1])) + game.add_child(Node.s32("personal_event_phase", 10)) + game.add_child(Node.s32("shop_event_phase", 6)) + game.add_child(Node.s32("netvs_phase", 0)) + game.add_child(Node.s32("card_phase", 9)) + game.add_child(Node.s32("other_phase", 9)) + game.add_child(Node.s32("music_open_phase", 8)) + game.add_child(Node.s32("collabo_phase", 8)) + game.add_child(Node.s32("local_matching_enable", 1)) + game.add_child(Node.s32("n_maching_sec", 60)) + game.add_child(Node.s32("l_matching_sec", 60)) + game.add_child(Node.s32("is_check_cpu", 0)) + game.add_child(Node.s32("week_no", 0)) + game.add_child(Node.s16_array("sel_ranking", [-1, -1, -1, -1, -1])) + game.add_child(Node.s16_array("up_ranking", [-1, -1, -1, -1, -1])) self.assertLoopback(root) def test_game_packet5(self) -> None: - root = Node.void('call') - root.set_attribute('model', 'LDJ:A:A:A:2015060700') - root.set_attribute('srcid', '012010000000DEADBEEF') - root.set_attribute('tag', '9yU+HH4q') + root = Node.void("call") + root.set_attribute("model", "LDJ:A:A:A:2015060700") + root.set_attribute("srcid", "012010000000DEADBEEF") + root.set_attribute("tag", "9yU+HH4q") - iidx22pc = Node.void('IIDX22pc') + iidx22pc = Node.void("IIDX22pc") root.add_child(iidx22pc) - iidx22pc.set_attribute('bookkeep', '0') - iidx22pc.set_attribute('cltype', '0') - iidx22pc.set_attribute('d_achi', '0') - iidx22pc.set_attribute('d_disp_judge', '0') - iidx22pc.set_attribute('d_exscore', '0') - iidx22pc.set_attribute('d_gno', '0') - iidx22pc.set_attribute('d_gtype', '0') - iidx22pc.set_attribute('d_hispeed', '0.000000') - iidx22pc.set_attribute('d_judge', '0') - iidx22pc.set_attribute('d_judgeAdj', '-3') - iidx22pc.set_attribute('d_largejudge', '0') - iidx22pc.set_attribute('d_lift', '0') - iidx22pc.set_attribute('d_notes', '0.000000') - iidx22pc.set_attribute('d_opstyle', '0') - iidx22pc.set_attribute('d_pace', '0') - iidx22pc.set_attribute('d_sdlen', '0') - iidx22pc.set_attribute('d_sdtype', '0') - iidx22pc.set_attribute('d_sorttype', '0') - iidx22pc.set_attribute('d_timing', '0') - iidx22pc.set_attribute('d_tune', '0') - iidx22pc.set_attribute('dp_opt', '0') - iidx22pc.set_attribute('dp_opt2', '0') - iidx22pc.set_attribute('gpos', '0') - iidx22pc.set_attribute('iidxid', '56665524') - iidx22pc.set_attribute('lid', 'US-3') - iidx22pc.set_attribute('method', 'save') - iidx22pc.set_attribute('mode', '6') - iidx22pc.set_attribute('pmode', '0') - iidx22pc.set_attribute('rtype', '0') - iidx22pc.set_attribute('s_achi', '4428') - iidx22pc.set_attribute('s_disp_judge', '1') - iidx22pc.set_attribute('s_exscore', '0') - iidx22pc.set_attribute('s_gno', '1') - iidx22pc.set_attribute('s_gtype', '2') - iidx22pc.set_attribute('s_hispeed', '2.302647') - iidx22pc.set_attribute('s_judge', '0') - iidx22pc.set_attribute('s_judgeAdj', '-3') - iidx22pc.set_attribute('s_largejudge', '0') - iidx22pc.set_attribute('s_lift', '60') - iidx22pc.set_attribute('s_notes', '29.483595') - iidx22pc.set_attribute('s_opstyle', '1') - iidx22pc.set_attribute('s_pace', '0') - iidx22pc.set_attribute('s_sdlen', '203') - iidx22pc.set_attribute('s_sdtype', '1') - iidx22pc.set_attribute('s_sorttype', '0') - iidx22pc.set_attribute('s_timing', '2') - iidx22pc.set_attribute('s_tune', '5') - iidx22pc.set_attribute('sp_opt', '8194') + iidx22pc.set_attribute("bookkeep", "0") + iidx22pc.set_attribute("cltype", "0") + iidx22pc.set_attribute("d_achi", "0") + iidx22pc.set_attribute("d_disp_judge", "0") + iidx22pc.set_attribute("d_exscore", "0") + iidx22pc.set_attribute("d_gno", "0") + iidx22pc.set_attribute("d_gtype", "0") + iidx22pc.set_attribute("d_hispeed", "0.000000") + iidx22pc.set_attribute("d_judge", "0") + iidx22pc.set_attribute("d_judgeAdj", "-3") + iidx22pc.set_attribute("d_largejudge", "0") + iidx22pc.set_attribute("d_lift", "0") + iidx22pc.set_attribute("d_notes", "0.000000") + iidx22pc.set_attribute("d_opstyle", "0") + iidx22pc.set_attribute("d_pace", "0") + iidx22pc.set_attribute("d_sdlen", "0") + iidx22pc.set_attribute("d_sdtype", "0") + iidx22pc.set_attribute("d_sorttype", "0") + iidx22pc.set_attribute("d_timing", "0") + iidx22pc.set_attribute("d_tune", "0") + iidx22pc.set_attribute("dp_opt", "0") + iidx22pc.set_attribute("dp_opt2", "0") + iidx22pc.set_attribute("gpos", "0") + iidx22pc.set_attribute("iidxid", "56665524") + iidx22pc.set_attribute("lid", "US-3") + iidx22pc.set_attribute("method", "save") + iidx22pc.set_attribute("mode", "6") + iidx22pc.set_attribute("pmode", "0") + iidx22pc.set_attribute("rtype", "0") + iidx22pc.set_attribute("s_achi", "4428") + iidx22pc.set_attribute("s_disp_judge", "1") + iidx22pc.set_attribute("s_exscore", "0") + iidx22pc.set_attribute("s_gno", "1") + iidx22pc.set_attribute("s_gtype", "2") + iidx22pc.set_attribute("s_hispeed", "2.302647") + iidx22pc.set_attribute("s_judge", "0") + iidx22pc.set_attribute("s_judgeAdj", "-3") + iidx22pc.set_attribute("s_largejudge", "0") + iidx22pc.set_attribute("s_lift", "60") + iidx22pc.set_attribute("s_notes", "29.483595") + iidx22pc.set_attribute("s_opstyle", "1") + iidx22pc.set_attribute("s_pace", "0") + iidx22pc.set_attribute("s_sdlen", "203") + iidx22pc.set_attribute("s_sdtype", "1") + iidx22pc.set_attribute("s_sorttype", "0") + iidx22pc.set_attribute("s_timing", "2") + iidx22pc.set_attribute("s_tune", "5") + iidx22pc.set_attribute("sp_opt", "8194") - pyramid = Node.void('pyramid') - pyramid.set_attribute('point', '408') + pyramid = Node.void("pyramid") + pyramid.set_attribute("point", "408") iidx22pc.add_child(pyramid) - achievements = Node.void('achievements') + achievements = Node.void("achievements") iidx22pc.add_child(achievements) - achievements.set_attribute('last_weekly', '0') - achievements.set_attribute('pack_comp', '0') - achievements.set_attribute('pack_flg', '0') - achievements.set_attribute('pack_id', '349') - achievements.set_attribute('play_pack', '0') - achievements.set_attribute('visit_flg', '1125899906842624') - achievements.set_attribute('weekly_num', '0') + achievements.set_attribute("last_weekly", "0") + achievements.set_attribute("pack_comp", "0") + achievements.set_attribute("pack_flg", "0") + achievements.set_attribute("pack_id", "349") + achievements.set_attribute("play_pack", "0") + achievements.set_attribute("visit_flg", "1125899906842624") + achievements.set_attribute("weekly_num", "0") - achievements.add_child(Node.s64_array('trophy', [ - 648333697107365824, - 120628451491823, - 281475567654912, - 0, - 1069547520, - 0, - 0, - 0, - 0, - 0, - 0, - 1125899906842624, - 4294967296, - 60348585478096, - 1498943592322, - 0, - 256, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - -4294967296, - 0, - 0, - 4294967704, - 858608469, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 5, - 2, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - ])) + achievements.add_child( + Node.s64_array( + "trophy", + [ + 648333697107365824, + 120628451491823, + 281475567654912, + 0, + 1069547520, + 0, + 0, + 0, + 0, + 0, + 0, + 1125899906842624, + 4294967296, + 60348585478096, + 1498943592322, + 0, + 256, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -4294967296, + 0, + 0, + 4294967704, + 858608469, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 5, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + ) + ) - deller = Node.void('deller') + deller = Node.void("deller") iidx22pc.add_child(deller) - deller.set_attribute('deller', '450') + deller.set_attribute("deller", "450") self.assertLoopback(root) def test_game_packet6(self) -> None: - root = Node.void('response') - facility = Node.void('facility') + root = Node.void("response") + facility = Node.void("facility") root.add_child(facility) - location = Node.void('location') + location = Node.void("location") facility.add_child(location) - location.add_child(Node.string('id', 'US-6')) - location.add_child(Node.string('country', 'US')) - location.add_child(Node.string('region', '.')) - location.add_child(Node.string('name', '')) - location.add_child(Node.u8('type', 0)) + location.add_child(Node.string("id", "US-6")) + location.add_child(Node.string("country", "US")) + location.add_child(Node.string("region", ".")) + location.add_child(Node.string("name", "")) + location.add_child(Node.u8("type", 0)) - line = Node.void('line') + line = Node.void("line") facility.add_child(line) - line.add_child(Node.string('id', '.')) - line.add_child(Node.u8('class', 0)) + line.add_child(Node.string("id", ".")) + line.add_child(Node.u8("class", 0)) - portfw = Node.void('portfw') + portfw = Node.void("portfw") facility.add_child(portfw) - portfw.add_child(Node.ipv4('globalip', '10.0.0.1')) - portfw.add_child(Node.u16('globalport', 20000)) - portfw.add_child(Node.u16('privateport', 20000)) + portfw.add_child(Node.ipv4("globalip", "10.0.0.1")) + portfw.add_child(Node.u16("globalport", 20000)) + portfw.add_child(Node.u16("privateport", 20000)) - public = Node.void('public') + public = Node.void("public") facility.add_child(public) - public.add_child(Node.u8('flag', 1)) - public.add_child(Node.string('name', '.')) - public.add_child(Node.string('latitude', '0')) - public.add_child(Node.string('longitude', '0')) + public.add_child(Node.u8("flag", 1)) + public.add_child(Node.string("name", ".")) + public.add_child(Node.string("latitude", "0")) + public.add_child(Node.string("longitude", "0")) - share = Node.void('share') + share = Node.void("share") facility.add_child(share) - eacoin = Node.void('eacoin') + eacoin = Node.void("eacoin") share.add_child(eacoin) - eacoin.add_child(Node.s32('notchamount', 0)) - eacoin.add_child(Node.s32('notchcount', 0)) - eacoin.add_child(Node.s32('supplylimit', 1000000)) + eacoin.add_child(Node.s32("notchamount", 0)) + eacoin.add_child(Node.s32("notchcount", 0)) + eacoin.add_child(Node.s32("supplylimit", 1000000)) - url = Node.void('url') + url = Node.void("url") share.add_child(url) - url.add_child(Node.string('eapass', 'http://some.dummy.net/')) - url.add_child(Node.string('arcadefan', 'http://some.dummy.net/')) - url.add_child(Node.string('konaminetdx', 'http://some.dummy.net/')) - url.add_child(Node.string('konamiid', 'http://some.dummy.net/')) - url.add_child(Node.string('eagate', 'http://some.dummy.net/')) + url.add_child(Node.string("eapass", "http://some.dummy.net/")) + url.add_child(Node.string("arcadefan", "http://some.dummy.net/")) + url.add_child(Node.string("konaminetdx", "http://some.dummy.net/")) + url.add_child(Node.string("konamiid", "http://some.dummy.net/")) + url.add_child(Node.string("eagate", "http://some.dummy.net/")) self.assertLoopback(root) def test_packet1(self) -> None: - root = Node.void('test') - root.set_attribute('test', 'test string value') + root = Node.void("test") + root.set_attribute("test", "test string value") # Regular nodes - root.add_child(Node.void('void_node')) - root.add_child(Node.s8('s8_node', -1)) - root.add_child(Node.u8('u8_node', 245)) - root.add_child(Node.s16('s16_node', -8000)) - root.add_child(Node.u16('u16_node', 65000)) - root.add_child(Node.s32('s32_node', -2000000000)) - root.add_child(Node.u32('u32_node', 4000000000)) - root.add_child(Node.s64('s64_node', -1234567890000)) - root.add_child(Node.u64('u64_node', 1234567890000)) - root.add_child(Node.binary('bin_node', b'DEADBEEF')) - root.add_child(Node.string('str_node', 'this is a string!')) - root.add_child(Node.ipv4('ip4_node', '192.168.1.24')) - root.add_child(Node.time('time_node', 1234567890)) - root.add_child(Node.float('float_node', 2.5)) - root.add_child(Node.fouru8('4u8_node', [0x20, 0x21, 0x22, 0x23])) - root.add_child(Node.bool('bool_true_node', True)) - root.add_child(Node.bool('bool_false_node', False)) + root.add_child(Node.void("void_node")) + root.add_child(Node.s8("s8_node", -1)) + root.add_child(Node.u8("u8_node", 245)) + root.add_child(Node.s16("s16_node", -8000)) + root.add_child(Node.u16("u16_node", 65000)) + root.add_child(Node.s32("s32_node", -2000000000)) + root.add_child(Node.u32("u32_node", 4000000000)) + root.add_child(Node.s64("s64_node", -1234567890000)) + root.add_child(Node.u64("u64_node", 1234567890000)) + root.add_child(Node.binary("bin_node", b"DEADBEEF")) + root.add_child(Node.string("str_node", "this is a string!")) + root.add_child(Node.ipv4("ip4_node", "192.168.1.24")) + root.add_child(Node.time("time_node", 1234567890)) + root.add_child(Node.float("float_node", 2.5)) + root.add_child(Node.fouru8("4u8_node", [0x20, 0x21, 0x22, 0x23])) + root.add_child(Node.bool("bool_true_node", True)) + root.add_child(Node.bool("bool_false_node", False)) # Array nodes - root.add_child(Node.s8_array('s8_array_node', [-1, -2, 3, 4, -5])) - root.add_child(Node.u8_array('u8_array_node', [245, 2, 0, 255, 1])) - root.add_child(Node.s16_array('s16_array_node', [-8000, 8000])) - root.add_child(Node.u16_array('u16_array_node', [65000, 1, 2, 65535])) - root.add_child(Node.s32_array('s32_array_node', [-2000000000, -1])) - root.add_child(Node.u32_array('u32_array_node', [4000000000, 0, 1, 2])) - root.add_child(Node.s64_array('s64_array_node', [-1234567890000, -1, 1, 1337])) - root.add_child(Node.u64_array('u64_array_node', [1234567890000, 123, 456, 7890])) - root.add_child(Node.time_array('time_array_node', [1234567890, 98765432])) - root.add_child(Node.float_array('float_array_node', [2.5, 0.0, 5.0, 20.5])) - root.add_child(Node.bool_array('bool_array_node', [False, True, True, False])) + root.add_child(Node.s8_array("s8_array_node", [-1, -2, 3, 4, -5])) + root.add_child(Node.u8_array("u8_array_node", [245, 2, 0, 255, 1])) + root.add_child(Node.s16_array("s16_array_node", [-8000, 8000])) + root.add_child(Node.u16_array("u16_array_node", [65000, 1, 2, 65535])) + root.add_child(Node.s32_array("s32_array_node", [-2000000000, -1])) + root.add_child(Node.u32_array("u32_array_node", [4000000000, 0, 1, 2])) + root.add_child(Node.s64_array("s64_array_node", [-1234567890000, -1, 1, 1337])) + root.add_child( + Node.u64_array("u64_array_node", [1234567890000, 123, 456, 7890]) + ) + root.add_child(Node.time_array("time_array_node", [1234567890, 98765432])) + root.add_child(Node.float_array("float_array_node", [2.5, 0.0, 5.0, 20.5])) + root.add_child(Node.bool_array("bool_array_node", [False, True, True, False])) # XML escaping - escape = Node.string('escape_test', '\r\n & \'thing\' "thing" \r\nthing on new line\r\n ') - escape.set_attribute('test', ' & \'thing\' "thing" \r\n thing') + escape = Node.string( + "escape_test", + "\r\n & 'thing' \"thing\" \r\nthing on new line\r\n ", + ) + escape.set_attribute("test", " & 'thing' \"thing\" \r\n thing") root.add_child(escape) # Unicode - unicode_node = Node.string('unicode', '今日は') - unicode_node.set_attribute('unicode_attr', 'わたし') + unicode_node = Node.string("unicode", "今日は") + unicode_node.set_attribute("unicode_attr", "わたし") root.add_child(unicode_node) self.assertLoopback(root) diff --git a/bemani/utils/afputils.py b/bemani/utils/afputils.py index 7626fd9..b451289 100644 --- a/bemani/utils/afputils.py +++ b/bemani/utils/afputils.py @@ -10,7 +10,19 @@ import textwrap from PIL import Image, ImageDraw # type: ignore from typing import Any, Dict, List, Optional, Tuple, TypeVar -from bemani.format.afp import TXP2File, Shape, SWF, Frame, Tag, AP2DoActionTag, AP2PlaceObjectTag, AP2DefineSpriteTag, AFPRenderer, Color, Matrix +from bemani.format.afp import ( + TXP2File, + Shape, + SWF, + Frame, + Tag, + AP2DoActionTag, + AP2PlaceObjectTag, + AP2DefineSpriteTag, + AFPRenderer, + Color, + Matrix, +) from bemani.format import IFS @@ -48,12 +60,14 @@ def write_bytecode(swf: SWF, directory: str, *, verbose: bool) -> None: # If we have frame labels, put them at the top as global defines. if lut: buff = [ - os.linesep.join([ - '// Defined frame labels from animation container, as used for frame lookups.', - 'FRAME_LUT = {', - *[f" {name!r}: {frame}," for name, frame in lut.items()], - '};', - ]), + os.linesep.join( + [ + "// Defined frame labels from animation container, as used for frame lookups.", + "FRAME_LUT = {", + *[f" {name!r}: {frame}," for name, frame in lut.items()], + "};", + ] + ), *buff, ] @@ -61,7 +75,7 @@ def write_bytecode(swf: SWF, directory: str, *, verbose: bool) -> None: filename = os.path.join(directory, swf.exported_name) + ".code" print(f"Writing code to {filename}...") with open(filename, "wb") as bfp: - bfp.write(f"{os.linesep}{os.linesep}".join(buff).encode('utf-8')) + bfp.write(f"{os.linesep}{os.linesep}".join(buff).encode("utf-8")) def parse_intlist(data: str) -> List[int]: @@ -69,8 +83,8 @@ def parse_intlist(data: str) -> List[int]: for chunk in data.split(","): chunk = chunk.strip() - if '-' in chunk: - start, end = chunk.split('-', 1) + if "-" in chunk: + start, end = chunk.split("-", 1) start_int = int(start.strip()) end_int = int(end.strip()) ints.extend(range(start_int, end_int + 1)) @@ -84,13 +98,13 @@ def extract_txp2( fname: str, output_dir: str, *, - split_textures: bool=False, - generate_mapping_overlays: bool=False, - write_mappings: bool=False, - write_raw: bool=False, - write_binaries: bool=False, - pretend: bool=False, - verbose: bool=False, + split_textures: bool = False, + generate_mapping_overlays: bool = False, + write_mappings: bool = False, + write_raw: bool = False, + write_binaries: bool = False, + pretend: bool = False, + verbose: bool = False, ) -> int: if split_textures: if write_raw: @@ -114,7 +128,7 @@ def extract_txp2( else: print(f"Writing {filename}.png texture...") with open(f"{filename}.png", "wb") as bfp: - texture.img.save(bfp, format='PNG') + texture.img.save(bfp, format="PNG") if not texture.img or write_raw: if pretend: @@ -129,14 +143,18 @@ def extract_txp2( else: print(f"Writing {filename}.xml texture info...") with open(f"{filename}.xml", "w") as sfp: - sfp.write(textwrap.dedent(f""" + sfp.write( + textwrap.dedent( + f""" {texture.width} {texture.height} {hex(texture.fmt)} {filename}.raw - """).strip()) + """ + ).strip() + ) if write_mappings: if not split_textures: @@ -152,7 +170,9 @@ def extract_txp2( else: print(f"Writing {filename}.xml region information...") with open(f"{filename}.xml", "w") as sfp: - sfp.write(textwrap.dedent(f""" + sfp.write( + textwrap.dedent( + f""" {region.left} {region.top} @@ -160,7 +180,9 @@ def extract_txp2( {region.bottom} {texturename} - """).strip()) + """ + ).strip() + ) if afpfile.fontdata is not None: filename = os.path.join(output_dir, "fontinfo.xml") @@ -212,7 +234,7 @@ def extract_txp2( for texture in afpfile.textures: if texture.name == texturename: overlays[texturename] = Image.new( - 'RGBA', + "RGBA", (texture.width, texture.height), (0, 0, 0, 0), ) @@ -222,7 +244,10 @@ def extract_txp2( draw = ImageDraw.Draw(overlays[texturename]) draw.rectangle( - ((region.left // 2, region.top // 2), (region.right // 2, region.bottom // 2)), + ( + (region.left // 2, region.top // 2), + (region.right // 2, region.bottom // 2), + ), fill=(0, 0, 0, 0), outline=(255, 0, 0, 255), width=1, @@ -240,7 +265,7 @@ def extract_txp2( else: print(f"Writing {filename} overlay...") with open(filename, "wb") as bfp: - img.save(bfp, format='PNG') + img.save(bfp, format="PNG") if split_textures: textures: Dict[str, Any] = {} @@ -270,13 +295,20 @@ def extract_txp2( else: print(f"Writing {filename} sprite...") sprite = textures[texturename].img.crop( - (region.left // 2, region.top // 2, region.right // 2, region.bottom // 2), + ( + region.left // 2, + region.top // 2, + region.right // 2, + region.bottom // 2, + ), ) with open(filename, "wb") as bfp: - sprite.save(bfp, format='PNG') + sprite.save(bfp, format="PNG") else: if not announced.get(texturename, False): - print(f"Cannot extract sprites from {texturename} because it is not a supported format!") + print( + f"Cannot extract sprites from {texturename} because it is not a supported format!" + ) announced[texturename] = True if write_bytecode: for swf in afpfile.swfdata: @@ -285,7 +317,9 @@ def extract_txp2( return 0 -def update_txp2(fname: str, update_dir: str, *, pretend: bool=False, verbose: bool=False) -> int: +def update_txp2( + fname: str, update_dir: str, *, pretend: bool = False, verbose: bool = False +) -> int: # First, parse the file out with open(fname, "rb") as bfp: afpfile = TXP2File(bfp.read(), verbose=verbose) @@ -312,7 +346,9 @@ def update_txp2(fname: str, update_dir: str, *, pretend: bool=False, verbose: bo filename = os.path.join(update_dir, filename) if os.path.isfile(filename): - print(f"Updating {texturename} sprite piece {spritename} from {filename}...") + print( + f"Updating {texturename} sprite piece {spritename} from {filename}..." + ) with open(filename, "rb") as bfp: afpfile.update_sprite(texturename, spritename, bfp.read()) @@ -330,18 +366,28 @@ def update_txp2(fname: str, update_dir: str, *, pretend: bool=False, verbose: bo return 0 -def print_txp2(fname: str, *, decompile_bytecode: bool=False, verbose: bool=False) -> int: +def print_txp2( + fname: str, *, decompile_bytecode: bool = False, verbose: bool = False +) -> int: # First, parse the file out with open(fname, "rb") as bfp: afpfile = TXP2File(bfp.read(), verbose=verbose) # Now, print it - print(json.dumps(afpfile.as_dict(decompile_bytecode=decompile_bytecode, verbose=verbose), sort_keys=True, indent=4)) + print( + json.dumps( + afpfile.as_dict(decompile_bytecode=decompile_bytecode, verbose=verbose), + sort_keys=True, + indent=4, + ) + ) return 0 -def parse_afp(afp: str, bsi: str, *, decompile_bytecode: bool=False, verbose: bool=False) -> int: +def parse_afp( + afp: str, bsi: str, *, decompile_bytecode: bool = False, verbose: bool = False +) -> int: # First, load the AFP and BSI files with open(afp, "rb") as bafp: with open(bsi, "rb") as bbsi: @@ -349,12 +395,18 @@ def parse_afp(afp: str, bsi: str, *, decompile_bytecode: bool=False, verbose: bo # Now, print it swf.parse(verbose=verbose) - print(json.dumps(swf.as_dict(decompile_bytecode=decompile_bytecode, verbose=verbose), sort_keys=True, indent=4)) + print( + json.dumps( + swf.as_dict(decompile_bytecode=decompile_bytecode, verbose=verbose), + sort_keys=True, + indent=4, + ) + ) return 0 -def decompile_afp(afp: str, bsi: str, output_dir: str, *, verbose: bool=False) -> int: +def decompile_afp(afp: str, bsi: str, output_dir: str, *, verbose: bool = False) -> int: # First, load the AFP and BSI files with open(afp, "rb") as bafp: with open(bsi, "rb") as bbsi: @@ -367,7 +419,7 @@ def decompile_afp(afp: str, bsi: str, output_dir: str, *, verbose: bool=False) - return 0 -def parse_geo(geo: str, *, verbose: bool=False) -> int: +def parse_geo(geo: str, *, verbose: bool = False) -> int: # First, load the AFP and BSI files with open(geo, "rb") as bfp: shape = Shape("", bfp.read()) @@ -381,7 +433,9 @@ def parse_geo(geo: str, *, verbose: bool=False) -> int: return 0 -def load_containers(renderer: AFPRenderer, containers: List[str], *, need_extras: bool, verbose: bool) -> None: +def load_containers( + renderer: AFPRenderer, containers: List[str], *, need_extras: bool, verbose: bool +) -> None: # This is a complicated one, as we need to be able to specify multiple # directories of files as well as support IFS files and TXP2 files. for container in containers: @@ -396,7 +450,10 @@ def load_containers(renderer: AFPRenderer, containers: List[str], *, need_extras if afpfile is not None: if verbose: - print(f"Loading files out of TXP2 container {container}...", file=sys.stderr) + print( + f"Loading files out of TXP2 container {container}...", + file=sys.stderr, + ) if need_extras: # First, load GE2D structures into the renderer. @@ -405,7 +462,9 @@ def load_containers(renderer: AFPRenderer, containers: List[str], *, need_extras renderer.add_shape(name, shape) if verbose: - print(f"Added {name} to animation shape library.", file=sys.stderr) + print( + f"Added {name} to animation shape library.", file=sys.stderr + ) # Now, split and load textures into the renderer. sheets: Dict[str, Any] = {} @@ -422,18 +481,30 @@ def load_containers(renderer: AFPRenderer, containers: List[str], *, need_extras sheets[texturename] = tex break else: - raise Exception("Could not find texture {texturename} to split!") + raise Exception( + "Could not find texture {texturename} to split!" + ) if sheets[texturename].img: sprite = sheets[texturename].img.crop( - (region.left // 2, region.top // 2, region.right // 2, region.bottom // 2), + ( + region.left // 2, + region.top // 2, + region.right // 2, + region.bottom // 2, + ), ) renderer.add_texture(name, sprite) if verbose: - print(f"Added {name} to animation texture library.", file=sys.stderr) + print( + f"Added {name} to animation texture library.", + file=sys.stderr, + ) else: - print(f"Cannot load {name} from {texturename} because it is not a supported format!") + print( + f"Cannot load {name} from {texturename} because it is not a supported format!" + ) # Finally, load the animation data itself into the renderer. for i, name in enumerate(afpfile.swfmap.entries): @@ -453,14 +524,17 @@ def load_containers(renderer: AFPRenderer, containers: List[str], *, need_extras if ifsfile is not None: if verbose: - print(f"Loading files out of IFS container {container}...", file=sys.stderr) + print( + f"Loading files out of IFS container {container}...", + file=sys.stderr, + ) for fname in ifsfile.filenames: if fname.startswith(f"geo{os.sep}"): if not need_extras: continue # Trim off directory. - shapename = fname[(3 + len(os.sep)):] + shapename = fname[(3 + len(os.sep)) :] # Load file, register it. fdata = ifsfile.read_file(fname) @@ -468,13 +542,16 @@ def load_containers(renderer: AFPRenderer, containers: List[str], *, need_extras renderer.add_shape(shapename, shape) if verbose: - print(f"Added {shapename} to animation shape library.", file=sys.stderr) + print( + f"Added {shapename} to animation shape library.", + file=sys.stderr, + ) elif fname.startswith(f"tex{os.sep}") and fname.endswith(".png"): if not need_extras: continue # Trim off directory, png extension. - texname = fname[(3 + len(os.sep)):][:-4] + texname = fname[(3 + len(os.sep)) :][:-4] # Load file, register it. fdata = ifsfile.read_file(fname) @@ -482,10 +559,13 @@ def load_containers(renderer: AFPRenderer, containers: List[str], *, need_extras renderer.add_texture(texname, tex) if verbose: - print(f"Added {texname} to animation texture library.", file=sys.stderr) + print( + f"Added {texname} to animation texture library.", + file=sys.stderr, + ) elif fname.startswith(f"afp{os.sep}"): # Trim off directory, see if it has a corresponding bsi. - afpname = fname[(3 + len(os.sep)):] + afpname = fname[(3 + len(os.sep)) :] bsipath = f"afp{os.sep}bsi{os.sep}{afpname}" if bsipath in ifsfile.filenames: @@ -495,11 +575,20 @@ def load_containers(renderer: AFPRenderer, containers: List[str], *, need_extras renderer.add_swf(afpname, flash) if verbose: - print(f"Added {afpname} to animation library.", file=sys.stderr) + print( + f"Added {afpname} to animation library.", + file=sys.stderr, + ) continue -def list_paths(containers: List[str], *, include_frames: bool=False, include_size: bool=False, verbose: bool=False) -> int: +def list_paths( + containers: List[str], + *, + include_frames: bool = False, + include_size: bool = False, + verbose: bool = False, +) -> int: renderer = AFPRenderer() load_containers(renderer, containers, need_extras=False, verbose=verbose) @@ -534,23 +623,31 @@ def adjust_background_loop( if background_loop_offset is None: background_loop_offset = 0 else: - background_loop_offset -= (background_loop_start + 1) + background_loop_offset -= background_loop_start + 1 # Don't one-index the end because we want it to be inclusive. if background_loop_end is None: background_loop_end = len(background) if background_loop_start >= background_loop_end: - raise Exception("Cannot start background loop after the end of the background loop!") + raise Exception( + "Cannot start background loop after the end of the background loop!" + ) if background_loop_start < 0 or background_loop_end < 0: raise Exception("Cannot start or end background loop on a negative frame!") - if background_loop_start >= len(background) or background_loop_end > len(background): - raise Exception("Cannot start or end background loop larger than the number of background animation frames!") + if background_loop_start >= len(background) or background_loop_end > len( + background + ): + raise Exception( + "Cannot start or end background loop larger than the number of background animation frames!" + ) background = background[background_loop_start:background_loop_end] if background_loop_offset < 0 or background_loop_offset >= len(background): - raise Exception("Cannot start first iteration of background loop outside the loop bounds!") + raise Exception( + "Cannot start first iteration of background loop outside the loop bounds!" + ) return background[background_loop_offset:] + background[:background_loop_offset] @@ -582,7 +679,9 @@ def render_path( if show_progress: print("Loading textures, shapes and animation instructions...") - renderer = AFPRenderer(single_threaded=disable_threads, enable_aa=enable_anti_aliasing) + renderer = AFPRenderer( + single_threaded=disable_threads, enable_aa=enable_anti_aliasing + ) load_containers(renderer, containers, need_extras=True, verbose=verbose) if show_progress: @@ -602,7 +701,9 @@ def render_path( if background_color: colorvals = background_color.split(",") if len(colorvals) not in [3, 4]: - raise Exception("Invalid color, specify a color as a comma-separated RGB or RGBA value!") + raise Exception( + "Invalid color, specify a color as a comma-separated RGB or RGBA value!" + ) if len(colorvals) == 3: colorvals.append("255") @@ -642,17 +743,19 @@ def render_path( startof, endof = os.path.splitext(fileof) if len(startof) == 0 or len(endof) == 0: raise Exception("Invalid image specified as background!") - startof = startof + '-' + startof = startof + "-" # Gather up the sequence of files so we can make frames out of them. seqdict: Dict[int, str] = {} for filename in os.listdir(dirof): if filename.startswith(startof) and filename.endswith(endof): - seqno = filename[len(startof):(-len(endof))] + seqno = filename[len(startof) : (-len(endof))] if seqno.isdigit(): seqint = int(seqno) if seqint in seqdict: - raise Exception(f"{filename} specifies the same background frame number as {seqdict[seqint]}!") + raise Exception( + f"{filename} specifies the same background frame number as {seqdict[seqint]}!" + ) seqdict[seqint] = filename # Now, order the sequence by the integer of the sequence number so we can load the images. @@ -662,7 +765,9 @@ def render_path( ) # Finally, get the filenames from this sequence. - filenames: List[str] = [os.path.join(dirof, filename) for (_, filename) in seqtuple] + filenames: List[str] = [ + os.path.join(dirof, filename) for (_, filename) in seqtuple + ] # Now that we have the list, lets load the images! for filename in filenames: @@ -682,7 +787,12 @@ def render_path( raise Exception("Invalid image specified as background!") if background: - background = adjust_background_loop(background, background_loop_start, background_loop_end, background_loop_offset) + background = adjust_background_loop( + background, + background_loop_start, + background_loop_end, + background_loop_offset, + ) else: raise Exception("Did not find any background images to load!") else: @@ -705,7 +815,9 @@ def render_path( if force_aspect_ratio: ratio = force_aspect_ratio.split(":") if len(ratio) != 2: - raise Exception("Invalid aspect ratio, specify a ratio such as 16:9 or 4:3!") + raise Exception( + "Invalid aspect ratio, specify a ratio such as 16:9 or 4:3!" + ) rx, ry = [float(r.strip()) for r in ratio] if rx <= 0 or ry <= 0: @@ -775,7 +887,9 @@ def render_path( ) ): if show_progress: - frameno = requested_frames[i] if requested_frames is not None else (i + 1) + frameno = ( + requested_frames[i] if requested_frames is not None else (i + 1) + ) print(f"Rendered animation frame {frameno}/{frames}.") images.append(img) @@ -788,7 +902,14 @@ def render_path( pass with open(output, "wb") as bfp: - images[0].save(bfp, format=fmt, save_all=True, append_images=images[1:], duration=duration, optimize=True) + images[0].save( + bfp, + format=fmt, + save_all=True, + append_images=images[1:], + duration=duration, + optimize=True, + ) print(f"Wrote animation to {output}") else: @@ -812,7 +933,9 @@ def render_path( movie_transform=transform, ) ): - frameno = requested_frames[i] if requested_frames is not None else (i + 1) + frameno = ( + requested_frames[i] if requested_frames is not None else (i + 1) + ) fullname = f"{filename}-{frameno:{digits}}{ext}" try: @@ -831,12 +954,14 @@ def render_path( def main() -> int: - parser = argparse.ArgumentParser(description="Konami AFP graphic file unpacker/repacker.") - subparsers = parser.add_subparsers(help='Action to take', dest='action') + parser = argparse.ArgumentParser( + description="Konami AFP graphic file unpacker/repacker." + ) + subparsers = parser.add_subparsers(help="Action to take", dest="action") extract_parser = subparsers.add_parser( - 'extract', - help='Extract relevant file data and textures from a TXP2 container', + "extract", + help="Extract relevant file data and textures from a TXP2 container", description="Extract textures, sprites, decompiled bytecode, AFP, BSI and GEO files from a TXP2 container.", ) extract_parser.add_argument( @@ -899,8 +1024,8 @@ def main() -> int: ) update_parser = subparsers.add_parser( - 'update', - help='Update relevant textures in a TXP2 container from a directory', + "update", + help="Update relevant textures in a TXP2 container from a directory", description="Update textures and sprites in a TXP2 container based on images in a directory.", ) update_parser.add_argument( @@ -927,9 +1052,9 @@ def main() -> int: ) print_parser = subparsers.add_parser( - 'print', - help='Print a TXP2 container\'s contents as a JSON dictionary', - description='Print a TXP2 container\'s contents as a JSON dictionary.', + "print", + help="Print a TXP2 container's contents as a JSON dictionary", + description="Print a TXP2 container's contents as a JSON dictionary.", ) print_parser.add_argument( "file", @@ -950,9 +1075,9 @@ def main() -> int: ) parseafp_parser = subparsers.add_parser( - 'parseafp', - help='Parse a raw AFP/BSI file pair previously extracted from an IFS or TXP2 container', - description='Parse a raw AFP/BSI file pair previously extracted from an IFS or TXP2 container.', + "parseafp", + help="Parse a raw AFP/BSI file pair previously extracted from an IFS or TXP2 container", + description="Parse a raw AFP/BSI file pair previously extracted from an IFS or TXP2 container.", ) parseafp_parser.add_argument( "afp", @@ -978,9 +1103,9 @@ def main() -> int: ) decompile_parser = subparsers.add_parser( - 'decompile', - help='Decompile bytecode in a raw AFP/BSI file pair previously extracted from an IFS or TXP2 container', - description='Decompile bytecode in a raw AFP/BSI file pair previously extracted from an IFS or TXP2 container.', + "decompile", + help="Decompile bytecode in a raw AFP/BSI file pair previously extracted from an IFS or TXP2 container", + description="Decompile bytecode in a raw AFP/BSI file pair previously extracted from an IFS or TXP2 container.", ) decompile_parser.add_argument( "afp", @@ -1002,15 +1127,15 @@ def main() -> int: "-d", "--directory", metavar="DIR", - default='.', + default=".", type=str, help="Directory to write decompiled pseudocode files. Defaults to current directory.", ) parsegeo_parser = subparsers.add_parser( - 'parsegeo', - help='Parse a raw GEO file previously extracted from an IFS or TXP2 container', - description='Parse a raw GEO file previously extracted from an IFS or TXP2 container.', + "parsegeo", + help="Parse a raw GEO file previously extracted from an IFS or TXP2 container", + description="Parse a raw GEO file previously extracted from an IFS or TXP2 container.", ) parsegeo_parser.add_argument( "geo", @@ -1025,15 +1150,15 @@ def main() -> int: ) render_parser = subparsers.add_parser( - 'render', - help='Render a particular animation out of a collection of TXP2 or IFS containers', - description='Render a particular animation out of a collection of TXP2 or IFS containers.', + "render", + help="Render a particular animation out of a collection of TXP2 or IFS containers", + description="Render a particular animation out of a collection of TXP2 or IFS containers.", ) render_parser.add_argument( "container", metavar="CONTAINER", type=str, - nargs='+', + nargs="+", help="A container to use for loading animation data. Can be either a TXP2 or IFS container.", ) render_parser.add_argument( @@ -1066,10 +1191,10 @@ def main() -> int: type=str, default="out.gif", help=( - 'The output file (ending either in .gif, .webp or .png) where the render should be saved. If .png is chosen then the ' - 'output will be a series of png files for each rendered frame. If .gif or .webp is chosen the output will be an ' - 'animated image. Note that the .gif file format has several severe limitations which result in sub-optimal animations ' - 'so it is recommended to use .webp or .png instead.' + "The output file (ending either in .gif, .webp or .png) where the render should be saved. If .png is chosen then the " + "output will be a series of png files for each rendered frame. If .gif or .webp is chosen the output will be an " + "animated image. Note that the .gif file format has several severe limitations which result in sub-optimal animations " + "so it is recommended to use .webp or .png instead." ), ) render_parser.add_argument( @@ -1211,15 +1336,15 @@ def main() -> int: ) list_parser = subparsers.add_parser( - 'list', - help='List out the possible paths to render from a collection of TXP2 or IFS containers', - description='List out the possible paths to render from a collection of TXP2 or IFS containers.', + "list", + help="List out the possible paths to render from a collection of TXP2 or IFS containers", + description="List out the possible paths to render from a collection of TXP2 or IFS containers.", ) list_parser.add_argument( "container", metavar="CONTAINER", type=str, - nargs='+', + nargs="+", help="A container to use for loading animation data. Can be either a TXP2 or IFS container.", ) list_parser.add_argument( @@ -1254,17 +1379,31 @@ def main() -> int: verbose=args.verbose, ) elif args.action == "update": - return update_txp2(args.file, args.dir, pretend=args.pretend, verbose=args.verbose) + return update_txp2( + args.file, args.dir, pretend=args.pretend, verbose=args.verbose + ) elif args.action == "print": - return print_txp2(args.file, decompile_bytecode=args.decompile_bytecode, verbose=args.verbose) + return print_txp2( + args.file, decompile_bytecode=args.decompile_bytecode, verbose=args.verbose + ) elif args.action == "parseafp": - return parse_afp(args.afp, args.bsi, decompile_bytecode=args.decompile_bytecode, verbose=args.verbose) + return parse_afp( + args.afp, + args.bsi, + decompile_bytecode=args.decompile_bytecode, + verbose=args.verbose, + ) elif args.action == "decompile": return decompile_afp(args.afp, args.bsi, args.directory, verbose=args.verbose) elif args.action == "parsegeo": return parse_geo(args.geo, verbose=args.verbose) elif args.action == "list": - return list_paths(args.container, include_size=args.include_size, include_frames=args.include_frames, verbose=args.verbose) + return list_paths( + args.container, + include_size=args.include_size, + include_frames=args.include_frames, + verbose=args.verbose, + ) elif args.action == "render": return render_path( args.container, diff --git a/bemani/utils/api.py b/bemani/utils/api.py index 2d6c523..dfaaf81 100644 --- a/bemani/utils/api.py +++ b/bemani/utils/api.py @@ -10,25 +10,46 @@ def load_config(filename: str) -> None: def main() -> None: - parser = argparse.ArgumentParser(description="An API services provider for eAmusement games, conforming to BEMAPI specs.") - parser.add_argument("-p", "--port", help="Port to listen on. Defaults to 80", type=int, default=80) - parser.add_argument("-c", "--config", help="Core configuration. Defaults to server.yaml", type=str, default="server.yaml") - parser.add_argument("-r", "--profile", help="Turn on profiling for API, writing CProfile data to the currenct directory", action="store_true") - parser.add_argument("-o", "--read-only", action="store_true", help="Force the database into read-only mode.") + parser = argparse.ArgumentParser( + description="An API services provider for eAmusement games, conforming to BEMAPI specs." + ) + parser.add_argument( + "-p", "--port", help="Port to listen on. Defaults to 80", type=int, default=80 + ) + parser.add_argument( + "-c", + "--config", + help="Core configuration. Defaults to server.yaml", + type=str, + default="server.yaml", + ) + parser.add_argument( + "-r", + "--profile", + help="Turn on profiling for API, writing CProfile data to the currenct directory", + action="store_true", + ) + parser.add_argument( + "-o", + "--read-only", + action="store_true", + help="Force the database into read-only mode.", + ) args = parser.parse_args() # Set up app load_config(args.config) if args.read_only: - config['database']['read_only'] = True + config["database"]["read_only"] = True if args.profile: from werkzeug.contrib.profiler import ProfilerMiddleware - app.wsgi_app = ProfilerMiddleware(app.wsgi_app, profile_dir='.') # type: ignore + + app.wsgi_app = ProfilerMiddleware(app.wsgi_app, profile_dir=".") # type: ignore # Run the app - app.run(host='0.0.0.0', port=args.port, debug=True) + app.run(host="0.0.0.0", port=args.port, debug=True) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/bemani/utils/arcutils.py b/bemani/utils/arcutils.py index b740ac7..684be6e 100644 --- a/bemani/utils/arcutils.py +++ b/bemani/utils/arcutils.py @@ -15,7 +15,7 @@ def main() -> None: "-d", "--directory", help="Directory to extract to. Defaults to current directory.", - default="." + default=".", ) parser.add_argument( "-l", @@ -26,11 +26,11 @@ def main() -> None: args = parser.parse_args() root = args.directory - if root[-1] != '/': - root = root + '/' + if root[-1] != "/": + root = root + "/" root = os.path.realpath(root) - rfp = open(args.file, 'rb') + rfp = open(args.file, "rb") data = rfp.read() rfp.close() @@ -39,13 +39,13 @@ def main() -> None: if args.list_only: print(fn) else: - print(f'Extracting {fn} to disk...') + print(f"Extracting {fn} to disk...") realfn = os.path.join(root, fn) dirof = os.path.dirname(realfn) os.makedirs(dirof, exist_ok=True) - with open(realfn, 'wb') as wfp: + with open(realfn, "wb") as wfp: wfp.write(arc.read_file(fn)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/bemani/utils/bemanishark.py b/bemani/utils/bemanishark.py index 35bcb87..8d0b7ef 100644 --- a/bemani/utils/bemanishark.py +++ b/bemani/utils/bemanishark.py @@ -6,7 +6,9 @@ from bemani.protocol import EAmuseProtocol, EAmuseException from bemani.common import HTTP -def mainloop(address: Optional[str]=None, port: int=80, verbose: bool=False) -> None: +def mainloop( + address: Optional[str] = None, port: int = 80, verbose: bool = False +) -> None: """ Main loop of BEMANIShark. Starts an instance of Sniffer and EAmuseProtocol and does a lazy job of banging them together with the above HTTP.parse. Will loop trying to decode @@ -22,49 +24,57 @@ def mainloop(address: Optional[str]=None, port: int=80, verbose: bool=False) -> while True: packets = sniffer.recv_stream() - inbound = HTTP.parse(packets['inbound'], request=True) - outbound = HTTP.parse(packets['outbound'], response=True) + inbound = HTTP.parse(packets["inbound"], request=True) + outbound = HTTP.parse(packets["outbound"], response=True) if inbound is not None: - if inbound['data'] is None: + if inbound["data"] is None: in_req = None else: try: in_req = parser.decode( - inbound['headers'].get('x-compress'), - inbound['headers'].get('x-eamuse-info'), - inbound['data'] + inbound["headers"].get("x-compress"), + inbound["headers"].get("x-eamuse-info"), + inbound["data"], ) except EAmuseException: in_req = None - print(f"Inbound request (from {packets['source_address']}:{packets['source_port']} to {packets['destination_address']}:{packets['destination_port']}):") + print( + f"Inbound request (from {packets['source_address']}:{packets['source_port']} to {packets['destination_address']}:{packets['destination_port']}):" + ) if verbose: print(f"HTTP {inbound['method']} request for URI {inbound['uri']}") print(f"Compression is {inbound['headers'].get('x-compress', 'none')}") - print(f"Encryption key is {inbound['headers'].get('x-eamuse-info', 'none')}") + print( + f"Encryption key is {inbound['headers'].get('x-eamuse-info', 'none')}" + ) if in_req is None: print("Inbound request was not parseable") else: print(in_req) if outbound is not None: - if outbound['data'] is None: + if outbound["data"] is None: out_req = None else: try: out_req = parser.decode( - outbound['headers'].get('x-compress'), - outbound['headers'].get('x-eamuse-info'), - outbound['data'] + outbound["headers"].get("x-compress"), + outbound["headers"].get("x-eamuse-info"), + outbound["data"], ) except EAmuseException: out_req = None - print(f"Outbound response (from {packets['destination_address']}:{packets['destination_port']} to {packets['source_address']}:{packets['source_port']}):") + print( + f"Outbound response (from {packets['destination_address']}:{packets['destination_port']} to {packets['source_address']}:{packets['source_port']}):" + ) if verbose: print(f"Compression is {outbound['headers'].get('x-compress', 'none')}") - print(f"Encryption key is {outbound['headers'].get('x-eamuse-info', 'none')}") + print( + f"Encryption key is {outbound['headers'].get('x-eamuse-info', 'none')}" + ) if out_req is None: print("Outbound response was not parseable") else: @@ -72,14 +82,26 @@ def mainloop(address: Optional[str]=None, port: int=80, verbose: bool=False) -> def main() -> None: - parser = argparse.ArgumentParser(description="A utility to sniff packets and decode them as eAmusement packets. Should probably be run as root.") - parser.add_argument("-p", "--port", help="Port to sniff on. Defaults to 80", type=int, default=80) - parser.add_argument("-a", "--address", help="Address to sniff on. Defaults to all addresses", type=str, default=None) - parser.add_argument("-v", "--verbose", help="Show extra packet information", action='store_true') + parser = argparse.ArgumentParser( + description="A utility to sniff packets and decode them as eAmusement packets. Should probably be run as root." + ) + parser.add_argument( + "-p", "--port", help="Port to sniff on. Defaults to 80", type=int, default=80 + ) + parser.add_argument( + "-a", + "--address", + help="Address to sniff on. Defaults to all addresses", + type=str, + default=None, + ) + parser.add_argument( + "-v", "--verbose", help="Show extra packet information", action="store_true" + ) args = parser.parse_args() mainloop(address=args.address, port=args.port, verbose=args.verbose) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/bemani/utils/binutils.py b/bemani/utils/binutils.py index 3d61c77..c41e22d 100644 --- a/bemani/utils/binutils.py +++ b/bemani/utils/binutils.py @@ -5,11 +5,20 @@ from bemani.protocol.binary import BinaryEncoding def main() -> None: - parser = argparse.ArgumentParser(description="A utility to convert binxml files to their XML representation.") - parser.add_argument("-i", "--infile", help="File containing an XML or binary node structure. Use - for stdin.", type=str, default=None, required=True) + parser = argparse.ArgumentParser( + description="A utility to convert binxml files to their XML representation." + ) + parser.add_argument( + "-i", + "--infile", + help="File containing an XML or binary node structure. Use - for stdin.", + type=str, + default=None, + required=True, + ) args = parser.parse_args() - if args.infile == '-': + if args.infile == "-": # Load from stdin packet = sys.stdin.buffer.read() else: @@ -22,5 +31,5 @@ def main() -> None: print(filexml) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/bemani/utils/cardconvert.py b/bemani/utils/cardconvert.py index c30d392..0a1ec51 100644 --- a/bemani/utils/cardconvert.py +++ b/bemani/utils/cardconvert.py @@ -4,7 +4,9 @@ from bemani.common import CardCipher, CardCipherException def main() -> None: - parser = argparse.ArgumentParser(description="A utility to convert between card IDs and back-of-card characters.") + parser = argparse.ArgumentParser( + description="A utility to convert between card IDs and back-of-card characters." + ) parser.add_argument( "number", help="card ID or back-of-card characters to convert.", @@ -17,12 +19,10 @@ def main() -> None: except CardCipherException: try: back = CardCipher.encode(args.number) - print(" ".join([ - back[i:(i + 4)] for i in range(0, len(back), 4) - ])) + print(" ".join([back[i : (i + 4)] for i in range(0, len(back), 4)])) except CardCipherException: - print('Bad card ID or back-of-card characters!') + print("Bad card ID or back-of-card characters!") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/bemani/utils/config.py b/bemani/utils/config.py index 5dc4d4c..f092168 100644 --- a/bemani/utils/config.py +++ b/bemani/utils/config.py @@ -16,14 +16,14 @@ from bemani.data import Config, Data def load_config(filename: str, config: Config) -> None: config.update(yaml.safe_load(open(filename))) - config['database']['engine'] = Data.create_engine(config) - config['filename'] = filename + config["database"]["engine"] = Data.create_engine(config) + config["filename"] = filename supported_series: Set[GameConstants] = set() for series in GameConstants: - if config.get('support', {}).get(series.value, False): + if config.get("support", {}).get(series.value, False): supported_series.add(series) - config['support'] = supported_series + config["support"] = supported_series def register_games(config: Config) -> None: diff --git a/bemani/utils/dbutils.py b/bemani/utils/dbutils.py index 56b4718..66849d6 100644 --- a/bemani/utils/dbutils.py +++ b/bemani/utils/dbutils.py @@ -15,7 +15,7 @@ def create(config: Config) -> None: def generate(config: Config, message: Optional[str], allow_empty: bool) -> None: if message is None: - raise Exception('Please provide a message!') + raise Exception("Please provide a message!") data = Data(config) data.generate(message, allow_empty) data.close() @@ -29,47 +29,49 @@ def upgrade(config: Config) -> None: def change_password(config: Config, username: Optional[str]) -> None: if username is None: - raise Exception('Please provide a username!') - password1 = getpass.getpass('Password: ') - password2 = getpass.getpass('Re-enter password: ') + raise Exception("Please provide a username!") + password1 = getpass.getpass("Password: ") + password2 = getpass.getpass("Re-enter password: ") if password1 != password2: - raise Exception('Passwords don\'t match!') + raise Exception("Passwords don't match!") data = Data(config) userid = data.local.user.from_username(username) if userid is None: - raise Exception('User not found!') + raise Exception("User not found!") data.local.user.update_password(userid, password1) - print(f'User {username} changed password.') + print(f"User {username} changed password.") def add_admin(config: Config, username: Optional[str]) -> None: if username is None: - raise Exception('Please provide a username!') + raise Exception("Please provide a username!") data = Data(config) userid = data.local.user.from_username(username) if userid is None: - raise Exception('User not found!') + raise Exception("User not found!") user = data.local.user.get_user(userid) user.admin = True data.local.user.put_user(user) - print(f'User {username} gained admin rights.') + print(f"User {username} gained admin rights.") def remove_admin(config: Config, username: Optional[str]) -> None: if username is None: - raise Exception('Please provide a username!') + raise Exception("Please provide a username!") data = Data(config) userid = data.local.user.from_username(username) if userid is None: - raise Exception('User not found!') + raise Exception("User not found!") user = data.local.user.get_user(userid) user.admin = False data.local.user.put_user(user) - print(f'User {username} lost admin rights.') + print(f"User {username} lost admin rights.") def main() -> None: - parser = argparse.ArgumentParser(description="A utility for working with databases created with this codebase.") + parser = argparse.ArgumentParser( + description="A utility for working with databases created with this codebase." + ) parser.add_argument( "operation", help="Operation to perform, options include 'create', 'generate', 'upgrade', 'change-password', 'add-admin' and 'remove-admin'.", @@ -91,9 +93,15 @@ def main() -> None: "-e", "--allow-empty", help="Allow empty migration script to be generated. Useful for data-only migrations.", - action='store_true', + action="store_true", + ) + parser.add_argument( + "-c", + "--config", + help="Core configuration. Defaults to server.yaml", + type=str, + default="server.yaml", ) - parser.add_argument("-c", "--config", help="Core configuration. Defaults to server.yaml", type=str, default="server.yaml") args = parser.parse_args() config = Config() @@ -110,7 +118,7 @@ def main() -> None: add_admin(config, args.username) elif args.operation == "remove-admin": remove_admin(config, args.username) - elif args.operation == 'change-password': + elif args.operation == "change-password": change_password(config, args.username) else: raise Exception(f"Unknown operation '{args.operation}'") @@ -119,5 +127,5 @@ def main() -> None: sys.exit(1) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/bemani/utils/frontend.py b/bemani/utils/frontend.py index 9e2c058..429cbea 100644 --- a/bemani/utils/frontend.py +++ b/bemani/utils/frontend.py @@ -15,7 +15,10 @@ from bemani.frontend.ddr import ddr_pages from bemani.frontend.sdvx import sdvx_pages from bemani.frontend.reflec import reflec_pages from bemani.frontend.museca import museca_pages -from bemani.utils.config import load_config as base_load_config, register_games as base_register_games +from bemani.utils.config import ( + load_config as base_load_config, + register_games as base_register_games, +) def register_blueprints() -> None: @@ -58,17 +61,37 @@ def load_config(filename: str) -> None: def main() -> None: - parser = argparse.ArgumentParser(description="A front end services provider for eAmusement games.") - parser.add_argument("-p", "--port", help="Port to listen on. Defaults to 80", type=int, default=80) - parser.add_argument("-c", "--config", help="Core configuration. Defaults to server.yaml", type=str, default="server.yaml") - parser.add_argument("-r", "--profile", help="Turn on profiling for front end, writing CProfile data to the currenct directory", action="store_true") - parser.add_argument("-o", "--read-only", action="store_true", help="Force the database into read-only mode.") + parser = argparse.ArgumentParser( + description="A front end services provider for eAmusement games." + ) + parser.add_argument( + "-p", "--port", help="Port to listen on. Defaults to 80", type=int, default=80 + ) + parser.add_argument( + "-c", + "--config", + help="Core configuration. Defaults to server.yaml", + type=str, + default="server.yaml", + ) + parser.add_argument( + "-r", + "--profile", + help="Turn on profiling for front end, writing CProfile data to the currenct directory", + action="store_true", + ) + parser.add_argument( + "-o", + "--read-only", + action="store_true", + help="Force the database into read-only mode.", + ) args = parser.parse_args() # Set up app load_config(args.config) if args.read_only: - config['database']['read_only'] = True + config["database"]["read_only"] = True # Register all blueprints register_blueprints() @@ -78,11 +101,12 @@ def main() -> None: if args.profile: from werkzeug.contrib.profiler import ProfilerMiddleware - app.wsgi_app = ProfilerMiddleware(app.wsgi_app, profile_dir='.') # type: ignore + + app.wsgi_app = ProfilerMiddleware(app.wsgi_app, profile_dir=".") # type: ignore # Run the app - app.run(host='0.0.0.0', port=args.port, debug=True) + app.run(host="0.0.0.0", port=args.port, debug=True) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/bemani/utils/ifsutils.py b/bemani/utils/ifsutils.py index a860562..6775967 100644 --- a/bemani/utils/ifsutils.py +++ b/bemani/utils/ifsutils.py @@ -17,7 +17,7 @@ def main() -> None: "-d", "--directory", help="Directory to extract to. Defaults to current directroy.", - default="." + default=".", ) parser.add_argument( "--convert-xml-files", @@ -32,16 +32,16 @@ def main() -> None: args = parser.parse_args() root = args.directory - if root[-1] != '/': - root = root + '/' + if root[-1] != "/": + root = root + "/" root = os.path.realpath(root) fileroot = os.path.dirname(os.path.realpath(args.file)) - def load_ifs(fname: str, root: bool=False) -> Optional[IFS]: + def load_ifs(fname: str, root: bool = False) -> Optional[IFS]: fname = os.path.join(fileroot, fname) if os.path.isfile(fname): - fp = open(fname, 'rb') + fp = open(fname, "rb") data = fp.read() fp.close() @@ -60,13 +60,13 @@ def main() -> None: raise Exception(f"Couldn't locate file {args.file}!") for fn in ifs.filenames: - print(f'Extracting {fn} to disk...') + print(f"Extracting {fn} to disk...") realfn = os.path.join(root, fn) dirof = os.path.dirname(realfn) os.makedirs(dirof, exist_ok=True) - with open(realfn, 'wb') as fp: + with open(realfn, "wb") as fp: fp.write(ifs.read_file(fn)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/bemani/utils/iidxutils.py b/bemani/utils/iidxutils.py index 9b3fc8e..ae9a78c 100644 --- a/bemani/utils/iidxutils.py +++ b/bemani/utils/iidxutils.py @@ -4,7 +4,9 @@ from bemani.format import IIDXMusicDB def main() -> None: - parser = argparse.ArgumentParser(description="A utility to patch a IIDX music database.") + parser = argparse.ArgumentParser( + description="A utility to patch a IIDX music database." + ) parser.add_argument( "infile", help="Music DB to work with.", @@ -22,26 +24,26 @@ def main() -> None: ) args = parser.parse_args() - rfp = open(args.infile, 'rb') + rfp = open(args.infile, "rb") data = rfp.read() rfp.close() db = IIDXMusicDB(data) if args.hide_leggendarias: for song in db.songs: - if song.title[-1:] == '†' or ( - song.difficulties[0] == 0 and - song.difficulties[1] == 0 and - song.difficulties[2] == 12 + if song.title[-1:] == "†" or ( + song.difficulties[0] == 0 + and song.difficulties[1] == 0 + and song.difficulties[2] == 12 ): - print(f'Patching \'{song.title}\' to only appear in leggendaria folder!') + print(f"Patching '{song.title}' to only appear in leggendaria folder!") song.folder = 0x5C - print('Generating new database file...') - wfp = open(args.outfile, 'wb') + print("Generating new database file...") + wfp = open(args.outfile, "wb") wfp.write(db.get_new_db()) wfp.close() -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/bemani/utils/jsx.py b/bemani/utils/jsx.py index ded6d13..b85679f 100644 --- a/bemani/utils/jsx.py +++ b/bemani/utils/jsx.py @@ -9,8 +9,16 @@ SCRIPT_PATH: str = os.path.dirname(os.path.realpath(__file__)) def main() -> None: - parser = argparse.ArgumentParser(description="Prebuild all JSX files so they can be statically served by nginx or similar.") - parser.add_argument("-d", "--output-directory", help="Output directory for the JSX files.", type=str, default="./build/jsx") + parser = argparse.ArgumentParser( + description="Prebuild all JSX files so they can be statically served by nginx or similar." + ) + parser.add_argument( + "-d", + "--output-directory", + help="Output directory for the JSX files.", + type=str, + default="./build/jsx", + ) args = parser.parse_args() outdir = os.path.abspath(args.output_directory) @@ -30,7 +38,7 @@ def main() -> None: if fname.endswith(".react.js"): fullpath = os.path.join(dirpath, fname) if fullpath.startswith(basedir): - files.append(fullpath[len(basedir):]) + files.append(fullpath[len(basedir) :]) for dname in dnames: if dname == "." or dname == "..": continue @@ -43,11 +51,13 @@ def main() -> None: outfile = os.path.join(outdir, fname) os.makedirs(os.path.dirname(outfile), exist_ok=True) - with open(infile, 'rb') as f: - jsx = transformer.transform_string(polyfill_fragments(f.read().decode('utf-8'))).encode('utf-8') + with open(infile, "rb") as f: + jsx = transformer.transform_string( + polyfill_fragments(f.read().decode("utf-8")) + ).encode("utf-8") print(f"Writing {outfile}...") - with open(outfile, 'wb') as f: + with open(outfile, "wb") as f: f.write(jsx) diff --git a/bemani/utils/proxy.py b/bemani/utils/proxy.py index 5aac21a..17ea930 100644 --- a/bemani/utils/proxy.py +++ b/bemani/utils/proxy.py @@ -20,7 +20,7 @@ def modify_request(config: Dict[str, Any], req_body: Node) -> Optional[Node]: def modify_response(config: Dict[str, Any], resp_body: Node) -> Optional[Node]: # Figure out if we need to modify anything in the response - if resp_body.name != 'response': + if resp_body.name != "response": # Not what we expected, bail return None @@ -29,83 +29,87 @@ def modify_response(config: Dict[str, Any], resp_body: Node) -> Optional[Node]: # Is this a services packet? We need to modify this to point the rest of the # game packets at this proxy :( - if body.name == 'services': + if body.name == "services": for child in body.children: - if child.name == 'item': - if child.attribute('name') == 'ntp': + if child.name == "item": + if child.attribute("name") == "ntp": # Don't override this continue - elif child.attribute('name') == 'keepalive': + elif child.attribute("name") == "keepalive": # Completely rewrite to point at the proxy server, otherwise if we're proxying # to a local backend, we will end up giving out a local address and we will get # Network NG. - address = socket.gethostbyname(config['keepalive']) + address = socket.gethostbyname(config["keepalive"]) child.set_attribute( - 'url', - f'http://{address}/core/keepalive?pa={address}&ia={address}&ga={address}&ma={address}&t1=2&t2=10', + "url", + f"http://{address}/core/keepalive?pa={address}&ia={address}&ga={address}&ma={address}&t1=2&t2=10", ) else: # Get netloc to replace - url = urlparse.urlparse(child.attribute('url')) + url = urlparse.urlparse(child.attribute("url")) defaultport = { - 'http': 80, - 'https': 443, + "http": 80, + "https": 443, }.get(url.scheme, 0) - if config['local_port'] != defaultport: - new_url = child.attribute('url').replace(url.netloc, f'{config["local_host"]}:{config["local_port"]}') + if config["local_port"] != defaultport: + new_url = child.attribute("url").replace( + url.netloc, f'{config["local_host"]}:{config["local_port"]}' + ) else: - new_url = child.attribute('url').replace(url.netloc, f'{config["local_host"]}') - child.set_attribute('url', new_url) + new_url = child.attribute("url").replace( + url.netloc, f'{config["local_host"]}' + ) + child.set_attribute("url", new_url) return resp_body return None -@app.route('/', defaults={'path': ''}, methods=['GET']) -@app.route('/', methods=['GET']) +@app.route("/", defaults={"path": ""}, methods=["GET"]) +@app.route("/", methods=["GET"]) def receive_healthcheck(path: str) -> Response: - if '*' in config['remote']: - remote_host = config['remote']['*']['host'] - remote_port = config['remote']['*']['port'] + if "*" in config["remote"]: + remote_host = config["remote"]["*"]["host"] + remote_port = config["remote"]["*"]["port"] else: return Response("No route for default PCBID", 500) - actual_path = f'/{path}' + actual_path = f"/{path}" if request.query_string is not None and len(request.query_string) > 0: actual_path = actual_path + f'?{request.query_string.decode("ascii")}' # Make request to foreign service, using the same parameters r = requests.get( - f'http://{remote_host}:{remote_port}{actual_path}', - timeout=config['timeout'], + f"http://{remote_host}:{remote_port}{actual_path}", + timeout=config["timeout"], allow_redirects=False, ) headers = {} - for header in ['Location']: + for header in ["Location"]: if header in r.headers: headers[header] = r.headers[header] return Response(r.content, r.status_code, headers) -@app.route('/', defaults={'path': ''}, methods=['POST']) -@app.route('/', methods=['POST']) +@app.route("/", defaults={"path": ""}, methods=["POST"]) +@app.route("/", methods=["POST"]) def receive_request(path: str) -> Response: # First, parse the packet itself client_proto = EAmuseProtocol() server_proto = EAmuseProtocol() - remote_address = request.headers.get('X-Remote-Address', None) - request_compression = request.headers.get('X-Compress', None) - request_encryption = request.headers.get('X-Eamuse-Info', None) - request_client = request.headers.get('User-Agent', None) + remote_address = request.headers.get("X-Remote-Address", None) + request_compression = request.headers.get("X-Compress", None) + request_encryption = request.headers.get("X-Eamuse-Info", None) + request_client = request.headers.get("User-Agent", None) - actual_path = f'/{path}' + actual_path = f"/{path}" if request.query_string is not None and len(request.query_string) > 0: actual_path = actual_path + f'?{request.query_string.decode("ascii")}' - if config['verbose']: + if config["verbose"]: print(f"HTTP request for URI {actual_path}") print(f"Compression is {request_compression}") print(f"Encryption key is {request_encryption}") @@ -120,18 +124,18 @@ def receive_request(path: str) -> Response: # Nothing to do here return Response("Unrecognized packet!", 500) - if config['verbose']: + if config["verbose"]: print("Original request to server:") print(req) # Grab PCBID for directing to mulitple servers - pcbid = req.attribute('srcid') - if pcbid in config['remote']: - remote_host = config['remote'][pcbid]['host'] - remote_port = config['remote'][pcbid]['port'] - elif '*' in config['remote']: - remote_host = config['remote']['*']['host'] - remote_port = config['remote']['*']['port'] + pcbid = req.attribute("srcid") + if pcbid in config["remote"]: + remote_host = config["remote"][pcbid]["host"] + remote_port = config["remote"][pcbid]["port"] + elif "*" in config["remote"]: + remote_host = config["remote"]["*"]["host"] + remote_port = config["remote"]["*"]["port"] else: return Response(f"No route for PCBID {pcbid}", 500) @@ -141,7 +145,7 @@ def receive_request(path: str) -> Response: # to the exact same thing. req_binary = request.data else: - if config['verbose']: + if config["verbose"]: print("Modified request to server:") print(modified_request) @@ -158,41 +162,41 @@ def receive_request(path: str) -> Response: headers = { # For lobby functionality, make sure the request receives # the original IP address - 'X-Remote-Address': remote_address or request.remote_addr, + "X-Remote-Address": remote_address or request.remote_addr, # Some remote servers can be somewhat buggy, so we make sure # to specify a range of encodings. - 'Accept-Encoding': 'identity, deflate, compress, gzip', + "Accept-Encoding": "identity, deflate, compress, gzip", } # Copy over required headers that are sent by game client. if request_compression: - headers['X-Compress'] = request_compression + headers["X-Compress"] = request_compression else: - headers['X-Compress'] = 'none' + headers["X-Compress"] = "none" if request_encryption: - headers['X-Eamuse-Info'] = request_encryption + headers["X-Eamuse-Info"] = request_encryption # Make sure to copy the user agent as well. if request_client is not None: - headers['User-Agent'] = request_client + headers["User-Agent"] = request_client # Make request to foreign service, using the same parameters prep_req = requests.Request( - 'POST', - url=f'http://{remote_host}:{remote_port}{actual_path}', + "POST", + url=f"http://{remote_host}:{remote_port}{actual_path}", headers=headers, data=req_binary, ).prepare() sess = requests.Session() - r = sess.send(prep_req, timeout=config['timeout']) + r = sess.send(prep_req, timeout=config["timeout"]) if r.status_code != 200: # Failed on remote side return Response("Failed to get response!", 500) # Decode response, for modification if necessary - response_compression = r.headers.get('X-Compress', None) - response_encryption = r.headers.get('X-Eamuse-Info', None) + response_compression = r.headers.get("X-Compress", None) + response_encryption = r.headers.get("X-Eamuse-Info", None) resp = server_proto.decode( response_compression, response_encryption, @@ -203,7 +207,7 @@ def receive_request(path: str) -> Response: # Nothing to do here return Response("Unrecognized packet!", 500) - if config['verbose']: + if config["verbose"]: print("Original response from server:") print(resp) @@ -213,7 +217,7 @@ def receive_request(path: str) -> Response: # to the exact same thing. resp_binary = r.content else: - if config['verbose']: + if config["verbose"]: print("Modified response from server:") print(modified_response) @@ -228,9 +232,9 @@ def receive_request(path: str) -> Response: # these responses here. flask_resp = Response(resp_binary) if response_compression is not None: - flask_resp.headers['X-Compress'] = response_compression + flask_resp.headers["X-Compress"] = response_compression if response_encryption is not None: - flask_resp.headers['X-Eamuse-Info'] = response_encryption + flask_resp.headers["X-Eamuse-Info"] = response_encryption return flask_resp @@ -238,70 +242,118 @@ def load_proxy_config(filename: str) -> None: global config config_data = yaml.safe_load(open(filename)) - if 'pcbid' in config_data and config_data['pcbid'] is not None: - for pcbid in config_data['pcbid']: - remote_name = config_data['pcbid'][pcbid] - remote_config = config_data['remote'][remote_name] - config['remote'][pcbid] = remote_config + if "pcbid" in config_data and config_data["pcbid"] is not None: + for pcbid in config_data["pcbid"]: + remote_name = config_data["pcbid"][pcbid] + remote_config = config_data["remote"][remote_name] + config["remote"][pcbid] = remote_config def load_config(filename: str) -> None: global config config_data = yaml.safe_load(open(filename)) - config.update({ - 'local_host': config_data['local']['host'], - 'local_port': config_data['local']['port'], - 'verbose': config_data.get('verbose', False), - 'timeout': config_data.get('timeout', 30), - 'keepalive': config_data.get('keepalive', 'localhost'), - }) + config.update( + { + "local_host": config_data["local"]["host"], + "local_port": config_data["local"]["port"], + "verbose": config_data.get("verbose", False), + "timeout": config_data.get("timeout", 30), + "keepalive": config_data.get("keepalive", "localhost"), + } + ) - if 'default' in config_data: - remote_config = config_data['remote'][config_data['default']] - config.update({ - 'remote': { - '*': remote_config, - }, - }) + if "default" in config_data: + remote_config = config_data["remote"][config_data["default"]] + config.update( + { + "remote": { + "*": remote_config, + }, + } + ) else: - config.update({ - 'remote': {} - }) + config.update({"remote": {}}) - if 'pcbid' in config_data and config_data['pcbid'] is not None: + if "pcbid" in config_data and config_data["pcbid"] is not None: load_proxy_config(filename) -if __name__ == '__main__': - parser = argparse.ArgumentParser(description="A utility to MITM non-SSL eAmusement connections.") - parser.add_argument("-p", "--port", help="Port to listen on. Defaults to 9090", type=int, default=9090) - parser.add_argument("-a", "--address", help="Address to listen on. Defaults to all addresses", type=str, default="0.0.0.0") - parser.add_argument("-r", "--real-address", help="Real address we are listening on (for NAT and such)", type=str, default='127.0.0.1') - parser.add_argument("-q", "--remote-port", help="Port to connect to.", type=int, required=True) - parser.add_argument("-b", "--remote-address", help="Address to connect to.", type=str, required=True) - parser.add_argument("-c", "--config", help="Configuration file for PCBID to remote server mapping.", type=str, default=None) - parser.add_argument("-k", "--keepalive", help="Keepalive domain to advertise. Defaults to localhost", type=str, default='localhost') - parser.add_argument("-v", "--verbose", help="Display verbose packet info.", action='store_true') - parser.add_argument("-t", "--timeout", help="Timeout (in seconds) for proxy requests. Defaults to 30 seconds.", type=int, default=30) +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="A utility to MITM non-SSL eAmusement connections." + ) + parser.add_argument( + "-p", + "--port", + help="Port to listen on. Defaults to 9090", + type=int, + default=9090, + ) + parser.add_argument( + "-a", + "--address", + help="Address to listen on. Defaults to all addresses", + type=str, + default="0.0.0.0", + ) + parser.add_argument( + "-r", + "--real-address", + help="Real address we are listening on (for NAT and such)", + type=str, + default="127.0.0.1", + ) + parser.add_argument( + "-q", "--remote-port", help="Port to connect to.", type=int, required=True + ) + parser.add_argument( + "-b", "--remote-address", help="Address to connect to.", type=str, required=True + ) + parser.add_argument( + "-c", + "--config", + help="Configuration file for PCBID to remote server mapping.", + type=str, + default=None, + ) + parser.add_argument( + "-k", + "--keepalive", + help="Keepalive domain to advertise. Defaults to localhost", + type=str, + default="localhost", + ) + parser.add_argument( + "-v", "--verbose", help="Display verbose packet info.", action="store_true" + ) + parser.add_argument( + "-t", + "--timeout", + help="Timeout (in seconds) for proxy requests. Defaults to 30 seconds.", + type=int, + default=30, + ) args = parser.parse_args() - config.update({ - 'local_host': args.real_address, - 'local_port': args.port, - 'remote': { - '*': { - 'host': args.remote_address, - 'port': args.remote_port, + config.update( + { + "local_host": args.real_address, + "local_port": args.port, + "remote": { + "*": { + "host": args.remote_address, + "port": args.remote_port, + }, }, - }, - 'verbose': args.verbose, - 'timeout': args.timeout, - 'keepalive': args.keepalive, - }) + "verbose": args.verbose, + "timeout": args.timeout, + "keepalive": args.keepalive, + } + ) # Fill in remote addresses for PCBIDs we should redirect to a non-default server if args.config is not None: load_proxy_config(args.config) - app.run(host='0.0.0.0', port=args.port, debug=True) + app.run(host="0.0.0.0", port=args.port, debug=True) diff --git a/bemani/utils/psmap.py b/bemani/utils/psmap.py index e5e4d43..13f9b93 100644 --- a/bemani/utils/psmap.py +++ b/bemani/utils/psmap.py @@ -9,7 +9,9 @@ from bemani.protocol import Node from bemani.utils.responsegen import generate_lines -def parse_psmap(pe: PEFile, offset: str, rootname: str, *, verbose: bool = False) -> Node: +def parse_psmap( + pe: PEFile, offset: str, rootname: str, *, verbose: bool = False +) -> Node: root = Node.void(rootname) base = int(offset, 16) @@ -28,7 +30,7 @@ def parse_psmap(pe: PEFile, offset: str, rootname: str, *, verbose: bool = False offset = offset + 1 # Its shift-jis encoded, so decode it now - return bytes(bytestring).decode('shift_jisx0213') + return bytes(bytestring).decode("shift_jisx0213") # For recursing into nodes saved_root: List[Node] = [] @@ -37,17 +39,23 @@ def parse_psmap(pe: PEFile, offset: str, rootname: str, *, verbose: bool = False while True: readbase = base if pe.is_64bit(): # 64 bit - chunk = pe.data[base:(base + 24)] + chunk = pe.data[base : (base + 24)] base = base + 24 - (nodetype, mandatory, outoffset, width, nameptr, default) = struct.unpack('= 0: - name = name[:name.index('#')] + if name.index("#") >= 0: + name = name[: name.index("#")] except ValueError: pass @@ -76,16 +84,16 @@ def parse_psmap(pe: PEFile, offset: str, rootname: str, *, verbose: bool = False if verbose: space = " " * len(saved_root) print( - f"{space}Node offset: {hex(readbase)}{os.linesep}" + - f"{space} Type: {hex(nodetype)}{os.linesep}" + - f"{space} Mandatory: {'yes' if mandatory != 0 else 'no'}{os.linesep}" + - f"{space} Name: {name}{os.linesep}" + - f"{space} Parse Offset: {outoffset}{os.linesep}" + - f"{space} Data Width: {width}{os.linesep}" + - ( + f"{space}Node offset: {hex(readbase)}{os.linesep}" + + f"{space} Type: {hex(nodetype)}{os.linesep}" + + f"{space} Mandatory: {'yes' if mandatory != 0 else 'no'}{os.linesep}" + + f"{space} Name: {name}{os.linesep}" + + f"{space} Parse Offset: {outoffset}{os.linesep}" + + f"{space} Data Width: {width}{os.linesep}" + + ( f"{space} Data Pointer: {'null' if defaultptr == 0 else hex(defaultptr)}" - if nodetype == 0x01 else - f"{space} Default: {default}" + if nodetype == 0x01 + else f"{space} Default: {default}" ), file=sys.stderr, ) @@ -179,7 +187,7 @@ def parse_psmap(pe: PEFile, offset: str, rootname: str, *, verbose: bool = False else: node = Node.u64(name, 0) elif nodetype == 0x0A: - node = Node.string(name, '') + node = Node.string(name, "") elif nodetype == 0x0D: node = Node.float(name, 0.0) elif nodetype == 0x32 or nodetype == 0x6D: @@ -193,12 +201,12 @@ def parse_psmap(pe: PEFile, offset: str, rootname: str, *, verbose: bool = False node = Node.bool(name, False) elif nodetype == 0x2F: # Special case, this is an attribute - if name[-1] != '@': - raise Exception(f'Attribute name {name} expected to end with @') - root.set_attribute(name[:-1], '') + if name[-1] != "@": + raise Exception(f"Attribute name {name} expected to end with @") + root.set_attribute(name[:-1], "") continue else: - raise Exception(f'Unimplemented node type 0x{nodetype:02x}') + raise Exception(f"Unimplemented node type 0x{nodetype:02x}") # Append it root.add_child(node) @@ -207,7 +215,9 @@ def parse_psmap(pe: PEFile, offset: str, rootname: str, *, verbose: bool = False def main() -> None: - parser = argparse.ArgumentParser(description="A utility to extract psmap node lists and generate code.") + parser = argparse.ArgumentParser( + description="A utility to extract psmap node lists and generate code." + ) parser.add_argument( "--file", help="DLL file to extract from.", @@ -245,7 +255,7 @@ def main() -> None: "If multiple sections must be emulated you can specify this multiple times." ), type=str, - action='append', + action="append", default=[], ) parser.add_argument( @@ -257,7 +267,7 @@ def main() -> None: "can specify this multiple times." ), type=str, - action='append', + action="append", default=[], ) parser.add_argument( @@ -269,7 +279,7 @@ def main() -> None: ) args = parser.parse_args() - fp = open(args.file, 'rb') + fp = open(args.file, "rb") data = fp.read() fp.close() @@ -278,7 +288,7 @@ def main() -> None: # If asked, attempt to emulate code which dynamically constructs a psmap structure. if args.emulate_code: for chunk in args.emulate_code: - emulate_start, emulate_end = chunk.split(':', 1) + emulate_start, emulate_end = chunk.split(":", 1) start = int(emulate_start, 16) end = int(emulate_end, 16) pe.emulate_code(start, end, verbose=args.verbose) @@ -291,9 +301,9 @@ def main() -> None: layout = parse_psmap(pe, args.offset, args.root, verbose=args.verbose) # Walk through, outputting each node and attaching it to its parent - code = '\n'.join(generate_lines(layout, {})) + code = "\n".join(generate_lines(layout, {})) - if args.outfile == '-': + if args.outfile == "-": print(code) else: with open(args.outfile, mode="a") as outfp: @@ -301,5 +311,5 @@ def main() -> None: outfp.close -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/bemani/utils/read.py b/bemani/utils/read.py index ad1edfa..2e350b5 100644 --- a/bemani/utils/read.py +++ b/bemani/utils/read.py @@ -45,7 +45,6 @@ class ReadAPI(APIProviderInterface): class ImportBase: - def __init__( self, config: Config, @@ -72,9 +71,11 @@ class ImportBase: self.__session.commit() self.__batch = False - def execute(self, sql: str, params: Optional[Dict[str, Any]]=None) -> CursorResult: + def execute( + self, sql: str, params: Optional[Dict[str, Any]] = None + ) -> CursorResult: if not self.__batch: - raise Exception('Logic error, cannot execute outside of a batch!') + raise Exception("Logic error, cannot execute outside of a batch!") if self.__config.database.read_only: # See if this is an insert/update/delete @@ -84,7 +85,7 @@ class ImportBase: "delete from ", ]: if write_statement in sql.lower(): - raise Exception('Read-only mode is active!') + raise Exception("Read-only mode is active!") return self.__session.execute(text(sql), params if params is not None else {}) def remote_music(self, server: str, token: str) -> GlobalMusicData: @@ -101,30 +102,38 @@ class ImportBase: cursor = self.execute("SELECT MAX(id) AS next_id FROM `music`") result = cursor.fetchone() try: - return result['next_id'] + 1 + return result["next_id"] + 1 except TypeError: # Nothing in DB return 1 - def get_music_id_for_song(self, songid: int, chart: int, version: Optional[int]=None) -> Optional[int]: + def get_music_id_for_song( + self, songid: int, chart: int, version: Optional[int] = None + ) -> Optional[int]: if version is None: # Normal lookup if self.version is None: - raise Exception('Cannot get music ID for song when operating on all versions!') + raise Exception( + "Cannot get music ID for song when operating on all versions!" + ) version = self.version - sql = ( - "SELECT id FROM `music` WHERE songid = :songid AND chart = :chart AND game = :game AND version != :version" - ) + sql = "SELECT id FROM `music` WHERE songid = :songid AND chart = :chart AND game = :game AND version != :version" else: # Specific version lookup - sql = ( - "SELECT id FROM `music` WHERE songid = :songid AND chart = :chart AND game = :game AND version = :version" - ) + sql = "SELECT id FROM `music` WHERE songid = :songid AND chart = :chart AND game = :game AND version = :version" - cursor = self.execute(sql, {'songid': songid, 'chart': chart, 'game': self.game.value, 'version': version}) + cursor = self.execute( + sql, + { + "songid": songid, + "chart": chart, + "game": self.game.value, + "version": version, + }, + ) if cursor.rowcount != 0: result = cursor.fetchone() - return result['id'] + return result["id"] else: return None @@ -134,7 +143,7 @@ class ImportBase: artist: Optional[str], genre: Optional[str], chart: int, - version: Optional[int]=None, + version: Optional[int] = None, ) -> Optional[int]: frags = [] if title is not None: @@ -149,17 +158,29 @@ class ImportBase: if version is None: # Normal lookup if self.version is None: - raise Exception('Cannot get music ID for song when operating on all versions!') + raise Exception( + "Cannot get music ID for song when operating on all versions!" + ) version = self.version frags.append("version != :version") else: frags.append("version = :version") sql = "SELECT id FROM `music` WHERE " + " AND ".join(frags) - cursor = self.execute(sql, {'title': title, 'artist': artist, 'genre': genre, 'chart': chart, 'game': self.game.value, 'version': version}) + cursor = self.execute( + sql, + { + "title": title, + "artist": artist, + "genre": genre, + "chart": chart, + "game": self.game.value, + "version": version, + }, + ) if cursor.rowcount != 0: result = cursor.fetchone() - return result['id'] + return result["id"] else: return None @@ -168,42 +189,46 @@ class ImportBase: musicid: int, songid: int, chart: int, - name: Optional[str]=None, - artist: Optional[str]=None, - genre: Optional[str]=None, - data: Optional[Dict[str, Any]]=None, - version: Optional[int]=None, + name: Optional[str] = None, + artist: Optional[str] = None, + genre: Optional[str] = None, + data: Optional[Dict[str, Any]] = None, + version: Optional[int] = None, ) -> None: version = version if version is not None else self.version if version is None: - raise Exception('Cannot get insert new song when operating on all versions!') + raise Exception( + "Cannot get insert new song when operating on all versions!" + ) if data is None: - jsondata = '{}' + jsondata = "{}" else: jsondata = json.dumps(data) try: sql = ( - "INSERT INTO `music` (id, songid, chart, game, version, name, artist, genre, data) " + - "VALUES (:id, :songid, :chart, :game, :version, :name, :artist, :genre, :data)" + "INSERT INTO `music` (id, songid, chart, game, version, name, artist, genre, data) " + + "VALUES (:id, :songid, :chart, :game, :version, :name, :artist, :genre, :data)" ) self.execute( sql, { - 'id': musicid, - 'songid': songid, - 'chart': chart, - 'game': self.game.value, - 'version': version, - 'name': name, - 'artist': artist, - 'genre': genre, - 'data': jsondata + "id": musicid, + "songid": songid, + "chart": chart, + "game": self.game.value, + "version": version, + "name": name, + "artist": artist, + "genre": genre, + "data": jsondata, }, ) except IntegrityError: if self.update: print("Entry already existed, so updating information!") - self.update_metadata_for_song(songid, chart, name, artist, genre, data, version) + self.update_metadata_for_song( + songid, chart, name, artist, genre, data, version + ) else: print("Entry already existed, so skip creating a second one!") @@ -211,11 +236,11 @@ class ImportBase: self, songid: int, chart: int, - name: Optional[str]=None, - artist: Optional[str]=None, - genre: Optional[str]=None, - data: Optional[Dict[str, Any]]=None, - version: Optional[int]=None, + name: Optional[str] = None, + artist: Optional[str] = None, + genre: Optional[str] = None, + data: Optional[Dict[str, Any]] = None, + version: Optional[int] = None, ) -> None: if data is None: jsondata = None @@ -240,25 +265,25 @@ class ImportBase: self.execute( sql, { - 'songid': songid, - 'chart': chart, - 'game': self.game.value, - 'version': version, - 'name': name, - 'artist': artist, - 'genre': genre, - 'data': jsondata + "songid": songid, + "chart": chart, + "game": self.game.value, + "version": version, + "name": name, + "artist": artist, + "genre": genre, + "data": jsondata, }, ) def update_metadata_for_music_id( self, musicid: int, - name: Optional[str]=None, - artist: Optional[str]=None, - genre: Optional[str]=None, - data: Optional[Dict[str, Any]]=None, - version: Optional[int]=None, + name: Optional[str] = None, + artist: Optional[str] = None, + genre: Optional[str] = None, + data: Optional[Dict[str, Any]] = None, + version: Optional[int] = None, ) -> None: if data is None: jsondata = None @@ -283,13 +308,13 @@ class ImportBase: self.execute( sql, { - 'musicid': musicid, - 'game': self.game.value, - 'version': version, - 'name': name, - 'artist': artist, - 'genre': genre, - 'data': jsondata + "musicid": musicid, + "game": self.game.value, + "version": version, + "name": name, + "artist": artist, + "genre": genre, + "data": jsondata, }, ) @@ -297,42 +322,42 @@ class ImportBase: self, cattype: str, catid: int, - data: Optional[Dict[str, Any]]=None, + data: Optional[Dict[str, Any]] = None, ) -> None: if data is None: - jsondata = '{}' + jsondata = "{}" else: jsondata = json.dumps(data) try: sql = ( - "INSERT INTO `catalog` (game, version, type, id, data) " + - "VALUES (:game, :version, :type, :id, :data)" + "INSERT INTO `catalog` (game, version, type, id, data) " + + "VALUES (:game, :version, :type, :id, :data)" ) self.execute( sql, { - 'id': catid, - 'type': cattype, - 'game': self.game.value, - 'version': self.version, - 'data': jsondata + "id": catid, + "type": cattype, + "game": self.game.value, + "version": self.version, + "data": jsondata, }, ) except IntegrityError: if self.update: print("Entry already existed, so updating information!") sql = ( - "UPDATE `catalog` SET data = :data WHERE " + - "game = :game AND version = :version AND type = :type AND id = :id" + "UPDATE `catalog` SET data = :data WHERE " + + "game = :game AND version = :version AND type = :type AND id = :id" ) self.execute( sql, { - 'id': catid, - 'type': cattype, - 'game': self.game.value, - 'version': self.version, - 'data': jsondata + "id": catid, + "type": cattype, + "game": self.game.value, + "version": self.version, + "data": jsondata, }, ) else: @@ -344,7 +369,7 @@ class ImportBase: """ # Make sure we don't leak connections after finising insertion. if self.__batch: - raise Exception('Logic error, opened a batch without closing!') + raise Exception("Logic error, opened a batch without closing!") if self.__session is not None: self.__session.close() if self.__conn is not None: @@ -356,7 +381,6 @@ class ImportBase: class ImportPopn(ImportBase): - def __init__( self, config: Config, @@ -365,14 +389,14 @@ class ImportPopn(ImportBase): update: bool, ) -> None: actual_version = { - '19': VersionConstants.POPN_MUSIC_TUNE_STREET, - '20': VersionConstants.POPN_MUSIC_FANTASIA, - '21': VersionConstants.POPN_MUSIC_SUNNY_PARK, - '22': VersionConstants.POPN_MUSIC_LAPISTORIA, - '23': VersionConstants.POPN_MUSIC_ECLALE, - '24': VersionConstants.POPN_MUSIC_USANEKO, - '25': VersionConstants.POPN_MUSIC_PEACE, - '26': VersionConstants.POPN_MUSIC_KAIMEI_RIDDLES, + "19": VersionConstants.POPN_MUSIC_TUNE_STREET, + "20": VersionConstants.POPN_MUSIC_FANTASIA, + "21": VersionConstants.POPN_MUSIC_SUNNY_PARK, + "22": VersionConstants.POPN_MUSIC_LAPISTORIA, + "23": VersionConstants.POPN_MUSIC_ECLALE, + "24": VersionConstants.POPN_MUSIC_USANEKO, + "25": VersionConstants.POPN_MUSIC_PEACE, + "26": VersionConstants.POPN_MUSIC_KAIMEI_RIDDLES, }.get(version, -1) if actual_version == VersionConstants.POPN_MUSIC_TUNE_STREET: @@ -384,9 +408,13 @@ class ImportPopn(ImportBase): # Newer pop'n has charts for easy, normal, hyper, another self.charts = [0, 1, 2, 3] else: - raise Exception("Unsupported Pop'n Music version, expected one of the following: 19, 20, 21, 22, 23, 24, 25, 26!") + raise Exception( + "Unsupported Pop'n Music version, expected one of the following: 19, 20, 21, 22, 23, 24, 25, 26!" + ) - super().__init__(config, GameConstants.POPN_MUSIC, actual_version, no_combine, update) + super().__init__( + config, GameConstants.POPN_MUSIC, actual_version, no_combine, update + ) def scrape(self, infile: str) -> List[Dict[str, Any]]: with open(infile, mode="rb") as myfile: @@ -437,51 +465,45 @@ class ImportPopn(ImportBase): battle_hyper_file_offset = 21 packedfmt = ( - '<' - 'I' # Genre - 'I' # Title - 'I' # Artist - 'I' # Comment - 'H' # ?? - 'H' # ?? - 'I' # Available charts mask - 'I' # Folder - 'I' # Event flags? - 'B' # Event flags? - 'B' # Normal difficulty - 'B' # Hyper difficulty - 'B' # Easy difficulty - 'B' # EX difficulty - 'B' # Battle normal difficulty - 'B' # Battle hyper difficulty - 'x' # ?? - 'x' # ?? - 'x' # ?? - 'H' # Normal chart pointer - 'H' # Hyper chart pointer - 'H' # Easy chart pointer - 'H' # EX chart pointer - 'H' # Battle normal pointer - 'H' # Battle hyper pointer - 'xxxxxxxxxxxxxxxxxx' + "<" + "I" # Genre + "I" # Title + "I" # Artist + "I" # Comment + "H" # ?? + "H" # ?? + "I" # Available charts mask + "I" # Folder + "I" # Event flags? + "B" # Event flags? + "B" # Normal difficulty + "B" # Hyper difficulty + "B" # Easy difficulty + "B" # EX difficulty + "B" # Battle normal difficulty + "B" # Battle hyper difficulty + "x" # ?? + "x" # ?? + "x" # ?? + "H" # Normal chart pointer + "H" # Hyper chart pointer + "H" # Easy chart pointer + "H" # EX chart pointer + "H" # Battle normal pointer + "H" # Battle hyper pointer + "xxxxxxxxxxxxxxxxxx" ) # Offsets into file DB for finding file and folder. file_folder_offset = 0 file_name_offset = 1 - filefmt = ( - '<' - 'I' # Folder - 'I' # Filename - 'I' - 'I' - 'I' - 'I' - ) + filefmt = "<" "I" "I" "I" "I" "I" "I" # Folder # Filename # Decoding function for chart masks - def available_charts(mask: int) -> Tuple[bool, bool, bool, bool, bool, bool]: + def available_charts( + mask: int, + ) -> Tuple[bool, bool, bool, bool, bool, bool]: return ( True, # Always an easy chart True, # Always a normal chart @@ -490,6 +512,7 @@ class ImportPopn(ImportBase): True, # Always a battle normal chart mask & 0x4000000 > 0, # Battle hyper chart bit ) + elif self.version == VersionConstants.POPN_MUSIC_FANTASIA: # Based on L39:J:A:A:2012091900 @@ -532,51 +555,45 @@ class ImportPopn(ImportBase): battle_hyper_file_offset = 21 packedfmt = ( - '<' - 'I' # Genre - 'I' # Title - 'I' # Artist - 'I' # Comment - 'H' # ?? - 'H' # ?? - 'I' # Available charts mask - 'I' # Folder - 'I' # Event flags? - 'B' # Event flags? - 'B' # Normal difficulty - 'B' # Hyper difficulty - 'B' # Easy difficulty - 'B' # EX difficulty - 'B' # Battle normal difficulty - 'B' # Battle hyper difficulty - 'x' # ?? - 'x' # ?? - 'x' # ?? - 'H' # Normal chart pointer - 'H' # Hyper chart pointer - 'H' # Easy chart pointer - 'H' # EX chart pointer - 'H' # Battle normal pointer - 'H' # Battle hyper pointer - 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' + "<" + "I" # Genre + "I" # Title + "I" # Artist + "I" # Comment + "H" # ?? + "H" # ?? + "I" # Available charts mask + "I" # Folder + "I" # Event flags? + "B" # Event flags? + "B" # Normal difficulty + "B" # Hyper difficulty + "B" # Easy difficulty + "B" # EX difficulty + "B" # Battle normal difficulty + "B" # Battle hyper difficulty + "x" # ?? + "x" # ?? + "x" # ?? + "H" # Normal chart pointer + "H" # Hyper chart pointer + "H" # Easy chart pointer + "H" # EX chart pointer + "H" # Battle normal pointer + "H" # Battle hyper pointer + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ) # Offsets into file DB for finding file and folder. file_folder_offset = 0 file_name_offset = 1 - filefmt = ( - '<' - 'I' # Folder - 'I' # Filename - 'I' - 'I' - 'I' - 'I' - ) + filefmt = "<" "I" "I" "I" "I" "I" "I" # Folder # Filename # Decoding function for chart masks - def available_charts(mask: int) -> Tuple[bool, bool, bool, bool, bool, bool]: + def available_charts( + mask: int, + ) -> Tuple[bool, bool, bool, bool, bool, bool]: return ( True, # Always an easy chart True, # Always a normal chart @@ -585,6 +602,7 @@ class ImportPopn(ImportBase): True, # Always a battle normal chart mask & 0x4000000 > 0, # Battle hyper chart bit ) + elif self.version == VersionConstants.POPN_MUSIC_SUNNY_PARK: # Based on M39:J:A:A:2014061900 @@ -627,52 +645,45 @@ class ImportPopn(ImportBase): battle_hyper_file_offset = 22 packedfmt = ( - '<' - 'I' # Genre - 'I' # Title - 'I' # Artist - 'I' # Comment - 'I' # English Title - 'I' # English Artist - 'I' # Extended genre? - 'H' # ?? - 'H' # ?? - 'I' # Available charts mask - 'I' # Folder - 'I' # Event unlocks? - 'H' # Event unlocks? - 'B' # Easy difficulty - 'B' # Normal difficulty - 'B' # Hyper difficulty - 'B' # EX difficulty - 'B' # Battle normal difficulty - 'B' # Battle hyper difficulty - 'H' # Easy chart pointer - 'H' # Normal chart pointer - 'H' # Hyper chart pointer - 'H' # EX chart pointer - 'H' # Battle normal pointer - 'H' # Battle hyper pointer - 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' + "<" + "I" # Genre + "I" # Title + "I" # Artist + "I" # Comment + "I" # English Title + "I" # English Artist + "I" # Extended genre? + "H" # ?? + "H" # ?? + "I" # Available charts mask + "I" # Folder + "I" # Event unlocks? + "H" # Event unlocks? + "B" # Easy difficulty + "B" # Normal difficulty + "B" # Hyper difficulty + "B" # EX difficulty + "B" # Battle normal difficulty + "B" # Battle hyper difficulty + "H" # Easy chart pointer + "H" # Normal chart pointer + "H" # Hyper chart pointer + "H" # EX chart pointer + "H" # Battle normal pointer + "H" # Battle hyper pointer + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ) # Offsets into file DB for finding file and folder. file_folder_offset = 0 file_name_offset = 1 - filefmt = ( - '<' - 'I' # Folder - 'I' # Filename - 'I' - 'I' - 'I' - 'I' - 'I' - ) + filefmt = "<" "I" "I" "I" "I" "I" "I" "I" # Folder # Filename # Decoding function for chart masks - def available_charts(mask: int) -> Tuple[bool, bool, bool, bool, bool, bool]: + def available_charts( + mask: int, + ) -> Tuple[bool, bool, bool, bool, bool, bool]: return ( mask & 0x0080000 > 0, # Easy chart bit True, # Always a normal chart @@ -681,6 +692,7 @@ class ImportPopn(ImportBase): True, # Always a battle normal chart mask & 0x4000000 > 0, # Battle hyper chart bit ) + elif self.version == VersionConstants.POPN_MUSIC_LAPISTORIA: # Based on M39:J:A:A:2015081900 @@ -723,51 +735,44 @@ class ImportPopn(ImportBase): battle_hyper_file_offset = 23 packedfmt = ( - '<' - 'I' # Genre - 'I' # Title - 'I' # Artist - 'I' # Comment - 'I' # English Title - 'I' # English Artist - 'H' # ?? - 'H' # ?? - 'I' # Available charts mask - 'I' # Folder - 'I' # Event unlocks? - 'H' # Event unlocks? - 'B' # Easy difficulty - 'B' # Normal difficulty - 'B' # Hyper difficulty - 'B' # EX difficulty - 'B' # Battle normal difficulty - 'B' # Battle hyper difficulty - 'H' # Easy chart pointer - 'H' # Normal chart pointer - 'H' # Hyper chart pointer - 'H' # EX chart pointer - 'H' # Battle normal pointer - 'H' # Battle hyper pointer - 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' + "<" + "I" # Genre + "I" # Title + "I" # Artist + "I" # Comment + "I" # English Title + "I" # English Artist + "H" # ?? + "H" # ?? + "I" # Available charts mask + "I" # Folder + "I" # Event unlocks? + "H" # Event unlocks? + "B" # Easy difficulty + "B" # Normal difficulty + "B" # Hyper difficulty + "B" # EX difficulty + "B" # Battle normal difficulty + "B" # Battle hyper difficulty + "H" # Easy chart pointer + "H" # Normal chart pointer + "H" # Hyper chart pointer + "H" # EX chart pointer + "H" # Battle normal pointer + "H" # Battle hyper pointer + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ) # Offsets into file DB for finding file and folder. file_folder_offset = 0 file_name_offset = 1 - filefmt = ( - '<' - 'I' # Folder - 'I' # Filename - 'I' - 'I' - 'I' - 'I' - 'I' - ) + filefmt = "<" "I" "I" "I" "I" "I" "I" "I" # Folder # Filename # Decoding function for chart masks - def available_charts(mask: int) -> Tuple[bool, bool, bool, bool, bool, bool]: + def available_charts( + mask: int, + ) -> Tuple[bool, bool, bool, bool, bool, bool]: return ( mask & 0x0080000 > 0, # Easy chart bit True, # Always a normal chart @@ -776,6 +781,7 @@ class ImportPopn(ImportBase): True, # Always a battle normal chart mask & 0x4000000 > 0, # Battle hyper chart bit ) + elif self.version == VersionConstants.POPN_MUSIC_ECLALE: # Based on M39:J:A:A:2016100500 @@ -818,52 +824,44 @@ class ImportPopn(ImportBase): battle_hyper_file_offset = 23 packedfmt = ( - '<' - 'I' # Genre - 'I' # Title - 'I' # Artist - 'I' # Comment - 'I' # English Title - 'I' # English Artist - 'H' # ?? - 'H' # ?? - 'I' # Available charts mask - 'I' # Folder - 'I' # Event unlocks? - 'H' # Event unlocks? - 'B' # Easy difficulty - 'B' # Normal difficulty - 'B' # Hyper difficulty - 'B' # EX difficulty - 'B' # Battle normal difficulty - 'B' # Battle hyper difficulty - 'H' # Easy chart pointer - 'H' # Normal chart pointer - 'H' # Hyper chart pointer - 'H' # EX chart pointer - 'H' # Battle normal pointer - 'H' # Battle hyper pointer - 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' + "<" + "I" # Genre + "I" # Title + "I" # Artist + "I" # Comment + "I" # English Title + "I" # English Artist + "H" # ?? + "H" # ?? + "I" # Available charts mask + "I" # Folder + "I" # Event unlocks? + "H" # Event unlocks? + "B" # Easy difficulty + "B" # Normal difficulty + "B" # Hyper difficulty + "B" # EX difficulty + "B" # Battle normal difficulty + "B" # Battle hyper difficulty + "H" # Easy chart pointer + "H" # Normal chart pointer + "H" # Hyper chart pointer + "H" # EX chart pointer + "H" # Battle normal pointer + "H" # Battle hyper pointer + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ) # Offsets into file DB for finding file and folder. file_folder_offset = 0 file_name_offset = 1 - filefmt = ( - '<' - 'I' # Folder - 'I' # Filename - 'I' - 'I' - 'I' - 'I' - 'I' - 'I' - ) + filefmt = "<" "I" "I" "I" "I" "I" "I" "I" "I" # Folder # Filename # Decoding function for chart masks - def available_charts(mask: int) -> Tuple[bool, bool, bool, bool, bool, bool]: + def available_charts( + mask: int, + ) -> Tuple[bool, bool, bool, bool, bool, bool]: return ( mask & 0x0080000 > 0, # Easy chart bit True, # Always a normal chart @@ -872,6 +870,7 @@ class ImportPopn(ImportBase): True, # Always a battle normal chart mask & 0x4000000 > 0, # Battle hyper chart bit ) + elif self.version == VersionConstants.POPN_MUSIC_USANEKO: # Based on M39:J:A:A:2018101500 @@ -914,53 +913,45 @@ class ImportPopn(ImportBase): battle_hyper_file_offset = 23 packedfmt = ( - '<' - 'I' # Genre - 'I' # Title - 'I' # Artist - 'I' # Comment - 'I' # English Title - 'I' # English Artist - 'H' # ?? - 'H' # ?? - 'I' # Available charts mask - 'I' # Folder - 'I' # Event unlocks? - 'I' # Event unlocks? - 'B' # Easy difficulty - 'B' # Normal difficulty - 'B' # Hyper difficulty - 'B' # EX difficulty - 'B' # Battle normal difficulty - 'B' # Battle hyper difficulty - 'xx' # Unknown pointer - 'H' # Easy chart pointer - 'H' # Normal chart pointer - 'H' # Hyper chart pointer - 'H' # EX chart pointer - 'H' # Battle normal pointer - 'H' # Battle hyper pointer - 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' + "<" + "I" # Genre + "I" # Title + "I" # Artist + "I" # Comment + "I" # English Title + "I" # English Artist + "H" # ?? + "H" # ?? + "I" # Available charts mask + "I" # Folder + "I" # Event unlocks? + "I" # Event unlocks? + "B" # Easy difficulty + "B" # Normal difficulty + "B" # Hyper difficulty + "B" # EX difficulty + "B" # Battle normal difficulty + "B" # Battle hyper difficulty + "xx" # Unknown pointer + "H" # Easy chart pointer + "H" # Normal chart pointer + "H" # Hyper chart pointer + "H" # EX chart pointer + "H" # Battle normal pointer + "H" # Battle hyper pointer + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ) # Offsets into file DB for finding file and folder. file_folder_offset = 0 file_name_offset = 1 - filefmt = ( - '<' - 'I' # Folder - 'I' # Filename - 'I' - 'I' - 'I' - 'I' - 'I' - 'I' - ) + filefmt = "<" "I" "I" "I" "I" "I" "I" "I" "I" # Folder # Filename # Decoding function for chart masks - def available_charts(mask: int) -> Tuple[bool, bool, bool, bool, bool, bool]: + def available_charts( + mask: int, + ) -> Tuple[bool, bool, bool, bool, bool, bool]: return ( mask & 0x0080000 > 0, # Easy chart bit True, # Always a normal chart @@ -1012,53 +1003,45 @@ class ImportPopn(ImportBase): battle_hyper_file_offset = 23 packedfmt = ( - '<' - 'I' # Genre - 'I' # Title - 'I' # Artist - 'I' # Comment - 'I' # English Title - 'I' # English Artist - 'H' # ?? - 'H' # ?? - 'I' # Available charts mask - 'I' # Folder - 'I' # Event unlocks? - 'I' # Event unlocks? - 'B' # Easy difficulty - 'B' # Normal difficulty - 'B' # Hyper difficulty - 'B' # EX difficulty - 'B' # Battle normal difficulty - 'B' # Battle hyper difficulty - 'xx' # Unknown pointer - 'H' # Easy chart pointer - 'H' # Normal chart pointer - 'H' # Hyper chart pointer - 'H' # EX chart pointer - 'H' # Battle normal pointer - 'H' # Battle hyper pointer - 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' + "<" + "I" # Genre + "I" # Title + "I" # Artist + "I" # Comment + "I" # English Title + "I" # English Artist + "H" # ?? + "H" # ?? + "I" # Available charts mask + "I" # Folder + "I" # Event unlocks? + "I" # Event unlocks? + "B" # Easy difficulty + "B" # Normal difficulty + "B" # Hyper difficulty + "B" # EX difficulty + "B" # Battle normal difficulty + "B" # Battle hyper difficulty + "xx" # Unknown pointer + "H" # Easy chart pointer + "H" # Normal chart pointer + "H" # Hyper chart pointer + "H" # EX chart pointer + "H" # Battle normal pointer + "H" # Battle hyper pointer + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ) # Offsets into file DB for finding file and folder. file_folder_offset = 0 file_name_offset = 1 - filefmt = ( - '<' - 'I' # Folder - 'I' # Filename - 'I' - 'I' - 'I' - 'I' - 'I' - 'I' - ) + filefmt = "<" "I" "I" "I" "I" "I" "I" "I" "I" # Folder # Filename # Decoding function for chart masks - def available_charts(mask: int) -> Tuple[bool, bool, bool, bool, bool, bool]: + def available_charts( + mask: int, + ) -> Tuple[bool, bool, bool, bool, bool, bool]: return ( mask & 0x0080000 > 0, # Easy chart bit True, # Always a normal chart @@ -1110,53 +1093,45 @@ class ImportPopn(ImportBase): battle_hyper_file_offset = 23 packedfmt = ( - '<' - 'I' # Genre - 'I' # Title - 'I' # Artist - 'I' # Comment - 'I' # English Title - 'I' # English Artist - 'H' # ?? - 'H' # ?? - 'I' # Available charts mask - 'I' # Folder - 'I' # Event unlocks? - 'I' # Event unlocks? - 'B' # Easy difficulty - 'B' # Normal difficulty - 'B' # Hyper difficulty - 'B' # EX difficulty - 'B' # Battle normal difficulty - 'B' # Battle hyper difficulty - 'xx' # Unknown pointer - 'H' # Easy chart pointer - 'H' # Normal chart pointer - 'H' # Hyper chart pointer - 'H' # EX chart pointer - 'H' # Battle normal pointer - 'H' # Battle hyper pointer - 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' + "<" + "I" # Genre + "I" # Title + "I" # Artist + "I" # Comment + "I" # English Title + "I" # English Artist + "H" # ?? + "H" # ?? + "I" # Available charts mask + "I" # Folder + "I" # Event unlocks? + "I" # Event unlocks? + "B" # Easy difficulty + "B" # Normal difficulty + "B" # Hyper difficulty + "B" # EX difficulty + "B" # Battle normal difficulty + "B" # Battle hyper difficulty + "xx" # Unknown pointer + "H" # Easy chart pointer + "H" # Normal chart pointer + "H" # Hyper chart pointer + "H" # EX chart pointer + "H" # Battle normal pointer + "H" # Battle hyper pointer + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ) # Offsets into file DB for finding file and folder. file_folder_offset = 0 file_name_offset = 1 - filefmt = ( - '<' - 'I' # Folder - 'I' # Filename - 'I' - 'I' - 'I' - 'I' - 'I' - 'I' - ) + filefmt = "<" "I" "I" "I" "I" "I" "I" "I" "I" # Folder # Filename # Decoding function for chart masks - def available_charts(mask: int) -> Tuple[bool, bool, bool, bool, bool, bool]: + def available_charts( + mask: int, + ) -> Tuple[bool, bool, bool, bool, bool, bool]: return ( mask & 0x0080000 > 0, # Easy chart bit True, # Always a normal chart @@ -1165,8 +1140,9 @@ class ImportPopn(ImportBase): True, # Always a battle normal chart mask & 0x4000000 > 0, # Battle hyper chart bit ) + else: - raise Exception(f'Unsupported version {self.version}') + raise Exception(f"Unsupported version {self.version}") def read_string(offset: int) -> str: # First, translate load offset in memory to disk offset @@ -1179,72 +1155,98 @@ class ImportPopn(ImportBase): offset = offset + 1 # Its shift-jis encoded, so decode it now - return bytes(bytestring).decode('shift_jisx0213') + return bytes(bytestring).decode("shift_jisx0213") def file_chunk(offset: int) -> Tuple[Any, ...]: fileoffset = file_offset + (file_step * offset) - filedata = data[fileoffset:(fileoffset + file_step)] + filedata = data[fileoffset : (fileoffset + file_step)] return struct.unpack(filefmt, filedata) def file_handle(offset: int) -> str: chunk = file_chunk(offset) - return read_string(chunk[file_folder_offset]) + '/' + read_string(chunk[file_name_offset]) + return ( + read_string(chunk[file_folder_offset]) + + "/" + + read_string(chunk[file_name_offset]) + ) songs = [] for songid in range(length): chunkoffset = offset + (step * songid) - chunkdata = data[chunkoffset:(chunkoffset + step)] + chunkdata = data[chunkoffset : (chunkoffset + step)] unpacked = struct.unpack(packedfmt, chunkdata) valid_charts = available_charts(unpacked[charts_offset]) songinfo = { - 'id': songid, - 'title': read_string(unpacked[title_offset]), - 'artist': read_string(unpacked[artist_offset]), - 'genre': read_string(unpacked[genre_offset]), - 'comment': read_string(unpacked[comment_offset]), - 'title_en': read_string(unpacked[english_title_offset]) if english_title_offset > 0 else '', - 'artist_en': read_string(unpacked[english_artist_offset]) if english_artist_offset > 0 else '', - 'long_genre': read_string(unpacked[extended_genre_offset]) if extended_genre_offset > 0 else '', - 'folder': unpacked[folder_offset], - 'difficulty': { - 'standard': { - 'easy': unpacked[easy_offset] if valid_charts[0] else 0, - 'normal': unpacked[normal_offset] if valid_charts[1] else 0, - 'hyper': unpacked[hyper_offset] if valid_charts[2] else 0, - 'ex': unpacked[ex_offset] if valid_charts[3] else 0, + "id": songid, + "title": read_string(unpacked[title_offset]), + "artist": read_string(unpacked[artist_offset]), + "genre": read_string(unpacked[genre_offset]), + "comment": read_string(unpacked[comment_offset]), + "title_en": read_string(unpacked[english_title_offset]) + if english_title_offset > 0 + else "", + "artist_en": read_string(unpacked[english_artist_offset]) + if english_artist_offset > 0 + else "", + "long_genre": read_string(unpacked[extended_genre_offset]) + if extended_genre_offset > 0 + else "", + "folder": unpacked[folder_offset], + "difficulty": { + "standard": { + "easy": unpacked[easy_offset] if valid_charts[0] else 0, + "normal": unpacked[normal_offset] if valid_charts[1] else 0, + "hyper": unpacked[hyper_offset] if valid_charts[2] else 0, + "ex": unpacked[ex_offset] if valid_charts[3] else 0, + }, + "battle": { + "normal": unpacked[battle_normal_offset] + if valid_charts[4] + else 0, + "hyper": unpacked[battle_hyper_offset] + if valid_charts[5] + else 0, }, - 'battle': { - 'normal': unpacked[battle_normal_offset] if valid_charts[4] else 0, - 'hyper': unpacked[battle_hyper_offset] if valid_charts[5] else 0, - } }, - 'file': { - 'standard': { - 'easy': file_handle(unpacked[easy_file_offset]) if valid_charts[0] else '', - 'normal': file_handle(unpacked[normal_file_offset]) if valid_charts[1] else '', - 'hyper': file_handle(unpacked[hyper_file_offset]) if valid_charts[2] else '', - 'ex': file_handle(unpacked[ex_file_offset]) if valid_charts[3] else '', + "file": { + "standard": { + "easy": file_handle(unpacked[easy_file_offset]) + if valid_charts[0] + else "", + "normal": file_handle(unpacked[normal_file_offset]) + if valid_charts[1] + else "", + "hyper": file_handle(unpacked[hyper_file_offset]) + if valid_charts[2] + else "", + "ex": file_handle(unpacked[ex_file_offset]) + if valid_charts[3] + else "", }, - 'battle': { - 'normal': file_handle(unpacked[battle_normal_file_offset]) if valid_charts[4] else '', - 'hyper': file_handle(unpacked[battle_hyper_file_offset]) if valid_charts[5] else '', + "battle": { + "normal": file_handle(unpacked[battle_normal_file_offset]) + if valid_charts[4] + else "", + "hyper": file_handle(unpacked[battle_hyper_file_offset]) + if valid_charts[5] + else "", }, }, } if ( - songinfo['title'] in ['-', '‐'] and - songinfo['genre'] in ['-', '‐'] and - songinfo['artist'] in ['-', '‐'] and - songinfo['comment'] in ['-', '‐'] + songinfo["title"] in ["-", "‐"] + and songinfo["genre"] in ["-", "‐"] + and songinfo["artist"] in ["-", "‐"] + and songinfo["comment"] in ["-", "‐"] ): # This is a removed song continue if ( - songinfo['title'] == 'DUMMY' and - songinfo['artist'] == 'DUMMY' and - songinfo['genre'] == 'DUMMY' + songinfo["title"] == "DUMMY" + and songinfo["artist"] == "DUMMY" + and songinfo["genre"] == "DUMMY" ): # This is a song the intern left in continue @@ -1277,11 +1279,11 @@ class ImportPopn(ImportBase): } for orig, rep in accent_lut.items(): - songinfo['title'] = songinfo['title'].replace(orig, rep) - songinfo['artist'] = songinfo['artist'].replace(orig, rep) - songinfo['title_en'] = songinfo['title_en'].replace(orig, rep) - songinfo['artist_en'] = songinfo['artist_en'].replace(orig, rep) - songinfo['genre'] = songinfo['genre'].replace(orig, rep) + songinfo["title"] = songinfo["title"].replace(orig, rep) + songinfo["artist"] = songinfo["artist"].replace(orig, rep) + songinfo["title_en"] = songinfo["title_en"].replace(orig, rep) + songinfo["artist_en"] = songinfo["artist_en"].replace(orig, rep) + songinfo["genre"] = songinfo["genre"].replace(orig, rep) songs.append(songinfo) return songs @@ -1292,10 +1294,10 @@ class ImportPopn(ImportBase): songs = music.get_all_songs(self.game, self.version) lut: Dict[int, Dict[str, Any]] = {} chart_map = { - 0: 'easy', - 1: 'normal', - 2: 'hyper', - 3: 'ex', + 0: "easy", + 1: "normal", + 2: "hyper", + 3: "ex", } # Format it the way we expect @@ -1306,94 +1308,101 @@ class ImportPopn(ImportBase): if song.id not in lut: lut[song.id] = { - 'id': song.id, - 'title': song.name, - 'artist': song.artist, - 'genre': song.genre, - 'comment': "", - 'title_en': "", - 'artist_en': "", - 'long_genre': "", - 'folder': song.data.get_str('category'), - 'difficulty': { - 'standard': { - 'easy': 0, - 'normal': 0, - 'hyper': 0, - 'ex': 0, + "id": song.id, + "title": song.name, + "artist": song.artist, + "genre": song.genre, + "comment": "", + "title_en": "", + "artist_en": "", + "long_genre": "", + "folder": song.data.get_str("category"), + "difficulty": { + "standard": { + "easy": 0, + "normal": 0, + "hyper": 0, + "ex": 0, + }, + "battle": { + "normal": 0, + "hyper": 0, }, - 'battle': { - 'normal': 0, - 'hyper': 0, - } }, - 'file': { - 'standard': { - 'easy': "", - 'normal': "", - 'hyper': "", - 'ex': "", + "file": { + "standard": { + "easy": "", + "normal": "", + "hyper": "", + "ex": "", }, - 'battle': { - 'normal': "", - 'hyper': "", + "battle": { + "normal": "", + "hyper": "", }, }, } - lut[song.id]['difficulty']['standard'][chart_map[song.chart]] = song.data.get_int('difficulty') + lut[song.id]["difficulty"]["standard"][ + chart_map[song.chart] + ] = song.data.get_int("difficulty") # Return the reassembled data return [val for _, val in lut.items()] def import_music_db(self, songs: List[Dict[str, Any]]) -> None: chart_map = { - 0: 'easy', - 1: 'normal', - 2: 'hyper', - 3: 'ex', + 0: "easy", + 1: "normal", + 2: "hyper", + 3: "ex", } for song in songs: self.start_batch() for chart in self.charts: # First, try to find in the DB from another version - old_id = self.get_music_id_for_song(song['id'], chart) + old_id = self.get_music_id_for_song(song["id"], chart) # Now, look up metadata - title = song['title_en'] if len(song['title_en']) > 0 else song['title'] - artist = song['artist_en'] if len(song['artist_en']) > 0 else song['artist'] - genre = song['genre'] + title = song["title_en"] if len(song["title_en"]) > 0 else song["title"] + artist = ( + song["artist_en"] if len(song["artist_en"]) > 0 else song["artist"] + ) + genre = song["genre"] # We only care about easy/normal/hyper/ex, so only provide mappings there if chart in chart_map: - difficulty = song['difficulty']['standard'][chart_map[chart]] + difficulty = song["difficulty"]["standard"][chart_map[chart]] else: difficulty = 0 if self.no_combine or old_id is None: # Insert original - print(f"New entry for {artist} {title} ({song['id']} chart {chart})") + print( + f"New entry for {artist} {title} ({song['id']} chart {chart})" + ) next_id = self.get_next_music_id() else: - print(f"Reused entry for {artist} {title} ({song['id']} chart {chart})") + print( + f"Reused entry for {artist} {title} ({song['id']} chart {chart})" + ) next_id = old_id self.insert_music_id_for_song( next_id, - song['id'], + song["id"], chart, title, artist, genre, { - 'category': str(song['folder']), - 'difficulty': difficulty, + "category": str(song["folder"]), + "difficulty": difficulty, }, ) self.finish_batch() class ImportJubeat(ImportBase): - def __init__( self, config: Config, @@ -1401,24 +1410,24 @@ class ImportJubeat(ImportBase): no_combine: bool, update: bool, ) -> None: - if version in ['saucer', 'saucer-fulfill', 'prop', 'qubell', 'clan', 'festo']: + if version in ["saucer", "saucer-fulfill", "prop", "qubell", "clan", "festo"]: actual_version = { - 'saucer': VersionConstants.JUBEAT_SAUCER, - 'saucer-fulfill': VersionConstants.JUBEAT_SAUCER_FULFILL, - 'prop': VersionConstants.JUBEAT_PROP, - 'qubell': VersionConstants.JUBEAT_QUBELL, - 'clan': VersionConstants.JUBEAT_CLAN, - 'festo': VersionConstants.JUBEAT_FESTO, + "saucer": VersionConstants.JUBEAT_SAUCER, + "saucer-fulfill": VersionConstants.JUBEAT_SAUCER_FULFILL, + "prop": VersionConstants.JUBEAT_PROP, + "qubell": VersionConstants.JUBEAT_QUBELL, + "clan": VersionConstants.JUBEAT_CLAN, + "festo": VersionConstants.JUBEAT_FESTO, }.get(version, -1) - elif version in ['omni-prop', 'omni-qubell', 'omni-clan', 'omni-festo']: + elif version in ["omni-prop", "omni-qubell", "omni-clan", "omni-festo"]: actual_version = { - 'omni-prop': VersionConstants.JUBEAT_PROP, - 'omni-qubell': VersionConstants.JUBEAT_QUBELL, - 'omni-clan': VersionConstants.JUBEAT_CLAN, - 'omni-festo': VersionConstants.JUBEAT_FESTO, + "omni-prop": VersionConstants.JUBEAT_PROP, + "omni-qubell": VersionConstants.JUBEAT_QUBELL, + "omni-clan": VersionConstants.JUBEAT_CLAN, + "omni-festo": VersionConstants.JUBEAT_FESTO, }.get(version, -1) + DBConstants.OMNIMIX_VERSION_BUMP - elif version == 'all': + elif version == "all": actual_version = None if actual_version in [ @@ -1442,13 +1451,17 @@ class ImportJubeat(ImportBase): self.charts = [0, 1, 2] else: - raise Exception("Unsupported Jubeat version, expected one of the following: saucer, saucer-fulfill, prop, omni-prop, qubell, omni-qubell, clan, omni-clan, festo, omni-festo!") + raise Exception( + "Unsupported Jubeat version, expected one of the following: saucer, saucer-fulfill, prop, omni-prop, qubell, omni-qubell, clan, omni-clan, festo, omni-festo!" + ) - super().__init__(config, GameConstants.JUBEAT, actual_version, no_combine, update) + super().__init__( + config, GameConstants.JUBEAT, actual_version, no_combine, update + ) def scrape(self, xmlfile: str) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]: if self.version is None: - raise Exception('Can\'t scrape Jubeat database for \'all\' version!') + raise Exception("Can't scrape Jubeat database for 'all' version!") try: # Probably UTF-8 music DB @@ -1456,17 +1469,17 @@ class ImportJubeat(ImportBase): root = tree.getroot() except ValueError: # Probably shift-jis emblems - with open(xmlfile, 'rb') as xmlhandle: - xmldata = xmlhandle.read().decode('shift_jisx0213') + with open(xmlfile, "rb") as xmlhandle: + xmldata = xmlhandle.read().decode("shift_jisx0213") root = ET.fromstring(xmldata) songs: List[Dict[str, Any]] = [] - for music_entry in root.find('body') or []: - songid = int(music_entry.find('music_id').text) - bpm_min = float(music_entry.find('bpm_min').text) - bpm_max = float(music_entry.find('bpm_max').text) + for music_entry in root.find("body") or []: + songid = int(music_entry.find("music_id").text) + bpm_min = float(music_entry.find("bpm_min").text) + bpm_max = float(music_entry.find("bpm_max").text) earliest_version = 0 - version = int(music_entry.find('version').text.strip(), 16) + version = int(music_entry.find("version").text.strip(), 16) while not version & 1: version >>= 1 earliest_version += 1 @@ -1488,40 +1501,44 @@ class ImportJubeat(ImportBase): } if bpm_max > 0 and bpm_min < 0: bpm_min = bpm_max - if music_entry.find('detail_level_bsc') is not None: + if music_entry.find("detail_level_bsc") is not None: difficulties = [ - float(music_entry.find('detail_level_bsc').text), - float(music_entry.find('detail_level_adv').text), - float(music_entry.find('detail_level_ext').text), + float(music_entry.find("detail_level_bsc").text), + float(music_entry.find("detail_level_adv").text), + float(music_entry.find("detail_level_ext").text), ] else: difficulties = [ - float(music_entry.find('level_bsc').text), - float(music_entry.find('level_adv').text), - float(music_entry.find('level_ext').text), + float(music_entry.find("level_bsc").text), + float(music_entry.find("level_adv").text), + float(music_entry.find("level_ext").text), ] genre = "other" - if music_entry.find('genre') is not None: # Qubell extend music_info doesn't have this field - for possible_genre in music_entry.find('genre'): + if ( + music_entry.find("genre") is not None + ): # Qubell extend music_info doesn't have this field + for possible_genre in music_entry.find("genre"): if int(possible_genre.text) != 0: genre = str(possible_genre.tag) - songs.append({ - 'id': songid, - # Title/artist aren't in the music data for Jubeat and must be manually populated. - # This is why there is a separate "import_metadata" and data file. - 'title': None, - 'artist': None, - 'genre': genre, - 'version': version_to_db_constant.get(earliest_version), - 'bpm_min': bpm_min, - 'bpm_max': bpm_max, - 'difficulty': { - 'basic': difficulties[0], - 'advanced': difficulties[1], - 'extreme': difficulties[2], - }, - }) + songs.append( + { + "id": songid, + # Title/artist aren't in the music data for Jubeat and must be manually populated. + # This is why there is a separate "import_metadata" and data file. + "title": None, + "artist": None, + "genre": genre, + "version": version_to_db_constant.get(earliest_version), + "bpm_min": bpm_min, + "bpm_max": bpm_max, + "difficulty": { + "basic": difficulties[0], + "advanced": difficulties[1], + "extreme": difficulties[2], + }, + } + ) emblems: List[Dict[str, Any]] = [] if self.version in { @@ -1530,38 +1547,42 @@ class ImportJubeat(ImportBase): VersionConstants.JUBEAT_CLAN, VersionConstants.JUBEAT_FESTO, }: - for emblem_entry in root.find('emblem_list') or []: + for emblem_entry in root.find("emblem_list") or []: print(emblem_entry) - index = int(emblem_entry.find('index').text) - layer = int(emblem_entry.find('layer').text) - music_id = int(emblem_entry.find('music_id').text) - evolved = int(emblem_entry.find('evolved').text) - rarity = int(emblem_entry.find('rarity').text) - name = emblem_entry.find('name').text + index = int(emblem_entry.find("index").text) + layer = int(emblem_entry.find("layer").text) + music_id = int(emblem_entry.find("music_id").text) + evolved = int(emblem_entry.find("evolved").text) + rarity = int(emblem_entry.find("rarity").text) + name = emblem_entry.find("name").text - emblems.append({ - 'id': index, - 'layer': layer, - 'music_id': music_id, - 'evolved': evolved, - 'rarity': rarity, - 'name': name, - }) + emblems.append( + { + "id": index, + "layer": layer, + "music_id": music_id, + "evolved": evolved, + "rarity": rarity, + "name": name, + } + ) return songs, emblems - def lookup(self, server: str, token: str) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]: + def lookup( + self, server: str, token: str + ) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]: if self.version is None: - raise Exception('Can\'t look up Jubeat database for \'all\' version!') + raise Exception("Can't look up Jubeat database for 'all' version!") # Grab music info from remote server music = self.remote_music(server, token) songs = music.get_all_songs(self.game, self.version) lut: Dict[int, Dict[str, Any]] = {} chart_map = { - 0: 'basic', - 1: 'advanced', - 2: 'extreme', + 0: "basic", + 1: "advanced", + 2: "extreme", } # Format it the way we expect @@ -1572,20 +1593,22 @@ class ImportJubeat(ImportBase): if song.id not in lut: lut[song.id] = { - 'id': song.id, - 'title': song.name, - 'artist': song.artist, - 'genre': song.genre, - 'version': song.data.get_int('version'), - 'bpm_min': song.data.get_float('bpm_min'), - 'bpm_max': song.data.get_float('bpm_max'), - 'difficulty': { - 'basic': 0.0, - 'advanced': 0.0, - 'extreme': 0.0, + "id": song.id, + "title": song.name, + "artist": song.artist, + "genre": song.genre, + "version": song.data.get_int("version"), + "bpm_min": song.data.get_float("bpm_min"), + "bpm_max": song.data.get_float("bpm_max"), + "difficulty": { + "basic": 0.0, + "advanced": 0.0, + "extreme": 0.0, }, } - lut[song.id]['difficulty'][chart_map[song.chart]] = song.data.get_float('difficulty') + lut[song.id]["difficulty"][chart_map[song.chart]] = song.data.get_float( + "difficulty" + ) # Reassemble the data reassembled_songs = [val for _, val in lut.items()] @@ -1600,14 +1623,16 @@ class ImportJubeat(ImportBase): game = self.remote_game(server, token) for item in game.get_items(self.game, self.version): if item.type == "emblem": - emblems.append({ - 'id': item.id, - 'layer': item.data.get_int('layer'), - 'music_id': item.data.get_int('music_id'), - 'evolved': item.data.get_int('evolved'), - 'rarity': item.data.get_int('rarity'), - 'name': item.data.get_str('name'), - }) + emblems.append( + { + "id": item.id, + "layer": item.data.get_int("layer"), + "music_id": item.data.get_int("music_id"), + "evolved": item.data.get_int("evolved"), + "rarity": item.data.get_int("rarity"), + "name": item.data.get_str("name"), + } + ) return reassembled_songs, emblems @@ -1643,24 +1668,24 @@ class ImportJubeat(ImportBase): def import_music_db(self, songs: List[Dict[str, Any]]) -> None: if self.version is None: - raise Exception('Can\'t import Jubeat database for \'all\' version!') + raise Exception("Can't import Jubeat database for 'all' version!") chart_map: Dict[int, str] = { - 0: 'basic', - 1: 'advanced', - 2: 'extreme', + 0: "basic", + 1: "advanced", + 2: "extreme", } for song in songs: # Skip over duplicate songs for the "play five different versions of this song # across different prefectures" event. The song ID range is 8000301-8000347, so # we arbitrarily choose to keep only the first one. - songid = song['id'] + songid = song["id"] if songid in set(range(80000302, 80000348)): continue self.start_batch() for chart in self.charts: - if(chart <= 2): + if chart <= 2: # First, try to find in the DB from another version old_id = self.__revivals(songid, chart) if self.no_combine or old_id is None: @@ -1672,10 +1697,10 @@ class ImportJubeat(ImportBase): print(f"Reused entry for {songid} chart {chart}") next_id = old_id data = { - 'difficulty': song['difficulty'][chart_map[chart]], - 'bpm_min': song['bpm_min'], - 'bpm_max': song['bpm_max'], - 'version': song['version'], + "difficulty": song["difficulty"][chart_map[chart]], + "bpm_min": song["bpm_min"], + "bpm_max": song["bpm_max"], + "version": song["version"], } else: # First, try to find in the DB from another version @@ -1689,17 +1714,25 @@ class ImportJubeat(ImportBase): print(f"Reused entry for {songid} chart {chart}") next_id = old_id data = { - 'difficulty': song['difficulty'][chart_map[chart - 3]], - 'bpm_min': song['bpm_min'], - 'bpm_max': song['bpm_max'], - 'version': song['version'], + "difficulty": song["difficulty"][chart_map[chart - 3]], + "bpm_min": song["bpm_min"], + "bpm_max": song["bpm_max"], + "version": song["version"], } - self.insert_music_id_for_song(next_id, songid, chart, song['title'], song['artist'], song['genre'], data) + self.insert_music_id_for_song( + next_id, + songid, + chart, + song["title"], + song["artist"], + song["genre"], + data, + ) self.finish_batch() def import_emblems(self, emblems: List[Dict[str, Any]]) -> None: if self.version is None: - raise Exception('Can\'t import Jubeat database for \'all\' version!') + raise Exception("Can't import Jubeat database for 'all' version!") self.start_batch() for i, emblem in enumerate(emblems): @@ -1710,14 +1743,14 @@ class ImportJubeat(ImportBase): print(f"New catalog entry for {emblem['music_id']}") self.insert_catalog_entry( - 'emblem', - emblem['id'], + "emblem", + emblem["id"], { - 'layer': emblem['layer'], - 'music_id': emblem['music_id'], - 'evolved': emblem['evolved'], - 'rarity': emblem['rarity'], - 'name': emblem['name'], + "layer": emblem["layer"], + "music_id": emblem["music_id"], + "evolved": emblem["evolved"], + "rarity": emblem["rarity"], + "name": emblem["name"], }, ) @@ -1725,10 +1758,12 @@ class ImportJubeat(ImportBase): def import_metadata(self, tsvfile: str) -> None: if self.version is not None: - raise Exception("Unsupported Jubeat version, expected one of the following: all") + raise Exception( + "Unsupported Jubeat version, expected one of the following: all" + ) - with open(tsvfile, newline='') as tsvhandle: - jubeatreader = csv.reader(tsvhandle, delimiter='\t', quotechar='"') + with open(tsvfile, newline="") as tsvhandle: + jubeatreader = csv.reader(tsvhandle, delimiter="\t", quotechar='"') for row in jubeatreader: songid = int(row[0]) name = row[1] @@ -1760,33 +1795,43 @@ class ImportIIDX(ImportBase): no_combine: bool, update: bool, ) -> None: - if version in ['20', '21', '22', '23', '24', '25', '26']: + if version in ["20", "21", "22", "23", "24", "25", "26"]: actual_version = { - '20': VersionConstants.IIDX_TRICORO, - '21': VersionConstants.IIDX_SPADA, - '22': VersionConstants.IIDX_PENDUAL, - '23': VersionConstants.IIDX_COPULA, - '24': VersionConstants.IIDX_SINOBUZ, - '25': VersionConstants.IIDX_CANNON_BALLERS, - '26': VersionConstants.IIDX_ROOTAGE, + "20": VersionConstants.IIDX_TRICORO, + "21": VersionConstants.IIDX_SPADA, + "22": VersionConstants.IIDX_PENDUAL, + "23": VersionConstants.IIDX_COPULA, + "24": VersionConstants.IIDX_SINOBUZ, + "25": VersionConstants.IIDX_CANNON_BALLERS, + "26": VersionConstants.IIDX_ROOTAGE, }[version] self.charts = [0, 1, 2, 3, 4, 5, 6] - elif version in ['omni-20', 'omni-21', 'omni-22', 'omni-23', 'omni-24', 'omni-25', 'omni-26']: + elif version in [ + "omni-20", + "omni-21", + "omni-22", + "omni-23", + "omni-24", + "omni-25", + "omni-26", + ]: actual_version = { - 'omni-20': VersionConstants.IIDX_TRICORO, - 'omni-21': VersionConstants.IIDX_SPADA, - 'omni-22': VersionConstants.IIDX_PENDUAL, - 'omni-23': VersionConstants.IIDX_COPULA, - 'omni-24': VersionConstants.IIDX_SINOBUZ, - 'omni-25': VersionConstants.IIDX_CANNON_BALLERS, - 'omni-26': VersionConstants.IIDX_ROOTAGE, + "omni-20": VersionConstants.IIDX_TRICORO, + "omni-21": VersionConstants.IIDX_SPADA, + "omni-22": VersionConstants.IIDX_PENDUAL, + "omni-23": VersionConstants.IIDX_COPULA, + "omni-24": VersionConstants.IIDX_SINOBUZ, + "omni-25": VersionConstants.IIDX_CANNON_BALLERS, + "omni-26": VersionConstants.IIDX_ROOTAGE, }[version] + DBConstants.OMNIMIX_VERSION_BUMP self.charts = [0, 1, 2, 3, 4, 5, 6] - elif version == 'all': + elif version == "all": actual_version = None self.charts = [0, 1, 2, 3, 4, 5, 6] else: - raise Exception("Unsupported IIDX version, expected one of the following: 20, 21, 22, 23, 24, 25, 26, omni-20, omni-21, omni-22, omni-23, omni-24, omni-25, omni-26!") + raise Exception( + "Unsupported IIDX version, expected one of the following: 20, 21, 22, 23, 24, 25, 26, omni-20, omni-21, omni-22, omni-23, omni-24, omni-25, omni-26!" + ) super().__init__(config, GameConstants.IIDX, actual_version, no_combine, update) @@ -1795,15 +1840,19 @@ class ImportIIDX(ImportBase): for (dirpath, dirnames, filenames) in os.walk(directory): for filename in filenames: songid, extension = os.path.splitext(filename) - if extension == '.1' or extension == '.ifs': + if extension == ".1" or extension == ".ifs": try: - files[int(songid)] = os.path.join(directory, os.path.join(dirpath, filename)) + files[int(songid)] = os.path.join( + directory, os.path.join(dirpath, filename) + ) except ValueError: # Invalid file pass for dirname in dirnames: - files.update(self.__gather_sound_files(os.path.join(directory, dirname))) + files.update( + self.__gather_sound_files(os.path.join(directory, dirname)) + ) return files @@ -1923,16 +1972,18 @@ class ImportIIDX(ImportBase): return 1 return chart - def scrape(self, binfile: str, assets_dir: Optional[str]) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]: + def scrape( + self, binfile: str, assets_dir: Optional[str] + ) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]: if self.version is None: - raise Exception('Can\'t import IIDX database for \'all\' version!') + raise Exception("Can't import IIDX database for 'all' version!") if assets_dir is not None: sound_files = self.__gather_sound_files(os.path.abspath(assets_dir)) else: sound_files = None - bh = open(binfile, 'rb') + bh = open(binfile, "rb") try: binarydata = bh.read() finally: @@ -1961,18 +2012,18 @@ class ImportIIDX(ImportBase): _, extension = os.path.splitext(filename) data = None - if extension == '.1': - fp = open(filename, 'rb') + if extension == ".1": + fp = open(filename, "rb") data = fp.read() fp.close() else: - fp = open(filename, 'rb') + fp = open(filename, "rb") ifsdata = fp.read() fp.close() ifs = IFS(ifsdata) for fn in ifs.filenames: _, extension = os.path.splitext(fn) - if extension == '.1': + if extension == ".1": data = ifs.read_file(fn) if data is not None: @@ -1981,40 +2032,46 @@ class ImportIIDX(ImportBase): bpm = (bpm_min, bpm_max) notecounts = iidxchart.notecounts else: - print(f"Could not find chart information for song {song.id}!") + print( + f"Could not find chart information for song {song.id}!" + ) else: - print(f"No chart information because chart for song {song.id} is missing!") - songs.append({ - 'id': song.id, - 'title': song.title, - 'artist': song.artist, - 'genre': song.genre, - 'bpm_min': bpm[0], - 'bpm_max': bpm[1], - 'difficulty': { - 'spn': song.difficulties[0], - 'sph': song.difficulties[1], - 'spa': song.difficulties[2], - 'dpn': song.difficulties[3], - 'dph': song.difficulties[4], - 'dpa': song.difficulties[5], - }, - 'notecount': { - 'spn': notecounts[0], - 'sph': notecounts[1], - 'spa': notecounts[2], - 'dpn': notecounts[3], - 'dph': notecounts[4], - 'dpa': notecounts[5], - }, - }) + print( + f"No chart information because chart for song {song.id} is missing!" + ) + songs.append( + { + "id": song.id, + "title": song.title, + "artist": song.artist, + "genre": song.genre, + "bpm_min": bpm[0], + "bpm_max": bpm[1], + "difficulty": { + "spn": song.difficulties[0], + "sph": song.difficulties[1], + "spa": song.difficulties[2], + "dpn": song.difficulties[3], + "dph": song.difficulties[4], + "dpa": song.difficulties[5], + }, + "notecount": { + "spn": notecounts[0], + "sph": notecounts[1], + "spa": notecounts[2], + "dpn": notecounts[3], + "dph": notecounts[4], + "dpa": notecounts[5], + }, + } + ) qpros: List[Dict[str, Any]] = [] if self.version == VersionConstants.IIDX_TRICORO: # Based on LDJ:J:A:A:2013090900 stride = 4 - qp_head_offset = 0x1CCB18 # qpro body parts are stored in 5 separate arrays in the game data, since there can be collision in - qp_head_length = 79 # the qpro id numbers, it's best to store them as separate types in the catalog as well. + qp_head_offset = 0x1CCB18 # qpro body parts are stored in 5 separate arrays in the game data, since there can be collision in + qp_head_length = 79 # the qpro id numbers, it's best to store them as separate types in the catalog as well. qp_hair_offset = 0x1CCC58 qp_hair_length = 103 qp_face_offset = 0x1CCDF8 @@ -2024,14 +2081,12 @@ class ImportIIDX(ImportBase): qp_body_offset = 0x1CD060 qp_body_length = 106 filename_offset = 0 - packedfmt = ( - 'I' # filename - ) + packedfmt = "I" # filename if self.version == VersionConstants.IIDX_SPADA: # Based on LDJ:J:A:A:2014071600 stride = 4 - qp_head_offset = 0x213B50 # qpro body parts are stored in 5 separate arrays in the game data, since there can be collision in - qp_head_length = 125 # the qpro id numbers, it's best to store them as separate types in the catalog as well. + qp_head_offset = 0x213B50 # qpro body parts are stored in 5 separate arrays in the game data, since there can be collision in + qp_head_length = 125 # the qpro id numbers, it's best to store them as separate types in the catalog as well. qp_hair_offset = 0x213D48 qp_hair_length = 126 qp_face_offset = 0x213F40 @@ -2041,14 +2096,12 @@ class ImportIIDX(ImportBase): qp_body_offset = 0x214280 qp_body_length = 135 filename_offset = 0 - packedfmt = ( - 'I' # filename - ) + packedfmt = "I" # filename if self.version == VersionConstants.IIDX_PENDUAL: # Based on LDJ:J:A:A:2015080500 stride = 4 - qp_head_offset = 0x1D5228 # qpro body parts are stored in 5 separate arrays in the game data, since there can be collision in - qp_head_length = 163 # the qpro id numbers, it's best to store them as separate types in the catalog as well. + qp_head_offset = 0x1D5228 # qpro body parts are stored in 5 separate arrays in the game data, since there can be collision in + qp_head_length = 163 # the qpro id numbers, it's best to store them as separate types in the catalog as well. qp_hair_offset = 0x1D54B8 qp_hair_length = 182 qp_face_offset = 0x1D5790 @@ -2058,14 +2111,12 @@ class ImportIIDX(ImportBase): qp_body_offset = 0x1D5C18 qp_body_length = 191 filename_offset = 0 - packedfmt = ( - 'I' # filename - ) + packedfmt = "I" # filename if self.version == VersionConstants.IIDX_COPULA: # Based on LDJ:J:A:A:2016083100 stride = 8 - qp_head_offset = 0x12F9D8 # qpro body parts are stored in 5 separate arrays in the game data, since there can be collision in - qp_head_length = 186 # the qpro id numbers, it's best to store them as separate types in the catalog as well. + qp_head_offset = 0x12F9D8 # qpro body parts are stored in 5 separate arrays in the game data, since there can be collision in + qp_head_length = 186 # the qpro id numbers, it's best to store them as separate types in the catalog as well. qp_hair_offset = 0x12FFA8 qp_hair_length = 202 qp_face_offset = 0x1305F8 @@ -2076,15 +2127,12 @@ class ImportIIDX(ImportBase): qp_body_length = 211 filename_offset = 0 qpro_id_offset = 1 - packedfmt = ( - 'I' # filename - 'I' # string containing id and name of the part - ) + packedfmt = "I" "I" # filename # string containing id and name of the part if self.version == VersionConstants.IIDX_SINOBUZ: # Based on LDJ:J:A:A:2017082800 stride = 8 - qp_head_offset = 0x149F88 # qpro body parts are stored in 5 separate arrays in the game data, since there can be collision in - qp_head_length = 211 # the qpro id numbers, it's best to store them as separate types in the catalog as well. + qp_head_offset = 0x149F88 # qpro body parts are stored in 5 separate arrays in the game data, since there can be collision in + qp_head_length = 211 # the qpro id numbers, it's best to store them as separate types in the catalog as well. qp_hair_offset = 0x14A620 qp_hair_length = 245 qp_face_offset = 0x14ADC8 @@ -2095,15 +2143,12 @@ class ImportIIDX(ImportBase): qp_body_length = 256 filename_offset = 0 qpro_id_offset = 1 - packedfmt = ( - 'I' # filename - 'I' # string containing id and name of the part - ) + packedfmt = "I" "I" # filename # string containing id and name of the part if self.version == VersionConstants.IIDX_CANNON_BALLERS: # Based on LDJ:J:A:A:2018091900 stride = 16 - qp_head_offset = 0x2339E0 # qpro body parts are stored in 5 separate arrays in the game data, since there can be collision in - qp_head_length = 231 # the qpro id numbers, it's best to store them as separate types in the catalog as well. + qp_head_offset = 0x2339E0 # qpro body parts are stored in 5 separate arrays in the game data, since there can be collision in + qp_head_length = 231 # the qpro id numbers, it's best to store them as separate types in the catalog as well. qp_hair_offset = 0x234850 qp_hair_length = 267 qp_face_offset = 0x235900 @@ -2114,15 +2159,12 @@ class ImportIIDX(ImportBase): qp_body_length = 282 filename_offset = 0 qpro_id_offset = 1 - packedfmt = ( - 'Q' # filename - 'Q' # string containing id and name of the part - ) + packedfmt = "Q" "Q" # filename # string containing id and name of the part if self.version == VersionConstants.IIDX_ROOTAGE: # Based on LDJ:J:A:A:2019090200 stride = 16 - qp_head_offset = 0x5065F0 # qpro body parts are stored in 5 separate arrays in the game data, since there can be collision in - qp_head_length = 259 # the qpro id numbers, it's best to store them as separate types in the catalog as well. + qp_head_offset = 0x5065F0 # qpro body parts are stored in 5 separate arrays in the game data, since there can be collision in + qp_head_length = 259 # the qpro id numbers, it's best to store them as separate types in the catalog as well. qp_hair_offset = 0x507620 qp_hair_length = 288 qp_face_offset = 0x508820 @@ -2133,10 +2175,7 @@ class ImportIIDX(ImportBase): qp_body_length = 304 filename_offset = 0 qpro_id_offset = 1 - packedfmt = ( - 'Q' # filename - 'Q' # string containing id and name of the part - ) + packedfmt = "Q" "Q" # filename # string containing id and name of the part def read_string(offset: int) -> str: # First, translate load offset in memory to disk offset @@ -2149,51 +2188,64 @@ class ImportIIDX(ImportBase): offset = offset + 1 # Its shift-jis encoded, so decode it now - return bytes(bytestring).decode('shift_jisx0213') + return bytes(bytestring).decode("shift_jisx0213") def read_qpro_db(offset: int, length: int, qp_type: str) -> None: for qpro_id in range(length): chunkoffset = offset + (stride * qpro_id) - chunkdata = binarydata[chunkoffset:(chunkoffset + stride)] + chunkdata = binarydata[chunkoffset : (chunkoffset + stride)] unpacked = struct.unpack(packedfmt, chunkdata) - filename = read_string(unpacked[filename_offset]).replace('qp_', '') - remove = f'_{qp_type}.ifs' - filename = filename.replace(remove, '').replace('_head1.ifs', '').replace('_head2.ifs', '') - if self.version in [VersionConstants.IIDX_TRICORO, VersionConstants.IIDX_SPADA, VersionConstants.IIDX_PENDUAL]: + filename = read_string(unpacked[filename_offset]).replace("qp_", "") + remove = f"_{qp_type}.ifs" + filename = ( + filename.replace(remove, "") + .replace("_head1.ifs", "") + .replace("_head2.ifs", "") + ) + if self.version in [ + VersionConstants.IIDX_TRICORO, + VersionConstants.IIDX_SPADA, + VersionConstants.IIDX_PENDUAL, + ]: name = filename # qpro names are not stored in these 3 games so use the identifier instead else: - name = read_string(unpacked[qpro_id_offset])[4:] # qpro name is stored in second string of form "000:name" + name = read_string(unpacked[qpro_id_offset])[ + 4: + ] # qpro name is stored in second string of form "000:name" qproinfo = { - 'identifier': filename, - 'id': qpro_id, - 'name': name, - 'type': qp_type, + "identifier": filename, + "id": qpro_id, + "name": name, + "type": qp_type, } qpros.append(qproinfo) + if import_qpros: - read_qpro_db(qp_head_offset, qp_head_length, 'head') - read_qpro_db(qp_hair_offset, qp_hair_length, 'hair') - read_qpro_db(qp_face_offset, qp_face_length, 'face') - read_qpro_db(qp_hand_offset, qp_hand_length, 'hand') - read_qpro_db(qp_body_offset, qp_body_length, 'body') + read_qpro_db(qp_head_offset, qp_head_length, "head") + read_qpro_db(qp_hair_offset, qp_hair_length, "hair") + read_qpro_db(qp_face_offset, qp_face_length, "face") + read_qpro_db(qp_hand_offset, qp_hand_length, "hand") + read_qpro_db(qp_body_offset, qp_body_length, "body") return songs, qpros - def lookup(self, server: str, token: str) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]: + def lookup( + self, server: str, token: str + ) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]: if self.version is None: - raise Exception('Can\'t look up IIDX database for \'all\' version!') + raise Exception("Can't look up IIDX database for 'all' version!") # Grab music info from remote server music = self.remote_music(server, token) songs = music.get_all_songs(self.game, self.version) lut: Dict[int, Dict[str, Any]] = {} chart_map = { - 0: 'spn', - 1: 'sph', - 2: 'spa', - 3: 'dpn', - 4: 'dph', - 5: 'dpa', + 0: "spn", + 1: "sph", + 2: "spa", + 3: "dpn", + 4: "dph", + 5: "dpa", } # Format it the way we expect @@ -2206,59 +2258,65 @@ class ImportIIDX(ImportBase): if song.id not in lut: lut[song.id] = { - 'id': song.id, - 'title': song.name, - 'artist': song.artist, - 'genre': song.genre, - 'bpm_min': song.data.get_int('bpm_min'), - 'bpm_max': song.data.get_int('bpm_max'), - 'difficulty': { - 'spn': 0, - 'sph': 0, - 'spa': 0, - 'dpn': 0, - 'dph': 0, - 'dpa': 0, + "id": song.id, + "title": song.name, + "artist": song.artist, + "genre": song.genre, + "bpm_min": song.data.get_int("bpm_min"), + "bpm_max": song.data.get_int("bpm_max"), + "difficulty": { + "spn": 0, + "sph": 0, + "spa": 0, + "dpn": 0, + "dph": 0, + "dpa": 0, }, - 'notecount': { - 'spn': 0, - 'sph': 0, - 'spa': 0, - 'dpn': 0, - 'dph': 0, - 'dpa': 0, + "notecount": { + "spn": 0, + "sph": 0, + "spa": 0, + "dpn": 0, + "dph": 0, + "dpa": 0, }, } if song.chart in chart_map: - lut[song.id]['difficulty'][chart_map[song.chart]] = song.data.get_int('difficulty') - lut[song.id]['notecount'][chart_map[song.chart]] = song.data.get_int('notecount') + lut[song.id]["difficulty"][chart_map[song.chart]] = song.data.get_int( + "difficulty" + ) + lut[song.id]["notecount"][chart_map[song.chart]] = song.data.get_int( + "notecount" + ) # Return the reassembled data qpros: List[Dict[str, Any]] = [] game = self.remote_game(server, token) for item in game.get_items(self.game, self.version): - if 'qp_' in item.type: - qpros.append({ - 'identifier': item.data.get_str('identifier'), - 'id': item.id, - 'name': item.data.get_str('name'), - 'type': item.data.get_str('type'), - }) + if "qp_" in item.type: + qpros.append( + { + "identifier": item.data.get_str("identifier"), + "id": item.id, + "name": item.data.get_str("name"), + "type": item.data.get_str("type"), + } + ) return [val for _, val in lut.items()], qpros def import_music_db(self, songs: List[Dict[str, Any]]) -> None: if self.version is None: - raise Exception('Can\'t import IIDX database for \'all\' version!') + raise Exception("Can't import IIDX database for 'all' version!") # Import each song into our DB chart_map = { - 0: 'spn', - 1: 'sph', - 2: 'spa', - 3: 'dpn', - 4: 'dph', - 5: 'dpa', + 0: "spn", + 1: "sph", + 2: "spa", + 3: "dpn", + 4: "dph", + 5: "dpa", } for song in songs: self.start_batch() @@ -2268,13 +2326,13 @@ class ImportIIDX(ImportBase): songdata: Dict[str, Any] = {} else: songdata = { - 'difficulty': song['difficulty'][chart_map[chart]], - 'bpm_min': song['bpm_min'], - 'bpm_max': song['bpm_max'], - 'notecount': song['notecount'][chart_map[chart]], + "difficulty": song["difficulty"][chart_map[chart]], + "bpm_min": song["bpm_min"], + "bpm_max": song["bpm_max"], + "notecount": song["notecount"][chart_map[chart]], } # First, try to find in the DB from another version - old_id = self.__revivals(song['id'], self.__charts(song['id'], chart)) + old_id = self.__revivals(song["id"], self.__charts(song["id"], chart)) if self.no_combine or old_id is None: # Insert original print(f"New entry for {song['id']} chart {chart}") @@ -2283,12 +2341,20 @@ class ImportIIDX(ImportBase): # Insert pointing at same ID so scores transfer print(f"Reused entry for {song['id']} chart {chart}") next_id = old_id - self.insert_music_id_for_song(next_id, song['id'], chart, song['title'], song['artist'], song['genre'], songdata) + self.insert_music_id_for_song( + next_id, + song["id"], + chart, + song["title"], + song["artist"], + song["genre"], + songdata, + ) self.finish_batch() def import_qpros(self, qpros: List[Dict[str, Any]]) -> None: if self.version is None: - raise Exception('Can\'t import IIDX database for \'all\' version!') + raise Exception("Can't import IIDX database for 'all' version!") self.start_batch() for i, qpro in enumerate(qpros): @@ -2300,10 +2366,10 @@ class ImportIIDX(ImportBase): print(f"New catalog entry for {qpro['id']}") self.insert_catalog_entry( f"qp_{qpro['type']}", - qpro['id'], + qpro["id"], { - 'name': qpro['name'], - 'identifier': qpro['identifier'], + "name": qpro["name"], + "identifier": qpro["identifier"], }, ) @@ -2311,10 +2377,12 @@ class ImportIIDX(ImportBase): def import_metadata(self, tsvfile: str) -> None: if self.version is not None: - raise Exception("Unsupported IIDX version, expected one of the following: all") + raise Exception( + "Unsupported IIDX version, expected one of the following: all" + ) - with open(tsvfile, newline='') as tsvhandle: - iidxreader = csv.reader(tsvhandle, delimiter='\t', quotechar='"') + with open(tsvfile, newline="") as tsvhandle: + iidxreader = csv.reader(tsvhandle, delimiter="\t", quotechar='"') for row in iidxreader: songid = int(row[0]) name = row[1] @@ -2336,7 +2404,6 @@ class ImportIIDX(ImportBase): class ImportDDR(ImportBase): - def __init__( self, config: Config, @@ -2344,17 +2411,19 @@ class ImportDDR(ImportBase): no_combine: bool, update: bool, ) -> None: - if version in ['12', '13', '14', '15', '16']: + if version in ["12", "13", "14", "15", "16"]: actual_version = { - '12': VersionConstants.DDR_X2, - '13': VersionConstants.DDR_X3_VS_2NDMIX, - '14': VersionConstants.DDR_2013, - '15': VersionConstants.DDR_2014, - '16': VersionConstants.DDR_ACE, + "12": VersionConstants.DDR_X2, + "13": VersionConstants.DDR_X3_VS_2NDMIX, + "14": VersionConstants.DDR_2013, + "15": VersionConstants.DDR_2014, + "16": VersionConstants.DDR_ACE, }[version] self.charts = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] else: - raise Exception("Unsupported DDR version, expected one of the following: 12, 13, 14, 15, 16") + raise Exception( + "Unsupported DDR version, expected one of the following: 12, 13, 14, 15, 16" + ) super().__init__(config, GameConstants.DDR, actual_version, no_combine, update) @@ -2365,18 +2434,18 @@ class ImportDDR(ImportBase): if self.version == VersionConstants.DDR_X2: # Based on JDX:J:A:A:2010111000 - offset = 0x254fc0 + offset = 0x254FC0 size = 0x14C length = 894 # Basic stuff like ID, bpm, chart difficulties - unpackfmt = ' None: actual_version = { - '1': VersionConstants.SDVX_BOOTH, - '2': VersionConstants.SDVX_INFINITE_INFECTION, - '3': VersionConstants.SDVX_GRAVITY_WARS, - '4': VersionConstants.SDVX_HEAVENLY_HAVEN, + "1": VersionConstants.SDVX_BOOTH, + "2": VersionConstants.SDVX_INFINITE_INFECTION, + "3": VersionConstants.SDVX_GRAVITY_WARS, + "4": VersionConstants.SDVX_HEAVENLY_HAVEN, }.get(version, -1) if actual_version == VersionConstants.SDVX_BOOTH: self.charts = [0, 1, 2] - elif actual_version in [VersionConstants.SDVX_INFINITE_INFECTION, VersionConstants.SDVX_GRAVITY_WARS]: + elif actual_version in [ + VersionConstants.SDVX_INFINITE_INFECTION, + VersionConstants.SDVX_GRAVITY_WARS, + ]: self.charts = [0, 1, 2, 3] elif actual_version == VersionConstants.SDVX_HEAVENLY_HAVEN: self.charts = [0, 1, 2, 3, 4] else: - raise Exception("Unsupported SDVX version, expected one of the following: 1, 2, 3, 4!") + raise Exception( + "Unsupported SDVX version, expected one of the following: 1, 2, 3, 4!" + ) super().__init__(config, GameConstants.SDVX, actual_version, no_combine, update) @@ -3047,7 +3146,7 @@ class ImportSDVX(ImportBase): size = 163 stride = 40 else: - raise Exception('Unsupported version for catalog scrape!') + raise Exception("Unsupported version for catalog scrape!") def read_string(spot: int) -> str: # First, translate load offset in memory to disk offset @@ -3059,7 +3158,7 @@ class ImportSDVX(ImportBase): bytestring.append(data[spot]) spot = spot + 1 - return bytes(bytestring).decode('shift_jis') + return bytes(bytestring).decode("shift_jis") entries = [] for i in range(size): @@ -3067,17 +3166,17 @@ class ImportSDVX(ImportBase): end = offset + (i + 1) * stride chunk = data[start:end] - values = struct.unpack(' None: - with open(csvfile, 'rb') as csvhandle: - csvdata = csvhandle.read().decode('shift_jisx0213') + with open(csvfile, "rb") as csvhandle: + csvdata = csvhandle.read().decode("shift_jisx0213") csvstr = io.StringIO(csvdata) - appealreader = csv.reader(csvstr, delimiter=',', quotechar='"') + appealreader = csv.reader(csvstr, delimiter=",", quotechar='"') for row in appealreader: appealids = [] if self.version == VersionConstants.SDVX_INFINITE_INFECTION: @@ -3118,28 +3217,30 @@ class ImportSDVX(ImportBase): except (TypeError, ValueError): pass else: - raise Exception(f'Cannot import appeal cards for SDVX version {self.version}') + raise Exception( + f"Cannot import appeal cards for SDVX version {self.version}" + ) self.start_batch() for appealid in appealids: print(f"New catalog entry for appeal card {appealid}") self.insert_catalog_entry( - 'appealcard', + "appealcard", appealid, {}, ) self.finish_batch() def import_music_db_or_appeal_cards(self, xmlfile: str) -> None: - with open(xmlfile, 'rb') as fp: + with open(xmlfile, "rb") as fp: # This is gross, but elemtree won't do it for us so whatever bytedata = fp.read() - strdata = bytedata.decode('shift_jisx0213', errors='replace') + strdata = bytedata.decode("shift_jisx0213", errors="replace") root = ET.fromstring(strdata) - for music_entry in root.findall('music'): + for music_entry in root.findall("music"): # Grab the ID - songid = int(music_entry.attrib['id']) + songid = int(music_entry.attrib["id"]) title = None artist = None bpm_min = None @@ -3149,92 +3250,105 @@ class ImportSDVX(ImportBase): if self.version == VersionConstants.SDVX_BOOTH: # Find normal info about the song - for info in music_entry.findall('info'): - if info.attrib['attr'] == 'title_yomigana': + for info in music_entry.findall("info"): + if info.attrib["attr"] == "title_yomigana": title = jaconv.h2z(info.text) - if info.attrib['attr'] == 'artist_yomigana': + if info.attrib["attr"] == "artist_yomigana": artist = jaconv.h2z(info.text) - if info.attrib['attr'] == 'bpm_min': + if info.attrib["attr"] == "bpm_min": bpm_min = float(info.text) - if info.attrib['attr'] == 'bpm_max': + if info.attrib["attr"] == "bpm_max": bpm_max = float(info.text) - if info.attrib['attr'] == 'limited': - limited = [int(info.text), int(info.text), int(info.text), int(info.text)] + if info.attrib["attr"] == "limited": + limited = [ + int(info.text), + int(info.text), + int(info.text), + int(info.text), + ] # Make sure we got everything - if title is None or artist is None or bpm_min is None or bpm_max is None: - raise Exception(f'Couldn\'t parse info for song {songid}') + if ( + title is None + or artist is None + or bpm_min is None + or bpm_max is None + ): + raise Exception(f"Couldn't parse info for song {songid}") # Grab valid difficulties - for difficulty in music_entry.findall('difficulty'): + for difficulty in music_entry.findall("difficulty"): # Figure out the actual difficulty offset = { - 'novice': 0, - 'advanced': 1, - 'exhaust': 2, - }.get(difficulty.attrib['attr']) + "novice": 0, + "advanced": 1, + "exhaust": 2, + }.get(difficulty.attrib["attr"]) if offset is None: continue - difficulties[offset] = int(difficulty.find('difnum').text) - elif self.version in [VersionConstants.SDVX_INFINITE_INFECTION, VersionConstants.SDVX_GRAVITY_WARS]: + difficulties[offset] = int(difficulty.find("difnum").text) + elif self.version in [ + VersionConstants.SDVX_INFINITE_INFECTION, + VersionConstants.SDVX_GRAVITY_WARS, + ]: # Find normal info about the song - info = music_entry.find('info') - title = info.find('title_name').text - artist = info.find('artist_name').text - bpm_min = float(info.find('bpm_min').text) / 100.0 - bpm_max = float(info.find('bpm_max').text) / 100.0 + info = music_entry.find("info") + title = info.find("title_name").text + artist = info.find("artist_name").text + bpm_min = float(info.find("bpm_min").text) / 100.0 + bpm_max = float(info.find("bpm_max").text) / 100.0 # Grab valid difficulties - for difficulty in music_entry.find('difficulty'): + for difficulty in music_entry.find("difficulty"): # Figure out the actual difficulty offset = { - 'novice': 0, - 'advanced': 1, - 'exhaust': 2, - 'infinite': 3, + "novice": 0, + "advanced": 1, + "exhaust": 2, + "infinite": 3, }.get(difficulty.tag) if offset is None: continue - difficulties[offset] = int(difficulty.find('difnum').text) - limited[offset] = int(difficulty.find('limited').text) + difficulties[offset] = int(difficulty.find("difnum").text) + limited[offset] = int(difficulty.find("limited").text) elif self.version == VersionConstants.SDVX_HEAVENLY_HAVEN: # Find normal info about the song - info = music_entry.find('info') - title = info.find('title_name').text - artist = info.find('artist_name').text - bpm_min = float(info.find('bpm_min').text) / 100.0 - bpm_max = float(info.find('bpm_max').text) / 100.0 + info = music_entry.find("info") + title = info.find("title_name").text + artist = info.find("artist_name").text + bpm_min = float(info.find("bpm_min").text) / 100.0 + bpm_max = float(info.find("bpm_max").text) / 100.0 # Grab valid difficulties - for difficulty in music_entry.find('difficulty'): + for difficulty in music_entry.find("difficulty"): # Figure out the actual difficulty offset = { - 'novice': 0, - 'advanced': 1, - 'exhaust': 2, - 'infinite': 3, - 'maximum': 4, + "novice": 0, + "advanced": 1, + "exhaust": 2, + "infinite": 3, + "maximum": 4, }.get(difficulty.tag) if offset is None: continue - difficulties[offset] = int(difficulty.find('difnum').text) - limited[offset] = int(difficulty.find('limited').text) + difficulties[offset] = int(difficulty.find("difnum").text) + limited[offset] = int(difficulty.find("limited").text) # Fix accent issues with title/artist accent_lut: Dict[str, str] = { - '驩': 'Ø', - '齲': '♥', - '齶': '♡', - '趁': 'Ǣ', - '騫': 'á', - '曦': 'à', - '驫': 'ā', - '齷': 'é', - '曩': 'è', - '䧺': 'ê', - '骭': 'ü', + "驩": "Ø", + "齲": "♥", + "齶": "♡", + "趁": "Ǣ", + "騫": "á", + "曦": "à", + "驫": "ā", + "齷": "é", + "曩": "è", + "䧺": "ê", + "骭": "ü", } for orig, rep in accent_lut.items(): @@ -3255,25 +3369,27 @@ class ImportSDVX(ImportBase): print(f"Reused entry for {songid} chart {chart}") next_id = old_id data = { - 'limited': limited[chart], - 'difficulty': difficulties[chart], - 'bpm_min': bpm_min, - 'bpm_max': bpm_max, + "limited": limited[chart], + "difficulty": difficulties[chart], + "bpm_min": bpm_min, + "bpm_max": bpm_max, } - self.insert_music_id_for_song(next_id, songid, chart, title, artist, None, data) + self.insert_music_id_for_song( + next_id, songid, chart, title, artist, None, data + ) self.finish_batch() appealids: List[int] = [] - for appeal_entry in root.findall('card'): + for appeal_entry in root.findall("card"): # Grab the ID - appealids.append(int(appeal_entry.attrib['id'])) + appealids.append(int(appeal_entry.attrib["id"])) if appealids: self.start_batch() for appealid in appealids: print(f"New catalog entry for appeal card {appealid}") self.insert_catalog_entry( - 'appealcard', + "appealcard", appealid, {}, ) @@ -3296,8 +3412,9 @@ class ImportSDVX(ImportBase): entry.name, entry.artist, entry.genre, - {} - ) for chart in self.charts + {}, + ) + for chart in self.charts } music_lut[entry.id][entry.chart] = entry @@ -3315,12 +3432,14 @@ class ImportSDVX(ImportBase): print(f"Reused entry for {song.id} chart {song.chart}") next_id = old_id data = { - 'limited': song.data.get_int('limited'), - 'difficulty': song.data.get_int('difficulty'), - 'bpm_min': song.data.get_int('bpm_min'), - 'bpm_max': song.data.get_int('bpm_max'), + "limited": song.data.get_int("limited"), + "difficulty": song.data.get_int("difficulty"), + "bpm_min": song.data.get_int("bpm_min"), + "bpm_max": song.data.get_int("bpm_max"), } - self.insert_music_id_for_song(next_id, song.id, song.chart, song.name, song.artist, None, data) + self.insert_music_id_for_song( + next_id, song.id, song.chart, song.name, song.artist, None, data + ) self.finish_batch() # Now, attempt to insert any catalog items we got for this version. @@ -3330,26 +3449,27 @@ class ImportSDVX(ImportBase): if item.type == "appealcard": print(f"New catalog entry for appeal card {item.id}") self.insert_catalog_entry( - 'appealcard', + "appealcard", item.id, {}, ) elif item.type == "song_unlock": - print(f"New catalog entry for {item.data.get_int('musicid')} chart {item.data.get_int('chart')}") + print( + f"New catalog entry for {item.data.get_int('musicid')} chart {item.data.get_int('chart')}" + ) self.insert_catalog_entry( - 'song_unlock', + "song_unlock", item.id, { - 'blocks': item.data.get_int('blocks'), - 'musicid': item.data.get_int('musicid'), - 'chart': item.data.get_int('chart'), + "blocks": item.data.get_int("blocks"), + "musicid": item.data.get_int("musicid"), + "chart": item.data.get_int("chart"), }, ) self.finish_batch() class ImportMuseca(ImportBase): - def __init__( self, config: Config, @@ -3357,29 +3477,38 @@ class ImportMuseca(ImportBase): no_combine: bool, update: bool, ) -> None: - if version in ['1', '1+1/2', 'plus']: + if version in ["1", "1+1/2", "plus"]: actual_version = { - '1': VersionConstants.MUSECA, - '1+1/2': VersionConstants.MUSECA_1_PLUS, - 'plus': VersionConstants.MUSECA_1_PLUS + DBConstants.OMNIMIX_VERSION_BUMP, + "1": VersionConstants.MUSECA, + "1+1/2": VersionConstants.MUSECA_1_PLUS, + "plus": VersionConstants.MUSECA_1_PLUS + + DBConstants.OMNIMIX_VERSION_BUMP, }.get(version, -1) - if actual_version in [VersionConstants.MUSECA, VersionConstants.MUSECA_1_PLUS, VersionConstants.MUSECA_1_PLUS + DBConstants.OMNIMIX_VERSION_BUMP]: + if actual_version in [ + VersionConstants.MUSECA, + VersionConstants.MUSECA_1_PLUS, + VersionConstants.MUSECA_1_PLUS + DBConstants.OMNIMIX_VERSION_BUMP, + ]: self.charts = [0, 1, 2, 3] else: - raise Exception("Unsupported Museca version, expected one of the following: 1, 1+1/2, plus!") + raise Exception( + "Unsupported Museca version, expected one of the following: 1, 1+1/2, plus!" + ) - super().__init__(config, GameConstants.MUSECA, actual_version, no_combine, update) + super().__init__( + config, GameConstants.MUSECA, actual_version, no_combine, update + ) def import_music_db(self, xmlfile: str) -> None: - with open(xmlfile, 'rb') as fp: + with open(xmlfile, "rb") as fp: # This is gross, but elemtree won't do it for us so whatever bytedata = fp.read() - strdata = bytedata.decode('shift_jisx0213') + strdata = bytedata.decode("shift_jisx0213") root = ET.fromstring(strdata) - for music_entry in root.findall('music'): + for music_entry in root.findall("music"): # Grab the ID - songid = int(music_entry.attrib['id']) + songid = int(music_entry.attrib["id"]) title = None artist = None bpm_min = None @@ -3388,26 +3517,26 @@ class ImportMuseca(ImportBase): difficulties = [0, 0, 0, 0] # Find normal info about the song - info = music_entry.find('info') - title = info.find('title_name').text - artist = info.find('artist_name').text - bpm_min = float(info.find('bpm_min').text) / 100.0 - bpm_max = float(info.find('bpm_max').text) / 100.0 + info = music_entry.find("info") + title = info.find("title_name").text + artist = info.find("artist_name").text + bpm_min = float(info.find("bpm_min").text) / 100.0 + bpm_max = float(info.find("bpm_max").text) / 100.0 # Grab valid difficulties - for difficulty in music_entry.find('difficulty'): + for difficulty in music_entry.find("difficulty"): # Figure out the actual difficulty offset = { - 'novice': 0, - 'advanced': 1, - 'exhaust': 2, - 'infinite': 3, + "novice": 0, + "advanced": 1, + "exhaust": 2, + "infinite": 3, }.get(difficulty.tag) if offset is None: continue - difficulties[offset] = int(difficulty.find('difnum').text) - limited[offset] = int(difficulty.find('limited').text) + difficulties[offset] = int(difficulty.find("difnum").text) + limited[offset] = int(difficulty.find("limited").text) # Import it self.start_batch() @@ -3423,12 +3552,14 @@ class ImportMuseca(ImportBase): print(f"Reused entry for {songid} chart {chart}") next_id = old_id data = { - 'limited': limited[chart], - 'difficulty': difficulties[chart], - 'bpm_min': bpm_min, - 'bpm_max': bpm_max, + "limited": limited[chart], + "difficulty": difficulties[chart], + "bpm_min": bpm_min, + "bpm_max": bpm_max, } - self.insert_music_id_for_song(next_id, songid, chart, title, artist, None, data) + self.insert_music_id_for_song( + next_id, songid, chart, title, artist, None, data + ) self.finish_batch() def import_from_server(self, server: str, token: str) -> None: @@ -3448,8 +3579,9 @@ class ImportMuseca(ImportBase): entry.name, entry.artist, entry.genre, - {} - ) for chart in self.charts + {}, + ) + for chart in self.charts } music_lut[entry.id][entry.chart] = entry @@ -3468,17 +3600,18 @@ class ImportMuseca(ImportBase): print(f"Reused entry for {song.id} chart {song.chart}") next_id = old_id data = { - 'limited': song.data.get_int('limited'), - 'difficulty': song.data.get_int('difficulty'), - 'bpm_min': song.data.get_int('bpm_min'), - 'bpm_max': song.data.get_int('bpm_max'), + "limited": song.data.get_int("limited"), + "difficulty": song.data.get_int("difficulty"), + "bpm_min": song.data.get_int("bpm_min"), + "bpm_max": song.data.get_int("bpm_max"), } - self.insert_music_id_for_song(next_id, song.id, song.chart, song.name, song.artist, None, data) + self.insert_music_id_for_song( + next_id, song.id, song.chart, song.name, song.artist, None, data + ) self.finish_batch() class ImportReflecBeat(ImportBase): - def __init__( self, config: Config, @@ -3489,20 +3622,24 @@ class ImportReflecBeat(ImportBase): # We always have 4 charts, even if we're importing from Colette and below, # so that we guarantee a stable song ID. We'll be in trouble if Reflec # ever adds a fifth chart. - if version in ['1', '2', '3', '4', '5', '6']: + if version in ["1", "2", "3", "4", "5", "6"]: actual_version = { - '1': VersionConstants.REFLEC_BEAT, - '2': VersionConstants.REFLEC_BEAT_LIMELIGHT, - '3': VersionConstants.REFLEC_BEAT_COLETTE, - '4': VersionConstants.REFLEC_BEAT_GROOVIN, - '5': VersionConstants.REFLEC_BEAT_VOLZZA, - '6': VersionConstants.REFLEC_BEAT_VOLZZA_2, + "1": VersionConstants.REFLEC_BEAT, + "2": VersionConstants.REFLEC_BEAT_LIMELIGHT, + "3": VersionConstants.REFLEC_BEAT_COLETTE, + "4": VersionConstants.REFLEC_BEAT_GROOVIN, + "5": VersionConstants.REFLEC_BEAT_VOLZZA, + "6": VersionConstants.REFLEC_BEAT_VOLZZA_2, }[version] self.charts = [0, 1, 2, 3] else: - raise Exception("Unsupported ReflecBeat version, expected one of the following: 1, 2, 3, 4, 5, 6") + raise Exception( + "Unsupported ReflecBeat version, expected one of the following: 1, 2, 3, 4, 5, 6" + ) - super().__init__(config, GameConstants.REFLEC_BEAT, actual_version, no_combine, update) + super().__init__( + config, GameConstants.REFLEC_BEAT, actual_version, no_combine, update + ) def scrape(self, infile: str) -> List[Dict[str, Any]]: with open(infile, mode="rb") as myfile: @@ -3550,7 +3687,7 @@ class ImportReflecBeat(ImportBase): song_length = 0x80 artist_offset = 0xB4 artist_length = 0x80 - chart_offset = 0x1b4 + chart_offset = 0x1B4 chart_length = 0x20 difficulties_offset = 0x1A8 elif self.version == VersionConstants.REFLEC_BEAT_GROOVIN: @@ -3596,7 +3733,7 @@ class ImportReflecBeat(ImportBase): chart_length = 0x20 difficulties_offset = 0x1CC else: - raise Exception(f'Unsupported ReflecBeat version {self.version}') + raise Exception(f"Unsupported ReflecBeat version {self.version}") def convert_string(inb: bytes) -> str: end = None @@ -3605,11 +3742,11 @@ class ImportReflecBeat(ImportBase): end = i break if end is None: - raise Exception('Invalid string!') + raise Exception("Invalid string!") if end == 0: - return '' + return "" - return inb[:end].decode('shift_jisx0213') + return inb[:end].decode("shift_jisx0213") def convert_version(songid: int, folder: int) -> int: if self.version == VersionConstants.REFLEC_BEAT_VOLZZA_2: @@ -3630,30 +3767,41 @@ class ImportReflecBeat(ImportBase): end = start + stride songdata = data[start:end] - title = convert_string(songdata[song_offset:(song_offset + song_length)]) + title = convert_string(songdata[song_offset : (song_offset + song_length)]) if artist_offset is None: - artist = '' + artist = "" else: - artist = convert_string(songdata[artist_offset:(artist_offset + artist_length)]) - if title == '' and artist == '': + artist = convert_string( + songdata[artist_offset : (artist_offset + artist_length)] + ) + if title == "" and artist == "": continue - songid = struct.unpack(' List[Dict[str, Any]]: @@ -3664,14 +3812,14 @@ class ImportReflecBeat(ImportBase): for song in songs: if song.id not in lut: lut[song.id] = { - 'id': song.id, - 'title': song.name, - 'artist': song.artist, - 'chartid': song.data.get_str('chart_id'), - 'difficulties': [0] * len(self.charts), - 'folder': song.data.get_int('folder'), + "id": song.id, + "title": song.name, + "artist": song.artist, + "chartid": song.data.get_str("chart_id"), + "difficulties": [0] * len(self.charts), + "folder": song.data.get_int("folder"), } - lut[song.id]['difficulties'][song.chart] = song.data.get_int('difficulty') + lut[song.id]["difficulties"][song.chart] = song.data.get_int("difficulty") # Return the reassembled data return [val for _, val in lut.items()] @@ -3680,14 +3828,16 @@ class ImportReflecBeat(ImportBase): for song in songs: self.start_batch() for chart in self.charts: - songid = song['id'] - chartid = song['chartid'] + songid = song["id"] + chartid = song["chartid"] # ReflecBeat re-numbers some of their songs and overlaps with IDs from older # versions, so we need to keep a virtual mapping similar to DDR. Its not good # enough to just do title/artist because Reflec also has revival charts that # are named the same. Luckily we have internal chart ID to map on! - old_id = self.get_music_id_for_song_data(None, None, chartid, chart, version=0) + old_id = self.get_music_id_for_song_data( + None, None, chartid, chart, version=0 + ) if self.no_combine or old_id is None: # Insert original print(f"New entry for {songid} chart {chart}") @@ -3703,13 +3853,13 @@ class ImportReflecBeat(ImportBase): next_id, self.version * 10000 + songid, chart, - song['title'], - song['artist'], + song["title"], + song["artist"], chartid, # Chart goes into genre for reflec, so we can handle revival charts { - 'difficulty': song['difficulties'][chart], - 'folder': song['folder'], - 'chart_id': chartid, + "difficulty": song["difficulties"][chart], + "folder": song["folder"], + "chart_id": chartid, }, version=0, ) @@ -3718,13 +3868,13 @@ class ImportReflecBeat(ImportBase): # Force a folder/difficulty update for this song. self.update_metadata_for_music_id( old_id, - song['title'], - song['artist'], + song["title"], + song["artist"], chartid, # Chart goes into genre for reflec, so we can handle revival charts { - 'difficulty': song['difficulties'][chart], - 'folder': song['folder'], - 'chart_id': chartid, + "difficulty": song["difficulties"][chart], + "folder": song["folder"], + "chart_id": chartid, }, version=0, ) @@ -3734,20 +3884,19 @@ class ImportReflecBeat(ImportBase): next_id, songid, chart, - song['title'], - song['artist'], + song["title"], + song["artist"], None, # Reflec Beat has no genres for real songs. { - 'difficulty': song['difficulties'][chart], - 'folder': song['folder'], - 'chart_id': chartid, + "difficulty": song["difficulties"][chart], + "folder": song["folder"], + "chart_id": chartid, }, ) self.finish_batch() class ImportDanceEvolution(ImportBase): - def __init__( self, config: Config, @@ -3755,12 +3904,16 @@ class ImportDanceEvolution(ImportBase): no_combine: bool, update: bool, ) -> None: - if version in ['1']: + if version in ["1"]: actual_version = 1 else: - raise Exception("Unsupported Dance Evolution version, expected one of the following: 1") + raise Exception( + "Unsupported Dance Evolution version, expected one of the following: 1" + ) - super().__init__(config, GameConstants.DANCE_EVOLUTION, actual_version, no_combine, update) + super().__init__( + config, GameConstants.DANCE_EVOLUTION, actual_version, no_combine, update + ) def scrape(self, infile: str) -> List[Dict[str, Any]]: with open(infile, mode="rb") as myfile: @@ -3768,16 +3921,16 @@ class ImportDanceEvolution(ImportBase): myfile.close() arc = ARC(data) - data = arc.read_file('data/song/song_params.plist') + data = arc.read_file("data/song/song_params.plist") # First, do a header check like the game does - if data[0:4] != b'MS02': + if data[0:4] != b"MS02": raise Exception("Invalid song params file!") - if data[4:6] not in [b'BE', b'LE']: + if data[4:6] not in [b"BE", b"LE"]: raise Exception("Invalid song params file!") def get_string(offset: int, default: Optional[str] = None) -> str: - lut_offset = struct.unpack('>I', data[(offset):(offset + 4)])[0] + lut_offset = struct.unpack(">I", data[(offset) : (offset + 4)])[0] if lut_offset == 0: if default is None: raise Exception("Expecting a string, got empty!") @@ -3785,14 +3938,18 @@ class ImportDanceEvolution(ImportBase): length = 0 while data[lut_offset + length] != 0: length += 1 - return data[lut_offset:(lut_offset + length)].decode('utf-8').replace("\n", " ") + return ( + data[lut_offset : (lut_offset + length)] + .decode("utf-8") + .replace("\n", " ") + ) def get_int(offset: int) -> int: - return struct.unpack('>I', data[(offset):(offset + 4)])[0] + return struct.unpack(">I", data[(offset) : (offset + 4)])[0] # Now, make sure we know how long the file is - numsongs = struct.unpack('>I', data[8:12])[0] - filelen = struct.unpack('>I', data[12:16])[0] + numsongs = struct.unpack(">I", data[8:12])[0] + filelen = struct.unpack(">I", data[12:16])[0] if filelen != len(data): raise Exception("Invalid song params file!") @@ -3807,8 +3964,8 @@ class ImportDanceEvolution(ImportBase): bpm_min = get_int(offset + 12) bpm_max = get_int(offset + 16) copyright = get_string(offset + 24, "") - title = get_string(offset + 52, 'Unknown song') - artist = get_string(offset + 56, 'Unknown artist') + title = get_string(offset + 52, "Unknown song") + artist = get_string(offset + 56, "Unknown artist") level = get_int(offset + 64) charares1 = get_string(offset + 72) # noqa: F841 charares2 = get_string(offset + 76) # noqa: F841 @@ -3820,16 +3977,18 @@ class ImportDanceEvolution(ImportBase): flag4 = data[offset + 116] != 0x00 # noqa: F841 # TODO: Get the real music ID from the data, once we have in-game traffic. - retval.append({ - 'id': i, - 'title': title, - 'artist': artist, - 'copyright': copyright or None, - 'sort_key': kana_sort, - 'bpm_min': bpm_min, - 'bpm_max': bpm_max, - 'level': level, - }) + retval.append( + { + "id": i, + "title": title, + "artist": artist, + "copyright": copyright or None, + "sort_key": kana_sort, + "bpm_min": bpm_min, + "bpm_max": bpm_max, + "level": level, + } + ) return retval @@ -3845,7 +4004,7 @@ class ImportDanceEvolution(ImportBase): self.start_batch() # First, try to find in the DB from another version - old_id = self.get_music_id_for_song(song['id'], 0) + old_id = self.get_music_id_for_song(song["id"], 0) if self.no_combine or old_id is None: # Insert original print(f"New entry for {song['id']} chart {0}") @@ -3855,79 +4014,81 @@ class ImportDanceEvolution(ImportBase): print(f"Reused entry for {song['id']} chart {0}") next_id = old_id data = { - 'level': song['level'], - 'bpm_min': song['bpm_min'], - 'bpm_max': song['bpm_max'], + "level": song["level"], + "bpm_min": song["bpm_min"], + "bpm_max": song["bpm_max"], } - self.insert_music_id_for_song(next_id, song['id'], 0, song['title'], song['artist'], None, data) + self.insert_music_id_for_song( + next_id, song["id"], 0, song["title"], song["artist"], None, data + ) self.finish_batch() if __name__ == "__main__": - parser = argparse.ArgumentParser(description='Import Game Music DB') + parser = argparse.ArgumentParser(description="Import Game Music DB") parser.add_argument( - '--series', - action='store', + "--series", + action="store", type=str, required=True, - help='The game series we are importing.', + help="The game series we are importing.", ) parser.add_argument( - '--version', - dest='version', - action='store', + "--version", + dest="version", + action="store", type=str, required=True, - help='The game version we are importing.', + help="The game version we are importing.", ) parser.add_argument( - '--csv', - dest='csv', - action='store', + "--csv", + dest="csv", + action="store", type=str, - help='The CSV file to read, for applicable games.', + help="The CSV file to read, for applicable games.", ) parser.add_argument( - '--tsv', - dest='tsv', - action='store', + "--tsv", + dest="tsv", + action="store", type=str, - help='The TSV file to read, for applicable games.', + help="The TSV file to read, for applicable games.", ) parser.add_argument( - '--xml', - dest='xml', - action='store', + "--xml", + dest="xml", + action="store", type=str, - help='The game XML file to read, for applicable games.', + help="The game XML file to read, for applicable games.", ) parser.add_argument( - '--bin', - dest='bin', - action='store', + "--bin", + dest="bin", + action="store", type=str, - help='The game binary file to read, for applicable games.', + help="The game binary file to read, for applicable games.", ) parser.add_argument( - '--assets', - dest='assets', - action='store', + "--assets", + dest="assets", + action="store", type=str, - help='The game sound assets directory, for applicable games.', + help="The game sound assets directory, for applicable games.", ) parser.add_argument( - '--no-combine', - dest='no_combine', - action='store_true', + "--no-combine", + dest="no_combine", + action="store_true", default=False, - help='Don\'t look for the same music ID in other versions.', + help="Don't look for the same music ID in other versions.", ) parser.add_argument( - '--update', - dest='update', - action='store_true', + "--update", + dest="update", + action="store_true", default=False, - help='Overwrite data with updated values when it already exists.', + help="Overwrite data with updated values when it already exists.", ) parser.add_argument( "--config", @@ -3950,8 +4111,12 @@ if __name__ == "__main__": args = parser.parse_args() if (args.token and not args.server) or (args.server and not args.token): raise Exception("Must specify both --server and --token together!") - if (args.csv or args.tsv or args.xml or args.bin or args.assets) and (args.server or args.token): - raise Exception("Cannot specify both a remote server and a local file to read from!") + if (args.csv or args.tsv or args.xml or args.bin or args.assets) and ( + args.server or args.token + ): + raise Exception( + "Cannot specify both a remote server and a local file to read from!" + ) # Load the config so we can talk to the server config = Config() @@ -3971,8 +4136,8 @@ if __name__ == "__main__": songs = popn.lookup(args.server, args.token) else: raise Exception( - 'No game DLL provided and no remote server specified! Please ' + - 'provide either a --bin or a --server and --token option!' + "No game DLL provided and no remote server specified! Please " + + "provide either a --bin or a --server and --token option!" ) popn.import_music_db(songs) popn.close() @@ -3991,8 +4156,8 @@ if __name__ == "__main__": songs, emblems = jubeat.lookup(args.server, args.token) else: raise Exception( - 'No music_info.xml or TSV provided and no remote server specified! Please ' + - 'provide either a --xml, --tsv or a --server and --token option!' + "No music_info.xml or TSV provided and no remote server specified! Please " + + "provide either a --xml, --tsv or a --server and --token option!" ) jubeat.import_music_db(songs) jubeat.import_emblems(emblems) @@ -4012,8 +4177,8 @@ if __name__ == "__main__": songs, qpros = iidx.lookup(args.server, args.token) else: raise Exception( - 'No music_data.bin or TSV provided and no remote server specified! Please ' + - 'provide either a --bin, --tsv or a --server and --token option!' + "No music_data.bin or TSV provided and no remote server specified! Please " + + "provide either a --bin, --tsv or a --server and --token option!" ) iidx.import_music_db(songs) iidx.import_qpros(qpros) @@ -4024,16 +4189,16 @@ if __name__ == "__main__": if args.server and args.token: songs = ddr.lookup(args.server, args.token) else: - if args.version == '16': + if args.version == "16": if args.bin is None: - raise Exception('No startup.arc provided!') + raise Exception("No startup.arc provided!") # DDR Ace has a different format altogether songs = ddr.parse_xml(args.bin) else: if args.bin is None: - raise Exception('No game DLL provided!') + raise Exception("No game DLL provided!") if args.xml is None: - raise Exception('No game music XML provided!') + raise Exception("No game music XML provided!") # DDR splits the music DB between the DLL and external XML # (Why??), so we must first scrape then hydrate with extra # data to get the full DB. @@ -4049,9 +4214,9 @@ if __name__ == "__main__": else: if args.xml is None and args.bin is None and args.csv is None: raise Exception( - 'No XML file or game DLL or appeal card CSV provided and ' + - 'no remote server specified! Please provide either a --xml, ' + - '--bin, --csv or a --server and --token option!' + "No XML file or game DLL or appeal card CSV provided and " + + "no remote server specified! Please provide either a --xml, " + + "--bin, --csv or a --server and --token option!" ) if args.xml is not None: sdvx.import_music_db_or_appeal_cards(args.xml) @@ -4069,8 +4234,8 @@ if __name__ == "__main__": museca.import_music_db(args.xml) else: raise Exception( - 'No music-info.xml provided and no remote server specified! ' + - 'Please provide either a --xml or a --server and --token option!' + "No music-info.xml provided and no remote server specified! " + + "Please provide either a --xml or a --server and --token option!" ) museca.close() @@ -4082,26 +4247,28 @@ if __name__ == "__main__": songs = reflec.lookup(args.server, args.token) else: raise Exception( - 'No game DLL provided and no remote server specified! ' + - 'Please provide either a --bin or a --server and --token option!' + "No game DLL provided and no remote server specified! " + + "Please provide either a --bin or a --server and --token option!" ) reflec.import_music_db(songs) reflec.close() elif series == GameConstants.DANCE_EVOLUTION: - danevo = ImportDanceEvolution(config, args.version, args.no_combine, args.update) + danevo = ImportDanceEvolution( + config, args.version, args.no_combine, args.update + ) if args.server and args.token: songs = danevo.lookup(args.server, args.token) elif args.bin is not None: songs = danevo.scrape(args.bin) else: raise Exception( - 'No resource_lists.arc provided and no remote server ' + - 'specified! Please provide either a --bin or a ' + - '--server and --token option!', + "No resource_lists.arc provided and no remote server " + + "specified! Please provide either a --bin or a " + + "--server and --token option!", ) danevo.import_music_db(songs) danevo.close() else: - raise Exception('Unsupported game series!') + raise Exception("Unsupported game series!") diff --git a/bemani/utils/replay.py b/bemani/utils/replay.py index 8f2781b..a79f680 100644 --- a/bemani/utils/replay.py +++ b/bemani/utils/replay.py @@ -6,27 +6,40 @@ import sys from bemani.protocol import EAmuseProtocol, Node -def hex_string(length: int, caps: bool=False) -> str: +def hex_string(length: int, caps: bool = False) -> str: if caps: - string = '0123456789ABCDEF' + string = "0123456789ABCDEF" else: - string = '0123456789abcdef' - return ''.join([random.choice(string) for x in range(length)]) + string = "0123456789abcdef" + return "".join([random.choice(string) for x in range(length)]) class Protocol: - def __init__(self, address: str, port: int, encryption: bool, compression: bool, verbose: bool) -> None: + def __init__( + self, + address: str, + port: int, + encryption: bool, + compression: bool, + verbose: bool, + ) -> None: self.__address = address self.__port = port self.__encryption = encryption self.__compression = compression self.__verbose = verbose - def exchange(self, uri: str, tree: Node, text_encoding: str="shift-jis", packet_encoding: str="binary") -> Node: + def exchange( + self, + uri: str, + tree: Node, + text_encoding: str = "shift-jis", + packet_encoding: str = "binary", + ) -> Node: headers = {} if self.__verbose: - print('Outgoing request:') + print("Outgoing request:") print(tree) # Handle encoding @@ -39,17 +52,17 @@ class Protocol: # Handle encryption if self.__encryption: - encryption = f'1-{hex_string(8)}-{hex_string(4)}' - headers['X-Eamuse-Info'] = encryption + encryption = f"1-{hex_string(8)}-{hex_string(4)}" + headers["X-Eamuse-Info"] = encryption else: encryption = None # Handle compression if self.__compression: - compression = 'lz77' + compression = "lz77" else: compression = None - headers['X-Compress'] = compression + headers["X-Compress"] = compression # Convert it proto = EAmuseProtocol() @@ -69,8 +82,8 @@ class Protocol: ) # Get the compression and encryption - encryption = headers.get('X-Eamuse-Info') - compression = headers.get('X-Compress') + encryption = headers.get("X-Eamuse-Info") + compression = headers.get("X-Compress") # Decode it packet = proto.decode( @@ -79,21 +92,50 @@ class Protocol: r.content, ) if self.__verbose: - print('Incoming response:') + print("Incoming response:") print(packet) return packet def main() -> None: - parser = argparse.ArgumentParser(description="A utility to replay a packet from a log or binary dump.") - parser.add_argument("-i", "--infile", help="File containing an XML or binary node structure. Use - for stdin.", type=str, default=None, required=True) - parser.add_argument("-e", "--encoding", help="Encoding for the packet, defaults to UTF-8.", type=str, default='utf-8') - parser.add_argument("-p", "--port", help="Port to talk to. Defaults to 80", type=int, default=80) - parser.add_argument("-a", "--address", help="Address to talk to. Defaults to 127.0.0.1", type=str, default="127.0.0.1") - parser.add_argument("-u", "--path", help="URI that we should post to. Defaults to '/'", type=str, default="/") + parser = argparse.ArgumentParser( + description="A utility to replay a packet from a log or binary dump." + ) + parser.add_argument( + "-i", + "--infile", + help="File containing an XML or binary node structure. Use - for stdin.", + type=str, + default=None, + required=True, + ) + parser.add_argument( + "-e", + "--encoding", + help="Encoding for the packet, defaults to UTF-8.", + type=str, + default="utf-8", + ) + parser.add_argument( + "-p", "--port", help="Port to talk to. Defaults to 80", type=int, default=80 + ) + parser.add_argument( + "-a", + "--address", + help="Address to talk to. Defaults to 127.0.0.1", + type=str, + default="127.0.0.1", + ) + parser.add_argument( + "-u", + "--path", + help="URI that we should post to. Defaults to '/'", + type=str, + default="/", + ) args = parser.parse_args() - if args.infile == '-': + if args.infile == "-": # Load from stdin packet = sys.stdin.buffer.read() else: @@ -103,10 +145,12 @@ def main() -> None: # Add an XML special node to force encoding (will be overwritten if there # is one in the packet). - packet = b''.join([ - f''.encode(args.encoding), - packet, - ]) + packet = b"".join( + [ + f''.encode(args.encoding), + packet, + ] + ) # Attempt to decode it proto = EAmuseProtocol() @@ -120,16 +164,16 @@ def main() -> None: # Can't decode, exit raise Exception("Unable to decode packet!") - model = tree.attribute('model') + model = tree.attribute("model") module = tree.children[0].name - method = tree.children[0].attribute('method') + method = tree.children[0].attribute("method") server = Protocol(args.address, args.port, False, False, False) server.exchange( - f'{args.path}?model={model}&module={module}&method={method}', + f"{args.path}?model={model}&module={module}&method={method}", tree, ) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/bemani/utils/responsegen.py b/bemani/utils/responsegen.py index 81200ff..acfc268 100644 --- a/bemani/utils/responsegen.py +++ b/bemani/utils/responsegen.py @@ -12,44 +12,48 @@ def generate_node_name(node: Node, used_names: Dict[str, Node]) -> str: return potential_name loop = 1 - while f'{potential_name}_{loop}' in used_names: + while f"{potential_name}_{loop}" in used_names: loop = loop + 1 - potential_name = f'{potential_name}_{loop}' + potential_name = f"{potential_name}_{loop}" used_names[potential_name] = node return potential_name def generate_node_create(node: Node) -> str: dtype = node.data_type - if dtype == 'str': - method = 'string' - elif dtype == 'bin': - method = 'binary' - elif dtype == 'ip4': - method = 'ipv4' - elif dtype == '4u8': - method = 'fouru8' + if dtype == "str": + method = "string" + elif dtype == "bin": + method = "binary" + elif dtype == "ip4": + method = "ipv4" + elif dtype == "4u8": + method = "fouru8" else: method = dtype if node.is_array: - method = f'{method}_array' + method = f"{method}_array" - if dtype != 'void': + if dtype != "void": # Format the type for display - if dtype == 'str': - value = f', \'{node.value}\'' - elif dtype == 'ip4': - value = f', \'{node.value[0]}.{node.value[1]}.{node.value[2]}.{node.value[3]}\'' + if dtype == "str": + value = f", '{node.value}'" + elif dtype == "ip4": + value = ( + f", '{node.value[0]}.{node.value[1]}.{node.value[2]}.{node.value[3]}'" + ) else: - value = f', {node.value}' + value = f", {node.value}" else: - value = '' + value = "" - return f'Node.{method}(\'{node.name}\'{value})' + return f"Node.{method}('{node.name}'{value})" -def generate_node_link(node_name: str, used_names: Dict[str, Node], parent: Node) -> str: +def generate_node_link( + node_name: str, used_names: Dict[str, Node], parent: Node +) -> str: # Find the node that parents this, link to it found_parent = None for parent_name in used_names: @@ -58,12 +62,14 @@ def generate_node_link(node_name: str, used_names: Dict[str, Node], parent: Node break if found_parent is None: - raise Exception(f'Failed to find parent name for {parent}') + raise Exception(f"Failed to find parent name for {parent}") - return f'{found_parent}.add_child({node_name})' + return f"{found_parent}.add_child({node_name})" -def generate_lines(node: Node, used_names: Dict[str, Node], parent: Optional[Node]=None) -> List[str]: +def generate_lines( + node: Node, used_names: Dict[str, Node], parent: Optional[Node] = None +) -> List[str]: # First, generate node itself create = generate_node_create(node) if not node.children and not node.attributes and parent: @@ -73,7 +79,7 @@ def generate_lines(node: Node, used_names: Dict[str, Node], parent: Optional[Nod # Print the node generate itself out = [] node_name = generate_node_name(node, used_names) - out.append(f'{node_name} = {create}') + out.append(f"{node_name} = {create}") # Now, generate add to parent if exists if parent is not None: @@ -81,7 +87,7 @@ def generate_lines(node: Node, used_names: Dict[str, Node], parent: Optional[Nod # Now generate node attributes for attr in node.attributes: - out.append(f'{node_name}.set_attribute(\'{attr}\', \'{node.attributes[attr]}\')') + out.append(f"{node_name}.set_attribute('{attr}', '{node.attributes[attr]}')") # Now, do the same for all children for child in node.children: @@ -91,7 +97,7 @@ def generate_lines(node: Node, used_names: Dict[str, Node], parent: Optional[Nod def generate_code(infile: str, outfile: str, encoding: str) -> None: - if infile == '-': + if infile == "-": # Load from stdin packet = sys.stdin.buffer.read() else: @@ -101,10 +107,12 @@ def generate_code(infile: str, outfile: str, encoding: str) -> None: # Add an XML special node to force encoding (will be overwritten if there # is one in the packet). - packet = b''.join([ - f''.encode(encoding), - packet, - ]) + packet = b"".join( + [ + f''.encode(encoding), + packet, + ] + ) # Attempt to decode it proto = EAmuseProtocol() @@ -119,9 +127,9 @@ def generate_code(infile: str, outfile: str, encoding: str) -> None: raise Exception("Unable to decode packet!") # Walk through, outputting each node and attaching it to its parent - code = '\n'.join(generate_lines(req, {})) + code = "\n".join(generate_lines(req, {})) - if outfile == '-': + if outfile == "-": print(code) else: with open(outfile, mode="a") as outfp: @@ -130,14 +138,36 @@ def generate_code(infile: str, outfile: str, encoding: str) -> None: def main() -> None: - parser = argparse.ArgumentParser(description="A utility to generate code that will generate a packet given an example packet from a log or binary dump.") - parser.add_argument("-i", "--infile", help="File containing an XML or binary node structure. Use - for stdin.", type=str, default=None, required=True) - parser.add_argument("-o", "--outfile", help="File to write python code to. Use - for stdout.", type=str, default=None, required=True) - parser.add_argument("-e", "--encoding", help="Encoding for the packet, defaults to UTF-8.", type=str, default='utf-8') + parser = argparse.ArgumentParser( + description="A utility to generate code that will generate a packet given an example packet from a log or binary dump." + ) + parser.add_argument( + "-i", + "--infile", + help="File containing an XML or binary node structure. Use - for stdin.", + type=str, + default=None, + required=True, + ) + parser.add_argument( + "-o", + "--outfile", + help="File to write python code to. Use - for stdout.", + type=str, + default=None, + required=True, + ) + parser.add_argument( + "-e", + "--encoding", + help="Encoding for the packet, defaults to UTF-8.", + type=str, + default="utf-8", + ) args = parser.parse_args() generate_code(args.infile, args.outfile, args.encoding) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/bemani/utils/sampleclient.py b/bemani/utils/sampleclient.py old mode 100755 new mode 100644 index 64f2948..f253c7e --- a/bemani/utils/sampleclient.py +++ b/bemani/utils/sampleclient.py @@ -7,171 +7,322 @@ from typing import Dict, List, Any, Optional class APIClient: - API_VERSION = 'v1' + API_VERSION = "v1" def __init__(self, base_uri: str, token: str) -> None: self.base_uri = base_uri self.token = token - def exchange_data(self, request_uri: str, request_args: Dict[str, Any]) -> Dict[str, Any]: - if self.base_uri[-1:] != '/': - uri = '{}/{}'.format(self.base_uri, request_uri) + def exchange_data( + self, request_uri: str, request_args: Dict[str, Any] + ) -> Dict[str, Any]: + if self.base_uri[-1:] != "/": + uri = "{}/{}".format(self.base_uri, request_uri) else: - uri = '{}{}'.format(self.base_uri, request_uri) + uri = "{}{}".format(self.base_uri, request_uri) headers = { - 'Authorization': 'Token {}'.format(self.token), - 'Content-Type': 'application/json; charset=utf-8', + "Authorization": "Token {}".format(self.token), + "Content-Type": "application/json; charset=utf-8", } - data = json.dumps(request_args).encode('utf8') + data = json.dumps(request_args).encode("utf8") r = requests.request( - 'GET', + "GET", uri, headers=headers, data=data, allow_redirects=False, ) - if r.headers['content-type'] != 'application/json; charset=utf-8': - raise Exception('API returned invalid content type \'{}\'!'.format(r.headers['content-type'])) + if r.headers["content-type"] != "application/json; charset=utf-8": + raise Exception( + "API returned invalid content type '{}'!".format( + r.headers["content-type"] + ) + ) jsondata = r.json() if r.status_code == 200: return jsondata - if 'error' not in jsondata: - raise Exception('API returned error code {} but did not include \'error\' attribute in response JSON!'.format(r.status_code)) - error = jsondata['error'] + if "error" not in jsondata: + raise Exception( + "API returned error code {} but did not include 'error' attribute in response JSON!".format( + r.status_code + ) + ) + error = jsondata["error"] if r.status_code == 401: - raise Exception('The API token used is not authorized against the server!') + raise Exception("The API token used is not authorized against the server!") if r.status_code == 404: - raise Exception('The server does not support this game/version or request object and returned \'{}\''.format(error)) + raise Exception( + "The server does not support this game/version or request object and returned '{}'".format( + error + ) + ) if r.status_code == 405: - raise Exception('The server did not recognize the request and returned \'{}\''.format(error)) + raise Exception( + "The server did not recognize the request and returned '{}'".format( + error + ) + ) if r.status_code == 500: - raise Exception('The server had an error processing the request and returned \'{}\''.format(error)) + raise Exception( + "The server had an error processing the request and returned '{}'".format( + error + ) + ) if r.status_code == 501: - raise Exception('The server does not support this version of the API!') - raise Exception('The server returned an invalid status code {}!'.format(r.status_code)) + raise Exception("The server does not support this version of the API!") + raise Exception( + "The server returned an invalid status code {}!".format(r.status_code) + ) def info_exchange(self) -> None: - resp = self.exchange_data('', {}) - print('Server name: {}'.format(resp['name'])) - print('Server admin email: {}'.format(resp['email'])) - print('Server supported versions: {}'.format(', '.join(resp['versions']))) + resp = self.exchange_data("", {}) + print("Server name: {}".format(resp["name"])) + print("Server admin email: {}".format(resp["email"])) + print("Server supported versions: {}".format(", ".join(resp["versions"]))) def __id_check(self, idtype: str, ids: List[str]) -> None: - if idtype not in ['card', 'song', 'instance', 'server']: - raise Exception('Invalid ID type provided!') - if idtype == 'card' and len(ids) == 0: - raise Exception('Invalid number of IDs given!') - if idtype == 'song' and len(ids) not in [1, 2]: - raise Exception('Invalid number of IDs given!') - if idtype == 'instance' and len(ids) != 3: - raise Exception('Invalid number of IDs given!') - if idtype == 'server' and len(ids) != 0: - raise Exception('Invalid number of IDs given!') + if idtype not in ["card", "song", "instance", "server"]: + raise Exception("Invalid ID type provided!") + if idtype == "card" and len(ids) == 0: + raise Exception("Invalid number of IDs given!") + if idtype == "song" and len(ids) not in [1, 2]: + raise Exception("Invalid number of IDs given!") + if idtype == "instance" and len(ids) != 3: + raise Exception("Invalid number of IDs given!") + if idtype == "server" and len(ids) != 0: + raise Exception("Invalid number of IDs given!") - def records_exchange(self, game: str, version: str, idtype: str, ids: List[str], since: Optional[int], until: Optional[int]) -> None: + def records_exchange( + self, + game: str, + version: str, + idtype: str, + ids: List[str], + since: Optional[int], + until: Optional[int], + ) -> None: self.__id_check(idtype, ids) params = { - 'ids': ids, - 'type': idtype, - 'objects': ['records'], + "ids": ids, + "type": idtype, + "objects": ["records"], } # type: Dict[str, Any] if since is not None: - params['since'] = since + params["since"] = since if until is not None: - params['until'] = until + params["until"] = until resp = self.exchange_data( - '{}/{}/{}'.format(self.API_VERSION, game, version), + "{}/{}/{}".format(self.API_VERSION, game, version), params, ) - print(json.dumps(resp['records'], indent=4)) + print(json.dumps(resp["records"], indent=4)) - def profile_exchange(self, game: str, version: str, idtype: str, ids: List[str]) -> None: + def profile_exchange( + self, game: str, version: str, idtype: str, ids: List[str] + ) -> None: self.__id_check(idtype, ids) resp = self.exchange_data( - '{}/{}/{}'.format(self.API_VERSION, game, version), + "{}/{}/{}".format(self.API_VERSION, game, version), { - 'ids': ids, - 'type': idtype, - 'objects': ['profile'], + "ids": ids, + "type": idtype, + "objects": ["profile"], }, ) - print(json.dumps(resp['profile'], indent=4)) + print(json.dumps(resp["profile"], indent=4)) - def statistics_exchange(self, game: str, version: str, idtype: str, ids: List[str]) -> None: + def statistics_exchange( + self, game: str, version: str, idtype: str, ids: List[str] + ) -> None: self.__id_check(idtype, ids) resp = self.exchange_data( - '{}/{}/{}'.format(self.API_VERSION, game, version), + "{}/{}/{}".format(self.API_VERSION, game, version), { - 'ids': ids, - 'type': idtype, - 'objects': ['statistics'], + "ids": ids, + "type": idtype, + "objects": ["statistics"], }, ) - print(json.dumps(resp['statistics'], indent=4)) + print(json.dumps(resp["statistics"], indent=4)) def catalog_exchange(self, game: str, version: str) -> None: resp = self.exchange_data( - '{}/{}/{}'.format(self.API_VERSION, game, version), + "{}/{}/{}".format(self.API_VERSION, game, version), { - 'ids': [], - 'type': 'server', - 'objects': ['catalog'], + "ids": [], + "type": "server", + "objects": ["catalog"], }, ) - print(json.dumps(resp['catalog'], indent=4)) + print(json.dumps(resp["catalog"], indent=4)) -def main(): +def main() -> None: # Global arguments - parser = argparse.ArgumentParser(description='A sample API client for an e-AMUSEMENT API provider.') - parser.add_argument('-t', '--token', type=str, required=True, help='The authorization token for speaing to the API.') - parser.add_argument('-b', '--base', type=str, required=True, help='Base URI to connect to for all requests.') - subparser = parser.add_subparsers(dest='request') + parser = argparse.ArgumentParser( + description="A sample API client for an e-AMUSEMENT API provider." + ) + parser.add_argument( + "-t", + "--token", + type=str, + required=True, + help="The authorization token for speaing to the API.", + ) + parser.add_argument( + "-b", + "--base", + type=str, + required=True, + help="Base URI to connect to for all requests.", + ) + subparser = parser.add_subparsers(dest="request") # Info request - subparser.add_parser('info') + subparser.add_parser("info") # Score request - record_parser = subparser.add_parser('records') - record_parser.add_argument('-g', '--game', type=str, required=True, help='The game we want to look records up for.') - record_parser.add_argument('-v', '--version', type=str, required=True, help='The version we want to look records up for.') - record_parser.add_argument('-t', '--type', type=str, required=True, choices=['card', 'song', 'instance', 'server'], help='The type of ID used to look up records.') - record_parser.add_argument('-s', '--since', metavar='TIMESTAMP', default=None, type=int, help='Only load records updated since TIMESTAMP') - record_parser.add_argument('-u', '--until', metavar='TIMESTAMP', default=None, type=int, help='Only load records updated before TIMESTAMP') - record_parser.add_argument('id', metavar='ID', nargs='*', type=str, help='The ID we will look up records for.') + record_parser = subparser.add_parser("records") + record_parser.add_argument( + "-g", + "--game", + type=str, + required=True, + help="The game we want to look records up for.", + ) + record_parser.add_argument( + "-v", + "--version", + type=str, + required=True, + help="The version we want to look records up for.", + ) + record_parser.add_argument( + "-t", + "--type", + type=str, + required=True, + choices=["card", "song", "instance", "server"], + help="The type of ID used to look up records.", + ) + record_parser.add_argument( + "-s", + "--since", + metavar="TIMESTAMP", + default=None, + type=int, + help="Only load records updated since TIMESTAMP", + ) + record_parser.add_argument( + "-u", + "--until", + metavar="TIMESTAMP", + default=None, + type=int, + help="Only load records updated before TIMESTAMP", + ) + record_parser.add_argument( + "id", + metavar="ID", + nargs="*", + type=str, + help="The ID we will look up records for.", + ) # Profile request - profile_parser = subparser.add_parser('profile') - profile_parser.add_argument('-g', '--game', type=str, required=True, help='The game we want to look profiles up for.') - profile_parser.add_argument('-v', '--version', type=str, required=True, help='The version we want to look profiles up for.') - profile_parser.add_argument('-t', '--type', type=str, required=True, choices=['card', 'server'], help='The type of ID used to look up profiles.') - profile_parser.add_argument('id', metavar='ID', nargs='*', type=str, help='The ID we will look up profiles for.') + profile_parser = subparser.add_parser("profile") + profile_parser.add_argument( + "-g", + "--game", + type=str, + required=True, + help="The game we want to look profiles up for.", + ) + profile_parser.add_argument( + "-v", + "--version", + type=str, + required=True, + help="The version we want to look profiles up for.", + ) + profile_parser.add_argument( + "-t", + "--type", + type=str, + required=True, + choices=["card", "server"], + help="The type of ID used to look up profiles.", + ) + profile_parser.add_argument( + "id", + metavar="ID", + nargs="*", + type=str, + help="The ID we will look up profiles for.", + ) # Statistics request - statistic_parser = subparser.add_parser('statistics') - statistic_parser.add_argument('-g', '--game', type=str, required=True, help='The game we want to look statistics up for.') - statistic_parser.add_argument('-v', '--version', type=str, required=True, help='The version we want to look statistics up for.') - statistic_parser.add_argument('-t', '--type', type=str, required=True, choices=['card', 'song', 'instance', 'server'], help='The type of ID used to look up statistics.') - statistic_parser.add_argument('id', metavar='ID', nargs='*', type=str, help='The ID we will look up statistics for.') + statistic_parser = subparser.add_parser("statistics") + statistic_parser.add_argument( + "-g", + "--game", + type=str, + required=True, + help="The game we want to look statistics up for.", + ) + statistic_parser.add_argument( + "-v", + "--version", + type=str, + required=True, + help="The version we want to look statistics up for.", + ) + statistic_parser.add_argument( + "-t", + "--type", + type=str, + required=True, + choices=["card", "song", "instance", "server"], + help="The type of ID used to look up statistics.", + ) + statistic_parser.add_argument( + "id", + metavar="ID", + nargs="*", + type=str, + help="The ID we will look up statistics for.", + ) # Catalog request - catalog_parser = subparser.add_parser('catalog') - catalog_parser.add_argument('-g', '--game', type=str, required=True, help='The game we want to look catalog entries up for.') - catalog_parser.add_argument('-v', '--version', type=str, required=True, help='The version we want to look catalog entries up for.') + catalog_parser = subparser.add_parser("catalog") + catalog_parser.add_argument( + "-g", + "--game", + type=str, + required=True, + help="The game we want to look catalog entries up for.", + ) + catalog_parser.add_argument( + "-v", + "--version", + type=str, + required=True, + help="The version we want to look catalog entries up for.", + ) # Grab args args = parser.parse_args() client = APIClient(args.base, args.token) - if args.request == 'info': + if args.request == "info": client.info_exchange() - elif args.request == 'records': + elif args.request == "records": client.records_exchange( args.game, args.version, @@ -180,30 +331,30 @@ def main(): args.since, args.until, ) - elif args.request == 'profile': + elif args.request == "profile": client.profile_exchange( args.game, args.version, args.type, args.id, ) - elif args.request == 'statistics': + elif args.request == "statistics": client.statistics_exchange( args.game, args.version, args.type, args.id, ) - elif args.request == 'catalog': + elif args.request == "catalog": client.catalog_exchange( args.game, args.version, ) else: - raise Exception('Invalid request type {}!'.format(args.request)) + raise Exception("Invalid request type {}!".format(args.request)) -if __name__ == '__main__': +if __name__ == "__main__": try: main() except Exception as e: diff --git a/bemani/utils/scheduler.py b/bemani/utils/scheduler.py index 8c59434..fbec82a 100644 --- a/bemani/utils/scheduler.py +++ b/bemani/utils/scheduler.py @@ -67,24 +67,37 @@ def run_scheduled_work(config: Config) -> None: cache.preload(data, config) # Now, possibly delete old log entries - keep_duration = config.get('event_log_duration', 0) + keep_duration = config.get("event_log_duration", 0) if keep_duration > 0: # Calculate timestamp of events we should delete oldest_event = Time.now() - keep_duration data.local.network.delete_events(oldest_event) -if __name__ == '__main__': - parser = argparse.ArgumentParser(description="A scheduler for work that needs to be done periodically.") - parser.add_argument("-c", "--config", help="Core configuration. Defaults to server.yaml", type=str, default="server.yaml") - parser.add_argument("-o", "--read-only", action="store_true", help="Force the database into read-only mode.") +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="A scheduler for work that needs to be done periodically." + ) + parser.add_argument( + "-c", + "--config", + help="Core configuration. Defaults to server.yaml", + type=str, + default="server.yaml", + ) + parser.add_argument( + "-o", + "--read-only", + action="store_true", + help="Force the database into read-only mode.", + ) args = parser.parse_args() # Set up global configuration config = Config() load_config(args.config, config) if args.read_only: - config['database']['read_only'] = True + config["database"]["read_only"] = True # Run out of band work run_scheduled_work(config) diff --git a/bemani/utils/services.py b/bemani/utils/services.py index da528b4..8f782d0 100644 --- a/bemani/utils/services.py +++ b/bemani/utils/services.py @@ -5,15 +5,18 @@ from flask import Flask, request, redirect, Response, make_response from bemani.protocol import EAmuseProtocol from bemani.backend import Dispatch, UnrecognizedPCBIDException from bemani.data import Config, Data -from bemani.utils.config import load_config as base_load_config, register_games as base_register_games +from bemani.utils.config import ( + load_config as base_load_config, + register_games as base_register_games, +) app = Flask(__name__) config = Config() -@app.route('/', defaults={'path': ''}, methods=['GET']) -@app.route('/', methods=['GET']) +@app.route("/", defaults={"path": ""}, methods=["GET"]) +@app.route("/", methods=["GET"]) def receive_healthcheck(path: str) -> Response: global config redirect_uri = config.server.redirect @@ -25,13 +28,13 @@ def receive_healthcheck(path: str) -> Response: return redirect(redirect_uri, code=308) # type: ignore -@app.route('/', defaults={'path': ''}, methods=['POST']) -@app.route('/', methods=['POST']) +@app.route("/", defaults={"path": ""}, methods=["POST"]) +@app.route("/", methods=["POST"]) def receive_request(path: str) -> Response: proto = EAmuseProtocol() - remote_address = request.headers.get('x-remote-address', None) - compression = request.headers.get('x-compress', None) - encryption = request.headers.get('x-eamuse-info', None) + remote_address = request.headers.get("x-remote-address", None) + compression = request.headers.get("x-compress", None) + encryption = request.headers.get("x-eamuse-info", None) req = proto.decode( compression, encryption, @@ -41,7 +44,7 @@ def receive_request(path: str) -> Response: if req is None: # Nothing to do here return Response("Unrecognized packet!", 500) - if req.name in {'soapenv:Envelope', 'soap:Envelope', 'methodCall'}: + if req.name in {"soapenv:Envelope", "soap:Envelope", "methodCall"}: # We get lots of spam from random bots trying to SOAP # us up, so ignore this shit. return Response("Unrecognized packet!", 500) @@ -49,8 +52,8 @@ def receive_request(path: str) -> Response: # Create and format config global config requestconfig = config.clone() - requestconfig['client'] = { - 'address': remote_address or request.remote_addr, + requestconfig["client"] = { + "address": remote_address or request.remote_addr, } dataprovider = Data(requestconfig) @@ -61,9 +64,9 @@ def receive_request(path: str) -> Response: if resp is None: # Nothing to do here dataprovider.local.network.put_event( - 'unhandled_packet', + "unhandled_packet", { - 'request': str(req), + "request": str(req), }, ) return Response("No response generated", 404) @@ -79,20 +82,20 @@ def receive_request(path: str) -> Response: # Some old clients are case-sensitive, even though http spec says these # shouldn't matter, so capitalize correctly. if compression: - response.headers['X-Compress'] = compression + response.headers["X-Compress"] = compression else: - response.headers['X-Compress'] = 'none' + response.headers["X-Compress"] = "none" if encryption: - response.headers['X-Eamuse-Info'] = encryption + response.headers["X-Eamuse-Info"] = encryption return response except UnrecognizedPCBIDException as e: dataprovider.local.network.put_event( - 'unauthorized_pcbid', + "unauthorized_pcbid", { - 'pcbid': e.pcbid, - 'model': e.model, - 'ip': e.ip, + "pcbid": e.pcbid, + "model": e.model, + "ip": e.ip, }, ) return Response("Unauthorized client", 403) @@ -100,11 +103,11 @@ def receive_request(path: str) -> Response: stack = traceback.format_exc() print(stack) dataprovider.local.network.put_event( - 'exception', + "exception", { - 'service': 'xrpc', - 'request': str(req), - 'traceback': stack, + "service": "xrpc", + "request": str(req), + "traceback": stack, }, ) return Response("Crash when handling packet!", 500) @@ -122,26 +125,47 @@ def load_config(filename: str) -> None: base_load_config(filename, config) -if __name__ == '__main__': - parser = argparse.ArgumentParser(description="A backend services provider for eAmusement games") - parser.add_argument("-p", "--port", help="Port to listen on. Defaults to 80", type=int, default=80) - parser.add_argument("-c", "--config", help="Core configuration. Defaults to server.yaml", type=str, default="server.yaml") - parser.add_argument("-r", "--profile", help="Turn on profiling for services, writing CProfile data to the currenct directory", action="store_true") - parser.add_argument("-o", "--read-only", action="store_true", help="Force the database into read-only mode.") +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="A backend services provider for eAmusement games" + ) + parser.add_argument( + "-p", "--port", help="Port to listen on. Defaults to 80", type=int, default=80 + ) + parser.add_argument( + "-c", + "--config", + help="Core configuration. Defaults to server.yaml", + type=str, + default="server.yaml", + ) + parser.add_argument( + "-r", + "--profile", + help="Turn on profiling for services, writing CProfile data to the currenct directory", + action="store_true", + ) + parser.add_argument( + "-o", + "--read-only", + action="store_true", + help="Force the database into read-only mode.", + ) args = parser.parse_args() # Set up global configuration, overriding config port for convenience in debugging. load_config(args.config) - config['server']['port'] = args.port + config["server"]["port"] = args.port if args.read_only: - config['database']['read_only'] = True + config["database"]["read_only"] = True # Register game handlers register_games() if args.profile: from werkzeug.contrib.profiler import ProfilerMiddleware - app.wsgi_app = ProfilerMiddleware(app.wsgi_app, profile_dir='.') # type: ignore + + app.wsgi_app = ProfilerMiddleware(app.wsgi_app, profile_dir=".") # type: ignore # Run the app - app.run(host='0.0.0.0', port=args.port, debug=True) + app.run(host="0.0.0.0", port=args.port, debug=True) diff --git a/bemani/utils/struct.py b/bemani/utils/struct.py index e9c3f17..5039f47 100644 --- a/bemani/utils/struct.py +++ b/bemani/utils/struct.py @@ -19,7 +19,7 @@ class LineNumber: class StructPrinter: - def __init__(self, pe: PEFile, default_encoding: str="ascii") -> None: + def __init__(self, pe: PEFile, default_encoding: str = "ascii") -> None: self.default_encoding = default_encoding self.pe = pe @@ -46,7 +46,9 @@ class StructPrinter: if not in_dereference: in_dereference = True if cur_accum: - raise Exception("Cannot have dereference marker in middle of specifier!") + raise Exception( + "Cannot have dereference marker in middle of specifier!" + ) else: # Double-indirect dereference. cur_accum += c @@ -82,13 +84,19 @@ class StructPrinter: continue # If we have either an integer prefix, or an offset prefix, accumulate here. - if c.isdigit() or c in '+-' or (c in 'xabcdefABCDEF' and ('+' in cur_accum or '-' in cur_accum)): + if ( + c.isdigit() + or c in "+-" + or (c in "xabcdefABCDEF" and ("+" in cur_accum or "-" in cur_accum)) + ): cur_accum += c continue if c == "&": if cur_accum: - raise Exception("Hex specifier should be at beginning of specifier!") + raise Exception( + "Hex specifier should be at beginning of specifier!" + ) cur_accum += c continue @@ -107,10 +115,16 @@ class StructPrinter: return prefix, specs - def parse_struct(self, startaddr: str, endaddr: str, countstr: str, fmt: str) -> List[Any]: + def parse_struct( + self, startaddr: str, endaddr: str, countstr: str, fmt: str + ) -> List[Any]: start: int = int(startaddr, 16) end: Optional[int] = int(endaddr, 16) if endaddr is not None else None - count: Optional[int] = int(countstr, 16 if "0x" in countstr else 10) if countstr is not None else None + count: Optional[int] = ( + int(countstr, 16 if "0x" in countstr else 10) + if countstr is not None + else None + ) if end is None and count is None: raise Exception("Can't handle endless structures!") @@ -130,7 +144,14 @@ class StructPrinter: return self.__parse_struct(start, end, count, prefix, specs) - def __parse_struct(self, start: int, end: Optional[int], count: Optional[int], prefix: str, specs: List[Any]) -> List[Any]: + def __parse_struct( + self, + start: int, + end: Optional[int], + count: Optional[int], + prefix: str, + specs: List[Any], + ) -> List[Any]: # Now, parse out each chunk. output = [] offset = start @@ -155,7 +176,9 @@ class StructPrinter: if spec[-1] == "#": if len(spec) > 1: if spec[0] not in "+-": - raise Exception("Line number offsets must include a '+' or '-' prefix!") + raise Exception( + "Line number offsets must include a '+' or '-' prefix!" + ) val = int(spec[:-1], 16 if "0x" in spec else 10) else: val = 0 @@ -163,8 +186,8 @@ class StructPrinter: elif spec == "z": # Null-terminated string bs = b"" - while self.pe.data[offset:(offset + 1)] != b"\x00": - bs += self.pe.data[offset:(offset + 1)] + while self.pe.data[offset : (offset + 1)] != b"\x00": + bs += self.pe.data[offset : (offset + 1)] offset += 1 # Advance past null byte offset += 1 @@ -175,8 +198,8 @@ class StructPrinter: line.append(bs.decode(self.default_encoding)) else: size = struct.calcsize(prefix + spec) - chunk = self.pe.data[offset:(offset + size)] - if spec != 'x': + chunk = self.pe.data[offset : (offset + size)] + if spec != "x": if dohex: line.append(hex(struct.unpack(prefix + spec, chunk)[0])) else: @@ -184,11 +207,11 @@ class StructPrinter: offset += size else: if self.pe.is_64bit(): - chunk = self.pe.data[offset:(offset + 8)] + chunk = self.pe.data[offset : (offset + 8)] pointer = struct.unpack(prefix + "Q", chunk)[0] offset += 8 else: - chunk = self.pe.data[offset:(offset + 4)] + chunk = self.pe.data[offset : (offset + 4)] pointer = struct.unpack(prefix + "I", chunk)[0] offset += 4 @@ -199,7 +222,9 @@ class StructPrinter: line.append(None) else: pointer = self.pe.virtual_to_physical(pointer) - subparse = self.__parse_struct(pointer, pointer + 1, None, prefix, spec) + subparse = self.__parse_struct( + pointer, pointer + 1, None, prefix, spec + ) if len(subparse) != 1: raise Exception("Logic error!") line.append(subparse[0]) @@ -213,7 +238,8 @@ def main() -> int: parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description="A utility to print structs out of a DLL.", - epilog=(""" + epilog=( + """ Some examples of valid format specifiers and what they do are as follows: *h = Decodes an array of short pointers, decoding the resulting shorts for each pointer in the array. @@ -231,7 +257,8 @@ Ih&h = Decodes an array of structures containing an unsigned integer and two sho *z&+0x200# = Decodes an array of null-terminated string pointers, displaying the array entry number in hex starting at 0x200 and string. Broken down, it has the following parts: *z = Dereference the current value (*) and treat that integer as a pointer to a null-terminated string (z). &+0x200# = Print the current line number (#), offset by the value 0x200 (+0x200) as a hex number (&). -"""), +""" + ), ) parser.add_argument( "--file", @@ -262,7 +289,7 @@ Ih&h = Decodes an array of structures containing an unsigned integer and two sho parser.add_argument( "--encoding", help="Encoding to use for strings, such as 'ascii', 'utf-8' or 'shift-jis'.", - default='ascii', + default="ascii", type=str, ) parser.add_argument( @@ -272,10 +299,10 @@ Ih&h = Decodes an array of structures containing an unsigned integer and two sho "for details. Additionally, prefixing a format specifier with * allows dereferencing pointers. " "Surround a chunk of format specifiers with parenthesis to dereference structures. Note that " "structures can be arbitrarily nested to decode complex data types. For ease of unpacking C string " - "pointers, the specifier \"z\" is recognzied to mean null-terminated string. A & preceeding a " + 'pointers, the specifier "z" is recognzied to mean null-terminated string. A & preceeding a ' "format specifier means that we should convert to hex before displaying. For the ease of decoding " - "enumerations, the specifier \"#\" is recognized to mean entry number. You can provide it an " - "offset value such as \"+20#\" to start at a certain number." + 'enumerations, the specifier "#" is recognized to mean entry number. You can provide it an ' + 'offset value such as "+20#" to start at a certain number.' ), type=str, default=None, @@ -290,7 +317,7 @@ Ih&h = Decodes an array of structures containing an unsigned integer and two sho "If multiple sections must be emulated you can specify this multiple times." ), type=str, - action='append', + action="append", default=[], ) parser.add_argument( @@ -302,7 +329,7 @@ Ih&h = Decodes an array of structures containing an unsigned integer and two sho "can specify this multiple times." ), type=str, - action='append', + action="append", default=[], ) parser.add_argument( @@ -321,7 +348,7 @@ Ih&h = Decodes an array of structures containing an unsigned integer and two sho print("You cannot specify both an --end and a --count!", file=sys.stderr) return 1 - fp = open(args.file, 'rb') + fp = open(args.file, "rb") data = fp.read() fp.close() @@ -344,7 +371,7 @@ Ih&h = Decodes an array of structures containing an unsigned integer and two sho # we're about to parse. if args.emulate_code: for chunk in args.emulate_code: - emulate_start, emulate_end = chunk.split(':', 1) + emulate_start, emulate_end = chunk.split(":", 1) start = int(emulate_start, 16) end = int(emulate_end, 16) pe.emulate_code(start, end, verbose=args.verbose) @@ -362,5 +389,5 @@ Ih&h = Decodes an array of structures containing an unsigned integer and two sho return 0 -if __name__ == '__main__': +if __name__ == "__main__": sys.exit(main()) diff --git a/bemani/utils/trafficgen.py b/bemani/utils/trafficgen.py index 9ff1b77..f3cebf7 100644 --- a/bemani/utils/trafficgen.py +++ b/bemani/utils/trafficgen.py @@ -61,480 +61,490 @@ from bemani.client.bishi import TheStarBishiBashiClient from bemani.client.mga.mga import MetalGearArcadeClient -def get_client(proto: ClientProtocol, pcbid: str, game: str, config: Dict[str, Any]) -> BaseClient: - if game == 'pnm-tune-street': +def get_client( + proto: ClientProtocol, pcbid: str, game: str, config: Dict[str, Any] +) -> BaseClient: + if game == "pnm-tune-street": return PopnMusicTuneStreetClient( proto, pcbid, config, ) - if game == 'pnm-fantasia': + if game == "pnm-fantasia": return PopnMusicFantasiaClient( proto, pcbid, config, ) - if game == 'pnm-sunny-park': + if game == "pnm-sunny-park": return PopnMusicSunnyParkClient( proto, pcbid, config, ) - if game == 'pnm-lapistoria': + if game == "pnm-lapistoria": return PopnMusicLapistoriaClient( proto, pcbid, config, ) - if game == 'pnm-eclale': + if game == "pnm-eclale": return PopnMusicEclaleClient( proto, pcbid, config, ) - if game == 'pnm-usaneko': + if game == "pnm-usaneko": return PopnMusicUsaNekoClient( proto, pcbid, config, ) - if game == 'pnm-peace': + if game == "pnm-peace": return PopnMusicPeaceClient( proto, pcbid, config, ) - if game == 'pnm-kaimei': + if game == "pnm-kaimei": return PopnMusicKaimeiClient( proto, pcbid, config, ) - if game == 'jubeat-saucer': + if game == "jubeat-saucer": return JubeatSaucerClient( proto, pcbid, config, ) - if game == 'jubeat-saucer-fulfill': + if game == "jubeat-saucer-fulfill": return JubeatSaucerFulfillClient( proto, pcbid, config, ) - if game == 'jubeat-prop': + if game == "jubeat-prop": return JubeatPropClient( proto, pcbid, config, ) - if game == 'jubeat-qubell': + if game == "jubeat-qubell": return JubeatQubellClient( proto, pcbid, config, ) - if game == 'jubeat-clan': + if game == "jubeat-clan": return JubeatClanClient( proto, pcbid, config, ) - if game == 'jubeat-festo': + if game == "jubeat-festo": return JubeatFestoClient( proto, pcbid, config, ) - if game == 'iidx-rootage': + if game == "iidx-rootage": return IIDXRootageClient( proto, pcbid, config, ) - if game == 'iidx-cannonballers': + if game == "iidx-cannonballers": return IIDXCannonBallersClient( proto, pcbid, config, ) - if game == 'iidx-sinobuz': + if game == "iidx-sinobuz": return IIDXSinobuzClient( proto, pcbid, config, ) - if game == 'iidx-copula': + if game == "iidx-copula": return IIDXCopulaClient( proto, pcbid, config, ) - if game == 'iidx-pendual': + if game == "iidx-pendual": return IIDXPendualClient( proto, pcbid, config, ) - if game == 'iidx-spada': + if game == "iidx-spada": return IIDXSpadaClient( proto, pcbid, config, ) - if game == 'iidx-tricoro': + if game == "iidx-tricoro": return IIDXTricoroClient( proto, pcbid, config, ) - if game == 'bishi': + if game == "bishi": return TheStarBishiBashiClient( proto, pcbid, config, ) - if game == 'ddr-x2': + if game == "ddr-x2": return DDRX2Client( proto, pcbid, config, ) - if game == 'ddr-x3': + if game == "ddr-x3": return DDRX3Client( proto, pcbid, config, ) - if game == 'ddr-2013': + if game == "ddr-2013": return DDR2013Client( proto, pcbid, config, ) - if game == 'ddr-2014': + if game == "ddr-2014": return DDR2014Client( proto, pcbid, config, ) - if game == 'ddr-ace': + if game == "ddr-ace": return DDRAceClient( proto, pcbid, config, ) - if game == 'sdvx-booth': + if game == "sdvx-booth": return SoundVoltexBoothClient( proto, pcbid, config, ) - if game == 'sdvx-infinite-infection': + if game == "sdvx-infinite-infection": return SoundVoltexInfiniteInfectionClient( proto, pcbid, config, ) - if game == 'sdvx-gravity-wars-s1': + if game == "sdvx-gravity-wars-s1": return SoundVoltexGravityWarsS1Client( proto, pcbid, config, ) - if game == 'sdvx-gravity-wars-s2': + if game == "sdvx-gravity-wars-s2": return SoundVoltexGravityWarsS2Client( proto, pcbid, config, ) - if game == 'sdvx-heavenly-haven': + if game == "sdvx-heavenly-haven": return SoundVoltexHeavenlyHavenClient( proto, pcbid, config, ) - if game == 'museca-1': + if game == "museca-1": return Museca1Client( proto, pcbid, config, ) - if game == 'museca-1+1/2': + if game == "museca-1+1/2": return Museca1PlusClient( proto, pcbid, config, ) - if game == 'reflec': + if game == "reflec": return ReflecBeat( proto, pcbid, config, ) - if game == 'reflec-limelight': + if game == "reflec-limelight": return ReflecBeatLimelight( proto, pcbid, config, ) - if game == 'reflec-colette': + if game == "reflec-colette": return ReflecBeatColette( proto, pcbid, config, ) - if game == 'reflec-groovin-upper': + if game == "reflec-groovin-upper": return ReflecBeatGroovinUpper( proto, pcbid, config, ) - if game == 'reflec-volzza': + if game == "reflec-volzza": return ReflecBeatVolzza( proto, pcbid, config, ) - if game == 'reflec-volzza2': + if game == "reflec-volzza2": return ReflecBeatVolzza2( proto, pcbid, config, ) - if game == 'metal-gear-arcade': + if game == "metal-gear-arcade": return MetalGearArcadeClient( proto, pcbid, config, ) - raise Exception(f'Unknown game {game}') + raise Exception(f"Unknown game {game}") -def mainloop(address: str, port: int, configfile: str, action: str, game: str, cardid: Optional[str], verbose: bool) -> None: +def mainloop( + address: str, + port: int, + configfile: str, + action: str, + game: str, + cardid: Optional[str], + verbose: bool, +) -> None: games = { - 'pnm-tune-street': { - 'name': "Pop'n Music Tune Street", - 'model': "K39:J:B:A:2010122200", - 'old_profile_model': "J39:J:A:A", - 'avs': None, + "pnm-tune-street": { + "name": "Pop'n Music Tune Street", + "model": "K39:J:B:A:2010122200", + "old_profile_model": "J39:J:A:A", + "avs": None, }, - 'pnm-fantasia': { - 'name': "Pop'n Music Fantasia", - 'model': "L39:J:B:A:2012091900", - 'old_profile_model': "K39:J:A:A", - 'avs': "2.13.6 r4921", + "pnm-fantasia": { + "name": "Pop'n Music Fantasia", + "model": "L39:J:B:A:2012091900", + "old_profile_model": "K39:J:A:A", + "avs": "2.13.6 r4921", }, - 'pnm-sunny-park': { - 'name': "Pop'n Music Sunny Park", - 'model': "M39:J:B:A:2014061900", - 'old_profile_model': "L39:J:A:A", - 'avs': "2.15.8 r6631", + "pnm-sunny-park": { + "name": "Pop'n Music Sunny Park", + "model": "M39:J:B:A:2014061900", + "old_profile_model": "L39:J:A:A", + "avs": "2.15.8 r6631", }, - 'pnm-lapistoria': { - 'name': "Pop'n Music Lapistoria", - 'model': "M39:J:B:A:2015081900", - 'old_profile_model': "M39:J:B:A", - 'avs': "2.15.8 r6631", + "pnm-lapistoria": { + "name": "Pop'n Music Lapistoria", + "model": "M39:J:B:A:2015081900", + "old_profile_model": "M39:J:B:A", + "avs": "2.15.8 r6631", }, - 'pnm-eclale': { - 'name': "Pop'n Music Eclale", - 'model': "M39:J:B:A:2016100500", - 'old_profile_model': "M39:J:B:A", - 'avs': "2.15.8 r6631", + "pnm-eclale": { + "name": "Pop'n Music Eclale", + "model": "M39:J:B:A:2016100500", + "old_profile_model": "M39:J:B:A", + "avs": "2.15.8 r6631", }, - 'pnm-usaneko': { - 'name': "Pop'n Music Usagi to Neko to Shounen no Yume", - 'model': "M39:J:B:A:2018101500", - 'old_profile_model': "M39:J:B:A", - 'avs': "2.15.8 r6631", + "pnm-usaneko": { + "name": "Pop'n Music Usagi to Neko to Shounen no Yume", + "model": "M39:J:B:A:2018101500", + "old_profile_model": "M39:J:B:A", + "avs": "2.15.8 r6631", }, - 'pnm-peace': { - 'name': "Pop'n Music peace", - 'model': "M39:J:B:A:2020092800", - 'old_profile_model': "M39:J:B:A", - 'avs': "2.15.8 r6631", + "pnm-peace": { + "name": "Pop'n Music peace", + "model": "M39:J:B:A:2020092800", + "old_profile_model": "M39:J:B:A", + "avs": "2.15.8 r6631", }, - 'pnm-kaimei': { - 'name': "Pop'n Music Kaimei riddles", - 'model': "M39:J:B:A:2022061300", - 'old_profile_model': "M39:J:B:A", - 'avs': "2.15.8 r6631", + "pnm-kaimei": { + "name": "Pop'n Music Kaimei riddles", + "model": "M39:J:B:A:2022061300", + "old_profile_model": "M39:J:B:A", + "avs": "2.15.8 r6631", }, - 'jubeat-saucer': { - 'name': "Jubeat Saucer", - 'model': "L44:J:A:A:2014012802", - 'avs': "2.15.8 r6631", + "jubeat-saucer": { + "name": "Jubeat Saucer", + "model": "L44:J:A:A:2014012802", + "avs": "2.15.8 r6631", }, - 'jubeat-saucer-fulfill': { - 'name': "Jubeat Saucer Fulfill", - 'model': "L44:J:B:A:2014111800", - 'avs': "2.15.8 r6631", + "jubeat-saucer-fulfill": { + "name": "Jubeat Saucer Fulfill", + "model": "L44:J:B:A:2014111800", + "avs": "2.15.8 r6631", }, - 'jubeat-prop': { - 'name': "Jubeat Prop", - 'model': "L44:J:B:A:2016031700", - 'avs': "2.15.8 r6631", + "jubeat-prop": { + "name": "Jubeat Prop", + "model": "L44:J:B:A:2016031700", + "avs": "2.15.8 r6631", }, - 'jubeat-qubell': { - 'name': "Jubeat Qubell", - 'model': "L44:J:D:A:2016111400", - 'avs': "2.15.8 r6631", + "jubeat-qubell": { + "name": "Jubeat Qubell", + "model": "L44:J:D:A:2016111400", + "avs": "2.15.8 r6631", }, - 'jubeat-clan': { - 'name': "Jubeat Clan", - 'model': "L44:J:E:A:2018070901", - 'avs': "2.17.3 r8311", + "jubeat-clan": { + "name": "Jubeat Clan", + "model": "L44:J:E:A:2018070901", + "avs": "2.17.3 r8311", }, - 'jubeat-festo': { - 'name': "Jubeat Festo", - 'model': "L44:J:B:A:2022052400", - 'avs': "2.17.3 r8311", + "jubeat-festo": { + "name": "Jubeat Festo", + "model": "L44:J:B:A:2022052400", + "avs": "2.17.3 r8311", }, - 'iidx-rootage': { - 'name': "Beatmania IIDX ROOTAGE", - 'model': "LDJ:J:A:A:2019090200", - 'avs': "2.17.0 r7883" + "iidx-rootage": { + "name": "Beatmania IIDX ROOTAGE", + "model": "LDJ:J:A:A:2019090200", + "avs": "2.17.0 r7883", }, - 'iidx-cannonballers': { - 'name': "Beatmania IIDX CANNON BALLERS", - 'model': "LDJ:J:A:A:2018091900", - 'avs': "2.17.0 r7883" + "iidx-cannonballers": { + "name": "Beatmania IIDX CANNON BALLERS", + "model": "LDJ:J:A:A:2018091900", + "avs": "2.17.0 r7883", }, - 'iidx-sinobuz': { - 'name': "Beatmania IIDX SINOBUZ", - 'model': "LDJ:J:A:A:2017082800", - 'avs': "2.16.1 r6901", + "iidx-sinobuz": { + "name": "Beatmania IIDX SINOBUZ", + "model": "LDJ:J:A:A:2017082800", + "avs": "2.16.1 r6901", }, - 'iidx-copula': { - 'name': "Beatmania IIDX copula", - 'model': "LDJ:J:A:A:2016083100", - 'avs': "2.16.1 r6901", + "iidx-copula": { + "name": "Beatmania IIDX copula", + "model": "LDJ:J:A:A:2016083100", + "avs": "2.16.1 r6901", }, - 'iidx-pendual': { - 'name': "Beatmania IIDX PENDUAL", - 'model': "LDJ:A:A:A:2015080500", - 'avs': "2.16.1 r6901", + "iidx-pendual": { + "name": "Beatmania IIDX PENDUAL", + "model": "LDJ:A:A:A:2015080500", + "avs": "2.16.1 r6901", }, - 'iidx-spada': { - 'name': "Beatmania IIDX SPADA", - 'model': "LDJ:A:A:A:2014071600", - 'avs': "2.16.1 r6901", + "iidx-spada": { + "name": "Beatmania IIDX SPADA", + "model": "LDJ:A:A:A:2014071600", + "avs": "2.16.1 r6901", }, - 'iidx-tricoro': { - 'name': "Beatmania IIDX Tricoro", - 'model': "LDJ:J:A:A:2013090900", - 'avs': "2.15.8 r6631", + "iidx-tricoro": { + "name": "Beatmania IIDX Tricoro", + "model": "LDJ:J:A:A:2013090900", + "avs": "2.15.8 r6631", }, - 'bishi': { - 'name': "The★BishiBashi", - 'model': "IBB:A:A:A:2009092900", - 'avs': None, + "bishi": { + "name": "The★BishiBashi", + "model": "IBB:A:A:A:2009092900", + "avs": None, }, - 'ddr-x2': { - 'name': "DanceDanceRevolution X2", - 'model': "JDX:J:A:A:2010111000", - 'avs': None, + "ddr-x2": { + "name": "DanceDanceRevolution X2", + "model": "JDX:J:A:A:2010111000", + "avs": None, }, - 'ddr-x3': { - 'name': "DanceDanceRevolution X3 VS 2ndMIX", - 'model': "KDX:J:A:A:2012112600", - 'avs': "2.13.6 r4921", + "ddr-x3": { + "name": "DanceDanceRevolution X3 VS 2ndMIX", + "model": "KDX:J:A:A:2012112600", + "avs": "2.13.6 r4921", }, - 'ddr-2013': { - 'name': "DanceDanceRevolution (2013)", - 'model': "MDX:J:A:A:2014032700", - 'avs': "2.15.8 r6631", + "ddr-2013": { + "name": "DanceDanceRevolution (2013)", + "model": "MDX:J:A:A:2014032700", + "avs": "2.15.8 r6631", }, - 'ddr-2014': { - 'name': "DanceDanceRevolution (2014)", - 'model': "MDX:A:A:A:2015122100", - 'avs': "2.15.8 r6631", + "ddr-2014": { + "name": "DanceDanceRevolution (2014)", + "model": "MDX:A:A:A:2015122100", + "avs": "2.15.8 r6631", }, - 'ddr-ace': { - 'name': "DanceDanceRevolution A", - 'model': "MDX:U:D:A:2017121400", - 'avs': "2.15.8 r6631", + "ddr-ace": { + "name": "DanceDanceRevolution A", + "model": "MDX:U:D:A:2017121400", + "avs": "2.15.8 r6631", }, - 'sdvx-booth': { - 'name': "SOUND VOLTEX BOOTH", - 'model': "KFC:J:A:A:2013052900", - 'avs': "2.15.8 r6631", + "sdvx-booth": { + "name": "SOUND VOLTEX BOOTH", + "model": "KFC:J:A:A:2013052900", + "avs": "2.15.8 r6631", }, - 'sdvx-infinite-infection': { - 'name': "SOUND VOLTEX II -infinite infection-", - 'model': "KFC:J:A:A:2014102200", - 'avs': "2.15.8 r6631", + "sdvx-infinite-infection": { + "name": "SOUND VOLTEX II -infinite infection-", + "model": "KFC:J:A:A:2014102200", + "avs": "2.15.8 r6631", }, - 'sdvx-gravity-wars-s1': { - 'name': "SOUND VOLTEX III GRAVITY WARS Season 1", - 'model': "KFC:J:A:A:2015111602", - 'avs': "2.15.8 r6631", + "sdvx-gravity-wars-s1": { + "name": "SOUND VOLTEX III GRAVITY WARS Season 1", + "model": "KFC:J:A:A:2015111602", + "avs": "2.15.8 r6631", }, - 'sdvx-gravity-wars-s2': { - 'name': "SOUND VOLTEX III GRAVITY WARS Season 2", - 'model': "KFC:J:A:A:2016121900", - 'avs': "2.15.8 r6631", + "sdvx-gravity-wars-s2": { + "name": "SOUND VOLTEX III GRAVITY WARS Season 2", + "model": "KFC:J:A:A:2016121900", + "avs": "2.15.8 r6631", }, - 'sdvx-heavenly-haven': { - 'name': "SOUND VOLTEX IV HEAVENLY HAVEN", - 'model': "KFC:J:A:A:2019020600", - 'avs': "2.15.8 r6631", + "sdvx-heavenly-haven": { + "name": "SOUND VOLTEX IV HEAVENLY HAVEN", + "model": "KFC:J:A:A:2019020600", + "avs": "2.15.8 r6631", }, - 'museca-1': { - 'name': "MÚSECA", - 'model': "PIX:J:A:A:2016071300", - 'avs': "2.17.0 r7883", + "museca-1": { + "name": "MÚSECA", + "model": "PIX:J:A:A:2016071300", + "avs": "2.17.0 r7883", }, - 'museca-1+1/2': { - 'name': "MÚSECA 1+1/2", - 'model': "PIX:J:A:A:2017042600", - 'avs': "2.17.0 r7883", + "museca-1+1/2": { + "name": "MÚSECA 1+1/2", + "model": "PIX:J:A:A:2017042600", + "avs": "2.17.0 r7883", }, - 'reflec': { - 'name': "REFLEC BEAT", - 'model': "KBR:A:A:A:2011112300", - 'avs': None, + "reflec": { + "name": "REFLEC BEAT", + "model": "KBR:A:A:A:2011112300", + "avs": None, }, - 'reflec-limelight': { - 'name': "REFLEC BEAT limelight", - 'model': "LBR:A:A:A:2012082900", - 'avs': "2.13.6 r4921", + "reflec-limelight": { + "name": "REFLEC BEAT limelight", + "model": "LBR:A:A:A:2012082900", + "avs": "2.13.6 r4921", }, - 'reflec-colette': { - 'name': "REFLEC BEAT colette", - 'model': "MBR:J:A:A:2014011600", - 'avs': "2.15.8 r6631", + "reflec-colette": { + "name": "REFLEC BEAT colette", + "model": "MBR:J:A:A:2014011600", + "avs": "2.15.8 r6631", }, - 'reflec-groovin-upper': { - 'name': "REFLEC BEAT groovin'!! Upper", - 'model': "MBR:J:A:A:2015102100", - 'avs': "2.15.8 r6631", + "reflec-groovin-upper": { + "name": "REFLEC BEAT groovin'!! Upper", + "model": "MBR:J:A:A:2015102100", + "avs": "2.15.8 r6631", }, - 'reflec-volzza': { - 'name': "REFLEC BEAT VOLZZA", - 'model': "MBR:J:A:A:2016030200", - 'avs': "2.15.8 r6631", + "reflec-volzza": { + "name": "REFLEC BEAT VOLZZA", + "model": "MBR:J:A:A:2016030200", + "avs": "2.15.8 r6631", }, - 'reflec-volzza2': { - 'name': "REFLEC BEAT VOLZZA 2", - 'model': "MBR:J:A:A:2016100400", - 'avs': "2.15.8 r6631", + "reflec-volzza2": { + "name": "REFLEC BEAT VOLZZA 2", + "model": "MBR:J:A:A:2016100400", + "avs": "2.15.8 r6631", }, - 'metal-gear-arcade': { - 'name': "Metal Gear Arcade", - 'model': "I36:J:A:A:2011092900", - 'avs': None, + "metal-gear-arcade": { + "name": "Metal Gear Arcade", + "model": "I36:J:A:A:2011092900", + "avs": None, }, } - if action == 'list': + if action == "list": for game in sorted([game for game in games]): print(f'{game} - {games[game]["name"]}') sys.exit(0) - if action == 'game': + if action == "game": if game not in games: - print(f'Unknown game {game}') + print(f"Unknown game {game}") sys.exit(2) config = yaml.safe_load(open(configfile)) @@ -544,11 +554,11 @@ def mainloop(address: str, port: int, configfile: str, action: str, game: str, c ClientProtocol( address, port, - config['core']['encryption'], - config['core']['compression'], + config["core"]["encryption"], + config["core"]["compression"], verbose, ), - config['core']['pcbid'], + config["core"]["pcbid"], game, games[game], ) @@ -557,69 +567,107 @@ def mainloop(address: str, port: int, configfile: str, action: str, game: str, c def main() -> None: - parser = argparse.ArgumentParser(description="A utility to generate game-like traffic for testing an eAmusement server.") - parser.add_argument("-p", "--port", help="Port to talk to. Defaults to 80", type=int, default=80) - parser.add_argument("-a", "--address", help="Address to talk to. Defaults to 127.0.0.1", type=str, default="127.0.0.1") - parser.add_argument("-c", "--config", help="Core configuration. Defaults to trafficgen.yaml", type=str, default="trafficgen.yaml") - parser.add_argument("-g", "--game", help="The game that should be emulated. Should be one of the games returned by --list", type=str, default=None) - parser.add_argument("-l", "--list", help="List all known games and exit.", action="store_true") - parser.add_argument("-i", "--cardid", help="Use this card ID instead of a random one.", type=str, default=None) - parser.add_argument("-v", "--verbose", help="Print packets that are sent/received.", action='store_true', default=False) + parser = argparse.ArgumentParser( + description="A utility to generate game-like traffic for testing an eAmusement server." + ) + parser.add_argument( + "-p", "--port", help="Port to talk to. Defaults to 80", type=int, default=80 + ) + parser.add_argument( + "-a", + "--address", + help="Address to talk to. Defaults to 127.0.0.1", + type=str, + default="127.0.0.1", + ) + parser.add_argument( + "-c", + "--config", + help="Core configuration. Defaults to trafficgen.yaml", + type=str, + default="trafficgen.yaml", + ) + parser.add_argument( + "-g", + "--game", + help="The game that should be emulated. Should be one of the games returned by --list", + type=str, + default=None, + ) + parser.add_argument( + "-l", "--list", help="List all known games and exit.", action="store_true" + ) + parser.add_argument( + "-i", + "--cardid", + help="Use this card ID instead of a random one.", + type=str, + default=None, + ) + parser.add_argument( + "-v", + "--verbose", + help="Print packets that are sent/received.", + action="store_true", + default=False, + ) args = parser.parse_args() if args.list: - action = 'list' + action = "list" game = None elif args.game: - action = 'game' + action = "game" game = args.game else: print("Unknown action to perform. Please specify --game or --list") sys.exit(1) game = { - 'pnm-19': 'pnm-tune-street', - 'pnm-20': 'pnm-fantasia', - 'pnm-21': 'pnm-sunny-park', - 'pnm-22': 'pnm-lapistoria', - 'pnm-23': 'pnm-eclale', - 'pnm-24': 'pnm-usaneko', - 'pnm-25': 'pnm-peace', - 'pnm-26': 'pnm-kaimei', - 'iidx-20': 'iidx-tricoro', - 'iidx-21': 'iidx-spada', - 'iidx-22': 'iidx-pendual', - 'iidx-23': 'iidx-copula', - 'iidx-24': 'iidx-sinobuz', - 'iidx-25': 'iidx-cannonballers', - 'iidx-26': 'iidx-rootage', - 'jubeat-5': 'jubeat-saucer', - 'jubeat-5+': 'jubeat-saucer-fulfill', - 'jubeat-6': 'jubeat-prop', - 'jubeat-7': 'jubeat-qubell', - 'jubeat-8': 'jubeat-clan', - 'jubeat-9': 'jubeat-festo', - 'ddr-12': 'ddr-x2', - 'ddr-13': 'ddr-x3', - 'ddr-14': 'ddr-2013', - 'ddr-15': 'ddr-2014', - 'ddr-16': 'ddr-ace', - 'sdvx-1': 'sdvx-booth', - 'sdvx-2': 'sdvx-infinite-infection', - 'sdvx-3s1': 'sdvx-gravity-wars-s1', - 'sdvx-3s2': 'sdvx-gravity-wars-s2', - 'sdvx-4': 'sdvx-heavenly-haven', - 'reflec-1': 'reflec', - 'reflec-2': 'reflec-limelight', - 'reflec-3': 'reflec-colette', - 'reflec-4': 'reflec-groovin-upper', - 'reflec-5': 'reflec-volzza', - 'reflec-6': 'reflec-volzza2', - 'mga': 'metal-gear-arcade', + "pnm-19": "pnm-tune-street", + "pnm-20": "pnm-fantasia", + "pnm-21": "pnm-sunny-park", + "pnm-22": "pnm-lapistoria", + "pnm-23": "pnm-eclale", + "pnm-24": "pnm-usaneko", + "pnm-25": "pnm-peace", + "pnm-26": "pnm-kaimei", + "iidx-20": "iidx-tricoro", + "iidx-21": "iidx-spada", + "iidx-22": "iidx-pendual", + "iidx-23": "iidx-copula", + "iidx-24": "iidx-sinobuz", + "iidx-25": "iidx-cannonballers", + "iidx-26": "iidx-rootage", + "jubeat-5": "jubeat-saucer", + "jubeat-5+": "jubeat-saucer-fulfill", + "jubeat-6": "jubeat-prop", + "jubeat-7": "jubeat-qubell", + "jubeat-8": "jubeat-clan", + "jubeat-9": "jubeat-festo", + "ddr-12": "ddr-x2", + "ddr-13": "ddr-x3", + "ddr-14": "ddr-2013", + "ddr-15": "ddr-2014", + "ddr-16": "ddr-ace", + "sdvx-1": "sdvx-booth", + "sdvx-2": "sdvx-infinite-infection", + "sdvx-3s1": "sdvx-gravity-wars-s1", + "sdvx-3s2": "sdvx-gravity-wars-s2", + "sdvx-4": "sdvx-heavenly-haven", + "reflec-1": "reflec", + "reflec-2": "reflec-limelight", + "reflec-3": "reflec-colette", + "reflec-4": "reflec-groovin-upper", + "reflec-5": "reflec-volzza", + "reflec-6": "reflec-volzza2", + "mga": "metal-gear-arcade", }.get(game, game) - mainloop(args.address, args.port, args.config, action, game, args.cardid, args.verbose) + mainloop( + args.address, args.port, args.config, action, game, args.cardid, args.verbose + ) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/bemani/utils/twodxutils.py b/bemani/utils/twodxutils.py index 2accc2c..0bdd956 100644 --- a/bemani/utils/twodxutils.py +++ b/bemani/utils/twodxutils.py @@ -5,7 +5,9 @@ from bemani.format import TwoDX def main() -> None: - parser = argparse.ArgumentParser(description="A utility to extract/build 2dx files.") + parser = argparse.ArgumentParser( + description="A utility to extract/build 2dx files." + ) parser.add_argument( "file", help="2dx file to extract/build.", @@ -39,26 +41,26 @@ def main() -> None: if args.directory is not None: root = args.directory - if root[-1] != '/': - root = root + '/' + if root[-1] != "/": + root = root + "/" root = os.path.realpath(root) - rfp = open(args.file, 'rb') + rfp = open(args.file, "rb") data = rfp.read() rfp.close() twodx = TwoDX(data) for fn in twodx.filenames: - print(f'Extracting {fn} to disk...') + print(f"Extracting {fn} to disk...") realfn = os.path.join(root, fn) dirof = os.path.dirname(realfn) os.makedirs(dirof, exist_ok=True) - with open(realfn, 'wb') as wfp: + with open(realfn, "wb") as wfp: wfp.write(twodx.read_file(fn)) elif len(args.wavfile) > 0: try: - rfp = open(args.file, 'rb') + rfp = open(args.file, "rb") data = rfp.read() rfp.close() @@ -71,18 +73,20 @@ def main() -> None: twodx.set_name(os.path.splitext(os.path.basename(args.file))[0]) for fn in args.wavfile: - rfp = open(fn, 'rb') + rfp = open(fn, "rb") data = rfp.read() rfp.close() twodx.write_file(os.path.basename(fn), data) - wfp = open(args.file, 'wb') + wfp = open(args.file, "wb") wfp.write(twodx.get_new_data()) wfp.close() else: - raise Exception("Please provide either a directory to extract to, or a wav file to build into a 2dx file!") + raise Exception( + "Please provide either a directory to extract to, or a wav file to build into a 2dx file!" + ) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/formatfiles b/formatfiles new file mode 100755 index 0000000..e9c9b1d --- /dev/null +++ b/formatfiles @@ -0,0 +1,3 @@ +#! /bin/bash + +black --extend-exclude "setup\.py|\.pyi|bemani\/data\/migrations\/" . diff --git a/requirements.txt b/requirements.txt index 2f1b5f3..2843d1a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,6 +7,7 @@ Requests mysqlclient passlib mypy +black typing-extensions types-python-dateutil types-requests diff --git a/verifylint b/verifylint index 6f1aee3..978674e 100755 --- a/verifylint +++ b/verifylint @@ -1,3 +1,3 @@ #! /bin/bash -flake8 bemani/ --ignore E501,E252,W504,B006,B008,B009 | grep -v "migrations\/" +flake8 bemani/ --ignore E203,E501,E252,W503,W504,B006,B008,B009 | grep -v "migrations\/" diff --git a/verifytyping b/verifytyping index f989aba..ed66b36 100755 --- a/verifytyping +++ b/verifytyping @@ -11,11 +11,13 @@ declare -a arr=( "frontend" "ifsutils" "iidxutils" + "jsx" "proxy" "psmap" "read" "replay" "responsegen" + "sampleclient" "scheduler" "services" "struct"