Relay TeamSync team chat events

This commit is contained in:
Your Name
2026-06-12 20:40:15 -04:00
parent 989eabe3a0
commit 133041f09b
4 changed files with 141 additions and 1 deletions
+10
View File
@@ -6241,6 +6241,16 @@ static asio::awaitable<void> on_EA_BB(std::shared_ptr<Client> c, Channel::Messag
} catch (const std::out_of_range&) {
}
}
if (TeamSync::relay_team_chat_enabled()) {
TeamSync::enqueue_team_chat(
team->team_id,
c->login->account->account_id,
c->character_file()->disp.visual.name.decode(c->language()),
msg.data.data(),
msg.data.size());
co_await TeamSync::exchange_once_now();
}
}
}
break;
+63
View File
@@ -27,6 +27,31 @@ static constexpr bool IS_WINDOWS = true;
static constexpr bool IS_WINDOWS = false;
#endif
static uint8_t team_sync_hex_value_server_state(char ch) {
if (ch >= '0' && ch <= '9') {
return ch - '0';
} else if (ch >= 'a' && ch <= 'f') {
return 10 + ch - 'a';
} else if (ch >= 'A' && ch <= 'F') {
return 10 + ch - 'A';
}
throw std::runtime_error("invalid hex character");
}
static std::string team_sync_decode_hex_string_server_state(const std::string& hex) {
if (hex.size() & 1) {
throw std::runtime_error("hex string has odd length");
}
std::string ret;
ret.resize(hex.size() / 2);
for (size_t z = 0; z < ret.size(); z++) {
ret[z] = static_cast<char>(
(team_sync_hex_value_server_state(hex[z * 2]) << 4) |
team_sync_hex_value_server_state(hex[z * 2 + 1]));
}
return ret;
}
CheatFlags::CheatFlags(const phosg::JSON& json) : CheatFlags() {
std::unordered_set<std::string> enabled_keys;
for (const auto& it : json.as_list()) {
@@ -1758,6 +1783,44 @@ void ServerState::load_teams() {
config_log.warning_f("Failed to apply canonical TeamSync team state: {}", e.what());
}
});
TeamSync::set_inbound_event_callback([this](const phosg::JSON& event) -> void {
try {
if (!this->team_index) {
return;
}
std::string type = event.get_string("type", "");
if (type != "team_chat") {
return;
}
uint32_t team_id = event.get_int("team_id", 0);
uint32_t sender_account_id = event.get_int("sender_account_id", 0);
std::string message_data_hex = event.get_string("message_data_hex", "");
std::string message_data = team_sync_decode_hex_string_server_state(message_data_hex);
auto team = this->team_index->get_by_id(team_id);
if (!team) {
return;
}
for (const auto& it : team->members) {
uint32_t target_account_id = it.second.account_id;
if (target_account_id == sender_account_id) {
continue;
}
try {
auto target_c = this->find_client(nullptr, target_account_id);
send_command(target_c, 0x07EA, 0x00000000, message_data);
} catch (const std::out_of_range&) {
}
}
} catch (const std::exception& e) {
config_log.warning_f("Failed to apply inbound TeamSync event: {}", e.what());
}
});
}
void ServerState::load_patch_indexes() {
+64 -1
View File
@@ -40,6 +40,10 @@ struct OutboundEvent {
uint32_t account_id = 0;
std::string name;
uint32_t sender_account_id = 0;
std::string sender_name;
std::string message_data_hex;
std::string team_name;
uint32_t creator_account_id = 0;
std::string creator_name;
@@ -65,6 +69,9 @@ static std::deque<OutboundEvent> outbound_events;
static std::mutex canonical_team_state_callback_mutex;
static CanonicalTeamStateCallback canonical_team_state_callback;
static std::mutex inbound_event_callback_mutex;
static InboundEventCallback inbound_event_callback;
static std::string source_label(const Config& cfg) {
if (!cfg.source.empty()) {
return cfg.source;
@@ -486,6 +493,31 @@ bool relay_team_actions_enabled() {
return cfg.enabled && cfg.relay_team_actions;
}
bool enqueue_team_chat(uint32_t team_id, uint32_t sender_account_id, const std::string& sender_name, const void* data, size_t size) {
auto cfg = get_config();
if (!cfg.enabled || !cfg.relay_team_chat || team_id == 0 || sender_account_id == 0 || !data || !size || size > 2048) {
return false;
}
std::lock_guard<std::mutex> g(exchange_state_mutex);
if (outbound_events.size() >= MAX_OUTBOUND_EVENTS) {
std::fprintf(stderr,
"[TeamSync] warning outbound_event_dropped reason=queue_full source=%s team_namespace=%s type=team_chat\n",
source_label(cfg).c_str(),
cfg.team_namespace.c_str());
return false;
}
OutboundEvent ev;
ev.type = "team_chat";
ev.team_id = team_id;
ev.sender_account_id = sender_account_id;
ev.sender_name = sender_name;
ev.message_data_hex = hex_encode(data, size);
outbound_events.emplace_back(std::move(ev));
return true;
}
bool enqueue_team_create(const std::string& team_name, uint32_t creator_account_id, const std::string& creator_name) {
auto cfg = get_config();
if (!cfg.enabled || !cfg.relay_team_actions) {
@@ -724,6 +756,22 @@ void set_canonical_team_state_callback(CanonicalTeamStateCallback cb) {
canonical_team_state_callback = std::move(cb);
}
void set_inbound_event_callback(InboundEventCallback cb) {
std::lock_guard<std::mutex> g(inbound_event_callback_mutex);
inbound_event_callback = std::move(cb);
}
static void apply_inbound_event(const phosg::JSON& event) {
InboundEventCallback cb;
{
std::lock_guard<std::mutex> g(inbound_event_callback_mutex);
cb = inbound_event_callback;
}
if (cb) {
cb(event);
}
}
static void apply_canonical_team_state(const phosg::JSON& canonical_team_state) {
CanonicalTeamStateCallback cb;
{
@@ -753,7 +801,17 @@ static std::string exchange_body_for_current_state(const Config& cfg) {
}
first = false;
if (ev.type == "team_create") {
if (ev.type == "team_chat") {
parts += std::format(
"{{\"seq\":{},\"type\":\"team_chat\",\"team_namespace\":\"{}\",\"team_id\":{},\"sender_account_id\":{},\"sender_name\":\"{}\",\"message_data_hex\":\"{}\"}}",
ev.seq,
json_escape(cfg.team_namespace),
ev.team_id,
ev.sender_account_id,
json_escape(ev.sender_name),
ev.message_data_hex);
} else if (ev.type == "team_create") {
parts += std::format(
"{{\"seq\":{},\"type\":\"team_create\",\"team_namespace\":\"{}\",\"creator_account_id\":{},\"creator_name\":\"{}\",\"team_name\":\"{}\"}}",
ev.seq,
@@ -873,6 +931,11 @@ static asio::awaitable<void> run_empty_exchange_once(const Config& cfg) {
bool truncated = response.get_bool("truncated", false);
size_t inbound_events = response.get("events", phosg::JSON::list()).as_list().size();
const auto& events_json = response.get("events", phosg::JSON::list()).as_list();
for (const auto& event_json_p : events_json) {
apply_inbound_event(*event_json_p);
}
const auto& canonical_team_state = response.get("canonical_team_state", phosg::JSON::dict());
if (!canonical_team_state.as_dict().empty()) {
apply_canonical_team_state(canonical_team_state);
+4
View File
@@ -36,6 +36,7 @@ bool relay_team_chat_enabled();
bool relay_team_points_enabled();
bool relay_team_actions_enabled();
bool enqueue_team_chat(uint32_t team_id, uint32_t sender_account_id, const std::string& sender_name, const void* data, size_t size);
bool enqueue_team_create(const std::string& team_name, uint32_t creator_account_id, const std::string& creator_name);
bool enqueue_team_member_add(uint32_t team_id, uint32_t account_id, const std::string& name);
bool enqueue_team_member_update(uint32_t account_id, const std::string& name, int64_t points_delta);
@@ -51,6 +52,9 @@ asio::awaitable<bool> exchange_once_now();
using CanonicalTeamStateCallback = std::function<void(const phosg::JSON&)>;
void set_canonical_team_state_callback(CanonicalTeamStateCallback cb);
using InboundEventCallback = std::function<void(const phosg::JSON&)>;
void set_inbound_event_callback(InboundEventCallback cb);
void start_exchange_task(asio::io_context& io_context);
} // namespace TeamSync