reformat more files; add Ep3 map endpoint in HTTP server
This commit is contained in:
@@ -544,8 +544,8 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
|
||||
}
|
||||
|
||||
case 0x67: {
|
||||
// Technically we should assign item IDs here, but the server will never
|
||||
// be able to see that we didn't, so we don't bother
|
||||
// Technically we should assign item IDs here, but the server will never be able to see that we didn't, so we
|
||||
// don't bother
|
||||
|
||||
const auto& game_config = this->game_configs[this->current_game_config_index];
|
||||
if (this->version == Version::PC_V2) {
|
||||
@@ -688,9 +688,7 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
|
||||
}
|
||||
auto& f = this->open_files.at(cmd.filename.decode());
|
||||
size_t block_offset = msg.flag * 0x400;
|
||||
size_t allowed_block_size = (block_offset < f.total_size)
|
||||
? min<size_t>(f.total_size - block_offset, 0x400)
|
||||
: 0;
|
||||
size_t allowed_block_size = (block_offset < f.total_size) ? min<size_t>(f.total_size - block_offset, 0x400) : 0;
|
||||
size_t data_size = min<size_t>(cmd.data_size, allowed_block_size);
|
||||
size_t block_end_offset = block_offset + data_size;
|
||||
if (block_end_offset > f.data.size()) {
|
||||
|
||||
+184
-33
@@ -1814,7 +1814,7 @@ phosg::JSON MapDefinition::AIParams::json(Language language) const {
|
||||
return phosg::JSON::dict({
|
||||
{"IsArkz", this->is_arkz ? true : false},
|
||||
{"Name", this->ai_name.decode(language)},
|
||||
{"CardIDs", std::move(params_json)},
|
||||
{"Params", std::move(params_json)},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1826,7 +1826,7 @@ phosg::JSON MapDefinition::DialogueSet::json(Language language) const {
|
||||
return phosg::JSON::dict({
|
||||
{"When", this->when.load()},
|
||||
{"PercentChance", this->percent_chance.load()},
|
||||
{"CardIDs", std::move(strings_json)},
|
||||
{"Strings", std::move(strings_json)},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1871,9 +1871,6 @@ phosg::JSON MapDefinition::EntryState::json() const {
|
||||
return phosg::JSON::dict({{"PlayerType", std::move(player_type_json)}, {"DeckType", std::move(deck_type_json)}});
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// phosg::JSON MapDefinition::json() const { ... }
|
||||
|
||||
string MapDefinition::CameraSpec::str() const {
|
||||
return std::format(
|
||||
"CameraSpec[a1=({:g} {:g} {:g} {:g} {:g} {:g} {:g} {:g} {:g}) camera=({:g} {:g} {:g}) focus=({:g} {:g} {:g}) a2=({:g} {:g} {:g})]",
|
||||
@@ -1883,6 +1880,57 @@ string MapDefinition::CameraSpec::str() const {
|
||||
this->unknown_a2[0], this->unknown_a2[1], this->unknown_a2[2]);
|
||||
}
|
||||
|
||||
struct UnavailableSCCardDefinition {
|
||||
size_t index;
|
||||
uint16_t card_id;
|
||||
const char* internal_name;
|
||||
};
|
||||
|
||||
static const array<UnavailableSCCardDefinition, 0x2A> unavailable_sc_card_defs = {
|
||||
UnavailableSCCardDefinition{0x00, 0x0005, "Guykild"},
|
||||
UnavailableSCCardDefinition{0x01, 0x0006, "Kylria"},
|
||||
UnavailableSCCardDefinition{0x02, 0x0110, "Saligun"},
|
||||
UnavailableSCCardDefinition{0x03, 0x0111, "Relmitos"},
|
||||
UnavailableSCCardDefinition{0x04, 0x0002, "Kranz"},
|
||||
UnavailableSCCardDefinition{0x05, 0x0004, "Sil'fer"},
|
||||
UnavailableSCCardDefinition{0x06, 0x0003, "Ino'lis"},
|
||||
UnavailableSCCardDefinition{0x07, 0x0112, "Viviana"},
|
||||
UnavailableSCCardDefinition{0x08, 0x0113, "Teifu"},
|
||||
UnavailableSCCardDefinition{0x09, 0x0001, "Orland"},
|
||||
UnavailableSCCardDefinition{0x0A, 0x0114, "Stella"},
|
||||
UnavailableSCCardDefinition{0x0B, 0x0115, "Glustar"},
|
||||
UnavailableSCCardDefinition{0x0C, 0x0117, "Hyze"},
|
||||
UnavailableSCCardDefinition{0x0D, 0x0118, "Rufina"},
|
||||
UnavailableSCCardDefinition{0x0E, 0x0119, "Peko"},
|
||||
UnavailableSCCardDefinition{0x0F, 0x011A, "Creinu"},
|
||||
UnavailableSCCardDefinition{0x10, 0x011B, "Reiz"},
|
||||
UnavailableSCCardDefinition{0x11, 0x0007, "Lura"},
|
||||
UnavailableSCCardDefinition{0x12, 0x0008, "Break"},
|
||||
UnavailableSCCardDefinition{0x13, 0x011C, "Rio"},
|
||||
UnavailableSCCardDefinition{0x14, 0x0116, "Endu"},
|
||||
UnavailableSCCardDefinition{0x15, 0x011D, "Memoru"},
|
||||
UnavailableSCCardDefinition{0x16, 0x011E, "K.C."},
|
||||
UnavailableSCCardDefinition{0x17, 0x011F, "Ohgun"},
|
||||
UnavailableSCCardDefinition{0x18, 0x02AA, "HERO_1"},
|
||||
UnavailableSCCardDefinition{0x19, 0x02AB, "HERO_2"},
|
||||
UnavailableSCCardDefinition{0x1A, 0x02AC, "HERO_3"},
|
||||
UnavailableSCCardDefinition{0x1B, 0x02AD, "HERO_4"},
|
||||
UnavailableSCCardDefinition{0x1C, 0x02AE, "HERO_5"},
|
||||
UnavailableSCCardDefinition{0x1D, 0x02AF, "HERO_6"},
|
||||
UnavailableSCCardDefinition{0x1E, 0x02B0, "DARK_1"},
|
||||
UnavailableSCCardDefinition{0x1F, 0x02B1, "DARK_2"},
|
||||
UnavailableSCCardDefinition{0x20, 0x02B2, "DARK_3"},
|
||||
UnavailableSCCardDefinition{0x21, 0x02B3, "DARK_4"},
|
||||
UnavailableSCCardDefinition{0x22, 0x02B4, "DARK_5"},
|
||||
UnavailableSCCardDefinition{0x23, 0x02B5, "DARK_6"},
|
||||
UnavailableSCCardDefinition{0x24, 0x029B, "LEUKON"},
|
||||
UnavailableSCCardDefinition{0x25, 0x029C, "CASTOR"},
|
||||
UnavailableSCCardDefinition{0x26, 0x029D, "POLLUX"},
|
||||
UnavailableSCCardDefinition{0x27, 0x029E, "AMPLUM"},
|
||||
UnavailableSCCardDefinition{0x28, 0x02BE, "CASTOR_USR"},
|
||||
UnavailableSCCardDefinition{0x29, 0x02BF, "POLLUX_USR"},
|
||||
};
|
||||
|
||||
string MapDefinition::str(const CardIndex* card_index, Language language) const {
|
||||
deque<string> lines;
|
||||
auto add_map = [&](const parray<parray<uint8_t, 0x10>, 0x10>& tiles) {
|
||||
@@ -2043,32 +2091,6 @@ string MapDefinition::str(const CardIndex* card_index, Language language) const
|
||||
lines.emplace_back(std::format(" map_category: {:02X}", this->map_category));
|
||||
lines.emplace_back(std::format(" cyber_block_type: {:02X}", this->cyber_block_type));
|
||||
lines.emplace_back(std::format(" a11: {:04X}", this->unknown_a11));
|
||||
static const array<const char*, 0x18> sc_card_entry_names = {
|
||||
"00 (Guykild; 0005)",
|
||||
"01 (Kylria; 0006)",
|
||||
"02 (Saligun; 0110)",
|
||||
"03 (Relmitos; 0111)",
|
||||
"04 (Kranz; 0002)",
|
||||
"05 (Sil'fer; 0004)",
|
||||
"06 (Ino'lis; 0003)",
|
||||
"07 (Viviana; 0112)",
|
||||
"08 (Teifu; 0113)",
|
||||
"09 (Orland; 0001)",
|
||||
"0A (Stella; 0114)",
|
||||
"0B (Glustar; 0115)",
|
||||
"0C (Hyze; 0117)",
|
||||
"0D (Rufina; 0118)",
|
||||
"0E (Peko; 0119)",
|
||||
"0F (Creinu; 011A)",
|
||||
"10 (Reiz; 011B)",
|
||||
"11 (Lura; 0007)",
|
||||
"12 (Break; 0008)",
|
||||
"13 (Rio; 011C)",
|
||||
"14 (Endu; 0116)",
|
||||
"15 (Memoru; 011D)",
|
||||
"16 (K.C.; 011E)",
|
||||
"17 (Ohgun; 011F)",
|
||||
};
|
||||
string unavailable_sc_cards = " unavailable_sc_cards: [";
|
||||
for (size_t z = 0; z < 0x18; z++) {
|
||||
if (this->unavailable_sc_cards[z] == 0xFFFF) {
|
||||
@@ -2077,10 +2099,11 @@ string MapDefinition::str(const CardIndex* card_index, Language language) const
|
||||
if (unavailable_sc_cards.size() > 25) {
|
||||
unavailable_sc_cards += ", ";
|
||||
}
|
||||
if (this->unavailable_sc_cards[z] >= sc_card_entry_names.size()) {
|
||||
if (this->unavailable_sc_cards[z] >= unavailable_sc_card_defs.size()) {
|
||||
unavailable_sc_cards += std::format("{:04X} (invalid)", this->unavailable_sc_cards[z]);
|
||||
} else {
|
||||
unavailable_sc_cards += sc_card_entry_names[this->unavailable_sc_cards[z]];
|
||||
const auto& def = unavailable_sc_card_defs[this->unavailable_sc_cards[z]];
|
||||
unavailable_sc_cards += std::format("{:04X} ({}; {:04X})", def.index, def.internal_name, def.card_id);
|
||||
}
|
||||
}
|
||||
unavailable_sc_cards += ']';
|
||||
@@ -2130,6 +2153,134 @@ string MapDefinition::str(const CardIndex* card_index, Language language) const
|
||||
return phosg::join(lines, "\n");
|
||||
}
|
||||
|
||||
phosg::JSON MapDefinition::json(Language language) const {
|
||||
phosg::JSON camera_zones_json = phosg::JSON::list();
|
||||
for (size_t team_id = 0; team_id < 2; team_id++) {
|
||||
phosg::JSON team_camera_zones_json = phosg::JSON::list();
|
||||
for (size_t camera_zone_id = 0; camera_zone_id < std::min<size_t>(this->num_camera_zones, 10); camera_zone_id++) {
|
||||
team_camera_zones_json.emplace_back(phosg::JSON::dict({
|
||||
{"Map", this->camera_zone_maps[team_id][camera_zone_id].json()},
|
||||
{"Spec", this->camera_zone_specs[team_id][camera_zone_id].json()},
|
||||
}));
|
||||
}
|
||||
camera_zones_json.emplace_back(std::move(team_camera_zones_json));
|
||||
}
|
||||
phosg::JSON overview_specs_json = phosg::JSON::list();
|
||||
for (size_t team_id = 0; team_id < 2; team_id++) {
|
||||
phosg::JSON team_overview_specs_json = phosg::JSON::list();
|
||||
for (size_t spec_id = 0; spec_id < 3; spec_id++) {
|
||||
team_overview_specs_json.emplace_back(this->overview_specs[spec_id][team_id].json());
|
||||
}
|
||||
overview_specs_json.emplace_back(std::move(team_overview_specs_json));
|
||||
}
|
||||
|
||||
auto overlay_state_json = phosg::JSON::dict({
|
||||
{"Tiles", this->overlay_state.tiles.json()},
|
||||
{"NTETrapTileColors", this->overlay_state.trap_tile_colors_nte.json()},
|
||||
{"NTETrapCardIDs", this->overlay_state.trap_card_ids_nte.json()},
|
||||
});
|
||||
|
||||
// Note: All typos/errors here are from AIPrm.dat
|
||||
static const array<string, 30> default_ai_names = {
|
||||
"Sample_Hunter", "Glustar", "Guykild", "Inolis", "Kilia", "Kranz", "Orland", "Relmitos", "Saligun", "Silfer",
|
||||
"Sample_Hunter", "Teifu", "Viviana", "Sample_Dark", "Break", "Creinu", "Endu", "Heiz", "KC", "Lura",
|
||||
"memoru", "Ohgun", "Peko", "Reiz", "Rio", "Rufina", "LKnight", "Boss_Castor", "Boss_Pollux", "Sample_Dark"};
|
||||
|
||||
auto npcs_json = phosg::JSON::list();
|
||||
for (size_t npc_index = 0; npc_index < 3; npc_index++) {
|
||||
auto npc = phosg::JSON::dict();
|
||||
|
||||
const auto& deck = this->npc_decks[npc_index];
|
||||
if (deck.deck_name.at(0)) {
|
||||
npc.emplace("Deck", deck.json(language));
|
||||
} else {
|
||||
npc.emplace("Deck", nullptr);
|
||||
}
|
||||
|
||||
int32_t entry_index = this->npc_ai_params_entry_index[npc_index];
|
||||
if (entry_index < 0) {
|
||||
const auto& ai_params = this->npc_ai_params[npc_index];
|
||||
if (ai_params.ai_name.at(0)) {
|
||||
npc.emplace("AIParams", ai_params.json(language));
|
||||
} else {
|
||||
npc.emplace("AIParams", nullptr);
|
||||
}
|
||||
} else if (static_cast<uint32_t>(entry_index) < default_ai_names.size()) {
|
||||
npc.emplace("AIParams", default_ai_names[entry_index]);
|
||||
} else {
|
||||
npc.emplace("AIParams", entry_index);
|
||||
}
|
||||
|
||||
auto dialogue_set_json = phosg::JSON::list();
|
||||
for (size_t ds_index = 0; ds_index < this->dialogue_sets[npc_index].size(); ds_index++) {
|
||||
const auto& ds = this->dialogue_sets[npc_index][ds_index];
|
||||
if (ds.when >= 0) {
|
||||
dialogue_set_json.emplace_back(ds.json(language));
|
||||
}
|
||||
}
|
||||
npc.emplace("DialogueSet", std::move(dialogue_set_json));
|
||||
}
|
||||
|
||||
auto unavailable_sc_cards_json = phosg::JSON::list();
|
||||
for (size_t z = 0; z < this->unavailable_sc_cards.size(); z++) {
|
||||
uint16_t index = this->unavailable_sc_cards[z];
|
||||
if (index < unavailable_sc_card_defs.size()) {
|
||||
const auto& def = unavailable_sc_card_defs[index];
|
||||
unavailable_sc_cards_json.emplace_back(phosg::JSON::dict({
|
||||
{"Index", def.index},
|
||||
{"Name", def.internal_name},
|
||||
{"CardID", def.card_id},
|
||||
}));
|
||||
} else if (index != 0xFFFF) {
|
||||
unavailable_sc_cards_json.emplace_back(index);
|
||||
}
|
||||
}
|
||||
|
||||
auto reward_card_ids_json = phosg::JSON::list();
|
||||
for (size_t z = 0; z < this->reward_card_ids.size(); z++) {
|
||||
if (this->reward_card_ids[z] != 0xFFFF) {
|
||||
reward_card_ids_json.emplace_back(this->reward_card_ids[z].load());
|
||||
}
|
||||
}
|
||||
|
||||
auto entry_states_json = phosg::JSON::list();
|
||||
for (size_t z = 0; z < this->entry_states.size(); z++) {
|
||||
entry_states_json.emplace_back(this->entry_states[z].json());
|
||||
}
|
||||
|
||||
return phosg::JSON::dict({
|
||||
{"Tag", this->tag.load()},
|
||||
{"MapNumber", this->map_number.load()},
|
||||
{"Width", this->width},
|
||||
{"Height", this->height},
|
||||
{"EnvironmentNumber", this->environment_number},
|
||||
{"EnvironmentName", name_for_environment_number(this->environment_number)},
|
||||
{"MapTiles", this->map_tiles.json()},
|
||||
{"StartTileDefinitions", this->start_tile_definitions.json()},
|
||||
{"CameraZones", std::move(camera_zones_json)},
|
||||
{"OverviewSpecs", std::move(overview_specs_json)},
|
||||
{"OverlayState", std::move(overlay_state_json)},
|
||||
{"Rules", this->default_rules.json()},
|
||||
{"Name", this->name.decode(language)},
|
||||
{"LocationName", this->location_name.decode(language)},
|
||||
{"QuestName", this->quest_name.decode(language)},
|
||||
{"Description", this->description.decode(language)},
|
||||
{"WorldMapCoords", phosg::JSON::list({this->map_x.load(), this->map_y.load()})},
|
||||
{"NPCs", std::move(npcs_json)},
|
||||
{"BeforeMessage", this->before_message.decode(language)},
|
||||
{"AfterMessage", this->after_message.decode(language)},
|
||||
{"DispatchMessage", this->dispatch_message.decode(language)},
|
||||
{"UnavailableSCCards", std::move(unavailable_sc_cards_json)},
|
||||
{"RewardCardIDs", std::move(reward_card_ids_json)},
|
||||
{"WinLevelOverride", this->win_level_override.load()},
|
||||
{"LossLevelOverride", this->loss_level_override.load()},
|
||||
{"FieldOffset", phosg::JSON::list({this->field_offset_x.load(), this->field_offset_y.load()})},
|
||||
{"MapCategory", this->map_category},
|
||||
{"CyberBlockType", this->cyber_block_type},
|
||||
{"EntryStates", std::move(entry_states_json)},
|
||||
});
|
||||
}
|
||||
|
||||
MapDefinitionTrial::MapDefinitionTrial(const MapDefinition& map)
|
||||
: tag(map.tag),
|
||||
map_number(map.map_number),
|
||||
|
||||
@@ -1257,7 +1257,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
|
||||
/* 58 */
|
||||
phosg::JSON json(Language language) const;
|
||||
} __packed_ws__(NPCDeck, 0x58);
|
||||
/* 1FE8 */ parray<NPCDeck, 3> npc_decks; // Unused if name[0] == 0
|
||||
/* 1FE8 */ parray<NPCDeck, 3> npc_decks; // Unused if deck_name[0] == 0
|
||||
|
||||
// These are not quite the same format as the entries in aiprm.dat. These entries are only used if the corresponding
|
||||
// NPC exists (if .name[0] is not 0) and if the corresponding entry in the npc_ai_params_entry_index is -1.
|
||||
@@ -1271,7 +1271,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
|
||||
/* 0114 */
|
||||
phosg::JSON json(Language language) const;
|
||||
} __packed_ws__(AIParams, 0x114);
|
||||
/* 20F0 */ parray<AIParams, 3> npc_ai_params; // Unused if name[0] == 0
|
||||
/* 20F0 */ parray<AIParams, 3> npc_ai_params; // Unused if ai_name[0] == 0
|
||||
|
||||
/* 242C */ parray<uint8_t, 8> unknown_a7;
|
||||
|
||||
|
||||
@@ -44,16 +44,14 @@ FileContentsCache::GetResult FileContentsCache::get_or_load(const char* name) {
|
||||
return this->get_or_load(string(name));
|
||||
}
|
||||
|
||||
shared_ptr<const FileContentsCache::File> FileContentsCache::get_or_throw(
|
||||
const std::string& name) {
|
||||
shared_ptr<const FileContentsCache::File> FileContentsCache::get_or_throw(const std::string& name) {
|
||||
auto throw_fn = +[](const std::string&) -> string {
|
||||
throw out_of_range("file missing from cache");
|
||||
};
|
||||
return this->get(name, throw_fn).file;
|
||||
}
|
||||
|
||||
shared_ptr<const FileContentsCache::File> FileContentsCache::get_or_throw(
|
||||
const char* name) {
|
||||
shared_ptr<const FileContentsCache::File> FileContentsCache::get_or_throw(const char* name) {
|
||||
return this->get_or_throw(string(name));
|
||||
}
|
||||
|
||||
|
||||
@@ -114,9 +114,9 @@ public:
|
||||
ThreadSafeFileCache& operator=(ThreadSafeFileCache&&) = delete;
|
||||
~ThreadSafeFileCache() = default;
|
||||
|
||||
// Warning: generate() is called while the lock is held for writing, so it
|
||||
// will block other threads.
|
||||
std::shared_ptr<const std::string> get(const std::string& name, std::function<std::shared_ptr<const std::string>(const std::string&)> generate);
|
||||
// generate() is called while the lock is held for writing, so it will block other threads.
|
||||
std::shared_ptr<const std::string> get(
|
||||
const std::string& name, std::function<std::shared_ptr<const std::string>(const std::string&)> generate);
|
||||
|
||||
private:
|
||||
std::shared_mutex lock;
|
||||
|
||||
@@ -105,7 +105,7 @@ string CompiledFunctionCode::generate_client_command(
|
||||
}
|
||||
|
||||
bool CompiledFunctionCode::is_big_endian() const {
|
||||
return this->arch == Architecture::POWERPC;
|
||||
return (this->arch == Architecture::POWERPC);
|
||||
}
|
||||
|
||||
static unordered_map<uint32_t, std::string> preprocess_function_code(const std::string& text) {
|
||||
@@ -483,8 +483,7 @@ bool FunctionCodeIndex::patch_menu_empty(uint32_t specific_version) const {
|
||||
|
||||
std::shared_ptr<const CompiledFunctionCode> FunctionCodeIndex::get_patch(
|
||||
const std::string& name, uint32_t specific_version) const {
|
||||
return this->name_and_specific_version_to_patch_function.at(
|
||||
std::format("{}-{:08X}", name, specific_version));
|
||||
return this->name_and_specific_version_to_patch_function.at(std::format("{}-{:08X}", name, specific_version));
|
||||
}
|
||||
|
||||
DOLFileIndex::DOLFileIndex(const string& directory) {
|
||||
|
||||
+2
-4
@@ -39,8 +39,7 @@ void GSLArchive::load_t() {
|
||||
}
|
||||
}
|
||||
|
||||
GSLArchive::GSLArchive(shared_ptr<const string> data, bool big_endian)
|
||||
: data(data) {
|
||||
GSLArchive::GSLArchive(shared_ptr<const string> data, bool big_endian) : data(data) {
|
||||
if (big_endian) {
|
||||
this->load_t<true>();
|
||||
} else {
|
||||
@@ -87,8 +86,7 @@ template <bool BE>
|
||||
string GSLArchive::generate_t(const unordered_map<string, string>& files) {
|
||||
phosg::StringWriter w;
|
||||
|
||||
// Make sure there's enough space for a blank header entry before any file's
|
||||
// data pages begin
|
||||
// Make sure there's enough space for a blank header entry before any file's data pages begin
|
||||
uint32_t data_start_offset = ((sizeof(GSLHeaderEntryT<BE>) * (files.size() + 1)) + 0x7FF) & (~0x7FF);
|
||||
uint32_t data_offset = data_start_offset;
|
||||
for (const auto& file : files) {
|
||||
|
||||
+8
-10
@@ -21,8 +21,7 @@
|
||||
using namespace std;
|
||||
using namespace std::placeholders;
|
||||
|
||||
GameServer::GameServer(shared_ptr<ServerState> state)
|
||||
: Server(state->io_context, "[GameServer] "), state(state) {}
|
||||
GameServer::GameServer(shared_ptr<ServerState> state) : Server(state->io_context, "[GameServer] "), state(state) {}
|
||||
|
||||
void GameServer::listen(
|
||||
const std::string& name,
|
||||
@@ -75,8 +74,8 @@ vector<shared_ptr<Client>> GameServer::get_clients_by_identifier(const string& i
|
||||
} catch (const invalid_argument&) {
|
||||
}
|
||||
|
||||
// TODO: It's kind of not great that we do a linear search here, but this is
|
||||
// only used in the shell, so it should be pretty rare.
|
||||
// TODO: It's kind of not great that we do a linear search here, but this is only used in the shell, so it should be
|
||||
// pretty rare.
|
||||
vector<shared_ptr<Client>> results;
|
||||
for (const auto& c : this->clients) {
|
||||
if (c->login && c->login->account->account_id == account_id_hex) {
|
||||
@@ -115,7 +114,8 @@ vector<shared_ptr<Client>> GameServer::get_clients_by_identifier(const string& i
|
||||
return results;
|
||||
}
|
||||
|
||||
shared_ptr<Client> GameServer::create_client(shared_ptr<GameServerSocket> listen_sock, asio::ip::tcp::socket&& client_sock) {
|
||||
shared_ptr<Client> GameServer::create_client(
|
||||
shared_ptr<GameServerSocket> listen_sock, asio::ip::tcp::socket&& client_sock) {
|
||||
uint32_t addr = ipv4_addr_for_asio_addr(client_sock.remote_endpoint().address());
|
||||
if (this->state->banned_ipv4_ranges->check(addr)) {
|
||||
if (client_sock.is_open()) {
|
||||
@@ -166,8 +166,7 @@ asio::awaitable<void> GameServer::handle_client(shared_ptr<Client> c) {
|
||||
asio::awaitable<void> GameServer::destroy_client(std::shared_ptr<Client> c) {
|
||||
this->log.info_f("Running cleanup tasks for {}", c->channel->name);
|
||||
|
||||
// The client may not actually be disconnected yet if an uncaught exception
|
||||
// occurred in a handler task
|
||||
// The client may not actually be disconnected yet if an uncaught exception occurred in a handler task
|
||||
c->channel->disconnect();
|
||||
|
||||
// Close the proxy session, if any
|
||||
@@ -184,9 +183,8 @@ asio::awaitable<void> GameServer::destroy_client(std::shared_ptr<Client> c) {
|
||||
this->log.warning_f("Error during client disconnect cleanup: {}", e.what());
|
||||
}
|
||||
|
||||
// Note: It's important to move the disconnect hooks out of the client here
|
||||
// because the hooks could modify c->disconnect_hooks while it's being
|
||||
// iterated here, which would invalidate these iterators.
|
||||
// Note: It's important to move the disconnect hooks out of the client here because the hooks could modify
|
||||
// c->disconnect_hooks while it's being iterated here, which would invalidate these iterators.
|
||||
unordered_map<string, function<void()>> hooks = std::move(c->disconnect_hooks);
|
||||
for (auto h_it : hooks) {
|
||||
try {
|
||||
|
||||
+2
-1
@@ -25,7 +25,8 @@ public:
|
||||
explicit GameServer(std::shared_ptr<ServerState> state);
|
||||
virtual ~GameServer() = default;
|
||||
|
||||
void listen(const std::string& name, const std::string& addr, uint16_t port, Version version, ServerBehavior initial_state);
|
||||
void listen(
|
||||
const std::string& name, const std::string& addr, uint16_t port, Version version, ServerBehavior initial_state);
|
||||
|
||||
std::shared_ptr<Client> connect_channel(std::shared_ptr<Channel> ch, uint16_t port, ServerBehavior initial_state);
|
||||
|
||||
|
||||
+50
-5
@@ -471,9 +471,10 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
||||
{"BattleStartTimeUsecs", ep3s->battle_start_usecs},
|
||||
{"TeamEXP", phosg::JSON::list({ep3s->team_exp[0], ep3s->team_exp[1]})},
|
||||
{"TeamDiceBonus", phosg::JSON::list({ep3s->team_dice_bonus[0], ep3s->team_dice_bonus[1]})},
|
||||
// TODO: Include information from these too?
|
||||
// std::shared_ptr<StateFlags> state_flags;
|
||||
// std::array<std::shared_ptr<PlayerState>, 4> player_states;
|
||||
});
|
||||
// std::shared_ptr<StateFlags> state_flags;
|
||||
// std::array<std::shared_ptr<PlayerState>, 4> player_states;
|
||||
lobby_json.emplace("Episode3BattleState", std::move(battle_state_json));
|
||||
} else {
|
||||
lobby_json.emplace("Episode3BattleState", nullptr);
|
||||
@@ -660,7 +661,51 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: Add /y/data/ep3/maps, /y/data/ep3/map/:map_number and /y/data/ep3/map/:map_number/raw
|
||||
this->router.add(HTTPRequest::Method::GET, "/y/data/ep3/maps", [this](ArgsT&& args) -> RetT {
|
||||
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> shared_ptr<phosg::JSON> {
|
||||
auto ret = make_shared<phosg::JSON>(phosg::JSON::dict());
|
||||
for (const auto& [map_number, map] : this->state->ep3_map_index->all_maps()) {
|
||||
auto languages_json = phosg::JSON::list();
|
||||
for (const auto& vm : map->all_versions()) {
|
||||
if (vm) {
|
||||
languages_json.emplace_back(name_for_language(vm->language));
|
||||
}
|
||||
}
|
||||
auto map_json = phosg::JSON::dict({
|
||||
{"Name", map->version(Language::ENGLISH)->map->name.decode(Language::ENGLISH)},
|
||||
{"VisibilityFlags", map->visibility_flags},
|
||||
{"Languages", std::move(languages_json)},
|
||||
});
|
||||
ret->emplace(std::format("{:08X}", map_number), std::move(map_json));
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
});
|
||||
|
||||
this->router.add(HTTPRequest::Method::GET, "/y/data/ep3/map/:map_number/:language", [this](ArgsT&& args) -> RetT {
|
||||
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> shared_ptr<phosg::JSON> {
|
||||
try {
|
||||
auto map = this->state->ep3_map_index->map_for_id(args.get_param<uint32_t>("map_number", true));
|
||||
auto vm = map->version(language_for_name(args.params.at("language")));
|
||||
return make_shared<phosg::JSON>(vm->map->json(vm->language));
|
||||
} catch (const std::out_of_range&) {
|
||||
throw HTTPError(404, "Map version does not exist");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this->router.add(HTTPRequest::Method::GET, "/y/data/ep3/map/:map_number/:language/raw", [this](ArgsT&& args) -> RetT {
|
||||
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> RawResponse {
|
||||
try {
|
||||
auto map = this->state->ep3_map_index->map_for_id(args.get_param<uint32_t>("map_number"));
|
||||
auto vm = map->version(language_for_name(args.params.at("language")));
|
||||
string data(reinterpret_cast<const char*>(vm->map.get()), sizeof(Episode3::MapDefinition));
|
||||
return RawResponse{.content_type = "application/octet-stream", .data = std::move(data)};
|
||||
} catch (const std::out_of_range&) {
|
||||
throw HTTPError(404, "Map version does not exist");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this->router.add(HTTPRequest::Method::GET, "/y/data/common-tables", [this](ArgsT&&) -> RetT {
|
||||
auto ret = make_shared<phosg::JSON>(phosg::JSON::list());
|
||||
@@ -731,8 +776,8 @@ asio::awaitable<void> HTTPServer::send_rare_drop_notification(shared_ptr<const p
|
||||
if (!this->rare_drop_subscribers.empty()) {
|
||||
string data = message->serialize();
|
||||
|
||||
// Make a copy of the rare drop subscribers set, so we can guarantee that
|
||||
// the client objects are all valid until this coroutine returns
|
||||
// Make a copy of the rare drop subscribers set, so we can guarantee that the client objects are all valid until
|
||||
// this coroutine returns
|
||||
unordered_set<shared_ptr<HTTPClient>> subscribers = this->rare_drop_subscribers;
|
||||
|
||||
size_t expected_results = subscribers.size();
|
||||
|
||||
+2
-1
@@ -32,6 +32,7 @@ protected:
|
||||
void require_GET(const HTTPRequest& req);
|
||||
phosg::JSON require_POST(const HTTPRequest& req);
|
||||
|
||||
virtual asio::awaitable<std::unique_ptr<HTTPResponse>> handle_request(std::shared_ptr<HTTPClient> c, HTTPRequest&& req);
|
||||
virtual asio::awaitable<std::unique_ptr<HTTPResponse>> handle_request(
|
||||
std::shared_ptr<HTTPClient> c, HTTPRequest&& req);
|
||||
virtual asio::awaitable<void> destroy_client(std::shared_ptr<HTTPClient> c);
|
||||
};
|
||||
|
||||
+1
-2
@@ -7,8 +7,7 @@
|
||||
using namespace std;
|
||||
|
||||
static inline uint16_t collapse_checksum(uint32_t sum) {
|
||||
// It's impossible for this to be necessary more than twice: the first
|
||||
// addition can carry out at most a single bit.
|
||||
// It's impossible for this to be necessary more than twice: the first addition can carry out at most a single bit.
|
||||
sum = (sum & 0xFFFF) + (sum >> 16);
|
||||
return (sum & 0xFFFF) + (sum >> 16);
|
||||
}
|
||||
|
||||
+54
-78
@@ -78,9 +78,8 @@ static string escape_hdlc_frame(const string& data, uint32_t escape_control_char
|
||||
return escape_hdlc_frame(data.data(), data.size(), escape_control_character_flags);
|
||||
}
|
||||
|
||||
// Note: these functions exist because seq nums are allowed to wrap around the
|
||||
// 32-bit integer space by design. We have to do the subtraction before the
|
||||
// comparison to allow integer overflow to occur if needed.
|
||||
// Note: these functions exist because seq nums are allowed to wrap around the 32-bit integer space by design. We have
|
||||
// to do the subtraction before the comparison to allow integer overflow to occur if needed.
|
||||
|
||||
static inline bool seq_num_less(uint32_t a, uint32_t b) {
|
||||
return (a - b) & 0x80000000;
|
||||
@@ -200,9 +199,8 @@ void IPSSChannel::disconnect() {
|
||||
}
|
||||
|
||||
void IPSSChannel::add_inbound_data(const void* data, size_t size) {
|
||||
// If recv_buf is not null, there is a coroutine waiting to receive data, and
|
||||
// inbound_data must be empty. Copy the data directly to the waiting
|
||||
// coroutine's buffer, and put the rest in this->inbound_data if needed.
|
||||
// If recv_buf is not null, there is a coroutine waiting to receive data, and inbound_data must be empty. Copy the
|
||||
// data directly to the waiting coroutine's buffer, and put the rest in this->inbound_data if needed.
|
||||
if (this->recv_buf) {
|
||||
size_t direct_size = min<size_t>(this->recv_buf_size, size);
|
||||
memcpy(this->recv_buf, data, direct_size);
|
||||
@@ -212,8 +210,7 @@ void IPSSChannel::add_inbound_data(const void* data, size_t size) {
|
||||
this->recv_buf = this->recv_buf_size ? (reinterpret_cast<uint8_t*>(this->recv_buf) + direct_size) : nullptr;
|
||||
}
|
||||
|
||||
// If there is still data left after the above, add it to the pending inbound
|
||||
// data buffer
|
||||
// If there is still data left after the above, add it to the pending inbound data buffer
|
||||
if (size > 0) {
|
||||
this->inbound_data.emplace_back(reinterpret_cast<const char*>(data), size);
|
||||
}
|
||||
@@ -239,10 +236,9 @@ void IPSSChannel::send_raw(string&& data) {
|
||||
conn->outbound_data_bytes += data.size();
|
||||
conn->outbound_data.emplace_back(std::move(data));
|
||||
|
||||
// If we're already waiting for an ACK from the remote client, don't send
|
||||
// another PSH right now - we will either send another PSH when we receive
|
||||
// the ACK or will retry sending the PSH soon (which will then include the
|
||||
// new data, if it's within the MTU from the last acked sequence number).
|
||||
// If we're already waiting for an ACK from the remote client, don't send another PSH right now - we will either send
|
||||
// another PSH when we receive the ACK or will retry sending the PSH soon (which will then include the new data, if
|
||||
// it's within the MTU from the last acked sequence number).
|
||||
if (!conn->awaiting_ack) {
|
||||
sim->schedule_send_pending_push_frame(conn, 0);
|
||||
}
|
||||
@@ -270,8 +266,7 @@ asio::awaitable<void> IPSSChannel::recv_raw(void* data, size_t size) {
|
||||
}
|
||||
}
|
||||
|
||||
// If there's still more data to read, block until it's available
|
||||
// (add_inbound_data is responsible for waking this coroutine)
|
||||
// If there's still more data to read, block until it's available (add_inbound_data will wake this coroutine)
|
||||
if (size > 0) {
|
||||
this->recv_buf = data;
|
||||
this->recv_buf_size = size;
|
||||
@@ -304,9 +299,8 @@ void IPStackSimulator::listen(const std::string& name, const string& addr, int p
|
||||
}
|
||||
|
||||
uint32_t IPStackSimulator::connect_address_for_remote_address(uint32_t remote_addr) {
|
||||
// Use an address not on the same subnet as the client, so that PSO Plus and
|
||||
// Episode III will think they're talking to a remote network and won't
|
||||
// reject the connection.
|
||||
// Use an address not on the same subnet as the client, so that PSO Plus and Episode III will think they're talking
|
||||
// to a remote network and won't reject the connection.
|
||||
return ((remote_addr & 0xFF000000) == 0x23000000) ? 0x24242424 : 0x23232323;
|
||||
}
|
||||
|
||||
@@ -555,10 +549,9 @@ asio::awaitable<void> IPStackSimulator::on_client_lcp_frame(shared_ptr<IPSSClien
|
||||
throw runtime_error("unknown LCP option");
|
||||
}
|
||||
}
|
||||
// Technically, we should implement the LCP state machine, but I'm too
|
||||
// lazy to do this right now. In our situation, it should suffice to
|
||||
// simply always send a Configure-Request to the client with a magic
|
||||
// number not equal to the one we received.
|
||||
// Technically, we should implement the LCP state machine, but I'm too lazy to do this right now. In our
|
||||
// situation, it should suffice to simply always send a Configure-Request to the client with a magic number not
|
||||
// equal to the one we received.
|
||||
phosg::StringWriter opts_w;
|
||||
opts_w.put_u8(0x01); // Maximum receive unit
|
||||
opts_w.put_u8(0x04);
|
||||
@@ -700,8 +693,7 @@ asio::awaitable<void> IPStackSimulator::on_client_ipcp_frame(shared_ptr<IPSSClie
|
||||
} else if ((remote_ip != 0x1E1E1E1E) ||
|
||||
(remote_primary_dns != 0x23232323) ||
|
||||
(remote_secondary_dns != 0x24242424)) {
|
||||
// Send a Configure-Nak if the client's request doesn't exactly match
|
||||
// what we want them to use.
|
||||
// Send a Configure-Nak if the client's request doesn't exactly match what we want them to use.
|
||||
phosg::StringWriter opts_w;
|
||||
opts_w.put_u8(0x03); // IP address
|
||||
opts_w.put_u8(0x06);
|
||||
@@ -725,8 +717,7 @@ asio::awaitable<void> IPStackSimulator::on_client_ipcp_frame(shared_ptr<IPSSClie
|
||||
} else { // Options OK
|
||||
c->ipv4_addr = remote_ip;
|
||||
|
||||
// As with LCP, we technically should implement the state machine, but I
|
||||
// continue to be lazy.
|
||||
// As with LCP, we technically should implement the state machine, but I continue to be lazy.
|
||||
phosg::StringWriter opts_w;
|
||||
opts_w.put_u8(0x03); // IP address
|
||||
opts_w.put_u8(0x06);
|
||||
@@ -806,15 +797,15 @@ asio::awaitable<void> IPStackSimulator::on_client_arp_frame(shared_ptr<IPSSClien
|
||||
});
|
||||
|
||||
// The incoming payload is:
|
||||
// uint8_t src_mac[6]; // MAC address of client
|
||||
// uint8_t src_ip[4]; // IP address of client
|
||||
// uint8_t dest_mac[6]; // MAC address of host (all zeroes)
|
||||
// uint8_t dest_ip[4]; // IP address of host
|
||||
// uint8_t src_mac[6]; // MAC address of client
|
||||
// uint8_t src_ip[4]; // IP address of client
|
||||
// uint8_t dest_mac[6]; // MAC address of host (all zeroes)
|
||||
// uint8_t dest_ip[4]; // IP address of host
|
||||
// The outgoing payload is:
|
||||
// uint8_t dest_mac[6]; // MAC address of host (from configuration)
|
||||
// uint8_t dest_ip[4]; // IP address of host
|
||||
// uint8_t src_mac[6]; // MAC address of client
|
||||
// uint8_t src_ip[4]; // IP address of client
|
||||
// uint8_t dest_mac[6]; // MAC address of host (from configuration)
|
||||
// uint8_t dest_ip[4]; // IP address of host
|
||||
// uint8_t src_mac[6]; // MAC address of client
|
||||
// uint8_t src_ip[4]; // IP address of client
|
||||
const char* payload_bytes = reinterpret_cast<const char*>(fi.payload);
|
||||
w.write(this->host_mac_address_bytes.data(), 6);
|
||||
w.write(payload_bytes + 16, 4);
|
||||
@@ -826,9 +817,7 @@ asio::awaitable<void> IPStackSimulator::on_client_arp_frame(shared_ptr<IPSSClien
|
||||
asio::awaitable<void> IPStackSimulator::on_client_udp_frame(shared_ptr<IPSSClient> c, const FrameInfo& fi) {
|
||||
// We only implement DHCP and newserv's DNS server here.
|
||||
|
||||
// Every received UDP packet will elicit exactly one UDP response from
|
||||
// newserv, so we prepare the response headers in advance
|
||||
|
||||
// Every received UDP packet will elicit exactly one UDP response from newserv, so we prepare the headers in advance
|
||||
IPv4Header r_ipv4;
|
||||
r_ipv4.version_ihl = 0x45;
|
||||
r_ipv4.tos = 0;
|
||||
@@ -888,8 +877,8 @@ asio::awaitable<void> IPStackSimulator::on_client_udp_frame(shared_ptr<IPSSClien
|
||||
// Populate the client's addresses
|
||||
c->mac_addr = dhcp.client_hardware_address.data();
|
||||
c->ipv4_addr = 0x0A000105; // 10.0.1.5
|
||||
// In this case, the client doesn't know its IPv4 address or ours yet,
|
||||
// so we overwrite the existing fields with the appropriate addresses.
|
||||
// In this case, the client doesn't know its IPv4 address or ours yet, so we overwrite the existing fields with
|
||||
// the appropriate addresses.
|
||||
r_ipv4.src_addr = 0x0A000101; // 10.0.1.1
|
||||
r_ipv4.dest_addr = c->ipv4_addr;
|
||||
|
||||
@@ -1005,8 +994,8 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
|
||||
}
|
||||
|
||||
if (fi.tcp->flags & TCPHeader::Flag::SYN) {
|
||||
// We never make connections back to the client, so we should never receive
|
||||
// a SYN+ACK. Essentially, no other flags should be set in any received SYN.
|
||||
// We never make connections back to the client, so we should never receive a SYN+ACK. Essentially, no other flags
|
||||
// should be set in any received SYN.
|
||||
if ((fi.tcp->flags & 0x0FFF) != TCPHeader::Flag::SYN) {
|
||||
throw runtime_error("TCP SYN contains extra flags");
|
||||
}
|
||||
@@ -1081,8 +1070,7 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
|
||||
if (!conn->awaiting_first_ack) {
|
||||
throw logic_error("SYN received on already-open connection after initial phase");
|
||||
}
|
||||
// TODO: We should check the syn/ack numbers here instead of just assuming
|
||||
// they're correct
|
||||
// TODO: We should check the syn/ack numbers here instead of just assuming they're correct
|
||||
conn_str = this->str_for_tcp_connection(c, conn);
|
||||
this->log.debug_f("Client resent SYN for TCP connection {}", conn_str);
|
||||
}
|
||||
@@ -1093,8 +1081,7 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
|
||||
conn_str, conn->acked_server_seq, conn->next_client_seq);
|
||||
|
||||
} else {
|
||||
// This frame isn't a SYN, so a connection object should already exist;
|
||||
// ignore the frame if there's no connection
|
||||
// This frame isn't a SYN, so a connection object should already exist; ignore the frame if there's no connection
|
||||
uint64_t key = this->tcp_conn_key_for_client_frame(fi);
|
||||
auto conn_it = c->tcp_connections.find(key);
|
||||
if (conn_it == c->tcp_connections.end()) {
|
||||
@@ -1158,24 +1145,20 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
|
||||
conn->server_channel.reset();
|
||||
}
|
||||
|
||||
// TODO: Are we supposed to send a response to an RST? Here we do, and the
|
||||
// client probably just ignores it anyway
|
||||
// TODO: Are we supposed to send a response to an RST? Here we do, and the client probably just ignores it anyway
|
||||
co_await this->send_tcp_frame(c, conn, fi.tcp->flags & (TCPHeader::Flag::RST | TCPHeader::Flag::FIN));
|
||||
|
||||
// Delete the connection object. The unique_ptr destructor flushes the
|
||||
// bufferevent, and thereby sends an EOF to the server's end.
|
||||
// Delete the connection object. The unique_ptr destructor flushes the bufferevent, and thereby sends an EOF to
|
||||
// the server's end.
|
||||
c->tcp_connections.erase(key);
|
||||
conn_valid = false;
|
||||
|
||||
} else if (fi.payload_size != 0) {
|
||||
// Note: The PSH flag isn't required to be set on all packets that
|
||||
// contain data. The PSH flag just means "tell the application that data
|
||||
// is available", so some senders only set the PSH flag on the last frame
|
||||
// of a large segment of data, since the application wouldn't be able to
|
||||
// process the segment until all of it is available. newserv can handle
|
||||
// incomplete commands, so we just ignore the PSH flag and forward any
|
||||
// data to the server immediately (hence the lack of a flag check in the
|
||||
// above condition).
|
||||
// Note: The PSH flag isn't required to be set on all packets that contain data. The PSH flag just means "tell
|
||||
// the application that data is available", so some senders only set the PSH flag on the last frame of a large
|
||||
// segment of data, since the application wouldn't be able to process the segment until all of it is available.
|
||||
// newserv can handle incomplete commands, so we just ignore the PSH flag and forward any data to the server
|
||||
// immediately (hence the lack of a flag check in the above condition).
|
||||
|
||||
string conn_str = this->log.should_log(phosg::LogLevel::L_WARNING)
|
||||
? this->str_for_tcp_connection(c, conn)
|
||||
@@ -1186,8 +1169,8 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
|
||||
payload_skip_bytes = 0;
|
||||
|
||||
} else if (seq_num_less(fi.tcp->seq_num, conn->next_client_seq)) {
|
||||
// If the frame overlaps an existing boundary, we'll accept some of the
|
||||
// data; otherwise we'll ignore it entirely (but still send an ACK)
|
||||
// If the frame overlaps an existing boundary, we'll accept some of the data; otherwise we'll ignore it
|
||||
// entirely (but still send an ACK)
|
||||
uint32_t end_seq = fi.tcp->seq_num + fi.payload_size;
|
||||
if (seq_num_less_or_equal(end_seq, conn->next_client_seq)) { // Fully "in the past"
|
||||
payload_skip_bytes = fi.payload_size;
|
||||
@@ -1196,9 +1179,8 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
|
||||
}
|
||||
|
||||
} else {
|
||||
// Payload is in the future - we must have missed a data frame. We'll
|
||||
// ignore it (but warn) and send an ACK later, and the client should
|
||||
// retransmit the lost data
|
||||
// Payload is in the future - we must have missed a data frame. We'll ignore it (but warn) and send an ACK
|
||||
// later, and the client should retransmit the lost data
|
||||
this->log.warning_f(
|
||||
"Client sent out-of-order sequence number (expected {:08X}, received {:08X}, 0x{:X} data bytes)",
|
||||
conn->next_client_seq, fi.tcp->seq_num, fi.payload_size);
|
||||
@@ -1288,10 +1270,8 @@ asio::awaitable<void> IPStackSimulator::send_pending_push_frame(
|
||||
|
||||
size_t bytes_to_send = min<size_t>(conn->outbound_data_bytes, conn->next_push_max_frame_size);
|
||||
if (c->protocol == VirtualNetworkProtocol::HDLC_TAPSERVER) {
|
||||
// There is a bug in Dolphin's modem implementation (which I wrote, so it's
|
||||
// my fault) that causes commands to be dropped when too much data is sent
|
||||
// at once. To work around this, we only send up to 200 bytes in each push
|
||||
// frame.
|
||||
// There is a bug in Dolphin's modem implementation (which I wrote, so it's my fault) that causes commands to be
|
||||
// dropped when too much data is sent. To work around this, we only send up to 200 bytes in each push frame.
|
||||
bytes_to_send = min<size_t>(bytes_to_send, 200);
|
||||
}
|
||||
|
||||
@@ -1300,23 +1280,20 @@ asio::awaitable<void> IPStackSimulator::send_pending_push_frame(
|
||||
|
||||
conn->linearize_outbound_data(bytes_to_send);
|
||||
if (conn->outbound_data.empty() || conn->outbound_data.front().size() < bytes_to_send) {
|
||||
// This should never happen because bytes_to_send should always be less
|
||||
// than or equal to conn->outbound_data_bytes, which itself should be equal
|
||||
// to the number of bytes that can be linearized
|
||||
// This should never happen because bytes_to_send should always be less than or equal to conn->outbound_data_bytes,
|
||||
// which itself should be equal to the number of bytes that can be linearized
|
||||
throw logic_error("failed to linearize enough bytes before sending TCP PSH");
|
||||
}
|
||||
co_await this->send_tcp_frame(c, conn, TCPHeader::Flag::PSH, conn->outbound_data.front().data(), bytes_to_send);
|
||||
conn->awaiting_ack = true;
|
||||
|
||||
// Schedule the timer for sending another PSH, in case the client doesn't
|
||||
// respond quickly enough
|
||||
// Schedule the timer for sending another PSH, in case the client doesn't respond quickly enough
|
||||
this->schedule_send_pending_push_frame(conn, conn->resend_push_usecs);
|
||||
|
||||
// If the client isn't responding to our PSHes, back off exponentially up to
|
||||
// a limit of 5 seconds between PSH frames. This window is reset when
|
||||
// acked_server_seq changes (that is, when the client has acknowledged any new
|
||||
// data). It seems some situations cause GameCube clients to drop packets more
|
||||
// often; to alleviate this, we also try to resend less data.
|
||||
// If the client isn't responding to our PSHes, back off exponentially up to a limit of 5 seconds between PSH frames.
|
||||
// This window is reset when acked_server_seq changes (that is, when the client has acknowledged any new data). It
|
||||
// seems some situations cause GameCube clients to drop packets more often; to alleviate this, we also try to resend
|
||||
// less data.
|
||||
conn->resend_push_usecs *= 2;
|
||||
if (conn->resend_push_usecs > 5000000) {
|
||||
conn->resend_push_usecs = 5000000;
|
||||
@@ -1401,9 +1378,8 @@ asio::awaitable<void> IPStackSimulator::open_server_connection(
|
||||
|
||||
asio::awaitable<void> IPStackSimulator::close_tcp_connection(
|
||||
shared_ptr<IPSSClient> c, shared_ptr<IPSSClient::TCPConnection> conn) {
|
||||
// Send an RST to the client. This is kind of rude (we really should use FIN)
|
||||
// but the PSO network stack always sends an RST to us when disconnecting, so
|
||||
// whatever
|
||||
// Send an RST to the client. This is kind of rude (we really should use FIN) but the PSO network stack always sends
|
||||
// an RST to us when disconnecting, so whatever
|
||||
co_await this->send_tcp_frame(c, conn, TCPHeader::Flag::RST);
|
||||
|
||||
// Delete the connection object
|
||||
|
||||
+4
-1
@@ -20,7 +20,10 @@ enum class GVRDataFormat : uint8_t {
|
||||
};
|
||||
|
||||
std::string encode_gvm(
|
||||
const phosg::ImageRGBA8888N& img, GVRDataFormat data_format, const std::string& internal_name, uint32_t global_index);
|
||||
const phosg::ImageRGBA8888N& img,
|
||||
GVRDataFormat data_format,
|
||||
const std::string& internal_name,
|
||||
uint32_t global_index);
|
||||
phosg::ImageRGB888 decode_fon(const std::string& data, size_t width);
|
||||
std::string encode_fon(const phosg::ImageRGB888& img);
|
||||
|
||||
|
||||
@@ -164,8 +164,7 @@ string IntegralExpression::UnaryOperatorNode::str() const {
|
||||
}
|
||||
}
|
||||
|
||||
IntegralExpression::FlagLookupNode::FlagLookupNode(uint16_t flag_index)
|
||||
: flag_index(flag_index) {}
|
||||
IntegralExpression::FlagLookupNode::FlagLookupNode(uint16_t flag_index) : flag_index(flag_index) {}
|
||||
|
||||
bool IntegralExpression::FlagLookupNode::operator==(const Node& other) const {
|
||||
try {
|
||||
@@ -187,10 +186,8 @@ string IntegralExpression::FlagLookupNode::str() const {
|
||||
return std::format("F_{:04X}", this->flag_index);
|
||||
}
|
||||
|
||||
IntegralExpression::ChallengeCompletionLookupNode::ChallengeCompletionLookupNode(
|
||||
Episode episode, uint8_t stage_index)
|
||||
: episode(episode),
|
||||
stage_index(stage_index) {}
|
||||
IntegralExpression::ChallengeCompletionLookupNode::ChallengeCompletionLookupNode(Episode episode, uint8_t stage_index)
|
||||
: episode(episode), stage_index(stage_index) {}
|
||||
|
||||
bool IntegralExpression::ChallengeCompletionLookupNode::operator==(const Node& other) const {
|
||||
try {
|
||||
@@ -217,8 +214,7 @@ string IntegralExpression::ChallengeCompletionLookupNode::str() const {
|
||||
return std::format("CC_{}_{}", abbreviation_for_episode(this->episode), static_cast<uint8_t>(this->stage_index + 1));
|
||||
}
|
||||
|
||||
IntegralExpression::TeamRewardLookupNode::TeamRewardLookupNode(const string& reward_name)
|
||||
: reward_name(reward_name) {}
|
||||
IntegralExpression::TeamRewardLookupNode::TeamRewardLookupNode(const string& reward_name) : reward_name(reward_name) {}
|
||||
|
||||
bool IntegralExpression::TeamRewardLookupNode::operator==(const Node& other) const {
|
||||
try {
|
||||
@@ -310,9 +306,8 @@ unique_ptr<const IntegralExpression::Node> IntegralExpression::parse_expr(string
|
||||
text = text.substr(0, text.size() - 1);
|
||||
}
|
||||
if (text.at(0) == '(' && text.at(text.size() - 1) == ')') {
|
||||
// It doesn't suffice to just check the first ant last characters, since
|
||||
// text could be like "(a) && (b)". Instead, we ignore the first and last
|
||||
// characters, and don't strip anything if the internal parentheses are
|
||||
// It doesn't suffice to just check the first and last characters, since text could be like "(a) && (b)".
|
||||
// Instead, we ignore the first and last characters, and don't strip anything if the internal parentheses are
|
||||
// unbalanced.
|
||||
size_t paren_level = 1;
|
||||
for (size_t z = 1; z < text.size() - 1; z++) {
|
||||
@@ -363,10 +358,8 @@ unique_ptr<const IntegralExpression::Node> IntegralExpression::parse_expr(string
|
||||
}
|
||||
if (!paren_level) {
|
||||
for (const auto& oper : operators) {
|
||||
// Awful hack (because I'm too lazy to add a tokenization step): if
|
||||
// the operator is followed or preceded by another copy of itself,
|
||||
// don't match it (this prevents us from matching & when the token is
|
||||
// actually &&)
|
||||
// Awful hack (because I'm too lazy to add a tokenization step): if the operator is followed or preceded by
|
||||
// another copy of itself, don't match it (this prevents us from matching & when the token is actually &&)
|
||||
if ((text.size() > z + oper.first.size()) &&
|
||||
((z < oper.first.size()) || (text.compare(z - oper.first.size(), oper.first.size(), oper.first) != 0)) &&
|
||||
(text.compare(z, oper.first.size(), oper.first) == 0) &&
|
||||
|
||||
+1
-1
@@ -507,7 +507,7 @@ protected:
|
||||
// identifies the entity on all PSO versions. (These are the IDs which newserv formats as K-XXX, E-XXX, and W-XXX,
|
||||
// though they are offset as needed for floors beyond the first.)
|
||||
// There must not be any random enemy sections in any MapFile passed to SuperMap; to resolve them,
|
||||
// materialize_random_sections must be called on all MapFiles first. This generally only is needed in Challenge mode.
|
||||
// materialize_random_sections must be called on all MapFiles first. This is generally only needed in Challenge mode.
|
||||
|
||||
class SuperMap {
|
||||
public:
|
||||
|
||||
@@ -566,6 +566,38 @@ Language language_for_char(char language_char) {
|
||||
}
|
||||
}
|
||||
|
||||
Language language_for_name(const string& name) {
|
||||
if (name.size() == 1) {
|
||||
return language_for_char(name[0]);
|
||||
}
|
||||
string lower_name = phosg::tolower(name);
|
||||
if (lower_name == "japanese") {
|
||||
return Language::JAPANESE;
|
||||
}
|
||||
if (lower_name == "english") {
|
||||
return Language::ENGLISH;
|
||||
}
|
||||
if (lower_name == "german") {
|
||||
return Language::GERMAN;
|
||||
}
|
||||
if (lower_name == "french") {
|
||||
return Language::FRENCH;
|
||||
}
|
||||
if (lower_name == "spanish") {
|
||||
return Language::SPANISH;
|
||||
}
|
||||
if (lower_name == "simplified chinese") {
|
||||
return Language::SIMPLIFIED_CHINESE;
|
||||
}
|
||||
if (lower_name == "traditional chinese") {
|
||||
return Language::TRADITIONAL_CHINESE;
|
||||
}
|
||||
if (lower_name == "korean") {
|
||||
return Language::KOREAN;
|
||||
}
|
||||
throw runtime_error("unknown language");
|
||||
}
|
||||
|
||||
const vector<string> tech_id_to_name = {
|
||||
"foie", "gifoie", "rafoie",
|
||||
"barta", "gibarta", "rabarta",
|
||||
|
||||
@@ -91,6 +91,7 @@ char abbreviation_for_difficulty(Difficulty difficulty);
|
||||
const char* name_for_language(Language language);
|
||||
char char_for_language(Language language);
|
||||
Language language_for_char(char language_char);
|
||||
Language language_for_name(const std::string& name);
|
||||
|
||||
extern const std::vector<const char*> name_for_mag_color;
|
||||
extern const std::unordered_map<std::string, uint8_t> mag_color_for_name;
|
||||
|
||||
+15
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <initializer_list>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/JSON.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
@@ -303,6 +304,20 @@ struct parray {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
phosg::JSON json() const {
|
||||
auto ret = phosg::JSON::list();
|
||||
for (size_t z = 0; z < Count; z++) {
|
||||
if constexpr (requires(ItemT x) { x.json(); }) {
|
||||
ret.emplace_back(this->items[z].json());
|
||||
} else if constexpr (requires(ItemT x) { x.load(); }) {
|
||||
ret.emplace_back(this->items[z].load());
|
||||
} else {
|
||||
ret.emplace_back(this->items[z]);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
} __attribute__((packed));
|
||||
|
||||
template <typename ItemT, size_t Count>
|
||||
|
||||
Reference in New Issue
Block a user