implement JSD1
This commit is contained in:
@@ -822,6 +822,12 @@ struct C_Login_BB_93 {
|
|||||||
ClientConfigBB cfg;
|
ClientConfigBB cfg;
|
||||||
ptext<char, 0x28> version_string;
|
ptext<char, 0x28> version_string;
|
||||||
ClientConfigFields() : 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;
|
} client_config;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+201
-160
@@ -90,29 +90,12 @@ void PSOPCEncryption::encrypt(void* vdata, size_t size, bool advance) {
|
|||||||
}
|
}
|
||||||
size >>= 2;
|
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++) {
|
for (size_t x = 0; x < size; x++) {
|
||||||
data[x] ^= this->next(advance);
|
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() {
|
void PSOGCEncryption::update_stream() {
|
||||||
@@ -184,74 +167,93 @@ void PSOGCEncryption::encrypt(void* vdata, size_t size, bool advance) {
|
|||||||
}
|
}
|
||||||
size >>= 2;
|
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++) {
|
for (size_t x = 0; x < size; x++) {
|
||||||
data[x] ^= this->next(advance);
|
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) {
|
||||||
void PSOBBEncryption::decrypt(void* vdata, size_t size, bool) {
|
if (this->subtype != Subtype::JSD1) {
|
||||||
if (size & 7) {
|
if (size & 7) {
|
||||||
throw invalid_argument("size must be a multiple of 8");
|
throw invalid_argument("size must be a multiple of 8");
|
||||||
}
|
}
|
||||||
size >>= 2;
|
size_t num_dwords = size >> 2;
|
||||||
|
le_uint32_t* dwords = reinterpret_cast<le_uint32_t*>(vdata);
|
||||||
uint32_t* data = reinterpret_cast<uint32_t*>(vdata);
|
|
||||||
uint32_t edx, ebx, ebp, esi, edi;
|
uint32_t edx, ebx, ebp, esi, edi;
|
||||||
|
|
||||||
edx = 0;
|
edx = 0;
|
||||||
while (edx < size) {
|
while (edx < num_dwords) {
|
||||||
ebx = data[edx];
|
ebx = dwords[edx];
|
||||||
ebx = ebx ^ this->stream[5];
|
ebx = ebx ^ this->stream[5];
|
||||||
ebp = ((this->stream[(ebx >> 0x18) + 0x12] + this->stream[((ebx >> 0x10) & 0xFF) + 0x112])
|
ebp = ((this->stream[(ebx >> 0x18) + 0x12] +
|
||||||
^ this->stream[((ebx >> 0x8) & 0xFF) + 0x212]) + this->stream[(ebx & 0xFF) + 0x312];
|
this->stream[((ebx >> 0x10) & 0xFF) + 0x112]) ^
|
||||||
|
this->stream[((ebx >> 0x8) & 0xFF) + 0x212]) +
|
||||||
|
this->stream[(ebx & 0xFF) + 0x312];
|
||||||
ebp = ebp ^ this->stream[4];
|
ebp = ebp ^ this->stream[4];
|
||||||
ebp ^= data[edx + 1];
|
ebp ^= dwords[edx + 1];
|
||||||
edi = ((this->stream[(ebp >> 0x18) + 0x12] + this->stream[((ebp >> 0x10) & 0xFF) + 0x112])
|
edi = ((this->stream[(ebp >> 0x18) + 0x12] +
|
||||||
^ this->stream[((ebp >> 0x8) & 0xFF) + 0x212]) + this->stream[(ebp & 0xFF) + 0x312];
|
this->stream[((ebp >> 0x10) & 0xFF) + 0x112]) ^
|
||||||
|
this->stream[((ebp >> 0x8) & 0xFF) + 0x212]) +
|
||||||
|
this->stream[(ebp & 0xFF) + 0x312];
|
||||||
edi = edi ^ this->stream[3];
|
edi = edi ^ this->stream[3];
|
||||||
ebx = ebx ^ edi;
|
ebx = ebx ^ edi;
|
||||||
esi = ((this->stream[(ebx >> 0x18) + 0x12] + this->stream[((ebx >> 0x10) & 0xFF) + 0x112])
|
esi = ((this->stream[(ebx >> 0x18) + 0x12] +
|
||||||
^ this->stream[((ebx >> 0x8) & 0xFF) + 0x212]) + this->stream[(ebx & 0xFF) + 0x312];
|
this->stream[((ebx >> 0x10) & 0xFF) + 0x112]) ^
|
||||||
|
this->stream[((ebx >> 0x8) & 0xFF) + 0x212]) +
|
||||||
|
this->stream[(ebx & 0xFF) + 0x312];
|
||||||
ebp = ebp ^ esi ^ this->stream[2];
|
ebp = ebp ^ esi ^ this->stream[2];
|
||||||
edi = ((this->stream[(ebp >> 0x18) + 0x12] + this->stream[((ebp >> 0x10) & 0xFF) + 0x112])
|
edi = ((this->stream[(ebp >> 0x18) + 0x12] +
|
||||||
^ this->stream[((ebp >> 0x8) & 0xFF) + 0x212]) + this->stream[(ebp & 0xFF) + 0x312];
|
this->stream[((ebp >> 0x10) & 0xFF) + 0x112]) ^
|
||||||
|
this->stream[((ebp >> 0x8) & 0xFF) + 0x212]) +
|
||||||
|
this->stream[(ebp & 0xFF) + 0x312];
|
||||||
edi = edi ^ this->stream[1];
|
edi = edi ^ this->stream[1];
|
||||||
ebp = ebp ^ this->stream[0];
|
ebp = ebp ^ this->stream[0];
|
||||||
ebx = ebx ^ edi;
|
ebx = ebx ^ edi;
|
||||||
data[edx] = ebp;
|
dwords[edx] = ebp;
|
||||||
data[edx + 1] = ebx;
|
dwords[edx + 1] = ebx;
|
||||||
edx += 2;
|
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) {
|
void PSOBBEncryption::encrypt(void* vdata, size_t size, bool advance) {
|
||||||
|
if (this->subtype != Subtype::JSD1) {
|
||||||
if (size & 7) {
|
if (size & 7) {
|
||||||
throw invalid_argument("size must be a multiple of 8");
|
throw invalid_argument("size must be a multiple of 8");
|
||||||
}
|
}
|
||||||
size >>= 2;
|
|
||||||
|
|
||||||
uint32_t* data = reinterpret_cast<uint32_t*>(vdata);
|
size_t num_dwords = size >> 2;
|
||||||
|
le_uint32_t* data = reinterpret_cast<le_uint32_t*>(vdata);
|
||||||
uint32_t edx, ebx, ebp, esi, edi;
|
uint32_t edx, ebx, ebp, esi, edi;
|
||||||
|
|
||||||
edx = 0;
|
edx = 0;
|
||||||
while (edx < size) {
|
while (edx < num_dwords) {
|
||||||
ebx = data[edx] ^ this->stream[0];
|
ebx = data[edx] ^ this->stream[0];
|
||||||
ebp = ((this->stream[(ebx >> 0x18) + 0x12] + this->stream[((ebx >> 0x10) & 0xFF) + 0x112])
|
ebp = ((this->stream[(ebx >> 0x18) + 0x12] + this->stream[((ebx >> 0x10) & 0xFF) + 0x112])
|
||||||
^ this->stream[((ebx >> 0x8) & 0xFF) + 0x212]) + this->stream[(ebx & 0xFF) + 0x312];
|
^ this->stream[((ebx >> 0x8) & 0xFF) + 0x212]) + this->stream[(ebx & 0xFF) + 0x312];
|
||||||
@@ -273,25 +275,36 @@ void PSOBBEncryption::encrypt(void* vdata, size_t size, bool) {
|
|||||||
data[edx + 1] = ebx;
|
data[edx + 1] = ebx;
|
||||||
edx += 2;
|
edx += 2;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void PSOBBEncryption::skip(size_t) { }
|
} 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PSOBBEncryption::PSOBBEncryption(
|
PSOBBEncryption::PSOBBEncryption(
|
||||||
const KeyFile& key, const void* original_seed, size_t seed_size)
|
const KeyFile& key, const void* original_seed, size_t seed_size)
|
||||||
: stream(this->generate_stream(key, original_seed, seed_size)) { }
|
: subtype(key.subtype), jsd1_stream_offset(0) {
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
// Note: This part is done in the 03 command handler in the BB client, and
|
// 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?)
|
// isn't actually part of the encryption library. (Why did they do this?)
|
||||||
@@ -304,18 +317,25 @@ vector<uint32_t> PSOBBEncryption::generate_stream(
|
|||||||
seed.push_back(original_seed_data[x + 2] ^ 0x18);
|
seed.push_back(original_seed_data[x + 2] ^ 0x18);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key.is_modcrypt) {
|
if (this->subtype != Subtype::JSD1) {
|
||||||
|
if (seed_size % 3) {
|
||||||
|
throw invalid_argument("seed size must be divisible by 3");
|
||||||
|
}
|
||||||
|
|
||||||
|
this->stream.resize(BB_STREAM_LENGTH, 0);
|
||||||
|
|
||||||
|
if (key.subtype == Subtype::MOCB1) {
|
||||||
for (size_t x = 0; x < 0x12; x++) {
|
for (size_t x = 0; x < 0x12; x++) {
|
||||||
uint8_t a = key.initial_keys[4 * x + 0];
|
uint8_t a = key.initial_keys[4 * x + 0];
|
||||||
uint8_t b = key.initial_keys[4 * x + 1];
|
uint8_t b = key.initial_keys[4 * x + 1];
|
||||||
uint8_t c = key.initial_keys[4 * x + 2];
|
uint8_t c = key.initial_keys[4 * x + 2];
|
||||||
uint8_t d = key.initial_keys[4 * x + 3];
|
uint8_t d = key.initial_keys[4 * x + 3];
|
||||||
stream[x] = ((a ^ d) << 24) | ((b ^ c) << 16) | (a << 8) | b;
|
this->stream[x] = ((a ^ d) << 24) | ((b ^ c) << 16) | (a << 8) | b;
|
||||||
}
|
}
|
||||||
memcpy(stream.data() + 0x12, &key.private_keys, sizeof(key.private_keys));
|
memcpy(this->stream.data() + 0x12, &key.private_keys, sizeof(key.private_keys));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
memcpy(stream.data(), &key, sizeof(key));
|
memcpy(this->stream.data(), &key, sizeof(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
// This block was formerly postprocess_initial_stream
|
// This block was formerly postprocess_initial_stream
|
||||||
@@ -342,7 +362,7 @@ vector<uint32_t> PSOBBEncryption::generate_stream(
|
|||||||
ebp = ebp | eax;
|
ebp = ebp | eax;
|
||||||
eax = ecx;
|
eax = ecx;
|
||||||
edx = eax % seed.size();
|
edx = eax % seed.size();
|
||||||
stream[ebx] ^= ebp;
|
this->stream[ebx] ^= ebp;
|
||||||
ecx = edx;
|
ecx = edx;
|
||||||
ebx++;
|
ebx++;
|
||||||
}
|
}
|
||||||
@@ -355,73 +375,73 @@ vector<uint32_t> PSOBBEncryption::generate_stream(
|
|||||||
edx = 0x48;
|
edx = 0x48;
|
||||||
|
|
||||||
while (edi < edx) {
|
while (edi < edx) {
|
||||||
esi = esi ^ stream[0];
|
esi = esi ^ this->stream[0];
|
||||||
eax = esi >> 0x18;
|
eax = esi >> 0x18;
|
||||||
ebx = (esi >> 0x10) & 0xFF;
|
ebx = (esi >> 0x10) & 0xFF;
|
||||||
eax = stream[eax + 0x12] + stream[ebx + 0x112];
|
eax = this->stream[eax + 0x12] + this->stream[ebx + 0x112];
|
||||||
ebx = (esi >> 8) & 0xFF;
|
ebx = (esi >> 8) & 0xFF;
|
||||||
eax = eax ^ stream[ebx + 0x212];
|
eax = eax ^ this->stream[ebx + 0x212];
|
||||||
ebx = esi & 0xFF;
|
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;
|
ecx = ecx ^ eax;
|
||||||
ebx = ecx >> 0x18;
|
ebx = ecx >> 0x18;
|
||||||
eax = (ecx >> 0x10) & 0xFF;
|
eax = (ecx >> 0x10) & 0xFF;
|
||||||
ebx = stream[ebx + 0x12] + stream[eax + 0x112];
|
ebx = this->stream[ebx + 0x12] + this->stream[eax + 0x112];
|
||||||
eax = (ecx >> 8) & 0xFF;
|
eax = (ecx >> 8) & 0xFF;
|
||||||
ebx = ebx ^ stream[eax + 0x212];
|
ebx = ebx ^ this->stream[eax + 0x212];
|
||||||
eax = ecx & 0xFF;
|
eax = ecx & 0xFF;
|
||||||
ebx = ebx + stream[eax + 0x312];
|
ebx = ebx + this->stream[eax + 0x312];
|
||||||
|
|
||||||
for (x = 0; x <= 5; x++) {
|
for (x = 0; x <= 5; x++) {
|
||||||
ebx = ebx ^ stream[(x * 2) + 2];
|
ebx = ebx ^ this->stream[(x * 2) + 2];
|
||||||
esi = esi ^ ebx;
|
esi = esi ^ ebx;
|
||||||
ebx = esi >> 0x18;
|
ebx = esi >> 0x18;
|
||||||
eax = (esi >> 0x10) & 0xFF;
|
eax = (esi >> 0x10) & 0xFF;
|
||||||
ebx = stream[ebx + 0x12] + stream[eax + 0x112];
|
ebx = this->stream[ebx + 0x12] + this->stream[eax + 0x112];
|
||||||
eax = (esi >> 8) & 0xFF;
|
eax = (esi >> 8) & 0xFF;
|
||||||
ebx = ebx ^ stream[eax + 0x212];
|
ebx = ebx ^ this->stream[eax + 0x212];
|
||||||
eax = esi & 0xFF;
|
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;
|
ecx = ecx ^ ebx;
|
||||||
ebx = ecx >> 0x18;
|
ebx = ecx >> 0x18;
|
||||||
eax = (ecx >> 0x10) & 0xFF;
|
eax = (ecx >> 0x10) & 0xFF;
|
||||||
ebx = stream[ebx + 0x12] + stream[eax + 0x112];
|
ebx = this->stream[ebx + 0x12] + this->stream[eax + 0x112];
|
||||||
eax = (ecx >> 8) & 0xFF;
|
eax = (ecx >> 8) & 0xFF;
|
||||||
ebx = ebx ^ stream[eax + 0x212];
|
ebx = ebx ^ this->stream[eax + 0x212];
|
||||||
eax = ecx & 0xFF;
|
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;
|
esi = esi ^ ebx;
|
||||||
eax = esi >> 0x18;
|
eax = esi >> 0x18;
|
||||||
ebx = (esi >> 0x10) & 0xFF;
|
ebx = (esi >> 0x10) & 0xFF;
|
||||||
eax = stream[eax + 0x12] + stream[ebx + 0x112];
|
eax = this->stream[eax + 0x12] + this->stream[ebx + 0x112];
|
||||||
ebx = (esi >> 8) & 0xFF;
|
ebx = (esi >> 8) & 0xFF;
|
||||||
eax = eax ^ stream[ebx + 0x212];
|
eax = eax ^ this->stream[ebx + 0x212];
|
||||||
ebx = esi & 0xFF;
|
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;
|
eax = ecx ^ eax;
|
||||||
ecx = eax >> 0x18;
|
ecx = eax >> 0x18;
|
||||||
ebx = (eax >> 0x10) & 0xFF;
|
ebx = (eax >> 0x10) & 0xFF;
|
||||||
ecx = stream[ecx + 0x12] + stream[ebx + 0x112];
|
ecx = this->stream[ecx + 0x12] + this->stream[ebx + 0x112];
|
||||||
ebx = (eax >> 8) & 0xFF;
|
ebx = (eax >> 8) & 0xFF;
|
||||||
ecx = ecx ^ stream[ebx + 0x212];
|
ecx = ecx ^ this->stream[ebx + 0x212];
|
||||||
ebx = eax & 0xFF;
|
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;
|
ecx = ecx ^ esi;
|
||||||
esi = stream[17];
|
esi = this->stream[17];
|
||||||
esi = esi ^ eax;
|
esi = esi ^ eax;
|
||||||
stream[(edi / 4)] = esi;
|
this->stream[(edi / 4)] = esi;
|
||||||
stream[(edi / 4)+1] = ecx;
|
this->stream[(edi / 4)+1] = ecx;
|
||||||
edi = edi + 8;
|
edi = edi + 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,86 +453,94 @@ vector<uint32_t> PSOBBEncryption::generate_stream(
|
|||||||
edx = 0x448;
|
edx = 0x448;
|
||||||
|
|
||||||
while (edi < edx) {
|
while (edi < edx) {
|
||||||
esi = esi ^ stream[0];
|
esi = esi ^ this->stream[0];
|
||||||
eax = esi >> 0x18;
|
eax = esi >> 0x18;
|
||||||
ebx = (esi >> 0x10) & 0xFF;
|
ebx = (esi >> 0x10) & 0xFF;
|
||||||
eax = stream[eax + 0x12] + stream[ebx + 0x112];
|
eax = this->stream[eax + 0x12] + this->stream[ebx + 0x112];
|
||||||
ebx = (esi >> 8) & 0xFF;
|
ebx = (esi >> 8) & 0xFF;
|
||||||
eax = eax ^ stream[ebx + 0x212];
|
eax = eax ^ this->stream[ebx + 0x212];
|
||||||
ebx = esi & 0xFF;
|
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;
|
ecx = ecx ^ eax;
|
||||||
ebx = ecx >> 0x18;
|
ebx = ecx >> 0x18;
|
||||||
eax = (ecx >> 0x10) & 0xFF;
|
eax = (ecx >> 0x10) & 0xFF;
|
||||||
ebx = stream[ebx + 0x12] + stream[eax + 0x112];
|
ebx = this->stream[ebx + 0x12] + this->stream[eax + 0x112];
|
||||||
eax = (ecx >> 8) & 0xFF;
|
eax = (ecx >> 8) & 0xFF;
|
||||||
ebx = ebx ^ stream[eax + 0x212];
|
ebx = ebx ^ this->stream[eax + 0x212];
|
||||||
eax = ecx & 0xFF;
|
eax = ecx & 0xFF;
|
||||||
ebx = ebx + stream[eax + 0x312];
|
ebx = ebx + this->stream[eax + 0x312];
|
||||||
|
|
||||||
for (x = 0; x <= 5; x++) {
|
for (x = 0; x <= 5; x++) {
|
||||||
ebx = ebx ^ stream[(x * 2) + 2];
|
ebx = ebx ^ this->stream[(x * 2) + 2];
|
||||||
esi = esi ^ ebx;
|
esi = esi ^ ebx;
|
||||||
ebx = esi >> 0x18;
|
ebx = esi >> 0x18;
|
||||||
eax = (esi >> 0x10) & 0xFF;
|
eax = (esi >> 0x10) & 0xFF;
|
||||||
ebx = stream[ebx + 0x12] + stream[eax + 0x112];
|
ebx = this->stream[ebx + 0x12] + this->stream[eax + 0x112];
|
||||||
eax = (esi >> 8) & 0xFF;
|
eax = (esi >> 8) & 0xFF;
|
||||||
ebx = ebx ^ stream[eax + 0x212];
|
ebx = ebx ^ this->stream[eax + 0x212];
|
||||||
eax = esi & 0xFF;
|
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;
|
ecx = ecx ^ ebx;
|
||||||
ebx = ecx >> 0x18;
|
ebx = ecx >> 0x18;
|
||||||
eax = (ecx >> 0x10) & 0xFF;
|
eax = (ecx >> 0x10) & 0xFF;
|
||||||
ebx = stream[ebx + 0x12] + stream[eax + 0x112];
|
ebx = this->stream[ebx + 0x12] + this->stream[eax + 0x112];
|
||||||
eax = (ecx >> 8) & 0xFF;
|
eax = (ecx >> 8) & 0xFF;
|
||||||
ebx = ebx ^ stream[eax + 0x212];
|
ebx = ebx ^ this->stream[eax + 0x212];
|
||||||
eax = ecx & 0xFF;
|
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;
|
esi = esi ^ ebx;
|
||||||
eax = esi >> 0x18;
|
eax = esi >> 0x18;
|
||||||
ebx = (esi >> 0x10) & 0xFF;
|
ebx = (esi >> 0x10) & 0xFF;
|
||||||
eax = stream[eax + 0x12] + stream[ebx + 0x112];
|
eax = this->stream[eax + 0x12] + this->stream[ebx + 0x112];
|
||||||
ebx = (esi >> 8) & 0xFF;
|
ebx = (esi >> 8) & 0xFF;
|
||||||
eax = eax ^ stream[ebx + 0x212];
|
eax = eax ^ this->stream[ebx + 0x212];
|
||||||
ebx = esi & 0xFF;
|
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;
|
eax = ecx ^ eax;
|
||||||
ecx = eax >> 0x18;
|
ecx = eax >> 0x18;
|
||||||
ebx = (eax >> 0x10) & 0xFF;
|
ebx = (eax >> 0x10) & 0xFF;
|
||||||
ecx = stream[ecx + 0x12] + stream[ebx + 0x112];
|
ecx = this->stream[ecx + 0x12] + this->stream[ebx + 0x112];
|
||||||
ebx = (eax >> 8) & 0xFF;
|
ebx = (eax >> 8) & 0xFF;
|
||||||
ecx = ecx ^ stream[ebx + 0x212];
|
ecx = ecx ^ this->stream[ebx + 0x212];
|
||||||
ebx = eax & 0xFF;
|
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;
|
ecx = ecx ^ esi;
|
||||||
esi = stream[17];
|
esi = this->stream[17];
|
||||||
esi = esi ^ eax;
|
esi = esi ^ eax;
|
||||||
stream[(ou / 4) + (edi / 4)] = esi;
|
this->stream[(ou / 4) + (edi / 4)] = esi;
|
||||||
stream[(ou / 4) + (edi / 4) + 1] = ecx;
|
this->stream[(ou / 4) + (edi / 4) + 1] = ecx;
|
||||||
edi = edi + 8;
|
edi = edi + 8;
|
||||||
}
|
}
|
||||||
ou = ou + 0x400;
|
ou = ou + 0x400;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return stream;
|
} 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
PSOBBMultiKeyClientEncryption::PSOBBMultiKeyClientEncryption(
|
PSOBBMultiKeyDetectorEncryption::PSOBBMultiKeyDetectorEncryption(
|
||||||
const vector<shared_ptr<const KeyFile>>& possible_keys,
|
const vector<shared_ptr<const PSOBBEncryption::KeyFile>>& possible_keys,
|
||||||
const string& expected_first_data,
|
const string& expected_first_data,
|
||||||
const void* seed,
|
const void* seed,
|
||||||
size_t seed_size)
|
size_t seed_size)
|
||||||
@@ -520,61 +548,74 @@ PSOBBMultiKeyClientEncryption::PSOBBMultiKeyClientEncryption(
|
|||||||
expected_first_data(expected_first_data),
|
expected_first_data(expected_first_data),
|
||||||
seed(reinterpret_cast<const char*>(seed), seed_size) { }
|
seed(reinterpret_cast<const char*>(seed), seed_size) { }
|
||||||
|
|
||||||
void PSOBBMultiKeyClientEncryption::encrypt(void* data, size_t size, bool advance) {
|
void PSOBBMultiKeyDetectorEncryption::encrypt(void* data, size_t size, bool advance) {
|
||||||
if (this->stream.empty()) {
|
if (!this->active_crypt.get()) {
|
||||||
throw logic_error("PSOBB multi-key encryption requires client input first");
|
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) {
|
void PSOBBMultiKeyDetectorEncryption::decrypt(void* data, size_t size, bool advance) {
|
||||||
if (this->stream.empty()) {
|
if (!this->active_crypt.get()) {
|
||||||
if (size != this->expected_first_data.size()) {
|
if (size != this->expected_first_data.size()) {
|
||||||
throw logic_error("initial decryption size does not match expected first data size");
|
throw logic_error("initial decryption size does not match expected first data size");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& key : this->possible_keys) {
|
for (const auto& key : this->possible_keys) {
|
||||||
this->active_key = key;
|
this->active_key = key;
|
||||||
this->stream = PSOBBEncryption::generate_stream(
|
this->active_crypt.reset(new PSOBBEncryption(
|
||||||
*this->active_key.get(), this->seed.data(), this->seed.size());
|
*this->active_key, this->seed.data(), this->seed.size()));
|
||||||
string test_data(reinterpret_cast<const char*>(data), 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) {
|
if (test_data == this->expected_first_data) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this->active_key.reset();
|
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");
|
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,
|
const void* seed,
|
||||||
size_t seed_size)
|
size_t seed_size,
|
||||||
: client_crypt(client_crypt),
|
bool jsd1_use_detector_seed)
|
||||||
seed(reinterpret_cast<const char*>(seed), seed_size) { }
|
: 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) {
|
void PSOBBMultiKeyImitatorEncryption::encrypt(void* data, size_t size, bool advance) {
|
||||||
this->ensure_stream_ready();
|
this->ensure_crypt()->encrypt(data, size, advance);
|
||||||
this->PSOBBEncryption::encrypt(data, size, advance);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PSOBBMultiKeyServerEncryption::decrypt(void* data, size_t size, bool advance) {
|
void PSOBBMultiKeyImitatorEncryption::decrypt(void* data, size_t size, bool advance) {
|
||||||
this->ensure_stream_ready();
|
this->ensure_crypt()->decrypt(data, size, advance);
|
||||||
this->PSOBBEncryption::decrypt(data, size, advance);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PSOBBMultiKeyServerEncryption::ensure_stream_ready() {
|
shared_ptr<PSOBBEncryption> PSOBBMultiKeyImitatorEncryption::ensure_crypt() {
|
||||||
if (this->stream.empty()) {
|
if (!this->active_crypt.get()) {
|
||||||
if (!this->client_crypt->active_key.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");
|
throw logic_error("server crypt cannot be initialized because client crypt is not ready");
|
||||||
}
|
}
|
||||||
this->stream = PSOBBEncryption::generate_stream(
|
// Hack: JSD1 uses the client seed for both ends of the connection and
|
||||||
*this->client_crypt->active_key, this->seed.data(), this->seed.size());
|
// 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
@@ -27,8 +27,6 @@ public:
|
|||||||
this->decrypt(data.data(), data.size(), advance);
|
this->decrypt(data.data(), data.size(), advance);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void skip(size_t size) = 0;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
PSOEncryption() = default;
|
PSOEncryption() = default;
|
||||||
};
|
};
|
||||||
@@ -38,7 +36,6 @@ public:
|
|||||||
explicit PSOPCEncryption(uint32_t seed);
|
explicit PSOPCEncryption(uint32_t seed);
|
||||||
|
|
||||||
virtual void encrypt(void* data, size_t size, bool advance = true);
|
virtual void encrypt(void* data, size_t size, bool advance = true);
|
||||||
virtual void skip(size_t size);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void update_stream();
|
void update_stream();
|
||||||
@@ -53,7 +50,6 @@ public:
|
|||||||
explicit PSOGCEncryption(uint32_t key);
|
explicit PSOGCEncryption(uint32_t key);
|
||||||
|
|
||||||
virtual void encrypt(void* data, size_t size, bool advance = true);
|
virtual void encrypt(void* data, size_t size, bool advance = true);
|
||||||
virtual void skip(size_t size);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void update_stream();
|
void update_stream();
|
||||||
@@ -65,37 +61,39 @@ protected:
|
|||||||
|
|
||||||
class PSOBBEncryption : public PSOEncryption {
|
class PSOBBEncryption : public PSOEncryption {
|
||||||
public:
|
public:
|
||||||
|
enum Subtype : uint8_t {
|
||||||
|
STANDARD = 0x00,
|
||||||
|
MOCB1 = 0x01,
|
||||||
|
JSD1 = 0x02,
|
||||||
|
};
|
||||||
|
|
||||||
struct KeyFile {
|
struct KeyFile {
|
||||||
// initial_keys are actually a stream of uint32_ts, but we treat them as
|
// initial_keys are actually a stream of uint32_ts, but we treat them as
|
||||||
// bytes for code simplicity
|
// bytes for code simplicity
|
||||||
uint8_t initial_keys[0x12 * 4];
|
uint8_t initial_keys[0x12 * 4];
|
||||||
uint32_t private_keys[0x400];
|
uint32_t private_keys[0x400];
|
||||||
uint8_t is_modcrypt;
|
Subtype subtype;
|
||||||
} __attribute__((packed));
|
} __attribute__((packed));
|
||||||
|
|
||||||
PSOBBEncryption(const KeyFile& key, const void* seed, size_t seed_size);
|
PSOBBEncryption(const KeyFile& key, const void* seed, size_t seed_size);
|
||||||
|
|
||||||
virtual void encrypt(void* data, size_t size, bool advance = true);
|
virtual void encrypt(void* data, size_t size, bool advance = true);
|
||||||
virtual void decrypt(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:
|
protected:
|
||||||
PSOBBEncryption();
|
Subtype subtype;
|
||||||
|
|
||||||
static std::vector<uint32_t> generate_stream(
|
|
||||||
const KeyFile& key, const void* seed, size_t seed_size);
|
|
||||||
|
|
||||||
std::vector<uint32_t> stream;
|
std::vector<uint32_t> stream;
|
||||||
|
uint8_t jsd1_stream_offset;
|
||||||
};
|
};
|
||||||
|
|
||||||
// The following classes provide support for multiple PSOBB private keys, and
|
// 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 ability to automatically detect which key the client is using based on
|
||||||
// the first 8 bytes they send.
|
// the first 8 bytes they send.
|
||||||
|
|
||||||
class PSOBBMultiKeyClientEncryption : public PSOBBEncryption {
|
class PSOBBMultiKeyDetectorEncryption : public PSOEncryption {
|
||||||
public:
|
public:
|
||||||
PSOBBMultiKeyClientEncryption(
|
PSOBBMultiKeyDetectorEncryption(
|
||||||
const std::vector<std::shared_ptr<const KeyFile>>& possible_keys,
|
const std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>>& possible_keys,
|
||||||
const std::string& expected_first_data,
|
const std::string& expected_first_data,
|
||||||
const void* seed,
|
const void* seed,
|
||||||
size_t seed_size);
|
size_t seed_size);
|
||||||
@@ -103,28 +101,37 @@ public:
|
|||||||
virtual void encrypt(void* data, size_t size, bool advance = true);
|
virtual void encrypt(void* data, size_t size, bool advance = true);
|
||||||
virtual void decrypt(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:
|
protected:
|
||||||
std::vector<std::shared_ptr<const KeyFile>> possible_keys;
|
std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> possible_keys;
|
||||||
std::shared_ptr<const KeyFile> active_key;
|
std::shared_ptr<const PSOBBEncryption::KeyFile> active_key;
|
||||||
|
std::shared_ptr<PSOBBEncryption> active_crypt;
|
||||||
std::string expected_first_data;
|
std::string expected_first_data;
|
||||||
std::string seed;
|
std::string seed;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PSOBBMultiKeyServerEncryption : public PSOBBEncryption {
|
class PSOBBMultiKeyImitatorEncryption : public PSOEncryption {
|
||||||
public:
|
public:
|
||||||
PSOBBMultiKeyServerEncryption(
|
PSOBBMultiKeyImitatorEncryption(
|
||||||
std::shared_ptr<const PSOBBMultiKeyClientEncryption> client_crypt,
|
std::shared_ptr<const PSOBBMultiKeyDetectorEncryption> client_crypt,
|
||||||
const void* seed,
|
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 encrypt(void* data, size_t size, bool advance = true);
|
||||||
virtual void decrypt(void* data, size_t size, bool advance = true);
|
virtual void decrypt(void* data, size_t size, bool advance = true);
|
||||||
|
|
||||||
protected:
|
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;
|
std::string seed;
|
||||||
|
bool jsd1_use_detector_seed;
|
||||||
};
|
};
|
||||||
|
|||||||
+15
-7
@@ -138,7 +138,7 @@ void for_each_received_command(
|
|||||||
function<void(uint16_t, uint16_t, string&)> fn) {
|
function<void(uint16_t, uint16_t, string&)> fn) {
|
||||||
struct evbuffer* buf = bufferevent_get_input(bev);
|
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 (;;) {
|
for (;;) {
|
||||||
PSOCommandHeader header;
|
PSOCommandHeader header;
|
||||||
if (evbuffer_copyout(buf, &header, header_size)
|
if (evbuffer_copyout(buf, &header, header_size)
|
||||||
@@ -152,18 +152,26 @@ void for_each_received_command(
|
|||||||
|
|
||||||
size_t command_logical_size = header.size(version);
|
size_t command_logical_size = header.size(version);
|
||||||
|
|
||||||
// BB pads commands to 8-byte boundaries, and this is not reflected in the
|
// If encryption is enabled, BB pads commands to 8-byte boundaries, and this
|
||||||
// size field
|
// is not reflected in the size field. This logic does not occur if
|
||||||
size_t command_physical_size = (version == GameVersion::BB)
|
// 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 + header_size - 1) & ~(header_size - 1))
|
||||||
: command_logical_size;
|
: command_logical_size;
|
||||||
if (evbuffer_get_length(buf) < command_physical_size) {
|
if (evbuffer_get_length(buf) < command_physical_size) {
|
||||||
break;
|
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');
|
string command_data(command_physical_size - header_size, '\0');
|
||||||
if (evbuffer_remove(buf, command_data.data(), command_data.size())
|
if (evbuffer_remove(buf, command_data.data(), command_data.size())
|
||||||
@@ -172,7 +180,7 @@ void for_each_received_command(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (crypt) {
|
if (crypt) {
|
||||||
crypt->skip(header_size);
|
crypt->decrypt(header_data.data(), header_data.size());
|
||||||
crypt->decrypt(command_data.data(), command_data.size());
|
crypt->decrypt(command_data.data(), command_data.size());
|
||||||
}
|
}
|
||||||
command_data.resize(command_logical_size - header_size);
|
command_data.resize(command_logical_size - header_size);
|
||||||
|
|||||||
+35
-24
@@ -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,
|
static bool process_server_bb_03(shared_ptr<ServerState>,
|
||||||
ProxyServer::LinkedSession& session, uint16_t command, uint32_t flag, string& data) {
|
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
|
||||||
// Most servers don't include after_message or have a shorter
|
// Most servers don't include after_message or have a shorter
|
||||||
// after_message than newserv does, so don't require it
|
// after_message than newserv does, so don't require it
|
||||||
const auto& cmd = check_size_t<S_ServerInit_BB_03>(data,
|
const auto& cmd = check_size_t<S_ServerInit_BB_03>(data,
|
||||||
offsetof(S_ServerInit_BB_03, after_message), 0xFFFF);
|
offsetof(S_ServerInit_BB_03, after_message), 0xFFFF);
|
||||||
|
|
||||||
// Unlike on PC/GC, BB linked sessions never have licenses.
|
if (!session.detector_crypt.get()) {
|
||||||
// TODO: Is there any way we can support this in the future? Probably not, due
|
throw runtime_error("BB linked session has no detector crypt");
|
||||||
// to BB's login and character select flow, right?
|
}
|
||||||
if (session.license) {
|
if (!session.login_command_bb.username.len()) {
|
||||||
throw runtime_error("BB linked session has license");
|
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
|
// Forward the login command we saved during the unlinked session.
|
||||||
// client will be able to understand it.
|
session.send_to_end(true, 0x93, 0x00, &session.login_command_bb,
|
||||||
forward_command(session, false, command, flag, data);
|
sizeof(session.login_command_bb));
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
return false;
|
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>,
|
static bool process_server_game_19_patch_14(shared_ptr<ServerState>,
|
||||||
ProxyServer::LinkedSession& session, uint16_t command, uint32_t, string& data) {
|
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
|
// This weird maximum size is here to properly handle the version-split
|
||||||
// command that some servers (including newserv) use on port 9100
|
// command that some servers (including newserv) use on port 9100
|
||||||
auto& cmd = check_size_t<S_Reconnect_19>(data, sizeof(S_Reconnect_19), 0xB0);
|
auto& cmd = check_size_t<S_Reconnect_19>(data, sizeof(S_Reconnect_19), 0xB0);
|
||||||
|
|||||||
+135
-50
@@ -106,7 +106,8 @@ void ProxyServer::ListeningSocket::dispatch_on_listen_error(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ProxyServer::ListeningSocket::on_listen_accept(int fd) {
|
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,
|
auto* bev = bufferevent_socket_new(this->server->base.get(), fd,
|
||||||
BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
|
BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
|
||||||
this->server->on_client_connect(bev, this->port, this->version,
|
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;
|
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);
|
this->on_client_connect(bev, server_port, version, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,9 +149,10 @@ void ProxyServer::on_client_connect(
|
|||||||
uint16_t listen_port,
|
uint16_t listen_port,
|
||||||
GameVersion version,
|
GameVersion version,
|
||||||
const struct sockaddr_storage* default_destination) {
|
const struct sockaddr_storage* default_destination) {
|
||||||
// If a default destination exists for this client, create a linked session
|
// If a default destination exists for this client and the client is a patch
|
||||||
// immediately and connect to the remote server. This creates a direct session
|
// client, create a linked session immediately and connect to the remote
|
||||||
if (default_destination) {
|
// server. This creates a direct session.
|
||||||
|
if (default_destination && (version == GameVersion::PATCH)) {
|
||||||
uint64_t session_id = this->next_unlicensed_session_id++;
|
uint64_t session_id = this->next_unlicensed_session_id++;
|
||||||
if (this->next_unlicensed_session_id == 0) {
|
if (this->next_unlicensed_session_id == 0) {
|
||||||
this->next_unlicensed_session_id = 0xFF00000000000001;
|
this->next_unlicensed_session_id = 0xFF00000000000001;
|
||||||
@@ -164,9 +167,9 @@ void ProxyServer::on_client_connect(
|
|||||||
session->log(INFO, "Opened linked session");
|
session->log(INFO, "Opened linked session");
|
||||||
session->resume(bev);
|
session->resume(bev);
|
||||||
|
|
||||||
// If no default destination exists, create an unlinked session - we'll have
|
// If no default destination exists, or the client is not a patch client,
|
||||||
// to get the destination from the client's config, which we'll get via a 9E
|
// create an unlinked session - we'll have to get the destination from the
|
||||||
// command soon
|
// client's config, which we'll get via a 9E command soon.
|
||||||
} else {
|
} else {
|
||||||
auto emplace_ret = this->bev_to_unlinked_session.emplace(bev, new UnlinkedSession(
|
auto emplace_ret = this->bev_to_unlinked_session.emplace(bev, new UnlinkedSession(
|
||||||
this, bev, listen_port, version));
|
this, bev, listen_port, version));
|
||||||
@@ -176,6 +179,12 @@ void ProxyServer::on_client_connect(
|
|||||||
auto session = emplace_ret.first->second;
|
auto session = emplace_ret.first->second;
|
||||||
this->log(INFO, "Opened unlinked session");
|
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) {
|
switch (version) {
|
||||||
case GameVersion::PATCH:
|
case GameVersion::PATCH:
|
||||||
throw logic_error("cannot create unlinked patch session");
|
throw logic_error("cannot create unlinked patch session");
|
||||||
@@ -204,6 +213,31 @@ void ProxyServer::on_client_connect(
|
|||||||
}
|
}
|
||||||
break;
|
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:
|
default:
|
||||||
throw logic_error("unsupported game version on proxy server");
|
throw logic_error("unsupported game version on proxy server");
|
||||||
}
|
}
|
||||||
@@ -219,6 +253,7 @@ ProxyServer::UnlinkedSession::UnlinkedSession(
|
|||||||
bev(bev, flush_and_free_bufferevent),
|
bev(bev, flush_and_free_bufferevent),
|
||||||
local_port(local_port),
|
local_port(local_port),
|
||||||
version(version) {
|
version(version) {
|
||||||
|
memset(&this->next_destination, 0, sizeof(this->next_destination));
|
||||||
bufferevent_setcb(this->bev.get(),
|
bufferevent_setcb(this->bev.get(),
|
||||||
&UnlinkedSession::dispatch_on_client_input, nullptr,
|
&UnlinkedSession::dispatch_on_client_input, nullptr,
|
||||||
&UnlinkedSession::dispatch_on_client_error, this);
|
&UnlinkedSession::dispatch_on_client_error, this);
|
||||||
@@ -241,6 +276,7 @@ void ProxyServer::UnlinkedSession::on_client_input() {
|
|||||||
uint32_t sub_version = 0;
|
uint32_t sub_version = 0;
|
||||||
string character_name;
|
string character_name;
|
||||||
ClientConfigBB client_config;
|
ClientConfigBB client_config;
|
||||||
|
C_Login_BB_93 login_command_bb;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for_each_received_command(this->bev.get(), this->version, this->crypt_in.get(),
|
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;
|
character_name = cmd.name;
|
||||||
client_config.cfg = cmd.client_config.cfg;
|
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 {
|
} else {
|
||||||
throw logic_error("unsupported unlinked session version");
|
throw logic_error("unsupported unlinked session version");
|
||||||
}
|
}
|
||||||
@@ -300,28 +347,43 @@ void ProxyServer::UnlinkedSession::on_client_input() {
|
|||||||
|
|
||||||
} catch (const out_of_range&) {
|
} catch (const out_of_range&) {
|
||||||
// If there's no open session for this license, then there must be a valid
|
// 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
|
// destination somewhere - either in the client config or in the unlinked
|
||||||
// session and set its initial destination
|
// session
|
||||||
if (client_config.cfg.magic != CLIENT_CONFIG_MAGIC) {
|
if (client_config.cfg.magic == CLIENT_CONFIG_MAGIC) {
|
||||||
this->log(ERROR, "Client configuration is invalid; cannot open session");
|
|
||||||
} else {
|
|
||||||
session.reset(new LinkedSession(
|
session.reset(new LinkedSession(
|
||||||
this->server,
|
this->server,
|
||||||
this->local_port,
|
this->local_port,
|
||||||
this->version,
|
this->version,
|
||||||
license,
|
license,
|
||||||
client_config));
|
client_config));
|
||||||
this->server->id_to_session.emplace(license->serial_number, session);
|
session->log(INFO, "Opened licensed session for unlinked session based on client config");
|
||||||
session->log(INFO, "Opened licensed session for unlinked session");
|
} 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)) {
|
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");
|
session->log(ERROR, "Linked session has different game version");
|
||||||
} else {
|
} else {
|
||||||
// Resume the linked session using the unlinked session
|
// Resume the linked session using the unlinked session
|
||||||
try {
|
try {
|
||||||
session->resume(move(this->bev), this->crypt_in, this->crypt_out, sub_version, character_name);
|
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_in.reset();
|
||||||
this->crypt_out.reset();
|
this->crypt_out.reset();
|
||||||
} catch (const exception& e) {
|
} catch (const exception& e) {
|
||||||
@@ -329,6 +391,7 @@ void ProxyServer::UnlinkedSession::on_client_input() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (should_close_unlinked_session) {
|
if (should_close_unlinked_session) {
|
||||||
this->log(INFO, "Closing session");
|
this->log(INFO, "Closing session");
|
||||||
@@ -381,13 +444,14 @@ ProxyServer::LinkedSession::LinkedSession(
|
|||||||
lobby_players(12),
|
lobby_players(12),
|
||||||
lobby_client_id(0) {
|
lobby_client_id(0) {
|
||||||
this->last_switch_enabled_command.subcommand = 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::LinkedSession::LinkedSession(
|
||||||
ProxyServer* server,
|
ProxyServer* server,
|
||||||
uint16_t local_port,
|
uint16_t local_port,
|
||||||
GameVersion version,
|
GameVersion version,
|
||||||
std::shared_ptr<const License> license,
|
shared_ptr<const License> license,
|
||||||
const ClientConfigBB& newserv_client_config)
|
const ClientConfigBB& newserv_client_config)
|
||||||
: LinkedSession(server, license->serial_number, local_port, version) {
|
: LinkedSession(server, license->serial_number, local_port, version) {
|
||||||
this->license = license;
|
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);
|
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::LinkedSession::LinkedSession(
|
||||||
ProxyServer* server,
|
ProxyServer* server,
|
||||||
uint64_t id,
|
uint64_t id,
|
||||||
@@ -410,16 +485,47 @@ ProxyServer::LinkedSession::LinkedSession(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ProxyServer::LinkedSession::resume(
|
void ProxyServer::LinkedSession::resume(
|
||||||
std::unique_ptr<struct bufferevent, void(*)(struct bufferevent*)>&& client_bev,
|
unique_ptr<struct bufferevent, void(*)(struct bufferevent*)>&& client_bev,
|
||||||
std::shared_ptr<PSOEncryption> client_input_crypt,
|
shared_ptr<PSOEncryption> client_input_crypt,
|
||||||
std::shared_ptr<PSOEncryption> client_output_crypt,
|
shared_ptr<PSOEncryption> client_output_crypt,
|
||||||
|
shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
|
||||||
uint32_t sub_version,
|
uint32_t sub_version,
|
||||||
const string& character_name) {
|
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()) {
|
if (this->client_bev.get()) {
|
||||||
throw runtime_error("client connection is already open for this session");
|
throw runtime_error("client connection is already open for this session");
|
||||||
}
|
}
|
||||||
if (this->next_destination.ss_family != AF_INET) {
|
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);
|
this->client_bev = move(client_bev);
|
||||||
@@ -428,10 +534,9 @@ void ProxyServer::LinkedSession::resume(
|
|||||||
&ProxyServer::LinkedSession::dispatch_on_client_error, this);
|
&ProxyServer::LinkedSession::dispatch_on_client_error, this);
|
||||||
bufferevent_enable(this->client_bev.get(), EV_READ | EV_WRITE);
|
bufferevent_enable(this->client_bev.get(), EV_READ | EV_WRITE);
|
||||||
|
|
||||||
|
this->detector_crypt = detector_crypt;
|
||||||
this->client_input_crypt = client_input_crypt;
|
this->client_input_crypt = client_input_crypt;
|
||||||
this->client_output_crypt = client_output_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_input_crypt.reset();
|
||||||
this->server_output_crypt.reset();
|
this->server_output_crypt.reset();
|
||||||
this->saving_files.clear();
|
this->saving_files.clear();
|
||||||
@@ -439,28 +544,6 @@ void ProxyServer::LinkedSession::resume(
|
|||||||
this->connect();
|
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() {
|
void ProxyServer::LinkedSession::connect() {
|
||||||
// Connect to the remote server. The command handlers will do the login steps
|
// Connect to the remote server. The command handlers will do the login steps
|
||||||
// and set up forwarding
|
// and set up forwarding
|
||||||
@@ -496,8 +579,8 @@ void ProxyServer::LinkedSession::connect() {
|
|||||||
|
|
||||||
|
|
||||||
ProxyServer::LinkedSession::SavingFile::SavingFile(
|
ProxyServer::LinkedSession::SavingFile::SavingFile(
|
||||||
const std::string& basename,
|
const string& basename,
|
||||||
const std::string& output_filename,
|
const string& output_filename,
|
||||||
uint32_t remaining_bytes)
|
uint32_t remaining_bytes)
|
||||||
: basename(basename),
|
: basename(basename),
|
||||||
output_filename(output_filename),
|
output_filename(output_filename),
|
||||||
@@ -600,6 +683,8 @@ void ProxyServer::LinkedSession::on_server_input() {
|
|||||||
[&](uint16_t command, uint32_t flag, string& data) {
|
[&](uint16_t command, uint32_t flag, string& data) {
|
||||||
print_received_command(command, flag, data.data(), data.size(),
|
print_received_command(command, flag, data.data(), data.size(),
|
||||||
this->version, this->server_name.c_str(), TerminalFormat::FG_RED);
|
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(
|
process_proxy_command(
|
||||||
this->server->state,
|
this->server->state,
|
||||||
*this,
|
*this,
|
||||||
@@ -676,8 +761,8 @@ shared_ptr<ProxyServer::LinkedSession> ProxyServer::get_session() {
|
|||||||
return this->id_to_session.begin()->second;
|
return this->id_to_session.begin()->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<ProxyServer::LinkedSession> ProxyServer::create_licensed_session(
|
shared_ptr<ProxyServer::LinkedSession> ProxyServer::create_licensed_session(
|
||||||
std::shared_ptr<const License> l, uint16_t local_port, GameVersion version,
|
shared_ptr<const License> l, uint16_t local_port, GameVersion version,
|
||||||
const ClientConfigBB& newserv_client_config) {
|
const ClientConfigBB& newserv_client_config) {
|
||||||
shared_ptr<LinkedSession> session(new LinkedSession(
|
shared_ptr<LinkedSession> session(new LinkedSession(
|
||||||
this, local_port, version, l, newserv_client_config));
|
this, local_port, version, l, newserv_client_config));
|
||||||
|
|||||||
+26
-1
@@ -47,9 +47,13 @@ public:
|
|||||||
std::unique_ptr<struct bufferevent, void(*)(struct bufferevent*)> server_bev;
|
std::unique_ptr<struct bufferevent, void(*)(struct bufferevent*)> server_bev;
|
||||||
uint16_t local_port;
|
uint16_t local_port;
|
||||||
struct sockaddr_storage next_destination;
|
struct sockaddr_storage next_destination;
|
||||||
|
|
||||||
|
uint8_t prev_server_command_bytes[6];
|
||||||
|
|
||||||
GameVersion version;
|
GameVersion version;
|
||||||
uint32_t sub_version;
|
uint32_t sub_version;
|
||||||
std::string character_name;
|
std::string character_name;
|
||||||
|
C_Login_BB_93 login_command_bb;
|
||||||
|
|
||||||
uint32_t remote_guild_card_number;
|
uint32_t remote_guild_card_number;
|
||||||
parray<uint8_t, 0x20> remote_client_config_data;
|
parray<uint8_t, 0x20> remote_client_config_data;
|
||||||
@@ -76,6 +80,7 @@ public:
|
|||||||
std::shared_ptr<PSOEncryption> client_output_crypt;
|
std::shared_ptr<PSOEncryption> client_output_crypt;
|
||||||
std::shared_ptr<PSOEncryption> server_input_crypt;
|
std::shared_ptr<PSOEncryption> server_input_crypt;
|
||||||
std::shared_ptr<PSOEncryption> server_output_crypt;
|
std::shared_ptr<PSOEncryption> server_output_crypt;
|
||||||
|
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt;
|
||||||
|
|
||||||
struct SavingFile {
|
struct SavingFile {
|
||||||
std::string basename;
|
std::string basename;
|
||||||
@@ -102,6 +107,12 @@ public:
|
|||||||
GameVersion version,
|
GameVersion version,
|
||||||
std::shared_ptr<const License> license,
|
std::shared_ptr<const License> license,
|
||||||
const ClientConfigBB& newserv_client_config);
|
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(
|
LinkedSession(
|
||||||
ProxyServer* server,
|
ProxyServer* server,
|
||||||
uint64_t id,
|
uint64_t id,
|
||||||
@@ -113,9 +124,21 @@ public:
|
|||||||
std::unique_ptr<struct bufferevent, void(*)(struct bufferevent*)>&& client_bev,
|
std::unique_ptr<struct bufferevent, void(*)(struct bufferevent*)>&& client_bev,
|
||||||
std::shared_ptr<PSOEncryption> client_input_crypt,
|
std::shared_ptr<PSOEncryption> client_input_crypt,
|
||||||
std::shared_ptr<PSOEncryption> client_output_crypt,
|
std::shared_ptr<PSOEncryption> client_output_crypt,
|
||||||
|
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
|
||||||
uint32_t sub_version,
|
uint32_t sub_version,
|
||||||
const std::string& character_name);
|
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();
|
void connect();
|
||||||
|
|
||||||
static void dispatch_on_client_input(struct bufferevent* bev, void* ctx);
|
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;
|
std::unique_ptr<struct bufferevent, void(*)(struct bufferevent*)> bev;
|
||||||
uint16_t local_port;
|
uint16_t local_port;
|
||||||
GameVersion version;
|
GameVersion version;
|
||||||
|
struct sockaddr_storage next_destination;
|
||||||
|
|
||||||
std::shared_ptr<PSOEncryption> crypt_out;
|
std::shared_ptr<PSOEncryption> crypt_out;
|
||||||
std::shared_ptr<PSOEncryption> crypt_in;
|
std::shared_ptr<PSOEncryption> crypt_in;
|
||||||
|
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt;
|
||||||
|
|
||||||
UnlinkedSession(ProxyServer* server, struct bufferevent* bev, uint16_t port, GameVersion version);
|
UnlinkedSession(ProxyServer* server, struct bufferevent* bev, uint16_t port, GameVersion version);
|
||||||
|
|
||||||
|
|||||||
+30
-10
@@ -35,6 +35,7 @@ void send_command(
|
|||||||
size_t size,
|
size_t size,
|
||||||
const char* name_str) {
|
const char* name_str) {
|
||||||
string send_data;
|
string send_data;
|
||||||
|
size_t logical_size;
|
||||||
|
|
||||||
switch (version) {
|
switch (version) {
|
||||||
case GameVersion::GC:
|
case GameVersion::GC:
|
||||||
@@ -46,8 +47,9 @@ void send_command(
|
|||||||
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
|
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
|
||||||
if (size) {
|
if (size) {
|
||||||
send_data.append(reinterpret_cast<const char*>(data), 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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,8 +62,9 @@ void send_command(
|
|||||||
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
|
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
|
||||||
if (size) {
|
if (size) {
|
||||||
send_data.append(reinterpret_cast<const char*>(data), 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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,8 +79,13 @@ void send_command(
|
|||||||
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
|
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
|
||||||
if (size) {
|
if (size) {
|
||||||
send_data.append(reinterpret_cast<const char*>(data), 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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +103,7 @@ void send_command(
|
|||||||
}
|
}
|
||||||
log(INFO, "Sending%s (version=%s command=%04hX flag=%08X)",
|
log(INFO, "Sending%s (version=%s command=%04hX flag=%08X)",
|
||||||
name_token.c_str(), name_for_version(version), command, flag);
|
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) {
|
if (use_terminal_colors) {
|
||||||
print_color_escape(stderr, TerminalFormat::NORMAL, TerminalFormat::END);
|
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;
|
S_ServerInit_BB_03 cmd;
|
||||||
cmd.copyright = bb_game_server_copyright;
|
cmd.copyright = bb_game_server_copyright;
|
||||||
random_data(cmd.server_key.data(), cmd.server_key.bytes());
|
cmd.server_key = server_key;
|
||||||
random_data(cmd.client_key.data(), cmd.client_key.bytes());
|
cmd.client_key = client_key;
|
||||||
cmd.after_message = anti_copyright;
|
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);
|
send_command_t(c, 0x03, 0x00, cmd);
|
||||||
|
|
||||||
static const string expected_first_data("\xB4\x00\x93\x00\x00\x00\x00\x00", 8);
|
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)));
|
s->bb_private_keys, expected_first_data, cmd.client_key.data(), sizeof(cmd.client_key)));
|
||||||
c->crypt_in = client_encr;
|
c->crypt_in = detector_crypt;
|
||||||
c->crypt_out.reset(new PSOBBMultiKeyServerEncryption(client_encr, cmd.server_key.data(), sizeof(cmd.server_key)));
|
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) {
|
void send_server_init_patch(shared_ptr<Client> c) {
|
||||||
|
|||||||
@@ -94,6 +94,9 @@ S_ServerInit_DC_PC_GC_02_17 prepare_server_init_contents_dc_pc_gc(
|
|||||||
bool initial_connection,
|
bool initial_connection,
|
||||||
uint32_t server_key,
|
uint32_t server_key,
|
||||||
uint32_t client_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,
|
void send_server_init(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c,
|
||||||
bool initial_connection);
|
bool initial_connection);
|
||||||
void send_update_client_config(std::shared_ptr<Client> c);
|
void send_update_client_config(std::shared_ptr<Client> c);
|
||||||
|
|||||||
Reference in New Issue
Block a user