766 lines
30 KiB
Python
766 lines
30 KiB
Python
import random
|
|
import time
|
|
from typing import Any, Dict, Optional, Tuple
|
|
|
|
from bemani.client.base import BaseClient
|
|
from bemani.protocol import Node
|
|
|
|
|
|
class IIDXTricoroClient(BaseClient):
|
|
NAME = "TEST"
|
|
|
|
def verify_shop_getname(self, lid: str) -> str:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
IIDX21shop = Node.void("shop")
|
|
call.add_child(IIDX21shop)
|
|
IIDX21shop.set_attribute("method", "getname")
|
|
IIDX21shop.set_attribute("lid", lid)
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
|
|
# Verify that response is correct
|
|
self.assert_path(resp, "response/shop/@opname")
|
|
self.assert_path(resp, "response/shop/@pid")
|
|
self.assert_path(resp, "response/shop/@cls_opt")
|
|
|
|
return resp.child("shop").attribute("opname")
|
|
|
|
def verify_shop_savename(self, lid: str, name: str) -> None:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
IIDX21shop = Node.void("shop")
|
|
IIDX21shop.set_attribute("lid", lid)
|
|
IIDX21shop.set_attribute("pid", "51")
|
|
IIDX21shop.set_attribute("method", "savename")
|
|
IIDX21shop.set_attribute("cls_opt", "0")
|
|
IIDX21shop.set_attribute("ccode", "US")
|
|
IIDX21shop.set_attribute("opname", name)
|
|
IIDX21shop.set_attribute("rcode", ".")
|
|
|
|
call.add_child(IIDX21shop)
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
|
|
# Verify that response is correct
|
|
self.assert_path(resp, "response/shop")
|
|
|
|
def verify_pc_common(self) -> None:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
IIDX21pc = Node.void("pc")
|
|
call.add_child(IIDX21pc)
|
|
IIDX21pc.set_attribute("method", "common")
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
|
|
# Verify that response is correct
|
|
self.assert_path(resp, "response/pc/ir/@beat")
|
|
self.assert_path(resp, "response/pc/limit/@phase")
|
|
self.assert_path(resp, "response/pc/boss/@phase")
|
|
self.assert_path(resp, "response/pc/red/@phase")
|
|
self.assert_path(resp, "response/pc/yellow/@phase")
|
|
self.assert_path(resp, "response/pc/medal/@phase")
|
|
self.assert_path(resp, "response/pc/tricolettepark/@open")
|
|
self.assert_path(resp, "response/pc/cafe/@open")
|
|
|
|
def verify_music_crate(self) -> None:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
IIDX21pc = Node.void("music")
|
|
call.add_child(IIDX21pc)
|
|
IIDX21pc.set_attribute("method", "crate")
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
|
|
self.assert_path(resp, "response/music")
|
|
for child in resp.child("music").children:
|
|
if child.name != "c":
|
|
raise Exception(f"Invalid node {child} in clear rate response!")
|
|
if len(child.value) != 12:
|
|
raise Exception(f"Invalid node data {child} in clear rate response!")
|
|
for v in child.value:
|
|
if v < 0 or v > 101:
|
|
raise Exception(f"Invalid clear percent {child} in clear rate response!")
|
|
|
|
def verify_shop_getconvention(self, lid: str) -> None:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
IIDX21pc = Node.void("shop")
|
|
call.add_child(IIDX21pc)
|
|
IIDX21pc.set_attribute("method", "getconvention")
|
|
IIDX21pc.set_attribute("lid", lid)
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
|
|
# Verify that response is correct
|
|
self.assert_path(resp, "response/shop/valid")
|
|
self.assert_path(resp, "response/shop/@music_0")
|
|
self.assert_path(resp, "response/shop/@music_1")
|
|
self.assert_path(resp, "response/shop/@music_2")
|
|
self.assert_path(resp, "response/shop/@music_3")
|
|
|
|
def verify_pc_visit(self, extid: int, lid: str) -> None:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
IIDX21pc = Node.void("pc")
|
|
call.add_child(IIDX21pc)
|
|
IIDX21pc.set_attribute("iidxid", str(extid))
|
|
IIDX21pc.set_attribute("lid", lid)
|
|
IIDX21pc.set_attribute("method", "visit")
|
|
IIDX21pc.set_attribute("pid", "51")
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
|
|
# Verify that response is correct
|
|
self.assert_path(resp, "response/pc/@aflg")
|
|
self.assert_path(resp, "response/pc/@anum")
|
|
self.assert_path(resp, "response/pc/@pflg")
|
|
self.assert_path(resp, "response/pc/@pnum")
|
|
self.assert_path(resp, "response/pc/@sflg")
|
|
self.assert_path(resp, "response/pc/@snum")
|
|
|
|
def verify_ranking_getranker(self, lid: str) -> None:
|
|
for clid in [0, 1, 2, 3, 4, 5, 6]:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
IIDX21pc = Node.void("ranking")
|
|
call.add_child(IIDX21pc)
|
|
IIDX21pc.set_attribute("method", "getranker")
|
|
IIDX21pc.set_attribute("lid", lid)
|
|
IIDX21pc.set_attribute("clid", str(clid))
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
|
|
# Verify that response is correct
|
|
self.assert_path(resp, "response/ranking")
|
|
|
|
def verify_shop_sentinfo(self, lid: str) -> None:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
IIDX21pc = Node.void("shop")
|
|
call.add_child(IIDX21pc)
|
|
IIDX21pc.set_attribute("method", "sentinfo")
|
|
IIDX21pc.set_attribute("lid", lid)
|
|
IIDX21pc.set_attribute("bflg", "1")
|
|
IIDX21pc.set_attribute("bnum", "2")
|
|
IIDX21pc.set_attribute("ioid", "0")
|
|
IIDX21pc.set_attribute("tax_phase", "0")
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
|
|
# Verify that response is correct
|
|
self.assert_path(resp, "response/shop")
|
|
|
|
def verify_pc_get(self, ref_id: str, card_id: str, lid: str) -> Dict[str, Any]:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
IIDX21pc = Node.void("pc")
|
|
call.add_child(IIDX21pc)
|
|
IIDX21pc.set_attribute("rid", ref_id)
|
|
IIDX21pc.set_attribute("did", ref_id)
|
|
IIDX21pc.set_attribute("pid", "51")
|
|
IIDX21pc.set_attribute("lid", lid)
|
|
IIDX21pc.set_attribute("cid", card_id)
|
|
IIDX21pc.set_attribute("method", "get")
|
|
IIDX21pc.set_attribute("ctype", "1")
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
|
|
# Verify that the response is correct
|
|
self.assert_path(resp, "response/pc/pcdata/@name")
|
|
self.assert_path(resp, "response/pc/pcdata/@pid")
|
|
self.assert_path(resp, "response/pc/pcdata/@id")
|
|
self.assert_path(resp, "response/pc/pcdata/@idstr")
|
|
self.assert_path(resp, "response/pc/packinfo")
|
|
self.assert_path(resp, "response/pc/commonboss/@deller")
|
|
self.assert_path(resp, "response/pc/commonboss/@orb")
|
|
self.assert_path(resp, "response/pc/commonboss/@baron")
|
|
self.assert_path(resp, "response/pc/secret/flg1")
|
|
self.assert_path(resp, "response/pc/secret/flg2")
|
|
self.assert_path(resp, "response/pc/secret/flg3")
|
|
self.assert_path(resp, "response/pc/achievements/trophy")
|
|
self.assert_path(resp, "response/pc/skin")
|
|
self.assert_path(resp, "response/pc/grade")
|
|
self.assert_path(resp, "response/pc/rlist")
|
|
self.assert_path(resp, "response/pc/step")
|
|
|
|
name = resp.child("pc/pcdata").attribute("name")
|
|
if name != self.NAME:
|
|
raise Exception(f"Invalid name '{name}' returned for Ref ID '{ref_id}'")
|
|
|
|
return {
|
|
"extid": int(resp.child("pc/pcdata").attribute("id")),
|
|
"sp_dan": int(resp.child("pc/grade").attribute("sgid")),
|
|
"dp_dan": int(resp.child("pc/grade").attribute("dgid")),
|
|
"deller": int(resp.child("pc/commonboss").attribute("deller")),
|
|
}
|
|
|
|
def verify_music_getrank(self, extid: int) -> Dict[int, Dict[int, Dict[str, int]]]:
|
|
scores: Dict[int, Dict[int, Dict[str, int]]] = {}
|
|
for cltype in [0, 1]: # singles, doubles
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
IIDX21music = Node.void("music")
|
|
call.add_child(IIDX21music)
|
|
IIDX21music.set_attribute("method", "getrank")
|
|
IIDX21music.set_attribute("iidxid", str(extid))
|
|
IIDX21music.set_attribute("cltype", str(cltype))
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
|
|
self.assert_path(resp, "response/music/style")
|
|
if int(resp.child("music/style").attribute("type")) != cltype:
|
|
raise Exception("Returned wrong clear type for IIDX21music.getrank!")
|
|
|
|
for child in resp.child("music").children:
|
|
if child.name == "m":
|
|
if child.value[0] != -1:
|
|
raise Exception("Got non-self score back when requesting only our scores!")
|
|
|
|
music_id = child.value[1]
|
|
normal_clear_status = child.value[2]
|
|
hyper_clear_status = child.value[3]
|
|
another_clear_status = child.value[4]
|
|
normal_ex_score = child.value[5]
|
|
hyper_ex_score = child.value[6]
|
|
another_ex_score = child.value[7]
|
|
normal_miss_count = child.value[8]
|
|
hyper_miss_count = child.value[9]
|
|
another_miss_count = child.value[10]
|
|
|
|
if cltype == 0:
|
|
normal = 0
|
|
hyper = 1
|
|
another = 2
|
|
else:
|
|
normal = 3
|
|
hyper = 4
|
|
another = 5
|
|
|
|
if music_id not in scores:
|
|
scores[music_id] = {}
|
|
|
|
scores[music_id][normal] = {
|
|
"clear_status": normal_clear_status,
|
|
"ex_score": normal_ex_score,
|
|
"miss_count": normal_miss_count,
|
|
}
|
|
scores[music_id][hyper] = {
|
|
"clear_status": hyper_clear_status,
|
|
"ex_score": hyper_ex_score,
|
|
"miss_count": hyper_miss_count,
|
|
}
|
|
scores[music_id][another] = {
|
|
"clear_status": another_clear_status,
|
|
"ex_score": another_ex_score,
|
|
"miss_count": another_miss_count,
|
|
}
|
|
elif child.name == "b":
|
|
music_id = child.value[0]
|
|
clear_status = child.value[1]
|
|
|
|
scores[music_id][6] = {
|
|
"clear_status": clear_status,
|
|
"ex_score": -1,
|
|
"miss_count": -1,
|
|
}
|
|
|
|
return scores
|
|
|
|
def verify_pc_save(self, extid: int, card: str, lid: str) -> None:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
IIDX21pc = Node.void("pc")
|
|
call.add_child(IIDX21pc)
|
|
IIDX21pc.set_attribute("achi", "449")
|
|
IIDX21pc.set_attribute("opt", "8208")
|
|
IIDX21pc.set_attribute("gpos", "0")
|
|
IIDX21pc.set_attribute("gno", "8")
|
|
IIDX21pc.set_attribute("timing", "0")
|
|
IIDX21pc.set_attribute("help", "0")
|
|
IIDX21pc.set_attribute("sdhd", "0")
|
|
IIDX21pc.set_attribute("sdtype", "0")
|
|
IIDX21pc.set_attribute("notes", "31.484070")
|
|
IIDX21pc.set_attribute("pase", "0")
|
|
IIDX21pc.set_attribute("judge", "0")
|
|
IIDX21pc.set_attribute("opstyle", "1")
|
|
IIDX21pc.set_attribute("hispeed", "5.771802")
|
|
IIDX21pc.set_attribute("mode", "6")
|
|
IIDX21pc.set_attribute("pmode", "0")
|
|
IIDX21pc.set_attribute("lift", "60")
|
|
IIDX21pc.set_attribute("judgeAdj", "0")
|
|
|
|
IIDX21pc.set_attribute("method", "save")
|
|
IIDX21pc.set_attribute("iidxid", str(extid))
|
|
IIDX21pc.set_attribute("lid", lid)
|
|
IIDX21pc.set_attribute("cid", card)
|
|
IIDX21pc.set_attribute("cltype", "0")
|
|
IIDX21pc.set_attribute("ctype", "1")
|
|
|
|
pyramid = Node.void("pyramid")
|
|
IIDX21pc.add_child(pyramid)
|
|
pyramid.set_attribute("point", "290")
|
|
destiny_catharsis = Node.void("destiny_catharsis")
|
|
IIDX21pc.add_child(destiny_catharsis)
|
|
destiny_catharsis.set_attribute("point", "290")
|
|
bemani_summer_collabo = Node.void("bemani_summer_collabo")
|
|
IIDX21pc.add_child(bemani_summer_collabo)
|
|
bemani_summer_collabo.set_attribute("point", "290")
|
|
deller = Node.void("deller")
|
|
IIDX21pc.add_child(deller)
|
|
deller.set_attribute("deller", "150")
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
self.assert_path(resp, "response/pc")
|
|
|
|
def verify_music_reg(self, extid: int, lid: str, score: Dict[str, Any]) -> None:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
IIDX21music = Node.void("music")
|
|
call.add_child(IIDX21music)
|
|
IIDX21music.set_attribute("convid", "-1")
|
|
IIDX21music.set_attribute("iidxid", str(extid))
|
|
IIDX21music.set_attribute("pgnum", str(score["pgnum"]))
|
|
IIDX21music.set_attribute("pid", "51")
|
|
IIDX21music.set_attribute("rankside", "1")
|
|
IIDX21music.set_attribute("cflg", str(score["clear_status"]))
|
|
IIDX21music.set_attribute("method", "reg")
|
|
IIDX21music.set_attribute("gnum", str(score["gnum"]))
|
|
IIDX21music.set_attribute("clid", str(score["chart"]))
|
|
IIDX21music.set_attribute("mnum", str(score["mnum"]))
|
|
IIDX21music.set_attribute("is_death", "0")
|
|
IIDX21music.set_attribute("theory", "0")
|
|
IIDX21music.set_attribute("shopconvid", lid)
|
|
IIDX21music.set_attribute("mid", str(score["id"]))
|
|
IIDX21music.set_attribute("shopflg", "1")
|
|
IIDX21music.add_child(Node.binary("ghost", bytes([1] * 64)))
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
self.assert_path(resp, "response/music/shopdata/@rank")
|
|
self.assert_path(resp, "response/music/ranklist/data")
|
|
|
|
def verify_music_appoint(self, extid: int, musicid: int, chart: int) -> Tuple[int, bytes]:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
IIDX21music = Node.void("music")
|
|
call.add_child(IIDX21music)
|
|
IIDX21music.set_attribute("clid", str(chart))
|
|
IIDX21music.set_attribute("method", "appoint")
|
|
IIDX21music.set_attribute("ctype", "0")
|
|
IIDX21music.set_attribute("iidxid", str(extid))
|
|
IIDX21music.set_attribute("subtype", "")
|
|
IIDX21music.set_attribute("mid", str(musicid))
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
self.assert_path(resp, "response/music/mydata/@score")
|
|
|
|
return (
|
|
int(resp.child("music/mydata").attribute("score")),
|
|
resp.child_value("music/mydata"),
|
|
)
|
|
|
|
def verify_pc_reg(self, ref_id: str, card_id: str, lid: str) -> int:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
IIDX21pc = Node.void("pc")
|
|
call.add_child(IIDX21pc)
|
|
IIDX21pc.set_attribute("lid", lid)
|
|
IIDX21pc.set_attribute("pid", "51")
|
|
IIDX21pc.set_attribute("method", "reg")
|
|
IIDX21pc.set_attribute("cid", card_id)
|
|
IIDX21pc.set_attribute("did", ref_id)
|
|
IIDX21pc.set_attribute("rid", ref_id)
|
|
IIDX21pc.set_attribute("name", self.NAME)
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
|
|
# Verify nodes that cause crashes if they don't exist
|
|
self.assert_path(resp, "response/pc/@id")
|
|
self.assert_path(resp, "response/pc/@id_str")
|
|
|
|
return int(resp.child("pc").attribute("id"))
|
|
|
|
def verify_pc_playstart(self) -> None:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
IIDX21pc = Node.void("pc")
|
|
IIDX21pc.set_attribute("method", "playstart")
|
|
IIDX21pc.set_attribute("side", "1")
|
|
call.add_child(IIDX21pc)
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
|
|
# Verify nodes that cause crashes if they don't exist
|
|
self.assert_path(resp, "response/pc")
|
|
|
|
def verify_music_play(self, score: Dict[str, int]) -> None:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
IIDX21music = Node.void("music")
|
|
IIDX21music.set_attribute("opt", "64")
|
|
IIDX21music.set_attribute("clid", str(score["chart"]))
|
|
IIDX21music.set_attribute("mid", str(score["id"]))
|
|
IIDX21music.set_attribute("gnum", str(score["gnum"]))
|
|
IIDX21music.set_attribute("cflg", str(score["clear_status"]))
|
|
IIDX21music.set_attribute("pgnum", str(score["pgnum"]))
|
|
IIDX21music.set_attribute("pid", "51")
|
|
IIDX21music.set_attribute("method", "play")
|
|
call.add_child(IIDX21music)
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
|
|
# Verify nodes that cause crashes if they don't exist
|
|
self.assert_path(resp, "response/music/@clid")
|
|
self.assert_path(resp, "response/music/@crate")
|
|
self.assert_path(resp, "response/music/@frate")
|
|
self.assert_path(resp, "response/music/@mid")
|
|
|
|
def verify_pc_playend(self) -> None:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
IIDX21pc = Node.void("pc")
|
|
IIDX21pc.set_attribute("cltype", "0")
|
|
IIDX21pc.set_attribute("bookkeep", "0")
|
|
IIDX21pc.set_attribute("mode", "1")
|
|
IIDX21pc.set_attribute("method", "playend")
|
|
call.add_child(IIDX21pc)
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
|
|
# Verify nodes that cause crashes if they don't exist
|
|
self.assert_path(resp, "response/pc")
|
|
|
|
def verify_music_breg(self, iidxid: int, score: Dict[str, int]) -> None:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
IIDX21music = Node.void("music")
|
|
IIDX21music.set_attribute("gnum", str(score["gnum"]))
|
|
IIDX21music.set_attribute("iidxid", str(iidxid))
|
|
IIDX21music.set_attribute("mid", str(score["id"]))
|
|
IIDX21music.set_attribute("method", "breg")
|
|
IIDX21music.set_attribute("pgnum", str(score["pgnum"]))
|
|
IIDX21music.set_attribute("cflg", str(score["clear_status"]))
|
|
call.add_child(IIDX21music)
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
|
|
# Verify nodes that cause crashes if they don't exist
|
|
self.assert_path(resp, "response/music")
|
|
|
|
def verify_grade_raised(self, iidxid: int, shop_name: str, dantype: str) -> None:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
IIDX21grade = Node.void("grade")
|
|
IIDX21grade.set_attribute("opname", shop_name)
|
|
IIDX21grade.set_attribute("is_mirror", "0")
|
|
IIDX21grade.set_attribute("oppid", "51")
|
|
IIDX21grade.set_attribute("achi", "50")
|
|
IIDX21grade.set_attribute("cflg", "4" if dantype == "sp" else "3")
|
|
IIDX21grade.set_attribute("gid", "5")
|
|
IIDX21grade.set_attribute("iidxid", str(iidxid))
|
|
IIDX21grade.set_attribute("gtype", "0" if dantype == "sp" else "1")
|
|
IIDX21grade.set_attribute("is_ex", "0")
|
|
IIDX21grade.set_attribute("pside", "0")
|
|
IIDX21grade.set_attribute("method", "raised")
|
|
call.add_child(IIDX21grade)
|
|
|
|
# Swap with server
|
|
resp = self.exchange("", call)
|
|
|
|
# Verify nodes that cause crashes if they don't exist
|
|
self.assert_path(resp, "response/grade/@pnum")
|
|
|
|
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_package_list()
|
|
self.verify_message_get()
|
|
lid = self.verify_facility_get()
|
|
self.verify_pcbevent_put()
|
|
self.verify_shop_getname(lid)
|
|
self.verify_pc_common()
|
|
self.verify_music_crate()
|
|
self.verify_shop_getconvention(lid)
|
|
self.verify_ranking_getranker(lid)
|
|
self.verify_shop_sentinfo(lid)
|
|
|
|
# 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_pc_reg(ref_id, card, lid)
|
|
self.verify_pc_get(ref_id, card, lid)
|
|
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
|
|
profile = self.verify_pc_get(ref_id, card, lid)
|
|
if profile["sp_dan"] != -1:
|
|
raise Exception("Somehow has SP DAN ranking on new profile!")
|
|
if profile["dp_dan"] != -1:
|
|
raise Exception("Somehow has DP DAN ranking on new profile!")
|
|
if profile["deller"] != 0:
|
|
raise Exception("Somehow has deller on new profile!")
|
|
scores = self.verify_music_getrank(profile["extid"])
|
|
if len(scores.keys()) > 0:
|
|
raise Exception("Somehow have scores on a new profile!")
|
|
|
|
for phase in [1, 2]:
|
|
if phase == 1:
|
|
dummyscores = [
|
|
# An okay score on a chart
|
|
{
|
|
"id": 1000,
|
|
"chart": 2,
|
|
"clear_status": 4,
|
|
"pgnum": 123,
|
|
"gnum": 123,
|
|
"mnum": 5,
|
|
},
|
|
# A good score on an easier chart of the same song
|
|
{
|
|
"id": 1000,
|
|
"chart": 0,
|
|
"clear_status": 7,
|
|
"pgnum": 246,
|
|
"gnum": 0,
|
|
"mnum": 0,
|
|
},
|
|
# A bad score on a hard chart
|
|
{
|
|
"id": 1003,
|
|
"chart": 2,
|
|
"clear_status": 1,
|
|
"pgnum": 10,
|
|
"gnum": 20,
|
|
"mnum": 50,
|
|
},
|
|
# A terrible score on an easy chart
|
|
{
|
|
"id": 1003,
|
|
"chart": 0,
|
|
"clear_status": 1,
|
|
"pgnum": 2,
|
|
"gnum": 5,
|
|
"mnum": 75,
|
|
},
|
|
]
|
|
if phase == 2:
|
|
dummyscores = [
|
|
# A better score on the same chart
|
|
{
|
|
"id": 1000,
|
|
"chart": 2,
|
|
"clear_status": 5,
|
|
"pgnum": 234,
|
|
"gnum": 234,
|
|
"mnum": 3,
|
|
},
|
|
# A worse score on another same chart
|
|
{
|
|
"id": 1000,
|
|
"chart": 0,
|
|
"clear_status": 4,
|
|
"pgnum": 123,
|
|
"gnum": 123,
|
|
"mnum": 35,
|
|
"expected_clear_status": 7,
|
|
"expected_ex_score": 492,
|
|
"expected_miss_count": 0,
|
|
},
|
|
]
|
|
|
|
for dummyscore in dummyscores:
|
|
self.verify_music_reg(profile["extid"], lid, dummyscore)
|
|
self.verify_pc_visit(profile["extid"], lid)
|
|
self.verify_pc_save(profile["extid"], card, lid)
|
|
scores = self.verify_music_getrank(profile["extid"])
|
|
for score in dummyscores:
|
|
data = scores.get(score["id"], {}).get(score["chart"], None)
|
|
if data is None:
|
|
raise Exception(f'Expected to get score back for song {score["id"]} chart {score["chart"]}!')
|
|
|
|
if "expected_ex_score" in score:
|
|
expected_score = score["expected_ex_score"]
|
|
else:
|
|
expected_score = (score["pgnum"] * 2) + score["gnum"]
|
|
if "expected_clear_status" in score:
|
|
expected_clear_status = score["expected_clear_status"]
|
|
else:
|
|
expected_clear_status = score["clear_status"]
|
|
if "expected_miss_count" in score:
|
|
expected_miss_count = score["expected_miss_count"]
|
|
else:
|
|
expected_miss_count = score["mnum"]
|
|
|
|
if data["ex_score"] != expected_score:
|
|
raise Exception(
|
|
f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\''
|
|
)
|
|
if data["clear_status"] != expected_clear_status:
|
|
raise Exception(
|
|
f'Expected a clear status of \'{expected_clear_status}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got clear status \'{data["clear_status"]}\''
|
|
)
|
|
if data["miss_count"] != expected_miss_count:
|
|
raise Exception(
|
|
f'Expected a miss count of \'{expected_miss_count}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got miss count \'{data["miss_count"]}\''
|
|
)
|
|
|
|
# Verify we can fetch our own ghost
|
|
ex_score, ghost = self.verify_music_appoint(profile["extid"], score["id"], score["chart"])
|
|
if ex_score != expected_score:
|
|
raise Exception(
|
|
f'Expected a score of \'{expected_score}\' for song \'{score["id"]}\' chart \'{score["chart"]}\' but got score \'{data["ex_score"]}\''
|
|
)
|
|
|
|
if len(ghost) != 64:
|
|
raise Exception(f"Wrong ghost length {len(ghost)} for ghost!")
|
|
for g in ghost:
|
|
if g != 0x01:
|
|
raise Exception(
|
|
f'Got back wrong ghost data for song \'{score["id"]}\' chart \'{score["chart"]}\''
|
|
)
|
|
|
|
# Sleep so we don't end up putting in score history on the same second
|
|
time.sleep(1)
|
|
|
|
# Verify that a player without a card can play
|
|
self.verify_pc_playstart()
|
|
self.verify_music_play(
|
|
{
|
|
"id": 1000,
|
|
"chart": 2,
|
|
"clear_status": 4,
|
|
"pgnum": 123,
|
|
"gnum": 123,
|
|
}
|
|
)
|
|
self.verify_pc_playend()
|
|
|
|
# Verify shop name change setting
|
|
self.verify_shop_savename(lid, "newname1")
|
|
newname = self.verify_shop_getname(lid)
|
|
if newname != "newname1":
|
|
raise Exception("Invalid shop name returned after change!")
|
|
self.verify_shop_savename(lid, "newname2")
|
|
newname = self.verify_shop_getname(lid)
|
|
if newname != "newname2":
|
|
raise Exception("Invalid shop name returned after change!")
|
|
|
|
# Verify beginner score saving
|
|
self.verify_music_breg(
|
|
profile["extid"],
|
|
{
|
|
"id": 1000,
|
|
"clear_status": 4,
|
|
"pgnum": 123,
|
|
"gnum": 123,
|
|
},
|
|
)
|
|
scores = self.verify_music_getrank(profile["extid"])
|
|
if 1000 not in scores:
|
|
raise Exception(f"Didn't get expected scores back for song {1000} beginner chart!")
|
|
if 6 not in scores[1000]:
|
|
raise Exception(f"Didn't get beginner score back for song {1000}!")
|
|
if scores[1000][6] != {"clear_status": 4, "ex_score": -1, "miss_count": -1}:
|
|
raise Exception("Didn't get correct status back from beginner save!")
|
|
|
|
# Verify DAN score saving and loading
|
|
self.verify_grade_raised(profile["extid"], newname, "sp")
|
|
self.verify_grade_raised(profile["extid"], newname, "dp")
|
|
profile = self.verify_pc_get(ref_id, card, lid)
|
|
if profile["sp_dan"] != 5:
|
|
raise Exception("Got wrong DAN score back for SP!")
|
|
if profile["dp_dan"] != 5:
|
|
raise Exception("Got wrong DAN score back for DP!")
|
|
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)
|