From 63e4a80ebaec36604493ee0e2d110f7568029599 Mon Sep 17 00:00:00 2001 From: Jennifer Taylor Date: Tue, 7 Sep 2021 17:55:53 +0000 Subject: [PATCH] Clean up core of backend a bit, replace functions with attributes where appropriate. --- bemani/backend/base.py | 66 +++++---- bemani/backend/core/cardmng.py | 205 +++++++++++++-------------- bemani/backend/core/core.py | 4 +- bemani/backend/core/eacoin.py | 5 +- bemani/backend/ddr/base.py | 13 +- bemani/backend/ddr/ddra20.py | 1 + bemani/backend/ddr/ddrace.py | 1 + bemani/backend/iidx/base.py | 13 +- bemani/backend/iidx/bistrover.py | 5 +- bemani/backend/iidx/cannonballers.py | 5 +- bemani/backend/iidx/heroicverse.py | 5 +- bemani/backend/iidx/rootage.py | 5 +- bemani/backend/popn/base.py | 11 +- bemani/backend/popn/common.py | 15 +- bemani/backend/reflec/base.py | 17 +-- bemani/backend/sdvx/heavenlyhaven.py | 13 +- 16 files changed, 180 insertions(+), 204 deletions(-) diff --git a/bemani/backend/base.py b/bemani/backend/base.py index 53428e9..5307430 100644 --- a/bemani/backend/base.py +++ b/bemani/backend/base.py @@ -132,6 +132,39 @@ class Base(ABC): """ name: str + @property + def extra_services(self) -> List[str]: + """ + A list of extra services that this game needs to advertise. + Override in your subclass if you need to advertise extra + services for a particular game or series. + """ + return [] + + @property + def supports_paseli(self) -> bool: + """ + An override so that particular games can disable PASELI support + regardless of the server settings. Some games and some regions + are buggy with respect to PASELI. + """ + return True + + @property + def supports_expired_profiles(self) -> bool: + """ + Override this in your subclass if your game or series requires non-expired profiles + in order to correctly present migrations to the user. + """ + return True + + @property + def requires_extended_regions(self) -> bool: + """ + Override this in your subclass if your game requires an updated region list. + """ + return False + def __init__(self, data: Data, config: Config, model: Model) -> None: self.data = data self.config = config @@ -215,35 +248,6 @@ class Base(ABC): for game in factory.MANAGED_CLASSES: yield (game.game, game.version, game.get_settings()) - def extra_services(self) -> List[str]: - """ - A list of extra services that this game needs to advertise. - Override in your subclass if you need to advertise extra - services for a particular game or series. - """ - return [] - - def supports_paseli(self) -> bool: - """ - An override so that particular games can disable PASELI support - regardless of the server settings. Some games and some regions - are buggy with respect to PASELI. - """ - return True - - def supports_expired_profiles(self) -> bool: - """ - Override this in your subclass if your game or series requires non-expired profiles - in order to correctly present migrations to the user. - """ - return True - - def requires_extended_regions(self) -> bool: - """ - Override this in your subclass if your game requires an updated region list. - """ - return False - def bind_profile(self, userid: UserID) -> None: """ Handling binding the user's profile to this version on this server. @@ -437,9 +441,9 @@ 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) diff --git a/bemani/backend/core/cardmng.py b/bemani/backend/core/cardmng.py index 9763a9a..3befbf2 100644 --- a/bemani/backend/core/cardmng.py +++ b/bemani/backend/core/cardmng.py @@ -1,5 +1,3 @@ -from typing import Optional - from bemani.backend.base import Base, Status from bemani.protocol import Node from bemani.common import Model @@ -9,120 +7,113 @@ class CardManagerHandler(Base): """ The class that handles card management. This assumes it is attached as a mixin to a game class so that it can understand if there's a profile for a game or not. + + Handle a request for card management. This is independent of a game's profile handling, + but still gives the game information as to whether or not a profile exists for a game. + These methods handle looking up a card, handling binding a profile to a game version, + returning whether a game profile exists or should be migrated, and creating a new account + when no account is associated with a card. """ - def handle_cardmng_request(self, request: Node) -> Optional[Node]: - """ - Handle a request for card management. This is independent of a game's profile handling, - but still gives the game information as to whether or not a profile exists for a game. - These methods handle looking up a card, handling binding a profile to a game version, - returning whether a game profile exists or should be migrated, and creating a new account - when no account is associated with a card. - """ - method = request.attribute('method') - - if method == 'inquire': - # 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') - 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)) - return root - - # Special handling for looking up whether the previous game's profile existed. If we - # don't do this then some games won't present the user with a migration. - bound = self.has_profile(userid) - expired = False - if bound is False: - if modelstring is not None: - model = Model.from_modelstring(modelstring) - oldgame = Base.create(self.data, self.config, model, self.model) - if oldgame is not None: - bound = oldgame.has_profile(userid) - expired = self.supports_expired_profiles() - - 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 + def handle_cardmng_inquire_request(self, request: Node) -> Node: + # 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') + 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('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') - - # 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') - - # 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') - - # Whether to allow paseli, as enabled by the operator and arcade owner. - 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('status', str(Status.NOT_REGISTERED)) return root - elif method == 'authpass': - # Given a dataid/refid previously found via inquire, verify the pin - 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 + # Special handling for looking up whether the previous game's profile existed. If we + # don't do this then some games won't present the user with a migration. + bound = self.has_profile(userid) + expired = False + if bound is False: + if modelstring is not None: + model = Model.from_modelstring(modelstring) + oldgame = Base.create(self.data, self.config, model, self.model) + if oldgame is not None: + bound = oldgame.has_profile(userid) + expired = self.supports_expired_profiles + + 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 + + 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') + + # 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') + + # 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') + + # Whether to allow paseli, as enabled by the operator and arcade owner. + 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') + 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') + 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)) + 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') + 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.SUCCESS if valid else Status.INVALID_PIN)) + root.set_attribute('status', str(Status.NOT_ALLOWED)) return root - elif method == 'getrefid': - # 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') - 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)) - 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) + 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) - 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') + 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) + return root - elif method == 'bindmodel': - # Given a refid, bind the user's card to the current version of the game - 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) - 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') + return root - elif method == 'getkeepspan': - # Unclear what this method does, return an arbitrary span - root = Node.void('cardmng') - root.set_attribute('keepspan', '30') - return root - - elif method == 'getdatalist': - # Unclear what this method does, return a dummy response - root = Node.void('cardmng') - return root - - # Invalid method - return None + def handle_cardmng_getdatalist_request(self, request: Node) -> Node: + # Unclear what this method does, return a dummy response + root = Node.void('cardmng') + return root diff --git a/bemani/backend/core/core.py b/bemani/backend/core/core.py index 603a043..6e9e729 100644 --- a/bemani/backend/core/core.py +++ b/bemani/backend/core/core.py @@ -41,7 +41,7 @@ class CoreHandler(Base): root.add_child(item('pcbtracker', url)) root.add_child(item('pkglist', url)) root.add_child(item('posevent', url)) - for srv in self.extra_services(): + for srv in self.extra_services: root.add_child(item(srv, url)) root.add_child(item('ntp', 'ntp://pool.ntp.org/')) @@ -61,7 +61,7 @@ class CoreHandler(Base): """ # 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('ecenable', '1' if (self.supports_paseli and self.config.paseli.enabled) else '0') root.set_attribute('expire', '600') return root diff --git a/bemani/backend/core/eacoin.py b/bemani/backend/core/eacoin.py index a1d5d18..92058c1 100644 --- a/bemani/backend/core/eacoin.py +++ b/bemani/backend/core/eacoin.py @@ -1,4 +1,5 @@ from typing import Optional +from typing_extensions import Final from bemani.backend.base import Base, Status from bemani.protocol import Node @@ -10,14 +11,14 @@ class PASELIHandler(Base): A mixin that can be used to provide PASELI services to a game. """ - INFINITE_PASELI_AMOUNT = 57300 + INFINITE_PASELI_AMOUNT: Final[int] = 57300 """ Override this in your subclass if the particular game/series needs a different padding amount to display PASELI transactions on the operator menu. """ - paseli_padding = 1 + paseli_padding: int = 1 def handle_eacoin_request(self, request: Node) -> Optional[Node]: """ diff --git a/bemani/backend/ddr/base.py b/bemani/backend/ddr/base.py index 1ce9b39..cb70ae3 100644 --- a/bemani/backend/ddr/base.py +++ b/bemani/backend/ddr/base.py @@ -51,6 +51,11 @@ class DDRBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): CHART_DOUBLE_EXPERT = 8 CHART_DOUBLE_CHALLENGE = 9 + # Return the local2 service so that DDR Ace will send certain packets. + extra_services: List[str] = [ + 'local2', + ] + def __init__(self, data: Data, config: Config, model: Model) -> None: super().__init__(data, config, model) if model.rev == 'X': @@ -64,14 +69,6 @@ class DDRBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): return DBConstants.OMNIMIX_VERSION_BUMP + self.version return self.version - def extra_services(self) -> List[str]: - """ - Return the local2 service so that DDR Ace will send certain packets. - """ - return [ - 'local2', - ] - def game_to_db_rank(self, game_rank: int) -> int: """ Given a game's rank constant, return the rank as defined above. diff --git a/bemani/backend/ddr/ddra20.py b/bemani/backend/ddr/ddra20.py index 51b50e8..0bfca00 100644 --- a/bemani/backend/ddr/ddra20.py +++ b/bemani/backend/ddr/ddra20.py @@ -16,6 +16,7 @@ class DDRA20( def previous_version(self) -> Optional[DDRBase]: return DDRAce(self.data, self.config, self.model) + @property def supports_paseli(self) -> bool: if self.model.dest != 'J': # DDR Ace in USA mode doesn't support PASELI properly. diff --git a/bemani/backend/ddr/ddrace.py b/bemani/backend/ddr/ddrace.py index b5c3ebb..40d7531 100644 --- a/bemani/backend/ddr/ddrace.py +++ b/bemani/backend/ddr/ddrace.py @@ -104,6 +104,7 @@ class DDRAce( def previous_version(self) -> Optional[DDRBase]: return DDR2014(self.data, self.config, self.model) + @property def supports_paseli(self) -> bool: if self.model.dest != 'J': # DDR Ace in USA mode doesn't support PASELI properly. diff --git a/bemani/backend/iidx/base.py b/bemani/backend/iidx/base.py index f836fa5..d328ef0 100644 --- a/bemani/backend/iidx/base.py +++ b/bemani/backend/iidx/base.py @@ -75,6 +75,11 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): GHOST_TYPE_RIVAL_TOP = 800 GHOST_TYPE_RIVAL_AVERAGE = 900 + # Return the local2 service so that Copula and above will send certain packets. + extra_services: List[str] = [ + 'local2', + ] + def __init__(self, data: Data, config: Config, model: Model) -> None: super().__init__(data, config, model) if model.rev == 'X': @@ -95,14 +100,6 @@ class IIDXBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): """ return None - def extra_services(self) -> List[str]: - """ - Return the local2 service so that Copula and above will send certain packets. - """ - return [ - 'local2', - ] - def format_profile(self, userid: UserID, profile: Profile) -> Node: """ Base handler for a profile. Given a userid and a profile dictionary, diff --git a/bemani/backend/iidx/bistrover.py b/bemani/backend/iidx/bistrover.py index 9b202ad..21f55ce 100644 --- a/bemani/backend/iidx/bistrover.py +++ b/bemani/backend/iidx/bistrover.py @@ -11,8 +11,7 @@ class IIDXBistrover(IIDXBase): name = 'Beatmania IIDX BISTROVER' version = VersionConstants.IIDX_BISTROVER + requires_extended_regions = True + def previous_version(self) -> Optional[IIDXBase]: return IIDXHeroicVerse(self.data, self.config, self.model) - - def requires_extended_regions(self) -> bool: - return True diff --git a/bemani/backend/iidx/cannonballers.py b/bemani/backend/iidx/cannonballers.py index e327c84..0a5d276 100644 --- a/bemani/backend/iidx/cannonballers.py +++ b/bemani/backend/iidx/cannonballers.py @@ -86,6 +86,8 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): FAVORITE_LIST_LENGTH = 20 + requires_extended_regions: bool = True + def previous_version(self) -> Optional[IIDXBase]: return IIDXSinobuz(self.data, self.config, self.model) @@ -160,9 +162,6 @@ class IIDXCannonBallers(IIDXCourse, IIDXBase): ], } - def requires_extended_regions(self) -> bool: - return True - def db_to_game_status(self, db_status: int) -> int: return { self.CLEAR_STATUS_NO_PLAY: self.GAME_CLEAR_STATUS_NO_PLAY, diff --git a/bemani/backend/iidx/heroicverse.py b/bemani/backend/iidx/heroicverse.py index 3572cec..c84c3fc 100644 --- a/bemani/backend/iidx/heroicverse.py +++ b/bemani/backend/iidx/heroicverse.py @@ -11,8 +11,7 @@ class IIDXHeroicVerse(IIDXBase): name = 'Beatmania IIDX HEROIC VERSE' version = VersionConstants.IIDX_HEROIC_VERSE + requires_extended_regions = True + def previous_version(self) -> Optional[IIDXBase]: return IIDXRootage(self.data, self.config, self.model) - - def requires_extended_regions(self) -> bool: - return True diff --git a/bemani/backend/iidx/rootage.py b/bemani/backend/iidx/rootage.py index 3cf7817..a2b2ab0 100644 --- a/bemani/backend/iidx/rootage.py +++ b/bemani/backend/iidx/rootage.py @@ -86,6 +86,8 @@ class IIDXRootage(IIDXCourse, IIDXBase): FAVORITE_LIST_LENGTH = 20 + requires_extended_regions = True + def previous_version(self) -> Optional[IIDXBase]: return IIDXCannonBallers(self.data, self.config, self.model) @@ -161,9 +163,6 @@ class IIDXRootage(IIDXCourse, IIDXBase): ], } - def requires_extended_regions(self) -> bool: - return True - def db_to_game_status(self, db_status: int) -> int: return { self.CLEAR_STATUS_NO_PLAY: self.GAME_CLEAR_STATUS_NO_PLAY, diff --git a/bemani/backend/popn/base.py b/bemani/backend/popn/base.py index 1df8fa2..b2bd182 100644 --- a/bemani/backend/popn/base.py +++ b/bemani/backend/popn/base.py @@ -42,6 +42,10 @@ class PopnMusicBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): OLD_PROFILE_ONLY = 1 OLD_PROFILE_FALLTHROUGH = 2 + # Pop'n Music in particular requires non-expired profiles to do conversions + # properly. + supports_expired_profiles = False + def previous_version(self) -> Optional['PopnMusicBase']: """ Returns the previous version of the game, based on this game. Should @@ -49,13 +53,6 @@ class PopnMusicBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): """ return None - def supports_expired_profiles(self) -> bool: - """ - Pop'n Music in particular requires non-expired profiles to do conversions - properly. - """ - return False - def format_profile(self, userid: UserID, profile: Profile) -> Node: """ Base handler for a profile. Given a userid and a profile dictionary, diff --git a/bemani/backend/popn/common.py b/bemani/backend/popn/common.py index 51ca73d..d251ba3 100644 --- a/bemani/backend/popn/common.py +++ b/bemani/backend/popn/common.py @@ -46,15 +46,12 @@ class PopnMusicModernBase(PopnMusicBase, metaclass=ABCMeta): # Biggest ID in the music DB GAME_MAX_MUSIC_ID: int - def extra_services(self) -> List[str]: - """ - Return the local2 and lobby2 service so that Pop'n Music 24 will - send game packets. - """ - return [ - 'local2', - 'lobby2', - ] + # Return the local2 and lobby2 service so that Pop'n Music 24+ will + # send game packets. + extra_services: List[str] = [ + 'local2', + 'lobby2', + ] @classmethod def run_scheduled_work(cls, data: Data, config: Dict[str, Any]) -> List[Tuple[str, Dict[str, Any]]]: diff --git a/bemani/backend/reflec/base.py b/bemani/backend/reflec/base.py index 5a783b9..c0379c5 100644 --- a/bemani/backend/reflec/base.py +++ b/bemani/backend/reflec/base.py @@ -34,6 +34,13 @@ class ReflecBeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): COMBO_TYPE_FULL_COMBO = DBConstants.REFLEC_BEAT_COMBO_TYPE_FULL_COMBO COMBO_TYPE_FULL_COMBO_ALL_JUST = 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', + ] + def previous_version(self) -> Optional['ReflecBeatBase']: """ Returns the previous version of the game, based on this game. Should @@ -41,16 +48,6 @@ class ReflecBeatBase(CoreHandler, CardManagerHandler, PASELIHandler, Base): """ return None - def extra_services(self) -> List[str]: - """ - Return the local2 and lobby2 service so that matching will work on newer - Reflec Beat games. - """ - return [ - 'local2', - 'lobby2', - ] - def format_profile(self, userid: UserID, profile: Profile) -> Node: """ Base handler for a profile. Given a userid and a profile dictionary, diff --git a/bemani/backend/sdvx/heavenlyhaven.py b/bemani/backend/sdvx/heavenlyhaven.py index 66c8078..c6a914d 100644 --- a/bemani/backend/sdvx/heavenlyhaven.py +++ b/bemani/backend/sdvx/heavenlyhaven.py @@ -72,6 +72,11 @@ class SoundVoltexHeavenlyHaven( GAME_SKILL_NAME_ID_RIKKA = 22 # For the course that ran from 1/18/2018-2/18/2018 GAME_SKILL_NAME_ID_KAC_8TH = 23 + # Return the local2 service so that SDVX 4 and above will send certain packets. + extra_services: List[str] = [ + 'local2', + ] + @classmethod def get_settings(cls) -> Dict[str, Any]: """ @@ -109,14 +114,6 @@ class SoundVoltexHeavenlyHaven( def previous_version(self) -> Optional[SoundVoltexBase]: return SoundVoltexGravityWars(self.data, self.config, self.model) - def extra_services(self) -> List[str]: - """ - Return the local2 service so that SDVX 4 and above will send certain packets. - """ - return [ - 'local2', - ] - def __game_to_db_clear_type(self, clear_type: int) -> int: return { self.GAME_CLEAR_TYPE_NO_PLAY: self.CLEAR_TYPE_NO_PLAY,