add download quest conversion function
This commit is contained in:
+107
-1
@@ -184,7 +184,7 @@ string prs_decompress(const string& data, size_t max_size) {
|
|||||||
}
|
}
|
||||||
output += static_cast<char>(ch);
|
output += static_cast<char>(ch);
|
||||||
if (max_size && (output.size() > max_size)) {
|
if (max_size && (output.size() > max_size)) {
|
||||||
throw runtime_error("maximumoutput size exceeded");
|
throw runtime_error("maximum output size exceeded");
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -258,3 +258,109 @@ string prs_decompress(const string& data, size_t max_size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t prs_decompress_size(const string& data, size_t max_size) {
|
||||||
|
size_t output_size = 0;
|
||||||
|
StringReader r(data.data(), data.size());
|
||||||
|
|
||||||
|
int32_t r3, r5;
|
||||||
|
int bitpos = 9;
|
||||||
|
int16_t currentbyte; // int16_t because it can be -1 when EOF occurs
|
||||||
|
int flag;
|
||||||
|
int offset;
|
||||||
|
unsigned long x;
|
||||||
|
|
||||||
|
currentbyte = get_u8_or_eof(r);
|
||||||
|
if (currentbyte == EOF) {
|
||||||
|
return output_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
bitpos--;
|
||||||
|
if (bitpos == 0) {
|
||||||
|
currentbyte = get_u8_or_eof(r);
|
||||||
|
if (currentbyte == EOF) {
|
||||||
|
return output_size;
|
||||||
|
}
|
||||||
|
bitpos = 8;
|
||||||
|
}
|
||||||
|
flag = currentbyte & 1;
|
||||||
|
currentbyte = currentbyte >> 1;
|
||||||
|
if (flag) {
|
||||||
|
int ch = get_u8_or_eof(r);
|
||||||
|
if (ch == EOF) {
|
||||||
|
return output_size;
|
||||||
|
}
|
||||||
|
output_size++;
|
||||||
|
if (max_size && (output_size > max_size)) {
|
||||||
|
throw runtime_error("maximum output size exceeded");
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bitpos--;
|
||||||
|
if (bitpos == 0) {
|
||||||
|
currentbyte = get_u8_or_eof(r);
|
||||||
|
if (currentbyte == EOF) {
|
||||||
|
return output_size;
|
||||||
|
}
|
||||||
|
bitpos = 8;
|
||||||
|
}
|
||||||
|
flag = currentbyte & 1;
|
||||||
|
currentbyte = currentbyte >> 1;
|
||||||
|
if (flag) {
|
||||||
|
r3 = get_u8_or_eof(r);
|
||||||
|
if (r3 == EOF) {
|
||||||
|
return output_size;
|
||||||
|
}
|
||||||
|
int high_byte = get_u8_or_eof(r);
|
||||||
|
if (high_byte == EOF) {
|
||||||
|
return output_size;
|
||||||
|
}
|
||||||
|
offset = ((high_byte & 0xFF) << 8) | (r3 & 0xFF);
|
||||||
|
if (offset == 0) {
|
||||||
|
return output_size;
|
||||||
|
}
|
||||||
|
r3 = r3 & 0x00000007;
|
||||||
|
r5 = (offset >> 3) | 0xFFFFE000;
|
||||||
|
if (r3 == 0) {
|
||||||
|
flag = 0;
|
||||||
|
r3 = get_u8_or_eof(r);
|
||||||
|
if (r3 == EOF) {
|
||||||
|
return output_size;
|
||||||
|
}
|
||||||
|
r3 = (r3 & 0xFF) + 1;
|
||||||
|
} else {
|
||||||
|
r3 += 2;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
r3 = 0;
|
||||||
|
for (x = 0; x < 2; x++) {
|
||||||
|
bitpos--;
|
||||||
|
if (bitpos == 0) {
|
||||||
|
currentbyte = get_u8_or_eof(r);
|
||||||
|
if (currentbyte == EOF) {
|
||||||
|
return output_size;
|
||||||
|
}
|
||||||
|
bitpos = 8;
|
||||||
|
}
|
||||||
|
flag = currentbyte & 1;
|
||||||
|
currentbyte = currentbyte >> 1;
|
||||||
|
offset = r3 << 1;
|
||||||
|
r3 = offset | flag;
|
||||||
|
}
|
||||||
|
offset = get_u8_or_eof(r);
|
||||||
|
if (offset == EOF) {
|
||||||
|
return output_size;
|
||||||
|
}
|
||||||
|
r3 += 2;
|
||||||
|
r5 = offset | 0xFFFFFF00;
|
||||||
|
}
|
||||||
|
if (r3 == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
output_size += r3;
|
||||||
|
if (max_size && (output_size > max_size)) {
|
||||||
|
throw runtime_error("maximum output size exceeded");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,3 +8,4 @@
|
|||||||
|
|
||||||
std::string prs_compress(const std::string& data);
|
std::string prs_compress(const std::string& data);
|
||||||
std::string prs_decompress(const std::string& data, size_t max_size = 0);
|
std::string prs_decompress(const std::string& data, size_t max_size = 0);
|
||||||
|
size_t prs_decompress_size(const std::string& data, size_t max_size = 0);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <phosg/Strings.hh>
|
#include <phosg/Strings.hh>
|
||||||
|
|
||||||
#include "Compression.hh"
|
#include "Compression.hh"
|
||||||
|
#include "PSOEncryption.hh"
|
||||||
#include "Text.hh"
|
#include "Text.hh"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
@@ -56,8 +57,8 @@ struct PSOQuestHeaderDC { // same for dc v1 and v2, thankfully
|
|||||||
uint32_t unknown_offset1;
|
uint32_t unknown_offset1;
|
||||||
uint32_t size;
|
uint32_t size;
|
||||||
uint32_t unused;
|
uint32_t unused;
|
||||||
|
uint8_t is_download;
|
||||||
uint8_t unknown1;
|
uint8_t unknown1;
|
||||||
uint8_t unknown2;
|
|
||||||
uint16_t quest_number; // 0xFFFF for challenge quests
|
uint16_t quest_number; // 0xFFFF for challenge quests
|
||||||
char name[0x20];
|
char name[0x20];
|
||||||
char short_description[0x80];
|
char short_description[0x80];
|
||||||
@@ -69,8 +70,8 @@ struct PSOQuestHeaderPC {
|
|||||||
uint32_t unknown_offset1;
|
uint32_t unknown_offset1;
|
||||||
uint32_t size;
|
uint32_t size;
|
||||||
uint32_t unused;
|
uint32_t unused;
|
||||||
|
uint8_t is_download;
|
||||||
uint8_t unknown1;
|
uint8_t unknown1;
|
||||||
uint8_t unknown2;
|
|
||||||
uint16_t quest_number; // 0xFFFF for challenge quests
|
uint16_t quest_number; // 0xFFFF for challenge quests
|
||||||
char16_t name[0x20];
|
char16_t name[0x20];
|
||||||
char16_t short_description[0x80];
|
char16_t short_description[0x80];
|
||||||
@@ -82,7 +83,8 @@ struct PSOQuestHeaderGC {
|
|||||||
uint32_t unknown_offset1;
|
uint32_t unknown_offset1;
|
||||||
uint32_t size;
|
uint32_t size;
|
||||||
uint32_t unused;
|
uint32_t unused;
|
||||||
uint16_t unknown1;
|
uint8_t is_download;
|
||||||
|
uint8_t unknown1;
|
||||||
uint8_t quest_number;
|
uint8_t quest_number;
|
||||||
uint8_t episode; // 1 = ep2. apparently some quests have 0xFF here, which means ep1 (?)
|
uint8_t episode; // 1 = ep2. apparently some quests have 0xFF here, which means ep1 (?)
|
||||||
char name[0x20];
|
char name[0x20];
|
||||||
@@ -362,3 +364,76 @@ vector<shared_ptr<const Quest>> QuestIndex::filter(GameVersion version,
|
|||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static string create_download_quest_file(const string& compressed_data,
|
||||||
|
size_t decompressed_size) {
|
||||||
|
struct PSODownloadQuestHeader {
|
||||||
|
uint32_t decompressed_size;
|
||||||
|
uint32_t encryption_seed; // note: use PC encryption, even for GC quests
|
||||||
|
};
|
||||||
|
|
||||||
|
string data(8, '\0');
|
||||||
|
auto* header = reinterpret_cast<PSODownloadQuestHeader*>(const_cast<char*>(
|
||||||
|
compressed_data.data()));
|
||||||
|
header->decompressed_size = decompressed_size + sizeof(PSODownloadQuestHeader);
|
||||||
|
header->encryption_seed = random_object<uint32_t>();
|
||||||
|
data += compressed_data;
|
||||||
|
|
||||||
|
// add extra bytes if necessary so encryption won't fail
|
||||||
|
data.resize((data.size() + 3) & (~3));
|
||||||
|
|
||||||
|
// TODO: for DC quests, do we use DC encryption?
|
||||||
|
PSOPCEncryption encr(header->encryption_seed);
|
||||||
|
encr.encrypt(const_cast<char*>(data.data() + sizeof(PSODownloadQuestHeader)),
|
||||||
|
data.size() - sizeof(PSODownloadQuestHeader));
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
shared_ptr<Quest> Quest::create_download_quest(const string& file_basename) const {
|
||||||
|
if (this->category == QuestCategory::Download) {
|
||||||
|
throw invalid_argument("quest is already a download quest");
|
||||||
|
}
|
||||||
|
|
||||||
|
string decompressed_bin = prs_decompress(*this->bin_contents());
|
||||||
|
void* data_ptr = const_cast<char*>(decompressed_bin.data());
|
||||||
|
switch (this->version) {
|
||||||
|
case GameVersion::DC:
|
||||||
|
reinterpret_cast<PSOQuestHeaderDC*>(data_ptr)->is_download = 0x01;
|
||||||
|
break;
|
||||||
|
case GameVersion::PC:
|
||||||
|
reinterpret_cast<PSOQuestHeaderDC*>(data_ptr)->is_download = 0x01;
|
||||||
|
break;
|
||||||
|
case GameVersion::GC:
|
||||||
|
reinterpret_cast<PSOQuestHeaderDC*>(data_ptr)->is_download = 0x01;
|
||||||
|
break;
|
||||||
|
case GameVersion::BB:
|
||||||
|
throw invalid_argument("PSOBB does not support download quests");
|
||||||
|
default:
|
||||||
|
throw invalid_argument("unknown game version");
|
||||||
|
}
|
||||||
|
|
||||||
|
shared_ptr<Quest> dlq(new Quest(file_basename));
|
||||||
|
dlq->quest_id = this->quest_id;
|
||||||
|
dlq->category = QuestCategory::Download;
|
||||||
|
dlq->episode = this->episode;
|
||||||
|
dlq->is_dcv1 = this->is_dcv1;
|
||||||
|
dlq->joinable = this->joinable;
|
||||||
|
dlq->version = this->version;
|
||||||
|
dlq->name = this->name;
|
||||||
|
dlq->short_description = this->short_description;
|
||||||
|
dlq->long_description = this->long_description;
|
||||||
|
|
||||||
|
dlq->bin_contents_ptr.reset(new string(create_download_quest_file(
|
||||||
|
prs_compress(decompressed_bin), decompressed_bin.size())));
|
||||||
|
|
||||||
|
auto dat_contents = this->dat_contents();
|
||||||
|
dlq->dat_contents_ptr.reset(new string(create_download_quest_file(
|
||||||
|
*dat_contents, prs_decompress_size(*dat_contents))));
|
||||||
|
|
||||||
|
save_file(dlq->bin_filename(), *dlq->bin_contents_ptr);
|
||||||
|
save_file(dlq->dat_filename(), *dlq->dat_contents_ptr);
|
||||||
|
|
||||||
|
return dlq;
|
||||||
|
}
|
||||||
|
|||||||
@@ -53,6 +53,9 @@ struct Quest {
|
|||||||
|
|
||||||
std::shared_ptr<const std::string> bin_contents() const;
|
std::shared_ptr<const std::string> bin_contents() const;
|
||||||
std::shared_ptr<const std::string> dat_contents() const;
|
std::shared_ptr<const std::string> dat_contents() const;
|
||||||
|
|
||||||
|
std::shared_ptr<Quest> create_download_quest(
|
||||||
|
const std::string& file_basename) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct QuestIndex {
|
struct QuestIndex {
|
||||||
|
|||||||
Reference in New Issue
Block a user