Files
DragonMinded_bemaniutils/bemani/client/popn/tunestreet.py
T
Jennifer Taylor 509cb4f0d9 Convert most of the format() string calls to f-strings using libcst.
Exact commands run were:

  python3 -m libcst.tool codemod convert_format_to_fstring.ConvertFormatStringCommand . --no-format
  python3 setup.py build_ext --inplace
2020-01-07 21:29:07 +00:00

380 lines
15 KiB
Python

import random
import time
from typing import Optional, Dict, List, Tuple, Any
from bemani.client.base import BaseClient
from bemani.protocol import Node
class PopnMusicTuneStreetClient(BaseClient):
NAME = 'TEST'
def verify_game_active(self) -> None:
call = self.call_node()
# Construct node
game = Node.void('game')
call.add_child(game)
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', '')
# Swap with server
resp = self.exchange('pnm/game', call)
# Verify that response is correct
self.assert_path(resp, "response/game/@status")
def verify_game_get(self) -> None:
call = self.call_node()
# Construct node
game = Node.void('game')
call.add_child(game)
game.set_attribute('method', 'get')
# Swap with server
resp = self.exchange('pnm/game', call)
# Verify that response is correct
self.assert_path(resp, "response/game")
for name in [
'game_phase',
'psp_phase',
]:
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]]:
call = self.call_node()
# Construct node
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)
if msg_type == 'new':
playerdata.set_attribute('model', self.config['old_profile_model'].split(':')[0])
# Swap with server
resp = self.exchange('pnm/playerdata', call)
if msg_type == 'new':
# Verify that response is correct
self.assert_path(resp, "response/playerdata/@status")
status = int(resp.child('playerdata').attribute('status'))
if status != 109:
raise Exception(f'Reference ID \'{ref_id}\' returned invalid status \'{status}\'')
# No score data
return None
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", "")
if name != self.NAME:
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)]
# Extract and return score data
def transform_medals(medal: int) -> Tuple[int, int, int, int]:
return (
(medal >> 0) & 0x3,
(medal >> 2) & 0x3,
(medal >> 4) & 0x3,
(medal >> 6) & 0x3,
)
medals = [transform_medals(medal) for medal in medals]
hiscore = resp.child('playerdata').child('hiscore').value
hiscores = []
for i in range(0, len(hiscore) * 8, 17):
byte_offset = int(i / 8)
bit_offset = int(i % 8)
try:
value = hiscore[byte_offset]
value = value + (hiscore[byte_offset + 1] << 8)
value = value + (hiscore[byte_offset + 2] << 16)
value = value >> bit_offset
hiscores.append(value & 0x1FFFF)
except IndexError:
# We indexed poorly above, so we ran into an odd value
pass
scores = [
(
hiscores[x + 1],
hiscores[x + 2],
hiscores[x + 0],
hiscores[x + 3],
) for x in range(0, len(hiscores), 7)
]
return {'medals': medals, 'scores': scores}
else:
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')
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')
# Add requested scores
for score in scores:
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']))
# Swap with server
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')
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')
# Swap with server
resp = self.exchange('pnm/playerdata', call)
# Verify nodes that cause crashes if they don't exist
self.assert_path(resp, "response/playerdata/b")
self.assert_path(resp, "response/playerdata/hiscore")
self.assert_path(resp, "response/playerdata/town")
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'
]
)
paseli_enabled = self.verify_pcbtracker_alive()
self.verify_message_get()
self.verify_package_list()
self.verify_facility_get()
self.verify_pcbevent_put()
self.verify_game_active()
self.verify_game_get()
# Verify card registration and profile lookup
if cardid is not None:
card = cardid
else:
card = self.random_card()
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)
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')
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)
# 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 cardid is None:
# Verify score handling
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']:
for i in range(4):
if medal[i] != 0:
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 phase in [1, 2]:
if phase == 1:
dummyscores = [
# An okay score on a chart
{
'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,
},
# A bad score on a hard chart
{
'id': 741,
'chart': 3,
'medal': 1,
'score': 45000,
},
# A terrible score on an easy chart
{
'id': 742,
'chart': 1,
'medal': 0,
'score': 1,
},
]
# Random score to add in
songid = random.randint(907, 950)
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,
})
if phase == 2:
dummyscores = [
# A better score on the same chart
{
'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,
},
]
self.verify_playerdata_set(ref_id, dummyscores)
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']]
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']
else:
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}\'')
if newmedal != expected_medal:
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)
else:
print("Skipping score checks for existing card")
# Verify paseli handling
if paseli_enabled:
print("PASELI enabled for this PCBID, executing PASELI checks")
else:
print("PASELI disabled for this PCBID, skipping PASELI checks")
return
sessid, balance = self.verify_eacoin_checkin(card)
if balance == 0:
print("Skipping PASELI consume check because card has 0 balance")
else:
self.verify_eacoin_consume(sessid, balance, random.randint(0, balance))
self.verify_eacoin_checkout(sessid)