support uncompressed episode 3 maps
@@ -96,10 +96,11 @@ struct prs_compress_ctx {
|
||||
}
|
||||
};
|
||||
|
||||
string prs_compress(const string& data) {
|
||||
string prs_compress(const void* vdata, size_t size) {
|
||||
const uint8_t* data = reinterpret_cast<const uint8_t*>(vdata);
|
||||
prs_compress_ctx pc;
|
||||
|
||||
ssize_t data_ssize = static_cast<ssize_t>(data.size());
|
||||
ssize_t data_ssize = static_cast<ssize_t>(size);
|
||||
ssize_t read_offset = 0;
|
||||
while (read_offset < data_ssize) {
|
||||
|
||||
@@ -117,8 +118,8 @@ string prs_compress(const string& data) {
|
||||
while ((this_size < 0x100) && // max copy size is 255 bytes
|
||||
((this_offset + this_size) < 0) && // don't copy past the read offset
|
||||
(this_size <= data_ssize - read_offset) && // don't copy past the end
|
||||
!memcmp(data.data() + read_offset + this_offset,
|
||||
data.data() + read_offset, this_size)) {
|
||||
!memcmp(data + read_offset + this_offset, data + read_offset,
|
||||
this_size)) {
|
||||
this_size++;
|
||||
}
|
||||
this_size--;
|
||||
@@ -143,6 +144,10 @@ string prs_compress(const string& data) {
|
||||
return pc.finish();
|
||||
}
|
||||
|
||||
string prs_compress(const string& data) {
|
||||
return prs_compress(data.data(), data.size());
|
||||
}
|
||||
|
||||
|
||||
|
||||
static int16_t get_u8_or_eof(StringReader& r) {
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
|
||||
|
||||
std::string prs_compress(const void* vdata, size_t size);
|
||||
std::string prs_compress(const std::string& data);
|
||||
|
||||
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);
|
||||
|
||||
@@ -774,36 +774,51 @@ Ep3DataIndex::Ep3DataIndex(const string& directory) {
|
||||
}
|
||||
|
||||
for (const auto& filename : list_directory(directory)) {
|
||||
if (ends_with(filename, ".mnm")) {
|
||||
try {
|
||||
string compressed_data = load_file(directory + "/" + filename);
|
||||
// There's a small header (Ep3CompressedMapHeader) before the compressed
|
||||
// data, which we ignore
|
||||
string data_to_decompress = compressed_data.substr(8);
|
||||
string data = prs_decompress(data_to_decompress);
|
||||
if (data.size() != sizeof(Ep3Map)) {
|
||||
throw runtime_error(string_printf(
|
||||
"decompressed data size is incorrect (expected %zu bytes, read %zu bytes)",
|
||||
sizeof(Ep3Map), data.size()));
|
||||
}
|
||||
try {
|
||||
shared_ptr<MapEntry> entry;
|
||||
|
||||
shared_ptr<MapEntry> entry(new MapEntry(
|
||||
{*reinterpret_cast<const Ep3Map*>(data.data()), move(compressed_data)}));
|
||||
if (ends_with(filename, ".mnmd")) {
|
||||
entry.reset(new MapEntry(load_object_file<Ep3Map>(directory + "/" + filename)));
|
||||
} else if (ends_with(filename, ".mnm")) {
|
||||
entry.reset(new MapEntry(load_file(directory + "/" + filename)));
|
||||
}
|
||||
|
||||
if (entry.get()) {
|
||||
if (!this->maps.emplace(entry->map.map_number, entry).second) {
|
||||
throw runtime_error("duplicate map number");
|
||||
}
|
||||
string name = entry->map.name;
|
||||
log(INFO, "Indexed Episode 3 map %s (%08" PRIX32 "; %s)",
|
||||
filename.c_str(), entry->map.map_number.load(), name.c_str());
|
||||
|
||||
} catch (const exception& e) {
|
||||
log(WARNING, "Failed to index Episode 3 map %s: %s",
|
||||
filename.c_str(), e.what());
|
||||
}
|
||||
|
||||
} catch (const exception& e) {
|
||||
log(WARNING, "Failed to index Episode 3 map %s: %s",
|
||||
filename.c_str(), e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ep3DataIndex::MapEntry::MapEntry(const Ep3Map& map) : map(map) { }
|
||||
|
||||
Ep3DataIndex::MapEntry::MapEntry(const string& compressed)
|
||||
: compressed_data(compressed) {
|
||||
string decompressed = prs_decompress(this->compressed_data);
|
||||
if (decompressed.size() != sizeof(Ep3Map)) {
|
||||
throw runtime_error(string_printf(
|
||||
"decompressed data size is incorrect (expected %zu bytes, read %zu bytes)",
|
||||
sizeof(Ep3Map), decompressed.size()));
|
||||
}
|
||||
this->map = *reinterpret_cast<const Ep3Map*>(decompressed.data());
|
||||
}
|
||||
|
||||
string Ep3DataIndex::MapEntry::compressed() const {
|
||||
if (this->compressed_data.empty()) {
|
||||
this->compressed_data = prs_compress(&this->map, sizeof(this->map));
|
||||
}
|
||||
return this->compressed_data;
|
||||
}
|
||||
|
||||
const string& Ep3DataIndex::get_compressed_card_definitions() const {
|
||||
if (this->compressed_card_definitions.empty()) {
|
||||
throw runtime_error("card definitions are not available");
|
||||
|
||||
@@ -295,7 +295,7 @@ struct Ep3Map { // .mnm format (after decompressing and discarding the header)
|
||||
ptext<char, 0x10> name;
|
||||
parray<be_uint16_t, 0x7E> unknown_a3;
|
||||
} __attribute__((packed));
|
||||
/* 20F0 */ parray<NPCCharacter, 3> npc_chars; // Unused if name[0] == 0
|
||||
/* 20F0 */ NPCCharacter npc_chars[3]; // Unused if name[0] == 0
|
||||
/* 242C */ parray<uint8_t, 0x14> unknown_a8; // Always FF?
|
||||
/* 2440 */ ptext<char, 0x190> before_message;
|
||||
/* 25D0 */ ptext<char, 0x190> after_message;
|
||||
@@ -305,7 +305,7 @@ struct Ep3Map { // .mnm format (after decompressing and discarding the header)
|
||||
be_uint16_t unknown_a2; // Always 0x0064 if valid, 0xFFFF if unused?
|
||||
ptext<char, 0x40> strings[4];
|
||||
} __attribute__((packed)); // Total size: 0x104 bytes
|
||||
/* 28F0 */ parray<DialogueSet, 0x10> dialogue_sets[3]; // Up to 0x10 per valid NPC
|
||||
/* 28F0 */ DialogueSet dialogue_sets[3][0x10]; // Up to 0x10 per valid NPC
|
||||
/* 59B0 */ be_uint16_t reward_card_id; // TODO: This could be an array. The only examples I've seen have only one here
|
||||
/* 59B2 */ parray<be_uint16_t, 0x33> unknown_a9;
|
||||
/* 5A18 */
|
||||
@@ -320,9 +320,17 @@ public:
|
||||
std::vector<std::string> text;
|
||||
};
|
||||
|
||||
struct MapEntry {
|
||||
class MapEntry {
|
||||
public:
|
||||
Ep3Map map;
|
||||
std::string compressed_data;
|
||||
|
||||
MapEntry(const Ep3Map& map);
|
||||
MapEntry(const std::string& compressed_data);
|
||||
|
||||
std::string compressed() const;
|
||||
|
||||
private:
|
||||
mutable std::string compressed_data;
|
||||
};
|
||||
|
||||
const std::string& get_compressed_card_definitions() const;
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <resource_file/Emulators/PPC32Emulator.hh>
|
||||
#endif
|
||||
|
||||
#include "Compression.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "SendCommands.hh"
|
||||
#include "ReceiveCommands.hh"
|
||||
@@ -637,11 +638,11 @@ static bool process_server_60_62_6C_6D_C9_CB(shared_ptr<ServerState>,
|
||||
if ((session.version == GameVersion::GC) && (data.size() >= 0x14)) {
|
||||
PSOSubcommand* subs = &check_size_t<PSOSubcommand>(data, 0x14, 0xFFFF);
|
||||
if (subs[0].dword == 0x000000B6 && subs[2].dword == 0x00000041) {
|
||||
string filename = string_printf("map%08" PRIX32 ".%" PRIu64 ".mnm",
|
||||
string filename = string_printf("map%08" PRIX32 ".%" PRIu64 ".mnmd",
|
||||
subs[3].dword.load(), now());
|
||||
string file_data = data.substr(0x0C);
|
||||
save_file(filename, file_data);
|
||||
session.log(INFO, "Wrote %zu bytes to %s", file_data.size(), filename.c_str());
|
||||
string map_data = prs_decompress(data.substr(0x14));
|
||||
save_file(filename, map_data);
|
||||
session.log(INFO, "Wrote %zu bytes to %s", map_data.size(), filename.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1399,13 +1399,19 @@ void send_ep3_map_list(shared_ptr<ServerState> s, shared_ptr<Lobby> l) {
|
||||
// sends the map data for the chosen map to all players in the game
|
||||
void send_ep3_map_data(shared_ptr<ServerState> s, shared_ptr<Lobby> l, uint32_t map_id) {
|
||||
auto entry = s->ep3_data_index->get_map(map_id);
|
||||
const auto& compressed = entry->compressed();
|
||||
|
||||
string data(12, '\0');
|
||||
string data(0x14, '\0');
|
||||
PSOSubcommand* subs = reinterpret_cast<PSOSubcommand*>(data.data());
|
||||
subs[0].dword = 0x000000B6;
|
||||
subs[1].dword = (19 + entry->compressed_data.size()) & 0xFFFFFFFC;
|
||||
subs[1].dword = (19 + compressed.size()) & 0xFFFFFFFC;
|
||||
subs[2].dword = 0x00000041;
|
||||
data += entry->compressed_data;
|
||||
subs[3].dword = entry->map.map_number.load();
|
||||
subs[4].dword = compressed.size();
|
||||
data += compressed;
|
||||
while (data.size() & 3) {
|
||||
data.push_back('\0');
|
||||
}
|
||||
|
||||
send_command(l, 0x6C, 0x00, data);
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 22 KiB |