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;
|
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;
|
ssize_t read_offset = 0;
|
||||||
while (read_offset < data_ssize) {
|
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
|
while ((this_size < 0x100) && // max copy size is 255 bytes
|
||||||
((this_offset + this_size) < 0) && // don't copy past the read offset
|
((this_offset + this_size) < 0) && // don't copy past the read offset
|
||||||
(this_size <= data_ssize - read_offset) && // don't copy past the end
|
(this_size <= data_ssize - read_offset) && // don't copy past the end
|
||||||
!memcmp(data.data() + read_offset + this_offset,
|
!memcmp(data + read_offset + this_offset, data + read_offset,
|
||||||
data.data() + read_offset, this_size)) {
|
this_size)) {
|
||||||
this_size++;
|
this_size++;
|
||||||
}
|
}
|
||||||
this_size--;
|
this_size--;
|
||||||
@@ -143,6 +144,10 @@ string prs_compress(const string& data) {
|
|||||||
return pc.finish();
|
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) {
|
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_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);
|
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)) {
|
for (const auto& filename : list_directory(directory)) {
|
||||||
if (ends_with(filename, ".mnm")) {
|
try {
|
||||||
try {
|
shared_ptr<MapEntry> entry;
|
||||||
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()));
|
|
||||||
}
|
|
||||||
|
|
||||||
shared_ptr<MapEntry> entry(new MapEntry(
|
if (ends_with(filename, ".mnmd")) {
|
||||||
{*reinterpret_cast<const Ep3Map*>(data.data()), move(compressed_data)}));
|
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) {
|
if (!this->maps.emplace(entry->map.map_number, entry).second) {
|
||||||
throw runtime_error("duplicate map number");
|
throw runtime_error("duplicate map number");
|
||||||
}
|
}
|
||||||
string name = entry->map.name;
|
string name = entry->map.name;
|
||||||
log(INFO, "Indexed Episode 3 map %s (%08" PRIX32 "; %s)",
|
log(INFO, "Indexed Episode 3 map %s (%08" PRIX32 "; %s)",
|
||||||
filename.c_str(), entry->map.map_number.load(), name.c_str());
|
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 {
|
const string& Ep3DataIndex::get_compressed_card_definitions() const {
|
||||||
if (this->compressed_card_definitions.empty()) {
|
if (this->compressed_card_definitions.empty()) {
|
||||||
throw runtime_error("card definitions are not available");
|
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;
|
ptext<char, 0x10> name;
|
||||||
parray<be_uint16_t, 0x7E> unknown_a3;
|
parray<be_uint16_t, 0x7E> unknown_a3;
|
||||||
} __attribute__((packed));
|
} __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?
|
/* 242C */ parray<uint8_t, 0x14> unknown_a8; // Always FF?
|
||||||
/* 2440 */ ptext<char, 0x190> before_message;
|
/* 2440 */ ptext<char, 0x190> before_message;
|
||||||
/* 25D0 */ ptext<char, 0x190> after_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?
|
be_uint16_t unknown_a2; // Always 0x0064 if valid, 0xFFFF if unused?
|
||||||
ptext<char, 0x40> strings[4];
|
ptext<char, 0x40> strings[4];
|
||||||
} __attribute__((packed)); // Total size: 0x104 bytes
|
} __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
|
/* 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;
|
/* 59B2 */ parray<be_uint16_t, 0x33> unknown_a9;
|
||||||
/* 5A18 */
|
/* 5A18 */
|
||||||
@@ -320,9 +320,17 @@ public:
|
|||||||
std::vector<std::string> text;
|
std::vector<std::string> text;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MapEntry {
|
class MapEntry {
|
||||||
|
public:
|
||||||
Ep3Map map;
|
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;
|
const std::string& get_compressed_card_definitions() const;
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
#include <resource_file/Emulators/PPC32Emulator.hh>
|
#include <resource_file/Emulators/PPC32Emulator.hh>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "Compression.hh"
|
||||||
#include "PSOProtocol.hh"
|
#include "PSOProtocol.hh"
|
||||||
#include "SendCommands.hh"
|
#include "SendCommands.hh"
|
||||||
#include "ReceiveCommands.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)) {
|
if ((session.version == GameVersion::GC) && (data.size() >= 0x14)) {
|
||||||
PSOSubcommand* subs = &check_size_t<PSOSubcommand>(data, 0x14, 0xFFFF);
|
PSOSubcommand* subs = &check_size_t<PSOSubcommand>(data, 0x14, 0xFFFF);
|
||||||
if (subs[0].dword == 0x000000B6 && subs[2].dword == 0x00000041) {
|
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());
|
subs[3].dword.load(), now());
|
||||||
string file_data = data.substr(0x0C);
|
string map_data = prs_decompress(data.substr(0x14));
|
||||||
save_file(filename, file_data);
|
save_file(filename, map_data);
|
||||||
session.log(INFO, "Wrote %zu bytes to %s", file_data.size(), filename.c_str());
|
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
|
// 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) {
|
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);
|
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());
|
PSOSubcommand* subs = reinterpret_cast<PSOSubcommand*>(data.data());
|
||||||
subs[0].dword = 0x000000B6;
|
subs[0].dword = 0x000000B6;
|
||||||
subs[1].dword = (19 + entry->compressed_data.size()) & 0xFFFFFFFC;
|
subs[1].dword = (19 + compressed.size()) & 0xFFFFFFFC;
|
||||||
subs[2].dword = 0x00000041;
|
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);
|
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 |