1
0
mirror of synced 2024-11-24 22:40:12 +01:00
bemaniutils/bemani/backend/bishi/bishi.py

209 lines
7.6 KiB
Python
Raw Normal View History

# vim: set fileencoding=utf-8
import binascii
import copy
import base64
from typing import Any, Dict, List
from bemani.backend.bishi.base import BishiBashiBase
from bemani.backend.ess import EventLogHandler
from bemani.common import ValidatedDict, GameConstants, VersionConstants
from bemani.data import UserID
from bemani.protocol import Node
class TheStarBishiBashi(
EventLogHandler,
BishiBashiBase,
):
name = "The★BishiBashi"
version = VersionConstants.BISHI_BASHI_TSBB
@classmethod
def get_settings(cls) -> Dict[str, Any]:
"""
Return all of our front-end modifiably settings.
"""
return {
'bools': [
{
'name': 'Force Unlock Characters',
'tip': 'Force unlock all characters on select screen.',
'category': 'game_config',
'setting': 'force_unlock_characters',
},
],
}
def __update_shop_name(self, profiledata: bytes) -> None:
# Figure out the profile type
csvs = profiledata.split(b',')
if len(csvs) < 2:
# Not long enough to care about
return
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')
except Exception:
return
self.update_machine_name(shopname)
def handle_system_getmaster_request(self, request: Node) -> Node:
# System message
root = Node.void('system')
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')
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.
return root
# Extract new profile info from old profile
oldprofile = self.get_profile(userid)
is_new = False
if oldprofile is None:
oldprofile = ValidatedDict()
is_new = True
newprofile = self.unformat_profile(userid, request, oldprofile, is_new)
# Write new profile
self.put_profile(userid, newprofile)
# Return success!
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]
profile = None
userid = None
if refid is not None:
userid = self.data.remote.user.from_refid(self.game, self.version, refid)
if userid is not None:
profile = self.get_profile(userid)
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.
return root
def format_profile(self, userid: UserID, profiletype: str, profile: ValidatedDict) -> 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]
# Figure out the profile type
csvs = strdata.split(b',')
if len(csvs) < 2:
# Not long enough to care about
continue
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')
if force_unlock_characters:
csvs[11] = b'3ffffffffffff'
else:
# Reward characters based on playing other games on the network
hexdata = csvs[11].decode('ascii')
while (len(hexdata) & 1) != 0:
hexdata = '0' + hexdata
unlock_bits = [b for b in binascii.unhexlify(hexdata)]
while len(unlock_bits) < 7:
unlock_bits.insert(0, 0)
# Reverse the array, so indexing makes more sense
unlock_bits = unlock_bits[::-1]
# Figure out what other games were played by this user
profiles = self.data.local.user.get_games_played(userid)
# IIDX
if len([p for p in profiles if p[0] == GameConstants.IIDX]) > 0:
unlock_bits[1] = unlock_bits[1] | 0x10
# Pop'n
if len([p for p in profiles if p[0] == GameConstants.POPN_MUSIC]) > 0:
unlock_bits[1] = unlock_bits[1] | 0x60
# Jubeat
if len([p for p in profiles if p[0] == GameConstants.JUBEAT]) > 0:
unlock_bits[2] = unlock_bits[2] | 0x02
# DDR
if len([p for p in profiles if p[0] == GameConstants.DDR]) > 0:
unlock_bits[6] = unlock_bits[6] | 0x03
# GFDM characters exist, but this network has no support for
# GFDM or Gitadora, so the bits were never added.
# Reconstruct table
unlock_bits = unlock_bits[::-1]
csvs[11] = ''.join(['{:02x}'.format(x) 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')
player.add_child(record)
d = Node.string('d', base64.b64encode(strdata).decode('ascii'))
record.add_child(d)
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))
return root
def unformat_profile(self, userid: UserID, request: Node, oldprofile: ValidatedDict, is_new: bool) -> ValidatedDict:
# Profile save request, data values are base64 encoded.
# d is a CSV, and bin1 is binary data.
newprofile = copy.deepcopy(oldprofile)
strdatas: List[bytes] = []
bindatas: List[bytes] = []
record = request.child('data/record')
for node in record.children:
if node.name != 'd':
continue
profile = base64.b64decode(node.value)
# Update the shop name if this is a new profile, since we know it came
# from this cabinet. This is the only source of truth for what the
# cabinet shop name is set to.
if is_new:
self.__update_shop_name(profile)
strdatas.append(profile)
bindatas.append(base64.b64decode(node.child_value('bin1')))
newprofile['strdatas'] = strdatas
newprofile['bindatas'] = bindatas
# Keep track of play statistics across all versions
self.update_play_statistics(userid)
return newprofile