mirror of
https://github.com/EQEmu/Server.git
synced 2026-05-24 14:12:27 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 75ddf8dfc3 | |||
| a3802ff257 | |||
| ca23b8612e | |||
| 485ae4809d | |||
| 452407ed67 | |||
| aa1c481f65 | |||
| 780dcdab5a | |||
| 16ec08e71c | |||
| 0024073cee | |||
| 37b8428c48 |
+125
-52
@@ -88,6 +88,9 @@ bool Client::Process()
|
||||
SendPlayToWorld((const char *) app->pBuffer);
|
||||
break;
|
||||
}
|
||||
case OP_SystemFingerprint: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
delete app;
|
||||
@@ -104,16 +107,19 @@ void Client::HandleSessionReady(const char *data, unsigned int size)
|
||||
return;
|
||||
}
|
||||
|
||||
if (size < sizeof(unsigned int)) {
|
||||
if (size < sizeof(int32)) {
|
||||
LogError("Session ready was too small");
|
||||
return;
|
||||
}
|
||||
|
||||
//existing sequence id
|
||||
int32 sequence_in = *(int32*)data;
|
||||
|
||||
m_client_status = cs_waiting_for_login;
|
||||
|
||||
auto *outapp = new EQApplicationPacket(OP_ChatMessage, sizeof(LoginHandShakeReply));
|
||||
auto buf = reinterpret_cast<LoginHandShakeReply *>(outapp->pBuffer);
|
||||
buf->base_header.sequence = 0x02;
|
||||
buf->base_header.sequence = sequence_in;
|
||||
buf->base_reply.success = true;
|
||||
buf->base_reply.error_str_id = 0x65; // 101 "No Error"
|
||||
|
||||
@@ -356,28 +362,55 @@ void Client::SendFailedLogin()
|
||||
m_stored_username.clear();
|
||||
m_stored_password.clear();
|
||||
|
||||
// unencrypted
|
||||
LoginBaseMessage h{};
|
||||
h.sequence = m_login_base_message.sequence; // login (3)
|
||||
h.encrypt_type = m_login_base_message.encrypt_type;
|
||||
if (m_client_version == cv_steam_latest) {
|
||||
// unencrypted
|
||||
LoginBaseMessage h{};
|
||||
h.sequence = m_login_base_message.sequence; // login (3)
|
||||
h.encrypt_type = m_login_base_message.encrypt_type;
|
||||
|
||||
// encrypted
|
||||
PlayerLoginReply r{};
|
||||
r.base_reply.success = false;
|
||||
r.base_reply.error_str_id = 105; // Error - The username and/or password were not valid
|
||||
// encrypted
|
||||
PlayerLoginReplySteamLatest r{};
|
||||
r.base_reply.success = false;
|
||||
r.base_reply.error_str_id = 105; // Error - The username and/or password were not valid
|
||||
|
||||
char encrypted_buffer[80] = {0};
|
||||
auto rc = eqcrypt_block((const char *) &r, sizeof(r), encrypted_buffer, 1);
|
||||
if (rc == nullptr) {
|
||||
LogDebug("Failed to encrypt eqcrypt block for failed login");
|
||||
char encrypted_buffer[80] = { 0 };
|
||||
auto rc = eqcrypt_block((const char*)&r, sizeof(r), encrypted_buffer, 1);
|
||||
if (rc == nullptr) {
|
||||
LogDebug("Failed to encrypt eqcrypt block for failed login");
|
||||
}
|
||||
|
||||
constexpr int outsize = sizeof(LoginBaseMessage) + sizeof(encrypted_buffer);
|
||||
EQApplicationPacket outapp(OP_LoginAccepted, outsize);
|
||||
outapp.WriteData(&h, sizeof(h));
|
||||
outapp.WriteData(&encrypted_buffer, sizeof(encrypted_buffer));
|
||||
|
||||
m_connection->QueuePacket(&outapp);
|
||||
}
|
||||
else {
|
||||
// unencrypted
|
||||
LoginBaseMessage h{};
|
||||
h.sequence = m_login_base_message.sequence; // login (3)
|
||||
h.encrypt_type = m_login_base_message.encrypt_type;
|
||||
|
||||
constexpr int outsize = sizeof(LoginBaseMessage) + sizeof(encrypted_buffer);
|
||||
EQApplicationPacket outapp(OP_LoginAccepted, outsize);
|
||||
outapp.WriteData(&h, sizeof(h));
|
||||
outapp.WriteData(&encrypted_buffer, sizeof(encrypted_buffer));
|
||||
// encrypted
|
||||
PlayerLoginReply r{};
|
||||
r.base_reply.success = false;
|
||||
r.base_reply.error_str_id = 105; // Error - The username and/or password were not valid
|
||||
|
||||
m_connection->QueuePacket(&outapp);
|
||||
char encrypted_buffer[80] = { 0 };
|
||||
auto rc = eqcrypt_block((const char*)&r, sizeof(r), encrypted_buffer, 1);
|
||||
if (rc == nullptr) {
|
||||
LogDebug("Failed to encrypt eqcrypt block for failed login");
|
||||
}
|
||||
|
||||
constexpr int outsize = sizeof(LoginBaseMessage) + sizeof(encrypted_buffer);
|
||||
EQApplicationPacket outapp(OP_LoginAccepted, outsize);
|
||||
outapp.WriteData(&h, sizeof(h));
|
||||
outapp.WriteData(&encrypted_buffer, sizeof(encrypted_buffer));
|
||||
|
||||
m_connection->QueuePacket(&outapp);
|
||||
}
|
||||
|
||||
m_client_status = cs_failed_to_login;
|
||||
}
|
||||
|
||||
@@ -463,46 +496,86 @@ void Client::DoSuccessfulLogin(LoginAccountsRepository::LoginAccounts &a)
|
||||
m_account_name = a.account_name;
|
||||
m_loginserver_name = a.source_loginserver;
|
||||
|
||||
// unencrypted
|
||||
LoginBaseMessage h{};
|
||||
h.sequence = m_login_base_message.sequence;
|
||||
h.compressed = false;
|
||||
h.encrypt_type = m_login_base_message.encrypt_type;
|
||||
h.unk3 = m_login_base_message.unk3;
|
||||
if (m_client_version == cv_steam_latest) {
|
||||
// unencrypted
|
||||
LoginBaseMessage h{};
|
||||
h.sequence = m_login_base_message.sequence;
|
||||
h.compressed = false;
|
||||
h.encrypt_type = m_login_base_message.encrypt_type;
|
||||
h.unk3 = m_login_base_message.unk3;
|
||||
|
||||
// not serializing any of the variable length strings so just use struct directly
|
||||
PlayerLoginReply r{};
|
||||
r.base_reply.success = true;
|
||||
r.base_reply.error_str_id = 101; // No Error
|
||||
r.unk1 = 0;
|
||||
r.unk2 = 0;
|
||||
r.lsid = a.id;
|
||||
r.failed_attempts = 0;
|
||||
r.show_player_count = server.options.IsShowPlayerCountEnabled();
|
||||
r.offer_min_days = 99;
|
||||
r.offer_min_views = -1;
|
||||
r.offer_cooldown_minutes = 0;
|
||||
r.web_offer_number = 0;
|
||||
r.web_offer_min_days = 99;
|
||||
r.web_offer_min_views = -1;
|
||||
r.web_offer_cooldown_minutes = 0;
|
||||
memcpy(r.key, m_key.c_str(), m_key.size());
|
||||
// not serializing any of the variable length strings so just use struct directly
|
||||
PlayerLoginReplySteamLatest r{};
|
||||
r.base_reply.success = true;
|
||||
r.base_reply.error_str_id = 101; // No Error
|
||||
r.unk1 = 0;
|
||||
r.unk2 = 0;
|
||||
r.lsid = a.id;
|
||||
r.failed_attempts = 0;
|
||||
r.show_player_count = server.options.IsShowPlayerCountEnabled();
|
||||
r.unk3 = 0;
|
||||
r.unk4 = 0;
|
||||
memcpy(r.key, m_key.c_str(), m_key.size());
|
||||
|
||||
SendExpansionPacketData(r);
|
||||
//todo: needs to be fixed
|
||||
//SendExpansionPacketData(r);
|
||||
|
||||
char encrypted_buffer[80] = {0};
|
||||
char encrypted_buffer[80] = { 0 };
|
||||
|
||||
auto rc = eqcrypt_block((const char *) &r, sizeof(r), encrypted_buffer, 1);
|
||||
if (rc == nullptr) {
|
||||
LogDebug("Failed to encrypt eqcrypt block");
|
||||
auto rc = eqcrypt_block((const char*)&r, sizeof(r), encrypted_buffer, 1);
|
||||
if (rc == nullptr) {
|
||||
LogDebug("Failed to encrypt eqcrypt block");
|
||||
}
|
||||
|
||||
constexpr int outsize = sizeof(LoginBaseMessage) + sizeof(encrypted_buffer);
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_LoginAccepted, outsize);
|
||||
outapp->WriteData(&h, sizeof(h));
|
||||
outapp->WriteData(&encrypted_buffer, sizeof(encrypted_buffer));
|
||||
|
||||
m_connection->QueuePacket(outapp.get());
|
||||
}
|
||||
else {
|
||||
// unencrypted
|
||||
LoginBaseMessage h{};
|
||||
h.sequence = m_login_base_message.sequence;
|
||||
h.compressed = false;
|
||||
h.encrypt_type = m_login_base_message.encrypt_type;
|
||||
h.unk3 = m_login_base_message.unk3;
|
||||
|
||||
constexpr int outsize = sizeof(LoginBaseMessage) + sizeof(encrypted_buffer);
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_LoginAccepted, outsize);
|
||||
outapp->WriteData(&h, sizeof(h));
|
||||
outapp->WriteData(&encrypted_buffer, sizeof(encrypted_buffer));
|
||||
// not serializing any of the variable length strings so just use struct directly
|
||||
PlayerLoginReply r{};
|
||||
r.base_reply.success = true;
|
||||
r.base_reply.error_str_id = 101; // No Error
|
||||
r.unk1 = 0;
|
||||
r.unk2 = 0;
|
||||
r.lsid = a.id;
|
||||
r.failed_attempts = 0;
|
||||
r.show_player_count = server.options.IsShowPlayerCountEnabled();
|
||||
r.offer_min_days = 99;
|
||||
r.offer_min_views = -1;
|
||||
r.offer_cooldown_minutes = 0;
|
||||
r.web_offer_number = 0;
|
||||
r.web_offer_min_days = 99;
|
||||
r.web_offer_min_views = -1;
|
||||
r.web_offer_cooldown_minutes = 0;
|
||||
memcpy(r.key, m_key.c_str(), m_key.size());
|
||||
|
||||
m_connection->QueuePacket(outapp.get());
|
||||
SendExpansionPacketData(r);
|
||||
|
||||
char encrypted_buffer[80] = { 0 };
|
||||
|
||||
auto rc = eqcrypt_block((const char*)&r, sizeof(r), encrypted_buffer, 1);
|
||||
if (rc == nullptr) {
|
||||
LogDebug("Failed to encrypt eqcrypt block");
|
||||
}
|
||||
|
||||
constexpr int outsize = sizeof(LoginBaseMessage) + sizeof(encrypted_buffer);
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_LoginAccepted, outsize);
|
||||
outapp->WriteData(&h, sizeof(h));
|
||||
outapp->WriteData(&encrypted_buffer, sizeof(encrypted_buffer));
|
||||
|
||||
m_connection->QueuePacket(outapp.get());
|
||||
}
|
||||
|
||||
m_client_status = cs_logged_in;
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ void CheckSoDOpcodeFile(const std::string &path)
|
||||
}
|
||||
}
|
||||
|
||||
void CheckLarionOpcodeFile(const std::string &path)
|
||||
void CheckSteamLatestOpcodeFile(const std::string &path)
|
||||
{
|
||||
if (File::Exists(path)) {
|
||||
return;
|
||||
@@ -87,15 +87,15 @@ void CheckLarionOpcodeFile(const std::string &path)
|
||||
fprintf(f, "OP_Login=0x0002\n");
|
||||
fprintf(f, "OP_ServerListRequest=0x0004\n");
|
||||
fprintf(f, "OP_PlayEverquestRequest=0x000d\n");
|
||||
fprintf(f, "OP_PlayEverquestResponse=0x0022\n");
|
||||
fprintf(f, "OP_ChatMessage=0x0017\n");
|
||||
fprintf(f, "OP_LoginAccepted=0x0018\n");
|
||||
fprintf(f, "OP_ServerListResponse=0x0019\n");
|
||||
fprintf(f, "OP_Poll=0x0029\n");
|
||||
fprintf(f, "OP_PlayEverquestResponse=0x0023\n");
|
||||
fprintf(f, "OP_ChatMessage=0x0018\n");
|
||||
fprintf(f, "OP_LoginAccepted=0x0019\n");
|
||||
fprintf(f, "OP_ServerListResponse=0x001a\n");
|
||||
fprintf(f, "OP_Poll=0x002a\n");
|
||||
fprintf(f, "OP_EnterChat=0x000f\n");
|
||||
fprintf(f, "OP_PollResponse=0x0011\n");
|
||||
fprintf(f, "OP_SystemFingerprint=0x0016\n");
|
||||
fprintf(f, "OP_ExpansionList=0x0030\n");
|
||||
fprintf(f, "OP_ExpansionList=0x0031\n");
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
@@ -177,40 +177,40 @@ ClientManager::ClientManager()
|
||||
}
|
||||
);
|
||||
|
||||
int larion_port = server.config.GetVariableInt("client_configuration", "larion_port", 15900);
|
||||
int steam_latest_port = server.config.GetVariableInt("client_configuration", "steam_latest_port", 15900);
|
||||
|
||||
EQStreamManagerInterfaceOptions larion_opts(larion_port, false, false);
|
||||
EQStreamManagerInterfaceOptions steam_latest_opts(steam_latest_port, false, false);
|
||||
|
||||
m_larion_stream = new EQ::Net::EQStreamManager(larion_opts);
|
||||
m_larion_ops = new RegularOpcodeManager;
|
||||
m_steam_latest_stream = new EQ::Net::EQStreamManager(steam_latest_opts);
|
||||
m_steam_latest_ops = new RegularOpcodeManager;
|
||||
|
||||
opcodes_path = fmt::format(
|
||||
"{}/{}",
|
||||
PathManager::Instance()->GetOpcodePath(),
|
||||
"login_opcodes_larion.conf"
|
||||
"login_opcodes_steam_latest.conf"
|
||||
);
|
||||
|
||||
CheckLarionOpcodeFile(opcodes_path);
|
||||
CheckSteamLatestOpcodeFile(opcodes_path);
|
||||
|
||||
if (!m_larion_ops->LoadOpcodes(opcodes_path.c_str())) {
|
||||
if (!m_steam_latest_ops->LoadOpcodes(opcodes_path.c_str())) {
|
||||
LogError(
|
||||
"ClientManager fatal error: couldn't load opcodes for Larion file [{}]",
|
||||
server.config.GetVariableString("client_configuration", "larion_opcodes", "login_opcodes.conf")
|
||||
"ClientManager fatal error: couldn't load opcodes for Steam Latest file [{}]",
|
||||
server.config.GetVariableString("client_configuration", "steam_latest_opcodes", "login_opcodes.conf")
|
||||
);
|
||||
|
||||
run_server = false;
|
||||
}
|
||||
|
||||
m_larion_stream->OnNewConnection(
|
||||
m_steam_latest_stream->OnNewConnection(
|
||||
[this](std::shared_ptr<EQ::Net::EQStream> stream) {
|
||||
LogInfo(
|
||||
"New Larion client connection from [{}:{}]",
|
||||
"New Steam Latest client connection from [{}:{}]",
|
||||
long2ip(stream->GetRemoteIP()),
|
||||
stream->GetRemotePort()
|
||||
);
|
||||
|
||||
stream->SetOpcodeManager(&m_larion_ops);
|
||||
Client *c = new Client(stream, cv_larion);
|
||||
stream->SetOpcodeManager(&m_steam_latest_ops);
|
||||
Client *c = new Client(stream, cv_steam_latest);
|
||||
m_clients.push_back(c);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -37,6 +37,6 @@ private:
|
||||
EQ::Net::EQStreamManager *m_titanium_stream;
|
||||
OpcodeManager *m_sod_ops;
|
||||
EQ::Net::EQStreamManager *m_sod_stream;
|
||||
OpcodeManager *m_larion_ops;
|
||||
EQ::Net::EQStreamManager *m_larion_stream;
|
||||
OpcodeManager *m_steam_latest_ops;
|
||||
EQ::Net::EQStreamManager *m_steam_latest_stream;
|
||||
};
|
||||
|
||||
@@ -67,6 +67,21 @@ struct PlayerLoginReply {
|
||||
char unknown[1]; // variable length, password unlikely? client doesn't send this on re-login from char select
|
||||
};
|
||||
|
||||
struct PlayerLoginReplySteamLatest
|
||||
{
|
||||
LoginBaseReplyMessage base_reply;
|
||||
int8_t unk1; // (default: 0)
|
||||
int8_t unk2; // (default: 0)
|
||||
int32_t lsid; // (default: -1)
|
||||
char key[11]; // client reads until null (variable length)
|
||||
int32_t failed_attempts;
|
||||
bool show_player_count; // admin flag, enables admin button and shows server player counts (default: false)
|
||||
int32_t unk3; // guess, needs more investigation (default: 0)
|
||||
int32_t unk4; // guess, needs more investigation (default: 0)
|
||||
char username[1]; // variable length, if not empty client attempts to re-login to server select when quitting from char select and sends this in a struct
|
||||
char unknown[1]; // variable length, password unlikely? client doesn't send this on re-login from char select
|
||||
};
|
||||
|
||||
// variable length, for reference
|
||||
struct LoginClientServerData {
|
||||
char ip[1];
|
||||
@@ -100,12 +115,18 @@ struct PlayEverquestResponse {
|
||||
uint32 server_number;
|
||||
};
|
||||
|
||||
//for reference
|
||||
struct SystemFingerprint {
|
||||
LoginBaseMessage base_header;
|
||||
char fingerprint[1];
|
||||
};
|
||||
|
||||
#pragma pack()
|
||||
|
||||
enum LSClientVersion {
|
||||
cv_titanium,
|
||||
cv_sod,
|
||||
cv_larion
|
||||
cv_steam_latest
|
||||
};
|
||||
|
||||
enum LSClientStatus {
|
||||
|
||||
+6
-6
@@ -3,12 +3,12 @@ OP_SessionReady=0x0001
|
||||
OP_Login=0x0002
|
||||
OP_ServerListRequest=0x0004
|
||||
OP_PlayEverquestRequest=0x000d
|
||||
OP_PlayEverquestResponse=0x0022
|
||||
OP_ChatMessage=0x0017
|
||||
OP_LoginAccepted=0x0018
|
||||
OP_ServerListResponse=0x0019
|
||||
OP_Poll=0x0029
|
||||
OP_PlayEverquestResponse=0x0023
|
||||
OP_ChatMessage=0x0018
|
||||
OP_LoginAccepted=0x0019
|
||||
OP_ServerListResponse=0x001a
|
||||
OP_Poll=0x002a
|
||||
OP_EnterChat=0x000f
|
||||
OP_PollResponse=0x0011
|
||||
OP_SystemFingerprint=0x0016
|
||||
OP_ExpansionList=0x0030
|
||||
OP_ExpansionList=0x0031
|
||||
@@ -706,19 +706,17 @@ bool WorldServer::ValidateWorldServerAdminLogin(
|
||||
|
||||
void WorldServer::SerializeForClientServerList(SerializeBuffer &out, bool use_local_ip, LSClientVersion version) const
|
||||
{
|
||||
// see LoginClientServerData_Struct
|
||||
if (use_local_ip) {
|
||||
out.WriteString(GetLocalIP());
|
||||
}
|
||||
else {
|
||||
out.WriteString(m_remote_ip_address);
|
||||
}
|
||||
if (version == cv_steam_latest) {
|
||||
if (use_local_ip) {
|
||||
out.WriteString(GetLocalIP());
|
||||
}
|
||||
else {
|
||||
out.WriteString(m_remote_ip_address);
|
||||
}
|
||||
|
||||
if (version == cv_larion) {
|
||||
out.WriteUInt32(9000);
|
||||
}
|
||||
out.WriteInt32(9000); // port, not currently settable in eqemu but needed for compat
|
||||
|
||||
switch (GetServerListID()) {
|
||||
switch (GetServerListID()) {
|
||||
case LS::ServerType::Legends:
|
||||
out.WriteInt32(LS::ServerTypeFlags::Legends);
|
||||
break;
|
||||
@@ -728,35 +726,74 @@ void WorldServer::SerializeForClientServerList(SerializeBuffer &out, bool use_lo
|
||||
default:
|
||||
out.WriteInt32(LS::ServerTypeFlags::Standard);
|
||||
break;
|
||||
}
|
||||
if (version == cv_larion) {
|
||||
auto server_id = m_server_id;
|
||||
//if this is 0, the client will not show the server in the list
|
||||
out.WriteUInt32(1);
|
||||
out.WriteUInt32(server_id);
|
||||
}
|
||||
else {
|
||||
}
|
||||
|
||||
out.WriteInt32(289); //unsure what this is yet
|
||||
out.WriteUInt32(m_server_id);
|
||||
}
|
||||
|
||||
out.WriteString(m_server_long_name);
|
||||
out.WriteString("us"); // country code
|
||||
out.WriteString("en"); // language code
|
||||
out.WriteString(m_server_long_name);
|
||||
out.WriteString("US"); // country code
|
||||
out.WriteString("EN"); // language code
|
||||
out.WriteString("Standard");
|
||||
out.WriteString("This server has no description set currently.");
|
||||
|
||||
// 0 = Up, 1 = Down, 2 = Up, 3 = down, 4 = locked, 5 = locked(down)
|
||||
if (GetStatus() < 0) {
|
||||
if (GetZonesBooted() == 0) {
|
||||
out.WriteInt32(LS::ServerStatusFlags::Down);
|
||||
if (GetStatus() < 0) {
|
||||
if (GetZonesBooted() == 0) {
|
||||
out.WriteInt32(LS::ServerStatusFlags::Down);
|
||||
}
|
||||
else {
|
||||
out.WriteInt32(LS::ServerStatusFlags::Locked);
|
||||
}
|
||||
}
|
||||
else {
|
||||
out.WriteInt32(LS::ServerStatusFlags::Locked);
|
||||
out.WriteInt32(LS::ServerStatusFlags::Up);
|
||||
}
|
||||
|
||||
out.WriteUInt32(GetPlayersOnline());
|
||||
out.WriteInt32(31); //expansions
|
||||
out.WriteInt32(0); //truebox
|
||||
}
|
||||
else {
|
||||
out.WriteInt32(LS::ServerStatusFlags::Up);
|
||||
}
|
||||
// see LoginClientServerData_Struct
|
||||
if (use_local_ip) {
|
||||
out.WriteString(GetLocalIP());
|
||||
}
|
||||
else {
|
||||
out.WriteString(m_remote_ip_address);
|
||||
}
|
||||
|
||||
switch (GetServerListID()) {
|
||||
case LS::ServerType::Legends:
|
||||
out.WriteInt32(LS::ServerTypeFlags::Legends);
|
||||
break;
|
||||
case LS::ServerType::Preferred:
|
||||
out.WriteInt32(LS::ServerTypeFlags::Preferred);
|
||||
break;
|
||||
default:
|
||||
out.WriteInt32(LS::ServerTypeFlags::Standard);
|
||||
break;
|
||||
}
|
||||
|
||||
out.WriteUInt32(GetPlayersOnline());
|
||||
out.WriteUInt32(m_server_id);
|
||||
out.WriteString(m_server_long_name);
|
||||
out.WriteString("us"); // country code
|
||||
out.WriteString("en"); // language code
|
||||
|
||||
// 0 = Up, 1 = Down, 2 = Up, 3 = down, 4 = locked, 5 = locked(down)
|
||||
if (GetStatus() < 0) {
|
||||
if (GetZonesBooted() == 0) {
|
||||
out.WriteInt32(LS::ServerStatusFlags::Down);
|
||||
}
|
||||
else {
|
||||
out.WriteInt32(LS::ServerStatusFlags::Locked);
|
||||
}
|
||||
}
|
||||
else {
|
||||
out.WriteInt32(LS::ServerStatusFlags::Up);
|
||||
}
|
||||
|
||||
out.WriteUInt32(GetPlayersOnline());
|
||||
}
|
||||
}
|
||||
|
||||
void WorldServer::FormatWorldServerName(char *name, int8 server_list_type)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
This is a bunch of ImHex patterns for viewing various Outer Brood packets
|
||||
@@ -0,0 +1,23 @@
|
||||
struct BaseResponse
|
||||
{
|
||||
u8 success;
|
||||
u32 error_str_id;
|
||||
char error_str[];
|
||||
};
|
||||
|
||||
struct Packet {
|
||||
BaseResponse base;
|
||||
s8 unk1; //I think this is just padding
|
||||
s8 unk2; //I think this is just padding
|
||||
u32 lsid;
|
||||
char key[];
|
||||
s32 failed_attempts;
|
||||
u8 show_player_count;
|
||||
s32 unk3; // 0
|
||||
s32 unk4; // 0
|
||||
char username[];
|
||||
char password[]; //I'm not sure this is correct, it feels like this might be some internal refresh token
|
||||
char paddingEnd[2];
|
||||
};
|
||||
|
||||
Packet p @ 0x00;
|
||||
@@ -0,0 +1,19 @@
|
||||
// 0x01
|
||||
|
||||
struct LoginBase
|
||||
{
|
||||
u32 sequence_id;
|
||||
u8 compressed;
|
||||
u8 encrypt_type;
|
||||
u32 unknown08;
|
||||
};
|
||||
|
||||
struct Packet {
|
||||
LoginBase base;
|
||||
u8 success;
|
||||
s32 error_str_id;
|
||||
char error_msg[];
|
||||
char other_msg[];
|
||||
};
|
||||
|
||||
Packet packet @0x00;
|
||||
@@ -0,0 +1,22 @@
|
||||
// 0x31
|
||||
|
||||
struct Expansion
|
||||
{
|
||||
u32 index;
|
||||
u8 owned;
|
||||
s32 expansion_name_string_id;
|
||||
s32 order_string_id;
|
||||
s32 unknown_string_id;
|
||||
u32 unknown17;
|
||||
|
||||
};
|
||||
|
||||
struct Packet {
|
||||
u32 unknown00;
|
||||
u32 unknown04;
|
||||
u16 unknown08;
|
||||
u32 expansion_count;
|
||||
Expansion expansions[expansion_count];
|
||||
};
|
||||
|
||||
Packet packet @0x00;
|
||||
@@ -0,0 +1,18 @@
|
||||
// 0x02
|
||||
|
||||
#include <std/mem.pat>
|
||||
|
||||
struct LoginBase
|
||||
{
|
||||
u32 sequence_id;
|
||||
u8 compressed;
|
||||
u8 encrypt_type;
|
||||
u32 unknown08;
|
||||
};
|
||||
|
||||
struct Packet {
|
||||
LoginBase base;
|
||||
u8 payload[std::mem::size() - $];
|
||||
};
|
||||
|
||||
Packet packet @0x00;
|
||||
@@ -0,0 +1,18 @@
|
||||
// 0x19
|
||||
|
||||
#include <std/mem.pat>
|
||||
|
||||
struct LoginBase
|
||||
{
|
||||
u32 sequence_id;
|
||||
u8 compressed;
|
||||
u8 encrypt_type;
|
||||
u32 unknown08;
|
||||
};
|
||||
|
||||
struct Packet {
|
||||
LoginBase base;
|
||||
u8 payload[std::mem::size() - $];
|
||||
};
|
||||
|
||||
Packet packet @0x00;
|
||||
@@ -0,0 +1,20 @@
|
||||
// 0xd
|
||||
|
||||
struct LoginBase
|
||||
{
|
||||
u32 sequence_id;
|
||||
u8 compressed;
|
||||
u8 encrypt_type;
|
||||
u32 unknown08;
|
||||
};
|
||||
|
||||
struct Packet {
|
||||
LoginBase base;
|
||||
u32 server_id;
|
||||
char fingerprint[];
|
||||
u32 unknown1;
|
||||
u8 unknown2;
|
||||
u32 unknown3;
|
||||
};
|
||||
|
||||
Packet packet @0x00;
|
||||
@@ -0,0 +1,19 @@
|
||||
// 0x23
|
||||
|
||||
struct LoginBase
|
||||
{
|
||||
u32 sequence_id;
|
||||
u8 compressed;
|
||||
u8 encrypt_type;
|
||||
u32 unknown08;
|
||||
};
|
||||
|
||||
struct Packet {
|
||||
LoginBase base;
|
||||
u8 success;
|
||||
u32 login_server_string_id;
|
||||
char login_server_string;
|
||||
|
||||
};
|
||||
|
||||
Packet packet @0x00;
|
||||
@@ -0,0 +1,15 @@
|
||||
// 0x04
|
||||
|
||||
struct LoginBase
|
||||
{
|
||||
u32 sequence_id;
|
||||
u8 compressed;
|
||||
u8 encrypt_type;
|
||||
u32 unknown08;
|
||||
};
|
||||
|
||||
struct Packet {
|
||||
LoginBase base;
|
||||
};
|
||||
|
||||
Packet packet @0x00;
|
||||
@@ -0,0 +1,44 @@
|
||||
// 0x1a
|
||||
// work in progress
|
||||
|
||||
struct LoginBase
|
||||
{
|
||||
u32 sequence_id;
|
||||
u8 compressed;
|
||||
u8 encrypt_type;
|
||||
u32 unknown08;
|
||||
};
|
||||
|
||||
struct Realm
|
||||
{
|
||||
char address[];
|
||||
u32 port;
|
||||
u32 server_category;
|
||||
//not sure yet, seen 289 on a lot of classic servers
|
||||
//41 fangbreaker, teek, oakwynd, tormax
|
||||
//31 yelniak
|
||||
//33 vaniki, mischief
|
||||
u32 status_code;
|
||||
u32 server_id;
|
||||
char name[];
|
||||
char language[];
|
||||
char region[];
|
||||
char server_type_desc[];
|
||||
char server_desc[];
|
||||
u32 server_flags;
|
||||
u32 players_online;
|
||||
u32 expansion; //I think
|
||||
u32 truebox_max_clients;
|
||||
};
|
||||
|
||||
struct Packet {
|
||||
LoginBase base;
|
||||
|
||||
u8 success;
|
||||
u32 login_server_string_id;
|
||||
char login_server_string[];
|
||||
u32 realm_count;
|
||||
Realm realms[realm_count];
|
||||
};
|
||||
|
||||
Packet packet @0x00;
|
||||
@@ -0,0 +1,16 @@
|
||||
// 0x01
|
||||
|
||||
struct LoginBase
|
||||
{
|
||||
u32 sequence_id;
|
||||
u8 compressed;
|
||||
u8 encrypt_type;
|
||||
u32 unknown08;
|
||||
};
|
||||
|
||||
struct Packet {
|
||||
LoginBase base;
|
||||
u16 unknown0a;
|
||||
};
|
||||
|
||||
Packet packet @0x00;
|
||||
@@ -0,0 +1,10 @@
|
||||
// 0x16
|
||||
|
||||
struct Packet {
|
||||
u32 sequence_id;
|
||||
u32 unknown04;
|
||||
u16 unknown08;
|
||||
char fingerprint_data[];
|
||||
};
|
||||
|
||||
Packet packet @0x00;
|
||||
@@ -0,0 +1,17 @@
|
||||
// 0x03
|
||||
// I'm not sure what this packet is, it sends right after play everquest response it sent client->server
|
||||
|
||||
struct LoginBase
|
||||
{
|
||||
u32 sequence_id;
|
||||
u8 compressed;
|
||||
u8 encrypt_type;
|
||||
u32 unknown08;
|
||||
};
|
||||
|
||||
struct Packet {
|
||||
LoginBase base;
|
||||
|
||||
};
|
||||
|
||||
Packet packet @0x00;
|
||||
@@ -0,0 +1,27 @@
|
||||
import argparse
|
||||
from Crypto.Cipher import DES
|
||||
|
||||
def decrypt_hex_string(hex_data):
|
||||
raw_hex = "".join(hex_data).replace(" ", "")
|
||||
|
||||
try:
|
||||
encrypted_bytes = bytes.fromhex(raw_hex)
|
||||
except ValueError:
|
||||
return "Error: Input is not valid hexadecimal."
|
||||
|
||||
key = b'\x00' * 8
|
||||
iv = b'\x00' * 8
|
||||
|
||||
cipher = DES.new(key, DES.MODE_CBC, iv)
|
||||
decrypted_bytes = cipher.decrypt(encrypted_bytes)
|
||||
|
||||
return decrypted_bytes
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Decrypt EQ Default Encryption.")
|
||||
parser.add_argument("data", nargs="+", help="The data hex string to decrypt")
|
||||
args = parser.parse_args()
|
||||
result = decrypt_hex_string(args.data)
|
||||
|
||||
print("--- Decrypted Data ---")
|
||||
print(f"Data: {result.hex(' ').upper()}")
|
||||
@@ -0,0 +1,434 @@
|
||||
struct Bind {
|
||||
u32 zoneid;
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
float heading;
|
||||
};
|
||||
|
||||
struct ArmorProperty
|
||||
{
|
||||
s32 type;
|
||||
s32 variation;
|
||||
s32 material;
|
||||
s32 newArmorId;
|
||||
s32 newArmorType;
|
||||
};
|
||||
|
||||
struct AA
|
||||
{
|
||||
s32 index;
|
||||
s32 points_spent;
|
||||
s32 charges_spent;
|
||||
};
|
||||
|
||||
struct EQGuid
|
||||
{
|
||||
u32 entity_id;
|
||||
u32 realm_id;
|
||||
};
|
||||
|
||||
struct SlotData
|
||||
{
|
||||
s32 slot_id;
|
||||
s64 value;
|
||||
};
|
||||
|
||||
struct EQAffect
|
||||
{
|
||||
float modifier;
|
||||
EQGuid caster_id;
|
||||
u32 duration;
|
||||
u32 max_duration;
|
||||
u8 level;
|
||||
s32 spell_id;
|
||||
s32 hitcount;
|
||||
u32 flags;
|
||||
u32 viral_timer;
|
||||
u8 type;
|
||||
SlotData slots[6];
|
||||
};
|
||||
|
||||
struct Coin
|
||||
{
|
||||
u32 platinum;
|
||||
u32 gold;
|
||||
u32 silver;
|
||||
u32 copper;
|
||||
};
|
||||
|
||||
struct BandolierItemInfo {
|
||||
char name[];
|
||||
s32 item_id;
|
||||
s32 icon;
|
||||
};
|
||||
|
||||
struct BandolierSet
|
||||
{
|
||||
char name[];
|
||||
BandolierItemInfo items[4];
|
||||
};
|
||||
|
||||
struct ItemIndex
|
||||
{
|
||||
s16 slot1;
|
||||
s16 slot2;
|
||||
s16 slot3;
|
||||
};
|
||||
|
||||
struct Claim
|
||||
{
|
||||
s32 feature_id;
|
||||
s32 count;
|
||||
};
|
||||
|
||||
struct Tribute {
|
||||
u32 BenefitTimer;
|
||||
s32 unknown1;
|
||||
s32 current_favor;
|
||||
s32 unknown2;
|
||||
s32 all_time_favor;
|
||||
s32 unknown3; //some of these are probably the bools on the pcclient;
|
||||
u16 unknown4;
|
||||
};
|
||||
|
||||
struct TributeBenefit
|
||||
{
|
||||
s32 benefit_id;
|
||||
s32 benefit_tier;
|
||||
};
|
||||
|
||||
struct RaidData
|
||||
{
|
||||
u32 main_assist1;
|
||||
u32 main_assist2;
|
||||
u32 main_assist3;
|
||||
char main_assist_name1[];
|
||||
char main_assist_name2[];
|
||||
char main_assist_name3[];
|
||||
u32 main_marker1;
|
||||
u32 main_marker2;
|
||||
u32 main_marker3;
|
||||
u32 master_looter;
|
||||
};
|
||||
|
||||
struct LdonData
|
||||
{
|
||||
u32 count;
|
||||
u32 ldon_categories[count];
|
||||
u32 ldon_points_available;
|
||||
};
|
||||
|
||||
struct PvPData
|
||||
{
|
||||
u32 kills;
|
||||
u32 deaths;
|
||||
u32 current_points;
|
||||
u32 career_points;
|
||||
u32 best_kill_streak;
|
||||
u32 worst_death_streak;
|
||||
u32 current_kill_streak;
|
||||
};
|
||||
|
||||
struct PvPKill
|
||||
{
|
||||
char name[];
|
||||
u32 level;
|
||||
u32 unknown1; //not sure
|
||||
u32 unknown2; //not sure
|
||||
u32 race;
|
||||
u32 class;
|
||||
u32 zone;
|
||||
u32 time;
|
||||
u32 points;
|
||||
};
|
||||
|
||||
struct PvPDeath
|
||||
{
|
||||
char name[];
|
||||
u32 level;
|
||||
u32 race;
|
||||
u32 class;
|
||||
u32 zone;
|
||||
u32 time;
|
||||
u32 points;
|
||||
};
|
||||
|
||||
struct AltCurrency
|
||||
{
|
||||
u32 alt_currency_str_length;
|
||||
u32 unknown1;
|
||||
char alt_currency_string[alt_currency_str_length];
|
||||
};
|
||||
|
||||
struct AchivementSubComponentData
|
||||
{
|
||||
s32 achievement_id;
|
||||
s32 component_id;
|
||||
s32 requirement_id;
|
||||
s32 requirement_type;
|
||||
s32 count;
|
||||
};
|
||||
|
||||
struct AlchemyBonusSkillData
|
||||
{
|
||||
s32 skill_id;
|
||||
s32 bonus;
|
||||
};
|
||||
|
||||
struct PersonaItemSlot
|
||||
{
|
||||
u32 item_id;
|
||||
u32 slot_id;
|
||||
};
|
||||
|
||||
struct PersonaEquipment
|
||||
{
|
||||
PersonaItemSlot item;
|
||||
u32 augment_count;
|
||||
PersonaItemSlot augments[augment_count];
|
||||
};
|
||||
|
||||
struct PersonaEquipmentSet
|
||||
{
|
||||
u32 class_id;
|
||||
u32 equipment_count;
|
||||
PersonaEquipment equipment[equipment_count];
|
||||
};
|
||||
|
||||
struct PcProfile
|
||||
{
|
||||
u32 profile_type;
|
||||
u32 profile_id;
|
||||
u32 shroud_template_id;
|
||||
u8 gender;
|
||||
u32 race;
|
||||
u32 class;
|
||||
u8 level;
|
||||
u8 level1;
|
||||
u32 bind_count;
|
||||
Bind binds[bind_count];
|
||||
u32 deity;
|
||||
u32 intoxication;
|
||||
u32 property_count;
|
||||
u32 properties[property_count];
|
||||
u32 armor_prop_count;
|
||||
ArmorProperty armor_props[armor_prop_count];
|
||||
u32 base_armor_prop_count;
|
||||
ArmorProperty base_armor_props[base_armor_prop_count];
|
||||
u32 body_tint_count;
|
||||
u32 body_tints[body_tint_count];
|
||||
u32 equip_tint_count;
|
||||
u32 equip_tints[equip_tint_count];
|
||||
u8 hair_color;
|
||||
u8 facial_hair_color;
|
||||
u32 npc_tint_index;
|
||||
u8 eye_color1;
|
||||
u8 eye_color2;
|
||||
u8 hair_style;
|
||||
u8 facial_hair;
|
||||
u8 face;
|
||||
u8 old_face;
|
||||
u32 heritage;
|
||||
u32 tattoo;
|
||||
u32 details;
|
||||
u8 texture_type;
|
||||
u8 material;
|
||||
u8 variation;
|
||||
float height;
|
||||
float width;
|
||||
float length;
|
||||
float view_height;
|
||||
u32 primary;
|
||||
u32 secondary;
|
||||
u32 practices;
|
||||
u32 base_mana;
|
||||
u32 base_hp;
|
||||
u32 base_str;
|
||||
u32 base_sta;
|
||||
u32 base_cha;
|
||||
u32 base_dex;
|
||||
u32 base_int;
|
||||
u32 base_agi;
|
||||
u32 base_wis;
|
||||
u32 base_heroic_str;
|
||||
u32 base_heroic_sta;
|
||||
u32 base_heroic_cha;
|
||||
u32 base_heroic_dex;
|
||||
u32 base_heroic_int;
|
||||
u32 base_heroic_agi;
|
||||
u32 base_heroic_wis;
|
||||
u32 aa_count;
|
||||
AA aas[aa_count];
|
||||
u32 skill_count;
|
||||
s32 skills[skill_count];
|
||||
u32 innate_skill_count;
|
||||
s32 innate_skills[innate_skill_count];
|
||||
u32 combat_ability_count;
|
||||
s32 combat_abilities[combat_ability_count];
|
||||
u32 combat_ability_timer_count;
|
||||
s32 combat_ability_timers[combat_ability_timer_count];
|
||||
u32 unk_ability_count;
|
||||
u32 linked_spell_timer_count;
|
||||
s32 linked_spell_timers[linked_spell_timer_count];
|
||||
u32 item_recast_timer_count;
|
||||
s32 item_recast_timers[item_recast_timer_count];
|
||||
u32 spell_book_slot_count;
|
||||
s32 spell_book_slots[spell_book_slot_count];
|
||||
u32 spell_gem_count;
|
||||
s32 spell_gems[spell_gem_count];
|
||||
u32 spell_recast_timer_count;
|
||||
s32 spell_recast_timers[spell_recast_timer_count];
|
||||
u8 max_allowed_spell_slots;
|
||||
u32 buff_count;
|
||||
EQAffect buffs[buff_count];
|
||||
Coin coin;
|
||||
Coin cursor_coin;
|
||||
u32 disc_timer;
|
||||
u32 mend_timer;
|
||||
u32 forage_timer;
|
||||
u32 thirst;
|
||||
u32 hunger;
|
||||
u32 aa_spent;
|
||||
u32 aa_window_count;
|
||||
u32 aa_window_stats[aa_window_count];
|
||||
u32 aa_points_unspent;
|
||||
u8 sneak;
|
||||
u8 hide;
|
||||
u32 bandolier_count;
|
||||
BandolierSet bandolier_sets[bandolier_count];
|
||||
u32 invslot_bitmask;
|
||||
u32 basedata_hp;
|
||||
u32 basedata_mana;
|
||||
u32 basedata_endur;
|
||||
u32 basedata_mr;
|
||||
u32 basedata_fr;
|
||||
u32 basedata_cr;
|
||||
u32 basedata_pr;
|
||||
u32 basedata_dr;
|
||||
u32 basedata_corrupt;
|
||||
u32 basedata_phr;
|
||||
float basedata_walkspeed;
|
||||
float basedata_runspeed;
|
||||
u32 basedata_hpregen;
|
||||
u32 basedata_manaregen;
|
||||
u32 basedata_mountmanaregen;
|
||||
u32 basedata_endurregen;
|
||||
u32 basedata_ac;
|
||||
u32 basedata_atk;
|
||||
u32 basedata_dmg;
|
||||
u32 basedata_delay;
|
||||
u32 endurance;
|
||||
u32 heroic_type;
|
||||
ItemIndex keyring_item_index[5];
|
||||
u64 exp;
|
||||
u64 aa_exp; //this is a guess, used to be 32 upped to 64
|
||||
u16 unknown1;
|
||||
EQGuid character_id;
|
||||
u32 name_length;
|
||||
char name[name_length];
|
||||
u32 last_name_length;
|
||||
char last_name[last_name_length];
|
||||
u32 creation_time;
|
||||
u32 account_creation_time;
|
||||
u32 last_played_time;
|
||||
u32 played_minutes;
|
||||
u32 entitled_days;
|
||||
u32 expansion_flags;
|
||||
u32 unknown2; //new field from laurion to obrood
|
||||
u32 language_count;
|
||||
u8 languages[language_count];
|
||||
u32 current_zone;
|
||||
float current_x;
|
||||
float current_y;
|
||||
float current_z;
|
||||
float current_heading;
|
||||
u8 animation;
|
||||
u8 pvp;
|
||||
u8 anon;
|
||||
u8 gm;
|
||||
u64 guild_id;
|
||||
u8 guild_show_sprite;
|
||||
u8 status;
|
||||
Coin coin2;
|
||||
Coin bank2;
|
||||
u32 bank_shared_plat;
|
||||
u32 claim_count;
|
||||
Claim claims[claim_count];
|
||||
Tribute tribute;
|
||||
u32 tribute_benefit_count;
|
||||
TributeBenefit tribute_benefits[tribute_benefit_count];
|
||||
u32 trophy_tribute_benefit_count;
|
||||
TributeBenefit trophy_tribute_benefit[trophy_tribute_benefit_count];
|
||||
u8 tasks[137]; //honestly not sure what this is, was just a guess
|
||||
u32 good_points_available;
|
||||
u32 good_points_earned;
|
||||
u32 bad_points_available;
|
||||
u32 bad_points_earned;
|
||||
u32 momentum_balance;
|
||||
u32 loyalty_reward_balance;
|
||||
u32 parcel_status;
|
||||
u32 vehicle_name_length;
|
||||
char vehicle_name[vehicle_name_length];
|
||||
u8 super_pkill;
|
||||
u8 unclone;
|
||||
u8 dead;
|
||||
u32 ld_timer;
|
||||
u32 spell_interrupt_count;
|
||||
u8 autosplit;
|
||||
u8 tells_off;
|
||||
u8 gm_invis;
|
||||
u32 kill_me;
|
||||
u8 cheater_ld_flag;
|
||||
u8 norent;
|
||||
u8 corpse;
|
||||
u8 client_gm_flag_set;
|
||||
u32 mentor_pct;
|
||||
RaidData raid;
|
||||
u32 unique_player_id;
|
||||
LdonData ldon_data;
|
||||
u32 air_supply;
|
||||
PvPData pvp_data;
|
||||
PvPKill last_kill;
|
||||
PvPDeath last_death;
|
||||
u32 kills_in_past_24_hours;
|
||||
u32 kill_list_count;
|
||||
PvPKill kill_list[kill_list_count];
|
||||
u32 pvp_infamy_level;
|
||||
u32 pvp_vitality;
|
||||
u32 cursor_krono;
|
||||
u32 krono;
|
||||
u8 autoconsent_group;
|
||||
u8 autoconsent_raid;
|
||||
u8 autoconsent_guild;
|
||||
u8 autoconsent_fellowship;
|
||||
u8 private_for_eq_players;
|
||||
u32 main_level;
|
||||
u8 show_helm;
|
||||
u32 downtime;
|
||||
AltCurrency alt_currency;
|
||||
u32 completed_event_subcomponent_count;
|
||||
AchivementSubComponentData completed_event_subcomponents[completed_event_subcomponent_count];
|
||||
u32 inprogress_event_subcomponent_count;
|
||||
AchivementSubComponentData inprogress_event_subcomponents[inprogress_event_subcomponent_count];
|
||||
u64 merc_aa_exp;
|
||||
u32 merc_aa_points;
|
||||
u32 merc_aa_spent;
|
||||
u32 starting_city_zone_id;
|
||||
u8 use_advanced_looting;
|
||||
u8 is_master_loot_candidate;
|
||||
u32 alchemy_bonus_list_count;
|
||||
AlchemyBonusSkillData alchemy_bonus_list[alchemy_bonus_list_count];
|
||||
u32 persona_count;
|
||||
PersonaEquipmentSet persona_equipment_set[persona_count];
|
||||
u8 term;
|
||||
};
|
||||
|
||||
struct Packet
|
||||
{
|
||||
u32 crc;
|
||||
u32 length;
|
||||
PcProfile profile;
|
||||
};
|
||||
|
||||
Packet p @ 0x00;
|
||||
@@ -0,0 +1,28 @@
|
||||
def patch_template(template_path, opcodes_path, output_path):
|
||||
try:
|
||||
with open(opcodes_path, 'r') as f:
|
||||
opcodes = [line.strip() for line in f if line.strip()]
|
||||
|
||||
with open(template_path, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
for index, value in enumerate(opcodes):
|
||||
placeholder = f"{{{{{index}}}}}"
|
||||
content = content.replace(placeholder, value)
|
||||
|
||||
with open(output_path, 'w') as f:
|
||||
f.write(content)
|
||||
|
||||
print(f"Successfully transformed: {output_path}")
|
||||
|
||||
except FileNotFoundError as e:
|
||||
print(f"Error: File Not Found - {e}")
|
||||
except Exception as e:
|
||||
print(f"Error: Exception - {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
patch_template(
|
||||
template_path='opcode.template',
|
||||
opcodes_path='opcodes.csv',
|
||||
output_path='patch_SteamLatest.conf'
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user