add HTTP server
This commit is contained in:
@@ -88,6 +88,7 @@ set(SOURCES
|
||||
src/FunctionCompiler.cc
|
||||
src/GSLArchive.cc
|
||||
src/GVMEncoder.cc
|
||||
src/HTTPServer.cc
|
||||
src/IPFrameInfo.cc
|
||||
src/IPStackSimulator.cc
|
||||
src/ItemCreator.cc
|
||||
|
||||
@@ -914,7 +914,7 @@ void Card::compute_action_chain_results(bool apply_action_conditions, bool ignor
|
||||
this->action_chain.chain.tp_effect_bonus = 0;
|
||||
|
||||
log.debug("(initial) medium=%s, strike_count=%hhu, ap_effect_bonus=%hhd, tp_effect_bonus=%hhd",
|
||||
name_for_attack_medium(this->action_chain.chain.attack_medium),
|
||||
name_for_enum(this->action_chain.chain.attack_medium),
|
||||
this->action_chain.chain.strike_count,
|
||||
this->action_chain.chain.ap_effect_bonus,
|
||||
this->action_chain.chain.tp_effect_bonus);
|
||||
|
||||
@@ -589,7 +589,7 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit
|
||||
break;
|
||||
trial_unimplemented:
|
||||
default:
|
||||
log.debug("%s: no adjustments for condition type", name_for_condition_type(cond_type));
|
||||
log.debug("%s: no adjustments for condition type", name_for_enum(cond_type));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1777,7 +1777,7 @@ bool CardSpecial::execute_effect(
|
||||
auto log = s->log_stack(string_printf("execute_effect(@%04hX #%04hX): ", card->get_card_ref(), card->get_card_id()));
|
||||
{
|
||||
string cond_str = cond.str(s);
|
||||
log.debug("cond=%s, card=@%04hX, expr_value=%hd, unknown_p5=%hd, cond_type=%s, unknown_p7=%" PRIu32 ", attacker_card_ref=@%04hX", cond_str.c_str(), ref_for_card(card), expr_value, unknown_p5, name_for_condition_type(cond_type), unknown_p7, attacker_card_ref);
|
||||
log.debug("cond=%s, card=@%04hX, expr_value=%hd, unknown_p5=%hd, cond_type=%s, unknown_p7=%" PRIu32 ", attacker_card_ref=@%04hX", cond_str.c_str(), ref_for_card(card), expr_value, unknown_p5, name_for_enum(cond_type), unknown_p7, attacker_card_ref);
|
||||
}
|
||||
bool is_nte = s->options.is_nte();
|
||||
|
||||
@@ -2841,7 +2841,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
|
||||
AttackMedium attack_medium = card2
|
||||
? card2->action_chain.chain.attack_medium
|
||||
: AttackMedium::UNKNOWN;
|
||||
log.debug("attack_medium=%s", name_for_attack_medium(attack_medium));
|
||||
log.debug("attack_medium=%s", name_for_enum(attack_medium));
|
||||
|
||||
auto add_card_refs = [&](const vector<uint16_t>& result_card_refs) -> void {
|
||||
for (uint16_t result_card_ref : result_card_refs) {
|
||||
|
||||
+275
-221
@@ -80,168 +80,6 @@ const char* name_for_link_color(uint8_t color) {
|
||||
}
|
||||
}
|
||||
|
||||
const char* name_for_card_type(CardType type) {
|
||||
switch (type) {
|
||||
case CardType::HUNTERS_SC:
|
||||
return "HUNTERS_SC";
|
||||
case CardType::ARKZ_SC:
|
||||
return "ARKZ_SC";
|
||||
case CardType::ITEM:
|
||||
return "ITEM";
|
||||
case CardType::CREATURE:
|
||||
return "CREATURE";
|
||||
case CardType::ACTION:
|
||||
return "ACTION";
|
||||
case CardType::ASSIST:
|
||||
return "ASSIST";
|
||||
case CardType::INVALID_FF:
|
||||
return "INVALID_FF";
|
||||
default:
|
||||
throw invalid_argument("invalid card type");
|
||||
}
|
||||
}
|
||||
|
||||
const char* name_for_card_class(CardClass cc) {
|
||||
switch (cc) {
|
||||
case CardClass::HU_SC:
|
||||
return "HU_SC";
|
||||
case CardClass::RA_SC:
|
||||
return "RA_SC";
|
||||
case CardClass::FO_SC:
|
||||
return "FO_SC";
|
||||
case CardClass::NATIVE_CREATURE:
|
||||
return "NATIVE_CREATURE";
|
||||
case CardClass::A_BEAST_CREATURE:
|
||||
return "A_BEAST_CREATURE";
|
||||
case CardClass::MACHINE_CREATURE:
|
||||
return "MACHINE_CREATURE";
|
||||
case CardClass::DARK_CREATURE:
|
||||
return "DARK_CREATURE";
|
||||
case CardClass::GUARD_ITEM:
|
||||
return "GUARD_ITEM";
|
||||
case CardClass::MAG_ITEM:
|
||||
return "MAG_ITEM";
|
||||
case CardClass::SWORD_ITEM:
|
||||
return "SWORD_ITEM";
|
||||
case CardClass::GUN_ITEM:
|
||||
return "GUN_ITEM";
|
||||
case CardClass::CANE_ITEM:
|
||||
return "CANE_ITEM";
|
||||
case CardClass::ATTACK_ACTION:
|
||||
return "ATTACK_ACTION";
|
||||
case CardClass::DEFENSE_ACTION:
|
||||
return "DEFENSE_ACTION";
|
||||
case CardClass::TECH:
|
||||
return "TECH";
|
||||
case CardClass::PHOTON_BLAST:
|
||||
return "PHOTON_BLAST";
|
||||
case CardClass::CONNECT_ONLY_ATTACK_ACTION:
|
||||
return "CONNECT_ONLY_ATTACK_ACTION";
|
||||
case CardClass::BOSS_ATTACK_ACTION:
|
||||
return "BOSS_ATTACK_ACTION";
|
||||
case CardClass::BOSS_TECH:
|
||||
return "BOSS_TECH";
|
||||
case CardClass::ASSIST:
|
||||
return "ASSIST";
|
||||
default:
|
||||
throw invalid_argument("invalid card class");
|
||||
}
|
||||
}
|
||||
|
||||
const char* name_for_attack_medium(AttackMedium medium) {
|
||||
switch (medium) {
|
||||
case AttackMedium::UNKNOWN:
|
||||
return "UNKNOWN";
|
||||
case AttackMedium::PHYSICAL:
|
||||
return "PHYSICAL";
|
||||
case AttackMedium::TECH:
|
||||
return "TECH";
|
||||
case AttackMedium::UNKNOWN_03:
|
||||
return "UNKNOWN_03";
|
||||
case AttackMedium::INVALID_FF:
|
||||
return "INVALID_FF";
|
||||
default:
|
||||
return "__INVALID__";
|
||||
}
|
||||
}
|
||||
|
||||
const char* name_for_criterion_code(CriterionCode code) {
|
||||
switch (code) {
|
||||
case CriterionCode::NONE:
|
||||
return "NONE";
|
||||
case CriterionCode::HU_CLASS_SC:
|
||||
return "HU_CLASS_SC";
|
||||
case CriterionCode::RA_CLASS_SC:
|
||||
return "RA_CLASS_SC";
|
||||
case CriterionCode::FO_CLASS_SC:
|
||||
return "FO_CLASS_SC";
|
||||
case CriterionCode::SAME_TEAM:
|
||||
return "SAME_TEAM";
|
||||
case CriterionCode::SAME_PLAYER:
|
||||
return "SAME_PLAYER";
|
||||
case CriterionCode::SAME_TEAM_NOT_SAME_PLAYER:
|
||||
return "SAME_TEAM_NOT_SAME_PLAYER";
|
||||
case CriterionCode::FC:
|
||||
return "FC";
|
||||
case CriterionCode::NOT_SC:
|
||||
return "NOT_SC";
|
||||
case CriterionCode::SC:
|
||||
return "SC";
|
||||
case CriterionCode::HU_OR_RA_CLASS_SC:
|
||||
return "HU_OR_RA_CLASS_SC";
|
||||
case CriterionCode::HUNTER_NON_ANDROID_SC:
|
||||
return "HUNTER_NON_ANDROID_SC";
|
||||
case CriterionCode::HUNTER_HU_CLASS_MALE_SC:
|
||||
return "HUNTER_HU_CLASS_MALE_SC";
|
||||
case CriterionCode::HUNTER_FEMALE_SC:
|
||||
return "HUNTER_FEMALE_SC";
|
||||
case CriterionCode::HUNTER_NON_RA_CLASS_HUMAN_SC:
|
||||
return "HUNTER_NON_RA_CLASS_HUMAN_SC";
|
||||
case CriterionCode::HUNTER_HU_CLASS_ANDROID_SC:
|
||||
return "HUNTER_HU_CLASS_ANDROID_SC";
|
||||
case CriterionCode::HUNTER_NON_RA_CLASS_NON_NEWMAN_SC:
|
||||
return "HUNTER_NON_RA_CLASS_NON_NEWMAN_SC";
|
||||
case CriterionCode::HUNTER_NON_NEWMAN_NON_FORCE_MALE_SC:
|
||||
return "HUNTER_NON_NEWMAN_NON_FORCE_MALE_SC";
|
||||
case CriterionCode::HUNTER_HUNEWEARL_CLASS_SC:
|
||||
return "HUNTER_HUNEWEARL_CLASS_SC";
|
||||
case CriterionCode::HUNTER_RA_CLASS_MALE_SC:
|
||||
return "HUNTER_RA_CLASS_MALE_SC";
|
||||
case CriterionCode::HUNTER_RA_CLASS_FEMALE_SC:
|
||||
return "HUNTER_RA_CLASS_FEMALE_SC";
|
||||
case CriterionCode::HUNTER_RA_OR_FO_CLASS_FEMALE_SC:
|
||||
return "HUNTER_RA_OR_FO_CLASS_FEMALE_SC";
|
||||
case CriterionCode::HUNTER_HU_OR_RA_CLASS_HUMAN_SC:
|
||||
return "HUNTER_HU_OR_RA_CLASS_HUMAN_SC";
|
||||
case CriterionCode::HUNTER_RA_CLASS_ANDROID_SC:
|
||||
return "HUNTER_RA_CLASS_ANDROID_SC";
|
||||
case CriterionCode::HUNTER_FO_CLASS_FEMALE_SC:
|
||||
return "HUNTER_FO_CLASS_FEMALE_SC";
|
||||
case CriterionCode::HUNTER_HUMAN_FEMALE_SC:
|
||||
return "HUNTER_HUMAN_FEMALE_SC";
|
||||
case CriterionCode::HUNTER_ANDROID_SC:
|
||||
return "HUNTER_ANDROID_SC";
|
||||
case CriterionCode::HU_OR_FO_CLASS_SC:
|
||||
return "HU_OR_FO_CLASS_SC";
|
||||
case CriterionCode::RA_OR_FO_CLASS_SC:
|
||||
return "RA_OR_FO_CLASS_SC";
|
||||
case CriterionCode::PHYSICAL_OR_UNKNOWN_ATTACK_MEDIUM:
|
||||
return "PHYSICAL_OR_UNKNOWN_ATTACK_MEDIUM";
|
||||
case CriterionCode::TECH_OR_UNKNOWN_ATTACK_MEDIUM:
|
||||
return "TECH_OR_UNKNOWN_ATTACK_MEDIUM";
|
||||
case CriterionCode::PHYSICAL_OR_TECH_OR_UNKNOWN_ATTACK_MEDIUM:
|
||||
return "PHYSICAL_OR_TECH_OR_UNKNOWN_ATTACK_MEDIUM";
|
||||
case CriterionCode::NON_PHYSICAL_NON_UNKNOWN_ATTACK_MEDIUM_NON_SC:
|
||||
return "NON_PHYSICAL_NON_UNKNOWN_ATTACK_MEDIUM_NON_SC";
|
||||
case CriterionCode::NON_PHYSICAL_NON_TECH_ATTACK_MEDIUM_NON_SC:
|
||||
return "NON_PHYSICAL_NON_TECH_ATTACK_MEDIUM_NON_SC";
|
||||
case CriterionCode::NON_PHYSICAL_NON_TECH_NON_UNKNOWN_ATTACK_MEDIUM_NON_SC:
|
||||
return "NON_PHYSICAL_NON_TECH_NON_UNKNOWN_ATTACK_MEDIUM_NON_SC";
|
||||
default:
|
||||
throw invalid_argument("invalid criterion code");
|
||||
}
|
||||
}
|
||||
|
||||
Location::Location() : Location(0, 0) {}
|
||||
Location::Location(uint8_t x, uint8_t y) : Location(x, y, Direction::RIGHT) {}
|
||||
Location::Location(uint8_t x, uint8_t y, Direction direction)
|
||||
@@ -262,7 +100,7 @@ bool Location::operator!=(const Location& other) const {
|
||||
|
||||
std::string Location::str() const {
|
||||
return string_printf("Location[x=%hhu, y=%hhu, dir=%s, u=%hhu]",
|
||||
this->x, this->y, name_for_direction(this->direction), this->unused);
|
||||
this->x, this->y, name_for_enum(this->direction), this->unused);
|
||||
}
|
||||
|
||||
void Location::clear() {
|
||||
@@ -324,23 +162,6 @@ Direction turn_around(Direction d) {
|
||||
}
|
||||
}
|
||||
|
||||
const char* name_for_direction(Direction d) {
|
||||
switch (d) {
|
||||
case Direction::RIGHT:
|
||||
return "LEFT";
|
||||
case Direction::UP:
|
||||
return "DOWN";
|
||||
case Direction::LEFT:
|
||||
return "RIGHT";
|
||||
case Direction::DOWN:
|
||||
return "UP";
|
||||
case Direction::INVALID_FF:
|
||||
return "INVALID_FF";
|
||||
default:
|
||||
return "__INVALID__";
|
||||
}
|
||||
}
|
||||
|
||||
bool card_class_is_tech_like(CardClass cc, bool is_nte) {
|
||||
// NTE does not consider BOSS_TECH to be a tech-like card class, but that's
|
||||
// probably because that card class just doesn't exist on NTE.
|
||||
@@ -632,27 +453,6 @@ static const vector<ConditionDescription> description_for_condition_type({
|
||||
/* 0x7D */ {false, "UNKNOWN_7D", nullptr},
|
||||
});
|
||||
|
||||
const char* name_for_condition_type(ConditionType cond_type) {
|
||||
try {
|
||||
return description_for_condition_type.at(static_cast<size_t>(cond_type)).name;
|
||||
} catch (const out_of_range&) {
|
||||
return "__INVALID__";
|
||||
}
|
||||
}
|
||||
|
||||
const char* name_for_action_subphase(ActionSubphase subphase) {
|
||||
switch (subphase) {
|
||||
case ActionSubphase::ATTACK:
|
||||
return "ATTACK";
|
||||
case ActionSubphase::DEFENSE:
|
||||
return "DEFENSE";
|
||||
case ActionSubphase::INVALID_FF:
|
||||
return "INVALID_FF";
|
||||
default:
|
||||
return "__INVALID__";
|
||||
}
|
||||
}
|
||||
|
||||
void CardDefinition::Stat::decode_code() {
|
||||
this->type = static_cast<Type>(this->code / 1000);
|
||||
int16_t value = this->code - (this->type * 1000);
|
||||
@@ -801,7 +601,7 @@ string CardDefinition::Effect::str(const char* separator, const TextSet* text_ar
|
||||
uint8_t type = static_cast<uint8_t>(this->apply_criterion);
|
||||
string cond_str = string_printf("cond=%02hhX", type);
|
||||
try {
|
||||
const char* name = name_for_criterion_code(this->apply_criterion);
|
||||
const char* name = name_for_enum(this->apply_criterion);
|
||||
cond_str += ':';
|
||||
cond_str += name;
|
||||
} catch (const invalid_argument&) {
|
||||
@@ -1072,24 +872,9 @@ static const char* name_for_assist_ai_param_target(uint8_t target) {
|
||||
}
|
||||
|
||||
string CardDefinition::str(bool single_line, const TextSet* text_archive) const {
|
||||
string type_str;
|
||||
try {
|
||||
type_str = name_for_card_type(this->type);
|
||||
} catch (const invalid_argument&) {
|
||||
type_str = string_printf("%02hhX", static_cast<uint8_t>(this->type));
|
||||
}
|
||||
string criterion_str;
|
||||
try {
|
||||
criterion_str = name_for_criterion_code(this->usable_criterion);
|
||||
} catch (const invalid_argument&) {
|
||||
criterion_str = string_printf("%02hhX", static_cast<uint8_t>(this->usable_criterion));
|
||||
}
|
||||
string card_class_str;
|
||||
try {
|
||||
card_class_str = name_for_card_class(this->card_class());
|
||||
} catch (const invalid_argument&) {
|
||||
card_class_str = string_printf("%04hX", this->be_card_class.load());
|
||||
}
|
||||
string type_str = name_for_enum(this->type);
|
||||
string criterion_str = name_for_enum(this->usable_criterion);
|
||||
string card_class_str = name_for_enum(this->card_class());
|
||||
string rank_str = name_for_rank(this->rank);
|
||||
string target_mode_str = name_for_target_mode(this->target_mode);
|
||||
string assist_turns_str = string_for_assist_turns(this->assist_turns);
|
||||
@@ -2863,6 +2648,275 @@ const char* name_for_enum<Episode3::AllowedCards>(Episode3::AllowedCards allowed
|
||||
case Episode3::AllowedCards::N_R_S_ONLY:
|
||||
return "N_R_S_ONLY";
|
||||
default:
|
||||
throw out_of_range("invalid allowed cards");
|
||||
return "__INVALID__";
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::BattlePhase>(Episode3::BattlePhase phase) {
|
||||
switch (phase) {
|
||||
case Episode3::BattlePhase::INVALID_00:
|
||||
return "INVALID_00";
|
||||
case Episode3::BattlePhase::DICE:
|
||||
return "DICE";
|
||||
case Episode3::BattlePhase::SET:
|
||||
return "SET";
|
||||
case Episode3::BattlePhase::MOVE:
|
||||
return "MOVE";
|
||||
case Episode3::BattlePhase::ACTION:
|
||||
return "ACTION";
|
||||
case Episode3::BattlePhase::DRAW:
|
||||
return "DRAW";
|
||||
case Episode3::BattlePhase::INVALID_FF:
|
||||
return "INVALID_FF";
|
||||
default:
|
||||
return "__INVALID__";
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::SetupPhase>(Episode3::SetupPhase phase) {
|
||||
switch (phase) {
|
||||
case Episode3::SetupPhase::REGISTRATION:
|
||||
return "REGISTRATION";
|
||||
case Episode3::SetupPhase::STARTER_ROLLS:
|
||||
return "STARTER_ROLLS";
|
||||
case Episode3::SetupPhase::HAND_REDRAW_OPTION:
|
||||
return "HAND_REDRAW_OPTION";
|
||||
case Episode3::SetupPhase::MAIN_BATTLE:
|
||||
return "MAIN_BATTLE";
|
||||
case Episode3::SetupPhase::BATTLE_ENDED:
|
||||
return "BATTLE_ENDED";
|
||||
case Episode3::SetupPhase::INVALID_FF:
|
||||
return "INVALID_FF";
|
||||
default:
|
||||
return "__INVALID__";
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::RegistrationPhase>(Episode3::RegistrationPhase phase) {
|
||||
switch (phase) {
|
||||
case Episode3::RegistrationPhase::AWAITING_NUM_PLAYERS:
|
||||
return "AWAITING_NUM_PLAYERS";
|
||||
case Episode3::RegistrationPhase::AWAITING_PLAYERS:
|
||||
return "AWAITING_PLAYERS";
|
||||
case Episode3::RegistrationPhase::AWAITING_DECKS:
|
||||
return "AWAITING_DECKS";
|
||||
case Episode3::RegistrationPhase::REGISTERED:
|
||||
return "REGISTERED";
|
||||
case Episode3::RegistrationPhase::BATTLE_STARTED:
|
||||
return "BATTLE_STARTED";
|
||||
case Episode3::RegistrationPhase::INVALID_FF:
|
||||
return "INVALID_FF";
|
||||
default:
|
||||
return "__INVALID__";
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::ActionSubphase>(Episode3::ActionSubphase phase) {
|
||||
switch (phase) {
|
||||
case Episode3::ActionSubphase::ATTACK:
|
||||
return "ATTACK";
|
||||
case Episode3::ActionSubphase::DEFENSE:
|
||||
return "DEFENSE";
|
||||
case Episode3::ActionSubphase::INVALID_FF:
|
||||
return "INVALID_FF";
|
||||
default:
|
||||
return "__INVALID__";
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::AttackMedium>(Episode3::AttackMedium medium) {
|
||||
switch (medium) {
|
||||
case Episode3::AttackMedium::UNKNOWN:
|
||||
return "UNKNOWN";
|
||||
case Episode3::AttackMedium::PHYSICAL:
|
||||
return "PHYSICAL";
|
||||
case Episode3::AttackMedium::TECH:
|
||||
return "TECH";
|
||||
case Episode3::AttackMedium::UNKNOWN_03:
|
||||
return "UNKNOWN_03";
|
||||
case Episode3::AttackMedium::INVALID_FF:
|
||||
return "INVALID_FF";
|
||||
default:
|
||||
return "__INVALID__";
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::CriterionCode>(Episode3::CriterionCode code) {
|
||||
switch (code) {
|
||||
case Episode3::CriterionCode::NONE:
|
||||
return "NONE";
|
||||
case Episode3::CriterionCode::HU_CLASS_SC:
|
||||
return "HU_CLASS_SC";
|
||||
case Episode3::CriterionCode::RA_CLASS_SC:
|
||||
return "RA_CLASS_SC";
|
||||
case Episode3::CriterionCode::FO_CLASS_SC:
|
||||
return "FO_CLASS_SC";
|
||||
case Episode3::CriterionCode::SAME_TEAM:
|
||||
return "SAME_TEAM";
|
||||
case Episode3::CriterionCode::SAME_PLAYER:
|
||||
return "SAME_PLAYER";
|
||||
case Episode3::CriterionCode::SAME_TEAM_NOT_SAME_PLAYER:
|
||||
return "SAME_TEAM_NOT_SAME_PLAYER";
|
||||
case Episode3::CriterionCode::FC:
|
||||
return "FC";
|
||||
case Episode3::CriterionCode::NOT_SC:
|
||||
return "NOT_SC";
|
||||
case Episode3::CriterionCode::SC:
|
||||
return "SC";
|
||||
case Episode3::CriterionCode::HU_OR_RA_CLASS_SC:
|
||||
return "HU_OR_RA_CLASS_SC";
|
||||
case Episode3::CriterionCode::HUNTER_NON_ANDROID_SC:
|
||||
return "HUNTER_NON_ANDROID_SC";
|
||||
case Episode3::CriterionCode::HUNTER_HU_CLASS_MALE_SC:
|
||||
return "HUNTER_HU_CLASS_MALE_SC";
|
||||
case Episode3::CriterionCode::HUNTER_FEMALE_SC:
|
||||
return "HUNTER_FEMALE_SC";
|
||||
case Episode3::CriterionCode::HUNTER_NON_RA_CLASS_HUMAN_SC:
|
||||
return "HUNTER_NON_RA_CLASS_HUMAN_SC";
|
||||
case Episode3::CriterionCode::HUNTER_HU_CLASS_ANDROID_SC:
|
||||
return "HUNTER_HU_CLASS_ANDROID_SC";
|
||||
case Episode3::CriterionCode::HUNTER_NON_RA_CLASS_NON_NEWMAN_SC:
|
||||
return "HUNTER_NON_RA_CLASS_NON_NEWMAN_SC";
|
||||
case Episode3::CriterionCode::HUNTER_NON_NEWMAN_NON_FORCE_MALE_SC:
|
||||
return "HUNTER_NON_NEWMAN_NON_FORCE_MALE_SC";
|
||||
case Episode3::CriterionCode::HUNTER_HUNEWEARL_CLASS_SC:
|
||||
return "HUNTER_HUNEWEARL_CLASS_SC";
|
||||
case Episode3::CriterionCode::HUNTER_RA_CLASS_MALE_SC:
|
||||
return "HUNTER_RA_CLASS_MALE_SC";
|
||||
case Episode3::CriterionCode::HUNTER_RA_CLASS_FEMALE_SC:
|
||||
return "HUNTER_RA_CLASS_FEMALE_SC";
|
||||
case Episode3::CriterionCode::HUNTER_RA_OR_FO_CLASS_FEMALE_SC:
|
||||
return "HUNTER_RA_OR_FO_CLASS_FEMALE_SC";
|
||||
case Episode3::CriterionCode::HUNTER_HU_OR_RA_CLASS_HUMAN_SC:
|
||||
return "HUNTER_HU_OR_RA_CLASS_HUMAN_SC";
|
||||
case Episode3::CriterionCode::HUNTER_RA_CLASS_ANDROID_SC:
|
||||
return "HUNTER_RA_CLASS_ANDROID_SC";
|
||||
case Episode3::CriterionCode::HUNTER_FO_CLASS_FEMALE_SC:
|
||||
return "HUNTER_FO_CLASS_FEMALE_SC";
|
||||
case Episode3::CriterionCode::HUNTER_HUMAN_FEMALE_SC:
|
||||
return "HUNTER_HUMAN_FEMALE_SC";
|
||||
case Episode3::CriterionCode::HUNTER_ANDROID_SC:
|
||||
return "HUNTER_ANDROID_SC";
|
||||
case Episode3::CriterionCode::HU_OR_FO_CLASS_SC:
|
||||
return "HU_OR_FO_CLASS_SC";
|
||||
case Episode3::CriterionCode::RA_OR_FO_CLASS_SC:
|
||||
return "RA_OR_FO_CLASS_SC";
|
||||
case Episode3::CriterionCode::PHYSICAL_OR_UNKNOWN_ATTACK_MEDIUM:
|
||||
return "PHYSICAL_OR_UNKNOWN_ATTACK_MEDIUM";
|
||||
case Episode3::CriterionCode::TECH_OR_UNKNOWN_ATTACK_MEDIUM:
|
||||
return "TECH_OR_UNKNOWN_ATTACK_MEDIUM";
|
||||
case Episode3::CriterionCode::PHYSICAL_OR_TECH_OR_UNKNOWN_ATTACK_MEDIUM:
|
||||
return "PHYSICAL_OR_TECH_OR_UNKNOWN_ATTACK_MEDIUM";
|
||||
case Episode3::CriterionCode::NON_PHYSICAL_NON_UNKNOWN_ATTACK_MEDIUM_NON_SC:
|
||||
return "NON_PHYSICAL_NON_UNKNOWN_ATTACK_MEDIUM_NON_SC";
|
||||
case Episode3::CriterionCode::NON_PHYSICAL_NON_TECH_ATTACK_MEDIUM_NON_SC:
|
||||
return "NON_PHYSICAL_NON_TECH_ATTACK_MEDIUM_NON_SC";
|
||||
case Episode3::CriterionCode::NON_PHYSICAL_NON_TECH_NON_UNKNOWN_ATTACK_MEDIUM_NON_SC:
|
||||
return "NON_PHYSICAL_NON_TECH_NON_UNKNOWN_ATTACK_MEDIUM_NON_SC";
|
||||
default:
|
||||
return "__UNKNOWN__";
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::CardType>(Episode3::CardType type) {
|
||||
switch (type) {
|
||||
case Episode3::CardType::HUNTERS_SC:
|
||||
return "HUNTERS_SC";
|
||||
case Episode3::CardType::ARKZ_SC:
|
||||
return "ARKZ_SC";
|
||||
case Episode3::CardType::ITEM:
|
||||
return "ITEM";
|
||||
case Episode3::CardType::CREATURE:
|
||||
return "CREATURE";
|
||||
case Episode3::CardType::ACTION:
|
||||
return "ACTION";
|
||||
case Episode3::CardType::ASSIST:
|
||||
return "ASSIST";
|
||||
case Episode3::CardType::INVALID_FF:
|
||||
return "INVALID_FF";
|
||||
default:
|
||||
return "__UNKNOWN__";
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::CardClass>(Episode3::CardClass cc) {
|
||||
switch (cc) {
|
||||
case Episode3::CardClass::HU_SC:
|
||||
return "HU_SC";
|
||||
case Episode3::CardClass::RA_SC:
|
||||
return "RA_SC";
|
||||
case Episode3::CardClass::FO_SC:
|
||||
return "FO_SC";
|
||||
case Episode3::CardClass::NATIVE_CREATURE:
|
||||
return "NATIVE_CREATURE";
|
||||
case Episode3::CardClass::A_BEAST_CREATURE:
|
||||
return "A_BEAST_CREATURE";
|
||||
case Episode3::CardClass::MACHINE_CREATURE:
|
||||
return "MACHINE_CREATURE";
|
||||
case Episode3::CardClass::DARK_CREATURE:
|
||||
return "DARK_CREATURE";
|
||||
case Episode3::CardClass::GUARD_ITEM:
|
||||
return "GUARD_ITEM";
|
||||
case Episode3::CardClass::MAG_ITEM:
|
||||
return "MAG_ITEM";
|
||||
case Episode3::CardClass::SWORD_ITEM:
|
||||
return "SWORD_ITEM";
|
||||
case Episode3::CardClass::GUN_ITEM:
|
||||
return "GUN_ITEM";
|
||||
case Episode3::CardClass::CANE_ITEM:
|
||||
return "CANE_ITEM";
|
||||
case Episode3::CardClass::ATTACK_ACTION:
|
||||
return "ATTACK_ACTION";
|
||||
case Episode3::CardClass::DEFENSE_ACTION:
|
||||
return "DEFENSE_ACTION";
|
||||
case Episode3::CardClass::TECH:
|
||||
return "TECH";
|
||||
case Episode3::CardClass::PHOTON_BLAST:
|
||||
return "PHOTON_BLAST";
|
||||
case Episode3::CardClass::CONNECT_ONLY_ATTACK_ACTION:
|
||||
return "CONNECT_ONLY_ATTACK_ACTION";
|
||||
case Episode3::CardClass::BOSS_ATTACK_ACTION:
|
||||
return "BOSS_ATTACK_ACTION";
|
||||
case Episode3::CardClass::BOSS_TECH:
|
||||
return "BOSS_TECH";
|
||||
case Episode3::CardClass::ASSIST:
|
||||
return "ASSIST";
|
||||
default:
|
||||
return "__INVALID__";
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::ConditionType>(Episode3::ConditionType cond_type) {
|
||||
try {
|
||||
return Episode3::description_for_condition_type.at(static_cast<size_t>(cond_type)).name;
|
||||
} catch (const out_of_range&) {
|
||||
return "__INVALID__";
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::Direction>(Episode3::Direction d) {
|
||||
switch (d) {
|
||||
case Episode3::Direction::RIGHT:
|
||||
return "LEFT";
|
||||
case Episode3::Direction::UP:
|
||||
return "DOWN";
|
||||
case Episode3::Direction::LEFT:
|
||||
return "RIGHT";
|
||||
case Episode3::Direction::DOWN:
|
||||
return "UP";
|
||||
case Episode3::Direction::INVALID_FF:
|
||||
return "INVALID_FF";
|
||||
default:
|
||||
return "__INVALID__";
|
||||
}
|
||||
}
|
||||
|
||||
+22
-13
@@ -59,8 +59,6 @@ enum class AttackMedium : uint8_t {
|
||||
INVALID_FF = 0xFF,
|
||||
};
|
||||
|
||||
const char* name_for_attack_medium(AttackMedium medium);
|
||||
|
||||
enum class CriterionCode : uint8_t {
|
||||
NONE = 0x00,
|
||||
HU_CLASS_SC = 0x01,
|
||||
@@ -99,8 +97,6 @@ enum class CriterionCode : uint8_t {
|
||||
NON_PHYSICAL_NON_TECH_NON_UNKNOWN_ATTACK_MEDIUM_NON_SC = 0x22,
|
||||
};
|
||||
|
||||
const char* name_for_criterion_code(CriterionCode code);
|
||||
|
||||
enum class CardRank : uint8_t {
|
||||
N1 = 0x01,
|
||||
R1 = 0x02,
|
||||
@@ -138,8 +134,6 @@ enum class CardType : uint8_t {
|
||||
END_CARD_LIST = 0xFF,
|
||||
};
|
||||
|
||||
const char* name_for_card_type(CardType type);
|
||||
|
||||
enum class CardClass : uint16_t {
|
||||
HU_SC = 0x0000,
|
||||
RA_SC = 0x0001,
|
||||
@@ -163,7 +157,6 @@ enum class CardClass : uint16_t {
|
||||
ASSIST = 0x0028,
|
||||
};
|
||||
|
||||
const char* name_for_card_class(CardClass cc);
|
||||
bool card_class_is_tech_like(CardClass cc, bool is_nte);
|
||||
|
||||
enum class TargetMode : uint8_t {
|
||||
@@ -310,8 +303,6 @@ enum class ConditionType : uint8_t {
|
||||
ANY_FF = 0xFF, // Used as a wildcard in some search functions
|
||||
};
|
||||
|
||||
const char* name_for_condition_type(ConditionType cond_type);
|
||||
|
||||
enum class AssistEffect : uint16_t {
|
||||
NONE = 0x0000,
|
||||
DICE_HALF = 0x0001,
|
||||
@@ -408,8 +399,6 @@ enum class ActionSubphase : uint8_t {
|
||||
INVALID_FF = 0xFF,
|
||||
};
|
||||
|
||||
const char* name_for_action_subphase(ActionSubphase subphase);
|
||||
|
||||
enum class SetupPhase : uint8_t {
|
||||
REGISTRATION = 0,
|
||||
STARTER_ROLLS = 1,
|
||||
@@ -439,7 +428,6 @@ enum class Direction : uint8_t {
|
||||
Direction turn_left(Direction d);
|
||||
Direction turn_right(Direction d);
|
||||
Direction turn_around(Direction d);
|
||||
const char* name_for_direction(Direction d);
|
||||
|
||||
struct Location {
|
||||
/* 00 */ uint8_t x;
|
||||
@@ -519,7 +507,7 @@ struct CardDefinition {
|
||||
} __attribute__((packed));
|
||||
|
||||
/* 0000 */ be_uint32_t card_id;
|
||||
/* 0004 */ parray<uint8_t, 0x40> jp_name;
|
||||
/* 0004 */ pstring<TextEncoding::SJIS, 0x40> jp_name;
|
||||
|
||||
// The list of card definitions ends with a "sentinel" definition that isn't a
|
||||
// real card, but instead has a negative number in the type field here.
|
||||
@@ -1556,3 +1544,24 @@ template <>
|
||||
Episode3::AllowedCards enum_for_name<Episode3::AllowedCards>(const char* name);
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::AllowedCards>(Episode3::AllowedCards allowed_cards);
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::BattlePhase>(Episode3::BattlePhase phase);
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::SetupPhase>(Episode3::SetupPhase phase);
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::RegistrationPhase>(Episode3::RegistrationPhase phase);
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::ActionSubphase>(Episode3::ActionSubphase phase);
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::AttackMedium>(Episode3::AttackMedium medium);
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::CriterionCode>(Episode3::CriterionCode code);
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::CardType>(Episode3::CardType type);
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::CardClass>(Episode3::CardClass cc);
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::ConditionType>(Episode3::ConditionType cond_type);
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::Direction>(Episode3::Direction d);
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace Episode3 {
|
||||
|
||||
struct NameEntry {
|
||||
/* 00 */ parray<char, 0x10> name;
|
||||
/* 00 */ pstring<TextEncoding::MARKED, 0x10> name;
|
||||
/* 10 */ uint8_t client_id;
|
||||
/* 11 */ uint8_t present;
|
||||
/* 12 */ uint8_t is_cpu_player;
|
||||
@@ -23,7 +23,7 @@ struct NameEntry {
|
||||
} __attribute__((packed));
|
||||
|
||||
struct DeckEntry {
|
||||
/* 00 */ pstring<TextEncoding::SJIS, 0x10> name;
|
||||
/* 00 */ pstring<TextEncoding::MARKED, 0x10> name;
|
||||
/* 10 */ le_uint32_t team_id;
|
||||
/* 14 */ parray<le_uint16_t, 31> card_ids;
|
||||
// If the following flag is not set to 3, then the God Whim assist effect can
|
||||
|
||||
@@ -1781,7 +1781,7 @@ bool PlayerState::set_action_cards_for_action_state(const ActionState& pa) {
|
||||
auto card = s->card_for_set_card_ref(pa.attacker_card_ref);
|
||||
if (card) {
|
||||
card->loc.direction = pa.facing_direction;
|
||||
log.debug("set facing direction to %s", name_for_direction(card->loc.direction));
|
||||
log.debug("set facing direction to %s", name_for_enum(card->loc.direction));
|
||||
|
||||
G_Unknown_Ep3_6xB4x4A cmd;
|
||||
cmd.card_refs.clear(0xFFFF);
|
||||
|
||||
@@ -68,7 +68,7 @@ std::string Condition::str(shared_ptr<const Server> s) const {
|
||||
"Condition[type=%s, turns=%hhu, a_arg=%hhd, dice=%hhu, flags=%02hhX, "
|
||||
"def_eff_index=%hhu, ref=%s, value=%hd, giver_ref=%s "
|
||||
"percent=%hhu value8=%hd order=%hu a8=%hu]",
|
||||
name_for_condition_type(this->type),
|
||||
name_for_enum(this->type),
|
||||
this->remaining_turns,
|
||||
this->a_arg_value,
|
||||
this->dice_roll_value,
|
||||
@@ -199,7 +199,7 @@ std::string ActionState::str(shared_ptr<const Server> s) const {
|
||||
"orig_attacker_ref=%s]",
|
||||
this->client_id.load(),
|
||||
this->unused,
|
||||
name_for_direction(this->facing_direction),
|
||||
name_for_enum(this->facing_direction),
|
||||
attacker_ref_s.c_str(),
|
||||
defense_ref_s.c_str(),
|
||||
target_refs_s.c_str(),
|
||||
@@ -258,9 +258,9 @@ std::string ActionChain::str(shared_ptr<const Server> s) const {
|
||||
unknown_card_ref_a3_s.c_str(),
|
||||
attack_action_card_refs_s.c_str(),
|
||||
this->attack_action_card_ref_count,
|
||||
name_for_attack_medium(this->attack_medium),
|
||||
name_for_enum(this->attack_medium),
|
||||
this->target_card_ref_count,
|
||||
name_for_action_subphase(this->action_subphase),
|
||||
name_for_enum(this->action_subphase),
|
||||
this->strike_count,
|
||||
this->damage_multiplier,
|
||||
this->attack_number,
|
||||
@@ -588,7 +588,7 @@ std::string ActionMetadata::str(shared_ptr<const Server> s) const {
|
||||
card_ref_s.c_str(),
|
||||
this->target_card_ref_count,
|
||||
this->defense_card_ref_count,
|
||||
name_for_action_subphase(this->action_subphase),
|
||||
name_for_enum(this->action_subphase),
|
||||
this->defense_power,
|
||||
this->defense_bonus,
|
||||
this->attack_bonus,
|
||||
|
||||
@@ -939,7 +939,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
||||
bool is_item_usability_check,
|
||||
AttackMedium attack_medium) const {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(string_printf("check_usability_or_condition_apply(%02hhX, #%04hX, %02hhX, #%04hX, #%04hX, %02hhX, %s, %s): ", client_id1, card_id1, client_id2, card_id2, card_id3, def_effect_index, is_item_usability_check ? "true" : "false", name_for_attack_medium(attack_medium)));
|
||||
auto log = s->log_stack(string_printf("check_usability_or_condition_apply(%02hhX, #%04hX, %02hhX, #%04hX, #%04hX, %02hhX, %s, %s): ", client_id1, card_id1, client_id2, card_id2, card_id3, def_effect_index, is_item_usability_check ? "true" : "false", name_for_enum(attack_medium)));
|
||||
|
||||
if (static_cast<uint8_t>(attack_medium) & 0x80) {
|
||||
attack_medium = AttackMedium::UNKNOWN;
|
||||
@@ -967,7 +967,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
||||
}
|
||||
criterion_code = ce1->def.effects[def_effect_index].apply_criterion;
|
||||
}
|
||||
log.debug("criterion_code=%s", name_for_criterion_code(criterion_code));
|
||||
log.debug("criterion_code=%s", name_for_enum(criterion_code));
|
||||
|
||||
// For item usability checks, prevent criteria that depend on player
|
||||
// positioning/team setup
|
||||
|
||||
@@ -0,0 +1,851 @@
|
||||
#include "HTTPServer.hh"
|
||||
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/event.h>
|
||||
#include <event2/http.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <phosg/Network.hh>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Loggers.hh"
|
||||
#include "ProxyServer.hh"
|
||||
#include "Server.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
const unordered_map<int, const char*> HTTPServer::explanation_for_response_code({
|
||||
{100, "Continue"},
|
||||
{101, "Switching Protocols"},
|
||||
{102, "Processing"},
|
||||
{200, "OK"},
|
||||
{201, "Created"},
|
||||
{202, "Accepted"},
|
||||
{203, "Non-Authoritative Information"},
|
||||
{204, "No Content"},
|
||||
{205, "Reset Content"},
|
||||
{206, "Partial Content"},
|
||||
{207, "Multi-Status"},
|
||||
{208, "Already Reported"},
|
||||
{226, "IM Used"},
|
||||
{300, "Multiple Choices"},
|
||||
{301, "Moved Permanently"},
|
||||
{302, "Found"},
|
||||
{303, "See Other"},
|
||||
{304, "Not Modified"},
|
||||
{305, "Use Proxy"},
|
||||
{307, "Temporary Redirect"},
|
||||
{308, "Permanent Redirect"},
|
||||
{400, "Bad Request"},
|
||||
{401, "Unathorized"},
|
||||
{402, "Payment Required"},
|
||||
{403, "Forbidden"},
|
||||
{404, "Not Found"},
|
||||
{405, "Method Not Allowed"},
|
||||
{406, "Not Acceptable"},
|
||||
{407, "Proxy Authentication Required"},
|
||||
{408, "Request Timeout"},
|
||||
{409, "Conflict"},
|
||||
{410, "Gone"},
|
||||
{411, "Length Required"},
|
||||
{412, "Precondition Failed"},
|
||||
{413, "Request Entity Too Large"},
|
||||
{414, "Request-URI Too Long"},
|
||||
{415, "Unsupported Media Type"},
|
||||
{416, "Requested Range Not Satisfiable"},
|
||||
{417, "Expectation Failed"},
|
||||
{418, "I\'m a Teapot"},
|
||||
{420, "Enhance Your Calm"},
|
||||
{422, "Unprocessable Entity"},
|
||||
{423, "Locked"},
|
||||
{424, "Failed Dependency"},
|
||||
{426, "Upgrade Required"},
|
||||
{428, "Precondition Required"},
|
||||
{429, "Too Many Requests"},
|
||||
{431, "Request Header Fields Too Large"},
|
||||
{444, "No Response"},
|
||||
{449, "Retry With"},
|
||||
{451, "Unavailable For Legal Reasons"},
|
||||
{500, "Internal Server Error"},
|
||||
{501, "Not Implemented"},
|
||||
{502, "Bad Gateway"},
|
||||
{503, "Service Unavailable"},
|
||||
{504, "Gateway Timeout"},
|
||||
{505, "HTTP Version Not Supported"},
|
||||
{506, "Variant Also Negotiates"},
|
||||
{507, "Insufficient Storage"},
|
||||
{508, "Loop Detected"},
|
||||
{509, "Bandwidth Limit Exceeded"},
|
||||
{510, "Not Extended"},
|
||||
{511, "Network Authentication Required"},
|
||||
{598, "Network Read Timeout Error"},
|
||||
{599, "Network Connect Timeout Error"},
|
||||
});
|
||||
|
||||
HTTPServer::http_error::http_error(int code, const string& what)
|
||||
: runtime_error(what),
|
||||
code(code) {}
|
||||
|
||||
void HTTPServer::send_response(struct evhttp_request* req, int code, const char* content_type, struct evbuffer* b) {
|
||||
struct evkeyvalq* headers = evhttp_request_get_output_headers(req);
|
||||
evhttp_add_header(headers, "Content-Type", content_type);
|
||||
evhttp_add_header(headers, "Server", "newserv");
|
||||
evhttp_send_reply(req, code, explanation_for_response_code.at(code), b);
|
||||
}
|
||||
|
||||
void HTTPServer::send_response(struct evhttp_request* req, int code, const char* content_type, const char* fmt, ...) {
|
||||
unique_ptr<struct evbuffer, void (*)(struct evbuffer*)> out_buffer(evbuffer_new(), evbuffer_free);
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
evbuffer_add_vprintf(out_buffer.get(), fmt, va);
|
||||
va_end(va);
|
||||
HTTPServer::send_response(req, code, content_type, out_buffer.get());
|
||||
}
|
||||
|
||||
unordered_multimap<string, string> HTTPServer::parse_url_params(const string& query) {
|
||||
unordered_multimap<string, string> params;
|
||||
if (query.empty()) {
|
||||
return params;
|
||||
}
|
||||
for (auto it : split(query, '&')) {
|
||||
size_t first_equals = it.find('=');
|
||||
if (first_equals != string::npos) {
|
||||
string value(it, first_equals + 1);
|
||||
|
||||
size_t write_offset = 0, read_offset = 0;
|
||||
for (; read_offset < value.size(); write_offset++) {
|
||||
if ((value[read_offset] == '%') && (read_offset < value.size() - 2)) {
|
||||
value[write_offset] =
|
||||
static_cast<char>(value_for_hex_char(value[read_offset + 1]) << 4) |
|
||||
static_cast<char>(value_for_hex_char(value[read_offset + 2]));
|
||||
read_offset += 3;
|
||||
} else if (value[write_offset] == '+') {
|
||||
value[write_offset] = ' ';
|
||||
read_offset++;
|
||||
} else {
|
||||
value[write_offset] = value[read_offset];
|
||||
read_offset++;
|
||||
}
|
||||
}
|
||||
value.resize(write_offset);
|
||||
|
||||
params.emplace(piecewise_construct, forward_as_tuple(it, 0, first_equals),
|
||||
forward_as_tuple(value));
|
||||
} else {
|
||||
params.emplace(it, "");
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
unordered_map<string, string> HTTPServer::parse_url_params_unique(const string& query) {
|
||||
unordered_map<string, string> ret;
|
||||
for (const auto& it : HTTPServer::parse_url_params(query)) {
|
||||
ret.emplace(it.first, std::move(it.second));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
const string& HTTPServer::get_url_param(
|
||||
const unordered_multimap<string, string>& params, const string& key, const string* _default) {
|
||||
|
||||
auto range = params.equal_range(key);
|
||||
if (range.first == range.second) {
|
||||
if (!_default) {
|
||||
throw out_of_range("URL parameter " + key + " not present");
|
||||
}
|
||||
return *_default;
|
||||
}
|
||||
|
||||
return range.first->second;
|
||||
}
|
||||
|
||||
HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
||||
: state(state),
|
||||
http(evhttp_new(this->state->base.get()), evhttp_free) {
|
||||
evhttp_set_gencb(this->http.get(), this->dispatch_handle_request, this);
|
||||
}
|
||||
|
||||
void HTTPServer::listen(const string& socket_path) {
|
||||
int fd = ::listen(socket_path, 0, SOMAXCONN);
|
||||
server_log.info("Listening on Unix socket %s on fd %d (HTTP)", socket_path.c_str(), fd);
|
||||
this->add_socket(fd);
|
||||
}
|
||||
|
||||
void HTTPServer::listen(const string& addr, int port) {
|
||||
if (port == 0) {
|
||||
this->listen(addr);
|
||||
} else {
|
||||
int fd = ::listen(addr, port, SOMAXCONN);
|
||||
string netloc_str = render_netloc(addr, port);
|
||||
server_log.info("Listening on TCP interface %s on fd %d (HTTP)", netloc_str.c_str(), fd);
|
||||
this->add_socket(fd);
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPServer::listen(int port) {
|
||||
this->listen("", port);
|
||||
}
|
||||
|
||||
void HTTPServer::add_socket(int fd) {
|
||||
evhttp_accept_socket(this->http.get(), fd);
|
||||
}
|
||||
|
||||
void HTTPServer::dispatch_handle_request(struct evhttp_request* req, void* ctx) {
|
||||
reinterpret_cast<HTTPServer*>(ctx)->handle_request(req);
|
||||
}
|
||||
|
||||
JSON HTTPServer::generate_client_config_json(const Client::Config& config) const {
|
||||
const char* drop_notifications_mode = "unknown";
|
||||
switch (config.get_drop_notification_mode()) {
|
||||
case Client::ItemDropNotificationMode::NOTHING:
|
||||
drop_notifications_mode = "off";
|
||||
break;
|
||||
case Client::ItemDropNotificationMode::RARES_ONLY:
|
||||
drop_notifications_mode = "rare";
|
||||
break;
|
||||
case Client::ItemDropNotificationMode::ALL_ITEMS:
|
||||
drop_notifications_mode = "on";
|
||||
break;
|
||||
case Client::ItemDropNotificationMode::ALL_ITEMS_INCLUDING_MESETA:
|
||||
drop_notifications_mode = "every";
|
||||
break;
|
||||
}
|
||||
|
||||
auto ret = JSON::dict({
|
||||
{"SpecificVersion", config.specific_version},
|
||||
{"SwitchAssistEnabled", (config.check_flag(Client::Flag::SWITCH_ASSIST_ENABLED) ? true : false)},
|
||||
{"InfiniteHPEnabled", (config.check_flag(Client::Flag::INFINITE_HP_ENABLED) ? true : false)},
|
||||
{"InfiniteTPEnabled", (config.check_flag(Client::Flag::INFINITE_TP_ENABLED) ? true : false)},
|
||||
{"DropNotificationMode", drop_notifications_mode},
|
||||
{"DebugEnabled", (config.check_flag(Client::Flag::DEBUG_ENABLED) ? true : false)},
|
||||
{"ProxySaveFilesEnabled", (config.check_flag(Client::Flag::PROXY_SAVE_FILES) ? true : false)},
|
||||
{"ProxyChatCommandsEnabled", (config.check_flag(Client::Flag::PROXY_CHAT_COMMANDS_ENABLED) ? true : false)},
|
||||
{"ProxyPlayerNotificationsEnabled", (config.check_flag(Client::Flag::PROXY_PLAYER_NOTIFICATIONS_ENABLED) ? true : false)},
|
||||
{"ProxySuppressClientPings", (config.check_flag(Client::Flag::PROXY_SUPPRESS_CLIENT_PINGS) ? true : false)},
|
||||
{"ProxyEp3InfiniteMesetaEnabled", (config.check_flag(Client::Flag::PROXY_EP3_INFINITE_MESETA_ENABLED) ? true : false)},
|
||||
{"ProxyEp3InfiniteTimeEnabled", (config.check_flag(Client::Flag::PROXY_EP3_INFINITE_TIME_ENABLED) ? true : false)},
|
||||
{"ProxyBlockFunctionCalls", (config.check_flag(Client::Flag::PROXY_BLOCK_FUNCTION_CALLS) ? true : false)},
|
||||
{"ProxyEp3UnmaskWhispers", (config.check_flag(Client::Flag::PROXY_EP3_UNMASK_WHISPERS) ? true : false)},
|
||||
});
|
||||
ret.emplace("OverrideRandomSeed", config.check_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED) ? config.override_random_seed : JSON(nullptr));
|
||||
ret.emplace("OverrideSectionID", (config.override_section_id != 0xFF) ? config.override_section_id : JSON(nullptr));
|
||||
ret.emplace("OverrideLobbyEvent", (config.override_lobby_event != 0xFF) ? config.override_lobby_event : JSON(nullptr));
|
||||
ret.emplace("OverrideLobbyNumber", (config.override_lobby_number != 0x80) ? config.override_lobby_number : JSON(nullptr));
|
||||
return ret;
|
||||
}
|
||||
|
||||
JSON HTTPServer::generate_license_json(shared_ptr<const License> l) const {
|
||||
auto ret = JSON::dict({
|
||||
{"SerialNumber", l->serial_number},
|
||||
{"Flags", l->flags},
|
||||
{"Ep3CurrentMeseta", l->ep3_current_meseta},
|
||||
{"Ep3TotalMesetaEarned", l->ep3_total_meseta_earned},
|
||||
{"BBTeamID", l->bb_team_id},
|
||||
});
|
||||
ret.emplace("BanEndTime", l->ban_end_time ? l->ban_end_time : JSON(nullptr));
|
||||
ret.emplace("XBGamertag", !l->xb_gamertag.empty() ? l->xb_gamertag : JSON(nullptr));
|
||||
ret.emplace("XBUserID", l->xb_user_id ? l->xb_user_id : JSON(nullptr));
|
||||
ret.emplace("BBUsername", !l->bb_username.empty() ? l->bb_username : JSON(nullptr));
|
||||
return ret;
|
||||
};
|
||||
|
||||
JSON HTTPServer::generate_game_client_json(shared_ptr<const Client> c) const {
|
||||
auto ret = JSON::dict({
|
||||
{"ID", c->id},
|
||||
{"RemoteAddress", render_sockaddr_storage(c->channel.remote_addr)},
|
||||
{"Version", name_for_enum(c->version())},
|
||||
{"SubVersion", c->sub_version},
|
||||
{"Config", this->generate_client_config_json(c->config)},
|
||||
{"Language", name_for_language_code(c->language())},
|
||||
{"LocationX", c->x},
|
||||
{"LocationZ", c->z},
|
||||
{"LocationFloor", c->floor},
|
||||
{"CanChat", c->can_chat},
|
||||
});
|
||||
ret.emplace("license", c->license ? this->generate_license_json(c->license) : JSON(nullptr));
|
||||
auto l = c->lobby.lock();
|
||||
if (l) {
|
||||
ret.emplace("LobbyID", l->lobby_id);
|
||||
ret.emplace("LobbyClientID", c->lobby_client_id);
|
||||
}
|
||||
if (c->version() == Version::BB_V4) {
|
||||
ret.emplace("BBCharacterIndex", c->bb_character_index);
|
||||
}
|
||||
auto p = c->character(false, false);
|
||||
if (p) {
|
||||
if (!is_ep3(c->version())) {
|
||||
ret.emplace("InventoryItems", p->inventory.num_items);
|
||||
if (c->version() != Version::DC_NTE) {
|
||||
ret.emplace("InventoryLanguage", p->inventory.language);
|
||||
ret.emplace("NumHPMaterialsUsed", p->get_material_usage(PSOBBCharacterFile::MaterialType::HP));
|
||||
ret.emplace("NumTPMaterialsUsed", p->get_material_usage(PSOBBCharacterFile::MaterialType::TP));
|
||||
if (!is_v1_or_v2(c->version())) {
|
||||
ret.emplace("NumPowerMaterialsUsed", p->get_material_usage(PSOBBCharacterFile::MaterialType::POWER));
|
||||
ret.emplace("NumDefMaterialsUsed", p->get_material_usage(PSOBBCharacterFile::MaterialType::DEF));
|
||||
ret.emplace("NumMindMaterialsUsed", p->get_material_usage(PSOBBCharacterFile::MaterialType::MIND));
|
||||
ret.emplace("NumEvadeMaterialsUsed", p->get_material_usage(PSOBBCharacterFile::MaterialType::EVADE));
|
||||
ret.emplace("NumLuckMaterialsUsed", p->get_material_usage(PSOBBCharacterFile::MaterialType::LUCK));
|
||||
}
|
||||
}
|
||||
JSON items_json = JSON::list();
|
||||
for (size_t z = 0; z < p->inventory.num_items; z++) {
|
||||
const auto& item = p->inventory.items[z];
|
||||
string description = this->state->describe_item(c->version(), item.data, false);
|
||||
string data_str = item.data.hex();
|
||||
auto item_dict = JSON::dict({
|
||||
{"Flags", item.flags.load()},
|
||||
{"Data", std::move(data_str)},
|
||||
{"Description", std::move(description)},
|
||||
{"ItemID", item.data.id.load()},
|
||||
});
|
||||
items_json.emplace_back(std::move(item_dict));
|
||||
}
|
||||
ret.emplace("ATP", p->disp.stats.char_stats.atp.load());
|
||||
ret.emplace("MST", p->disp.stats.char_stats.mst.load());
|
||||
ret.emplace("EVP", p->disp.stats.char_stats.evp.load());
|
||||
ret.emplace("HP", p->disp.stats.char_stats.hp.load());
|
||||
ret.emplace("DFP", p->disp.stats.char_stats.dfp.load());
|
||||
ret.emplace("ATA", p->disp.stats.char_stats.ata.load());
|
||||
ret.emplace("LCK", p->disp.stats.char_stats.lck.load());
|
||||
ret.emplace("EXP", p->disp.stats.experience.load());
|
||||
ret.emplace("Meseta", p->disp.stats.meseta.load());
|
||||
auto tech_levels_json = JSON::dict();
|
||||
for (size_t z = 0; z < 0x13; z++) {
|
||||
auto level = p->get_technique_level(z);
|
||||
tech_levels_json.emplace(name_for_technique(z), (level != 0xFF) ? level : JSON(nullptr));
|
||||
}
|
||||
ret.emplace("TechniqueLevels", std::move(tech_levels_json));
|
||||
}
|
||||
ret.emplace("Height", p->disp.stats.height.load());
|
||||
ret.emplace("Level", p->disp.stats.level.load());
|
||||
ret.emplace("NameColor", p->disp.visual.name_color.load());
|
||||
ret.emplace("ExtraModel", (p->disp.visual.validation_flags & 2) ? p->disp.visual.extra_model : JSON(nullptr));
|
||||
ret.emplace("SectionID", name_for_section_id(p->disp.visual.section_id));
|
||||
ret.emplace("CharClass", name_for_char_class(p->disp.visual.section_id));
|
||||
ret.emplace("Costume", p->disp.visual.costume.load());
|
||||
ret.emplace("Skin", p->disp.visual.skin.load());
|
||||
ret.emplace("Face", p->disp.visual.face.load());
|
||||
ret.emplace("Head", p->disp.visual.head.load());
|
||||
ret.emplace("Hair", p->disp.visual.hair.load());
|
||||
ret.emplace("HairR", p->disp.visual.hair_r.load());
|
||||
ret.emplace("HairG", p->disp.visual.hair_g.load());
|
||||
ret.emplace("HairB", p->disp.visual.hair_b.load());
|
||||
ret.emplace("ProportionX", p->disp.visual.proportion_x.load());
|
||||
ret.emplace("ProportionY", p->disp.visual.proportion_y.load());
|
||||
|
||||
ret.emplace("Name", p->disp.name.decode(c->language()));
|
||||
ret.emplace("PlayTimeSeconds", p->disp.play_time.load());
|
||||
|
||||
ret.emplace("AutoReply", p->auto_reply.decode(c->language()));
|
||||
ret.emplace("InfoBoard", p->info_board.decode(c->language()));
|
||||
auto battle_place_counts = JSON::list({
|
||||
p->battle_records.place_counts[0].load(),
|
||||
p->battle_records.place_counts[1].load(),
|
||||
p->battle_records.place_counts[2].load(),
|
||||
p->battle_records.place_counts[3].load(),
|
||||
});
|
||||
ret.emplace("BattlePlaceCounts", std::move(battle_place_counts));
|
||||
ret.emplace("BattleDisconnectCount", p->battle_records.disconnect_count.load());
|
||||
|
||||
if (!is_ep3(c->version())) {
|
||||
auto json_for_challenge_times = []<size_t Count>(const parray<ChallengeTime<false>, Count>& times) -> JSON {
|
||||
auto times_json = JSON::list();
|
||||
for (size_t z = 0; z < times.size(); z++) {
|
||||
times_json.emplace_back(times[z].load());
|
||||
}
|
||||
return times_json;
|
||||
};
|
||||
ret.emplace("ChallengeTitleColorXRGB1555", p->challenge_records.title_color.load());
|
||||
ret.emplace("ChallengeTimesEp1Online", json_for_challenge_times(p->challenge_records.times_ep1_online));
|
||||
ret.emplace("ChallengeTimesEp2Online", json_for_challenge_times(p->challenge_records.times_ep2_online));
|
||||
ret.emplace("ChallengeTimesEp1Offline", json_for_challenge_times(p->challenge_records.times_ep1_offline));
|
||||
ret.emplace("ChallengeGraveIsEp2", p->challenge_records.grave_is_ep2 ? true : false);
|
||||
ret.emplace("ChallengeGraveStageNum", p->challenge_records.grave_stage_num);
|
||||
ret.emplace("ChallengeGraveFloor", p->challenge_records.grave_floor);
|
||||
ret.emplace("ChallengeGraveDeaths", p->challenge_records.grave_deaths.load());
|
||||
{
|
||||
uint16_t year = 2000 + ((p->challenge_records.grave_time >> 28) & 0x0F);
|
||||
uint8_t month = (p->challenge_records.grave_time >> 24) & 0x0F;
|
||||
uint8_t day = (p->challenge_records.grave_time >> 16) & 0xFF;
|
||||
uint8_t hour = (p->challenge_records.grave_time >> 8) & 0xFF;
|
||||
uint8_t minute = p->challenge_records.grave_time & 0xFF;
|
||||
ret.emplace("ChallengeGraveTime", string_printf("%04hu-%02hhu-%02hhu %02hhu:%02hhu:00", year, month, day, hour, minute));
|
||||
}
|
||||
string grave_enemy_types;
|
||||
if (p->challenge_records.grave_defeated_by_enemy_rt_index) {
|
||||
for (EnemyType type : enemy_types_for_rare_table_index(p->challenge_records.grave_is_ep2 ? Episode::EP2 : Episode::EP1, p->challenge_records.grave_defeated_by_enemy_rt_index)) {
|
||||
if (!grave_enemy_types.empty()) {
|
||||
grave_enemy_types += "/";
|
||||
}
|
||||
grave_enemy_types += name_for_enum(type);
|
||||
}
|
||||
}
|
||||
ret.emplace("ChallengeGraveDefeatedByEnemy", std::move(grave_enemy_types));
|
||||
ret.emplace("ChallengeGraveX", p->challenge_records.grave_x.load());
|
||||
ret.emplace("ChallengeGraveY", p->challenge_records.grave_y.load());
|
||||
ret.emplace("ChallengeGraveZ", p->challenge_records.grave_z.load());
|
||||
ret.emplace("ChallengeGraveTeam", p->challenge_records.grave_team.decode());
|
||||
ret.emplace("ChallengeGraveMessage", p->challenge_records.grave_message.decode());
|
||||
ret.emplace("ChallengeAwardStateEp1OnlineFlags", p->challenge_records.ep1_online_award_state.rank_award_flags.load());
|
||||
ret.emplace("ChallengeAwardStateEp1OnlineMaxRank", p->challenge_records.ep1_online_award_state.maximum_rank.load());
|
||||
ret.emplace("ChallengeAwardStateEp2OnlineFlags", p->challenge_records.ep2_online_award_state.rank_award_flags.load());
|
||||
ret.emplace("ChallengeAwardStateEp2OnlineMaxRank", p->challenge_records.ep2_online_award_state.maximum_rank.load());
|
||||
ret.emplace("ChallengeAwardStateEp1OfflineFlags", p->challenge_records.ep1_offline_award_state.rank_award_flags.load());
|
||||
ret.emplace("ChallengeAwardStateEp1OfflineMaxRank", p->challenge_records.ep1_offline_award_state.maximum_rank.load());
|
||||
ret.emplace("ChallengeRankTitle", p->challenge_records.rank_title.decode());
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
JSON HTTPServer::generate_proxy_client_json(shared_ptr<const ProxyServer::LinkedSession> ses) const {
|
||||
struct LobbyPlayer {
|
||||
uint32_t guild_card_number = 0;
|
||||
uint64_t xb_user_id = 0;
|
||||
std::string name;
|
||||
uint8_t language = 0;
|
||||
uint8_t section_id = 0;
|
||||
uint8_t char_class = 0;
|
||||
};
|
||||
std::vector<LobbyPlayer> lobby_players;
|
||||
|
||||
auto lobby_players_json = JSON::list();
|
||||
for (size_t z = 0; z < ses->lobby_players.size(); z++) {
|
||||
const auto& p = ses->lobby_players[z];
|
||||
if (p.guild_card_number) {
|
||||
lobby_players_json.emplace_back(JSON::dict({
|
||||
{"GuildCardNumber", p.guild_card_number},
|
||||
{"Name", p.name},
|
||||
{"Language", name_for_language_code(p.language)},
|
||||
{"SectionID", name_for_section_id(p.section_id)},
|
||||
{"CharClass", name_for_char_class(p.char_class)},
|
||||
}));
|
||||
lobby_players_json.back().emplace("XBUserID", p.xb_user_id ? p.xb_user_id : JSON(nullptr));
|
||||
} else {
|
||||
lobby_players_json.emplace_back(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
auto ret = JSON::dict({
|
||||
{"ID", ses->id},
|
||||
{"RemoteClientAddress", render_sockaddr_storage(ses->client_channel.remote_addr)},
|
||||
{"RemoteServerAddress", render_sockaddr_storage(ses->server_channel.remote_addr)},
|
||||
{"LocalPort", ses->local_port},
|
||||
{"NextDestination", render_sockaddr_storage(ses->next_destination)},
|
||||
{"Version", name_for_enum(ses->version())},
|
||||
{"SubVersion", ses->sub_version},
|
||||
{"Name", ses->character_name},
|
||||
{"DCHardwareID", ses->hardware_id},
|
||||
{"RemoteGuildCardNumber", ses->remote_guild_card_number},
|
||||
{"RemoteClientConfigData", format_data_string(&ses->remote_client_config_data[0], ses->remote_client_config_data.size())},
|
||||
{"Config", this->generate_client_config_json(ses->config)},
|
||||
{"Language", name_for_language_code(ses->language())},
|
||||
{"LobbyClientID", ses->lobby_client_id},
|
||||
{"LeaderClientID", ses->leader_client_id},
|
||||
{"LocationX", ses->x},
|
||||
{"LocationZ", ses->z},
|
||||
{"LocationFloor", ses->floor},
|
||||
{"IsInGame", ses->is_in_game},
|
||||
{"IsInQuest", ses->is_in_quest},
|
||||
{"LobbyEvent", ses->lobby_event},
|
||||
{"LobbyDifficulty", name_for_difficulty(ses->lobby_difficulty)},
|
||||
{"LobbySectionID", name_for_section_id(ses->lobby_section_id)},
|
||||
{"LobbyMode", name_for_mode(ses->lobby_mode)},
|
||||
{"LobbyEpisode", name_for_episode(ses->lobby_episode)},
|
||||
{"LobbyRandomSeed", ses->lobby_random_seed},
|
||||
{"LobbyPlayers", std::move(lobby_players_json)},
|
||||
});
|
||||
switch (ses->drop_mode) {
|
||||
case ProxyServer::LinkedSession::DropMode::DISABLED:
|
||||
ret.emplace("DropMode", "none");
|
||||
break;
|
||||
case ProxyServer::LinkedSession::DropMode::PASSTHROUGH:
|
||||
ret.emplace("DropMode", "default");
|
||||
break;
|
||||
case ProxyServer::LinkedSession::DropMode::INTERCEPT:
|
||||
ret.emplace("DropMode", "proxy");
|
||||
break;
|
||||
}
|
||||
ret.emplace("License", ses->license ? this->generate_license_json(ses->license) : JSON(nullptr));
|
||||
return ret;
|
||||
}
|
||||
|
||||
JSON HTTPServer::generate_lobby_json(shared_ptr<const Lobby> l) const {
|
||||
std::array<std::shared_ptr<Client>, 12> clients;
|
||||
|
||||
auto client_ids_json = JSON::list();
|
||||
for (size_t z = 0; z < l->max_clients; z++) {
|
||||
client_ids_json.emplace_back(l->clients[z] ? l->clients[z]->id : JSON(nullptr));
|
||||
}
|
||||
|
||||
auto ret = JSON::dict({
|
||||
{"ID", l->lobby_id},
|
||||
{"AllowedVersions", l->allowed_versions},
|
||||
{"Event", l->event},
|
||||
{"LeaderClientID", l->leader_id},
|
||||
{"MaxClients", l->max_clients},
|
||||
{"IdleTimeoutUsecs", l->idle_timeout_usecs},
|
||||
{"ClientIDs", std::move(client_ids_json)},
|
||||
{"IsGame", l->is_game()},
|
||||
{"IsPersistent", l->check_flag(Lobby::Flag::PERSISTENT)},
|
||||
});
|
||||
|
||||
if (l->is_game()) {
|
||||
ret.emplace("CheatsEnabled", l->check_flag(Lobby::Flag::CHEATS_ENABLED));
|
||||
ret.emplace("MinLevel", l->min_level + 1);
|
||||
ret.emplace("MaxLevel", l->max_level + 1);
|
||||
ret.emplace("BaseVersion", l->base_version);
|
||||
ret.emplace("Episode", name_for_episode(l->episode));
|
||||
ret.emplace("HasPassword", !l->password.empty());
|
||||
ret.emplace("Name", l->name);
|
||||
ret.emplace("RandomSeed", l->random_seed);
|
||||
if (l->episode != Episode::EP3) {
|
||||
ret.emplace("QuestInProgress", l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS));
|
||||
ret.emplace("JoinableQuestInProgress", l->check_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS));
|
||||
auto variations_json = JSON::list();
|
||||
for (size_t z = 0; z < l->variations.size(); z++) {
|
||||
variations_json.emplace_back(l->variations[z].load());
|
||||
}
|
||||
ret.emplace("Variations", std::move(variations_json));
|
||||
ret.emplace("SectionID", name_for_section_id(l->section_id));
|
||||
ret.emplace("Mode", name_for_mode(l->mode));
|
||||
ret.emplace("Difficulty", name_for_difficulty(l->difficulty));
|
||||
ret.emplace("BaseEXPMultiplier", l->base_exp_multiplier);
|
||||
ret.emplace("AllowedDropModes", l->allowed_drop_modes);
|
||||
switch (l->drop_mode) {
|
||||
case Lobby::DropMode::DISABLED:
|
||||
ret.emplace("DropMode", "none");
|
||||
break;
|
||||
case Lobby::DropMode::CLIENT:
|
||||
ret.emplace("DropMode", "client");
|
||||
break;
|
||||
case Lobby::DropMode::SERVER_SHARED:
|
||||
ret.emplace("DropMode", "shared");
|
||||
break;
|
||||
case Lobby::DropMode::SERVER_PRIVATE:
|
||||
ret.emplace("DropMode", "private");
|
||||
break;
|
||||
case Lobby::DropMode::SERVER_DUPLICATE:
|
||||
ret.emplace("DropMode", "duplicate");
|
||||
break;
|
||||
}
|
||||
if (l->mode == GameMode::CHALLENGE) {
|
||||
ret.emplace("ChallengeEXPMultiplier", l->challenge_exp_multiplier);
|
||||
if (l->challenge_params) {
|
||||
ret.emplace("ChallengeStageNumber", l->challenge_params->stage_number);
|
||||
ret.emplace("ChallengeRankColor", l->challenge_params->rank_color);
|
||||
ret.emplace("ChallengeRankText", l->challenge_params->rank_text);
|
||||
ret.emplace("ChallengeRank0ThresholdBitmask", l->challenge_params->rank_thresholds[0].bitmask);
|
||||
ret.emplace("ChallengeRank0ThresholdSeconds", l->challenge_params->rank_thresholds[0].seconds);
|
||||
ret.emplace("ChallengeRank1ThresholdBitmask", l->challenge_params->rank_thresholds[1].bitmask);
|
||||
ret.emplace("ChallengeRank1ThresholdSeconds", l->challenge_params->rank_thresholds[1].seconds);
|
||||
ret.emplace("ChallengeRank2ThresholdBitmask", l->challenge_params->rank_thresholds[2].bitmask);
|
||||
ret.emplace("ChallengeRank2ThresholdSeconds", l->challenge_params->rank_thresholds[2].seconds);
|
||||
}
|
||||
}
|
||||
|
||||
auto floor_items_json = JSON::list();
|
||||
for (size_t floor = 0; floor < l->floor_item_managers.size(); floor++) {
|
||||
for (const auto& it : l->floor_item_managers[floor].items) {
|
||||
const auto& item = it.second;
|
||||
string description = this->state->describe_item(l->base_version, item->data, false);
|
||||
string data_str = item->data.hex();
|
||||
auto item_dict = JSON::dict({
|
||||
{"LocationFloor", floor},
|
||||
{"LocationX", item->x},
|
||||
{"LocationZ", item->z},
|
||||
{"DropNumber", item->drop_number},
|
||||
{"VisibilityFlags", item->visibility_flags},
|
||||
{"Data", std::move(data_str)},
|
||||
{"Description", std::move(description)},
|
||||
{"ItemID", item->data.id.load()},
|
||||
});
|
||||
floor_items_json.emplace_back(std::move(item_dict));
|
||||
}
|
||||
}
|
||||
ret.emplace("FloorItems", std::move(floor_items_json));
|
||||
if (l->quest) {
|
||||
auto battle_rules_json = l->quest->battle_rules ? l->quest->battle_rules->json() : nullptr;
|
||||
auto challenge_template_index_json = (l->quest->challenge_template_index >= 0)
|
||||
? l->quest->challenge_template_index
|
||||
: JSON(nullptr);
|
||||
auto quest_json = JSON::dict({
|
||||
{"Number", l->quest->quest_number},
|
||||
{"Episode", name_for_episode(l->quest->episode)},
|
||||
{"Joinable", l->quest->joinable},
|
||||
{"Name", l->quest->name},
|
||||
{"BattleRules", std::move(battle_rules_json)},
|
||||
{"ChallengeTemplateIndex", std::move(challenge_template_index_json)},
|
||||
});
|
||||
ret.emplace("Quest", std::move(quest_json));
|
||||
} else {
|
||||
ret.emplace("Quest", nullptr);
|
||||
}
|
||||
|
||||
} else {
|
||||
ret.emplace("BattleInProgress", l->check_flag(Lobby::Flag::BATTLE_IN_PROGRESS));
|
||||
ret.emplace("IsSpectatorTeam", l->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM));
|
||||
ret.emplace("SpectatorsForbidden", l->check_flag(Lobby::Flag::SPECTATORS_FORBIDDEN));
|
||||
|
||||
auto ep3s = l->ep3_server;
|
||||
if (ep3s) {
|
||||
auto players_json = JSON::list();
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
if (!ep3s->name_entries[z].present) {
|
||||
players_json.emplace_back(nullptr);
|
||||
} else {
|
||||
auto lc = l->clients[z];
|
||||
|
||||
auto deck_entry = ep3s->deck_entries[z];
|
||||
JSON deck_json = nullptr;
|
||||
if (deck_entry) {
|
||||
auto cards_json = JSON::list();
|
||||
for (size_t w = 0; w < deck_entry->card_ids.size(); w++) {
|
||||
try {
|
||||
const auto& ce = ep3s->options.card_index->definition_for_id(deck_entry->card_ids[w]);
|
||||
auto name = ce->def.en_name.decode();
|
||||
if (name.empty()) {
|
||||
name = ce->def.en_short_name.decode();
|
||||
}
|
||||
if (name.empty()) {
|
||||
name = ce->def.jp_name.decode();
|
||||
}
|
||||
if (name.empty()) {
|
||||
name = ce->def.jp_short_name.decode();
|
||||
}
|
||||
cards_json.emplace_back(name);
|
||||
} catch (const out_of_range&) {
|
||||
cards_json.emplace_back(deck_entry->card_ids[w].load());
|
||||
}
|
||||
}
|
||||
deck_json = JSON::dict({
|
||||
{"Name", deck_entry->name.decode(lc ? lc->language() : 1)},
|
||||
{"TeamID", deck_entry->team_id.load()},
|
||||
{"Cards", std::move(cards_json)},
|
||||
{"GodWhimFlag", deck_entry->god_whim_flag},
|
||||
{"PlayerLevel", deck_entry->player_level.load()},
|
||||
});
|
||||
}
|
||||
|
||||
auto player_json = JSON::dict({
|
||||
{"PlayerName", ep3s->name_entries[z].name.decode(lc ? lc->language() : 1)},
|
||||
{"ClientID", ep3s->name_entries[z].client_id},
|
||||
{"IsCOM", !!ep3s->name_entries[z].is_cpu_player},
|
||||
{"Deck", std::move(deck_json)},
|
||||
});
|
||||
players_json.emplace_back(std::move(player_json));
|
||||
}
|
||||
}
|
||||
auto battle_state_json = JSON::dict({
|
||||
{"BehaviorFlags", ep3s->options.behavior_flags},
|
||||
{"RandomSeed", ep3s->options.random_crypt ? ep3s->options.random_crypt->seed() : JSON(nullptr)},
|
||||
{"RandomOffset", ep3s->options.random_crypt ? ep3s->options.random_crypt->absolute_offset() : JSON(nullptr)},
|
||||
{"Tournament", ep3s->options.tournament ? ep3s->options.tournament->json() : nullptr},
|
||||
{"MapNumber", ep3s->last_chosen_map ? ep3s->last_chosen_map->map_number : JSON(nullptr)},
|
||||
{"EnvironmentNumber", ep3s->map_and_rules ? ep3s->map_and_rules->environment_number : JSON(nullptr)},
|
||||
{"Rules", ep3s->map_and_rules ? ep3s->map_and_rules->rules.json() : nullptr},
|
||||
{"Players", std::move(players_json)},
|
||||
{"IsBattleFinished", ep3s->battle_finished},
|
||||
{"IsBattleInprogress", ep3s->battle_in_progress},
|
||||
{"RoundNumber", ep3s->round_num},
|
||||
{"FirstTeamTurn", ep3s->first_team_turn},
|
||||
{"CurrentTeamTurn", ep3s->current_team_turn1},
|
||||
{"BattlePhase", name_for_enum(ep3s->battle_phase)},
|
||||
{"SetupPhase", ep3s->setup_phase},
|
||||
{"RegistrationPhase", ep3s->registration_phase},
|
||||
{"ActionSubphase", ep3s->action_subphase},
|
||||
{"BattleStartTimeUsecs", ep3s->battle_start_usecs},
|
||||
{"TeamEXP", JSON::list({ep3s->team_exp[0], ep3s->team_exp[1]})},
|
||||
{"TeamDiceBonus", JSON::list({ep3s->team_dice_bonus[0], ep3s->team_dice_bonus[1]})},
|
||||
});
|
||||
// std::shared_ptr<StateFlags> state_flags;
|
||||
// std::array<std::shared_ptr<PlayerState>, 4> player_states;
|
||||
ret.emplace("Episode3BattleState", std::move(battle_state_json));
|
||||
} else {
|
||||
ret.emplace("Episode3BattleState", nullptr);
|
||||
}
|
||||
auto watched_lobby = l->watched_lobby.lock();
|
||||
if (watched_lobby) {
|
||||
ret.emplace("WatchedLobbyID", watched_lobby->lobby_id);
|
||||
}
|
||||
auto watcher_lobby_ids_json = JSON::list();
|
||||
for (const auto& watcher_lobby : l->watcher_lobbies) {
|
||||
watcher_lobby_ids_json.emplace_back(watcher_lobby->lobby_id);
|
||||
}
|
||||
ret.emplace("WatcherLobbyIDs", std::move(watcher_lobby_ids_json));
|
||||
ret.emplace("IsReplayLobby", !!l->battle_player);
|
||||
}
|
||||
|
||||
} else { // Not game
|
||||
ret.emplace("IsPublic", l->check_flag(Lobby::Flag::PUBLIC));
|
||||
ret.emplace("IsDefault", l->check_flag(Lobby::Flag::DEFAULT));
|
||||
ret.emplace("IsOverflow", l->check_flag(Lobby::Flag::IS_OVERFLOW));
|
||||
ret.emplace("Block", l->block);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
JSON HTTPServer::generate_game_server_clients_json() const {
|
||||
JSON res = JSON::list();
|
||||
for (const auto& it : this->state->channel_to_client) {
|
||||
res.emplace_back(this->generate_game_client_json(it.second));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
JSON HTTPServer::generate_proxy_server_clients_json() const {
|
||||
JSON res = JSON::list();
|
||||
for (const auto& it : this->state->proxy_server->all_sessions()) {
|
||||
res.emplace_back(this->generate_proxy_client_json(it.second));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
JSON HTTPServer::generate_lobbies_json() const {
|
||||
JSON res = JSON::list();
|
||||
for (const auto& it : this->state->id_to_lobby) {
|
||||
res.emplace_back(this->generate_lobby_json(it.second));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
JSON HTTPServer::generate_summary_json() const {
|
||||
auto clients_json = JSON::list();
|
||||
for (const auto& it : this->state->channel_to_client) {
|
||||
auto c = it.second;
|
||||
auto p = c->character(false, false);
|
||||
auto l = c->lobby.lock();
|
||||
clients_json.emplace_back(JSON::dict({
|
||||
{"ID", c->id},
|
||||
{"SerialNumber", c->license ? c->license->serial_number : JSON(nullptr)},
|
||||
{"Name", p ? p->disp.name.decode(it.second->language()) : JSON(nullptr)},
|
||||
{"Version", name_for_enum(it.second->version())},
|
||||
{"Language", name_for_language_code(it.second->language())},
|
||||
{"Level", p ? p->disp.stats.level + 1 : JSON(nullptr)},
|
||||
{"Class", p ? name_for_char_class(p->disp.visual.char_class) : JSON(nullptr)},
|
||||
{"SectionID", p ? name_for_section_id(p->disp.visual.section_id) : JSON(nullptr)},
|
||||
{"LobbyID", l ? l->lobby_id : JSON(nullptr)},
|
||||
}));
|
||||
}
|
||||
|
||||
auto proxy_clients_json = JSON::list();
|
||||
for (const auto& it : this->state->proxy_server->all_sessions()) {
|
||||
proxy_clients_json.emplace_back(JSON::dict({
|
||||
{"SerialNumber", it.second->license ? it.second->license->serial_number : JSON(nullptr)},
|
||||
{"Name", it.second->character_name},
|
||||
{"Version", name_for_enum(it.second->version())},
|
||||
{"Language", name_for_language_code(it.second->language())},
|
||||
}));
|
||||
}
|
||||
|
||||
auto games_json = JSON::list();
|
||||
for (const auto& it : this->state->id_to_lobby) {
|
||||
auto l = it.second;
|
||||
if (l->is_game()) {
|
||||
games_json.emplace_back(JSON::dict({
|
||||
{"ID", l->lobby_id},
|
||||
{"Name", l->name},
|
||||
{"BaseVersion", name_for_enum(l->base_version)},
|
||||
{"Players", l->count_clients()},
|
||||
{"CheatsEnabled", l->check_flag(Lobby::Flag::CHEATS_ENABLED)},
|
||||
{"QuestInProgress", l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)},
|
||||
{"JoinableQuestInProgress", l->check_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS)},
|
||||
{"BattleInProgress", l->check_flag(Lobby::Flag::BATTLE_IN_PROGRESS)},
|
||||
{"IsSpectatorTeam", l->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM)},
|
||||
{"SectionID", name_for_section_id(l->section_id)},
|
||||
{"Episode", name_for_episode(l->episode)},
|
||||
{"Mode", name_for_mode(l->mode)},
|
||||
{"Difficulty", name_for_difficulty(l->difficulty)},
|
||||
{"HasPassword", !l->password.empty()},
|
||||
}));
|
||||
auto ep3s = l->ep3_server;
|
||||
if (l->episode == Episode::EP3 && ep3s) {
|
||||
games_json.back().emplace("Ep3MapNumber", ep3s->last_chosen_map ? ep3s->last_chosen_map->map_number : JSON(nullptr));
|
||||
games_json.back().emplace("Ep3Rules", ep3s->map_and_rules ? ep3s->map_and_rules->rules.json() : nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return JSON::dict({
|
||||
{"Clients", std::move(clients_json)},
|
||||
{"ProxyClients", std::move(proxy_clients_json)},
|
||||
{"Games", std::move(games_json)},
|
||||
});
|
||||
}
|
||||
|
||||
JSON HTTPServer::generate_all_json() const {
|
||||
return JSON::dict({
|
||||
{"Clients", this->generate_game_server_clients_json()},
|
||||
{"ProxyClients", this->generate_proxy_server_clients_json()},
|
||||
{"Lobbies", this->generate_lobbies_json()},
|
||||
});
|
||||
}
|
||||
|
||||
void HTTPServer::handle_request(struct evhttp_request* req) {
|
||||
JSON ret;
|
||||
uint32_t serialize_options = 0;
|
||||
try {
|
||||
string uri = evhttp_request_get_uri(req);
|
||||
|
||||
std::unordered_multimap<std::string, std::string> query;
|
||||
size_t query_pos = uri.find('?');
|
||||
if (query_pos != string::npos) {
|
||||
query = this->parse_url_params(uri.substr(query_pos + 1));
|
||||
uri.resize(query_pos);
|
||||
}
|
||||
|
||||
static const string default_format_option = "false";
|
||||
if (this->get_url_param(query, "format", &default_format_option) == "true") {
|
||||
serialize_options = JSON::SerializeOption::FORMAT | JSON::SerializeOption::SORT_DICT_KEYS;
|
||||
}
|
||||
|
||||
if (uri == "/") {
|
||||
auto endpoints_json = JSON::list({
|
||||
"/y/clients",
|
||||
"/y/proxy-clients",
|
||||
"/y/lobbies",
|
||||
"/y/summary",
|
||||
"/y/all",
|
||||
});
|
||||
ret = JSON::dict({{"endpoints", std::move(endpoints_json)}});
|
||||
|
||||
} else if (uri == "/y/clients") {
|
||||
ret = this->generate_game_server_clients_json();
|
||||
} else if (uri == "/y/proxy-clients") {
|
||||
ret = this->generate_proxy_server_clients_json();
|
||||
} else if (uri == "/y/lobbies") {
|
||||
ret = this->generate_lobbies_json();
|
||||
} else if (uri == "/y/summary") {
|
||||
ret = this->generate_summary_json();
|
||||
} else if (uri == "/y/all") {
|
||||
ret = this->generate_all_json();
|
||||
|
||||
} else {
|
||||
throw http_error(404, "unknown action");
|
||||
}
|
||||
|
||||
} catch (const http_error& e) {
|
||||
unique_ptr<struct evbuffer, void (*)(struct evbuffer*)> out_buffer(evbuffer_new(), evbuffer_free);
|
||||
evbuffer_add_printf(out_buffer.get(), "%s", e.what());
|
||||
this->send_response(req, e.code, "text/plain", out_buffer.get());
|
||||
return;
|
||||
|
||||
} catch (const exception& e) {
|
||||
unique_ptr<struct evbuffer, void (*)(struct evbuffer*)> out_buffer(evbuffer_new(), evbuffer_free);
|
||||
evbuffer_add_printf(out_buffer.get(), "Error during request: %s", e.what());
|
||||
this->send_response(req, 500, "text/plain", out_buffer.get());
|
||||
server_log.warning("internal server error during http request: %s", e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
unique_ptr<struct evbuffer, void (*)(struct evbuffer*)> out_buffer(evbuffer_new(), evbuffer_free);
|
||||
string* serialized = new string(ret.serialize(JSON::SerializeOption::ESCAPE_CONTROLS_ONLY | serialize_options));
|
||||
auto cleanup = +[](const void*, size_t, void* s) -> void {
|
||||
delete reinterpret_cast<string*>(s);
|
||||
};
|
||||
evbuffer_add_reference(out_buffer.get(), serialized->data(), serialized->size(), cleanup, serialized);
|
||||
this->send_response(req, 200, "application/json", out_buffer.get());
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/event.h>
|
||||
#include <event2/http.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "ProxyServer.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
class HTTPServer {
|
||||
public:
|
||||
HTTPServer(std::shared_ptr<ServerState> state);
|
||||
HTTPServer(const HTTPServer&) = delete;
|
||||
HTTPServer(HTTPServer&&) = delete;
|
||||
HTTPServer& operator=(const HTTPServer&) = delete;
|
||||
HTTPServer& operator=(HTTPServer&&) = delete;
|
||||
virtual ~HTTPServer() = default;
|
||||
|
||||
void listen(const std::string& socket_path);
|
||||
void listen(const std::string& addr, int port);
|
||||
void listen(int port);
|
||||
void add_socket(int fd);
|
||||
|
||||
protected:
|
||||
class http_error : public std::runtime_error {
|
||||
public:
|
||||
http_error(int code, const std::string& what);
|
||||
int code;
|
||||
};
|
||||
|
||||
std::shared_ptr<ServerState> state;
|
||||
std::shared_ptr<struct evhttp> http;
|
||||
|
||||
static void dispatch_handle_request(struct evhttp_request* req, void* ctx);
|
||||
void handle_request(struct evhttp_request* req);
|
||||
|
||||
static const std::unordered_map<int, const char*> explanation_for_response_code;
|
||||
static void send_response(struct evhttp_request* req, int code, const char* content_type, struct evbuffer* b);
|
||||
static void send_response(struct evhttp_request* req, int code, const char* content_type, const char* fmt, ...);
|
||||
|
||||
static std::unordered_multimap<std::string, std::string> parse_url_params(const std::string& query);
|
||||
static std::unordered_map<std::string, std::string> parse_url_params_unique(const std::string& query);
|
||||
static const std::string& get_url_param(
|
||||
const std::unordered_multimap<std::string, std::string>& params,
|
||||
const std::string& key,
|
||||
const std::string* _default = nullptr);
|
||||
|
||||
JSON generate_client_config_json(const Client::Config& config) const;
|
||||
JSON generate_license_json(std::shared_ptr<const License> l) const;
|
||||
JSON generate_game_client_json(std::shared_ptr<const Client> c) const;
|
||||
JSON generate_proxy_client_json(std::shared_ptr<const ProxyServer::LinkedSession> ses) const;
|
||||
JSON generate_lobby_json(std::shared_ptr<const Lobby> l) const;
|
||||
JSON generate_game_server_clients_json() const;
|
||||
JSON generate_proxy_server_clients_json() const;
|
||||
JSON generate_lobbies_json() const;
|
||||
JSON generate_summary_json() const;
|
||||
JSON generate_all_json() const;
|
||||
};
|
||||
+11
@@ -28,6 +28,7 @@
|
||||
#include "DNSServer.hh"
|
||||
#include "GSLArchive.hh"
|
||||
#include "GVMEncoder.hh"
|
||||
#include "HTTPServer.hh"
|
||||
#include "IPStackSimulator.hh"
|
||||
#include "Loggers.hh"
|
||||
#include "NetworkAddresses.hh"
|
||||
@@ -2368,6 +2369,7 @@ Action a_run_server_replay_log(
|
||||
shared_ptr<ServerShell> shell;
|
||||
shared_ptr<ReplaySession> replay_session;
|
||||
shared_ptr<IPStackSimulator> ip_stack_simulator;
|
||||
shared_ptr<HTTPServer> http_server;
|
||||
if (is_replay) {
|
||||
config_log.info("Starting proxy server");
|
||||
state->proxy_server = make_shared<ProxyServer>(base, state);
|
||||
@@ -2453,6 +2455,15 @@ Action a_run_server_replay_log(
|
||||
ip_stack_simulator->listen(spec, netloc.first, netloc.second, FrameInfo::LinkType::HDLC);
|
||||
}
|
||||
}
|
||||
|
||||
if (!state->http_addresses.empty() || !state->http_addresses.empty()) {
|
||||
config_log.info("Starting HTTP server");
|
||||
http_server = make_shared<HTTPServer>(state);
|
||||
for (const auto& it : state->http_addresses) {
|
||||
auto netloc = parse_netloc(it);
|
||||
http_server->listen(netloc.first, netloc.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!state->username.empty()) {
|
||||
|
||||
+6
-3
@@ -843,7 +843,7 @@ void ProxyServer::LinkedSession::on_input(Channel& ch, uint16_t command, uint32_
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<ProxyServer::LinkedSession> ProxyServer::get_session() {
|
||||
shared_ptr<ProxyServer::LinkedSession> ProxyServer::get_session() const {
|
||||
if (this->id_to_session.empty()) {
|
||||
throw runtime_error("no sessions exist");
|
||||
}
|
||||
@@ -853,8 +853,7 @@ shared_ptr<ProxyServer::LinkedSession> ProxyServer::get_session() {
|
||||
return this->id_to_session.begin()->second;
|
||||
}
|
||||
|
||||
shared_ptr<ProxyServer::LinkedSession> ProxyServer::get_session_by_name(
|
||||
const std::string& name) {
|
||||
shared_ptr<ProxyServer::LinkedSession> ProxyServer::get_session_by_name(const std::string& name) const {
|
||||
try {
|
||||
uint64_t session_id = stoull(name, nullptr, 16);
|
||||
return this->id_to_session.at(session_id);
|
||||
@@ -865,6 +864,10 @@ shared_ptr<ProxyServer::LinkedSession> ProxyServer::get_session_by_name(
|
||||
}
|
||||
}
|
||||
|
||||
const unordered_map<uint64_t, shared_ptr<ProxyServer::LinkedSession>>& ProxyServer::all_sessions() const {
|
||||
return this->id_to_session;
|
||||
}
|
||||
|
||||
shared_ptr<ProxyServer::LinkedSession> ProxyServer::create_licensed_session(
|
||||
shared_ptr<License> l,
|
||||
uint16_t local_port,
|
||||
|
||||
+6
-2
@@ -173,13 +173,17 @@ public:
|
||||
|
||||
void clear_lobby_players(size_t num_slots);
|
||||
|
||||
void set_drop_mode(DropMode new_mode);
|
||||
|
||||
void send_to_game_server(const char* error_message = nullptr);
|
||||
void disconnect();
|
||||
bool is_connected() const;
|
||||
};
|
||||
|
||||
std::shared_ptr<LinkedSession> get_session();
|
||||
std::shared_ptr<LinkedSession> get_session_by_name(const std::string& name);
|
||||
std::shared_ptr<LinkedSession> get_session() const;
|
||||
std::shared_ptr<LinkedSession> get_session_by_name(const std::string& name) const;
|
||||
const std::unordered_map<uint64_t, std::shared_ptr<LinkedSession>>& all_sessions() const;
|
||||
|
||||
std::shared_ptr<LinkedSession> create_licensed_session(
|
||||
std::shared_ptr<License> l,
|
||||
uint16_t local_port,
|
||||
|
||||
+1
-5
@@ -216,11 +216,7 @@ Server::Server(
|
||||
destroy_clients_ev(event_new(this->base.get(), -1, EV_TIMEOUT, &Server::dispatch_destroy_clients, this), event_free),
|
||||
state(state) {}
|
||||
|
||||
void Server::listen(
|
||||
const std::string& addr_str,
|
||||
const string& socket_path,
|
||||
Version version,
|
||||
ServerBehavior behavior) {
|
||||
void Server::listen(const std::string& addr_str, const string& socket_path, Version version, ServerBehavior behavior) {
|
||||
int fd = ::listen(socket_path, 0, SOMAXCONN);
|
||||
server_log.info("Listening on Unix socket %s on fd %d as %s",
|
||||
socket_path.c_str(), fd, addr_str.c_str());
|
||||
|
||||
@@ -615,6 +615,16 @@ void ServerState::load_config() {
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
try {
|
||||
for (const auto& item : json.at("HTTPListen").as_list()) {
|
||||
if (item->is_int()) {
|
||||
this->http_addresses.emplace_back(string_printf("0.0.0.0:%" PRId64, item->as_int()));
|
||||
} else {
|
||||
this->http_addresses.emplace_back(item->as_string());
|
||||
}
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
}
|
||||
|
||||
auto local_address_str = json.at("LocalAddress").as_string();
|
||||
|
||||
@@ -78,6 +78,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
uint16_t dns_server_port = 0;
|
||||
std::vector<std::string> ip_stack_addresses;
|
||||
std::vector<std::string> ppp_stack_addresses;
|
||||
std::vector<std::string> http_addresses;
|
||||
uint64_t client_ping_interval_usecs = 30000000;
|
||||
uint64_t client_idle_timeout_usecs = 60000000;
|
||||
uint64_t patch_client_idle_timeout_usecs = 300000000;
|
||||
|
||||
+18
-6
@@ -229,7 +229,7 @@ const char* abbreviation_for_section_id(uint8_t section_id) {
|
||||
if (section_id < section_id_to_abbreviation.size()) {
|
||||
return section_id_to_abbreviation[section_id];
|
||||
} else {
|
||||
return "<Unknown>";
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ const char* name_for_section_id(uint8_t section_id) {
|
||||
if (section_id < section_id_to_name.size()) {
|
||||
return section_id_to_name[section_id];
|
||||
} else {
|
||||
return "<Unknown>";
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,7 +262,7 @@ const string& name_for_event(uint8_t event) {
|
||||
if (event < lobby_event_to_name.size()) {
|
||||
return lobby_event_to_name[event];
|
||||
} else {
|
||||
static const string ret = "<Unknown lobby event>";
|
||||
static const string ret = "Unknown lobby event";
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -287,7 +287,7 @@ const string& name_for_lobby_type(uint8_t type) {
|
||||
try {
|
||||
return lobby_type_to_name.at(type);
|
||||
} catch (const out_of_range&) {
|
||||
static const string ret = "<Unknown lobby type>";
|
||||
static const string ret = "Unknown lobby type";
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -312,7 +312,7 @@ const string& name_for_npc(uint8_t npc) {
|
||||
try {
|
||||
return npc_id_to_name.at(npc);
|
||||
} catch (const out_of_range&) {
|
||||
static const string ret = "<Unknown NPC>";
|
||||
static const string ret = "Unknown NPC";
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -459,6 +459,18 @@ char abbreviation_for_difficulty(uint8_t difficulty) {
|
||||
}
|
||||
}
|
||||
|
||||
const char* name_for_language_code(uint8_t language_code) {
|
||||
array<const char*, 8> names = {{"Japanese",
|
||||
"English",
|
||||
"German",
|
||||
"French",
|
||||
"Spanish",
|
||||
"Simplified Chinese",
|
||||
"Traditional Chinese",
|
||||
"Korean"}};
|
||||
return (language_code < 8) ? names[language_code] : "Unknown";
|
||||
}
|
||||
|
||||
char char_for_language_code(uint8_t language_code) {
|
||||
return (language_code < 8) ? "JEGFSBTK"[language_code] : '?';
|
||||
}
|
||||
@@ -544,7 +556,7 @@ const string& name_for_technique(uint8_t tech) {
|
||||
try {
|
||||
return tech_id_to_name.at(tech);
|
||||
} catch (const out_of_range&) {
|
||||
static const string ret = "<Unknown technique>";
|
||||
static const string ret = "Unknown technique";
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ const char* name_for_difficulty(uint8_t difficulty);
|
||||
const char* token_name_for_difficulty(uint8_t difficulty);
|
||||
char abbreviation_for_difficulty(uint8_t difficulty);
|
||||
|
||||
const char* name_for_language_code(uint8_t language_code);
|
||||
char char_for_language_code(uint8_t language_code);
|
||||
uint8_t language_code_for_char(char language_char);
|
||||
|
||||
|
||||
@@ -150,11 +150,19 @@
|
||||
// On Windows, Unix sockets are not available, so you can only use TCP sockets
|
||||
// here. You can get Dolphin to connect locally by adding a port to this list
|
||||
// and configuring Dolphin to connect to the same port. For example, you could
|
||||
// set this to ["127.0.0.1:5059"], and configure Dolphin's tapserver BBA to
|
||||
// connect to 127.0.0.1:5059.
|
||||
// set this to ["127.0.0.1:5059"] (which listens on port 5059 but only accepts
|
||||
// connections from the local machine), and configure Dolphin's tapserver BBA
|
||||
// to connect to 127.0.0.1:5059.
|
||||
"IPStackListen": [],
|
||||
"PPPStackListen": [],
|
||||
|
||||
// Where to listen for HTTP connections. The HTTP server is intended as a
|
||||
// private interface to interact with newserv from e.g. an in-house Web portal
|
||||
// or Discord bot. It would be unwise to expose any of these ports to the
|
||||
// public Internet (hence why the default here is blank). The format of
|
||||
// entries in this list is the same as for IPStackListen and PPPStackListen.
|
||||
"HTTPListen": [],
|
||||
|
||||
// Other servers to support proxying to. If this is empty for any game
|
||||
// version, the proxy server is disabled for that version. Entries in these
|
||||
// dictionaries should be of the form "name": "address:port"; the names are
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
"DNSServerPort": 0,
|
||||
"IPStackListen": [],
|
||||
"PPPStackListen": [],
|
||||
"HTTPListen": [],
|
||||
"Episode3BehaviorFlags": 0xFA,
|
||||
|
||||
"Episode3InfiniteMeseta": false,
|
||||
|
||||
Reference in New Issue
Block a user