1
0
mirror of synced 2025-01-31 04:03:45 +01:00

Clean up core of backend a bit, replace functions with attributes where appropriate.

This commit is contained in:
Jennifer Taylor 2021-09-07 17:55:53 +00:00
parent c7affef159
commit 63e4a80eba
16 changed files with 180 additions and 204 deletions

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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]:
"""

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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,

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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,

View File

@ -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,

View File

@ -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]]]:

View File

@ -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,

View File

@ -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,