make some progress toward getting psogc to work again
This commit is contained in:
+5
-2
@@ -5,6 +5,7 @@
|
|||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
|
|
||||||
|
#include <phosg/Encoding.hh>
|
||||||
#include <phosg/Network.hh>
|
#include <phosg/Network.hh>
|
||||||
#include <phosg/Strings.hh>
|
#include <phosg/Strings.hh>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -93,8 +94,9 @@ void DNSServer::run_thread() {
|
|||||||
if (bytes > 0) {
|
if (bytes > 0) {
|
||||||
input.resize(bytes);
|
input.resize(bytes);
|
||||||
|
|
||||||
|
uint32_t remote_address = bswap32(remote.sin_addr.s_addr);
|
||||||
uint32_t connect_address;
|
uint32_t connect_address;
|
||||||
if (is_local_address(remote.sin_addr.s_addr)) {
|
if (is_local_address(remote_address)) {
|
||||||
connect_address = this->local_connect_address;
|
connect_address = this->local_connect_address;
|
||||||
} else {
|
} else {
|
||||||
connect_address = this->external_connect_address;
|
connect_address = this->external_connect_address;
|
||||||
@@ -121,11 +123,12 @@ string DNSServer::build_response(const std::string& input,
|
|||||||
string ret;
|
string ret;
|
||||||
size_t name_len = strlen(input.data() + 0x0C) + 1;
|
size_t name_len = strlen(input.data() + 0x0C) + 1;
|
||||||
|
|
||||||
|
uint32_t connect_address_be = bswap32(connect_address);
|
||||||
ret.append(input.substr(0, 2));
|
ret.append(input.substr(0, 2));
|
||||||
ret.append("\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00", 10);
|
ret.append("\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00", 10);
|
||||||
ret.append(input.substr(12, name_len));
|
ret.append(input.substr(12, name_len));
|
||||||
ret.append("\x00\x01\x00\x01\xC0\x0C\x00\x01\x00\x01\x00\x00\x00\x3C\x00\x04", 16);
|
ret.append("\x00\x01\x00\x01\xC0\x0C\x00\x01\x00\x01\x00\x00\x00\x3C\x00\x04", 16);
|
||||||
ret.append(reinterpret_cast<const char*>(&connect_address), 4);
|
ret.append(reinterpret_cast<const char*>(&connect_address_be), 4);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -34,7 +34,7 @@ struct License {
|
|||||||
char gc_password[12]; // GC password
|
char gc_password[12]; // GC password
|
||||||
uint32_t privileges; // privilege level
|
uint32_t privileges; // privilege level
|
||||||
uint64_t ban_end_time; // end time of ban (zero = not banned)
|
uint64_t ban_end_time; // end time of ban (zero = not banned)
|
||||||
};
|
} __attribute__((packed));
|
||||||
|
|
||||||
class LicenseManager {
|
class LicenseManager {
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ void Lobby::reassign_leader_on_client_departure_locked(size_t leaving_client_ind
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw out_of_range("no clients remain");
|
this->leader_id = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Lobby::any_client_loading() const {
|
bool Lobby::any_client_loading() const {
|
||||||
@@ -68,11 +68,11 @@ void Lobby::add_client(shared_ptr<Client> c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Lobby::add_client_locked(shared_ptr<Client> c) {
|
void Lobby::add_client_locked(shared_ptr<Client> c) {
|
||||||
rw_guard g(this->lock, true);
|
|
||||||
size_t index;
|
size_t index;
|
||||||
for (index = 0; index < this->max_clients; index++) {
|
for (index = 0; index < this->max_clients; index++) {
|
||||||
if (!this->clients[index].get()) {
|
if (!this->clients[index].get()) {
|
||||||
this->clients[index] = c;
|
this->clients[index] = c;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (index >= this->max_clients) {
|
if (index >= this->max_clients) {
|
||||||
@@ -101,8 +101,6 @@ void Lobby::remove_client(shared_ptr<Client> c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Lobby::remove_client_locked(shared_ptr<Client> c) {
|
void Lobby::remove_client_locked(shared_ptr<Client> c) {
|
||||||
rw_guard g(this->lock, true);
|
|
||||||
|
|
||||||
if (this->clients[c->lobby_client_id] != c) {
|
if (this->clients[c->lobby_client_id] != c) {
|
||||||
throw logic_error("client\'s lobby client id does not match client list");
|
throw logic_error("client\'s lobby client id does not match client list");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
#include <arpa/inet.h>
|
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
#include <event2/event.h>
|
||||||
|
#include <event2/thread.h>
|
||||||
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <phosg/JSON.hh>
|
#include <phosg/JSON.hh>
|
||||||
#include <phosg/Strings.hh>
|
#include <phosg/Strings.hh>
|
||||||
|
#include <phosg/Filesystem.hh>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
|
||||||
#include "NetworkAddresses.hh"
|
#include "NetworkAddresses.hh"
|
||||||
|
#include "SendCommands.hh"
|
||||||
#include "DNSServer.hh"
|
#include "DNSServer.hh"
|
||||||
#include "ServerState.hh"
|
#include "ServerState.hh"
|
||||||
#include "Server.hh"
|
#include "Server.hh"
|
||||||
@@ -61,7 +64,7 @@ void populate_state_from_config(shared_ptr<ServerState> s,
|
|||||||
shared_ptr<JSONObject> config_json) {
|
shared_ptr<JSONObject> config_json) {
|
||||||
const auto& d = config_json->as_dict();
|
const auto& d = config_json->as_dict();
|
||||||
|
|
||||||
s->name = d.at("ServerName")->as_string();
|
s->name = decode_sjis(d.at("ServerName")->as_string());
|
||||||
|
|
||||||
// TODO: make this configurable
|
// TODO: make this configurable
|
||||||
s->port_configuration = default_port_to_behavior;
|
s->port_configuration = default_port_to_behavior;
|
||||||
@@ -76,36 +79,38 @@ void populate_state_from_config(shared_ptr<ServerState> s,
|
|||||||
box_categories, unit_types));
|
box_categories, unit_types));
|
||||||
|
|
||||||
shared_ptr<vector<MenuItem>> information_menu(new vector<MenuItem>());
|
shared_ptr<vector<MenuItem>> information_menu(new vector<MenuItem>());
|
||||||
shared_ptr<unordered_map<uint32_t, u16string>> id_to_information_contents(
|
shared_ptr<vector<u16string>> information_contents(new vector<u16string>());
|
||||||
new unordered_map<uint32_t, u16string>());
|
|
||||||
|
|
||||||
uint32_t item_id = 1;
|
information_menu->emplace_back(INFORMATION_MENU_GO_BACK, u"Go back",
|
||||||
|
u"Return to the\nmain menu", 0);
|
||||||
|
|
||||||
|
uint32_t item_id = 0;
|
||||||
for (const auto& item : d.at("InformationMenuContents")->as_list()) {
|
for (const auto& item : d.at("InformationMenuContents")->as_list()) {
|
||||||
auto& v = item->as_list();
|
auto& v = item->as_list();
|
||||||
information_menu->emplace_back(item_id, decode_sjis(v.at(0)->as_string()),
|
information_menu->emplace_back(item_id, decode_sjis(v.at(0)->as_string()),
|
||||||
decode_sjis(v.at(1)->as_string()), MenuItemFlag::RequiresMessageBoxes);
|
decode_sjis(v.at(1)->as_string()), MenuItemFlag::RequiresMessageBoxes);
|
||||||
id_to_information_contents->emplace(item_id, decode_sjis(v.at(2)->as_string()));
|
information_contents->emplace_back(decode_sjis(v.at(2)->as_string()));
|
||||||
item_id++;
|
item_id++;
|
||||||
}
|
}
|
||||||
s->information_menu = information_menu;
|
s->information_menu = information_menu;
|
||||||
s->id_to_information_contents = id_to_information_contents;
|
s->information_contents = information_contents;
|
||||||
|
|
||||||
s->num_threads = d.at("Threads")->as_int();
|
s->num_threads = d.at("Threads")->as_int();
|
||||||
|
|
||||||
auto local_address_str = d.at("LocalAddress")->as_string();
|
auto local_address_str = d.at("LocalAddress")->as_string();
|
||||||
uint32_t local_address = inet_addr(local_address_str.c_str());
|
s->local_address = address_for_string(local_address_str.c_str());
|
||||||
if (s->all_addresses.emplace(local_address).second) {
|
s->all_addresses.emplace(s->local_address);
|
||||||
log(INFO, "added local address: %hhu.%hhu.%hhu.%hhu",
|
log(INFO, "added local address: %s", local_address_str.c_str());
|
||||||
static_cast<uint8_t>(local_address >> 24), static_cast<uint8_t>(local_address >> 16),
|
|
||||||
static_cast<uint8_t>(local_address >> 8), static_cast<uint8_t>(local_address));
|
|
||||||
}
|
|
||||||
|
|
||||||
auto external_address_str = d.at("LocalAddress")->as_string();
|
auto external_address_str = d.at("ExternalAddress")->as_string();
|
||||||
uint32_t external_address = inet_addr(external_address_str.c_str());
|
s->external_address = address_for_string(external_address_str.c_str());
|
||||||
if (s->all_addresses.emplace(external_address).second) {
|
s->all_addresses.emplace(s->external_address);
|
||||||
log(INFO, "added external address: %hhu.%hhu.%hhu.%hhu",
|
log(INFO, "added external address: %s", external_address_str.c_str());
|
||||||
static_cast<uint8_t>(external_address >> 24), static_cast<uint8_t>(external_address >> 16),
|
|
||||||
static_cast<uint8_t>(external_address >> 8), static_cast<uint8_t>(external_address));
|
try {
|
||||||
|
s->run_dns_server = d.at("RunDNSServer")->as_bool();
|
||||||
|
} catch (const JSONObject::key_error&) {
|
||||||
|
s->run_dns_server = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,6 +120,9 @@ int main(int argc,char* argv[]) {
|
|||||||
log(INFO, "fuzziqer software newserv");
|
log(INFO, "fuzziqer software newserv");
|
||||||
|
|
||||||
signal(SIGPIPE, SIG_IGN);
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
if (evthread_use_pthreads()) {
|
||||||
|
log(ERROR, "cannot enable multithreading in libevent");
|
||||||
|
}
|
||||||
|
|
||||||
log(INFO, "creating server state");
|
log(INFO, "creating server state");
|
||||||
shared_ptr<ServerState> state(new ServerState());
|
shared_ptr<ServerState> state(new ServerState());
|
||||||
@@ -122,9 +130,8 @@ int main(int argc,char* argv[]) {
|
|||||||
log(INFO, "reading network addresses");
|
log(INFO, "reading network addresses");
|
||||||
state->all_addresses = get_local_address_list();
|
state->all_addresses = get_local_address_list();
|
||||||
for (uint32_t addr : state->all_addresses) {
|
for (uint32_t addr : state->all_addresses) {
|
||||||
log(INFO, "found address: %hhu.%hhu.%hhu.%hhu",
|
string addr_str = string_for_address(addr);
|
||||||
static_cast<uint8_t>(addr >> 24), static_cast<uint8_t>(addr >> 16),
|
log(INFO, "found address: %s", addr_str.c_str());
|
||||||
static_cast<uint8_t>(addr >> 8), static_cast<uint8_t>(addr));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log(INFO, "loading configuration");
|
log(INFO, "loading configuration");
|
||||||
@@ -143,15 +150,20 @@ int main(int argc,char* argv[]) {
|
|||||||
log(INFO, "collecting quest metadata");
|
log(INFO, "collecting quest metadata");
|
||||||
state->quest_index.reset(new QuestIndex("system/quests"));
|
state->quest_index.reset(new QuestIndex("system/quests"));
|
||||||
|
|
||||||
log(INFO, "starting dns server");
|
shared_ptr<DNSServer> dns_server;
|
||||||
DNSServer dns_server(state->local_address, state->external_address);
|
if (state->run_dns_server) {
|
||||||
// TODO: call dns_server.listen appropriately
|
log(INFO, "starting dns server on port 53");
|
||||||
dns_server.start();
|
dns_server.reset(new DNSServer(state->local_address, state->external_address));
|
||||||
|
dns_server->listen("", 53);
|
||||||
|
dns_server->start();
|
||||||
|
}
|
||||||
|
|
||||||
log(INFO, "starting game server");
|
log(INFO, "starting game server");
|
||||||
Server game_server(state);
|
shared_ptr<Server> game_server(new Server(state));
|
||||||
// TODO: call game_server.listen appropriately
|
for (const auto& it : state->port_configuration) {
|
||||||
game_server.start();
|
game_server->listen("", it.second.port, it.second.version, it.second.behavior);
|
||||||
|
}
|
||||||
|
game_server->start();
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
sigset_t s;
|
sigset_t s;
|
||||||
@@ -160,10 +172,10 @@ int main(int argc,char* argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log(INFO, "waiting for servers to terminate");
|
log(INFO, "waiting for servers to terminate");
|
||||||
dns_server.schedule_stop();
|
dns_server->schedule_stop();
|
||||||
game_server.schedule_stop();
|
game_server->schedule_stop();
|
||||||
dns_server.wait_for_stop();
|
dns_server->wait_for_stop();
|
||||||
game_server.wait_for_stop();
|
game_server->wait_for_stop();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ OBJECTS=FileContentsCache.o Menu.o PSOProtocol.o Client.o Lobby.o \
|
|||||||
Text.o DNSServer.o Main.o
|
Text.o DNSServer.o Main.o
|
||||||
CXX=g++
|
CXX=g++
|
||||||
CXXFLAGS=-I/opt/local/include -I/usr/local/include -std=c++14 -g -DHAVE_INTTYPES_H -DHAVE_NETINET_IN_H -Wall -Werror
|
CXXFLAGS=-I/opt/local/include -I/usr/local/include -std=c++14 -g -DHAVE_INTTYPES_H -DHAVE_NETINET_IN_H -Wall -Werror
|
||||||
LDFLAGS=-L/opt/local/lib -L/usr/local/lib -std=c++14 -levent -lphosg -lpthread
|
LDFLAGS=-L/opt/local/lib -L/usr/local/lib -std=c++14 -levent -levent_pthreads -lphosg -lpthread
|
||||||
EXECUTABLE=newserv
|
EXECUTABLE=newserv
|
||||||
|
|
||||||
all: $(EXECUTABLE)
|
all: $(EXECUTABLE)
|
||||||
|
|||||||
+20
-2
@@ -41,7 +41,7 @@ uint32_t resolve_address(const char* address) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct sockaddr_in* res_sin = (struct sockaddr_in*)res4->ai_addr;
|
struct sockaddr_in* res_sin = (struct sockaddr_in*)res4->ai_addr;
|
||||||
return res_sin->sin_addr.s_addr;
|
return bswap32(res_sin->sin_addr.s_addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
set<uint32_t> get_local_address_list() {
|
set<uint32_t> get_local_address_list() {
|
||||||
@@ -71,9 +71,27 @@ set<uint32_t> get_local_address_list() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool is_local_address(uint32_t addr) {
|
bool is_local_address(uint32_t addr) {
|
||||||
uint8_t net = addr & 0xFF;
|
uint8_t net = (addr >> 24) & 0xFF;
|
||||||
if ((net != 127) && (net != 172) && (net != 10) && (net != 192)) {
|
if ((net != 127) && (net != 172) && (net != 10) && (net != 192)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool is_local_address(const sockaddr_storage& daddr) {
|
||||||
|
if (daddr.ss_family != AF_INET) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const sockaddr_in* sin = reinterpret_cast<const sockaddr_in*>(&daddr);
|
||||||
|
return is_local_address(bswap32(sin->sin_addr.s_addr));
|
||||||
|
}
|
||||||
|
|
||||||
|
string string_for_address(uint32_t address) {
|
||||||
|
return string_printf("%hhu.%hhu.%hhu.%hhu",
|
||||||
|
static_cast<uint8_t>(address >> 24), static_cast<uint8_t>(address >> 16),
|
||||||
|
static_cast<uint8_t>(address >> 8), static_cast<uint8_t>(address));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t address_for_string(const char* address) {
|
||||||
|
return bswap32(inet_addr(address));
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <netinet/in.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -13,3 +15,7 @@ uint32_t resolve_address(const char* address);
|
|||||||
std::set<uint32_t> get_local_address_list();
|
std::set<uint32_t> get_local_address_list();
|
||||||
uint32_t get_connected_address(int fd);
|
uint32_t get_connected_address(int fd);
|
||||||
bool is_local_address(uint32_t daddr);
|
bool is_local_address(uint32_t daddr);
|
||||||
|
bool is_local_address(const sockaddr_storage& daddr);
|
||||||
|
|
||||||
|
std::string string_for_address(uint32_t address);
|
||||||
|
uint32_t address_for_string(const char* address);
|
||||||
|
|||||||
+5
-8
@@ -130,25 +130,22 @@ PSOGCEncryption::PSOGCEncryption(uint32_t seed) : offset(0) {
|
|||||||
basekey = basekey & 0x7FFFFFFF;
|
basekey = basekey & 0x7FFFFFFF;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this->stream[this->offset] = basekey;
|
this->stream[this->offset++] = basekey;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->stream[this->offset] = (((this->stream[0] >> 9) ^ (this->stream[this->offset] << 23)) ^ this->stream[15]);
|
this->stream[this->offset - 1] = (((this->stream[0] >> 9) ^ (this->stream[this->offset - 1] << 23)) ^ this->stream[15]);
|
||||||
|
|
||||||
source1 = 0;
|
source1 = 0;
|
||||||
source2 = 1;
|
source2 = 1;
|
||||||
source3 = this->offset - 1;
|
source3 = this->offset - 1;
|
||||||
while (this->offset != GC_STREAM_LENGTH) {
|
while (this->offset != GC_STREAM_LENGTH) {
|
||||||
this->stream[this->offset] = (this->stream[source3] ^ (((this->stream[source1] << 23) & 0xFF800000) ^ ((this->stream[source2] >> 9) & 0x007FFFFF)));
|
this->stream[this->offset++] = (this->stream[source3++] ^ (((this->stream[source1++] << 23) & 0xFF800000) ^ ((this->stream[source2++] >> 9) & 0x007FFFFF)));
|
||||||
this->offset++;
|
|
||||||
source1++;
|
|
||||||
source2++;
|
|
||||||
source3++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t x = 0; x < 4; x++) {
|
for (size_t x = 0; x < 3; x++) {
|
||||||
this->update_stream();
|
this->update_stream();
|
||||||
}
|
}
|
||||||
|
this->offset = GC_STREAM_LENGTH - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PSOGCEncryption::encrypt(void* vdata, size_t size) {
|
void PSOGCEncryption::encrypt(void* vdata, size_t size) {
|
||||||
|
|||||||
@@ -308,7 +308,10 @@ shared_ptr<const string> Quest::dat_contents() const {
|
|||||||
|
|
||||||
|
|
||||||
QuestIndex::QuestIndex(const char* directory) : directory(directory) {
|
QuestIndex::QuestIndex(const char* directory) : directory(directory) {
|
||||||
for (const auto& filename : list_directory(this->directory)) {
|
auto filename_set = list_directory(this->directory);
|
||||||
|
vector<string> filenames(filename_set.begin(), filename_set.end());
|
||||||
|
sort(filenames.begin(), filenames.end());
|
||||||
|
for (const auto& filename : filenames) {
|
||||||
string full_path = this->directory + "/" + filename;
|
string full_path = this->directory + "/" + filename;
|
||||||
|
|
||||||
if (ends_with(filename, ".gba")) {
|
if (ends_with(filename, ".gba")) {
|
||||||
|
|||||||
+88
-28
@@ -43,9 +43,10 @@ void process_connect(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c)
|
|||||||
switch (c->server_behavior) {
|
switch (c->server_behavior) {
|
||||||
case ServerBehavior::SplitReconnect: {
|
case ServerBehavior::SplitReconnect: {
|
||||||
uint16_t pc_port = s->port_configuration.at("pc-login").port;
|
uint16_t pc_port = s->port_configuration.at("pc-login").port;
|
||||||
send_pc_gc_split_reconnect(c, 0, pc_port);
|
uint16_t gc_port = s->port_configuration.at("gc-jp10").port;
|
||||||
c->server_behavior = ServerBehavior::LoginServer;
|
send_pc_gc_split_reconnect(c, s->connect_address_for_client(c), pc_port, gc_port);
|
||||||
// intentional fallthrough
|
c->should_disconnect = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case ServerBehavior::LoginServer:
|
case ServerBehavior::LoginServer:
|
||||||
@@ -68,7 +69,7 @@ void process_login_complete(shared_ptr<ServerState> s, shared_ptr<Client> c) {
|
|||||||
send_ep3_rank_update(c);
|
send_ep3_rank_update(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
send_menu(c, u"Main menu", MAIN_MENU_ID, s->main_menu, false);
|
send_menu(c, s->name.c_str(), MAIN_MENU_ID, s->main_menu, false);
|
||||||
|
|
||||||
} else if (c->server_behavior == ServerBehavior::LobbyServer) {
|
} else if (c->server_behavior == ServerBehavior::LobbyServer) {
|
||||||
|
|
||||||
@@ -146,6 +147,7 @@ void process_verify_license_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
|||||||
uint32_t sub_version;
|
uint32_t sub_version;
|
||||||
char unused3[0x60];
|
char unused3[0x60];
|
||||||
char password[0x10];
|
char password[0x10];
|
||||||
|
char unused4[0x20];
|
||||||
};
|
};
|
||||||
check_size(size, sizeof(Cmd));
|
check_size(size, sizeof(Cmd));
|
||||||
const auto* cmd = reinterpret_cast<const Cmd*>(data);
|
const auto* cmd = reinterpret_cast<const Cmd*>(data);
|
||||||
@@ -242,6 +244,7 @@ void process_login_d_e_pc_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
|||||||
char unused3[0x60];
|
char unused3[0x60];
|
||||||
char name[0x10];
|
char name[0x10];
|
||||||
ClientConfig cfg;
|
ClientConfig cfg;
|
||||||
|
uint8_t unused4[0x64];
|
||||||
};
|
};
|
||||||
check_size(size, sizeof(Cmd));
|
check_size(size, sizeof(Cmd));
|
||||||
const auto* cmd = reinterpret_cast<const Cmd*>(data);
|
const auto* cmd = reinterpret_cast<const Cmd*>(data);
|
||||||
@@ -315,7 +318,8 @@ void process_login_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
|||||||
switch (c->config.cfg.bb_game_state) {
|
switch (c->config.cfg.bb_game_state) {
|
||||||
case ClientStateBB::InitialLogin:
|
case ClientStateBB::InitialLogin:
|
||||||
// first login? send them to the other port
|
// first login? send them to the other port
|
||||||
send_reconnect(c, 0, s->port_configuration.at("bb-data1").port);
|
send_reconnect(c, s->connect_address_for_client(c),
|
||||||
|
s->port_configuration.at("bb-data1").port);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ClientStateBB::DownloadData: {
|
case ClientStateBB::DownloadData: {
|
||||||
@@ -340,13 +344,13 @@ void process_login_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
send_reconnect(c, 0, s->port_configuration.at("bb-login").port);
|
send_reconnect(c, s->connect_address_for_client(c),
|
||||||
|
s->port_configuration.at("bb-login").port);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void process_client_checksum(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
void process_client_checksum(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||||
uint16_t command, uint32_t flag, uint16_t size, const void* data) { // 96
|
uint16_t command, uint32_t flag, uint16_t size, const void* data) { // 96
|
||||||
check_size(size, 0);
|
|
||||||
send_command(c, 0x97, 0x01);
|
send_command(c, 0x97, 0x01);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,13 +483,61 @@ void process_message_box_closed(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
|||||||
if (c->in_information_menu) {
|
if (c->in_information_menu) {
|
||||||
// add a reference to ensure it's not destroyed by another thread
|
// add a reference to ensure it's not destroyed by another thread
|
||||||
auto info_menu = s->information_menu;
|
auto info_menu = s->information_menu;
|
||||||
send_menu(c, u"Information", INFORMATION_MENU_ID, *info_menu, true);
|
send_menu(c, u"Information", INFORMATION_MENU_ID, *info_menu, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void process_menu_item_info_request(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||||
|
uint16_t command, uint32_t flag, uint16_t size, const void* data) { // 09
|
||||||
|
struct Cmd {
|
||||||
|
uint32_t menu_id;
|
||||||
|
uint32_t item_id;
|
||||||
|
};
|
||||||
|
check_size(size, sizeof(Cmd), sizeof(Cmd));
|
||||||
|
const auto* cmd = reinterpret_cast<const Cmd*>(data);
|
||||||
|
|
||||||
|
switch (cmd->menu_id) {
|
||||||
|
case MAIN_MENU_ID:
|
||||||
|
switch (cmd->item_id) {
|
||||||
|
case MAIN_MENU_GO_TO_LOBBY:
|
||||||
|
send_ship_info(c, u"Go to the lobby.");
|
||||||
|
break;
|
||||||
|
case MAIN_MENU_INFORMATION:
|
||||||
|
send_ship_info(c, u"View server information.");
|
||||||
|
break;
|
||||||
|
case MAIN_MENU_DISCONNECT:
|
||||||
|
send_ship_info(c, u"End your session.");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
send_ship_info(c, u"Incorrect menu item ID.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case INFORMATION_MENU_ID:
|
||||||
|
if (cmd->item_id == INFORMATION_MENU_GO_BACK) {
|
||||||
|
send_ship_info(c, u"Return to the\nmain menu.");
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
// add a reference to ensure it's not destroyed by another thread
|
||||||
|
// we use item_id + 1 here because "go back" is the first item
|
||||||
|
auto info_menu = s->information_menu;
|
||||||
|
send_ship_info(c, info_menu->at(cmd->item_id + 1).description.c_str());
|
||||||
|
} catch (const out_of_range&) {
|
||||||
|
send_ship_info(c, u"$C6No such information exists.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
send_ship_info(c, u"Incorrect menu ID.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||||
uint16_t command, uint32_t flag, uint16_t size, const void* data) { // 09 10
|
uint16_t command, uint32_t flag, uint16_t size, const void* data) { // 10
|
||||||
bool uses_unicode = ((c->version == GameVersion::PC) || (c->version == GameVersion::BB));
|
bool uses_unicode = ((c->version == GameVersion::PC) || (c->version == GameVersion::BB));
|
||||||
|
|
||||||
struct Cmd {
|
struct Cmd {
|
||||||
@@ -504,16 +556,17 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
|||||||
switch (cmd->item_id) {
|
switch (cmd->item_id) {
|
||||||
case MAIN_MENU_GO_TO_LOBBY: {
|
case MAIN_MENU_GO_TO_LOBBY: {
|
||||||
static const vector<string> version_to_port_name({
|
static const vector<string> version_to_port_name({
|
||||||
"dc-lobby", "dc-lobby", "pc-lobby", "bb-lobby", "gc-lobby", "bb-lobby"});
|
"dc-lobby", "pc-lobby", "bb-lobby", "gc-lobby", "bb-lobby"});
|
||||||
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version));
|
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version));
|
||||||
|
|
||||||
send_reconnect(c, 0, s->port_configuration.at(port_name).port);
|
send_reconnect(c, s->connect_address_for_client(c),
|
||||||
|
s->port_configuration.at(port_name).port);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case MAIN_MENU_INFORMATION: {
|
case MAIN_MENU_INFORMATION: {
|
||||||
auto info_menu = s->information_menu;
|
auto info_menu = s->information_menu;
|
||||||
send_menu(c, u"Information", INFORMATION_MENU_ID, *info_menu, true);
|
send_menu(c, u"Information", INFORMATION_MENU_ID, *info_menu, false);
|
||||||
c->in_information_menu = true;
|
c->in_information_menu = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -526,17 +579,18 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
|||||||
send_message_box(c, u"Incorrect menu item ID.");
|
send_message_box(c, u"Incorrect menu item ID.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case INFORMATION_MENU_ID: {
|
case INFORMATION_MENU_ID: {
|
||||||
if (cmd->item_id == 0) {
|
if (cmd->item_id == INFORMATION_MENU_GO_BACK) {
|
||||||
c->in_information_menu = false;
|
c->in_information_menu = false;
|
||||||
send_menu(c, u"Main menu", MAIN_MENU_ID, s->main_menu, false);
|
send_menu(c, s->name.c_str(), MAIN_MENU_ID, s->main_menu, false);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
// add a reference to ensure it's not destroyed by another thread
|
// add a reference to ensure it's not destroyed by another thread
|
||||||
auto info_menu = s->id_to_information_contents;
|
auto info_menu = s->information_contents;
|
||||||
send_message_box(c, info_menu->at(cmd->item_id).c_str());
|
send_message_box(c, info_menu->at(cmd->item_id).c_str());
|
||||||
} catch (const out_of_range&) {
|
} catch (const out_of_range&) {
|
||||||
send_message_box(c, u"$C6No such information exists.");
|
send_message_box(c, u"$C6No such information exists.");
|
||||||
@@ -720,14 +774,14 @@ void process_game_list_request(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
|||||||
|
|
||||||
void process_change_ship(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
void process_change_ship(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||||
uint16_t command, uint32_t flag, uint16_t size, const void* data) { // A0
|
uint16_t command, uint32_t flag, uint16_t size, const void* data) { // A0
|
||||||
check_size(size, 0);
|
|
||||||
send_message_box(c, u""); // we do this to avoid the "log window in message box" bug
|
send_message_box(c, u""); // we do this to avoid the "log window in message box" bug
|
||||||
|
|
||||||
static const vector<string> version_to_port_name({
|
static const vector<string> version_to_port_name({
|
||||||
"dc-login", "dc-login", "pc-login", "bb-patch", "gc-login", "bb-login"});
|
"dc-login", "pc-login", "bb-patch", "gc-us3", "bb-login"});
|
||||||
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version));
|
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version));
|
||||||
|
|
||||||
send_reconnect(c, 0, s->port_configuration.at(port_name).port);
|
send_reconnect(c, s->connect_address_for_client(c),
|
||||||
|
s->port_configuration.at(port_name).port);
|
||||||
}
|
}
|
||||||
|
|
||||||
void process_change_block(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
void process_change_block(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||||
@@ -850,18 +904,20 @@ void process_player_data(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
|||||||
uint16_t command, uint32_t flag, uint16_t size, const void* data) { // 61 98
|
uint16_t command, uint32_t flag, uint16_t size, const void* data) { // 61 98
|
||||||
|
|
||||||
{
|
{
|
||||||
|
// note: we add extra buffer on the end when checking sizes because the
|
||||||
|
// autoreply text is a variable length
|
||||||
rw_guard g(c->lock, true);
|
rw_guard g(c->lock, true);
|
||||||
switch (c->version) {
|
switch (c->version) {
|
||||||
case GameVersion::PC:
|
case GameVersion::PC:
|
||||||
check_size(size, sizeof(PSOPlayerDataPC));
|
check_size(size, sizeof(PSOPlayerDataPC), sizeof(PSOPlayerDataPC) + 2 * 0xAC);
|
||||||
c->player.import(*reinterpret_cast<const PSOPlayerDataPC*>(data));
|
c->player.import(*reinterpret_cast<const PSOPlayerDataPC*>(data));
|
||||||
break;
|
break;
|
||||||
case GameVersion::GC:
|
case GameVersion::GC:
|
||||||
check_size(size, sizeof(PSOPlayerDataGC));
|
check_size(size, sizeof(PSOPlayerDataGC), sizeof(PSOPlayerDataGC) + 0xAC);
|
||||||
c->player.import(*reinterpret_cast<const PSOPlayerDataGC*>(data));
|
c->player.import(*reinterpret_cast<const PSOPlayerDataGC*>(data));
|
||||||
break;
|
break;
|
||||||
case GameVersion::BB:
|
case GameVersion::BB:
|
||||||
check_size(size, sizeof(PSOPlayerDataBB));
|
check_size(size, sizeof(PSOPlayerDataBB), sizeof(PSOPlayerDataBB) + 2 * 0xAC);
|
||||||
c->player.import(*reinterpret_cast<const PSOPlayerDataBB*>(data));
|
c->player.import(*reinterpret_cast<const PSOPlayerDataBB*>(data));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -1609,7 +1665,7 @@ static process_command_t dc_handlers[0x100] = {
|
|||||||
// 00
|
// 00
|
||||||
NULL, NULL, NULL, NULL,
|
NULL, NULL, NULL, NULL,
|
||||||
NULL, process_ignored_command, process_chat_dc_gc, NULL,
|
NULL, process_ignored_command, process_chat_dc_gc, NULL,
|
||||||
process_game_list_request, process_menu_selection, NULL, NULL,
|
process_game_list_request, process_menu_item_info_request, NULL, NULL,
|
||||||
NULL, NULL, NULL, NULL,
|
NULL, NULL, NULL, NULL,
|
||||||
|
|
||||||
// 10
|
// 10
|
||||||
@@ -1707,7 +1763,7 @@ static process_command_t pc_handlers[0x100] = {
|
|||||||
// 00
|
// 00
|
||||||
NULL, NULL, NULL, NULL,
|
NULL, NULL, NULL, NULL,
|
||||||
NULL, process_ignored_command, process_chat_pc_bb, NULL,
|
NULL, process_ignored_command, process_chat_pc_bb, NULL,
|
||||||
process_game_list_request, process_menu_selection, NULL, NULL,
|
process_game_list_request, process_menu_item_info_request, NULL, NULL,
|
||||||
NULL, NULL, NULL, NULL,
|
NULL, NULL, NULL, NULL,
|
||||||
|
|
||||||
// 10
|
// 10
|
||||||
@@ -1805,7 +1861,7 @@ static process_command_t gc_handlers[0x100] = {
|
|||||||
// 00
|
// 00
|
||||||
NULL, NULL, NULL, NULL,
|
NULL, NULL, NULL, NULL,
|
||||||
NULL, process_ignored_command, process_chat_dc_gc, NULL,
|
NULL, process_ignored_command, process_chat_dc_gc, NULL,
|
||||||
process_game_list_request, process_menu_selection, NULL, NULL,
|
process_game_list_request, process_menu_item_info_request, NULL, NULL,
|
||||||
NULL, NULL, NULL, NULL,
|
NULL, NULL, NULL, NULL,
|
||||||
|
|
||||||
// 10
|
// 10
|
||||||
@@ -1882,7 +1938,7 @@ static process_command_t gc_handlers[0x100] = {
|
|||||||
|
|
||||||
// D0
|
// D0
|
||||||
NULL, NULL, NULL, NULL,
|
NULL, NULL, NULL, NULL,
|
||||||
NULL, NULL, process_ignored_command, NULL,
|
NULL, NULL, process_message_box_closed, NULL,
|
||||||
process_info_board_request, process_write_info_board_dc_gc, NULL, process_verify_license_gc,
|
process_info_board_request, process_write_info_board_dc_gc, NULL, process_verify_license_gc,
|
||||||
process_ep3_menu_challenge, NULL, NULL, NULL,
|
process_ep3_menu_challenge, NULL, NULL, NULL,
|
||||||
|
|
||||||
@@ -1903,7 +1959,7 @@ static process_command_t bb_handlers[0x100] = {
|
|||||||
// 00
|
// 00
|
||||||
NULL, NULL, NULL, NULL,
|
NULL, NULL, NULL, NULL,
|
||||||
NULL, process_ignored_command, process_chat_pc_bb, NULL,
|
NULL, process_ignored_command, process_chat_pc_bb, NULL,
|
||||||
process_game_list_request, process_menu_selection, NULL, NULL,
|
process_game_list_request, process_menu_item_info_request, NULL, NULL,
|
||||||
NULL, NULL, NULL, NULL,
|
NULL, NULL, NULL, NULL,
|
||||||
|
|
||||||
// 10
|
// 10
|
||||||
@@ -2037,12 +2093,16 @@ static process_command_t patch_handlers[0x100] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static process_command_t* handlers[6] = {
|
static process_command_t* handlers[6] = {
|
||||||
dc_handlers, dc_handlers, pc_handlers, patch_handlers, gc_handlers, bb_handlers};
|
dc_handlers, pc_handlers, patch_handlers, gc_handlers, bb_handlers};
|
||||||
|
|
||||||
void process_command(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
void process_command(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||||
uint16_t command, uint32_t flag, uint16_t size, const void* data) {
|
uint16_t command, uint32_t flag, uint16_t size, const void* data) {
|
||||||
|
log(INFO, "received version=%d size=%04hX command=%04hX flag=%08X",
|
||||||
|
static_cast<int>(c->version), size, command, flag);
|
||||||
|
print_data(stderr, data, size);
|
||||||
|
|
||||||
auto fn = handlers[static_cast<size_t>(c->version)][command & 0xFF];
|
auto fn = handlers[static_cast<size_t>(c->version)][command & 0xFF];
|
||||||
if (!fn) {
|
if (fn) {
|
||||||
fn(s, c, command, flag, size, data);
|
fn(s, c, command, flag, size, data);
|
||||||
} else {
|
} else {
|
||||||
process_unimplemented_command(s, c, command, flag, size, data);
|
process_unimplemented_command(s, c, command, flag, size, data);
|
||||||
|
|||||||
@@ -31,13 +31,15 @@ struct ItemSubcommand {
|
|||||||
|
|
||||||
void check_size(uint16_t size, uint16_t min_size, uint16_t max_size) {
|
void check_size(uint16_t size, uint16_t min_size, uint16_t max_size) {
|
||||||
if (size < min_size) {
|
if (size < min_size) {
|
||||||
throw runtime_error("command too small");
|
throw runtime_error(string_printf("command too small (expected at least %zX bytes, got %zX bytes)",
|
||||||
|
min_size, size));
|
||||||
}
|
}
|
||||||
if (max_size == 0) {
|
if (max_size == 0) {
|
||||||
max_size = min_size;
|
max_size = min_size;
|
||||||
}
|
}
|
||||||
if (size > max_size) {
|
if (size > max_size) {
|
||||||
throw runtime_error("command too large");
|
throw runtime_error(string_printf("command too large (expected at most %zX bytes, got %zX bytes)",
|
||||||
|
max_size, size));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +66,7 @@ void forward_subcommand(shared_ptr<Lobby> l, shared_ptr<Client> c,
|
|||||||
}
|
}
|
||||||
send_command(target, command, flag, p, count * 4);
|
send_command(target, command, flag, p, count * 4);
|
||||||
} else {
|
} else {
|
||||||
|
// TODO: don't send the command back to the client it originated from
|
||||||
send_command(l, command, flag, p, count * 4);
|
send_command(l, command, flag, p, count * 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+37
-26
@@ -1,6 +1,7 @@
|
|||||||
#include "SendCommands.hh"
|
#include "SendCommands.hh"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <phosg/Encoding.hh>
|
||||||
#include <phosg/Random.hh>
|
#include <phosg/Random.hh>
|
||||||
#include <phosg/Strings.hh>
|
#include <phosg/Strings.hh>
|
||||||
#include <phosg/Time.hh>
|
#include <phosg/Time.hh>
|
||||||
@@ -66,6 +67,9 @@ void send_command(shared_ptr<Client> c, uint16_t command, uint32_t flag,
|
|||||||
throw logic_error("unimplemented game version in send_command");
|
throw logic_error("unimplemented game version in send_command");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log(INFO, "sending command");
|
||||||
|
print_data(stderr, send_data.data(), send_data.size());
|
||||||
|
|
||||||
c->send(move(send_data));
|
c->send(move(send_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,8 +125,19 @@ static void send_server_init_dc_pc_gc(shared_ptr<Client> c, const char* copyrigh
|
|||||||
send_command(c, command, 0x00, cmd);
|
send_command(c, command, 0x00, cmd);
|
||||||
|
|
||||||
rw_guard g(c->lock, true);
|
rw_guard g(c->lock, true);
|
||||||
|
switch (c->version) {
|
||||||
|
case GameVersion::DC:
|
||||||
|
case GameVersion::PC:
|
||||||
c->crypt_out.reset(new PSOPCEncryption(server_key));
|
c->crypt_out.reset(new PSOPCEncryption(server_key));
|
||||||
c->crypt_in.reset(new PSOPCEncryption(client_key));
|
c->crypt_in.reset(new PSOPCEncryption(client_key));
|
||||||
|
break;
|
||||||
|
case GameVersion::GC:
|
||||||
|
c->crypt_out.reset(new PSOGCEncryption(server_key));
|
||||||
|
c->crypt_in.reset(new PSOGCEncryption(client_key));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw invalid_argument("incorrect client version");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void send_server_init_pc(shared_ptr<Client> c, bool initial_connection) {
|
static void send_server_init_pc(shared_ptr<Client> c, bool initial_connection) {
|
||||||
@@ -211,28 +226,18 @@ void send_update_client_config(shared_ptr<Client> c) {
|
|||||||
|
|
||||||
|
|
||||||
void send_reconnect(shared_ptr<Client> c, uint32_t address, uint16_t port) {
|
void send_reconnect(shared_ptr<Client> c, uint32_t address, uint16_t port) {
|
||||||
if (!address) {
|
|
||||||
const sockaddr_in* local_addr = reinterpret_cast<const sockaddr_in*>(&c->local_addr);
|
|
||||||
address = local_addr->sin_addr.s_addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
uint32_t address;
|
uint32_t address;
|
||||||
uint16_t port;
|
uint16_t port;
|
||||||
uint16_t unused;
|
uint16_t unused;
|
||||||
} cmd = {address, port, 0};
|
} cmd = {bswap32(address), port, 0};
|
||||||
send_command(c, 0x19, 0x00, cmd);
|
send_command(c, 0x19, 0x00, cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
// sends the command (first used by Schthack) that separates PC and GC users
|
// sends the command (first used by Schthack) that separates PC and GC users
|
||||||
// that connect on the same port
|
// that connect on the same port
|
||||||
void send_pc_gc_split_reconnect(shared_ptr<Client> c, uint32_t address,
|
void send_pc_gc_split_reconnect(shared_ptr<Client> c, uint32_t address,
|
||||||
uint16_t pc_port) {
|
uint16_t pc_port, uint16_t gc_port) {
|
||||||
if (!address) {
|
|
||||||
const sockaddr_in* local_addr = reinterpret_cast<const sockaddr_in*>(&c->local_addr);
|
|
||||||
address = local_addr->sin_addr.s_addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
uint32_t pc_address;
|
uint32_t pc_address;
|
||||||
uint16_t pc_port;
|
uint16_t pc_port;
|
||||||
@@ -240,13 +245,17 @@ void send_pc_gc_split_reconnect(shared_ptr<Client> c, uint32_t address,
|
|||||||
uint8_t gc_command;
|
uint8_t gc_command;
|
||||||
uint8_t gc_flag;
|
uint8_t gc_flag;
|
||||||
uint16_t gc_size;
|
uint16_t gc_size;
|
||||||
uint8_t unused2[0xB0 - 0x1D];
|
uint32_t gc_address;
|
||||||
|
uint16_t gc_port;
|
||||||
|
uint8_t unused2[0xB0 - 0x23];
|
||||||
} __attribute__((packed)) cmd;
|
} __attribute__((packed)) cmd;
|
||||||
memset(&cmd, 0, sizeof(cmd));
|
memset(&cmd, 0, sizeof(cmd));
|
||||||
cmd.pc_address = address;
|
cmd.pc_address = bswap32(address);
|
||||||
cmd.pc_port = pc_port;
|
cmd.pc_port = pc_port;
|
||||||
cmd.gc_command = 0x19;
|
cmd.gc_command = 0x19;
|
||||||
cmd.gc_size = 0x97;
|
cmd.gc_size = 0x97;
|
||||||
|
cmd.gc_address = bswap32(address);
|
||||||
|
cmd.gc_port = gc_port;
|
||||||
send_command(c, 0x19, 0x00, cmd);
|
send_command(c, 0x19, 0x00, cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,8 +454,7 @@ static void send_large_message_pc_patch_bb(shared_ptr<Client> c, uint8_t command
|
|||||||
{0, from_serial_number};
|
{0, from_serial_number};
|
||||||
}
|
}
|
||||||
data += text;
|
data += text;
|
||||||
add_color_inplace(const_cast<char16_t*>(data.data()) +
|
add_color_inplace(data, (include_header ? sizeof(LargeMessageOptionalHeader) : 0));
|
||||||
(include_header ? sizeof(LargeMessageOptionalHeader) : 0));
|
|
||||||
data.resize((data.size() + 4) & ~3);
|
data.resize((data.size() + 4) & ~3);
|
||||||
send_command(c, command, 0x00, data);
|
send_command(c, command, 0x00, data);
|
||||||
}
|
}
|
||||||
@@ -460,8 +468,7 @@ static void send_large_message_dc_gc(shared_ptr<Client> c, uint8_t command,
|
|||||||
{0, from_serial_number};
|
{0, from_serial_number};
|
||||||
}
|
}
|
||||||
data += encode_sjis(text);
|
data += encode_sjis(text);
|
||||||
add_color_inplace(const_cast<char*>(data.data()) +
|
add_color_inplace(data, (include_header ? sizeof(LargeMessageOptionalHeader) : 0));
|
||||||
(include_header ? sizeof(LargeMessageOptionalHeader) : 0));
|
|
||||||
data.resize((data.size() + 4) & ~3);
|
data.resize((data.size() + 4) & ~3);
|
||||||
send_command(c, command, 0x00, data);
|
send_command(c, command, 0x00, data);
|
||||||
}
|
}
|
||||||
@@ -517,7 +524,7 @@ void send_chat_message(shared_ptr<Client> c, uint32_t from_serial_number,
|
|||||||
data.append(u"\x09J");
|
data.append(u"\x09J");
|
||||||
}
|
}
|
||||||
data.append(from_name);
|
data.append(from_name);
|
||||||
data.append(u"\x09J");
|
data.append(u"\x09\x09J");
|
||||||
data.append(text);
|
data.append(text);
|
||||||
send_large_message(c, 0x06, data.c_str(), from_serial_number, true);
|
send_large_message(c, 0x06, data.c_str(), from_serial_number, true);
|
||||||
}
|
}
|
||||||
@@ -637,18 +644,21 @@ static void send_card_search_result_dc_pc_gc(shared_ptr<ServerState> s,
|
|||||||
cmd.destination_command.dcgc_flag = 0x00;
|
cmd.destination_command.dcgc_flag = 0x00;
|
||||||
cmd.destination_command.dcgc_size = 0x000C;
|
cmd.destination_command.dcgc_size = 0x000C;
|
||||||
}
|
}
|
||||||
|
// TODO: make this actually make sense... currently we just take the sockname
|
||||||
|
// for the target client
|
||||||
const sockaddr_in* local_addr = reinterpret_cast<const sockaddr_in*>(&result->local_addr);
|
const sockaddr_in* local_addr = reinterpret_cast<const sockaddr_in*>(&result->local_addr);
|
||||||
cmd.destination_command.address = local_addr->sin_addr.s_addr;
|
cmd.destination_command.address = local_addr->sin_addr.s_addr;
|
||||||
cmd.destination_command.port = ntohs(local_addr->sin_port);
|
cmd.destination_command.port = ntohs(local_addr->sin_port);
|
||||||
cmd.destination_command.unused = 0;
|
cmd.destination_command.unused = 0;
|
||||||
|
|
||||||
|
auto encoded_server_name = encode_sjis(s->name);
|
||||||
if (result_lobby->is_game()) {
|
if (result_lobby->is_game()) {
|
||||||
string encoded_lobby_name = encode_sjis(result_lobby->name);
|
string encoded_lobby_name = encode_sjis(result_lobby->name);
|
||||||
snprintf(cmd.location_string, sizeof(cmd.location_string),
|
snprintf(cmd.location_string, sizeof(cmd.location_string),
|
||||||
"%s, Block 00, ,%s", encoded_lobby_name.c_str(), s->name.c_str());
|
"%s, Block 00, ,%s", encoded_lobby_name.c_str(), encoded_server_name.c_str());
|
||||||
} else {
|
} else {
|
||||||
snprintf(cmd.location_string, sizeof(cmd.location_string), "Block 00, ,%s",
|
snprintf(cmd.location_string, sizeof(cmd.location_string), "Block 00, ,%s",
|
||||||
s->name.c_str());
|
encoded_server_name.c_str());
|
||||||
}
|
}
|
||||||
cmd.menu_id = LOBBY_MENU_ID;
|
cmd.menu_id = LOBBY_MENU_ID;
|
||||||
cmd.lobby_id = result->lobby_id;
|
cmd.lobby_id = result->lobby_id;
|
||||||
@@ -696,13 +706,14 @@ static void send_card_search_result_bb(shared_ptr<ServerState> s,
|
|||||||
cmd.destination_command.port = ntohs(local_addr->sin_port);
|
cmd.destination_command.port = ntohs(local_addr->sin_port);
|
||||||
cmd.destination_command.unused = 0;
|
cmd.destination_command.unused = 0;
|
||||||
|
|
||||||
|
auto encoded_server_name = encode_sjis(s->name);
|
||||||
if (result_lobby->is_game()) {
|
if (result_lobby->is_game()) {
|
||||||
string encoded_lobby_name = encode_sjis(result_lobby->name);
|
string encoded_lobby_name = encode_sjis(result_lobby->name);
|
||||||
snprintf(cmd.location_string, sizeof(cmd.location_string),
|
snprintf(cmd.location_string, sizeof(cmd.location_string),
|
||||||
"%s, Block 00, ,%s", encoded_lobby_name.c_str(), s->name.c_str());
|
"%s, Block 00, ,%s", encoded_lobby_name.c_str(), encoded_server_name.c_str());
|
||||||
} else {
|
} else {
|
||||||
snprintf(cmd.location_string, sizeof(cmd.location_string), "Block 00, ,%s",
|
snprintf(cmd.location_string, sizeof(cmd.location_string), "Block 00, ,%s",
|
||||||
s->name.c_str());
|
encoded_server_name.c_str());
|
||||||
}
|
}
|
||||||
cmd.menu_id = LOBBY_MENU_ID;
|
cmd.menu_id = LOBBY_MENU_ID;
|
||||||
cmd.lobby_id = result->lobby_id;
|
cmd.lobby_id = result->lobby_id;
|
||||||
@@ -927,7 +938,7 @@ static void send_game_menu_pc(shared_ptr<Client> c, shared_ptr<ServerState> s) {
|
|||||||
auto& e = entries.back();
|
auto& e = entries.back();
|
||||||
e.menu_id = GAME_MENU_ID;
|
e.menu_id = GAME_MENU_ID;
|
||||||
e.game_id = 0;
|
e.game_id = 0;
|
||||||
decode_sjis(e.name, s->name.c_str(), 0x10);
|
char16cpy(e.name, s->name.c_str(), 0x10);
|
||||||
}
|
}
|
||||||
for (shared_ptr<Lobby> l : s->all_lobbies()) {
|
for (shared_ptr<Lobby> l : s->all_lobbies()) {
|
||||||
if (!l->is_game()) {
|
if (!l->is_game()) {
|
||||||
@@ -970,7 +981,7 @@ static void send_game_menu_gc(shared_ptr<Client> c, shared_ptr<ServerState> s) {
|
|||||||
auto& e = entries.back();
|
auto& e = entries.back();
|
||||||
e.menu_id = GAME_MENU_ID;
|
e.menu_id = GAME_MENU_ID;
|
||||||
e.game_id = 0;
|
e.game_id = 0;
|
||||||
strncpy(e.name, s->name.c_str(), 0x10);
|
encode_sjis(e.name, s->name.c_str(), 0x10);
|
||||||
e.flags = 0x0004;
|
e.flags = 0x0004;
|
||||||
}
|
}
|
||||||
for (shared_ptr<Lobby> l : s->all_lobbies()) {
|
for (shared_ptr<Lobby> l : s->all_lobbies()) {
|
||||||
@@ -1019,7 +1030,7 @@ static void send_game_menu_bb(shared_ptr<Client> c, shared_ptr<ServerState> s) {
|
|||||||
e.menu_id = GAME_MENU_ID;
|
e.menu_id = GAME_MENU_ID;
|
||||||
e.game_id = 0;
|
e.game_id = 0;
|
||||||
e.flags = 0x0004;
|
e.flags = 0x0004;
|
||||||
decode_sjis(e.name, s->name.c_str(), 0x10);
|
char16cpy(e.name, s->name.c_str(), 0x10);
|
||||||
}
|
}
|
||||||
for (shared_ptr<Lobby> l : s->all_lobbies()) {
|
for (shared_ptr<Lobby> l : s->all_lobbies()) {
|
||||||
if (!l->is_game()) {
|
if (!l->is_game()) {
|
||||||
|
|||||||
+8
-1
@@ -23,6 +23,7 @@
|
|||||||
#define QUEST_MENU_ID 0x7F02CA94
|
#define QUEST_MENU_ID 0x7F02CA94
|
||||||
#define QUEST_FILTER_MENU_ID 0xC38CA039
|
#define QUEST_FILTER_MENU_ID 0xC38CA039
|
||||||
|
|
||||||
|
#define INFORMATION_MENU_GO_BACK 0xFFFFFFFF
|
||||||
#define MAIN_MENU_GO_TO_LOBBY 0x00000001
|
#define MAIN_MENU_GO_TO_LOBBY 0x00000001
|
||||||
#define MAIN_MENU_INFORMATION 0x00000002
|
#define MAIN_MENU_INFORMATION 0x00000002
|
||||||
#define MAIN_MENU_DISCONNECT 0x00000003
|
#define MAIN_MENU_DISCONNECT 0x00000003
|
||||||
@@ -41,6 +42,12 @@ void send_command(std::shared_ptr<TARGET> c, uint16_t command, uint32_t flag,
|
|||||||
send_command(c, command, flag, &data, sizeof(data));
|
send_command(c, command, flag, &data, sizeof(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename TARGET>
|
||||||
|
void send_command(std::shared_ptr<TARGET> c, uint16_t command, uint32_t flag,
|
||||||
|
const std::string& data) {
|
||||||
|
send_command(c, command, flag, data.data(), data.size());
|
||||||
|
}
|
||||||
|
|
||||||
template <typename TARGET, typename STRUCT>
|
template <typename TARGET, typename STRUCT>
|
||||||
void send_command(std::shared_ptr<TARGET> c, uint16_t command, uint32_t flag,
|
void send_command(std::shared_ptr<TARGET> c, uint16_t command, uint32_t flag,
|
||||||
const std::vector<STRUCT>& data) {
|
const std::vector<STRUCT>& data) {
|
||||||
@@ -65,7 +72,7 @@ void send_update_client_config(std::shared_ptr<Client> c);
|
|||||||
|
|
||||||
void send_reconnect(std::shared_ptr<Client> c, uint32_t address, uint16_t port);
|
void send_reconnect(std::shared_ptr<Client> c, uint32_t address, uint16_t port);
|
||||||
void send_pc_gc_split_reconnect(std::shared_ptr<Client> c, uint32_t address,
|
void send_pc_gc_split_reconnect(std::shared_ptr<Client> c, uint32_t address,
|
||||||
uint16_t pc_port);
|
uint16_t pc_port, uint16_t gc_port);
|
||||||
|
|
||||||
void send_client_init_bb(std::shared_ptr<Client> c, uint32_t error);
|
void send_client_init_bb(std::shared_ptr<Client> c, uint32_t error);
|
||||||
void send_team_and_key_config_bb(std::shared_ptr<Client> c);
|
void send_team_and_key_config_bb(std::shared_ptr<Client> c);
|
||||||
|
|||||||
@@ -143,6 +143,12 @@ void Server::on_client_input(Server::WorkerThread& wt,
|
|||||||
|
|
||||||
c->last_recv_time = now();
|
c->last_recv_time = now();
|
||||||
this->receive_and_process_commands(c, bev);
|
this->receive_and_process_commands(c, bev);
|
||||||
|
|
||||||
|
if (c->should_disconnect) {
|
||||||
|
wt.disconnect_client(bev);
|
||||||
|
this->process_client_disconnect(c);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::on_client_error(Server::WorkerThread& wt,
|
void Server::on_client_error(Server::WorkerThread& wt,
|
||||||
@@ -206,8 +212,14 @@ void Server::receive_and_process_commands(shared_ptr<Client> c, struct buffereve
|
|||||||
// to call string functions on the buffer in command handlers
|
// to call string functions on the buffer in command handlers
|
||||||
string data = c->recv_buffer.substr(offset + header_size, size - header_size);
|
string data = c->recv_buffer.substr(offset + header_size, size - header_size);
|
||||||
data.append(4, '\0');
|
data.append(4, '\0');
|
||||||
|
try {
|
||||||
process_command(this->state, c, header->command(c->version),
|
process_command(this->state, c, header->command(c->version),
|
||||||
header->flag(c->version), size - header_size, data.data());
|
header->flag(c->version), size - header_size, data.data());
|
||||||
|
} catch (const exception& e) {
|
||||||
|
log(INFO, "[Server] error in client stream: %s", e.what());
|
||||||
|
c->should_disconnect = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// BB pads commands to 8-byte boundaries, so if we see a shorter command,
|
// BB pads commands to 8-byte boundaries, so if we see a shorter command,
|
||||||
// skip over the padding
|
// skip over the padding
|
||||||
|
|||||||
+16
-2
@@ -3,13 +3,15 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "SendCommands.hh"
|
#include "SendCommands.hh"
|
||||||
|
#include "NetworkAddresses.hh"
|
||||||
#include "Text.hh"
|
#include "Text.hh"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ServerState::ServerState() : next_lobby_id(1), next_game_id(-1) {
|
ServerState::ServerState() : run_dns_server(true), next_lobby_id(1),
|
||||||
|
next_game_id(-1) {
|
||||||
this->main_menu.emplace_back(MAIN_MENU_GO_TO_LOBBY, u"Go to lobby",
|
this->main_menu.emplace_back(MAIN_MENU_GO_TO_LOBBY, u"Go to lobby",
|
||||||
u"Join the lobby.", 0);
|
u"Join the lobby.", 0);
|
||||||
this->main_menu.emplace_back(MAIN_MENU_INFORMATION, u"Information",
|
this->main_menu.emplace_back(MAIN_MENU_INFORMATION, u"Information",
|
||||||
@@ -85,7 +87,9 @@ void ServerState::send_lobby_join_notifications(shared_ptr<Lobby> l,
|
|||||||
shared_ptr<Client> joining_client) {
|
shared_ptr<Client> joining_client) {
|
||||||
rw_guard g2(l->lock, false);
|
rw_guard g2(l->lock, false);
|
||||||
for (auto& other_client : l->clients) {
|
for (auto& other_client : l->clients) {
|
||||||
if (other_client == joining_client) {
|
if (!other_client) {
|
||||||
|
continue;
|
||||||
|
} else if (other_client == joining_client) {
|
||||||
send_join_lobby(joining_client, l);
|
send_join_lobby(joining_client, l);
|
||||||
} else {
|
} else {
|
||||||
send_player_join_notification(other_client, l, joining_client);
|
send_player_join_notification(other_client, l, joining_client);
|
||||||
@@ -173,3 +177,13 @@ shared_ptr<Client> ServerState::find_client(const char16_t* identifier,
|
|||||||
|
|
||||||
throw out_of_range("client not found");
|
throw out_of_range("client not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t ServerState::connect_address_for_client(std::shared_ptr<Client> c) {
|
||||||
|
// TODO: we can do something much smarter here, like use the sockname to find
|
||||||
|
// out which interface the client is connected to, and return that address
|
||||||
|
if (is_local_address(c->remote_addr)) {
|
||||||
|
return this->local_address;
|
||||||
|
} else {
|
||||||
|
return this->external_address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+4
-2
@@ -26,7 +26,7 @@ struct PortConfiguration {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct ServerState {
|
struct ServerState {
|
||||||
std::string name;
|
std::u16string name;
|
||||||
std::unordered_map<std::string, PortConfiguration> port_configuration;
|
std::unordered_map<std::string, PortConfiguration> port_configuration;
|
||||||
std::shared_ptr<const QuestIndex> quest_index;
|
std::shared_ptr<const QuestIndex> quest_index;
|
||||||
std::shared_ptr<const LevelTable> level_table;
|
std::shared_ptr<const LevelTable> level_table;
|
||||||
@@ -37,7 +37,7 @@ struct ServerState {
|
|||||||
|
|
||||||
std::vector<MenuItem> main_menu;
|
std::vector<MenuItem> main_menu;
|
||||||
std::shared_ptr<std::vector<MenuItem>> information_menu;
|
std::shared_ptr<std::vector<MenuItem>> information_menu;
|
||||||
std::shared_ptr<std::unordered_map<uint32_t, std::u16string>> id_to_information_contents;
|
std::shared_ptr<std::vector<std::u16string>> information_contents;
|
||||||
|
|
||||||
size_t num_threads;
|
size_t num_threads;
|
||||||
|
|
||||||
@@ -71,4 +71,6 @@ struct ServerState {
|
|||||||
|
|
||||||
std::shared_ptr<Client> find_client(const char16_t* identifier = NULL,
|
std::shared_ptr<Client> find_client(const char16_t* identifier = NULL,
|
||||||
uint64_t serial_number = 0, std::shared_ptr<Lobby> l = NULL);
|
uint64_t serial_number = 0, std::shared_ptr<Lobby> l = NULL);
|
||||||
|
|
||||||
|
uint32_t connect_address_for_client(std::shared_ptr<Client> c);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ void add_language_marker_inplace(char* a, char e, size_t dest_count) {
|
|||||||
if (existing_count > dest_count - 3) {
|
if (existing_count > dest_count - 3) {
|
||||||
existing_count = dest_count - 3;
|
existing_count = dest_count - 3;
|
||||||
}
|
}
|
||||||
memmove(&a[2], a, existing_count + 1);
|
memmove(&a[2], a, (existing_count + 1) * sizeof(char));
|
||||||
a[0] = '\t';
|
a[0] = '\t';
|
||||||
a[1] = e;
|
a[1] = e;
|
||||||
a[existing_count + 2] = 0;
|
a[existing_count + 2] = 0;
|
||||||
@@ -168,7 +168,7 @@ void add_language_marker_inplace(char16_t* a, char16_t e, size_t dest_count) {
|
|||||||
if (existing_count > dest_count - 3) {
|
if (existing_count > dest_count - 3) {
|
||||||
existing_count = dest_count - 3;
|
existing_count = dest_count - 3;
|
||||||
}
|
}
|
||||||
memmove(&a[2], a, existing_count + 1);
|
memmove(&a[2], a, (existing_count + 1) * sizeof(char16_t));
|
||||||
a[0] = '\t';
|
a[0] = '\t';
|
||||||
a[1] = e;
|
a[1] = e;
|
||||||
a[existing_count + 2] = 0;
|
a[existing_count + 2] = 0;
|
||||||
|
|||||||
@@ -40,8 +40,9 @@ void replace_char_inplace(T* a, T f, T r) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void add_color_inplace(T* a) {
|
size_t add_color_inplace(T* a) {
|
||||||
T* d = a;
|
T* d = a;
|
||||||
|
T* orig_d = d;
|
||||||
|
|
||||||
while (*a) {
|
while (*a) {
|
||||||
if (*a == '$') {
|
if (*a == '$') {
|
||||||
@@ -59,7 +60,18 @@ void add_color_inplace(T* a) {
|
|||||||
} else {
|
} else {
|
||||||
*(d++) = *a;
|
*(d++) = *a;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
*(d++) = *a;
|
||||||
}
|
}
|
||||||
a++;
|
a++;
|
||||||
}
|
}
|
||||||
|
*d = 0;
|
||||||
|
|
||||||
|
return d - orig_d;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void add_color_inplace(std::basic_string<T>& a, size_t header_bytes) {
|
||||||
|
size_t count = add_color_inplace(const_cast<T*>(a.data() + header_bytes));
|
||||||
|
a.resize(count + header_bytes);
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -11,11 +11,13 @@
|
|||||||
// Server's name (max. 16 characters)
|
// Server's name (max. 16 characters)
|
||||||
"ServerName": "Alexandria",
|
"ServerName": "Alexandria",
|
||||||
// Address to connect local clients to
|
// Address to connect local clients to
|
||||||
"LocalAddress": "10.0.1.6",
|
"LocalAddress": "192.168.0.5",
|
||||||
// Address to connect external clients to
|
// Address to connect external clients to
|
||||||
"ExternalAddress": "10.0.1.6",
|
"ExternalAddress": "10.0.1.6",
|
||||||
// Number of worker threads to run
|
// Number of worker threads to run
|
||||||
"Threads": 1,
|
"Threads": 1,
|
||||||
|
// Set to false to disable the DNS server
|
||||||
|
"RunDNSServer": false,
|
||||||
|
|
||||||
// ****************
|
// ****************
|
||||||
// INFORMATION MENU
|
// INFORMATION MENU
|
||||||
@@ -23,7 +25,7 @@
|
|||||||
|
|
||||||
// Each entry is a 3-list of [title, short-description, full-contents].
|
// Each entry is a 3-list of [title, short-description, full-contents].
|
||||||
"InformationMenuContents": [
|
"InformationMenuContents": [
|
||||||
["Text", "$C7Some things you\nmay need to know\nabout text on\nthis server", "$C7Everything you type will be filtered.\n\nDollar signs will become tab chars, which can be\nused to color team names and info boards.\nTo color your text, type %sCx, where x is a\nvalue from the Text Colors list.\n\nPound signs (number signs) will become returns\n(newlines).\n\nA percent sign will create a special character.\nTyping a percent sign followed by one of these\nletters will make one of these special characters:\n%%d = %d %%x = %x %%p = %p %%+ = %+\n%%1 = %1 %%2 = %2 %%3 = %3 %%c = %c\n%%l = %l %%y = %y %%X = %X %%Y = %Y\n%%Z = %Z %%? = %? %%C = %C %%R = %R\n%%s = %s %%%% = %% %%n = %n"],
|
["Text", "$C7Some things you\nmay need to know\nabout text on\nthis server", "$C7Everything you type will be filtered.\n\nDollar signs will become tab chars, which can be\nused to color team names and info boards.\nTo color your text, type %sCx, where x is a\nvalue from the Text Colors list.\n\nPound signs (number signs) will become returns\n(newlines), the sequence %%s will become %s,\nand the sequence %%%% will become %%."],
|
||||||
["Text colors", "$C7Display color values", "These values can be used to color text.\n\n$C0Color 0$C7 - Black\n$C1Color 1$C7 - Blue\n$C2Color 2$C7 - Green\n$C3Color 3$C7 - Cyan\n$C4Color 4$C7 - Red\n$C5Color 5$C7 - Purple\n$C6Color 6$C7 - Yellow\n$C7Color 7$C7 - White\n$C8Color 8$C7 - Pink\n$C9Color 9$C7 - Violet\n$CGColor G$C7 - Orange Pulse"],
|
["Text colors", "$C7Display color values", "These values can be used to color text.\n\n$C0Color 0$C7 - Black\n$C1Color 1$C7 - Blue\n$C2Color 2$C7 - Green\n$C3Color 3$C7 - Cyan\n$C4Color 4$C7 - Red\n$C5Color 5$C7 - Purple\n$C6Color 6$C7 - Yellow\n$C7Color 7$C7 - White\n$C8Color 8$C7 - Pink\n$C9Color 9$C7 - Violet\n$CGColor G$C7 - Orange Pulse"],
|
||||||
["Lobby commands", "$C7Display commands\nfor use in the\nlobby", "Lobby commands: you must be a moderator to use\nthese commands.\n\n%sallevent <event> - change the server's event\n%sevent <event> - change this lobby's event\n%stype <type> - change this lobby's type\n%sann <message> - announce a message\n%sax <message> - send a message to the server"],
|
["Lobby commands", "$C7Display commands\nfor use in the\nlobby", "Lobby commands: you must be a moderator to use\nthese commands.\n\n%sallevent <event> - change the server's event\n%sevent <event> - change this lobby's event\n%stype <type> - change this lobby's type\n%sann <message> - announce a message\n%sax <message> - send a message to the server"],
|
||||||
["Game commands", "$C7Display commands\nfor use in games", "Game commands: you must be the game leader to\nuse these commands.\n\n%spassword <password> - set the game's password\n%smaxlevel <%n> - set the game's maximum level\n%sminlevel <%n> - set the game's minimum level\n%scheat - enable or disable cheat mode"],
|
["Game commands", "$C7Display commands\nfor use in games", "Game commands: you must be the game leader to\nuse these commands.\n\n%spassword <password> - set the game's password\n%smaxlevel <%n> - set the game's maximum level\n%sminlevel <%n> - set the game's minimum level\n%scheat - enable or disable cheat mode"],
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
0x09 0x0009 # TAB
|
||||||
|
0x0A 0x000A # NEWLINE
|
||||||
0x20 0x0020 # SPACE
|
0x20 0x0020 # SPACE
|
||||||
0x21 0x0021 # EXCLAMATION MARK
|
0x21 0x0021 # EXCLAMATION MARK
|
||||||
0x22 0x0022 # QUOTATION MARK
|
0x22 0x0022 # QUOTATION MARK
|
||||||
|
|||||||
Reference in New Issue
Block a user