diff --git a/CMakeLists.txt b/CMakeLists.txt index 403fd5e5..3a0b10de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,12 +35,12 @@ add_executable(newserv src/FileContentsCache.cc src/Menu.cc src/PSOProtocol.cc + src/PSOEncryption.cc src/Client.cc src/Lobby.cc src/ServerState.cc src/Server.cc src/License.cc - src/PSOEncryption.cc src/Player.cc src/SendCommands.cc src/ChatCommands.cc diff --git a/src/Client.hh b/src/Client.hh index b801c573..fdc72a18 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -51,7 +51,6 @@ struct Client { ServerBehavior server_behavior; bool is_virtual_connection; bool should_disconnect; - std::string recv_buffer; // timing & menus uint64_t play_time_begin; // time of connection (used for incrementing play time on BB) diff --git a/src/PSOEncryption.cc b/src/PSOEncryption.cc index 5f5d3bfe..f41f3a47 100644 --- a/src/PSOEncryption.cc +++ b/src/PSOEncryption.cc @@ -16,9 +16,9 @@ using namespace std; -// most ciphers used by pso are symmetric; alias decrypt to encrypt by default -void PSOEncryption::decrypt(void* data, size_t size) { - this->encrypt(data, size); +// Most ciphers used by PSO are symmetric; alias decrypt to encrypt by default +void PSOEncryption::decrypt(void* data, size_t size, bool advance) { + this->encrypt(data, size, advance); } @@ -71,26 +71,50 @@ PSOPCEncryption::PSOPCEncryption(uint32_t seed) : offset(1) { } } -uint32_t PSOPCEncryption::next() { +uint32_t PSOPCEncryption::next(bool advance) { if (this->offset == PC_STREAM_LENGTH) { this->update_stream(); this->offset = 1; } - return this->stream[this->offset++]; + uint32_t ret = this->stream[this->offset]; + if (advance) { + this->offset++; + } + return ret; } -void PSOPCEncryption::encrypt(void* vdata, size_t size) { +void PSOPCEncryption::encrypt(void* vdata, size_t size, bool advance) { if (size & 3) { throw invalid_argument("size must be a multiple of 4"); } + if (!advance && (size != 4)) { + throw logic_error("cannot peek-encrypt/decrypt with size > 4"); + } size >>= 2; uint32_t* data = reinterpret_cast(vdata); for (size_t x = 0; x < size; x++) { - data[x] ^= this->next(); + 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() { @@ -110,12 +134,15 @@ void PSOGCEncryption::update_stream() { this->offset = 0; } -uint32_t PSOGCEncryption::next() { - this->offset++; +uint32_t PSOGCEncryption::next(bool advance) { if (this->offset == GC_STREAM_LENGTH) { this->update_stream(); } - return this->stream[this->offset]; + uint32_t ret = this->stream[this->offset]; + if (advance) { + this->offset++; + } + return ret; } PSOGCEncryption::PSOGCEncryption(uint32_t seed) : offset(0) { @@ -145,27 +172,43 @@ PSOGCEncryption::PSOGCEncryption(uint32_t seed) : offset(0) { this->stream[this->offset++] = (this->stream[source3++] ^ (((this->stream[source1++] << 23) & 0xFF800000) ^ ((this->stream[source2++] >> 9) & 0x007FFFFF))); } - for (size_t x = 0; x < 3; x++) { + for (size_t x = 0; x < 4; x++) { this->update_stream(); } - this->offset = GC_STREAM_LENGTH - 1; } -void PSOGCEncryption::encrypt(void* vdata, size_t size) { +void PSOGCEncryption::encrypt(void* vdata, size_t size, bool advance) { if (size & 3) { throw invalid_argument("size must be a multiple of 4"); } + if (!advance && (size != 4)) { + throw logic_error("cannot peek-encrypt/decrypt with size > 4"); + } size >>= 2; uint32_t* data = reinterpret_cast(vdata); for (size_t x = 0; x < size; x++) { - data[x] ^= this->next(); + 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) { +void PSOBBEncryption::decrypt(void* vdata, size_t size, bool) { if (size & 7) { throw invalid_argument("size must be a multiple of 8"); } @@ -178,19 +221,19 @@ void PSOBBEncryption::decrypt(void* vdata, size_t size) { 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 = ((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 = ((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]; + 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 = ((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; @@ -200,7 +243,7 @@ void PSOBBEncryption::decrypt(void* vdata, size_t size) { } } -void PSOBBEncryption::encrypt(void* vdata, size_t size) { +void PSOBBEncryption::encrypt(void* vdata, size_t size, bool) { if (size & 7) { throw invalid_argument("size must be a multiple of 8"); } @@ -213,19 +256,19 @@ void PSOBBEncryption::encrypt(void* vdata, size_t size) { while (edx < size) { ebx = data[edx]; ebx = ebx ^ 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 = ((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 = ((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]; + 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 = ((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; @@ -235,8 +278,12 @@ void PSOBBEncryption::encrypt(void* vdata, size_t size) { } } -PSOBBEncryption::PSOBBEncryption(const KeyFile& key, const void* original_seed, - size_t seed_size) : offset(0) { +PSOBBEncryption::PSOBBEncryption( + const KeyFile& key, const void* original_seed, size_t seed_size) + : stream(this->generate_stream(key, original_seed, seed_size)) { } + +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"); } @@ -250,194 +297,198 @@ PSOBBEncryption::PSOBBEncryption(const KeyFile& key, const void* original_seed, seed.push_back(original_seed_data[x + 2] ^ 0x18); } - memcpy(this->stream, &key, sizeof(key)); + vector stream(BB_STREAM_LENGTH, 0); + memcpy(stream.data(), &key, sizeof(key)); - this->postprocess_initial_stream(seed); -} + // This block was formerly postprocess_initial_stream + { + uint32_t eax, ecx, edx, ebx, ebp, esi, edi, ou, x; -void PSOBBEncryption::postprocess_initial_stream(const string& seed) { - uint32_t eax, ecx, edx, ebx, ebp, esi, edi, ou, x; + ecx = 0; + ebx = 0; - ecx = 0; - ebx = 0; - - while (ebx < (seed.size() / 4)) { - ebp = static_cast(seed[ecx]) << 0x18; - eax = ecx + 1; - edx = eax % seed.size(); - eax = (static_cast(seed[edx]) << 0x10) & 0xFF0000; - ebp = (ebp | eax) & 0xffff00ff; - eax = ecx + 2; - edx = eax % seed.size(); - eax = (static_cast(seed[edx]) << 0x08) & 0xFF00; - ebp = (ebp | eax) & 0xffffff00; - eax = ecx + 3; - ecx = ecx + 4; - edx = eax % seed.size(); - eax = static_cast(seed[edx]); - ebp = ebp | eax; - eax = ecx; - edx = eax % seed.size(); - this->stream[ebx] ^= ebp; - ecx = edx; - ebx++; - } - - ebp = 0; - esi = 0; - ecx = 0; - edi = 0; - ebx = 0; - edx = 0x48; - - 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]; + while (ebx < (seed.size() / 4)) { + ebp = static_cast(seed[ecx]) << 0x18; + eax = ecx + 1; + edx = eax % seed.size(); + eax = (static_cast(seed[edx]) << 0x10) & 0xFF0000; + ebp = (ebp | eax) & 0xFFFF00FF; + eax = ecx + 2; + edx = eax % seed.size(); + eax = (static_cast(seed[edx]) << 0x08) & 0xFF00; + ebp = (ebp | eax) & 0xFFFFFF00; + eax = ecx + 3; + ecx = ecx + 4; + edx = eax % seed.size(); + eax = static_cast(seed[edx]); + ebp = ebp | eax; + eax = ecx; + edx = eax % seed.size(); + stream[ebx] ^= ebp; + ecx = edx; + ebx++; } - 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[(edi / 4)] = esi; - this->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 ^ this->stream[0]; + esi = esi ^ stream[0]; eax = esi >> 0x18; - ebx = (esi >> 0x10) & 0xff; - eax = this->stream[eax + 0x12] + this->stream[ebx + 0x112]; + ebx = (esi >> 0x10) & 0xFF; + eax = stream[eax + 0x12] + stream[ebx + 0x112]; ebx = (esi >> 8) & 0xFF; - eax = eax ^ this->stream[ebx + 0x212]; + eax = eax ^ stream[ebx + 0x212]; ebx = esi & 0xFF; - eax = eax + this->stream[ebx + 0x312]; + eax = eax + stream[ebx + 0x312]; - eax = eax ^ this->stream[1]; + eax = eax ^ stream[1]; ecx = ecx ^ eax; ebx = ecx >> 0x18; eax = (ecx >> 0x10) & 0xFF; - ebx = this->stream[ebx + 0x12] + this->stream[eax + 0x112]; + ebx = stream[ebx + 0x12] + stream[eax + 0x112]; eax = (ecx >> 8) & 0xFF; - ebx = ebx ^ this->stream[eax + 0x212]; + ebx = ebx ^ stream[eax + 0x212]; eax = ecx & 0xFF; - ebx = ebx + this->stream[eax + 0x312]; + ebx = ebx + stream[eax + 0x312]; for (x = 0; x <= 5; x++) { - ebx = ebx ^ this->stream[(x * 2) + 2]; + ebx = ebx ^ stream[(x * 2) + 2]; esi = esi ^ ebx; ebx = esi >> 0x18; eax = (esi >> 0x10) & 0xFF; - ebx = this->stream[ebx + 0x12] + this->stream[eax + 0x112]; + ebx = stream[ebx + 0x12] + stream[eax + 0x112]; eax = (esi >> 8) & 0xFF; - ebx = ebx ^ this->stream[eax + 0x212]; + ebx = ebx ^ stream[eax + 0x212]; eax = esi & 0xFF; - ebx = ebx + this->stream[eax + 0x312]; + ebx = ebx + stream[eax + 0x312]; - ebx = ebx ^ this->stream[(x * 2) + 3]; + ebx = ebx ^ stream[(x * 2) + 3]; ecx = ecx ^ ebx; ebx = ecx >> 0x18; eax = (ecx >> 0x10) & 0xFF; - ebx = this->stream[ebx + 0x12] + this->stream[eax + 0x112]; + ebx = stream[ebx + 0x12] + stream[eax + 0x112]; eax = (ecx >> 8) & 0xFF; - ebx = ebx ^ this->stream[eax + 0x212]; + ebx = ebx ^ stream[eax + 0x212]; eax = ecx & 0xFF; - ebx = ebx + this->stream[eax + 0x312]; + ebx = ebx + stream[eax + 0x312]; } - ebx = ebx ^ this->stream[14]; + ebx = ebx ^ stream[14]; esi = esi ^ ebx; eax = esi >> 0x18; ebx = (esi >> 0x10) & 0xFF; - eax = this->stream[eax + 0x12] + this->stream[ebx + 0x112]; + eax = stream[eax + 0x12] + stream[ebx + 0x112]; ebx = (esi >> 8) & 0xFF; - eax = eax ^ this->stream[ebx + 0x212]; + eax = eax ^ stream[ebx + 0x212]; ebx = esi & 0xFF; - eax = eax + this->stream[ebx + 0x312]; + eax = eax + stream[ebx + 0x312]; - eax = eax ^ this->stream[15]; + eax = eax ^ stream[15]; eax = ecx ^ eax; ecx = eax >> 0x18; ebx = (eax >> 0x10) & 0xFF; - ecx = this->stream[ecx + 0x12] + this->stream[ebx + 0x112]; + ecx = stream[ecx + 0x12] + stream[ebx + 0x112]; ebx = (eax >> 8) & 0xFF; - ecx = ecx ^ this->stream[ebx + 0x212]; + ecx = ecx ^ stream[ebx + 0x212]; ebx = eax & 0xFF; - ecx = ecx + this->stream[ebx + 0x312]; + ecx = ecx + stream[ebx + 0x312]; - ecx = ecx ^ this->stream[16]; + ecx = ecx ^ stream[16]; ecx = ecx ^ esi; - esi = this->stream[17]; + esi = stream[17]; esi = esi ^ eax; - this->stream[(ou / 4) + (edi / 4)] = esi; - this->stream[(ou / 4) + (edi / 4) + 1] = ecx; + stream[(edi / 4)] = esi; + 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 ^ 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]; + + 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]; + + 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]; + + 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]; + } + + 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[(ou / 4) + (edi / 4)] = esi; + stream[(ou / 4) + (edi / 4) + 1] = ecx; + edi = edi + 8; + } + ou = ou + 0x400; + } } + + return stream; } + +void PSOBBEncryption::skip(size_t) { } diff --git a/src/PSOEncryption.hh b/src/PSOEncryption.hh index 632a231f..a198ec3a 100644 --- a/src/PSOEncryption.hh +++ b/src/PSOEncryption.hh @@ -4,6 +4,7 @@ #include #include +#include @@ -15,8 +16,9 @@ class PSOEncryption { public: virtual ~PSOEncryption() = default; - virtual void encrypt(void* data, size_t size) = 0; - virtual void decrypt(void* data, size_t size); + virtual void encrypt(void* data, size_t size, bool advance = true) = 0; + virtual void decrypt(void* data, size_t size, bool advance = true); + virtual void skip(size_t size) = 0; protected: PSOEncryption() = default; @@ -26,11 +28,12 @@ class PSOPCEncryption : public PSOEncryption { public: explicit PSOPCEncryption(uint32_t seed); - virtual void encrypt(void* data, size_t size); + virtual void encrypt(void* data, size_t size, bool advance = true); + virtual void skip(size_t size); protected: void update_stream(); - uint32_t next(); + uint32_t next(bool advance = true); uint32_t stream[PC_STREAM_LENGTH]; uint16_t offset; @@ -40,11 +43,12 @@ class PSOGCEncryption : public PSOEncryption { public: explicit PSOGCEncryption(uint32_t key); - virtual void encrypt(void* data, size_t size); + virtual void encrypt(void* data, size_t size, bool advance = true); + virtual void skip(size_t size); protected: void update_stream(); - uint32_t next(); + uint32_t next(bool advance = true); uint32_t stream[GC_STREAM_LENGTH]; uint16_t offset; @@ -59,16 +63,13 @@ public: PSOBBEncryption(const KeyFile& key, const void* seed, size_t seed_size); - virtual void encrypt(void* data, size_t size); - virtual void decrypt(void* data, size_t 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() = default; + static std::vector generate_stream( + const KeyFile& key, const void* seed, size_t seed_size); - void postprocess_initial_stream(const std::string& seed); - - void update_stream(); - - uint32_t stream[BB_STREAM_LENGTH]; - uint16_t offset; + const std::vector stream; }; diff --git a/src/PSOProtocol.cc b/src/PSOProtocol.cc index 4560ea55..38ae3433 100644 --- a/src/PSOProtocol.cc +++ b/src/PSOProtocol.cc @@ -1,11 +1,19 @@ #include "PSOProtocol.hh" +#include + #include using namespace std; +PSOCommandHeader::PSOCommandHeader() { + this->bb.size = 0; + this->bb.command = 0; + this->bb.flag = 0; +} + uint16_t PSOCommandHeader::command(GameVersion version) const { switch (version) { case GameVersion::DC: @@ -48,3 +56,56 @@ uint32_t PSOCommandHeader::flag(GameVersion version) const { throw logic_error("unknown game version"); } + + +void for_each_received_command( + struct bufferevent* bev, + GameVersion version, + PSOEncryption* crypt, + function fn) { + struct evbuffer* buf = bufferevent_get_input(bev); + + size_t header_size = version == GameVersion::BB ? 8 : 4; + for (;;) { + PSOCommandHeader header; + if (evbuffer_copyout(buf, &header, header_size) + < static_cast(header_size)) { + break; + } + + if (crypt) { + crypt->decrypt(&header, header_size, false); + } + + 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) + ? (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 + + evbuffer_drain(buf, header_size); + + string command_data(command_logical_size - header_size, '\0'); + if (evbuffer_remove(buf, command_data.data(), command_data.size()) + < static_cast(command_data.size())) { + throw logic_error("enough bytes available, but could not remove them"); + } + if (command_logical_size != command_physical_size) { + evbuffer_drain(buf, command_physical_size - command_logical_size); + } + + if (crypt) { + crypt->skip(header_size); + crypt->decrypt(command_data.data(), command_data.size()); + } + + fn(header.command(version), header.flag(version), command_data); + } +} \ No newline at end of file diff --git a/src/PSOProtocol.hh b/src/PSOProtocol.hh index 58da2a14..78ccab81 100644 --- a/src/PSOProtocol.hh +++ b/src/PSOProtocol.hh @@ -1,26 +1,30 @@ #pragma once #include +#include + +#include #include "Version.hh" +#include "PSOEncryption.hh" struct PSOCommandHeaderPC { uint16_t size; uint8_t command; uint8_t flag; -}; +} __attribute__((packed)); struct PSOCommandHeaderDCGC { uint8_t command; uint8_t flag; uint16_t size; -}; +} __attribute__((packed)); struct PSOCommandHeaderBB { uint16_t size; uint16_t command; uint32_t flag; -}; +} __attribute__((packed)); union PSOCommandHeader { PSOCommandHeaderDCGC dc; @@ -31,10 +35,18 @@ union PSOCommandHeader { uint16_t command(GameVersion version) const; uint16_t size(GameVersion version) const; uint32_t flag(GameVersion version) const; -}; + + PSOCommandHeader(); +} __attribute__((packed)); union PSOSubcommand { uint8_t byte[4]; uint16_t word[2]; uint32_t dword; -}; +} __attribute__((packed)); + +void for_each_received_command( + struct bufferevent* bev, + GameVersion version, + PSOEncryption* crypt, + std::function fn); diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 11a2f894..20a49b3e 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -128,24 +128,34 @@ static const char* dc_lobby_server_copyright = "DreamCast Lobby Server. Copyrigh static const char* bb_game_server_copyright = "Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM."; static const char* patch_server_copyright = "Patch Server. Copyright SonicTeam, LTD. 2001"; -static void send_server_init_dc_pc_gc(shared_ptr c, const char* copyright_text, - uint8_t command) { - struct { +string prepare_server_init_contents_dc_pc_gc( + bool initial_connection, + uint32_t server_key, + uint32_t client_key) { + struct Command { char copyright[0x40]; uint32_t server_key; uint32_t client_key; char after_message[200]; - } cmd; + }; + string ret(sizeof(Command), '\0'); + auto* cmd = reinterpret_cast(ret.data()); + strcpy(cmd->copyright, initial_connection ? dc_port_map_copyright : dc_lobby_server_copyright); + cmd->server_key = server_key; + cmd->client_key = client_key; + strcpy(cmd->after_message, anti_copyright); + return ret; +} + +static void send_server_init_dc_pc_gc(shared_ptr c, + bool initial_connection, uint8_t command) { uint32_t server_key = random_object(); uint32_t client_key = random_object(); - memset(&cmd, 0, sizeof(cmd)); - strcpy(cmd.copyright, copyright_text); - cmd.server_key = server_key; - cmd.client_key = client_key; - strcpy(cmd.after_message, anti_copyright); - send_command(c, command, 0x00, cmd); + string data = prepare_server_init_contents_dc_pc_gc( + initial_connection, server_key, client_key); + send_command(c, command, 0x00, data); switch (c->version) { case GameVersion::DC: @@ -163,19 +173,14 @@ static void send_server_init_dc_pc_gc(shared_ptr c, const char* copyrigh } static void send_server_init_pc(shared_ptr c, bool initial_connection) { - send_server_init_dc_pc_gc(c, - initial_connection ? dc_port_map_copyright : dc_lobby_server_copyright, - 0x17); + send_server_init_dc_pc_gc(c, initial_connection, 0x17); } static void send_server_init_gc(shared_ptr c, bool initial_connection) { - send_server_init_dc_pc_gc(c, - initial_connection ? dc_port_map_copyright : dc_lobby_server_copyright, - initial_connection ? 0x17 : 0x02); + send_server_init_dc_pc_gc(c, initial_connection, initial_connection ? 0x17 : 0x02); } -static void send_server_init_bb(shared_ptr s, shared_ptr c, - bool) { +static void send_server_init_bb(shared_ptr s, shared_ptr c) { struct { char copyright[0x60]; uint8_t server_key[0x30]; @@ -196,7 +201,7 @@ static void send_server_init_bb(shared_ptr s, shared_ptr c, sizeof(cmd.client_key))); } -static void send_server_init_patch(shared_ptr c, bool) { +static void send_server_init_patch(shared_ptr c) { struct { char copyright[0x40]; uint32_t server_key; @@ -223,11 +228,11 @@ void send_server_init(shared_ptr s, shared_ptr c, if (c->version == GameVersion::PC) { send_server_init_pc(c, initial_connection); } else if (c->version == GameVersion::PATCH) { - send_server_init_patch(c, initial_connection); + send_server_init_patch(c); } else if (c->version == GameVersion::GC) { send_server_init_gc(c, initial_connection); } else if (c->version == GameVersion::BB) { - send_server_init_bb(s, c, initial_connection); + send_server_init_bb(s, c); } else { throw logic_error("unimplemented versioned command"); } diff --git a/src/SendCommands.hh b/src/SendCommands.hh index 6731317b..7db17831 100644 --- a/src/SendCommands.hh +++ b/src/SendCommands.hh @@ -74,7 +74,8 @@ void send_command(std::shared_ptr c, uint16_t command, uint32_t flag, - +std::string prepare_server_init_contents_dc_pc_gc( + bool initial_connection, uint32_t server_key, uint32_t 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); diff --git a/src/Server.cc b/src/Server.cc index cb5151d4..27c5e19c 100644 --- a/src/Server.cc +++ b/src/Server.cc @@ -208,55 +208,16 @@ void Server::on_disconnecting_client_error(struct bufferevent* bev, } void Server::receive_and_process_commands(shared_ptr c) { - struct evbuffer* buf = bufferevent_get_input(c->bev); - size_t header_size = (c->version == GameVersion::BB) ? 8 : 4; - - // read as much data into recv_buffer as we can and decrypt it - size_t existing_bytes = c->recv_buffer.size(); - size_t new_bytes = evbuffer_get_length(buf); - new_bytes &= ~(header_size - 1); // only read in multiples of header_size - c->recv_buffer.resize(existing_bytes + new_bytes); - void* recv_ptr = c->recv_buffer.data() + existing_bytes; - if (evbuffer_remove(buf, recv_ptr, new_bytes) != static_cast(new_bytes)) { - throw runtime_error("some bytes could not be read from the receive buffer"); - } - - // decrypt the received data if encryption is enabled - if (c->crypt_in.get()) { - c->crypt_in->decrypt(recv_ptr, new_bytes); - } - - // process as many commands as possible - size_t offset = 0; - while (offset < c->recv_buffer.size()) { - const PSOCommandHeader* header = reinterpret_cast( - c->recv_buffer.data() + offset); - size_t size = header->size(c->version); - if (offset + size > c->recv_buffer.size()) { - break; // don't have a complete command; we're done for now - } - - // if we get here, then we have a complete, decrypted command waiting to be - // processed. we copy it out and append zeroes on the end so that it's safe - // to call string functions on the buffer in command handlers - string data = c->recv_buffer.substr(offset + header_size, size - header_size); - data.append(4, '\0'); - try { - process_command(this->state, c, header->command(c->version), - header->flag(c->version), size - header_size, data.data()); - } catch (const exception& e) { - log(INFO, "[Server] Error in client stream: %s", e.what()); - c->should_disconnect = true; - return; - } - - // BB pads commands to 8-byte boundaries, so if we see a shorter command, - // skip over the padding - offset += (size + header_size - 1) & ~(header_size - 1); - } - - // remove the processed commands from the receive buffer - c->recv_buffer = c->recv_buffer.substr(offset); + for_each_received_command(c->bev, c->version, c->crypt_in.get(), + [this, c](uint16_t command, uint16_t flag, const std::string& data) { + try { + process_command(this->state, c, command, flag, data.size(), data.data()); + } catch (const exception& e) { + log(INFO, "[Server] Error in client stream: %s", e.what()); + c->should_disconnect = true; + return; + } + }); } Server::Server(shared_ptr base,