829 lines
32 KiB
Python
829 lines
32 KiB
Python
import random
|
|
import time
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from bemani.client.base import BaseClient
|
|
from bemani.common import CardCipher, Time
|
|
from bemani.protocol import Node
|
|
|
|
|
|
class JubeatClanClient(BaseClient):
|
|
NAME = "TEST"
|
|
|
|
def verify_shopinfo_regist(self) -> None:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
shopinfo = Node.void("shopinfo")
|
|
shopinfo.set_attribute("method", "regist")
|
|
call.add_child(shopinfo)
|
|
shop = Node.void("shop")
|
|
shopinfo.add_child(shop)
|
|
shop.add_child(Node.string("name", ""))
|
|
shop.add_child(Node.string("pref", "JP-14"))
|
|
shop.add_child(Node.string("softwareid", ""))
|
|
shop.add_child(Node.string("systemid", self.pcbid))
|
|
shop.add_child(Node.string("hardwareid", "01020304050607080900"))
|
|
shop.add_child(Node.string("locationid", "US-1"))
|
|
shop.add_child(Node.string("monitor", "D26L155 6252 151"))
|
|
testmode = Node.void("testmode")
|
|
shop.add_child(testmode)
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
|
|
# Verify that response is correct
|
|
self.assert_path(resp, "response/shopinfo/data/cabid")
|
|
self.assert_path(resp, "response/shopinfo/data/locationid")
|
|
self.assert_path(resp, "response/shopinfo/data/tax_phase")
|
|
self.assert_path(resp, "response/shopinfo/data/facility/exist")
|
|
self.assert_path(resp, "response/shopinfo/data/info/event_info")
|
|
self.assert_path(resp, "response/shopinfo/data/info/share_music")
|
|
self.assert_path(resp, "response/shopinfo/data/info/genre_def_music")
|
|
self.assert_path(resp, "response/shopinfo/data/info/black_jacket_list")
|
|
self.assert_path(resp, "response/shopinfo/data/info/white_music_list")
|
|
self.assert_path(resp, "response/shopinfo/data/info/white_marker_list")
|
|
self.assert_path(resp, "response/shopinfo/data/info/white_theme_list")
|
|
self.assert_path(resp, "response/shopinfo/data/info/open_music_list")
|
|
self.assert_path(resp, "response/shopinfo/data/info/shareable_music_list")
|
|
self.assert_path(resp, "response/shopinfo/data/info/jbox/point")
|
|
self.assert_path(resp, "response/shopinfo/data/info/jbox/emblem/normal/index")
|
|
self.assert_path(resp, "response/shopinfo/data/info/jbox/emblem/premium/index")
|
|
self.assert_path(resp, "response/shopinfo/data/info/born/status")
|
|
self.assert_path(resp, "response/shopinfo/data/info/born/year")
|
|
self.assert_path(resp, "response/shopinfo/data/info/collection/rating_s")
|
|
self.assert_path(resp, "response/shopinfo/data/info/expert_option/is_available")
|
|
self.assert_path(
|
|
resp, "response/shopinfo/data/info/all_music_matching/is_available"
|
|
)
|
|
self.assert_path(
|
|
resp, "response/shopinfo/data/info/all_music_matching/team/default_flag"
|
|
)
|
|
self.assert_path(
|
|
resp, "response/shopinfo/data/info/all_music_matching/team/redbelk_flag"
|
|
)
|
|
self.assert_path(
|
|
resp, "response/shopinfo/data/info/all_music_matching/team/cyanttle_flag"
|
|
)
|
|
self.assert_path(
|
|
resp, "response/shopinfo/data/info/all_music_matching/team/greenesia_flag"
|
|
)
|
|
self.assert_path(
|
|
resp, "response/shopinfo/data/info/all_music_matching/team/plumpark_flag"
|
|
)
|
|
self.assert_path(resp, "response/shopinfo/data/info/question_list")
|
|
self.assert_path(resp, "response/shopinfo/data/info/drop_list")
|
|
self.assert_path(resp, "response/shopinfo/data/info/daily_bonus_list")
|
|
self.assert_path(resp, "response/shopinfo/data/info/department/pack_list")
|
|
|
|
def verify_demodata_get_info(self) -> None:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
demodata = Node.void("demodata")
|
|
call.add_child(demodata)
|
|
demodata.set_attribute("method", "get_info")
|
|
pcbinfo = Node.void("pcbinfo")
|
|
demodata.add_child(pcbinfo)
|
|
pcbinfo.set_attribute("client_data_version", "0")
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
|
|
# Verify that response is correct
|
|
self.assert_path(resp, "response/demodata/data/info/black_jacket_list")
|
|
|
|
def verify_demodata_get_news(self) -> None:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
demodata = Node.void("demodata")
|
|
call.add_child(demodata)
|
|
demodata.set_attribute("method", "get_news")
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
|
|
# Verify that response is correct
|
|
self.assert_path(resp, "response/demodata/data/officialnews")
|
|
|
|
def verify_demodata_get_jbox_list(self) -> None:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
demodata = Node.void("demodata")
|
|
call.add_child(demodata)
|
|
demodata.set_attribute("method", "get_jbox_list")
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
|
|
# Verify that response is correct
|
|
self.assert_path(resp, "response/demodata/@status")
|
|
|
|
def __verify_profile(self, resp: Node) -> int:
|
|
self.assert_path(resp, "response/gametop/data/info/event_info")
|
|
self.assert_path(resp, "response/gametop/data/info/share_music")
|
|
self.assert_path(resp, "response/gametop/data/info/genre_def_music")
|
|
self.assert_path(resp, "response/gametop/data/info/black_jacket_list")
|
|
self.assert_path(resp, "response/gametop/data/info/white_music_list")
|
|
self.assert_path(resp, "response/gametop/data/info/white_marker_list")
|
|
self.assert_path(resp, "response/gametop/data/info/white_theme_list")
|
|
self.assert_path(resp, "response/gametop/data/info/open_music_list")
|
|
self.assert_path(resp, "response/gametop/data/info/shareable_music_list")
|
|
self.assert_path(resp, "response/gametop/data/info/jbox/point")
|
|
self.assert_path(resp, "response/gametop/data/info/jbox/emblem/normal/index")
|
|
self.assert_path(resp, "response/gametop/data/info/jbox/emblem/premium/index")
|
|
self.assert_path(resp, "response/gametop/data/info/born/status")
|
|
self.assert_path(resp, "response/gametop/data/info/born/year")
|
|
self.assert_path(resp, "response/gametop/data/info/collection/rating_s")
|
|
self.assert_path(resp, "response/gametop/data/info/expert_option/is_available")
|
|
self.assert_path(
|
|
resp, "response/gametop/data/info/all_music_matching/is_available"
|
|
)
|
|
self.assert_path(
|
|
resp, "response/gametop/data/info/all_music_matching/team/default_flag"
|
|
)
|
|
self.assert_path(
|
|
resp, "response/gametop/data/info/all_music_matching/team/redbelk_flag"
|
|
)
|
|
self.assert_path(
|
|
resp, "response/gametop/data/info/all_music_matching/team/cyanttle_flag"
|
|
)
|
|
self.assert_path(
|
|
resp, "response/gametop/data/info/all_music_matching/team/greenesia_flag"
|
|
)
|
|
self.assert_path(
|
|
resp, "response/gametop/data/info/all_music_matching/team/plumpark_flag"
|
|
)
|
|
self.assert_path(resp, "response/gametop/data/info/question_list")
|
|
self.assert_path(resp, "response/gametop/data/info/drop_list")
|
|
self.assert_path(resp, "response/gametop/data/info/daily_bonus_list")
|
|
self.assert_path(resp, "response/gametop/data/info/department/pack_list")
|
|
|
|
for item in [
|
|
"tune_cnt",
|
|
"save_cnt",
|
|
"saved_cnt",
|
|
"fc_cnt",
|
|
"ex_cnt",
|
|
"clear_cnt",
|
|
"match_cnt",
|
|
"beat_cnt",
|
|
"mynews_cnt",
|
|
"bonus_tune_points",
|
|
"is_bonus_tune_played",
|
|
"inherit",
|
|
"mtg_entry_cnt",
|
|
"mtg_hold_cnt",
|
|
"mtg_result",
|
|
]:
|
|
self.assert_path(resp, f"response/gametop/data/player/info/{item}")
|
|
|
|
for item in [
|
|
"music_list",
|
|
"secret_list",
|
|
"theme_list",
|
|
"marker_list",
|
|
"title_list",
|
|
"parts_list",
|
|
"emblem_list",
|
|
"commu_list",
|
|
"new/secret_list",
|
|
"new/theme_list",
|
|
"new/marker_list",
|
|
]:
|
|
self.assert_path(resp, f"response/gametop/data/player/item/{item}")
|
|
|
|
for item in [
|
|
"play_time",
|
|
"shopname",
|
|
"areaname",
|
|
"music_id",
|
|
"seq_id",
|
|
"sort",
|
|
"category",
|
|
"expert_option",
|
|
]:
|
|
self.assert_path(resp, f"response/gametop/data/player/last/{item}")
|
|
|
|
for item in [
|
|
"marker",
|
|
"theme",
|
|
"title",
|
|
"parts",
|
|
"rank_sort",
|
|
"combo_disp",
|
|
"emblem",
|
|
"matching",
|
|
"hard",
|
|
"hazard",
|
|
]:
|
|
self.assert_path(resp, f"response/gametop/data/player/last/settings/{item}")
|
|
|
|
# Misc stuff
|
|
self.assert_path(resp, "response/gametop/data/player/session_id")
|
|
self.assert_path(resp, "response/gametop/data/player/event_flag")
|
|
|
|
# Profile settings
|
|
self.assert_path(resp, "response/gametop/data/player/name")
|
|
self.assert_path(resp, "response/gametop/data/player/jid")
|
|
|
|
# Required nodes for events and stuff
|
|
self.assert_path(resp, "response/gametop/data/player/history")
|
|
self.assert_path(resp, "response/gametop/data/player/lab_edit_seq")
|
|
self.assert_path(resp, "response/gametop/data/player/event_info")
|
|
self.assert_path(resp, "response/gametop/data/player/navi/flag")
|
|
self.assert_path(
|
|
resp, "response/gametop/data/player/fc_challenge/today/music_id"
|
|
)
|
|
self.assert_path(resp, "response/gametop/data/player/fc_challenge/today/state")
|
|
self.assert_path(
|
|
resp, "response/gametop/data/player/fc_challenge/whim/music_id"
|
|
)
|
|
self.assert_path(resp, "response/gametop/data/player/fc_challenge/whim/state")
|
|
self.assert_path(resp, "response/gametop/data/player/official_news/news_list")
|
|
self.assert_path(resp, "response/gametop/data/player/rivallist")
|
|
self.assert_path(
|
|
resp, "response/gametop/data/player/free_first_play/is_available"
|
|
)
|
|
self.assert_path(resp, "response/gametop/data/player/jbox/point")
|
|
self.assert_path(resp, "response/gametop/data/player/jbox/emblem/normal/index")
|
|
self.assert_path(resp, "response/gametop/data/player/jbox/emblem/premium/index")
|
|
self.assert_path(resp, "response/gametop/data/player/new_music")
|
|
self.assert_path(resp, "response/gametop/data/player/gift_list")
|
|
self.assert_path(resp, "response/gametop/data/player/born/status")
|
|
self.assert_path(resp, "response/gametop/data/player/question_list")
|
|
self.assert_path(resp, "response/gametop/data/player/jubility/@param")
|
|
self.assert_path(
|
|
resp, "response/gametop/data/player/jubility/target_music_list"
|
|
)
|
|
self.assert_path(resp, "response/gametop/data/player/team/@id")
|
|
self.assert_path(resp, "response/gametop/data/player/team/section")
|
|
self.assert_path(resp, "response/gametop/data/player/team/street")
|
|
self.assert_path(resp, "response/gametop/data/player/team/house_number_1")
|
|
self.assert_path(resp, "response/gametop/data/player/team/house_number_2")
|
|
self.assert_path(resp, "response/gametop/data/player/team/move/@house_number_1")
|
|
self.assert_path(resp, "response/gametop/data/player/team/move/@house_number_2")
|
|
self.assert_path(resp, "response/gametop/data/player/team/move/@id")
|
|
self.assert_path(resp, "response/gametop/data/player/team/move/@section")
|
|
self.assert_path(resp, "response/gametop/data/player/team/move/@street")
|
|
self.assert_path(resp, "response/gametop/data/player/union_battle/@id")
|
|
self.assert_path(resp, "response/gametop/data/player/union_battle/power")
|
|
self.assert_path(resp, "response/gametop/data/player/server")
|
|
self.assert_path(resp, "response/gametop/data/player/eamuse_gift_list")
|
|
self.assert_path(resp, "response/gametop/data/player/clan_course_list")
|
|
self.assert_path(resp, "response/gametop/data/player/category_list")
|
|
self.assert_path(resp, "response/gametop/data/player/drop_list/drop/@id")
|
|
self.assert_path(resp, "response/gametop/data/player/drop_list/drop/exp")
|
|
self.assert_path(resp, "response/gametop/data/player/drop_list/drop/flag")
|
|
self.assert_path(
|
|
resp, "response/gametop/data/player/drop_list/drop/item_list/item/@id"
|
|
)
|
|
self.assert_path(
|
|
resp, "response/gametop/data/player/drop_list/drop/item_list/item/num"
|
|
)
|
|
self.assert_path(
|
|
resp, "response/gametop/data/player/fill_in_category/no_gray_flag_list"
|
|
)
|
|
self.assert_path(
|
|
resp, "response/gametop/data/player/fill_in_category/all_yellow_flag_list"
|
|
)
|
|
self.assert_path(
|
|
resp, "response/gametop/data/player/fill_in_category/full_combo_flag_list"
|
|
)
|
|
self.assert_path(
|
|
resp, "response/gametop/data/player/fill_in_category/excellent_flag_list"
|
|
)
|
|
self.assert_path(resp, "response/gametop/data/player/daily_bonus_list")
|
|
self.assert_path(resp, "response/gametop/data/player/ticket_list")
|
|
|
|
# Return the jid
|
|
return resp.child_value("gametop/data/player/jid")
|
|
|
|
def verify_gameend_regist(
|
|
self,
|
|
ref_id: str,
|
|
jid: int,
|
|
scores: List[Dict[str, Any]],
|
|
) -> None:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
gameend = Node.void("gameend")
|
|
call.add_child(gameend)
|
|
gameend.set_attribute("method", "regist")
|
|
gameend.add_child(Node.s32("retry", 0))
|
|
pcbinfo = Node.void("pcbinfo")
|
|
gameend.add_child(pcbinfo)
|
|
pcbinfo.set_attribute("client_data_version", "0")
|
|
data = Node.void("data")
|
|
gameend.add_child(data)
|
|
player = Node.void("player")
|
|
data.add_child(player)
|
|
player.add_child(Node.string("refid", ref_id))
|
|
player.add_child(Node.s32("jid", jid))
|
|
player.add_child(Node.string("name", self.NAME))
|
|
result = Node.void("result")
|
|
data.add_child(result)
|
|
result.set_attribute("count", str(len(scores)))
|
|
|
|
# Send scores
|
|
scoreid = 0
|
|
for score in scores:
|
|
# Always played
|
|
bits = 0x1
|
|
if score["clear"]:
|
|
bits |= 0x2
|
|
if score["fc"]:
|
|
bits |= 0x4
|
|
if score["ex"]:
|
|
bits |= 0x8
|
|
|
|
# Intentionally starting at 1 because that's what the game does
|
|
scoreid = scoreid + 1
|
|
tune = Node.void("tune")
|
|
result.add_child(tune)
|
|
tune.set_attribute("id", str(scoreid))
|
|
tune.add_child(Node.s32("music", score["id"]))
|
|
tune.add_child(Node.s64("timestamp", Time.now() * 1000))
|
|
player_1 = Node.void("player")
|
|
tune.add_child(player_1)
|
|
player_1.set_attribute("rank", "1")
|
|
scorenode = Node.s32("score", score["score"])
|
|
player_1.add_child(scorenode)
|
|
scorenode.set_attribute("seq", str(score["chart"]))
|
|
scorenode.set_attribute("clear", str(bits))
|
|
scorenode.set_attribute("combo", "69")
|
|
player_1.add_child(
|
|
Node.u8_array(
|
|
"mbar",
|
|
[
|
|
239,
|
|
175,
|
|
170,
|
|
170,
|
|
190,
|
|
234,
|
|
187,
|
|
158,
|
|
153,
|
|
230,
|
|
170,
|
|
90,
|
|
102,
|
|
170,
|
|
85,
|
|
150,
|
|
150,
|
|
102,
|
|
85,
|
|
234,
|
|
171,
|
|
169,
|
|
157,
|
|
150,
|
|
170,
|
|
101,
|
|
230,
|
|
90,
|
|
214,
|
|
255,
|
|
],
|
|
)
|
|
)
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
self.assert_path(resp, "response/gameend/data/player/session_id")
|
|
|
|
def verify_gameend_final(
|
|
self,
|
|
ref_id: str,
|
|
jid: int,
|
|
) -> None:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
gameend = Node.void("gameend")
|
|
call.add_child(gameend)
|
|
gameend.set_attribute("method", "final")
|
|
gameend.add_child(Node.s32("retry", 0))
|
|
pcbinfo = Node.void("pcbinfo")
|
|
gameend.add_child(pcbinfo)
|
|
pcbinfo.set_attribute("client_data_version", "0")
|
|
data = Node.void("data")
|
|
gameend.add_child(data)
|
|
player = Node.void("player")
|
|
data.add_child(player)
|
|
player.add_child(Node.string("refid", ref_id))
|
|
player.add_child(Node.s32("jid", jid))
|
|
jbox = Node.void("jbox")
|
|
player.add_child(jbox)
|
|
jbox.add_child(Node.s32("point", 0))
|
|
emblem = Node.void("emblem")
|
|
jbox.add_child(emblem)
|
|
emblem.add_child(Node.u8("type", 0))
|
|
emblem.add_child(Node.s16("index", 0))
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
self.assert_path(resp, "response/gameend/@status")
|
|
|
|
def verify_gametop_regist(self, card_id: str, ref_id: str) -> int:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
gametop = Node.void("gametop")
|
|
call.add_child(gametop)
|
|
gametop.set_attribute("method", "regist")
|
|
data = Node.void("data")
|
|
gametop.add_child(data)
|
|
player = Node.void("player")
|
|
data.add_child(player)
|
|
player.add_child(Node.string("refid", ref_id))
|
|
player.add_child(Node.string("datid", ref_id))
|
|
player.add_child(Node.string("uid", card_id))
|
|
player.add_child(Node.bool("inherit", True))
|
|
player.add_child(Node.string("name", self.NAME))
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
|
|
# Verify nodes that cause crashes if they don't exist
|
|
return self.__verify_profile(resp)
|
|
|
|
def verify_gametop_get_pdata(self, card_id: str, ref_id: str) -> int:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
gametop = Node.void("gametop")
|
|
call.add_child(gametop)
|
|
gametop.set_attribute("method", "get_pdata")
|
|
retry = Node.s32("retry", 0)
|
|
gametop.add_child(retry)
|
|
data = Node.void("data")
|
|
gametop.add_child(data)
|
|
player = Node.void("player")
|
|
data.add_child(player)
|
|
player.add_child(Node.string("refid", ref_id))
|
|
player.add_child(Node.string("datid", ref_id))
|
|
player.add_child(Node.string("uid", card_id))
|
|
player.add_child(Node.string("card_no", CardCipher.encode(card_id)))
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
|
|
# Verify nodes that cause crashes if they don't exist
|
|
return self.__verify_profile(resp)
|
|
|
|
def verify_gametop_get_mdata(self, jid: int) -> Dict[str, List[Dict[str, Any]]]:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
gametop = Node.void("gametop")
|
|
call.add_child(gametop)
|
|
gametop.set_attribute("method", "get_mdata")
|
|
retry = Node.s32("retry", 0)
|
|
gametop.add_child(retry)
|
|
data = Node.void("data")
|
|
gametop.add_child(data)
|
|
player = Node.void("player")
|
|
data.add_child(player)
|
|
player.add_child(Node.s32("jid", jid))
|
|
# Technically the game sends this same packet 3 times, one with
|
|
# each value 1, 2, 3 here. Unclear why, but we won't emulate it.
|
|
player.add_child(Node.s8("mdata_ver", 1))
|
|
player.add_child(Node.bool("rival", False))
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
|
|
# Parse out scores
|
|
self.assert_path(resp, "response/gametop/data/player/mdata_list")
|
|
|
|
ret = {}
|
|
for musicdata in resp.child("gametop/data/player/mdata_list").children:
|
|
if musicdata.name != "musicdata":
|
|
raise Exception("Unexpected node in playdata!")
|
|
|
|
music_id = musicdata.attribute("music_id")
|
|
scores_by_chart: List[Dict[str, int]] = [{}, {}, {}]
|
|
|
|
def extract_cnts(name: str, val: List[int]) -> None:
|
|
scores_by_chart[0][name] = val[0]
|
|
scores_by_chart[1][name] = val[1]
|
|
scores_by_chart[2][name] = val[2]
|
|
|
|
extract_cnts("plays", musicdata.child_value("play_cnt"))
|
|
extract_cnts("clears", musicdata.child_value("clear_cnt"))
|
|
extract_cnts("full_combos", musicdata.child_value("fc_cnt"))
|
|
extract_cnts("excellents", musicdata.child_value("ex_cnt"))
|
|
extract_cnts("score", musicdata.child_value("score"))
|
|
extract_cnts("medal", musicdata.child_value("clear"))
|
|
ret[music_id] = scores_by_chart
|
|
|
|
return ret
|
|
|
|
def verify_gametop_get_meeting(self, jid: int) -> None:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
gametop = Node.void("gametop")
|
|
call.add_child(gametop)
|
|
gametop.set_attribute("method", "get_meeting")
|
|
gametop.add_child(Node.s32("retry", 0))
|
|
data = Node.void("data")
|
|
gametop.add_child(data)
|
|
player = Node.void("player")
|
|
data.add_child(player)
|
|
player.add_child(Node.s32("jid", jid))
|
|
pcbinfo = Node.void("pcbinfo")
|
|
gametop.add_child(pcbinfo)
|
|
pcbinfo.set_attribute("client_data_version", "0")
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
|
|
# Verify expected nodes
|
|
self.assert_path(resp, "response/gametop/data/meeting/single/@count")
|
|
self.assert_path(resp, "response/gametop/data/meeting/tag/@count")
|
|
self.assert_path(resp, "response/gametop/data/reward/total")
|
|
self.assert_path(resp, "response/gametop/data/reward/point")
|
|
|
|
def verify_recommend_get_recommend(self, jid: int) -> None:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
recommend = Node.void("recommend")
|
|
call.add_child(recommend)
|
|
recommend.set_attribute("method", "get_recommend")
|
|
recommend.add_child(Node.s32("retry", 0))
|
|
player = Node.void("player")
|
|
recommend.add_child(player)
|
|
player.add_child(Node.s32("jid", jid))
|
|
player.add_child(Node.void("music_list"))
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
|
|
# Verify expected nodes
|
|
self.assert_path(resp, "response/recommend/data/player/music_list")
|
|
|
|
def verify_demodata_get_hitchart(self) -> None:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
gametop = Node.void("demodata")
|
|
call.add_child(gametop)
|
|
gametop.set_attribute("method", "get_hitchart")
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
|
|
# Verify expected nodes
|
|
self.assert_path(resp, "response/demodata/data/update")
|
|
self.assert_path(resp, "response/demodata/data/hitchart_lic")
|
|
self.assert_path(resp, "response/demodata/data/hitchart_org")
|
|
|
|
def verify_jbox_get_list(self, jid: int) -> None:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
jbox = Node.void("jbox")
|
|
call.add_child(jbox)
|
|
jbox.set_attribute("method", "get_list")
|
|
data = Node.void("data")
|
|
jbox.add_child(data)
|
|
player = Node.void("player")
|
|
data.add_child(player)
|
|
player.add_child(Node.s32("jid", jid))
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
|
|
# Verify that response is correct
|
|
self.assert_path(resp, "response/jbox/selection_list")
|
|
|
|
def verify_jbox_get_agreement(self, jid: int) -> None:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
jbox = Node.void("jbox")
|
|
call.add_child(jbox)
|
|
jbox.set_attribute("method", "get_agreement")
|
|
data = Node.void("data")
|
|
jbox.add_child(data)
|
|
player = Node.void("player")
|
|
data.add_child(player)
|
|
player.add_child(Node.s32("jid", jid))
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
|
|
# Verify that response is correct
|
|
self.assert_path(resp, "response/jbox/is_agreement")
|
|
|
|
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(ecflag=3)
|
|
self.verify_package_list()
|
|
self.verify_message_get()
|
|
self.verify_facility_get(encoding="Shift-JIS")
|
|
self.verify_pcbevent_put()
|
|
self.verify_shopinfo_regist()
|
|
self.verify_demodata_get_info()
|
|
self.verify_demodata_get_news()
|
|
self.verify_demodata_get_jbox_list()
|
|
self.verify_demodata_get_hitchart()
|
|
|
|
# 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_gametop_regist(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
|
|
jid = self.verify_gametop_get_pdata(card, ref_id)
|
|
self.verify_recommend_get_recommend(jid)
|
|
scores = self.verify_gametop_get_mdata(jid)
|
|
self.verify_gametop_get_meeting(jid)
|
|
if scores is None:
|
|
raise Exception("Expected to get scores back, didn't get anything!")
|
|
if len(scores) > 0:
|
|
raise Exception("Got nonzero score count on a new card!")
|
|
|
|
# Verify end of game behavior
|
|
self.verify_jbox_get_list(jid)
|
|
self.verify_jbox_get_agreement(jid)
|
|
self.verify_gameend_final(ref_id, jid)
|
|
|
|
for phase in [1, 2]:
|
|
if phase == 1:
|
|
dummyscores = [
|
|
# An okay score on a chart
|
|
{
|
|
"id": 40000059,
|
|
"chart": 2,
|
|
"clear": True,
|
|
"fc": False,
|
|
"ex": False,
|
|
"score": 800000,
|
|
"expected_medal": 0x3,
|
|
},
|
|
# A good score on an easier chart of the same song
|
|
{
|
|
"id": 40000059,
|
|
"chart": 1,
|
|
"clear": True,
|
|
"fc": True,
|
|
"ex": False,
|
|
"score": 990000,
|
|
"expected_medal": 0x5,
|
|
},
|
|
# A perfect score on an easiest chart of the same song
|
|
{
|
|
"id": 40000059,
|
|
"chart": 0,
|
|
"clear": True,
|
|
"fc": True,
|
|
"ex": True,
|
|
"score": 1000000,
|
|
"expected_medal": 0x9,
|
|
},
|
|
# A bad score on a hard chart
|
|
{
|
|
"id": 30000024,
|
|
"chart": 2,
|
|
"clear": False,
|
|
"fc": False,
|
|
"ex": False,
|
|
"score": 400000,
|
|
"expected_medal": 0x1,
|
|
},
|
|
# A terrible score on an easy chart
|
|
{
|
|
"id": 50000045,
|
|
"chart": 0,
|
|
"clear": False,
|
|
"fc": False,
|
|
"ex": False,
|
|
"score": 100000,
|
|
"expected_medal": 0x1,
|
|
},
|
|
]
|
|
if phase == 2:
|
|
dummyscores = [
|
|
# A better score on the same chart
|
|
{
|
|
"id": 50000045,
|
|
"chart": 0,
|
|
"clear": True,
|
|
"fc": False,
|
|
"ex": False,
|
|
"score": 850000,
|
|
"expected_medal": 0x3,
|
|
},
|
|
# A worse score on another same chart
|
|
{
|
|
"id": 40000059,
|
|
"chart": 1,
|
|
"clear": True,
|
|
"fc": False,
|
|
"ex": False,
|
|
"score": 925000,
|
|
"expected_score": 990000,
|
|
"expected_medal": 0x7,
|
|
},
|
|
]
|
|
|
|
self.verify_gameend_regist(ref_id, jid, dummyscores)
|
|
jid = self.verify_gametop_get_pdata(card, ref_id)
|
|
scores = self.verify_gametop_get_mdata(jid)
|
|
|
|
for score in dummyscores:
|
|
newscore = scores[str(score["id"])][score["chart"]]
|
|
|
|
if "expected_score" in score:
|
|
expected_score = score["expected_score"]
|
|
else:
|
|
expected_score = score["score"]
|
|
|
|
if newscore["score"] != expected_score:
|
|
raise Exception(
|
|
f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{newscore["score"]}\''
|
|
)
|
|
|
|
if newscore["medal"] != score["expected_medal"]:
|
|
raise Exception(
|
|
f'Expected a medal of \'{score["expected_medal"]}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got medal \'{newscore["medal"]}\''
|
|
)
|
|
|
|
# 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)
|