implement JSD1

This commit is contained in:
Martin Michelsen
2022-05-08 19:25:18 -07:00
parent e1e6ca1517
commit cedb0c648e
9 changed files with 615 additions and 409 deletions
+6
View File
@@ -822,6 +822,12 @@ struct C_Login_BB_93 {
ClientConfigBB cfg;
ptext<char, 0x28> version_string;
ClientConfigFields() : version_string() { }
ClientConfigFields(const ClientConfigFields& other)
: version_string(other.version_string) { }
inline ClientConfigFields& operator=(const ClientConfigFields& other) {
this->version_string = other.version_string;
return *this;
}
} client_config;
};
+327 -286
View File
@@ -90,29 +90,12 @@ void PSOPCEncryption::encrypt(void* vdata, size_t size, bool advance) {
}
size >>= 2;
uint32_t* data = reinterpret_cast<uint32_t*>(vdata);
le_uint32_t* data = reinterpret_cast<le_uint32_t*>(vdata);
for (size_t x = 0; x < size; x++) {
data[x] ^= this->next(advance);
}
}
void PSOPCEncryption::skip(size_t size) {
if (size & 3) {
throw invalid_argument("size must be a multiple of 4");
}
size >>= 2;
// TODO: Do something smarter than just calling next() in a loop here
size_t new_offset = this->offset + size;
while (new_offset > PC_STREAM_LENGTH) {
this->update_stream();
// The PC encryption apparently always skips the first key in the stream
new_offset -= (PC_STREAM_LENGTH - 1);
}
this->offset = new_offset;
}
void PSOGCEncryption::update_stream() {
@@ -184,114 +167,144 @@ void PSOGCEncryption::encrypt(void* vdata, size_t size, bool advance) {
}
size >>= 2;
uint32_t* data = reinterpret_cast<uint32_t*>(vdata);
le_uint32_t* data = reinterpret_cast<le_uint32_t*>(vdata);
for (size_t x = 0; x < size; x++) {
data[x] ^= this->next(advance);
}
}
void PSOGCEncryption::skip(size_t size) {
if (size & 3) {
throw invalid_argument("size must be a multiple of 4");
}
size >>= 2;
size_t new_offset = this->offset + size;
while (new_offset > GC_STREAM_LENGTH) {
this->update_stream();
new_offset -= GC_STREAM_LENGTH;
}
this->offset = new_offset;
}
void PSOBBEncryption::decrypt(void* vdata, size_t size, bool advance) {
if (this->subtype != Subtype::JSD1) {
if (size & 7) {
throw invalid_argument("size must be a multiple of 8");
}
size_t num_dwords = size >> 2;
le_uint32_t* dwords = reinterpret_cast<le_uint32_t*>(vdata);
uint32_t edx, ebx, ebp, esi, edi;
void PSOBBEncryption::decrypt(void* vdata, size_t size, bool) {
if (size & 7) {
throw invalid_argument("size must be a multiple of 8");
}
size >>= 2;
edx = 0;
while (edx < num_dwords) {
ebx = dwords[edx];
ebx = ebx ^ this->stream[5];
ebp = ((this->stream[(ebx >> 0x18) + 0x12] +
this->stream[((ebx >> 0x10) & 0xFF) + 0x112]) ^
this->stream[((ebx >> 0x8) & 0xFF) + 0x212]) +
this->stream[(ebx & 0xFF) + 0x312];
ebp = ebp ^ this->stream[4];
ebp ^= dwords[edx + 1];
edi = ((this->stream[(ebp >> 0x18) + 0x12] +
this->stream[((ebp >> 0x10) & 0xFF) + 0x112]) ^
this->stream[((ebp >> 0x8) & 0xFF) + 0x212]) +
this->stream[(ebp & 0xFF) + 0x312];
edi = edi ^ this->stream[3];
ebx = ebx ^ edi;
esi = ((this->stream[(ebx >> 0x18) + 0x12] +
this->stream[((ebx >> 0x10) & 0xFF) + 0x112]) ^
this->stream[((ebx >> 0x8) & 0xFF) + 0x212]) +
this->stream[(ebx & 0xFF) + 0x312];
ebp = ebp ^ esi ^ this->stream[2];
edi = ((this->stream[(ebp >> 0x18) + 0x12] +
this->stream[((ebp >> 0x10) & 0xFF) + 0x112]) ^
this->stream[((ebp >> 0x8) & 0xFF) + 0x212]) +
this->stream[(ebp & 0xFF) + 0x312];
edi = edi ^ this->stream[1];
ebp = ebp ^ this->stream[0];
ebx = ebx ^ edi;
dwords[edx] = ebp;
dwords[edx + 1] = ebx;
edx += 2;
}
uint32_t* data = reinterpret_cast<uint32_t*>(vdata);
uint32_t edx, ebx, ebp, esi, edi;
edx = 0;
while (edx < size) {
ebx = data[edx];
ebx = ebx ^ this->stream[5];
ebp = ((this->stream[(ebx >> 0x18) + 0x12] + this->stream[((ebx >> 0x10) & 0xFF) + 0x112])
^ this->stream[((ebx >> 0x8) & 0xFF) + 0x212]) + this->stream[(ebx & 0xFF) + 0x312];
ebp = ebp ^ this->stream[4];
ebp ^= data[edx + 1];
edi = ((this->stream[(ebp >> 0x18) + 0x12] + this->stream[((ebp >> 0x10) & 0xFF) + 0x112])
^ this->stream[((ebp >> 0x8) & 0xFF) + 0x212]) + this->stream[(ebp & 0xFF) + 0x312];
edi = edi ^ this->stream[3];
ebx = ebx ^ edi;
esi = ((this->stream[(ebx >> 0x18) + 0x12] + this->stream[((ebx >> 0x10) & 0xFF) + 0x112])
^ this->stream[((ebx >> 0x8) & 0xFF) + 0x212]) + this->stream[(ebx & 0xFF) + 0x312];
ebp = ebp ^ esi ^ this->stream[2];
edi = ((this->stream[(ebp >> 0x18) + 0x12] + this->stream[((ebp >> 0x10) & 0xFF) + 0x112])
^ this->stream[((ebp >> 0x8) & 0xFF) + 0x212]) + this->stream[(ebp & 0xFF) + 0x312];
edi = edi ^ this->stream[1];
ebp = ebp ^ this->stream[0];
ebx = ebx ^ edi;
data[edx] = ebp;
data[edx + 1] = ebx;
edx += 2;
} else { // subtype == Subtype::JSD1
if (!advance && (size > 0x100)) {
throw logic_error("JSD1 can only peek-decrypt up to 0x100 bytes");
}
uint8_t* bytes = reinterpret_cast<uint8_t*>(vdata);
for (size_t z = 0; z < size; z += 2) {
uint8_t a = bytes[z];
uint8_t b = bytes[z + 1];
bytes[z] = (a & 0x55) | (b & 0xAA);
bytes[z + 1] = (a & 0xAA) | (b & 0x55);
}
uint8_t* stream_bytes = reinterpret_cast<uint8_t*>(this->stream.data());
for (size_t z = 0; z < size; z++) {
bytes[z] ^= stream_bytes[this->jsd1_stream_offset];
if (advance) {
stream_bytes[this->jsd1_stream_offset] -= bytes[z];
}
this->jsd1_stream_offset++;
}
if (!advance) {
this->jsd1_stream_offset -= size;
}
}
}
void PSOBBEncryption::encrypt(void* vdata, size_t size, bool) {
if (size & 7) {
throw invalid_argument("size must be a multiple of 8");
}
size >>= 2;
void PSOBBEncryption::encrypt(void* vdata, size_t size, bool advance) {
if (this->subtype != Subtype::JSD1) {
if (size & 7) {
throw invalid_argument("size must be a multiple of 8");
}
uint32_t* data = reinterpret_cast<uint32_t*>(vdata);
uint32_t edx, ebx, ebp, esi, edi;
size_t num_dwords = size >> 2;
le_uint32_t* data = reinterpret_cast<le_uint32_t*>(vdata);
uint32_t edx, ebx, ebp, esi, edi;
edx = 0;
while (edx < size) {
ebx = data[edx] ^ this->stream[0];
ebp = ((this->stream[(ebx >> 0x18) + 0x12] + this->stream[((ebx >> 0x10) & 0xFF) + 0x112])
^ this->stream[((ebx >> 0x8) & 0xFF) + 0x212]) + this->stream[(ebx & 0xFF) + 0x312];
ebp = ebp ^ this->stream[1];
ebp ^= data[edx + 1];
edi = ((this->stream[(ebp >> 0x18) + 0x12] + this->stream[((ebp >> 0x10) & 0xFF) + 0x112])
^ this->stream[((ebp >> 0x8) & 0xFF) + 0x212]) + this->stream[(ebp & 0xFF) + 0x312];
edi = edi ^ this->stream[2];
ebx = ebx ^ edi;
esi = ((this->stream[(ebx >> 0x18) + 0x12] + this->stream[((ebx >> 0x10) & 0xFF) + 0x112])
^ this->stream[((ebx >> 0x8) & 0xFF) + 0x212]) + this->stream[(ebx & 0xFF) + 0x312];
ebp = ebp ^ esi ^ this->stream[3];
edi = ((this->stream[(ebp >> 0x18) + 0x12] + this->stream[((ebp >> 0x10) & 0xFF) + 0x112])
^ this->stream[((ebp >> 0x8) & 0xFF) + 0x212]) + this->stream[(ebp & 0xFF) + 0x312];
edi = edi ^ this->stream[4];
ebp = ebp ^ this->stream[5];
ebx = ebx ^ edi;
data[edx] = ebp;
data[edx + 1] = ebx;
edx += 2;
edx = 0;
while (edx < num_dwords) {
ebx = data[edx] ^ this->stream[0];
ebp = ((this->stream[(ebx >> 0x18) + 0x12] + this->stream[((ebx >> 0x10) & 0xFF) + 0x112])
^ this->stream[((ebx >> 0x8) & 0xFF) + 0x212]) + this->stream[(ebx & 0xFF) + 0x312];
ebp = ebp ^ this->stream[1];
ebp ^= data[edx + 1];
edi = ((this->stream[(ebp >> 0x18) + 0x12] + this->stream[((ebp >> 0x10) & 0xFF) + 0x112])
^ this->stream[((ebp >> 0x8) & 0xFF) + 0x212]) + this->stream[(ebp & 0xFF) + 0x312];
edi = edi ^ this->stream[2];
ebx = ebx ^ edi;
esi = ((this->stream[(ebx >> 0x18) + 0x12] + this->stream[((ebx >> 0x10) & 0xFF) + 0x112])
^ this->stream[((ebx >> 0x8) & 0xFF) + 0x212]) + this->stream[(ebx & 0xFF) + 0x312];
ebp = ebp ^ esi ^ this->stream[3];
edi = ((this->stream[(ebp >> 0x18) + 0x12] + this->stream[((ebp >> 0x10) & 0xFF) + 0x112])
^ this->stream[((ebp >> 0x8) & 0xFF) + 0x212]) + this->stream[(ebp & 0xFF) + 0x312];
edi = edi ^ this->stream[4];
ebp = ebp ^ this->stream[5];
ebx = ebx ^ edi;
data[edx] = ebp;
data[edx + 1] = ebx;
edx += 2;
}
} else { // subtype == Subtype::JSD1
if (!advance && (size > 0x100)) {
throw logic_error("JSD1 can only peek-encrypt up to 0x100 bytes");
}
uint8_t* bytes = reinterpret_cast<uint8_t*>(vdata);
uint8_t* stream_bytes = reinterpret_cast<uint8_t*>(this->stream.data());
for (size_t z = 0; z < size; z++) {
uint8_t v = bytes[z];
bytes[z] = v ^ stream_bytes[this->jsd1_stream_offset];
if (advance) {
stream_bytes[this->jsd1_stream_offset] -= v;
}
this->jsd1_stream_offset++;
}
if (!advance) {
this->jsd1_stream_offset -= size;
}
for (size_t z = 0; z < size; z += 2) {
uint8_t a = bytes[z];
uint8_t b = bytes[z + 1];
bytes[z] = (a & 0x55) | (b & 0xAA);
bytes[z + 1] = (a & 0xAA) | (b & 0x55);
}
}
}
void PSOBBEncryption::skip(size_t) { }
PSOBBEncryption::PSOBBEncryption(
const KeyFile& key, const void* original_seed, size_t seed_size)
: stream(this->generate_stream(key, original_seed, seed_size)) { }
PSOBBEncryption::PSOBBEncryption() { }
vector<uint32_t> PSOBBEncryption::generate_stream(
const KeyFile& key, const void* original_seed, size_t seed_size) {
if (seed_size % 3) {
throw invalid_argument("seed size must be divisible by 3");
}
vector<uint32_t> stream(BB_STREAM_LENGTH, 0);
: subtype(key.subtype), jsd1_stream_offset(0) {
// Note: This part is done in the 03 command handler in the BB client, and
// isn't actually part of the encryption library. (Why did they do this?)
@@ -304,215 +317,230 @@ vector<uint32_t> PSOBBEncryption::generate_stream(
seed.push_back(original_seed_data[x + 2] ^ 0x18);
}
if (key.is_modcrypt) {
for (size_t x = 0; x < 0x12; x++) {
uint8_t a = key.initial_keys[4 * x + 0];
uint8_t b = key.initial_keys[4 * x + 1];
uint8_t c = key.initial_keys[4 * x + 2];
uint8_t d = key.initial_keys[4 * x + 3];
stream[x] = ((a ^ d) << 24) | ((b ^ c) << 16) | (a << 8) | b;
}
memcpy(stream.data() + 0x12, &key.private_keys, sizeof(key.private_keys));
} else {
memcpy(stream.data(), &key, sizeof(key));
}
// This block was formerly postprocess_initial_stream
{
uint32_t eax, ecx, edx, ebx, ebp, esi, edi, ou, x;
ecx = 0;
ebx = 0;
while (ebx < 0x12) {
ebp = static_cast<uint32_t>(seed[ecx]) << 0x18;
eax = ecx + 1;
edx = eax % seed.size();
eax = (static_cast<uint32_t>(seed[edx]) << 0x10) & 0x00FF0000;
ebp = (ebp | eax) & 0xFFFF00FF;
eax = ecx + 2;
edx = eax % seed.size();
eax = (static_cast<uint32_t>(seed[edx]) << 0x08) & 0x0000FF00;
ebp = (ebp | eax) & 0xFFFFFF00;
eax = ecx + 3;
ecx = ecx + 4;
edx = eax % seed.size();
eax = static_cast<uint32_t>(seed[edx]) & 0x000000FF;
ebp = ebp | eax;
eax = ecx;
edx = eax % seed.size();
stream[ebx] ^= ebp;
ecx = edx;
ebx++;
if (this->subtype != Subtype::JSD1) {
if (seed_size % 3) {
throw invalid_argument("seed size must be divisible by 3");
}
ebp = 0;
esi = 0;
ecx = 0;
edi = 0;
ebx = 0;
edx = 0x48;
this->stream.resize(BB_STREAM_LENGTH, 0);
while (edi < edx) {
esi = esi ^ stream[0];
eax = esi >> 0x18;
ebx = (esi >> 0x10) & 0xFF;
eax = stream[eax + 0x12] + stream[ebx + 0x112];
ebx = (esi >> 8) & 0xFF;
eax = eax ^ stream[ebx + 0x212];
ebx = esi & 0xFF;
eax = eax + stream[ebx + 0x312];
if (key.subtype == Subtype::MOCB1) {
for (size_t x = 0; x < 0x12; x++) {
uint8_t a = key.initial_keys[4 * x + 0];
uint8_t b = key.initial_keys[4 * x + 1];
uint8_t c = key.initial_keys[4 * x + 2];
uint8_t d = key.initial_keys[4 * x + 3];
this->stream[x] = ((a ^ d) << 24) | ((b ^ c) << 16) | (a << 8) | b;
}
memcpy(this->stream.data() + 0x12, &key.private_keys, sizeof(key.private_keys));
eax = eax ^ stream[1];
ecx = ecx ^ eax;
ebx = ecx >> 0x18;
eax = (ecx >> 0x10) & 0xFF;
ebx = stream[ebx + 0x12] + stream[eax + 0x112];
eax = (ecx >> 8) & 0xFF;
ebx = ebx ^ stream[eax + 0x212];
eax = ecx & 0xFF;
ebx = ebx + stream[eax + 0x312];
} else {
memcpy(this->stream.data(), &key, sizeof(key));
}
for (x = 0; x <= 5; x++) {
ebx = ebx ^ stream[(x * 2) + 2];
esi = esi ^ ebx;
ebx = esi >> 0x18;
eax = (esi >> 0x10) & 0xFF;
ebx = stream[ebx + 0x12] + stream[eax + 0x112];
eax = (esi >> 8) & 0xFF;
ebx = ebx ^ stream[eax + 0x212];
eax = esi & 0xFF;
ebx = ebx + stream[eax + 0x312];
// This block was formerly postprocess_initial_stream
{
uint32_t eax, ecx, edx, ebx, ebp, esi, edi, ou, x;
ebx = ebx ^ stream[(x * 2) + 3];
ecx = ecx ^ ebx;
ebx = ecx >> 0x18;
eax = (ecx >> 0x10) & 0xFF;
ebx = stream[ebx + 0x12] + stream[eax + 0x112];
eax = (ecx >> 8) & 0xFF;
ebx = ebx ^ stream[eax + 0x212];
eax = ecx & 0xFF;
ebx = ebx + stream[eax + 0x312];
ecx = 0;
ebx = 0;
while (ebx < 0x12) {
ebp = static_cast<uint32_t>(seed[ecx]) << 0x18;
eax = ecx + 1;
edx = eax % seed.size();
eax = (static_cast<uint32_t>(seed[edx]) << 0x10) & 0x00FF0000;
ebp = (ebp | eax) & 0xFFFF00FF;
eax = ecx + 2;
edx = eax % seed.size();
eax = (static_cast<uint32_t>(seed[edx]) << 0x08) & 0x0000FF00;
ebp = (ebp | eax) & 0xFFFFFF00;
eax = ecx + 3;
ecx = ecx + 4;
edx = eax % seed.size();
eax = static_cast<uint32_t>(seed[edx]) & 0x000000FF;
ebp = ebp | eax;
eax = ecx;
edx = eax % seed.size();
this->stream[ebx] ^= ebp;
ecx = edx;
ebx++;
}
ebx = ebx ^ stream[14];
esi = esi ^ ebx;
eax = esi >> 0x18;
ebx = (esi >> 0x10) & 0xFF;
eax = stream[eax + 0x12] + stream[ebx + 0x112];
ebx = (esi >> 8) & 0xFF;
eax = eax ^ stream[ebx + 0x212];
ebx = esi & 0xFF;
eax = eax + stream[ebx + 0x312];
eax = eax ^ stream[15];
eax = ecx ^ eax;
ecx = eax >> 0x18;
ebx = (eax >> 0x10) & 0xFF;
ecx = stream[ecx + 0x12] + stream[ebx + 0x112];
ebx = (eax >> 8) & 0xFF;
ecx = ecx ^ stream[ebx + 0x212];
ebx = eax & 0xFF;
ecx = ecx + stream[ebx + 0x312];
ecx = ecx ^ stream[16];
ecx = ecx ^ esi;
esi = stream[17];
esi = esi ^ eax;
stream[(edi / 4)] = esi;
stream[(edi / 4)+1] = ecx;
edi = edi + 8;
}
eax = 0;
edx = 0;
ou = 0;
while (ou < 0x1000) {
edi = 0x48;
edx = 0x448;
ebp = 0;
esi = 0;
ecx = 0;
edi = 0;
ebx = 0;
edx = 0x48;
while (edi < edx) {
esi = esi ^ stream[0];
esi = esi ^ this->stream[0];
eax = esi >> 0x18;
ebx = (esi >> 0x10) & 0xFF;
eax = stream[eax + 0x12] + stream[ebx + 0x112];
eax = this->stream[eax + 0x12] + this->stream[ebx + 0x112];
ebx = (esi >> 8) & 0xFF;
eax = eax ^ stream[ebx + 0x212];
eax = eax ^ this->stream[ebx + 0x212];
ebx = esi & 0xFF;
eax = eax + stream[ebx + 0x312];
eax = eax + this->stream[ebx + 0x312];
eax = eax ^ stream[1];
eax = eax ^ this->stream[1];
ecx = ecx ^ eax;
ebx = ecx >> 0x18;
eax = (ecx >> 0x10) & 0xFF;
ebx = stream[ebx + 0x12] + stream[eax + 0x112];
ebx = this->stream[ebx + 0x12] + this->stream[eax + 0x112];
eax = (ecx >> 8) & 0xFF;
ebx = ebx ^ stream[eax + 0x212];
ebx = ebx ^ this->stream[eax + 0x212];
eax = ecx & 0xFF;
ebx = ebx + stream[eax + 0x312];
ebx = ebx + this->stream[eax + 0x312];
for (x = 0; x <= 5; x++) {
ebx = ebx ^ stream[(x * 2) + 2];
ebx = ebx ^ this->stream[(x * 2) + 2];
esi = esi ^ ebx;
ebx = esi >> 0x18;
eax = (esi >> 0x10) & 0xFF;
ebx = stream[ebx + 0x12] + stream[eax + 0x112];
ebx = this->stream[ebx + 0x12] + this->stream[eax + 0x112];
eax = (esi >> 8) & 0xFF;
ebx = ebx ^ stream[eax + 0x212];
ebx = ebx ^ this->stream[eax + 0x212];
eax = esi & 0xFF;
ebx = ebx + stream[eax + 0x312];
ebx = ebx + this->stream[eax + 0x312];
ebx = ebx ^ stream[(x * 2) + 3];
ebx = ebx ^ this->stream[(x * 2) + 3];
ecx = ecx ^ ebx;
ebx = ecx >> 0x18;
eax = (ecx >> 0x10) & 0xFF;
ebx = stream[ebx + 0x12] + stream[eax + 0x112];
ebx = this->stream[ebx + 0x12] + this->stream[eax + 0x112];
eax = (ecx >> 8) & 0xFF;
ebx = ebx ^ stream[eax + 0x212];
ebx = ebx ^ this->stream[eax + 0x212];
eax = ecx & 0xFF;
ebx = ebx + stream[eax + 0x312];
ebx = ebx + this->stream[eax + 0x312];
}
ebx = ebx ^ stream[14];
ebx = ebx ^ this->stream[14];
esi = esi ^ ebx;
eax = esi >> 0x18;
ebx = (esi >> 0x10) & 0xFF;
eax = stream[eax + 0x12] + stream[ebx + 0x112];
eax = this->stream[eax + 0x12] + this->stream[ebx + 0x112];
ebx = (esi >> 8) & 0xFF;
eax = eax ^ stream[ebx + 0x212];
eax = eax ^ this->stream[ebx + 0x212];
ebx = esi & 0xFF;
eax = eax + stream[ebx + 0x312];
eax = eax + this->stream[ebx + 0x312];
eax = eax ^ stream[15];
eax = eax ^ this->stream[15];
eax = ecx ^ eax;
ecx = eax >> 0x18;
ebx = (eax >> 0x10) & 0xFF;
ecx = stream[ecx + 0x12] + stream[ebx + 0x112];
ecx = this->stream[ecx + 0x12] + this->stream[ebx + 0x112];
ebx = (eax >> 8) & 0xFF;
ecx = ecx ^ stream[ebx + 0x212];
ecx = ecx ^ this->stream[ebx + 0x212];
ebx = eax & 0xFF;
ecx = ecx + stream[ebx + 0x312];
ecx = ecx + this->stream[ebx + 0x312];
ecx = ecx ^ stream[16];
ecx = ecx ^ this->stream[16];
ecx = ecx ^ esi;
esi = stream[17];
esi = this->stream[17];
esi = esi ^ eax;
stream[(ou / 4) + (edi / 4)] = esi;
stream[(ou / 4) + (edi / 4) + 1] = ecx;
this->stream[(edi / 4)] = esi;
this->stream[(edi / 4)+1] = ecx;
edi = edi + 8;
}
ou = ou + 0x400;
eax = 0;
edx = 0;
ou = 0;
while (ou < 0x1000) {
edi = 0x48;
edx = 0x448;
while (edi < edx) {
esi = esi ^ this->stream[0];
eax = esi >> 0x18;
ebx = (esi >> 0x10) & 0xFF;
eax = this->stream[eax + 0x12] + this->stream[ebx + 0x112];
ebx = (esi >> 8) & 0xFF;
eax = eax ^ this->stream[ebx + 0x212];
ebx = esi & 0xFF;
eax = eax + this->stream[ebx + 0x312];
eax = eax ^ this->stream[1];
ecx = ecx ^ eax;
ebx = ecx >> 0x18;
eax = (ecx >> 0x10) & 0xFF;
ebx = this->stream[ebx + 0x12] + this->stream[eax + 0x112];
eax = (ecx >> 8) & 0xFF;
ebx = ebx ^ this->stream[eax + 0x212];
eax = ecx & 0xFF;
ebx = ebx + this->stream[eax + 0x312];
for (x = 0; x <= 5; x++) {
ebx = ebx ^ this->stream[(x * 2) + 2];
esi = esi ^ ebx;
ebx = esi >> 0x18;
eax = (esi >> 0x10) & 0xFF;
ebx = this->stream[ebx + 0x12] + this->stream[eax + 0x112];
eax = (esi >> 8) & 0xFF;
ebx = ebx ^ this->stream[eax + 0x212];
eax = esi & 0xFF;
ebx = ebx + this->stream[eax + 0x312];
ebx = ebx ^ this->stream[(x * 2) + 3];
ecx = ecx ^ ebx;
ebx = ecx >> 0x18;
eax = (ecx >> 0x10) & 0xFF;
ebx = this->stream[ebx + 0x12] + this->stream[eax + 0x112];
eax = (ecx >> 8) & 0xFF;
ebx = ebx ^ this->stream[eax + 0x212];
eax = ecx & 0xFF;
ebx = ebx + this->stream[eax + 0x312];
}
ebx = ebx ^ this->stream[14];
esi = esi ^ ebx;
eax = esi >> 0x18;
ebx = (esi >> 0x10) & 0xFF;
eax = this->stream[eax + 0x12] + this->stream[ebx + 0x112];
ebx = (esi >> 8) & 0xFF;
eax = eax ^ this->stream[ebx + 0x212];
ebx = esi & 0xFF;
eax = eax + this->stream[ebx + 0x312];
eax = eax ^ this->stream[15];
eax = ecx ^ eax;
ecx = eax >> 0x18;
ebx = (eax >> 0x10) & 0xFF;
ecx = this->stream[ecx + 0x12] + this->stream[ebx + 0x112];
ebx = (eax >> 8) & 0xFF;
ecx = ecx ^ this->stream[ebx + 0x212];
ebx = eax & 0xFF;
ecx = ecx + this->stream[ebx + 0x312];
ecx = ecx ^ this->stream[16];
ecx = ecx ^ esi;
esi = this->stream[17];
esi = esi ^ eax;
this->stream[(ou / 4) + (edi / 4)] = esi;
this->stream[(ou / 4) + (edi / 4) + 1] = ecx;
edi = edi + 8;
}
ou = ou + 0x400;
}
}
} else { // subtype == Subtype::JSD1
this->stream.resize(0x40);
uint8_t* stream_bytes = reinterpret_cast<uint8_t*>(this->stream.data());
size_t seed_offset = 0;
for (size_t z = 0; z < 0x100; z++) {
stream_bytes[z] = (z + seed[seed_offset]) ^ (static_cast<uint8_t>(seed[seed_offset]) >> 1);
seed_offset = (seed_offset + 1) % seed.size();
}
}
return stream;
}
PSOBBMultiKeyClientEncryption::PSOBBMultiKeyClientEncryption(
const vector<shared_ptr<const KeyFile>>& possible_keys,
PSOBBMultiKeyDetectorEncryption::PSOBBMultiKeyDetectorEncryption(
const vector<shared_ptr<const PSOBBEncryption::KeyFile>>& possible_keys,
const string& expected_first_data,
const void* seed,
size_t seed_size)
@@ -520,61 +548,74 @@ PSOBBMultiKeyClientEncryption::PSOBBMultiKeyClientEncryption(
expected_first_data(expected_first_data),
seed(reinterpret_cast<const char*>(seed), seed_size) { }
void PSOBBMultiKeyClientEncryption::encrypt(void* data, size_t size, bool advance) {
if (this->stream.empty()) {
void PSOBBMultiKeyDetectorEncryption::encrypt(void* data, size_t size, bool advance) {
if (!this->active_crypt.get()) {
throw logic_error("PSOBB multi-key encryption requires client input first");
}
this->PSOBBEncryption::encrypt(data, size, advance);
this->active_crypt->encrypt(data, size, advance);
}
void PSOBBMultiKeyClientEncryption::decrypt(void* data, size_t size, bool advance) {
if (this->stream.empty()) {
void PSOBBMultiKeyDetectorEncryption::decrypt(void* data, size_t size, bool advance) {
if (!this->active_crypt.get()) {
if (size != this->expected_first_data.size()) {
throw logic_error("initial decryption size does not match expected first data size");
}
for (const auto& key : this->possible_keys) {
this->active_key = key;
this->stream = PSOBBEncryption::generate_stream(
*this->active_key.get(), this->seed.data(), this->seed.size());
this->active_crypt.reset(new PSOBBEncryption(
*this->active_key, this->seed.data(), this->seed.size()));
string test_data(reinterpret_cast<const char*>(data), size);
this->PSOBBEncryption::decrypt(test_data.data(), test_data.size());
this->active_crypt->decrypt(test_data.data(), test_data.size(), false);
if (test_data == this->expected_first_data) {
break;
}
this->active_key.reset();
this->stream.clear();
this->active_crypt.reset();
}
if (!this->active_key.get()) {
if (!this->active_crypt.get()) {
throw runtime_error("none of the registered private keys are valid for this client");
}
}
this->PSOBBEncryption::decrypt(data, size, advance);
this->active_crypt->decrypt(data, size, advance);
}
PSOBBMultiKeyServerEncryption::PSOBBMultiKeyServerEncryption(
shared_ptr<const PSOBBMultiKeyClientEncryption> client_crypt,
PSOBBMultiKeyImitatorEncryption::PSOBBMultiKeyImitatorEncryption(
shared_ptr<const PSOBBMultiKeyDetectorEncryption> detector_crypt,
const void* seed,
size_t seed_size)
: client_crypt(client_crypt),
seed(reinterpret_cast<const char*>(seed), seed_size) { }
size_t seed_size,
bool jsd1_use_detector_seed)
: detector_crypt(detector_crypt),
seed(reinterpret_cast<const char*>(seed), seed_size),
jsd1_use_detector_seed(jsd1_use_detector_seed) { }
void PSOBBMultiKeyServerEncryption::encrypt(void* data, size_t size, bool advance) {
this->ensure_stream_ready();
this->PSOBBEncryption::encrypt(data, size, advance);
void PSOBBMultiKeyImitatorEncryption::encrypt(void* data, size_t size, bool advance) {
this->ensure_crypt()->encrypt(data, size, advance);
}
void PSOBBMultiKeyServerEncryption::decrypt(void* data, size_t size, bool advance) {
this->ensure_stream_ready();
this->PSOBBEncryption::decrypt(data, size, advance);
void PSOBBMultiKeyImitatorEncryption::decrypt(void* data, size_t size, bool advance) {
this->ensure_crypt()->decrypt(data, size, advance);
}
void PSOBBMultiKeyServerEncryption::ensure_stream_ready() {
if (this->stream.empty()) {
if (!this->client_crypt->active_key.get()) {
shared_ptr<PSOBBEncryption> PSOBBMultiKeyImitatorEncryption::ensure_crypt() {
if (!this->active_crypt.get()) {
auto key = this->detector_crypt->get_active_key();
if (!key.get()) {
throw logic_error("server crypt cannot be initialized because client crypt is not ready");
}
this->stream = PSOBBEncryption::generate_stream(
*this->client_crypt->active_key, this->seed.data(), this->seed.size());
// Hack: JSD1 uses the client seed for both ends of the connection and
// ignores the server seed (though each end has its own state after that).
// To handle this, we use the other crypt's seed if the type is JSD1.
if ((key->subtype == PSOBBEncryption::Subtype::JSD1) && this->jsd1_use_detector_seed) {
const auto& detector_seed = this->detector_crypt->get_seed();
this->active_crypt.reset(new PSOBBEncryption(
*key, detector_seed.data(), detector_seed.size()));
} else {
this->active_crypt.reset(new PSOBBEncryption(
*key, this->seed.data(), this->seed.size()));
}
}
return this->active_crypt;
}
+30 -23
View File
@@ -27,8 +27,6 @@ public:
this->decrypt(data.data(), data.size(), advance);
}
virtual void skip(size_t size) = 0;
protected:
PSOEncryption() = default;
};
@@ -38,7 +36,6 @@ public:
explicit PSOPCEncryption(uint32_t seed);
virtual void encrypt(void* data, size_t size, bool advance = true);
virtual void skip(size_t size);
protected:
void update_stream();
@@ -53,7 +50,6 @@ public:
explicit PSOGCEncryption(uint32_t key);
virtual void encrypt(void* data, size_t size, bool advance = true);
virtual void skip(size_t size);
protected:
void update_stream();
@@ -65,37 +61,39 @@ protected:
class PSOBBEncryption : public PSOEncryption {
public:
enum Subtype : uint8_t {
STANDARD = 0x00,
MOCB1 = 0x01,
JSD1 = 0x02,
};
struct KeyFile {
// initial_keys are actually a stream of uint32_ts, but we treat them as
// bytes for code simplicity
uint8_t initial_keys[0x12 * 4];
uint32_t private_keys[0x400];
uint8_t is_modcrypt;
Subtype subtype;
} __attribute__((packed));
PSOBBEncryption(const KeyFile& key, const void* seed, size_t seed_size);
virtual void encrypt(void* data, size_t size, bool advance = true);
virtual void decrypt(void* data, size_t size, bool advance = true);
virtual void skip(size_t size);
protected:
PSOBBEncryption();
static std::vector<uint32_t> generate_stream(
const KeyFile& key, const void* seed, size_t seed_size);
Subtype subtype;
std::vector<uint32_t> stream;
uint8_t jsd1_stream_offset;
};
// The following classes provide support for multiple PSOBB private keys, and
// the ability to automatically detect which key the client is using based on
// the first 8 bytes they send.
class PSOBBMultiKeyClientEncryption : public PSOBBEncryption {
class PSOBBMultiKeyDetectorEncryption : public PSOEncryption {
public:
PSOBBMultiKeyClientEncryption(
const std::vector<std::shared_ptr<const KeyFile>>& possible_keys,
PSOBBMultiKeyDetectorEncryption(
const std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>>& possible_keys,
const std::string& expected_first_data,
const void* seed,
size_t seed_size);
@@ -103,28 +101,37 @@ public:
virtual void encrypt(void* data, size_t size, bool advance = true);
virtual void decrypt(void* data, size_t size, bool advance = true);
friend class PSOBBMultiKeyServerEncryption;
inline std::shared_ptr<const PSOBBEncryption::KeyFile> get_active_key() const {
return this->active_key;
}
inline const std::string& get_seed() const {
return this->seed;
}
protected:
std::vector<std::shared_ptr<const KeyFile>> possible_keys;
std::shared_ptr<const KeyFile> active_key;
std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> possible_keys;
std::shared_ptr<const PSOBBEncryption::KeyFile> active_key;
std::shared_ptr<PSOBBEncryption> active_crypt;
std::string expected_first_data;
std::string seed;
};
class PSOBBMultiKeyServerEncryption : public PSOBBEncryption {
class PSOBBMultiKeyImitatorEncryption : public PSOEncryption {
public:
PSOBBMultiKeyServerEncryption(
std::shared_ptr<const PSOBBMultiKeyClientEncryption> client_crypt,
PSOBBMultiKeyImitatorEncryption(
std::shared_ptr<const PSOBBMultiKeyDetectorEncryption> client_crypt,
const void* seed,
size_t seed_size);
size_t seed_size,
bool jsd1_use_detector_seed);
virtual void encrypt(void* data, size_t size, bool advance = true);
virtual void decrypt(void* data, size_t size, bool advance = true);
protected:
void ensure_stream_ready();
std::shared_ptr<PSOBBEncryption> ensure_crypt();
std::shared_ptr<const PSOBBMultiKeyClientEncryption> client_crypt;
std::shared_ptr<const PSOBBMultiKeyDetectorEncryption> detector_crypt;
std::shared_ptr<PSOBBEncryption> active_crypt;
std::string seed;
bool jsd1_use_detector_seed;
};
+15 -7
View File
@@ -138,7 +138,7 @@ void for_each_received_command(
function<void(uint16_t, uint16_t, string&)> fn) {
struct evbuffer* buf = bufferevent_get_input(bev);
size_t header_size = version == GameVersion::BB ? 8 : 4;
size_t header_size = (version == GameVersion::BB) ? 8 : 4;
for (;;) {
PSOCommandHeader header;
if (evbuffer_copyout(buf, &header, header_size)
@@ -152,18 +152,26 @@ void for_each_received_command(
size_t command_logical_size = header.size(version);
// BB pads commands to 8-byte boundaries, and this is not reflected in the
// size field
size_t command_physical_size = (version == GameVersion::BB)
// If encryption is enabled, BB pads commands to 8-byte boundaries, and this
// is not reflected in the size field. This logic does not occur if
// encryption is not yet enabled.
size_t command_physical_size = (crypt && (version == GameVersion::BB))
? ((command_logical_size + header_size - 1) & ~(header_size - 1))
: command_logical_size;
if (evbuffer_get_length(buf) < command_physical_size) {
break;
}
// If we get here, then there is a full command in the buffer
// If we get here, then there is a full command in the buffer. Some
// encryption algorithms' advancement depends on the decrypted data, so we
// have to actually decrypt the header again (with advance=true) to keep
// them in a consistent state.
evbuffer_drain(buf, header_size);
string header_data(header_size, '\0');
if (evbuffer_remove(buf, header_data.data(), header_data.size())
< static_cast<ssize_t>(header_data.size())) {
throw logic_error("enough bytes available, but could not remove them");
}
string command_data(command_physical_size - header_size, '\0');
if (evbuffer_remove(buf, command_data.data(), command_data.size())
@@ -172,7 +180,7 @@ void for_each_received_command(
}
if (crypt) {
crypt->skip(header_size);
crypt->decrypt(header_data.data(), header_data.size());
crypt->decrypt(command_data.data(), command_data.size());
}
command_data.resize(command_logical_size - header_size);
+35 -24
View File
@@ -204,38 +204,33 @@ static bool process_server_pc_gc_patch_02_17(shared_ptr<ServerState> s,
}
}
static bool process_server_bb_03(shared_ptr<ServerState> s,
ProxyServer::LinkedSession& session, uint16_t command, uint32_t flag, string& data) {
static bool process_server_bb_03(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
// Most servers don't include after_message or have a shorter
// after_message than newserv does, so don't require it
const auto& cmd = check_size_t<S_ServerInit_BB_03>(data,
offsetof(S_ServerInit_BB_03, after_message), 0xFFFF);
// Unlike on PC/GC, BB linked sessions never have licenses.
// TODO: Is there any way we can support this in the future? Probably not, due
// to BB's login and character select flow, right?
if (session.license) {
throw runtime_error("BB linked session has license");
if (!session.detector_crypt.get()) {
throw runtime_error("BB linked session has no detector crypt");
}
if (!session.login_command_bb.username.len()) {
throw logic_error("linked BB session does not have a saved login command");
}
session.log(INFO, "No license in linked session");
// This isn't forwarded to the client, so only recreate the server's crypts.
// Use the same crypt type as the client... the server has the luxury of being
// able to try all the crypts it knows to detect what type the client uses,
// but the client can't do this since it sends the first encrypted data on the
// connection.
session.server_input_crypt.reset(new PSOBBMultiKeyImitatorEncryption(
session.detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), false));
session.server_output_crypt.reset(new PSOBBMultiKeyImitatorEncryption(
session.detector_crypt, cmd.client_key.data(), sizeof(cmd.client_key), false));
// We have to forward the command before setting up encryption, so the
// client will be able to understand it.
forward_command(session, false, command, flag, data);
// BB encryption is stateless after it's initialized, unlike previous
// versions, so we can get away with only two instances instead of four here.
// This is convenient because the two encryptions are linked together due to
// our use of multiple private keys, unlike for the other versions.
static const string expected_first_data("\xB4\x00\x93\x00\x00\x00\x00\x00", 8);
shared_ptr<PSOBBMultiKeyClientEncryption> client_encr(new PSOBBMultiKeyClientEncryption(
s->bb_private_keys, expected_first_data, cmd.client_key.data(), sizeof(cmd.client_key)));
session.server_input_crypt.reset(new PSOBBMultiKeyServerEncryption(
client_encr, cmd.server_key.data(), sizeof(cmd.server_key)));
session.server_output_crypt = client_encr;
session.client_input_crypt = session.server_output_crypt;
session.client_output_crypt = session.server_input_crypt;
// Forward the login command we saved during the unlinked session.
session.send_to_end(true, 0x93, 0x00, &session.login_command_bb,
sizeof(session.login_command_bb));
return false;
}
@@ -395,6 +390,22 @@ static bool process_server_gc_E4(shared_ptr<ServerState>,
static bool process_server_game_19_patch_14(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t command, uint32_t, string& data) {
// If the command is shorter than 6 bytes, use the previous server command to
// fill it in. This simulates a behavior used by some private servers where a
// longer previous command is used to fill part of the client's receive buffer
// with meaningful data, then an intentionally undersize 19 command is sent
// which results in the client using the previous command's data as part of
// the 19 command's contents. They presumably do this in an attempt to prevent
// people from using proxies.
if (data.size() < sizeof(session.prev_server_command_bytes)) {
data.append(
reinterpret_cast<const char*>(&session.prev_server_command_bytes[data.size()]),
sizeof(session.prev_server_command_bytes) - data.size());
}
if (data.size() < sizeof(S_Reconnect_19)) {
data.resize(sizeof(S_Reconnect_19), '\0');
}
// This weird maximum size is here to properly handle the version-split
// command that some servers (including newserv) use on port 9100
auto& cmd = check_size_t<S_Reconnect_19>(data, sizeof(S_Reconnect_19), 0xB0);
+143 -58
View File
@@ -106,7 +106,8 @@ void ProxyServer::ListeningSocket::dispatch_on_listen_error(
}
void ProxyServer::ListeningSocket::on_listen_accept(int fd) {
this->log(INFO, "Client connected on fd %d", fd);
this->log(INFO, "Client connected on fd %d (port %hu, version %s)",
fd, this->port, name_for_version(this->version));
auto* bev = bufferevent_socket_new(this->server->base.get(), fd,
BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
this->server->on_client_connect(bev, this->port, this->version,
@@ -136,7 +137,8 @@ void ProxyServer::connect_client(struct bufferevent* bev, uint16_t server_port)
return;
}
this->log(INFO, "Client connected on virtual connection %p", bev);
this->log(INFO, "Client connected on virtual connection %p (port %hu)", bev,
server_port);
this->on_client_connect(bev, server_port, version, nullptr);
}
@@ -147,9 +149,10 @@ void ProxyServer::on_client_connect(
uint16_t listen_port,
GameVersion version,
const struct sockaddr_storage* default_destination) {
// If a default destination exists for this client, create a linked session
// immediately and connect to the remote server. This creates a direct session
if (default_destination) {
// If a default destination exists for this client and the client is a patch
// client, create a linked session immediately and connect to the remote
// server. This creates a direct session.
if (default_destination && (version == GameVersion::PATCH)) {
uint64_t session_id = this->next_unlicensed_session_id++;
if (this->next_unlicensed_session_id == 0) {
this->next_unlicensed_session_id = 0xFF00000000000001;
@@ -164,9 +167,9 @@ void ProxyServer::on_client_connect(
session->log(INFO, "Opened linked session");
session->resume(bev);
// If no default destination exists, create an unlinked session - we'll have
// to get the destination from the client's config, which we'll get via a 9E
// command soon
// If no default destination exists, or the client is not a patch client,
// create an unlinked session - we'll have to get the destination from the
// client's config, which we'll get via a 9E command soon.
} else {
auto emplace_ret = this->bev_to_unlinked_session.emplace(bev, new UnlinkedSession(
this, bev, listen_port, version));
@@ -176,6 +179,12 @@ void ProxyServer::on_client_connect(
auto session = emplace_ret.first->second;
this->log(INFO, "Opened unlinked session");
// Note that this should only be set when the linked session is created, not
// when it is resumed!
if (default_destination) {
session->next_destination = *default_destination;
}
switch (version) {
case GameVersion::PATCH:
throw logic_error("cannot create unlinked patch session");
@@ -204,6 +213,31 @@ void ProxyServer::on_client_connect(
}
break;
}
case GameVersion::BB: {
parray<uint8_t, 0x30> server_key;
parray<uint8_t, 0x30> client_key;
random_data(server_key.data(), server_key.bytes());
random_data(client_key.data(), client_key.bytes());
auto cmd = prepare_server_init_contents_bb(server_key, client_key);
send_command(
session->bev.get(),
session->version,
session->crypt_out.get(),
0x03,
0,
&cmd,
sizeof(cmd),
"unlinked proxy client");
bufferevent_flush(session->bev.get(), EV_READ | EV_WRITE, BEV_FLUSH);
static const string expected_first_data("\xB4\x00\x93\x00\x00\x00\x00\x00", 8);
session->detector_crypt.reset(new PSOBBMultiKeyDetectorEncryption(
this->state->bb_private_keys, expected_first_data, cmd.client_key.data(), sizeof(cmd.client_key)));
session->crypt_in = session->detector_crypt;
session->crypt_out.reset(new PSOBBMultiKeyImitatorEncryption(
session->detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), true));
break;
}
default:
throw logic_error("unsupported game version on proxy server");
}
@@ -219,6 +253,7 @@ ProxyServer::UnlinkedSession::UnlinkedSession(
bev(bev, flush_and_free_bufferevent),
local_port(local_port),
version(version) {
memset(&this->next_destination, 0, sizeof(this->next_destination));
bufferevent_setcb(this->bev.get(),
&UnlinkedSession::dispatch_on_client_input, nullptr,
&UnlinkedSession::dispatch_on_client_error, this);
@@ -241,6 +276,7 @@ void ProxyServer::UnlinkedSession::on_client_input() {
uint32_t sub_version = 0;
string character_name;
ClientConfigBB client_config;
C_Login_BB_93 login_command_bb;
try {
for_each_received_command(this->bev.get(), this->version, this->crypt_in.get(),
@@ -273,6 +309,17 @@ void ProxyServer::UnlinkedSession::on_client_input() {
character_name = cmd.name;
client_config.cfg = cmd.client_config.cfg;
} else if (this->version == GameVersion::BB) {
// We should only get a 93 while the session is unlinked; if we get
// anything else, disconnect
if (command != 0x93) {
throw runtime_error("command is not 93");
}
const auto& cmd = check_size_t<C_Login_BB_93>(data);
license = this->server->state->license_manager->verify_bb(
cmd.username, cmd.password);
login_command_bb = cmd;
} else {
throw logic_error("unsupported unlinked session version");
}
@@ -300,32 +347,48 @@ void ProxyServer::UnlinkedSession::on_client_input() {
} catch (const out_of_range&) {
// If there's no open session for this license, then there must be a valid
// destination in the client config. If there is, open a new linked
// session and set its initial destination
if (client_config.cfg.magic != CLIENT_CONFIG_MAGIC) {
this->log(ERROR, "Client configuration is invalid; cannot open session");
} else {
// destination somewhere - either in the client config or in the unlinked
// session
if (client_config.cfg.magic == CLIENT_CONFIG_MAGIC) {
session.reset(new LinkedSession(
this->server,
this->local_port,
this->version,
license,
client_config));
this->server->id_to_session.emplace(license->serial_number, session);
session->log(INFO, "Opened licensed session for unlinked session");
session->log(INFO, "Opened licensed session for unlinked session based on client config");
} else if (this->next_destination.ss_family == AF_INET) {
session.reset(new LinkedSession(
this->server,
this->local_port,
this->version,
license,
this->next_destination));
session->log(INFO, "Opened licensed session for unlinked session based on unlinked default destination");
} else {
this->log(ERROR, "Cannot open linked session: no valid destination in client config or unlinked session");
}
}
if (session.get() && (session->version != this->version)) {
session->log(ERROR, "Linked session has different game version");
} else {
// Resume the linked session using the unlinked session
try {
session->resume(move(this->bev), this->crypt_in, this->crypt_out, sub_version, character_name);
this->crypt_in.reset();
this->crypt_out.reset();
} catch (const exception& e) {
session->log(ERROR, "Failed to resume linked session: %s", e.what());
if (session.get()) {
this->server->id_to_session.emplace(license->serial_number, session);
if (session->version != this->version) {
session->log(ERROR, "Linked session has different game version");
} else {
// Resume the linked session using the unlinked session
try {
if (this->version == GameVersion::BB) {
session->resume(move(this->bev), this->crypt_in, this->crypt_out,
this->detector_crypt, login_command_bb);
} else {
session->resume(move(this->bev), this->crypt_in, this->crypt_out,
this->detector_crypt, sub_version, character_name);
}
this->crypt_in.reset();
this->crypt_out.reset();
} catch (const exception& e) {
session->log(ERROR, "Failed to resume linked session: %s", e.what());
}
}
}
}
@@ -381,13 +444,14 @@ ProxyServer::LinkedSession::LinkedSession(
lobby_players(12),
lobby_client_id(0) {
this->last_switch_enabled_command.subcommand = 0;
memset(this->prev_server_command_bytes, 0, sizeof(this->prev_server_command_bytes));
}
ProxyServer::LinkedSession::LinkedSession(
ProxyServer* server,
uint16_t local_port,
GameVersion version,
std::shared_ptr<const License> license,
shared_ptr<const License> license,
const ClientConfigBB& newserv_client_config)
: LinkedSession(server, license->serial_number, local_port, version) {
this->license = license;
@@ -399,6 +463,17 @@ ProxyServer::LinkedSession::LinkedSession(
dest_sin->sin_addr.s_addr = htonl(this->newserv_client_config.cfg.proxy_destination_address);
}
ProxyServer::LinkedSession::LinkedSession(
ProxyServer* server,
uint16_t local_port,
GameVersion version,
std::shared_ptr<const License> license,
const struct sockaddr_storage& next_destination)
: LinkedSession(server, license->serial_number, local_port, version) {
this->license = license;
this->next_destination = next_destination;
}
ProxyServer::LinkedSession::LinkedSession(
ProxyServer* server,
uint64_t id,
@@ -410,16 +485,47 @@ ProxyServer::LinkedSession::LinkedSession(
}
void ProxyServer::LinkedSession::resume(
std::unique_ptr<struct bufferevent, void(*)(struct bufferevent*)>&& client_bev,
std::shared_ptr<PSOEncryption> client_input_crypt,
std::shared_ptr<PSOEncryption> client_output_crypt,
unique_ptr<struct bufferevent, void(*)(struct bufferevent*)>&& client_bev,
shared_ptr<PSOEncryption> client_input_crypt,
shared_ptr<PSOEncryption> client_output_crypt,
shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
uint32_t sub_version,
const string& character_name) {
this->sub_version = sub_version;
this->character_name = character_name;
this->resume_inner(move(client_bev), client_input_crypt, client_output_crypt,
detector_crypt);
}
void ProxyServer::LinkedSession::resume(
unique_ptr<struct bufferevent, void(*)(struct bufferevent*)>&& client_bev,
shared_ptr<PSOEncryption> client_input_crypt,
shared_ptr<PSOEncryption> client_output_crypt,
shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
C_Login_BB_93 login_command_bb) {
this->login_command_bb = login_command_bb;
this->resume_inner(move(client_bev), client_input_crypt, client_output_crypt,
detector_crypt);
}
void ProxyServer::LinkedSession::resume(struct bufferevent* client_bev) {
unique_ptr<struct bufferevent, void(*)(struct bufferevent*)> bev_unique(
client_bev, flush_and_free_bufferevent);
this->sub_version = 0;
this->character_name.clear();
this->resume_inner(move(bev_unique), nullptr, nullptr, nullptr);
}
void ProxyServer::LinkedSession::resume_inner(
unique_ptr<struct bufferevent, void(*)(struct bufferevent*)>&& client_bev,
shared_ptr<PSOEncryption> client_input_crypt,
shared_ptr<PSOEncryption> client_output_crypt,
shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt) {
if (this->client_bev.get()) {
throw runtime_error("client connection is already open for this session");
}
if (this->next_destination.ss_family != AF_INET) {
throw logic_error("attempted to resume an unlicensed linked session wihout destination set");
throw logic_error("attempted to resume an unlicensed linked session without destination set");
}
this->client_bev = move(client_bev);
@@ -428,10 +534,9 @@ void ProxyServer::LinkedSession::resume(
&ProxyServer::LinkedSession::dispatch_on_client_error, this);
bufferevent_enable(this->client_bev.get(), EV_READ | EV_WRITE);
this->detector_crypt = detector_crypt;
this->client_input_crypt = client_input_crypt;
this->client_output_crypt = client_output_crypt;
this->sub_version = sub_version;
this->character_name = character_name;
this->server_input_crypt.reset();
this->server_output_crypt.reset();
this->saving_files.clear();
@@ -439,28 +544,6 @@ void ProxyServer::LinkedSession::resume(
this->connect();
}
void ProxyServer::LinkedSession::resume(struct bufferevent* client_bev) {
if (this->client_bev.get()) {
throw runtime_error("client connection is already open for this session");
}
this->client_bev.reset(client_bev);
bufferevent_setcb(this->client_bev.get(),
&ProxyServer::LinkedSession::dispatch_on_client_input, nullptr,
&ProxyServer::LinkedSession::dispatch_on_client_error, this);
bufferevent_enable(this->client_bev.get(), EV_READ | EV_WRITE);
this->client_input_crypt.reset();
this->client_output_crypt.reset();
this->server_input_crypt.reset();
this->server_output_crypt.reset();
this->sub_version = 0;
this->character_name.clear();
this->saving_files.clear();
this->connect();
}
void ProxyServer::LinkedSession::connect() {
// Connect to the remote server. The command handlers will do the login steps
// and set up forwarding
@@ -496,8 +579,8 @@ void ProxyServer::LinkedSession::connect() {
ProxyServer::LinkedSession::SavingFile::SavingFile(
const std::string& basename,
const std::string& output_filename,
const string& basename,
const string& output_filename,
uint32_t remaining_bytes)
: basename(basename),
output_filename(output_filename),
@@ -600,6 +683,8 @@ void ProxyServer::LinkedSession::on_server_input() {
[&](uint16_t command, uint32_t flag, string& data) {
print_received_command(command, flag, data.data(), data.size(),
this->version, this->server_name.c_str(), TerminalFormat::FG_RED);
size_t bytes_to_save = min<size_t>(data.size(), sizeof(this->prev_server_command_bytes));
memcpy(this->prev_server_command_bytes, data.data(), bytes_to_save);
process_proxy_command(
this->server->state,
*this,
@@ -676,8 +761,8 @@ shared_ptr<ProxyServer::LinkedSession> ProxyServer::get_session() {
return this->id_to_session.begin()->second;
}
std::shared_ptr<ProxyServer::LinkedSession> ProxyServer::create_licensed_session(
std::shared_ptr<const License> l, uint16_t local_port, GameVersion version,
shared_ptr<ProxyServer::LinkedSession> ProxyServer::create_licensed_session(
shared_ptr<const License> l, uint16_t local_port, GameVersion version,
const ClientConfigBB& newserv_client_config) {
shared_ptr<LinkedSession> session(new LinkedSession(
this, local_port, version, l, newserv_client_config));
+26 -1
View File
@@ -47,9 +47,13 @@ public:
std::unique_ptr<struct bufferevent, void(*)(struct bufferevent*)> server_bev;
uint16_t local_port;
struct sockaddr_storage next_destination;
uint8_t prev_server_command_bytes[6];
GameVersion version;
uint32_t sub_version;
std::string character_name;
C_Login_BB_93 login_command_bb;
uint32_t remote_guild_card_number;
parray<uint8_t, 0x20> remote_client_config_data;
@@ -76,6 +80,7 @@ public:
std::shared_ptr<PSOEncryption> client_output_crypt;
std::shared_ptr<PSOEncryption> server_input_crypt;
std::shared_ptr<PSOEncryption> server_output_crypt;
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt;
struct SavingFile {
std::string basename;
@@ -102,6 +107,12 @@ public:
GameVersion version,
std::shared_ptr<const License> license,
const ClientConfigBB& newserv_client_config);
LinkedSession(
ProxyServer* server,
uint16_t local_port,
GameVersion version,
std::shared_ptr<const License> license,
const struct sockaddr_storage& next_destination);
LinkedSession(
ProxyServer* server,
uint64_t id,
@@ -113,9 +124,21 @@ public:
std::unique_ptr<struct bufferevent, void(*)(struct bufferevent*)>&& client_bev,
std::shared_ptr<PSOEncryption> client_input_crypt,
std::shared_ptr<PSOEncryption> client_output_crypt,
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
uint32_t sub_version,
const std::string& character_name);
void resume(struct bufferevent* bev);
void resume(
std::unique_ptr<struct bufferevent, void(*)(struct bufferevent*)>&& client_bev,
std::shared_ptr<PSOEncryption> client_input_crypt,
std::shared_ptr<PSOEncryption> client_output_crypt,
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
C_Login_BB_93 login_command_bb);
void resume(struct bufferevent* client_bev);
void resume_inner(
std::unique_ptr<struct bufferevent, void(*)(struct bufferevent*)>&& client_bev,
std::shared_ptr<PSOEncryption> client_input_crypt,
std::shared_ptr<PSOEncryption> client_output_crypt,
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt);
void connect();
static void dispatch_on_client_input(struct bufferevent* bev, void* ctx);
@@ -182,9 +205,11 @@ private:
std::unique_ptr<struct bufferevent, void(*)(struct bufferevent*)> bev;
uint16_t local_port;
GameVersion version;
struct sockaddr_storage next_destination;
std::shared_ptr<PSOEncryption> crypt_out;
std::shared_ptr<PSOEncryption> crypt_in;
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt;
UnlinkedSession(ProxyServer* server, struct bufferevent* bev, uint16_t port, GameVersion version);
+30 -10
View File
@@ -35,6 +35,7 @@ void send_command(
size_t size,
const char* name_str) {
string send_data;
size_t logical_size;
switch (version) {
case GameVersion::GC:
@@ -46,8 +47,9 @@ void send_command(
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
if (size) {
send_data.append(reinterpret_cast<const char*>(data), size);
send_data.resize(header.size);
send_data.resize(header.size, '\0');
}
logical_size = header.size;
break;
}
@@ -60,8 +62,9 @@ void send_command(
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
if (size) {
send_data.append(reinterpret_cast<const char*>(data), size);
send_data.resize(header.size);
send_data.resize(header.size, '\0');
}
logical_size = header.size;
break;
}
@@ -76,8 +79,13 @@ void send_command(
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
if (size) {
send_data.append(reinterpret_cast<const char*>(data), size);
send_data.resize((send_data.size() + 7) & ~7);
if (crypt) {
send_data.resize((send_data.size() + 7) & ~7, '\0');
} else {
send_data.resize(header.size, '\0');
}
}
logical_size = header.size;
break;
}
@@ -95,7 +103,7 @@ void send_command(
}
log(INFO, "Sending%s (version=%s command=%04hX flag=%08X)",
name_token.c_str(), name_for_version(version), command, flag);
print_data(stderr, send_data.data(), send_data.size());
print_data(stderr, send_data.data(), logical_size);
if (use_terminal_colors) {
print_color_escape(stderr, TerminalFormat::NORMAL, TerminalFormat::END);
}
@@ -204,19 +212,31 @@ void send_server_init_dc_pc_gc(shared_ptr<Client> c,
}
}
void send_server_init_bb(shared_ptr<ServerState> s, shared_ptr<Client> c) {
S_ServerInit_BB_03 prepare_server_init_contents_bb(
const parray<uint8_t, 0x30>& server_key,
const parray<uint8_t, 0x30>& client_key) {
S_ServerInit_BB_03 cmd;
cmd.copyright = bb_game_server_copyright;
random_data(cmd.server_key.data(), cmd.server_key.bytes());
random_data(cmd.client_key.data(), cmd.client_key.bytes());
cmd.server_key = server_key;
cmd.client_key = client_key;
cmd.after_message = anti_copyright;
return cmd;
}
void send_server_init_bb(shared_ptr<ServerState> s, shared_ptr<Client> c) {
parray<uint8_t, 0x30> server_key;
parray<uint8_t, 0x30> client_key;
random_data(server_key.data(), server_key.bytes());
random_data(client_key.data(), client_key.bytes());
auto cmd = prepare_server_init_contents_bb(server_key, client_key);
send_command_t(c, 0x03, 0x00, cmd);
static const string expected_first_data("\xB4\x00\x93\x00\x00\x00\x00\x00", 8);
shared_ptr<PSOBBMultiKeyClientEncryption> client_encr(new PSOBBMultiKeyClientEncryption(
shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt(new PSOBBMultiKeyDetectorEncryption(
s->bb_private_keys, expected_first_data, cmd.client_key.data(), sizeof(cmd.client_key)));
c->crypt_in = client_encr;
c->crypt_out.reset(new PSOBBMultiKeyServerEncryption(client_encr, cmd.server_key.data(), sizeof(cmd.server_key)));
c->crypt_in = detector_crypt;
c->crypt_out.reset(new PSOBBMultiKeyImitatorEncryption(
detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), true));
}
void send_server_init_patch(shared_ptr<Client> c) {
+3
View File
@@ -94,6 +94,9 @@ S_ServerInit_DC_PC_GC_02_17 prepare_server_init_contents_dc_pc_gc(
bool initial_connection,
uint32_t server_key,
uint32_t client_key);
S_ServerInit_BB_03 prepare_server_init_contents_bb(
const parray<uint8_t, 0x30>& server_key,
const parray<uint8_t, 0x30>& client_key);
void send_server_init(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c,
bool initial_connection);
void send_update_client_config(std::shared_ptr<Client> c);