From cedb0c648e2f9a69837114365212a618ec29d7c1 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sun, 8 May 2022 19:25:18 -0700 Subject: [PATCH] implement JSD1 --- src/CommandFormats.hh | 6 + src/PSOEncryption.cc | 613 ++++++++++++++++++++++-------------------- src/PSOEncryption.hh | 53 ++-- src/PSOProtocol.cc | 22 +- src/ProxyCommands.cc | 59 ++-- src/ProxyServer.cc | 201 ++++++++++---- src/ProxyServer.hh | 27 +- src/SendCommands.cc | 40 ++- src/SendCommands.hh | 3 + 9 files changed, 615 insertions(+), 409 deletions(-) diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 9826bfaa..962212e4 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -822,6 +822,12 @@ struct C_Login_BB_93 { ClientConfigBB cfg; ptext 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; }; diff --git a/src/PSOEncryption.cc b/src/PSOEncryption.cc index ce8af224..8af11447 100644 --- a/src/PSOEncryption.cc +++ b/src/PSOEncryption.cc @@ -90,29 +90,12 @@ void PSOPCEncryption::encrypt(void* vdata, size_t size, bool advance) { } size >>= 2; - uint32_t* data = reinterpret_cast(vdata); + le_uint32_t* data = reinterpret_cast(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(vdata); + le_uint32_t* data = reinterpret_cast(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(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(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(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(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(vdata); - uint32_t edx, ebx, ebp, esi, edi; + size_t num_dwords = size >> 2; + le_uint32_t* data = reinterpret_cast(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(vdata); + uint8_t* stream_bytes = reinterpret_cast(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 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 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 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(seed[ecx]) << 0x18; - eax = ecx + 1; - edx = eax % seed.size(); - eax = (static_cast(seed[edx]) << 0x10) & 0x00FF0000; - ebp = (ebp | eax) & 0xFFFF00FF; - eax = ecx + 2; - edx = eax % seed.size(); - eax = (static_cast(seed[edx]) << 0x08) & 0x0000FF00; - ebp = (ebp | eax) & 0xFFFFFF00; - eax = ecx + 3; - ecx = ecx + 4; - edx = eax % seed.size(); - eax = static_cast(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(seed[ecx]) << 0x18; + eax = ecx + 1; + edx = eax % seed.size(); + eax = (static_cast(seed[edx]) << 0x10) & 0x00FF0000; + ebp = (ebp | eax) & 0xFFFF00FF; + eax = ecx + 2; + edx = eax % seed.size(); + eax = (static_cast(seed[edx]) << 0x08) & 0x0000FF00; + ebp = (ebp | eax) & 0xFFFFFF00; + eax = ecx + 3; + ecx = ecx + 4; + edx = eax % seed.size(); + eax = static_cast(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(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(seed[seed_offset]) >> 1); + seed_offset = (seed_offset + 1) % seed.size(); } } - - return stream; } -PSOBBMultiKeyClientEncryption::PSOBBMultiKeyClientEncryption( - const vector>& possible_keys, +PSOBBMultiKeyDetectorEncryption::PSOBBMultiKeyDetectorEncryption( + const vector>& 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(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(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 client_crypt, + + +PSOBBMultiKeyImitatorEncryption::PSOBBMultiKeyImitatorEncryption( + shared_ptr detector_crypt, const void* seed, - size_t seed_size) - : client_crypt(client_crypt), - seed(reinterpret_cast(seed), seed_size) { } + size_t seed_size, + bool jsd1_use_detector_seed) + : detector_crypt(detector_crypt), + seed(reinterpret_cast(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 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; } diff --git a/src/PSOEncryption.hh b/src/PSOEncryption.hh index d065b510..402fa45f 100644 --- a/src/PSOEncryption.hh +++ b/src/PSOEncryption.hh @@ -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 generate_stream( - const KeyFile& key, const void* seed, size_t seed_size); - + Subtype subtype; std::vector 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>& possible_keys, + PSOBBMultiKeyDetectorEncryption( + const std::vector>& 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 get_active_key() const { + return this->active_key; + } + inline const std::string& get_seed() const { + return this->seed; + } protected: - std::vector> possible_keys; - std::shared_ptr active_key; + std::vector> possible_keys; + std::shared_ptr active_key; + std::shared_ptr active_crypt; std::string expected_first_data; std::string seed; }; -class PSOBBMultiKeyServerEncryption : public PSOBBEncryption { +class PSOBBMultiKeyImitatorEncryption : public PSOEncryption { public: - PSOBBMultiKeyServerEncryption( - std::shared_ptr client_crypt, + PSOBBMultiKeyImitatorEncryption( + std::shared_ptr 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 ensure_crypt(); - std::shared_ptr client_crypt; + std::shared_ptr detector_crypt; + std::shared_ptr active_crypt; std::string seed; + bool jsd1_use_detector_seed; }; diff --git a/src/PSOProtocol.cc b/src/PSOProtocol.cc index 12edbf8c..a8339242 100644 --- a/src/PSOProtocol.cc +++ b/src/PSOProtocol.cc @@ -138,7 +138,7 @@ void for_each_received_command( function 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(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); diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index abbb3292..41b410c2 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -204,38 +204,33 @@ static bool process_server_pc_gc_patch_02_17(shared_ptr s, } } -static bool process_server_bb_03(shared_ptr s, - ProxyServer::LinkedSession& session, uint16_t command, uint32_t flag, string& data) { +static bool process_server_bb_03(shared_ptr, + 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(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 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, static bool process_server_game_19_patch_14(shared_ptr, 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(&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(data, sizeof(S_Reconnect_19), 0xB0); diff --git a/src/ProxyServer.cc b/src/ProxyServer.cc index 72f888c6..52b94b6a 100644 --- a/src/ProxyServer.cc +++ b/src/ProxyServer.cc @@ -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 server_key; + parray 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(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 license, + shared_ptr 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 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&& client_bev, - std::shared_ptr client_input_crypt, - std::shared_ptr client_output_crypt, + unique_ptr&& client_bev, + shared_ptr client_input_crypt, + shared_ptr client_output_crypt, + shared_ptr 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&& client_bev, + shared_ptr client_input_crypt, + shared_ptr client_output_crypt, + shared_ptr 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 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&& client_bev, + shared_ptr client_input_crypt, + shared_ptr client_output_crypt, + shared_ptr 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(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::get_session() { return this->id_to_session.begin()->second; } -std::shared_ptr ProxyServer::create_licensed_session( - std::shared_ptr l, uint16_t local_port, GameVersion version, +shared_ptr ProxyServer::create_licensed_session( + shared_ptr l, uint16_t local_port, GameVersion version, const ClientConfigBB& newserv_client_config) { shared_ptr session(new LinkedSession( this, local_port, version, l, newserv_client_config)); diff --git a/src/ProxyServer.hh b/src/ProxyServer.hh index 0c98aff8..dd9feb0e 100644 --- a/src/ProxyServer.hh +++ b/src/ProxyServer.hh @@ -47,9 +47,13 @@ public: std::unique_ptr 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 remote_client_config_data; @@ -76,6 +80,7 @@ public: std::shared_ptr client_output_crypt; std::shared_ptr server_input_crypt; std::shared_ptr server_output_crypt; + std::shared_ptr detector_crypt; struct SavingFile { std::string basename; @@ -102,6 +107,12 @@ public: GameVersion version, std::shared_ptr license, const ClientConfigBB& newserv_client_config); + LinkedSession( + ProxyServer* server, + uint16_t local_port, + GameVersion version, + std::shared_ptr license, + const struct sockaddr_storage& next_destination); LinkedSession( ProxyServer* server, uint64_t id, @@ -113,9 +124,21 @@ public: std::unique_ptr&& client_bev, std::shared_ptr client_input_crypt, std::shared_ptr client_output_crypt, + std::shared_ptr detector_crypt, uint32_t sub_version, const std::string& character_name); - void resume(struct bufferevent* bev); + void resume( + std::unique_ptr&& client_bev, + std::shared_ptr client_input_crypt, + std::shared_ptr client_output_crypt, + std::shared_ptr detector_crypt, + C_Login_BB_93 login_command_bb); + void resume(struct bufferevent* client_bev); + void resume_inner( + std::unique_ptr&& client_bev, + std::shared_ptr client_input_crypt, + std::shared_ptr client_output_crypt, + std::shared_ptr detector_crypt); void connect(); static void dispatch_on_client_input(struct bufferevent* bev, void* ctx); @@ -182,9 +205,11 @@ private: std::unique_ptr bev; uint16_t local_port; GameVersion version; + struct sockaddr_storage next_destination; std::shared_ptr crypt_out; std::shared_ptr crypt_in; + std::shared_ptr detector_crypt; UnlinkedSession(ProxyServer* server, struct bufferevent* bev, uint16_t port, GameVersion version); diff --git a/src/SendCommands.cc b/src/SendCommands.cc index ac01210f..e513241f 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -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(&header), sizeof(header)); if (size) { send_data.append(reinterpret_cast(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(&header), sizeof(header)); if (size) { send_data.append(reinterpret_cast(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(&header), sizeof(header)); if (size) { send_data.append(reinterpret_cast(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 c, } } -void send_server_init_bb(shared_ptr s, shared_ptr c) { +S_ServerInit_BB_03 prepare_server_init_contents_bb( + const parray& server_key, + const parray& 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 s, shared_ptr c) { + parray server_key; + parray 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 client_encr(new PSOBBMultiKeyClientEncryption( + shared_ptr 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 c) { diff --git a/src/SendCommands.hh b/src/SendCommands.hh index 27300902..39216950 100644 --- a/src/SendCommands.hh +++ b/src/SendCommands.hh @@ -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& server_key, + const parray& client_key); void send_server_init(std::shared_ptr s, std::shared_ptr c, bool initial_connection); void send_update_client_config(std::shared_ptr c);