1
0
mirror of synced 2024-11-27 23:50:47 +01:00

Enough of the profile portion to get festo to let you card in.

This commit is contained in:
Jennifer Taylor 2022-08-13 23:10:56 +00:00
parent ed43e9d3e6
commit 149eca0aab
2 changed files with 472 additions and 3 deletions

View File

@ -1105,7 +1105,6 @@ class JubeatClan(
return Node.void('gameend')
def format_scores(self, userid: UserID, profile: Profile, scores: List[Score]) -> Node:
root = Node.void('gametop')
datanode = Node.void('data')
root.add_child(datanode)

View File

@ -1,19 +1,25 @@
# vim: set fileencoding=utf-8
from typing import Dict, List, Optional
import random
from typing import Any, Dict, List, Optional, Set
from typing_extensions import Final
from bemani.backend.jubeat.base import JubeatBase
from bemani.backend.jubeat.common import (
JubeatDemodataGetNewsHandler,
JubeatGametopGetMeetingHandler,
JubeatLoggerReportHandler,
)
from bemani.backend.jubeat.clan import JubeatClan
from bemani.common import VersionConstants
from bemani.backend.base import Status
from bemani.common import Profile, ValidatedDict, VersionConstants
from bemani.data import UserID, Score
from bemani.protocol import Node
class JubeatFesto(
JubeatDemodataGetNewsHandler,
JubeatGametopGetMeetingHandler,
JubeatLoggerReportHandler,
JubeatBase
):
@ -70,14 +76,27 @@ class JubeatFesto(
},
}
EVENT_STATUS_OPEN: Final[int] = 0x1
EVENT_STATUS_COMPLETE: Final[int] = 0x2
# TODO: Verify these
COURSE_STATUS_SEEN: Final[int] = 0x01
COURSE_STATUS_PLAYED: Final[int] = 0x02
COURSE_STATUS_CLEARED: Final[int] = 0x04
# Return the netlog service so that Festo doesn't crash on coin-in.
extra_services: List[str] = [
'netlog',
'slocal',
]
def previous_version(self) -> Optional[JubeatBase]:
return JubeatClan(self.data, self.config, self.model)
def __get_course_list(self) -> List[Dict[str, Any]]:
return [
]
def __get_global_info(self) -> Node:
info = Node.void('info')
@ -388,3 +407,454 @@ class JubeatFesto(
data.add_child(self.__get_global_info())
return shopinfo
def handle_gametop_regist_request(self, request: Node) -> Node:
data = request.child('data')
player = data.child('player')
refid = player.child_value('refid')
name = player.child_value('name')
root = self.new_profile_by_refid(refid, name)
return root
def handle_gametop_get_pdata_request(self, request: Node) -> Node:
data = request.child('data')
player = data.child('player')
refid = player.child_value('refid')
root = self.get_profile_by_refid(refid)
if root is None:
root = Node.void('gametop')
root.set_attribute('status', str(Status.NO_PROFILE))
return root
def handle_gametop_get_mdata_request(self, request: Node) -> Node:
data = request.child('data')
player = data.child('player')
extid = player.child_value('jid')
mdata_ver = player.child_value('mdata_ver') # Game requests mdata 3 times per profile for some reason
if mdata_ver != 1:
root = Node.void('gametop')
datanode = Node.void('data')
root.add_child(datanode)
player = Node.void('player')
datanode.add_child(player)
player.add_child(Node.s32('jid', extid))
playdata = Node.void('mdata_list')
player.add_child(playdata)
return root
root = self.get_scores_by_extid(extid)
if root is None:
root = Node.void('gametop')
root.set_attribute('status', str(Status.NO_PROFILE))
return root
def format_scores(self, userid: UserID, profile: Profile, scores: List[Score]) -> Node:
root = Node.void('gametop')
datanode = Node.void('data')
root.add_child(datanode)
player = Node.void('player')
datanode.add_child(player)
player.add_child(Node.s32('jid', profile.extid))
playdata = Node.void('mdata_list')
player.add_child(playdata)
# TODO: Need to add hard mode charts, make previous games ignore them, and sum
# them up here as well.
music = ValidatedDict()
for score in scores:
data = music.get_dict(str(score.id))
play_cnt = data.get_int_array('play_cnt', 3)
clear_cnt = data.get_int_array('clear_cnt', 3)
clear_flags = data.get_int_array('clear_flags', 3)
fc_cnt = data.get_int_array('fc_cnt', 3)
ex_cnt = data.get_int_array('ex_cnt', 3)
points = data.get_int_array('points', 3)
# Replace data for this chart type
play_cnt[score.chart] = score.plays
clear_cnt[score.chart] = score.data.get_int('clear_count')
fc_cnt[score.chart] = score.data.get_int('full_combo_count')
ex_cnt[score.chart] = score.data.get_int('excellent_count')
points[score.chart] = score.points
# Format the clear flags
clear_flags[score.chart] = self.GAME_FLAG_BIT_PLAYED
if score.data.get_int('clear_count') > 0:
clear_flags[score.chart] |= self.GAME_FLAG_BIT_CLEARED
if score.data.get_int('full_combo_count') > 0:
clear_flags[score.chart] |= self.GAME_FLAG_BIT_FULL_COMBO
if score.data.get_int('excellent_count') > 0:
clear_flags[score.chart] |= self.GAME_FLAG_BIT_EXCELLENT
# Save chart data back
data.replace_int_array('play_cnt', 3, play_cnt)
data.replace_int_array('clear_cnt', 3, clear_cnt)
data.replace_int_array('clear_flags', 3, clear_flags)
data.replace_int_array('fc_cnt', 3, fc_cnt)
data.replace_int_array('ex_cnt', 3, ex_cnt)
data.replace_int_array('points', 3, points)
# Update the ghost (untyped)
ghost = data.get('ghost', [None, None, None])
ghost[score.chart] = score.data.get('ghost')
data['ghost'] = ghost
# Save it back
music.replace_dict(str(score.id), data)
for scoreid in music:
scoredata = music.get_dict(scoreid)
musicdata = Node.void('musicdata')
playdata.add_child(musicdata)
musicdata.set_attribute('music_id', scoreid)
normalnode = Node.void('normal')
musicdata.add_child(normalnode)
normalnode.add_child(Node.s32_array('play_cnt', scoredata.get_int_array('play_cnt', 3)))
normalnode.add_child(Node.s32_array('clear_cnt', scoredata.get_int_array('clear_cnt', 3)))
normalnode.add_child(Node.s32_array('fc_cnt', scoredata.get_int_array('fc_cnt', 3)))
normalnode.add_child(Node.s32_array('ex_cnt', scoredata.get_int_array('ex_cnt', 3)))
normalnode.add_child(Node.s32_array('score', scoredata.get_int_array('points', 3)))
normalnode.add_child(Node.s8_array('clear', scoredata.get_int_array('clear_flags', 3)))
for i, ghost in enumerate(scoredata.get('ghost', [None, None, None])):
if ghost is None:
continue
bar = Node.u8_array('bar', ghost)
normalnode.add_child(bar)
bar.set_attribute('seq', str(i))
return root
def format_profile(self, userid: UserID, profile: Profile) -> Node:
root = Node.void('gametop')
data = Node.void('data')
root.add_child(data)
# Jubeat Clan appears to allow full event overrides per-player
data.add_child(self.__get_global_info())
player = Node.void('player')
data.add_child(player)
# Basic profile info
player.add_child(Node.string('name', profile.get_str('name', 'なし')))
player.add_child(Node.s32('jid', profile.extid))
# Miscelaneous crap
player.add_child(Node.s32('session_id', 1))
player.add_child(Node.u64('event_flag', profile.get_int('event_flag')))
# Player info and statistics
info = Node.void('info')
player.add_child(info)
info.add_child(Node.s32('tune_cnt', profile.get_int('tune_cnt')))
info.add_child(Node.s32('save_cnt', profile.get_int('save_cnt')))
info.add_child(Node.s32('saved_cnt', profile.get_int('saved_cnt')))
info.add_child(Node.s32('fc_cnt', profile.get_int('fc_cnt')))
info.add_child(Node.s32('ex_cnt', profile.get_int('ex_cnt')))
info.add_child(Node.s32('clear_cnt', profile.get_int('clear_cnt')))
info.add_child(Node.s32('match_cnt', profile.get_int('match_cnt')))
info.add_child(Node.s32('beat_cnt', profile.get_int('beat_cnt')))
info.add_child(Node.s32('mynews_cnt', profile.get_int('mynews_cnt')))
info.add_child(Node.s32('mtg_entry_cnt', profile.get_int('mtg_entry_cnt')))
info.add_child(Node.s32('mtg_hold_cnt', profile.get_int('mtg_hold_cnt')))
info.add_child(Node.u8('mtg_result', profile.get_int('mtg_result')))
info.add_child(Node.s32('bonus_tune_points', profile.get_int('bonus_tune_points')))
info.add_child(Node.bool('is_bonus_tune_played', profile.get_bool('is_bonus_tune_played')))
# Looks to be set to true when there's an old profile, stops tutorial from
# happening on first load.
info.add_child(Node.bool('inherit', profile.get_bool('has_old_version')))
# Last played data, for showing cursor and such
lastdict = profile.get_dict('last')
last = Node.void('last')
player.add_child(last)
last.add_child(Node.s64('play_time', lastdict.get_int('play_time')))
last.add_child(Node.string('shopname', lastdict.get_str('shopname')))
last.add_child(Node.string('areaname', lastdict.get_str('areaname')))
last.add_child(Node.s32('music_id', lastdict.get_int('music_id')))
last.add_child(Node.s8('seq_id', lastdict.get_int('seq_id')))
last.add_child(Node.s8('sort', lastdict.get_int('sort')))
last.add_child(Node.s8('category', lastdict.get_int('category')))
last.add_child(Node.s8('expert_option', lastdict.get_int('expert_option')))
settings = Node.void('settings')
last.add_child(settings)
settings.add_child(Node.s8('marker', lastdict.get_int('marker')))
settings.add_child(Node.s8('theme', lastdict.get_int('theme')))
settings.add_child(Node.s16('title', lastdict.get_int('title')))
settings.add_child(Node.s16('parts', lastdict.get_int('parts')))
settings.add_child(Node.s8('rank_sort', lastdict.get_int('rank_sort')))
settings.add_child(Node.s8('combo_disp', lastdict.get_int('combo_disp')))
settings.add_child(Node.s16_array('emblem', lastdict.get_int_array('emblem', 5)))
settings.add_child(Node.s8('matching', lastdict.get_int('matching')))
settings.add_child(Node.s8('hard', lastdict.get_int('hard')))
settings.add_child(Node.s8('hazard', lastdict.get_int('hazard')))
# Secret unlocks, TODO: Make these configurable so events work.
item = Node.void('item')
player.add_child(item)
item.add_child(Node.s32_array('music_list', profile.get_int_array('music_list', 64, [-1] * 64)))
item.add_child(Node.s32_array('secret_list', profile.get_int_array('secret_list', 64, [-1] * 64)))
item.add_child(Node.s32_array('theme_list', profile.get_int_array('theme_list', 16, [-1] * 16)))
item.add_child(Node.s32_array('marker_list', profile.get_int_array('marker_list', 16, [-1] * 16)))
item.add_child(Node.s32_array('title_list', profile.get_int_array('title_list', 160, [-1] * 160)))
item.add_child(Node.s32_array('parts_list', profile.get_int_array('parts_list', 160, [-1] * 160)))
item.add_child(Node.s32_array('emblem_list', profile.get_int_array('emblem_list', 96, [-1] * 96)))
item.add_child(Node.s32_array('commu_list', profile.get_int_array('commu_list', 16, [-1] * 16)))
new = Node.void('new')
item.add_child(new)
new.add_child(Node.s32_array('secret_list', profile.get_int_array('secret_list_new', 64, [-1] * 64)))
new.add_child(Node.s32_array('theme_list', profile.get_int_array('theme_list_new', 16, [-1] * 16)))
new.add_child(Node.s32_array('marker_list', profile.get_int_array('marker_list_new', 16, [-1] * 16)))
# Add rivals to profile.
rivallist = Node.void('rivallist')
player.add_child(rivallist)
links = self.data.local.user.get_links(self.game, self.version, userid)
rivalcount = 0
for link in links:
if link.type != 'rival':
continue
rprofile = self.get_profile(link.other_userid)
if rprofile is None:
continue
rival = Node.void('rival')
rivallist.add_child(rival)
rival.add_child(Node.s32('jid', rprofile.extid))
rival.add_child(Node.string('name', rprofile.get_str('name')))
# This looks like a carry-over from prop's career and isn't displayed.
career = Node.void('career')
rival.add_child(career)
career.add_child(Node.s16('level', 1))
# Lazy way of keeping track of rivals, since we can only have 3
# or the game with throw up.
rivalcount += 1
if rivalcount >= 3:
break
lab_edit_seq = Node.void('lab_edit_seq')
player.add_child(lab_edit_seq)
lab_edit_seq.set_attribute('count', '0')
# Full combo challenge
entry = self.data.local.game.get_time_sensitive_settings(self.game, self.version, 'fc_challenge')
if entry is None:
entry = ValidatedDict()
# Figure out if we've played these songs
start_time, end_time = self.data.local.network.get_schedule_duration('daily')
today_attempts = self.data.local.music.get_all_attempts(
self.game, self.version, userid, entry.get_int('today', -1), timelimit=start_time
)
whim_attempts = self.data.local.music.get_all_attempts(
self.game, self.version, userid, entry.get_int('whim', -1), timelimit=start_time
)
# TODO: Are these still the right state constants?
fc_challenge = Node.void('fc_challenge')
player.add_child(fc_challenge)
today = Node.void('today')
fc_challenge.add_child(today)
today.add_child(Node.s32('music_id', entry.get_int('today', -1)))
today.add_child(Node.u8('state', 0x40 if len(today_attempts) > 0 else 0x0))
whim = Node.void('whim')
fc_challenge.add_child(whim)
whim.add_child(Node.s32('music_id', entry.get_int('whim', -1)))
whim.add_child(Node.u8('state', 0x40 if len(whim_attempts) > 0 else 0x0))
# No news, ever.
official_news = Node.void('official_news')
player.add_child(official_news)
news_list = Node.void('news_list')
official_news.add_child(news_list)
# Sane defaults for unknown/who cares nodes
history = Node.void('history')
player.add_child(history)
history.set_attribute('count', '0')
free_first_play = Node.void('free_first_play')
player.add_child(free_first_play)
free_first_play.add_child(Node.bool('is_available', False))
# Player status for events
event_info = Node.void('event_info')
player.add_child(event_info)
achievements = self.data.local.user.get_achievements(self.game, self.version, userid)
event_completion: Dict[int, bool] = {}
course_completion: Dict[int, ValidatedDict] = {}
for achievement in achievements:
if achievement.type == 'event':
event_completion[achievement.id] = achievement.data.get_bool('is_completed')
if achievement.type == 'course':
course_completion[achievement.id] = achievement.data
for eventid, eventdata in self.EVENTS.items():
# There are two significant bits here, bit 0 and bit 1, I think the first
# one is whether the event is started, second is if its finished?
event = Node.void('event')
event_info.add_child(event)
event.set_attribute('type', str(eventid))
state = 0x0
state |= self.EVENT_STATUS_OPEN if eventdata['enabled'] else 0
state |= self.EVENT_STATUS_COMPLETE if event_completion.get(eventid, False) else 0
event.add_child(Node.u8('state', state))
# JBox stuff
jbox = Node.void('jbox')
jboxdict = profile.get_dict('jbox')
player.add_child(jbox)
jbox.add_child(Node.s32('point', jboxdict.get_int('point')))
emblem = Node.void('emblem')
jbox.add_child(emblem)
normal = Node.void('normal')
emblem.add_child(normal)
premium = Node.void('premium')
emblem.add_child(premium)
# Calculate a random index for normal and premium to give to player
# as a gatcha.
gameitems = self.data.local.game.get_items(self.game, self.version)
normalemblems: Set[int] = set()
premiumemblems: Set[int] = set()
for gameitem in gameitems:
if gameitem.type == 'emblem':
if gameitem.data.get_int('rarity') in {1, 2, 3}:
normalemblems.add(gameitem.id)
if gameitem.data.get_int('rarity') in {3, 4, 5}:
premiumemblems.add(gameitem.id)
# Default to some emblems in case the catalog is not available.
normalindex = 2
premiumindex = 1
if normalemblems:
normalindex = random.sample(normalemblems, 1)[0]
if premiumemblems:
premiumindex = random.sample(premiumemblems, 1)[0]
normal.add_child(Node.s16('index', normalindex))
premium.add_child(Node.s16('index', premiumindex))
# New Music stuff
new_music = Node.void('new_music')
player.add_child(new_music)
navi = Node.void('navi')
player.add_child(navi)
navi.add_child(Node.u64('flag', profile.get_int('navi_flag')))
# Gift list, maybe from other players?
gift_list = Node.void('gift_list')
player.add_child(gift_list)
# If we had gifts, they look like this. This is incomplete, however,
# because I never bothered to find the virtual function to decode "detail".
# Note that detail is only necessary if you don't want to give reason/id,
# so its gotta be some hacked-on override.
# <gift reason="??" id="??">
# <detail>??</detail>
# </gift>
# Birthday event?
born = Node.void('born')
player.add_child(born)
born.add_child(Node.s8('status', profile.get_int('born_status')))
born.add_child(Node.s16('year', profile.get_int('born_year')))
# More crap
question_list = Node.void('question_list')
player.add_child(question_list)
emo_list = Node.void('emo_list')
player.add_child(emo_list)
# Some server node
server = Node.void('server')
player.add_child(server)
# Course List Progress
course_list = Node.void('course_list')
player.add_child(course_list)
# Each course that we have completed has one of the following nodes.
for course in self.__get_course_list():
status_dict = course_completion.get(course['id'], ValidatedDict())
status = 0
status |= self.COURSE_STATUS_SEEN if status_dict.get_bool('seen') else 0
status |= self.COURSE_STATUS_PLAYED if status_dict.get_bool('played') else 0
status |= self.COURSE_STATUS_CLEARED if status_dict.get_bool('cleared') else 0
coursenode = Node.void('course')
course_list.add_child(coursenode)
coursenode.set_attribute('id', str(course['id']))
coursenode.add_child(Node.s8('status', status))
# For some reason, this is on the course list node this time around.
category_list = Node.void('category_list')
course_list.add_child(category_list)
# Fill in category
fill_in_category = Node.void('fill_in_category')
player.add_child(fill_in_category)
normal = Node.void('normal')
fill_in_category.add_child(normal)
normal.add_child(
Node.s32_array('no_gray_flag_list', profile.get_int_array('normal_no_gray_flag_list', 16, [-1] * 16))
)
normal.add_child(
Node.s32_array('all_yellow_flag_list', profile.get_int_array('normal_all_yellow_flag_list', 16, [-1] * 16))
)
normal.add_child(
Node.s32_array('full_combo_flag_list', profile.get_int_array('normal_full_combo_flag_list', 16, [-1] * 16))
)
normal.add_child(
Node.s32_array('excellent_flag_list', profile.get_int_array('normal_excellent_flag_list', 16, [-1] * 16))
)
hard = Node.void('hard')
fill_in_category.add_child(hard)
hard.add_child(
Node.s32_array('no_gray_flag_list', profile.get_int_array('hard_no_gray_flag_list', 16, [-1] * 16))
)
hard.add_child(
Node.s32_array('all_yellow_flag_list', profile.get_int_array('hard_all_yellow_flag_list', 16, [-1] * 16))
)
hard.add_child(
Node.s32_array('full_combo_flag_list', profile.get_int_array('hard_full_combo_flag_list', 16, [-1] * 16))
)
hard.add_child(
Node.s32_array('excellent_flag_list', profile.get_int_array('hard_excellent_flag_list', 16, [-1] * 16))
)
# TODO: Unknown department stuff
department = Node.void('department')
player.add_child(department)
department.add_child(Node.void('shop_list'))
# TODO: Unknown stamp stuff
stamp = Node.void('stamp')
player.add_child(stamp)
stamp.add_child(Node.void('sheet_list'))
# TODO: team_battle?
# TODO: eamuse_gift_list?
# TODO: hike_event
# TODO: festo_dungeon
# TODO: travel
return root