From 20389011e9cbd470febf84d43851df582afdf403 Mon Sep 17 00:00:00 2001 From: Midorica Date: Wed, 28 Jun 2023 12:54:16 -0400 Subject: [PATCH] Adding proper hero unlock after stage clear on SAO --- titles/sao/base.py | 85 +++++++++++++++++++++++++++++++++++-- titles/sao/handlers/base.py | 4 +- 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/titles/sao/base.py b/titles/sao/base.py index 8886a49..ad03e97 100644 --- a/titles/sao/base.py +++ b/titles/sao/base.py @@ -1160,14 +1160,91 @@ class SaoBase: resp = SaoTrialTowerPlayEndResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1) return resp.make() - def handle_c90a(self, request: Any) -> bytes: #should be tweaked for proper item unlock + def handle_c90a(self, request: Any) -> bytes: #quest/episode_play_end_unanalyzed_log_fixed - resp = SaoEpisodePlayEndUnanalyzedLogFixedResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1) + + req = bytes.fromhex(request)[24:] + + req_struct = Struct( + Padding(16), + "ticket_id_size" / Rebuild(Int32ub, len_(this.ticket_id) * 2), # calculates the length of the ticket_id + "ticket_id" / PaddedString(this.ticket_id_size, "utf_16_le"), # ticket_id is a (zero) padded string + "user_id_size" / Rebuild(Int32ub, len_(this.user_id) * 2), # calculates the length of the user_id + "user_id" / PaddedString(this.user_id_size, "utf_16_le"), # user_id is a (zero) padded string + ) + + req_data = req_struct.parse(req) + user_id = req_data.user_id + + with open('titles/sao/data/RewardTable.csv', 'r') as f: + keys_unanalyzed = next(f).strip().split(',') + data_unanalyzed = list(DictReader(f, fieldnames=keys_unanalyzed)) + + randomized_unanalyzed_id = choice(data_unanalyzed) + heroList = self.game_data.static.get_hero_id(randomized_unanalyzed_id['CommonRewardId']) + i = 0 + + # Create a loop to check if the id is a hero or else try 15 times before closing the loop and sending a dummy hero + while not heroList: + if i == 15: + # Return the dummy hero but not save it + resp = SaoEpisodePlayEndUnanalyzedLogFixedResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1, 102000070) + return resp.make() + + i += 1 + randomized_unanalyzed_id = choice(data_unanalyzed) + heroList = self.game_data.static.get_hero_id(randomized_unanalyzed_id['CommonRewardId']) + + hero_data = self.game_data.item.get_hero_log(user_id, randomized_unanalyzed_id['CommonRewardId']) + + # Avoid having a duplicated card and cause an overwrite + if not hero_data: + self.game_data.item.put_hero_log(user_id, randomized_unanalyzed_id['CommonRewardId'], 1, 0, 101000016, 0, 30086, 1001, 1002, 0, 0) + + resp = SaoEpisodePlayEndUnanalyzedLogFixedResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1, randomized_unanalyzed_id['CommonRewardId']) return resp.make() def handle_c91a(self, request: Any) -> bytes: # handler is identical to the episode #quest/trial_tower_play_end_unanalyzed_log_fixed - resp = SaoEpisodePlayEndUnanalyzedLogFixedResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1) + req = bytes.fromhex(request)[24:] + + req_struct = Struct( + Padding(16), + "ticket_id_size" / Rebuild(Int32ub, len_(this.ticket_id) * 2), # calculates the length of the ticket_id + "ticket_id" / PaddedString(this.ticket_id_size, "utf_16_le"), # ticket_id is a (zero) padded string + "user_id_size" / Rebuild(Int32ub, len_(this.user_id) * 2), # calculates the length of the user_id + "user_id" / PaddedString(this.user_id_size, "utf_16_le"), # user_id is a (zero) padded string + ) + + req_data = req_struct.parse(req) + user_id = req_data.user_id + + with open('titles/sao/data/RewardTable.csv', 'r') as f: + keys_unanalyzed = next(f).strip().split(',') + data_unanalyzed = list(DictReader(f, fieldnames=keys_unanalyzed)) + + randomized_unanalyzed_id = choice(data_unanalyzed) + heroList = self.game_data.static.get_hero_id(randomized_unanalyzed_id['CommonRewardId']) + i = 0 + + # Create a loop to check if the id is a hero or else try 15 times before closing the loop and sending a dummy hero + while not heroList: + if i == 15: + # Return the dummy hero but not save it + resp = SaoEpisodePlayEndUnanalyzedLogFixedResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1, 102000070) + return resp.make() + + i += 1 + randomized_unanalyzed_id = choice(data_unanalyzed) + heroList = self.game_data.static.get_hero_id(randomized_unanalyzed_id['CommonRewardId']) + + hero_data = self.game_data.item.get_hero_log(user_id, randomized_unanalyzed_id['CommonRewardId']) + + # Avoid having a duplicated card and cause an overwrite + if not hero_data: + self.game_data.item.put_hero_log(user_id, randomized_unanalyzed_id['CommonRewardId'], 1, 0, 101000016, 0, 30086, 1001, 1002, 0, 0) + + resp = SaoEpisodePlayEndUnanalyzedLogFixedResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1, randomized_unanalyzed_id['CommonRewardId']) return resp.make() def handle_cd00(self, request: Any) -> bytes: @@ -1189,7 +1266,7 @@ class SaoBase: #defrag_match/get_defrag_match_league_score_ranking_list resp = SaoGetDefragMatchLeagueScoreRankingListResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1) return resp.make() - + def handle_d404(self, request: Any) -> bytes: #other/bnid_serial_code_check resp = SaoBnidSerialCodeCheckResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1) diff --git a/titles/sao/handlers/base.py b/titles/sao/handlers/base.py index c634caf..f5c114f 100644 --- a/titles/sao/handlers/base.py +++ b/titles/sao/handlers/base.py @@ -1661,7 +1661,7 @@ class SaoEpisodePlayEndUnanalyzedLogFixedRequest(SaoBaseRequest): super().__init__(data) class SaoEpisodePlayEndUnanalyzedLogFixedResponse(SaoBaseResponse): - def __init__(self, cmd) -> None: + def __init__(self, cmd, randomized_unanalyzed_id) -> None: super().__init__(cmd) self.result = 1 self.play_end_unanalyzed_log_reward_data_list_size = 1 # Number of arrays @@ -1670,7 +1670,7 @@ class SaoEpisodePlayEndUnanalyzedLogFixedResponse(SaoBaseResponse): self.common_reward_data_size = 1 self.common_reward_type_1 = 1 - self.common_reward_id_1 = 102000070 + self.common_reward_id_1 = int(randomized_unanalyzed_id) self.common_reward_num_1 = 1 def make(self) -> bytes: