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

361 lines
14 KiB
Python
Raw Normal View History

import random
import time
from typing import Any, Dict, List, Optional
from bemani.client.base import BaseClient
from bemani.protocol import Node
class PopnMusicLapistoriaClient(BaseClient):
NAME = ''
def verify_pcb22_boot(self) -> None:
call = self.call_node()
# Construct node
pcb22 = Node.void('pcb22')
call.add_child(pcb22)
pcb22.set_attribute('method', 'boot')
# Swap with server
resp = self.exchange('', call)
# Verify that response is correct
self.assert_path(resp, "response/pcb22/@status")
def verify_info22_common(self) -> None:
call = self.call_node()
# Construct node
info22 = Node.void('info22')
call.add_child(info22)
info22.set_attribute('loc_id', 'JP-1')
info22.set_attribute('method', 'common')
# Swap with server
resp = self.exchange('', call)
# Verify that response is correct
self.assert_path(resp, "response/info22")
for name in [
'phase',
'story',
]:
node = resp.child('info22').child(name)
if node is None:
raise Exception('Missing node \'{}\' in response!'.format(name))
if node.data_type != 'void':
raise Exception('Node \'{}\' has wrong data type!'.format(name))
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')
call.add_child(player22)
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))
# Swap with server
resp = self.exchange('', call)
if msg_type == 'new':
# Verify that response is correct
self.assert_path(resp, "response/player22/@status")
status = int(resp.child('player22').attribute('status'))
if status != 109:
raise Exception('Reference ID \'{}\' returned invalid status \'{}\''.format(ref_id, status))
# No score data
return None
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")
self.assert_path(resp, "response/player22/account/my_best")
self.assert_path(resp, "response/player22/account/latest_music")
self.assert_path(resp, "response/player22/netvs")
self.assert_path(resp, "response/player22/config")
self.assert_path(resp, "response/player22/option")
self.assert_path(resp, "response/player22/info")
self.assert_path(resp, "response/player22/custom_cate")
self.assert_path(resp, "response/player22/customize")
name = resp.child('player22').child('account').child('name').value
if name != self.NAME:
raise Exception('Invalid name \'{}\' returned for Ref ID \'{}\''.format(name, ref_id))
# Extract and return score data
medals: Dict[int, List[int]] = {}
scores: Dict[int, List[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')
if songid not in medals:
medals[songid] = [0, 0, 0, 0]
medals[songid][chart] = medal
if songid not in scores:
scores[songid] = [0, 0, 0, 0]
scores[songid][chart] = points
return {'medals': medals, 'scores': scores}
else:
raise Exception('Unrecognized message type \'{}\''.format(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')
call.add_child(player22)
player22.set_attribute('method', 'write')
player22.add_child(Node.string('ref_id', value=ref_id))
# Add required children
config = Node.void('config')
player22.add_child(config)
config.add_child(Node.s16('chara', value=1543))
# Add requested scores
for score in scores:
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']))
# Swap with server
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')
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))
# Swap with server
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')
call.add_child(player22)
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))
# Swap with server
resp = self.exchange('', call)
# Verify nodes that cause crashes if they don't exist
self.assert_path(resp, "response/player22/account")
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_pcb22_boot()
self.verify_info22_common()
# Verify card registration and profile lookup
if cardid is not None:
card = cardid
else:
card = self.random_card()
print("Generated random card ID {} for use.".format(card))
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('Invalid refid \'{}\' returned when registering card'.format(ref_id))
if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled):
raise Exception('Invalid refid \'{}\' returned when querying card'.format(ref_id))
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)
# 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('Invalid refid \'{}\' returned when querying card'.format(ref_id))
if cardid is None:
# Verify score handling
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']:
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': 5,
'score': 76543,
},
# A good score on an easier chart of the same song
{
'id': 987,
'chart': 0,
'medal': 6,
'score': 99999,
},
# A bad score on a hard chart
{
'id': 741,
'chart': 3,
'medal': 2,
'score': 45000,
},
# A terrible score on an easy chart
{
'id': 742,
'chart': 1,
'medal': 2,
'score': 1,
},
]
# Random score to add in
songid = random.randint(907, 950)
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,
})
if phase == 2:
dummyscores = [
# A better score on the same chart
{
'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,
},
]
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')
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('Expected a score of \'{}\' for song \'{}\' chart \'{}\' but got score \'{}\''.format(
expected_score, score['id'], score['chart'], newscore,
))
if newmedal != expected_medal:
raise Exception('Expected a medal of \'{}\' for song \'{}\' chart \'{}\' but got medal \'{}\''.format(
expected_medal, score['id'], score['chart'], 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)