move patch servers to separate threads

This commit is contained in:
Martin Michelsen
2024-02-17 22:28:03 -08:00
parent 350a89f3da
commit 1d42faac3e
12 changed files with 920 additions and 521 deletions
+1
View File
@@ -103,6 +103,7 @@ set(SOURCES
src/Map.cc
src/Menu.cc
src/NetworkAddresses.cc
src/PatchServer.cc
src/PatchFileIndex.cc
src/PlayerFilesManager.cc
src/PlayerSubordinates.cc
-3
View File
@@ -186,9 +186,6 @@ public:
uint8_t bb_connection_phase;
uint64_t ping_start_time;
// Patch server
std::vector<PatchFileChecksumRequest> patch_file_checksum_requests;
// Lobby/positioning
Config config;
Config synced_config;
+33 -1
View File
@@ -33,6 +33,7 @@
#include "NetworkAddresses.hh"
#include "PSOGCObjectGraph.hh"
#include "PSOProtocol.hh"
#include "PatchServer.hh"
#include "ProxyServer.hh"
#include "Quest.hh"
#include "QuestScript.hh"
@@ -2411,12 +2412,29 @@ Action a_run_server_replay_log(
state->proxy_server->listen(pc->addr, pc->port, pc->version);
}
}
} else if (pc->behavior == ServerBehavior::PATCH_SERVER_PC) {
if (!state->pc_patch_server.get()) {
config_log.info("Starting PC_V2 patch server");
state->pc_patch_server = make_shared<PatchServer>(state->generate_patch_server_config(false));
}
string spec = string_printf("TU-%hu-%s-patch2", pc->port, pc->name.c_str());
state->pc_patch_server->listen(spec, pc->addr, pc->port, Version::PC_PATCH);
} else if (pc->behavior == ServerBehavior::PATCH_SERVER_BB) {
if (!state->bb_patch_server.get()) {
config_log.info("Starting BB_V4 patch server");
state->bb_patch_server = make_shared<PatchServer>(state->generate_patch_server_config(true));
}
string spec = string_printf("TU-%hu-%s-patch4", pc->port, pc->name.c_str());
state->bb_patch_server->listen(spec, pc->addr, pc->port, Version::BB_PATCH);
} else {
if (!state->game_server.get()) {
config_log.info("Starting game server");
state->game_server = make_shared<Server>(base, state);
}
string spec = string_printf("T-%hu-%s-%s-%s", pc->port, name_for_enum(pc->version), pc->name.c_str(), name_for_enum(pc->behavior));
string spec = string_printf("TG-%hu-%s-%s-%s", pc->port, name_for_enum(pc->version), pc->name.c_str(), name_for_enum(pc->behavior));
state->game_server->listen(spec, pc->addr, pc->port, pc->version, pc->behavior);
}
}
@@ -2470,6 +2488,20 @@ Action a_run_server_replay_log(
}
config_log.info("Normal shutdown");
if (state->pc_patch_server) {
state->pc_patch_server->schedule_stop();
}
if (state->bb_patch_server) {
state->bb_patch_server->schedule_stop();
}
if (state->pc_patch_server) {
config_log.info("Waiting for PC_V2 patch server to stop");
state->pc_patch_server->wait_for_stop();
}
if (state->bb_patch_server) {
config_log.info("Waiting for BB_V4 patch server to stop");
state->bb_patch_server->wait_for_stop();
}
state->proxy_server.reset(); // Break reference cycle
});
+453
View File
@@ -0,0 +1,453 @@
#include "PatchServer.hh"
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/event.h>
#include <event2/listener.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <algorithm>
#include <iostream>
#include <phosg/Encoding.hh>
#include <phosg/Network.hh>
#include <phosg/Strings.hh>
#include <phosg/Time.hh>
#include "EventUtils.hh"
#include "Loggers.hh"
#include "PSOProtocol.hh"
#include "ReceiveCommands.hh"
using namespace std;
static atomic<uint64_t> next_id(1);
PatchServer::Client::Client(
shared_ptr<PatchServer> server,
struct bufferevent* bev,
Version version,
uint64_t idle_timeout_usecs,
bool hide_data_from_logs)
: server(server),
id(next_id++),
log(string_printf("[C-%" PRIX64 "] ", this->id), client_log.min_level),
channel(bev, version, 1, nullptr, nullptr, this, string_printf("C-%" PRIX64, this->id), TerminalFormat::FG_YELLOW, TerminalFormat::FG_GREEN),
idle_timeout_usecs(idle_timeout_usecs),
idle_timeout_event(
event_new(bufferevent_get_base(bev), -1, EV_TIMEOUT, &PatchServer::Client::dispatch_idle_timeout, this),
event_free) {
this->reschedule_timeout_event();
// Don't print data sent to patch clients to the logs. The patch server
// protocol is fully understood and data logs for patch clients are generally
// more annoying than helpful at this point.
if (hide_data_from_logs) {
this->channel.terminal_recv_color = TerminalFormat::END;
this->channel.terminal_send_color = TerminalFormat::END;
}
this->log.info("Created");
}
void PatchServer::Client::reschedule_timeout_event() {
struct timeval idle_tv = usecs_to_timeval(this->idle_timeout_usecs);
event_add(this->idle_timeout_event.get(), &idle_tv);
}
void PatchServer::Client::dispatch_idle_timeout(evutil_socket_t, short, void* ctx) {
reinterpret_cast<Client*>(ctx)->idle_timeout();
}
void PatchServer::Client::idle_timeout() {
this->log.info("Idle timeout expired");
auto s = this->server.lock();
if (s) {
auto c = this->shared_from_this();
s->disconnect_client(c);
} else {
this->channel.disconnect();
this->log.info("Server is deleted; cannot disconnect client");
}
}
void PatchServer::send_server_init(shared_ptr<Client> c) const {
uint32_t server_key = random_object<uint32_t>();
uint32_t client_key = random_object<uint32_t>();
S_ServerInit_Patch_02 cmd;
cmd.copyright.encode("Patch Server. Copyright SonicTeam, LTD. 2001");
cmd.server_key = server_key;
cmd.client_key = client_key;
c->channel.send(0x02, 0x00, cmd);
c->channel.crypt_out = make_shared<PSOV2Encryption>(server_key);
c->channel.crypt_in = make_shared<PSOV2Encryption>(client_key);
}
void PatchServer::send_message_box(shared_ptr<Client> c, const string& text) const {
StringWriter w;
try {
if (c->version() == Version::PC_PATCH) {
w.write(tt_encode_marked_optional(text, c->channel.language, true));
} else if (c->version() == Version::BB_PATCH) {
w.write(tt_encode_marked_optional(add_color(text), c->channel.language, true));
} else {
throw logic_error("non-patch client on patch server");
}
} catch (const runtime_error& e) {
log_warning("Failed to encode message for patch message box command: %s", e.what());
return;
}
w.put_u16(0);
while (w.str().size() & 3) {
w.put_u8(0);
}
c->channel.send(0x13, 0x00, w.str());
}
void PatchServer::send_enter_directory(shared_ptr<Client> c, const string& dir) const {
S_EnterDirectory_Patch_09 cmd = {{dir, 1}};
c->channel.send(0x09, 0x00, cmd);
}
void PatchServer::on_02(shared_ptr<Client> c, string& data) {
check_size_v(data.size(), 0);
c->channel.send(0x04, 0x00); // This requests the user's login information
}
void PatchServer::change_to_directory(
shared_ptr<Client> c,
vector<string>& client_path_directories,
const vector<string>& file_path_directories) const {
// First, exit all leaf directories that don't match the desired path
while (!client_path_directories.empty() &&
((client_path_directories.size() > file_path_directories.size()) ||
(client_path_directories.back() != file_path_directories[client_path_directories.size() - 1]))) {
c->channel.send(0x0A, 0x00);
client_path_directories.pop_back();
}
// At this point, client_path_directories should be a prefix of
// file_path_directories (or should match exactly)
if (client_path_directories.size() > file_path_directories.size()) {
throw logic_error("did not exit all necessary directories");
}
for (size_t x = 0; x < client_path_directories.size(); x++) {
if (client_path_directories[x] != file_path_directories[x]) {
throw logic_error("intermediate path is not a prefix of final path");
}
}
// Second, enter all necessary leaf directories
while (client_path_directories.size() < file_path_directories.size()) {
const string& dir = file_path_directories[client_path_directories.size()];
this->send_enter_directory(c, dir);
client_path_directories.emplace_back(dir);
}
}
void PatchServer::on_04(shared_ptr<Client> c, string& data) {
const auto& cmd = check_size_t<C_Login_Patch_04>(data);
string username = cmd.username.decode();
string password = cmd.password.decode();
// There are 3 cases here:
// - No login information at all: just proceed without checking license
// - Username only: check that license exists if allow_unregistered_users is off
// - Username and password: call verify_bb
if (!username.empty() && !password.empty()) {
try {
this->config->license_index->verify_bb(username, password);
} catch (const LicenseIndex::incorrect_password& e) {
this->send_message_box(c, string_printf("Login failed: %s", e.what()));
this->disconnect_client(c);
return;
} catch (const LicenseIndex::missing_license& e) {
if (!this->config->allow_unregistered_users) {
this->send_message_box(c, string_printf("Login failed: %s", e.what()));
this->disconnect_client(c);
return;
}
}
} else if (!username.empty() && !this->config->allow_unregistered_users) {
try {
this->config->license_index->get_by_bb_username(username);
} catch (const LicenseIndex::missing_license& e) {
this->send_message_box(c, string_printf("Login failed: %s", e.what()));
this->disconnect_client(c);
return;
}
}
if (!this->config->message.empty()) {
this->send_message_box(c, this->config->message.c_str());
}
const auto& index = this->config->patch_file_index;
if (index.get()) {
c->channel.send(0x0B, 0x00); // Start patch session; go to root directory
vector<string> path_directories;
for (const auto& file : index->all_files()) {
this->change_to_directory(c, path_directories, file->path_directories);
S_FileChecksumRequest_Patch_0C req = {c->patch_file_checksum_requests.size(), {file->name, 1}};
c->channel.send(0x0C, 0x00, req);
c->patch_file_checksum_requests.emplace_back(file);
}
this->change_to_directory(c, path_directories, {});
c->channel.send(0x0D, 0x00); // End of checksum requests
} else {
// No patch index present: just do something that will satisfy the client
// without actually checking or downloading any files
this->send_enter_directory(c, ".");
this->send_enter_directory(c, "data");
this->send_enter_directory(c, "scene");
c->channel.send(0x0A, 0x00);
c->channel.send(0x0A, 0x00);
c->channel.send(0x0A, 0x00);
c->channel.send(0x12, 0x00);
}
}
void PatchServer::on_0F(shared_ptr<Client> c, string& data) {
auto& cmd = check_size_t<C_FileInformation_Patch_0F>(data);
auto& req = c->patch_file_checksum_requests.at(cmd.request_id);
req.crc32 = cmd.checksum;
req.size = cmd.size;
req.response_received = true;
}
void PatchServer::on_10(shared_ptr<Client> c, string&) {
S_StartFileDownloads_Patch_11 start_cmd = {0, 0};
for (const auto& req : c->patch_file_checksum_requests) {
if (!req.response_received) {
throw runtime_error("client did not respond to checksum request");
}
if (req.needs_update()) {
c->log.info("File %s needs update (CRC: %08" PRIX32 "/%08" PRIX32 ", size: %" PRIu32 "/%" PRIu32 ")",
req.file->name.c_str(), req.file->crc32, req.crc32, req.file->size, req.size);
start_cmd.total_bytes += req.file->size;
start_cmd.num_files++;
} else {
c->log.info("File %s is up to date", req.file->name.c_str());
}
}
if (start_cmd.num_files) {
c->channel.send(0x11, 0x00, start_cmd);
vector<string> path_directories;
for (const auto& req : c->patch_file_checksum_requests) {
if (req.needs_update()) {
this->change_to_directory(c, path_directories, req.file->path_directories);
S_OpenFile_Patch_06 open_cmd = {0, req.file->size, {req.file->name, 1}};
c->channel.send(0x06, 0x00, open_cmd);
for (size_t x = 0; x < req.file->chunk_crcs.size(); x++) {
auto data = req.file->load_data();
size_t chunk_size = min<uint32_t>(req.file->size - (x * 0x4000), 0x4000);
vector<pair<const void*, size_t>> blocks;
S_WriteFileHeader_Patch_07 cmd_header = {x, req.file->chunk_crcs[x], chunk_size};
blocks.emplace_back(&cmd_header, sizeof(cmd_header));
blocks.emplace_back(data->data() + (x * 0x4000), chunk_size);
c->channel.send(0x07, 0x00, blocks);
}
S_CloseCurrentFile_Patch_08 close_cmd = {0};
c->channel.send(0x08, 0x00, close_cmd);
}
}
this->change_to_directory(c, path_directories, {});
}
c->channel.send(0x12, 0x00);
}
void PatchServer::disconnect_client(shared_ptr<Client> c) {
if (c->channel.is_virtual_connection) {
server_log.info("Client disconnected: C-%" PRIX64 " on virtual connection %p", c->id, c->channel.bev.get());
} else if (c->channel.bev) {
server_log.info("Client disconnected: C-%" PRIX64 " on fd %d", c->id, bufferevent_getfd(c->channel.bev.get()));
} else {
server_log.info("Client C-%" PRIX64 " removed from game server", c->id);
}
this->channel_to_client.erase(&c->channel);
c->channel.disconnect();
// We can't just let c be destroyed here, since disconnect_client can be
// called from within the client's channel's receive handler. So, we instead
// move it to another set, which we'll clear in an immediately-enqueued
// callback after the current event. This will also call the client's
// disconnect hooks (if any).
this->clients_to_destroy.insert(std::move(c));
this->enqueue_destroy_clients();
}
void PatchServer::enqueue_destroy_clients() {
auto tv = usecs_to_timeval(0);
event_add(this->destroy_clients_ev.get(), &tv);
}
void PatchServer::dispatch_destroy_clients(evutil_socket_t, short, void* ctx) {
reinterpret_cast<PatchServer*>(ctx)->clients_to_destroy.clear();
}
void PatchServer::dispatch_on_listen_accept(
struct evconnlistener* listener, evutil_socket_t fd,
struct sockaddr* address, int socklen, void* ctx) {
reinterpret_cast<PatchServer*>(ctx)->on_listen_accept(listener, fd, address, socklen);
}
void PatchServer::dispatch_on_listen_error(
struct evconnlistener* listener, void* ctx) {
reinterpret_cast<PatchServer*>(ctx)->on_listen_error(listener);
}
void PatchServer::on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr*, int) {
int listen_fd = evconnlistener_get_fd(listener);
ListeningSocket* listening_socket;
try {
listening_socket = &this->listening_sockets.at(listen_fd);
} catch (const out_of_range& e) {
server_log.warning("Can\'t determine version for socket %d; disconnecting client", listen_fd);
close(fd);
return;
}
struct bufferevent* bev = bufferevent_socket_new(this->base.get(), fd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
auto c = make_shared<Client>(
this->shared_from_this(),
bev,
listening_socket->version,
this->config->idle_timeout_usecs,
this->config->hide_data_from_logs);
c->channel.on_command_received = PatchServer::on_client_input;
c->channel.on_error = PatchServer::on_client_error;
c->channel.context_obj = this;
this->channel_to_client.emplace(&c->channel, c);
server_log.info("Patch client connected: U-%" PRIX64 " on fd %d via %d (%s)",
c->id, fd, listen_fd, listening_socket->addr_str.c_str());
this->send_server_init(c);
}
void PatchServer::on_listen_error(struct evconnlistener* listener) {
int err = EVUTIL_SOCKET_ERROR();
server_log.error("Failure on listening socket %d: %d (%s)",
evconnlistener_get_fd(listener), err, evutil_socket_error_to_string(err));
event_base_loopexit(this->base.get(), nullptr);
}
void PatchServer::on_client_input(Channel& ch, uint16_t command, uint32_t, std::string& data) {
PatchServer* server = reinterpret_cast<PatchServer*>(ch.context_obj);
shared_ptr<Client> c = server->channel_to_client.at(&ch);
try {
switch (command) {
case 0x02:
server->on_02(c, data);
break;
case 0x04:
server->on_04(c, data);
break;
case 0x0F:
server->on_0F(c, data);
break;
case 0x10:
server->on_10(c, data);
break;
default:
throw runtime_error("invalid command");
}
} catch (const exception& e) {
server_log.warning("Error processing client command: %s", e.what());
}
}
void PatchServer::on_client_error(Channel& ch, short events) {
PatchServer* server = reinterpret_cast<PatchServer*>(ch.context_obj);
shared_ptr<Client> c = server->channel_to_client.at(&ch);
if (events & BEV_EVENT_ERROR) {
int err = EVUTIL_SOCKET_ERROR();
server_log.warning("Client caused error %d (%s)", err, evutil_socket_error_to_string(err));
}
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
server->disconnect_client(c);
}
}
PatchServer::PatchServer(shared_ptr<const Config> config)
: base(event_base_new(), event_base_free),
config(config),
destroy_clients_ev(event_new(this->base.get(), -1, EV_TIMEOUT, &PatchServer::dispatch_destroy_clients, this), event_free),
th(&PatchServer::thread_fn, this) {}
void PatchServer::schedule_stop() {
event_base_loopexit(this->base.get(), nullptr);
}
void PatchServer::wait_for_stop() {
this->th.join();
}
void PatchServer::listen(const std::string& addr_str, const string& socket_path, Version version) {
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());
this->add_socket(addr_str, fd, version);
}
void PatchServer::listen(const std::string& addr_str, const string& addr, int port, Version version) {
if (port == 0) {
this->listen(addr_str, addr, version);
} 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 as %s", netloc_str.c_str(), fd, addr_str.c_str());
this->add_socket(addr_str, fd, version);
}
}
void PatchServer::listen(const std::string& addr_str, int port, Version version) {
this->listen(addr_str, "", port, version);
}
PatchServer::ListeningSocket::ListeningSocket(PatchServer* s, const std::string& addr_str, int fd, Version version)
: addr_str(addr_str),
fd(fd),
version(version),
listener(evconnlistener_new(s->base.get(), PatchServer::dispatch_on_listen_accept, s, LEV_OPT_REUSEABLE, 0, this->fd),
evconnlistener_free) {
evconnlistener_set_error_cb(this->listener.get(), PatchServer::dispatch_on_listen_error);
}
void PatchServer::add_socket(const std::string& addr_str, int fd, Version version) {
this->listening_sockets.emplace(piecewise_construct, forward_as_tuple(fd), forward_as_tuple(this, addr_str, fd, version));
}
void PatchServer::thread_fn() {
event_base_loop(this->base.get(), EVLOOP_NO_EXIT_ON_EMPTY);
}
void PatchServer::set_config(std::shared_ptr<const Config> config) {
forward_to_event_thread(this->base, [s = this->shared_from_this(), config = std::move(config)]() {
s->config = config;
});
}
+127
View File
@@ -0,0 +1,127 @@
#pragma once
#include <event2/event.h>
#include <stdint.h>
#include <memory>
#include <string>
#include <unordered_set>
#include <vector>
#include "Channel.hh"
#include "License.hh"
#include "PatchFileIndex.hh"
#include "Version.hh"
class PatchServer : public std::enable_shared_from_this<PatchServer> {
public:
struct Config {
bool allow_unregistered_users;
bool hide_data_from_logs;
uint64_t idle_timeout_usecs;
std::string message;
std::shared_ptr<const LicenseIndex> license_index;
std::shared_ptr<const PatchFileIndex> patch_file_index;
};
PatchServer() = delete;
explicit PatchServer(std::shared_ptr<const Config> config);
PatchServer(const PatchServer&) = delete;
PatchServer(PatchServer&&) = delete;
PatchServer& operator=(const PatchServer&) = delete;
PatchServer& operator=(PatchServer&&) = delete;
virtual ~PatchServer() = default;
void schedule_stop();
void wait_for_stop();
void listen(const std::string& addr_str, const std::string& socket_path, Version version);
void listen(const std::string& addr_str, const std::string& addr, int port, Version version);
void listen(const std::string& addr_str, int port, Version version);
void add_socket(const std::string& addr_str, int fd, Version version);
void set_config(std::shared_ptr<const Config> config);
private:
class Client : public std::enable_shared_from_this<Client> {
public:
std::weak_ptr<PatchServer> server;
uint64_t id;
PrefixedLogger log;
Channel channel;
std::vector<PatchFileChecksumRequest> patch_file_checksum_requests;
uint64_t idle_timeout_usecs;
std::unique_ptr<struct event, void (*)(struct event*)> idle_timeout_event;
Client(
std::shared_ptr<PatchServer> server,
struct bufferevent* bev,
Version version,
uint64_t idle_timeout_usecs,
bool hide_data_from_logs);
~Client() = default;
void reschedule_timeout_event();
inline Version version() const {
return this->channel.version;
}
static void dispatch_idle_timeout(evutil_socket_t, short, void* ctx);
void idle_timeout();
const std::string& get_bb_username() const;
void set_bb_username(const std::string& bb_username);
};
struct ListeningSocket {
std::string addr_str;
int fd;
Version version;
std::unique_ptr<struct evconnlistener, void (*)(struct evconnlistener*)> listener;
ListeningSocket(PatchServer* s, const std::string& name, int fd, Version version);
};
std::shared_ptr<struct event_base> base;
std::shared_ptr<const Config> config;
std::unordered_set<std::shared_ptr<Client>> clients_to_destroy;
std::shared_ptr<struct event> destroy_clients_ev;
std::unordered_map<int, ListeningSocket> listening_sockets;
std::unordered_map<Channel*, std::shared_ptr<Client>> channel_to_client;
std::thread th;
void send_server_init(std::shared_ptr<Client> c) const;
void send_message_box(std::shared_ptr<Client> c, const std::string& text) const;
void send_enter_directory(std::shared_ptr<Client> c, const std::string& dir) const;
void change_to_directory(
std::shared_ptr<Client> c,
std::vector<std::string>& client_path_directories,
const std::vector<std::string>& file_path_directories) const;
void on_02(std::shared_ptr<Client> c, std::string& data);
void on_04(std::shared_ptr<Client> c, std::string& data);
void on_0F(std::shared_ptr<Client> c, std::string& data);
void on_10(std::shared_ptr<Client> c, std::string& data);
void disconnect_client(std::shared_ptr<Client> c);
void enqueue_destroy_clients();
static void dispatch_destroy_clients(evutil_socket_t, short, void* ctx);
static void dispatch_on_listen_accept(struct evconnlistener* listener,
evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx);
static void dispatch_on_listen_error(struct evconnlistener* listener, void* ctx);
void on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr* address, int socklen);
void on_listen_error(struct evconnlistener* listener);
static void on_client_input(Channel& ch, uint16_t command, uint32_t flag, std::string& data);
static void on_client_error(Channel& ch, short events);
void thread_fn();
};
+277 -464
View File
@@ -5097,181 +5097,6 @@ static void on_EA_BB(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
}
}
static void on_02_P(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
check_size_v(data.size(), 0);
send_command(c, 0x04, 0x00); // This requests the user's login information
}
static void change_to_directory_patch(
shared_ptr<Client> c,
vector<string>& client_path_directories,
const vector<string>& file_path_directories) {
// First, exit all leaf directories that don't match the desired path
while (!client_path_directories.empty() &&
((client_path_directories.size() > file_path_directories.size()) ||
(client_path_directories.back() != file_path_directories[client_path_directories.size() - 1]))) {
send_command(c, 0x0A, 0x00);
client_path_directories.pop_back();
}
// At this point, client_path_directories should be a prefix of
// file_path_directories (or should match exactly)
if (client_path_directories.size() > file_path_directories.size()) {
throw logic_error("did not exit all necessary directories");
}
for (size_t x = 0; x < client_path_directories.size(); x++) {
if (client_path_directories[x] != file_path_directories[x]) {
throw logic_error("intermediate path is not a prefix of final path");
}
}
// Second, enter all necessary leaf directories
while (client_path_directories.size() < file_path_directories.size()) {
const string& dir = file_path_directories[client_path_directories.size()];
send_enter_directory_patch(c, dir);
client_path_directories.emplace_back(dir);
}
}
static void on_04_P(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
const auto& cmd = check_size_t<C_Login_Patch_04>(data);
auto s = c->require_server_state();
string username = cmd.username.decode();
string password = cmd.password.decode();
// There are 3 cases here:
// - No login information at all: just proceed without checking license
// - Username only: check that license exists if allow_unregistered_users is off
// - Username and password: call verify_bb
if (!username.empty() && !password.empty()) {
try {
auto l = s->license_index->verify_bb(username, password);
c->set_license(l);
} catch (const LicenseIndex::incorrect_password& e) {
send_message_box(c, string_printf("Login failed: %s", e.what()));
c->should_disconnect = true;
return;
} catch (const LicenseIndex::missing_license& e) {
if (!s->allow_unregistered_users) {
send_message_box(c, string_printf("Login failed: %s", e.what()));
c->should_disconnect = true;
return;
} else {
c->config.set_flag(Client::Flag::LICENSE_WAS_CREATED);
auto l = s->license_index->create_license();
l->serial_number = fnv1a32(cmd.username.decode()) & 0x7FFFFFFF;
l->bb_username = cmd.username.decode();
l->bb_password = cmd.password.decode();
s->license_index->add(l);
if (!s->is_replay) {
l->save();
}
c->set_license(l);
string l_str = l->str();
c->log.info("Created license %s", l_str.c_str());
}
}
} else if (!username.empty() && !s->allow_unregistered_users) {
try {
auto l = s->license_index->get_by_bb_username(username);
c->set_license(l);
} catch (const LicenseIndex::missing_license& e) {
send_message_box(c, string_printf("Login failed: %s", e.what()));
c->should_disconnect = true;
return;
}
} else {
auto l = s->license_index->create_temporary_license();
l->serial_number = random_object<uint32_t>() & 0x7FFFFFFF;
l->bb_username = "__unknown__";
l->bb_password = "__unknown__";
c->set_license(l);
}
// On BB we can use colors and newlines should be \n; on PC we can't use
// colors, the text is auto-word-wrapped, and newlines should be \r\n.
bool is_bb_patch = (c->version() == Version::BB_PATCH);
const string& message = is_bb_patch
? s->bb_patch_server_message
: s->pc_patch_server_message;
if (!message.empty()) {
send_message_box(c, message.c_str());
}
auto index = is_bb_patch ? s->bb_patch_file_index : s->pc_patch_file_index;
if (index.get()) {
send_command(c, 0x0B, 0x00); // Start patch session; go to root directory
vector<string> path_directories;
for (const auto& file : index->all_files()) {
change_to_directory_patch(c, path_directories, file->path_directories);
S_FileChecksumRequest_Patch_0C req = {c->patch_file_checksum_requests.size(), {file->name, 1}};
send_command_t(c, 0x0C, 0x00, req);
c->patch_file_checksum_requests.emplace_back(file);
}
change_to_directory_patch(c, path_directories, {});
send_command(c, 0x0D, 0x00); // End of checksum requests
} else {
// No patch index present: just do something that will satisfy the client
// without actually checking or downloading any files
send_enter_directory_patch(c, ".");
send_enter_directory_patch(c, "data");
send_enter_directory_patch(c, "scene");
send_command(c, 0x0A, 0x00);
send_command(c, 0x0A, 0x00);
send_command(c, 0x0A, 0x00);
send_command(c, 0x12, 0x00);
}
}
static void on_0F_P(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
auto& cmd = check_size_t<C_FileInformation_Patch_0F>(data);
auto& req = c->patch_file_checksum_requests.at(cmd.request_id);
req.crc32 = cmd.checksum;
req.size = cmd.size;
req.response_received = true;
}
static void on_10_P(shared_ptr<Client> c, uint16_t, uint32_t, string&) {
S_StartFileDownloads_Patch_11 start_cmd = {0, 0};
for (const auto& req : c->patch_file_checksum_requests) {
if (!req.response_received) {
throw runtime_error("client did not respond to checksum request");
}
if (req.needs_update()) {
c->log.info("File %s needs update (CRC: %08" PRIX32 "/%08" PRIX32 ", size: %" PRIu32 "/%" PRIu32 ")",
req.file->name.c_str(), req.file->crc32, req.crc32, req.file->size, req.size);
start_cmd.total_bytes += req.file->size;
start_cmd.num_files++;
} else {
c->log.info("File %s is up to date", req.file->name.c_str());
}
}
if (start_cmd.num_files) {
send_command_t(c, 0x11, 0x00, start_cmd);
vector<string> path_directories;
for (const auto& req : c->patch_file_checksum_requests) {
if (req.needs_update()) {
change_to_directory_patch(c, path_directories, req.file->path_directories);
send_patch_file(c, req.file);
}
}
change_to_directory_patch(c, path_directories, {});
}
send_command(c, 0x12, 0x00);
}
static void on_ignored(shared_ptr<Client>, uint16_t, uint32_t, string&) {}
static void on_unimplemented_command(
@@ -5286,293 +5111,287 @@ typedef void (*on_command_t)(shared_ptr<Client> c, uint16_t command, uint32_t fl
// Command handler table, indexed by command number and game version. Null
// entries in this table cause on_unimplemented_command to be called, which
// disconnects the client.
static_assert(NUM_VERSIONS == 14, "Don\'t forget to update the ReceiveCommands handler table");
static on_command_t handlers[0x100][NUM_VERSIONS] = {
static_assert(NUM_VERSIONS - 2 == 12, "Don\'t forget to update the ReceiveCommands handler table");
static on_command_t handlers[0x100][NUM_VERSIONS - 2] = {
// clang-format off
// PC_PATCH BB_PATCH DC_NTE DC_112000 DCV1 DCV2 PC_NTE PC GCNTE GC EP3TE EP3 XB BB
/* 00 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 01 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 02 */ {on_02_P, on_02_P, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 03 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 04 */ {on_04_P, on_04_P, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 05 */ {nullptr, nullptr, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_05_XB, on_ignored},
/* 06 */ {nullptr, nullptr, on_06, on_06, on_06, on_06, on_06, on_06, on_06, on_06, on_06, on_06, on_06, on_06},
/* 07 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 08 */ {nullptr, nullptr, on_08_E6, on_08_E6, on_08_E6, on_08_E6, on_08_E6, on_08_E6, on_08_E6, on_08_E6, on_08_E6, on_08_E6, on_08_E6, on_08_E6},
/* 09 */ {nullptr, nullptr, on_09, on_09, on_09, on_09, on_09, on_09, on_09, on_09, on_09, on_09, on_09, on_09},
/* 0A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 0B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 0C */ {nullptr, nullptr, on_0C_C1_E7_EC,on_0C_C1_E7_EC,on_0C_C1_E7_EC,on_0C_C1_E7_EC, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 0D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 0E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 0F */ {on_0F_P, on_0F_P, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* 10 */ {on_10_P, on_10_P, on_10, on_10, on_10, on_10, on_10, on_10, on_10, on_10, on_10, on_10, on_10, on_10},
/* 11 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 12 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 13 */ {nullptr, nullptr, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_13_A7_V3_BB, on_13_A7_V3_BB, on_13_A7_V3_BB, on_13_A7_V3_BB, on_13_A7_V3_BB, on_13_A7_V3_BB},
/* 14 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 15 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 16 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 17 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 18 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 19 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 1A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 1B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 1C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 1D */ {nullptr, nullptr, on_1D, on_1D, on_1D, on_1D, on_1D, on_1D, on_1D, on_1D, on_1D, on_1D, on_1D, on_1D},
/* 1E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 1F */ {nullptr, nullptr, on_1F, on_1F, on_1F, on_1F, on_1F, on_1F, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* 20 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 21 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 22 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_ignored},
/* 23 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 24 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 25 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 26 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 27 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 28 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 29 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 2A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 2B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 2C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 2D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 2E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 2F */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* 30 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 31 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 32 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 33 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 34 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 35 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 36 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 37 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 38 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 39 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 3A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 3B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 3C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 3D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 3E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 3F */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* 40 */ {nullptr, nullptr, on_40, on_40, on_40, on_40, on_40, on_40, on_40, on_40, on_40, on_40, on_40, on_40},
/* 41 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 42 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 43 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 44 */ {nullptr, nullptr, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_44_A6_V3_BB, on_44_A6_V3_BB, on_44_A6_V3_BB, on_44_A6_V3_BB, on_44_A6_V3_BB, on_44_A6_V3_BB},
/* 45 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 46 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 47 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 48 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 49 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 4A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 4B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 4C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 4D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 4E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 4F */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* 50 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 51 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 52 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 53 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 54 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 55 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 56 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 57 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 58 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 59 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 5A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 5B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 5C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 5D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 5E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 5F */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* 60 */ {nullptr, nullptr, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB},
/* 61 */ {nullptr, nullptr, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98},
/* 62 */ {nullptr, nullptr, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB},
/* 63 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 64 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 65 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 66 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 67 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 68 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 69 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 6A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 6B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 6C */ {nullptr, nullptr, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB},
/* 6D */ {nullptr, nullptr, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB},
/* 6E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 6F */ {nullptr, nullptr, on_6F, on_6F, on_6F, on_6F, on_6F, on_6F, on_6F, on_6F, on_6F, on_6F, on_6F, on_6F},
// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* 70 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 71 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 72 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 73 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 74 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 75 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 76 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 77 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 78 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 79 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 7A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 7B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 7C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 7D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 7E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 7F */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* 80 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 81 */ {nullptr, nullptr, on_81, on_81, on_81, on_81, on_81, on_81, on_81, on_81, on_81, on_81, on_81, on_81},
/* 82 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 83 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 84 */ {nullptr, nullptr, on_84, on_84, on_84, on_84, on_84, on_84, on_84, on_84, on_84, on_84, on_84, on_84},
/* 85 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 86 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 87 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 88 */ {nullptr, nullptr, on_88_DCNTE, on_88_DCNTE, on_88_DCNTE, on_88_DCNTE, nullptr, nullptr, on_88_DCNTE, on_88_DCNTE, on_88_DCNTE, on_88_DCNTE, nullptr, nullptr},
/* 89 */ {nullptr, nullptr, on_89, on_89, on_89, on_89, on_89, on_89, on_89, on_89, on_89, on_89, on_89, on_89},
/* 8A */ {nullptr, nullptr, on_8A, on_8A, on_8A, on_8A, on_8A, on_8A, on_8A, on_8A, on_8A, on_8A, on_8A, on_8A},
/* 8B */ {nullptr, nullptr, on_8B_DCNTE, on_8B_DCNTE, on_8B_DCNTE, on_8B_DCNTE, nullptr, nullptr, on_8B_DCNTE, on_8B_DCNTE, on_8B_DCNTE, on_8B_DCNTE, nullptr, nullptr},
/* 8C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 8D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 8E */ {nullptr, nullptr, on_8E_DCNTE, on_8E_DCNTE, on_8E_DCNTE, on_8E_DCNTE, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 8F */ {nullptr, nullptr, on_8F_DCNTE, on_8F_DCNTE, on_8F_DCNTE, on_8F_DCNTE, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* 90 */ {nullptr, nullptr, on_90_DC, on_90_DC, on_90_DC, on_90_DC, nullptr, nullptr, on_90_DC, on_90_DC, on_90_DC, on_90_DC, nullptr, nullptr},
/* 91 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 92 */ {nullptr, nullptr, on_92_DC, on_92_DC, on_92_DC, on_92_DC, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 93 */ {nullptr, nullptr, on_93_DC, on_93_DC, on_93_DC, on_93_DC, nullptr, nullptr, on_93_DC, on_93_DC, on_93_DC, on_93_DC, nullptr, on_93_BB},
/* 94 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 95 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 96 */ {nullptr, nullptr, on_96, on_96, on_96, on_96, on_96, on_96, on_96, on_96, on_96, on_96, on_96, nullptr},
/* 97 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 98 */ {nullptr, nullptr, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98},
/* 99 */ {nullptr, nullptr, on_99, on_99, on_99, on_99, on_99, on_99, on_99, on_99, on_99, on_99, on_99, on_99},
/* 9A */ {nullptr, nullptr, on_9A, on_9A, on_9A, on_9A, on_9A, on_9A, on_9A, on_9A, on_9A, on_9A, nullptr, nullptr},
/* 9B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 9C */ {nullptr, nullptr, on_9C, on_9C, on_9C, on_9C, on_9C, on_9C, on_9C, on_9C, on_9C, on_9C, on_9C, nullptr},
/* 9D */ {nullptr, nullptr, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, nullptr},
/* 9E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, on_9E_XB, nullptr},
/* 9F */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_9F, on_9F, on_9F, on_9F, on_9F},
// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* A0 */ {nullptr, nullptr, on_A0, on_A0, on_A0, on_A0, on_A0, on_A0, on_A0, on_A0, on_A0, on_A0, on_A0, on_A0},
/* A1 */ {nullptr, nullptr, on_A1, on_A1, on_A1, on_A1, on_A1, on_A1, on_A1, on_A1, on_A1, on_A1, on_A1, on_A1},
/* A2 */ {nullptr, nullptr, on_A2, on_A2, on_A2, on_A2, on_A2, on_A2, on_A2, on_A2, on_A2, on_A2, on_A2, on_A2},
/* A3 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* A4 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* A5 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* A6 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_44_A6_V3_BB, on_44_A6_V3_BB, on_44_A6_V3_BB, on_44_A6_V3_BB, on_44_A6_V3_BB, nullptr},
/* A7 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_13_A7_V3_BB, on_13_A7_V3_BB, on_13_A7_V3_BB, on_13_A7_V3_BB, on_13_A7_V3_BB, nullptr},
/* A8 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* A9 */ {nullptr, nullptr, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored},
/* AA */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_AA, on_AA, on_AA, on_AA, on_AA, on_AA, on_AA, on_AA},
/* AB */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* AC */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_AC_V3_BB, on_AC_V3_BB, on_AC_V3_BB, on_AC_V3_BB, on_AC_V3_BB, on_AC_V3_BB},
/* AD */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* AE */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* AF */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* B0 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* B1 */ {nullptr, nullptr, on_B1, on_B1, on_B1, on_B1, on_B1, on_B1, on_B1, on_B1, on_B1, on_B1, on_B1, nullptr},
/* B2 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* B3 */ {nullptr, nullptr, on_B3, on_B3, on_B3, on_B3, on_B3, on_B3, on_B3, on_B3, on_B3, on_B3, on_B3, on_B3},
/* B4 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* B5 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* B6 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* B7 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_B7_Ep3, on_B7_Ep3, nullptr, nullptr},
/* B8 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_ignored, on_ignored, nullptr, nullptr},
/* B9 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_ignored, on_ignored, nullptr, nullptr},
/* BA */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_BA_Ep3, on_BA_Ep3, nullptr, nullptr},
/* BB */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* BC */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* BD */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* BE */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* BF */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* C0 */ {nullptr, nullptr, nullptr, nullptr, nullptr, on_C0, on_C0, on_C0, on_C0, on_C0, on_C0, on_C0, on_C0, on_C0},
/* C1 */ {nullptr, nullptr, on_0C_C1_E7_EC,on_0C_C1_E7_EC,on_0C_C1_E7_EC,on_0C_C1_E7_EC, on_C1_PC, on_C1_PC, on_0C_C1_E7_EC, on_0C_C1_E7_EC, on_0C_C1_E7_EC, on_0C_C1_E7_EC, on_0C_C1_E7_EC, on_C1_BB},
/* C2 */ {nullptr, nullptr, nullptr, nullptr, nullptr, on_C2, on_C2, on_C2, on_C2, on_C2, on_C2, on_C2, on_C2, on_C2},
/* C3 */ {nullptr, nullptr, nullptr, nullptr, nullptr, on_C3, on_C3, on_C3, on_C3, on_C3, on_C3, on_C3, on_C3, on_C3},
/* C4 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* C5 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* C6 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_C6, on_C6, on_C6, on_C6, on_C6, on_C6, on_C6, on_C6},
/* C7 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_C7, on_C7, on_C7, on_C7, on_C7, on_C7, on_C7, on_C7},
/* C8 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_C8, on_C8, on_C8, on_C8, on_C8, on_C8, on_C8, on_C8},
/* C9 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_6x_C9_CB, on_6x_C9_CB, on_C9_XB, nullptr},
/* CA */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_CA_Ep3, on_CA_Ep3, nullptr, nullptr},
/* CB */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_6x_C9_CB, on_6x_C9_CB, nullptr, nullptr},
/* CC */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* CD */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* CE */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* CF */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* D0 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_D0_V3_BB, on_D0_V3_BB, on_D0_V3_BB, on_D0_V3_BB, on_D0_V3_BB, on_D0_V3_BB},
/* D1 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* D2 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_D2_V3_BB, on_D2_V3_BB, on_D2_V3_BB, on_D2_V3_BB, on_D2_V3_BB, on_D2_V3_BB},
/* D3 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* D4 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_D4_V3_BB, on_D4_V3_BB, on_D4_V3_BB, on_D4_V3_BB, on_D4_V3_BB, on_D4_V3_BB},
/* D5 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* D6 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_D6_V3, on_D6_V3, on_D6_V3, on_D6_V3, on_D6_V3, nullptr},
/* D7 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_D7_GC, on_D7_GC, on_D7_GC, on_D7_GC, on_D7_GC, nullptr},
/* D8 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_D8, on_D8, on_D8, on_D8, on_D8, on_D8, on_D8, on_D8},
/* D9 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_D9, on_D9, on_D9, on_D9, on_D9, on_D9, on_D9, on_D9},
/* DA */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* DB */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_DB_V3, on_DB_V3, on_DB_V3, on_DB_V3, on_DB_V3, nullptr},
/* DC */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_DC_Ep3, on_DC_Ep3, nullptr, on_DC_BB},
/* DD */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* DE */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* DF */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_DF_BB},
// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* E0 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_E0_BB},
/* E1 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* E2 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_E2_Ep3, on_E2_Ep3, nullptr, on_E2_BB},
/* E3 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_E3_BB},
/* E4 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_E4_Ep3, on_E4_Ep3, nullptr, nullptr},
/* E5 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_E5_Ep3, on_E5_Ep3, nullptr, on_E5_BB},
/* E6 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_08_E6, on_08_E6, nullptr, nullptr},
/* E7 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_0C_C1_E7_EC, on_0C_C1_E7_EC, nullptr, on_E7_BB},
/* E8 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_E8_BB},
/* E9 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* EA */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_EA_BB},
/* EB */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_EB_BB},
/* EC */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_0C_C1_E7_EC, on_0C_C1_E7_EC, nullptr, on_EC_BB},
/* ED */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_ED_BB},
/* EE */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_EE_Ep3, on_EE_Ep3, nullptr, nullptr},
/* EF */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_EF_Ep3, on_EF_Ep3, nullptr, nullptr},
// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* F0 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* F1 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* F2 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* F3 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* F4 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* F5 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* F6 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* F7 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* F8 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* F9 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* FA */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* FB */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* FC */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* FD */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* FE */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* FF */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
// PC_PATCH BB_PATCH DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
// DC_NTE DC_112000 DCV1 DCV2 PC_NTE PC GCNTE GC EP3TE EP3 XB BB
/* 00 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 01 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 02 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 03 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 04 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 05 */ {on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_05_XB, on_ignored},
/* 06 */ {on_06, on_06, on_06, on_06, on_06, on_06, on_06, on_06, on_06, on_06, on_06, on_06},
/* 07 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 08 */ {on_08_E6, on_08_E6, on_08_E6, on_08_E6, on_08_E6, on_08_E6, on_08_E6, on_08_E6, on_08_E6, on_08_E6, on_08_E6, on_08_E6},
/* 09 */ {on_09, on_09, on_09, on_09, on_09, on_09, on_09, on_09, on_09, on_09, on_09, on_09},
/* 0A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 0B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 0C */ {on_0C_C1_E7_EC,on_0C_C1_E7_EC,on_0C_C1_E7_EC,on_0C_C1_E7_EC, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 0D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 0E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 0F */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
// DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* 10 */ {on_10, on_10, on_10, on_10, on_10, on_10, on_10, on_10, on_10, on_10, on_10, on_10},
/* 11 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 12 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 13 */ {on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_13_A7_V3_BB, on_13_A7_V3_BB, on_13_A7_V3_BB, on_13_A7_V3_BB, on_13_A7_V3_BB, on_13_A7_V3_BB},
/* 14 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 15 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 16 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 17 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 18 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 19 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 1A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 1B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 1C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 1D */ {on_1D, on_1D, on_1D, on_1D, on_1D, on_1D, on_1D, on_1D, on_1D, on_1D, on_1D, on_1D},
/* 1E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 1F */ {on_1F, on_1F, on_1F, on_1F, on_1F, on_1F, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
// DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* 20 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 21 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 22 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_ignored},
/* 23 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 24 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 25 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 26 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 27 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 28 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 29 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 2A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 2B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 2C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 2D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 2E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 2F */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
// DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* 30 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 31 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 32 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 33 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 34 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 35 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 36 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 37 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 38 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 39 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 3A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 3B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 3C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 3D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 3E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 3F */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
// DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* 40 */ {on_40, on_40, on_40, on_40, on_40, on_40, on_40, on_40, on_40, on_40, on_40, on_40},
/* 41 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 42 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 43 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 44 */ {on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_44_A6_V3_BB, on_44_A6_V3_BB, on_44_A6_V3_BB, on_44_A6_V3_BB, on_44_A6_V3_BB, on_44_A6_V3_BB},
/* 45 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 46 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 47 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 48 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 49 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 4A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 4B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 4C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 4D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 4E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 4F */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
// DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* 50 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 51 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 52 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 53 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 54 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 55 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 56 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 57 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 58 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 59 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 5A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 5B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 5C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 5D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 5E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 5F */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
// DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* 60 */ {on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB},
/* 61 */ {on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98},
/* 62 */ {on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB},
/* 63 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 64 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 65 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 66 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 67 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 68 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 69 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 6A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 6B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 6C */ {on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB},
/* 6D */ {on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB, on_6x_C9_CB},
/* 6E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 6F */ {on_6F, on_6F, on_6F, on_6F, on_6F, on_6F, on_6F, on_6F, on_6F, on_6F, on_6F, on_6F},
// DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* 70 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 71 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 72 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 73 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 74 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 75 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 76 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 77 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 78 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 79 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 7A */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 7B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 7C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 7D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 7E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 7F */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
// DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* 80 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 81 */ {on_81, on_81, on_81, on_81, on_81, on_81, on_81, on_81, on_81, on_81, on_81, on_81},
/* 82 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 83 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 84 */ {on_84, on_84, on_84, on_84, on_84, on_84, on_84, on_84, on_84, on_84, on_84, on_84},
/* 85 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 86 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 87 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 88 */ {on_88_DCNTE, on_88_DCNTE, on_88_DCNTE, on_88_DCNTE, nullptr, nullptr, on_88_DCNTE, on_88_DCNTE, on_88_DCNTE, on_88_DCNTE, nullptr, nullptr},
/* 89 */ {on_89, on_89, on_89, on_89, on_89, on_89, on_89, on_89, on_89, on_89, on_89, on_89},
/* 8A */ {on_8A, on_8A, on_8A, on_8A, on_8A, on_8A, on_8A, on_8A, on_8A, on_8A, on_8A, on_8A},
/* 8B */ {on_8B_DCNTE, on_8B_DCNTE, on_8B_DCNTE, on_8B_DCNTE, nullptr, nullptr, on_8B_DCNTE, on_8B_DCNTE, on_8B_DCNTE, on_8B_DCNTE, nullptr, nullptr},
/* 8C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 8D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 8E */ {on_8E_DCNTE, on_8E_DCNTE, on_8E_DCNTE, on_8E_DCNTE, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 8F */ {on_8F_DCNTE, on_8F_DCNTE, on_8F_DCNTE, on_8F_DCNTE, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
// DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* 90 */ {on_90_DC, on_90_DC, on_90_DC, on_90_DC, nullptr, nullptr, on_90_DC, on_90_DC, on_90_DC, on_90_DC, nullptr, nullptr},
/* 91 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 92 */ {on_92_DC, on_92_DC, on_92_DC, on_92_DC, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 93 */ {on_93_DC, on_93_DC, on_93_DC, on_93_DC, nullptr, nullptr, on_93_DC, on_93_DC, on_93_DC, on_93_DC, nullptr, on_93_BB},
/* 94 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 95 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 96 */ {on_96, on_96, on_96, on_96, on_96, on_96, on_96, on_96, on_96, on_96, on_96, nullptr},
/* 97 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 98 */ {on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98},
/* 99 */ {on_99, on_99, on_99, on_99, on_99, on_99, on_99, on_99, on_99, on_99, on_99, on_99},
/* 9A */ {on_9A, on_9A, on_9A, on_9A, on_9A, on_9A, on_9A, on_9A, on_9A, on_9A, nullptr, nullptr},
/* 9B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 9C */ {on_9C, on_9C, on_9C, on_9C, on_9C, on_9C, on_9C, on_9C, on_9C, on_9C, on_9C, nullptr},
/* 9D */ {on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, nullptr},
/* 9E */ {nullptr, nullptr, nullptr, nullptr, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, on_9E_XB, nullptr},
/* 9F */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_9F, on_9F, on_9F, on_9F, on_9F},
// DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* A0 */ {on_A0, on_A0, on_A0, on_A0, on_A0, on_A0, on_A0, on_A0, on_A0, on_A0, on_A0, on_A0},
/* A1 */ {on_A1, on_A1, on_A1, on_A1, on_A1, on_A1, on_A1, on_A1, on_A1, on_A1, on_A1, on_A1},
/* A2 */ {on_A2, on_A2, on_A2, on_A2, on_A2, on_A2, on_A2, on_A2, on_A2, on_A2, on_A2, on_A2},
/* A3 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* A4 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* A5 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* A6 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_44_A6_V3_BB, on_44_A6_V3_BB, on_44_A6_V3_BB, on_44_A6_V3_BB, on_44_A6_V3_BB, nullptr},
/* A7 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_13_A7_V3_BB, on_13_A7_V3_BB, on_13_A7_V3_BB, on_13_A7_V3_BB, on_13_A7_V3_BB, nullptr},
/* A8 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* A9 */ {on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored},
/* AA */ {nullptr, nullptr, nullptr, nullptr, on_AA, on_AA, on_AA, on_AA, on_AA, on_AA, on_AA, on_AA},
/* AB */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* AC */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_AC_V3_BB, on_AC_V3_BB, on_AC_V3_BB, on_AC_V3_BB, on_AC_V3_BB, on_AC_V3_BB},
/* AD */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* AE */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* AF */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
// DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* B0 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* B1 */ {on_B1, on_B1, on_B1, on_B1, on_B1, on_B1, on_B1, on_B1, on_B1, on_B1, on_B1, nullptr},
/* B2 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* B3 */ {on_B3, on_B3, on_B3, on_B3, on_B3, on_B3, on_B3, on_B3, on_B3, on_B3, on_B3, on_B3},
/* B4 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* B5 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* B6 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* B7 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_B7_Ep3, on_B7_Ep3, nullptr, nullptr},
/* B8 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_ignored, on_ignored, nullptr, nullptr},
/* B9 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_ignored, on_ignored, nullptr, nullptr},
/* BA */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_BA_Ep3, on_BA_Ep3, nullptr, nullptr},
/* BB */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* BC */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* BD */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* BE */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* BF */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
// DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* C0 */ {nullptr, nullptr, nullptr, on_C0, on_C0, on_C0, on_C0, on_C0, on_C0, on_C0, on_C0, on_C0},
/* C1 */ {on_0C_C1_E7_EC,on_0C_C1_E7_EC,on_0C_C1_E7_EC,on_0C_C1_E7_EC, on_C1_PC, on_C1_PC, on_0C_C1_E7_EC, on_0C_C1_E7_EC, on_0C_C1_E7_EC, on_0C_C1_E7_EC, on_0C_C1_E7_EC, on_C1_BB},
/* C2 */ {nullptr, nullptr, nullptr, on_C2, on_C2, on_C2, on_C2, on_C2, on_C2, on_C2, on_C2, on_C2},
/* C3 */ {nullptr, nullptr, nullptr, on_C3, on_C3, on_C3, on_C3, on_C3, on_C3, on_C3, on_C3, on_C3},
/* C4 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* C5 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* C6 */ {nullptr, nullptr, nullptr, nullptr, on_C6, on_C6, on_C6, on_C6, on_C6, on_C6, on_C6, on_C6},
/* C7 */ {nullptr, nullptr, nullptr, nullptr, on_C7, on_C7, on_C7, on_C7, on_C7, on_C7, on_C7, on_C7},
/* C8 */ {nullptr, nullptr, nullptr, nullptr, on_C8, on_C8, on_C8, on_C8, on_C8, on_C8, on_C8, on_C8},
/* C9 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_6x_C9_CB, on_6x_C9_CB, on_C9_XB, nullptr},
/* CA */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_CA_Ep3, on_CA_Ep3, nullptr, nullptr},
/* CB */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_6x_C9_CB, on_6x_C9_CB, nullptr, nullptr},
/* CC */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* CD */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* CE */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* CF */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
// DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* D0 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_D0_V3_BB, on_D0_V3_BB, on_D0_V3_BB, on_D0_V3_BB, on_D0_V3_BB, on_D0_V3_BB},
/* D1 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* D2 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_D2_V3_BB, on_D2_V3_BB, on_D2_V3_BB, on_D2_V3_BB, on_D2_V3_BB, on_D2_V3_BB},
/* D3 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* D4 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_D4_V3_BB, on_D4_V3_BB, on_D4_V3_BB, on_D4_V3_BB, on_D4_V3_BB, on_D4_V3_BB},
/* D5 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* D6 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_D6_V3, on_D6_V3, on_D6_V3, on_D6_V3, on_D6_V3, nullptr},
/* D7 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_D7_GC, on_D7_GC, on_D7_GC, on_D7_GC, on_D7_GC, nullptr},
/* D8 */ {nullptr, nullptr, nullptr, nullptr, on_D8, on_D8, on_D8, on_D8, on_D8, on_D8, on_D8, on_D8},
/* D9 */ {nullptr, nullptr, nullptr, nullptr, on_D9, on_D9, on_D9, on_D9, on_D9, on_D9, on_D9, on_D9},
/* DA */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* DB */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_DB_V3, on_DB_V3, on_DB_V3, on_DB_V3, on_DB_V3, nullptr},
/* DC */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_DC_Ep3, on_DC_Ep3, nullptr, on_DC_BB},
/* DD */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* DE */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* DF */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_DF_BB},
// DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* E0 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_E0_BB},
/* E1 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* E2 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_E2_Ep3, on_E2_Ep3, nullptr, on_E2_BB},
/* E3 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_E3_BB},
/* E4 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_E4_Ep3, on_E4_Ep3, nullptr, nullptr},
/* E5 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_E5_Ep3, on_E5_Ep3, nullptr, on_E5_BB},
/* E6 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_08_E6, on_08_E6, nullptr, nullptr},
/* E7 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_0C_C1_E7_EC, on_0C_C1_E7_EC, nullptr, on_E7_BB},
/* E8 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_E8_BB},
/* E9 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* EA */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_EA_BB},
/* EB */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_EB_BB},
/* EC */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_0C_C1_E7_EC, on_0C_C1_E7_EC, nullptr, on_EC_BB},
/* ED */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_ED_BB},
/* EE */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_EE_Ep3, on_EE_Ep3, nullptr, nullptr},
/* EF */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_EF_Ep3, on_EF_Ep3, nullptr, nullptr},
// DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
/* F0 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* F1 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* F2 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* F3 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* F4 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* F5 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* F6 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* F7 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* F8 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* F9 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* FA */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* FB */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* FC */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* FD */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* FE */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* FF */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
// DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB
// clang-format on
};
static void check_unlicensed_command(Version version, uint8_t command) {
switch (version) {
case Version::PC_PATCH:
case Version::BB_PATCH:
if (command != 0x02 && command != 0x04) {
throw runtime_error("only commands 02 and 04 may be sent before login");
}
break;
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
@@ -5619,11 +5438,7 @@ static void check_unlicensed_command(Version version, uint8_t command) {
}
}
void on_command(
shared_ptr<Client> c,
uint16_t command,
uint32_t flag,
string& data) {
void on_command(shared_ptr<Client> c, uint16_t command, uint32_t flag, string& data) {
c->reschedule_ping_and_timeout_events();
// Most of the command handlers assume the client is registered, logged in,
@@ -5634,7 +5449,7 @@ void on_command(
check_unlicensed_command(c->version(), command);
}
auto fn = handlers[command & 0xFF][static_cast<size_t>(c->version())];
auto fn = handlers[command & 0xFF][static_cast<size_t>(c->version()) - 2];
if (fn) {
fn(c, command, flag, data);
} else {
@@ -5658,8 +5473,6 @@ void on_command_with_header(shared_ptr<Client> c, const string& data) {
on_command(c, header.command, header.flag, sub_data);
break;
}
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::PC_NTE:
case Version::PC_V2: {
auto& header = check_size_t<PSOCommandHeaderPC>(data, 0xFFFF);
-46
View File
@@ -152,7 +152,6 @@ static const char* dc_port_map_copyright = "DreamCast Port Map. Copyright SEGA E
static const char* dc_lobby_server_copyright = "DreamCast Lobby Server. Copyright SEGA Enterprises. 1999";
static const char* bb_game_server_copyright = "Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM.";
static const char* bb_pm_server_copyright = "PSO NEW PM Server. Copyright 1999-2002 SONICTEAM.";
static const char* patch_server_copyright = "Patch Server. Copyright SonicTeam, LTD. 2001";
S_ServerInitWithAfterMessage_DC_PC_V3_02_17_91_9B<0xB4>
prepare_server_init_contents_console(
@@ -240,20 +239,6 @@ void send_server_init_bb(shared_ptr<Client> c, uint8_t flags) {
sizeof(cmd.basic_cmd.server_key), true);
}
void send_server_init_patch(shared_ptr<Client> c) {
uint32_t server_key = random_object<uint32_t>();
uint32_t client_key = random_object<uint32_t>();
S_ServerInit_Patch_02 cmd;
cmd.copyright.encode(patch_server_copyright);
cmd.server_key = server_key;
cmd.client_key = client_key;
send_command_t(c, 0x02, 0x00, cmd);
c->channel.crypt_out = make_shared<PSOV2Encryption>(server_key);
c->channel.crypt_in = make_shared<PSOV2Encryption>(client_key);
}
void send_server_init(shared_ptr<Client> c, uint8_t flags) {
switch (c->version()) {
case Version::DC_NTE:
@@ -269,10 +254,6 @@ void send_server_init(shared_ptr<Client> c, uint8_t flags) {
case Version::XB_V3:
send_server_init_dc_pc_v3(c, flags);
break;
case Version::PC_PATCH:
case Version::BB_PATCH:
send_server_init_patch(c);
break;
case Version::BB_V4:
send_server_init_bb(c, flags);
break;
@@ -676,33 +657,6 @@ void send_complete_player_bb(shared_ptr<Client> c) {
send_command_t(c, 0x00E7, 0x00000000, cmd);
}
////////////////////////////////////////////////////////////////////////////////
// patch functions
void send_enter_directory_patch(shared_ptr<Client> c, const string& dir) {
S_EnterDirectory_Patch_09 cmd = {{dir, 1}};
send_command_t(c, 0x09, 0x00, cmd);
}
void send_patch_file(shared_ptr<Client> c, shared_ptr<PatchFileIndex::File> f) {
S_OpenFile_Patch_06 open_cmd = {0, f->size, {f->name, 1}};
send_command_t(c, 0x06, 0x00, open_cmd);
for (size_t x = 0; x < f->chunk_crcs.size(); x++) {
auto data = f->load_data();
size_t chunk_size = min<uint32_t>(f->size - (x * 0x4000), 0x4000);
vector<pair<const void*, size_t>> blocks;
S_WriteFileHeader_Patch_07 cmd_header = {x, f->chunk_crcs[x], chunk_size};
blocks.emplace_back(&cmd_header, sizeof(cmd_header));
blocks.emplace_back(data->data() + (x * 0x4000), chunk_size);
send_command(c, 0x07, 0x00, blocks);
}
S_CloseCurrentFile_Patch_08 close_cmd = {0};
send_command_t(c, 0x08, 0x00, close_cmd);
}
////////////////////////////////////////////////////////////////////////////////
// message functions
-3
View File
@@ -178,9 +178,6 @@ void send_stream_file_chunk_bb(std::shared_ptr<Client> c, uint32_t chunk_index);
void send_approve_player_choice_bb(std::shared_ptr<Client> c);
void send_complete_player_bb(std::shared_ptr<Client> c);
void send_enter_directory_patch(std::shared_ptr<Client> c, const std::string& dir);
void send_patch_file(std::shared_ptr<Client> c, std::shared_ptr<PatchFileIndex::File> f);
void send_message_box(std::shared_ptr<Client> c, const std::string& text);
void send_ep3_timed_message_box(Channel& ch, uint32_t frames, const std::string& text);
void send_lobby_name(std::shared_ptr<Client> c, const std::string& text);
+1 -2
View File
@@ -134,6 +134,7 @@ void Server::connect_client(
c->channel.on_command_received = Server::on_client_input;
c->channel.on_error = Server::on_client_error;
c->channel.context_obj = this;
this->state->channel_to_client.emplace(&c->channel, c);
server_log.info(
"Client connected: C-%" PRIX64 " on virtual connection %p via T-%hu-%s-%s-VI",
@@ -143,8 +144,6 @@ void Server::connect_client(
name_for_enum(version),
name_for_enum(initial_state));
this->state->channel_to_client.emplace(&c->channel, c);
// Manually set the remote address, since the bufferevent has no fd and the
// Channel constructor can't figure out the virtual remote address
auto* remote_sin = reinterpret_cast<sockaddr_in*>(&c->channel.remote_addr);
+21
View File
@@ -645,6 +645,7 @@ void ServerState::load_config() {
this->client_ping_interval_usecs = json.get_int("ClientPingInterval", 30000000);
this->client_idle_timeout_usecs = json.get_int("ClientIdleTimeout", 60000000);
this->patch_client_idle_timeout_usecs = json.get_int("PatchClientIdleTimeout", 300000000);
this->ip_stack_debug = json.get_bool("IPStackDebug", false);
this->allow_unregistered_users = json.get_bool("AllowUnregisteredUsers", false);
@@ -1679,3 +1680,23 @@ void ServerState::load_all() {
this->load_teams(false);
this->load_quest_index(false);
}
shared_ptr<PatchServer::Config> ServerState::generate_patch_server_config(bool is_bb) const {
auto ret = make_shared<PatchServer::Config>();
ret->allow_unregistered_users = this->allow_unregistered_users;
ret->hide_data_from_logs = this->hide_download_commands;
ret->idle_timeout_usecs = this->patch_client_idle_timeout_usecs;
ret->message = is_bb ? this->bb_patch_server_message : this->pc_patch_server_message;
ret->license_index = this->license_index;
ret->patch_file_index = is_bb ? this->bb_patch_file_index : this->pc_patch_file_index;
return ret;
}
void ServerState::update_patch_server_configs() const {
if (this->pc_patch_server) {
this->pc_patch_server->set_config(this->generate_patch_server_config(false));
}
if (this->bb_patch_server) {
this->bb_patch_server->set_config(this->generate_patch_server_config(true));
}
}
+7
View File
@@ -24,6 +24,7 @@
#include "License.hh"
#include "Lobby.hh"
#include "Menu.hh"
#include "PatchServer.hh"
#include "PlayerFilesManager.hh"
#include "Quest.hh"
#include "TeamIndex.hh"
@@ -79,6 +80,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::vector<std::string> ppp_stack_addresses;
uint64_t client_ping_interval_usecs = 30000000;
uint64_t client_idle_timeout_usecs = 60000000;
uint64_t patch_client_idle_timeout_usecs = 300000000;
bool ip_stack_debug = false;
bool allow_unregistered_users = false;
bool allow_pc_nte = false;
@@ -235,6 +237,8 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::shared_ptr<ProxyServer> proxy_server;
std::shared_ptr<Server> game_server;
std::shared_ptr<PatchServer> pc_patch_server;
std::shared_ptr<PatchServer> bb_patch_server;
explicit ServerState(const std::string& config_filename = "");
ServerState(std::shared_ptr<struct event_base> base, const std::string& config_filename, bool is_replay);
@@ -312,6 +316,9 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
}
}
std::shared_ptr<PatchServer::Config> generate_patch_server_config(bool is_bb) const;
void update_patch_server_configs() const;
// The following functions may only be called from a non-event thread if they
// take a from_non_event_thread argument; any function that does not have this
// argument must be called only from the event thread.
-2
View File
@@ -11,8 +11,6 @@
using namespace std;
// A third case is when inbuf is NULL or *inbuf is NULL, and outbuf is NULL or *outbuf is NULL. In this case, the iconv function sets cds conversion state to the initial state.
const iconv_t TextTranscoder::INVALID_IC = (iconv_t)(-1);
const size_t TextTranscoder::FAILURE_RESULT = static_cast<size_t>(-1);