#include "Map.hh" #include #include #include "Loggers.hh" #include "PSOEncryption.hh" #include "StaticGameData.hh" using namespace std; Map::Enemy::Enemy(EnemyType type) : type(type), flags(0), last_hit_by_client_id(0) {} string Map::Enemy::str() const { return string_printf("[Map::Enemy %s flags=%02hhX last_hit_by_client_id=%hu]", name_for_enum(this->type), this->flags, this->last_hit_by_client_id); } struct EnemyEntry { /* 00 */ le_uint16_t base_type; /* 02 */ le_uint16_t unknown_a0; // Overwritten by client at load time /* 04 */ le_uint16_t enemy_index; // Overwritten by client at load time /* 06 */ le_uint16_t num_clones; /* 08 */ le_uint16_t area; /* 0A */ le_uint16_t entity_id; // == enemy_index + 0x1000 /* 0C */ le_uint16_t section; /* 0E */ le_uint16_t wave_number; /* 10 */ le_uint32_t wave_number2; /* 14 */ le_float x; /* 18 */ le_float y; /* 1C */ le_float z; /* 20 */ le_uint32_t x_angle; /* 24 */ le_uint32_t y_angle; /* 28 */ le_uint32_t z_angle; /* 2C */ le_uint32_t unknown_a3; /* 30 */ le_uint32_t unknown_a4; /* 34 */ le_uint32_t unknown_a5; /* 38 */ le_uint32_t unknown_a6; /* 3C */ le_uint32_t unknown_a7; /* 40 */ le_uint32_t skin; /* 44 */ le_uint32_t unknown_a8; /* 48 */ string str() const { return string_printf("EnemyEntry(base_type=%hX, a0=%hX, enemy_index=%hX, num_clones=%hX, area=%hX, entity_id=%hX, section=%hX, wave_number=%hX, wave_number2=%" PRIX32 ", x=%g, y=%g, z=%g, x_angle=%" PRIX32 ", y_angle=%" PRIX32 ", z_angle=%" PRIX32 ", a3=%" PRIX32 ", a4=%" PRIX32 ", a5=%" PRIX32 ", a6=%" PRIX32 ", a7=%" PRIX32 ", skin=%" PRIX32 ", a8=%" PRIX32 ")", this->base_type.load(), this->unknown_a0.load(), this->enemy_index.load(), this->num_clones.load(), this->area.load(), this->entity_id.load(), this->section.load(), this->wave_number.load(), this->wave_number2.load(), this->x.load(), this->y.load(), this->z.load(), this->x_angle.load(), this->y_angle.load(), this->z_angle.load(), this->unknown_a3.load(), this->unknown_a4.load(), this->unknown_a5.load(), this->unknown_a6.load(), this->unknown_a7.load(), this->skin.load(), this->unknown_a8.load()); } } __attribute__((packed)); struct ObjectEntry { /* 00 */ le_uint16_t base_type; /* 02 */ le_uint16_t unknown_a1; /* 04 */ le_uint32_t unknown_a2; /* 08 */ le_uint16_t id; /* 0A */ le_uint16_t group; /* 0C */ le_uint16_t section; /* 0E */ le_uint16_t unknown_a3; /* 10 */ le_float x; /* 14 */ le_float y; /* 18 */ le_float z; /* 1C */ le_uint32_t x_angle; /* 20 */ le_uint32_t y_angle; /* 24 */ le_uint32_t z_angle; /* 28 */ le_uint32_t unknown_a4; /* 2C */ le_uint32_t unknown_a5; /* 30 */ le_uint32_t unknown_a6; /* 34 */ le_uint32_t unknown_a7; /* 38 */ le_uint32_t unknown_a8; /* 3C */ le_uint32_t unknown_a9; /* 40 */ le_uint32_t unknown_a10; /* 44 */ string str() const { return string_printf("ObjectEntry(base_type=%hX, a1=%hX, a2=%" PRIX32 ", id=%hX, group=%hX, section=%hX, a3=%hX, x=%g, y=%g, z=%g, x_angle=%" PRIX32 ", y_angle=%" PRIX32 ", z_angle=%" PRIX32 ", a3=%" PRIX32 ", a4=%" PRIX32 ", a5=%" PRIX32 ", a6=%" PRIX32 ", a7=%" PRIX32 ", a8=%" PRIX32 ", a9=%" PRIX32 ")", this->base_type.load(), this->unknown_a1.load(), this->unknown_a2.load(), this->id.load(), this->group.load(), this->section.load(), this->unknown_a3.load(), this->x.load(), this->y.load(), this->z.load(), this->x_angle.load(), this->y_angle.load(), this->z_angle.load(), this->unknown_a3.load(), this->unknown_a4.load(), this->unknown_a5.load(), this->unknown_a6.load(), this->unknown_a7.load(), this->unknown_a8.load(), this->unknown_a9.load()); } } __attribute__((packed)); void Map::clear() { this->enemies.clear(); } void Map::add_enemies_from_map_data( Episode episode, uint8_t difficulty, uint8_t event, shared_ptr data) { const auto* map = reinterpret_cast(data->data()); size_t entry_count = data->size() / sizeof(EnemyEntry); if (data->size() != entry_count * sizeof(EnemyEntry)) { throw runtime_error("data size is not a multiple of entry size"); } for (size_t y = 0; y < entry_count; y++) { const auto& e = map[y]; string hex = format_data_string(&e, sizeof(e)); fprintf(stderr, "[%04zX] %s\n", y, hex.c_str()); switch (e.base_type) { case 0x40: enemies.emplace_back((e.skin & 0x01) ? EnemyType::HILDEBLUE : EnemyType::HILDEBEAR); break; case 0x41: { bool is_rare = (e.skin & 0x01); switch (episode) { case Episode::EP1: enemies.emplace_back(is_rare ? EnemyType::AL_RAPPY : EnemyType::RAG_RAPPY); break; case Episode::EP2: if (is_rare) { switch (event) { case 0x01: enemies.emplace_back(EnemyType::SAINT_RAPPY); break; case 0x04: enemies.emplace_back(EnemyType::EGG_RAPPY); break; case 0x05: enemies.emplace_back(EnemyType::HALLO_RAPPY); break; default: enemies.emplace_back(EnemyType::LOVE_RAPPY); } } else { enemies.emplace_back(EnemyType::RAG_RAPPY); } break; case Episode::EP4: if (e.area > 0x05) { enemies.emplace_back(is_rare ? EnemyType::DEL_RAPPY_ALT : EnemyType::SAND_RAPPY_ALT); } else { enemies.emplace_back(is_rare ? EnemyType::DEL_RAPPY : EnemyType::SAND_RAPPY); } break; default: throw logic_error("invalid episode"); } break; } case 0x42: { enemies.emplace_back(EnemyType::MONEST); for (size_t x = 0; x < 30; x++) { enemies.emplace_back(EnemyType::MOTHMANT); } break; } case 0x43: { enemies.emplace_back(e.unknown_a4 ? EnemyType::BARBAROUS_WOLF : EnemyType::SAVAGE_WOLF); break; } case 0x44: static const EnemyType types[3] = {EnemyType::BOOMA, EnemyType::GOBOOMA, EnemyType::GIGOBOOMA}; enemies.emplace_back(types[e.skin % 3]); break; case 0x60: enemies.emplace_back(EnemyType::GRASS_ASSASSIN); break; case 0x61: if ((episode == Episode::EP2) && (e.area > 0x0F)) { enemies.emplace_back(EnemyType::DEL_LILY); } else { enemies.emplace_back((e.skin & 1) ? EnemyType::NAR_LILY : EnemyType::POISON_LILY); } break; case 0x62: enemies.emplace_back(EnemyType::NANO_DRAGON); break; case 0x63: { static const EnemyType types[3] = {EnemyType::EVIL_SHARK, EnemyType::PAL_SHARK, EnemyType::GUIL_SHARK}; enemies.emplace_back(types[e.skin % 3]); break; } case 0x64: { bool is_rare = (e.skin & 1); for (size_t x = 0; x < 5; x++) { // Main slime + 4 clones enemies.emplace_back(is_rare ? EnemyType::POFUILLY_SLIME : EnemyType::POUILLY_SLIME); } break; } case 0x65: enemies.emplace_back(EnemyType::PAN_ARMS); enemies.emplace_back(EnemyType::HIDOOM); enemies.emplace_back(EnemyType::MIGIUM); break; case 0x80: enemies.emplace_back((e.skin & 0x01) ? EnemyType::GILLCHIC : EnemyType::DUBCHIC); break; case 0x81: enemies.emplace_back(EnemyType::GARANZ); break; case 0x82: { EnemyType type = e.unknown_a4 ? EnemyType::SINOW_GOLD : EnemyType::SINOW_BEAT; size_t count = (e.num_clones == 0) ? 5 : (e.num_clones + 1); for (size_t z = 0; z < count; z++) { enemies.emplace_back(type); } break; } case 0x83: enemies.emplace_back(EnemyType::CANADINE); break; case 0x84: enemies.emplace_back(EnemyType::CANANE); for (size_t x = 0; x < 8; x++) { enemies.emplace_back(EnemyType::CANADINE_GROUP); } break; case 0x85: enemies.emplace_back(EnemyType::DUBWITCH); break; case 0xA0: enemies.emplace_back(EnemyType::DELSABER); break; case 0xA1: enemies.emplace_back(EnemyType::CHAOS_SORCERER); enemies.emplace_back(EnemyType::BEE_R); enemies.emplace_back(EnemyType::BEE_L); break; case 0xA2: enemies.emplace_back(EnemyType::DARK_GUNNER); break; case 0xA3: enemies.emplace_back(EnemyType::DEATH_GUNNER); break; case 0xA4: enemies.emplace_back(EnemyType::CHAOS_BRINGER); break; case 0xA5: enemies.emplace_back(EnemyType::DARK_BELRA); break; case 0xA6: { static const EnemyType types[3] = {EnemyType::DIMENIAN, EnemyType::LA_DIMENIAN, EnemyType::SO_DIMENIAN}; enemies.emplace_back(types[e.skin % 3]); break; } case 0xA7: enemies.emplace_back(EnemyType::BULCLAW); for (size_t x = 0; x < 4; x++) { enemies.emplace_back(EnemyType::CLAW); } break; case 0xA8: enemies.emplace_back(EnemyType::CLAW); break; case 0xC0: if (episode == Episode::EP1) { enemies.emplace_back(EnemyType::DRAGON); } else if (episode == Episode::EP2) { enemies.emplace_back(EnemyType::GAL_GRYPHON); } else { throw runtime_error("DRAGON-type enemy placed outside of Episodes 1 or 2"); } break; case 0xC1: enemies.emplace_back(EnemyType::DE_ROL_LE); for (size_t z = 0; z < 0x0A; z++) { enemies.emplace_back(EnemyType::DE_ROL_LE_BODY); } for (size_t z = 0; z < 0x09; z++) { enemies.emplace_back(EnemyType::DE_ROL_LE_MINE); } break; case 0xC2: enemies.emplace_back(EnemyType::VOL_OPT_1); for (size_t z = 0; z < 6; z++) { enemies.emplace_back(EnemyType::VOL_OPT_PILLAR); } for (size_t z = 0; z < 24; z++) { enemies.emplace_back(EnemyType::VOL_OPT_MONITOR); } for (size_t z = 0; z < 2; z++) { enemies.emplace_back(EnemyType::NONE); } enemies.emplace_back(EnemyType::VOL_OPT_AMP); enemies.emplace_back(EnemyType::VOL_OPT_CORE); enemies.emplace_back(EnemyType::NONE); break; case 0xC5: enemies.emplace_back(EnemyType::VOL_OPT_2); break; case 0xC8: if (difficulty) { enemies.emplace_back(EnemyType::DARK_FALZ_3); } else { enemies.emplace_back(EnemyType::DARK_FALZ_2); } for (size_t x = 0; x < 0x1FD; x++) { enemies.emplace_back(difficulty == 3 ? EnemyType::DARVANT_ULTIMATE : EnemyType::DARVANT); } enemies.emplace_back(EnemyType::DARK_FALZ_3); enemies.emplace_back(EnemyType::DARK_FALZ_2); enemies.emplace_back(EnemyType::DARK_FALZ_1); break; case 0xCA: for (size_t z = 0; z < 0x201; z++) { enemies.emplace_back(EnemyType::OLGA_FLOW_2); } break; case 0xCB: enemies.emplace_back(EnemyType::BARBA_RAY); for (size_t z = 0; z < 0x2F; z++) { enemies.emplace_back(EnemyType::PIG_RAY); } break; case 0xCC: for (size_t z = 0; z < 6; z++) { enemies.emplace_back(EnemyType::GOL_DRAGON); } break; case 0xD4: { EnemyType type = (e.skin & 1) ? EnemyType::SINOW_SPIGELL : EnemyType::SINOW_BERILL; for (size_t z = 0; z < 5; z++) { enemies.emplace_back(type); } break; } case 0xD5: enemies.emplace_back((e.skin & 0x01) ? EnemyType::MERILTAS : EnemyType::MERILLIA); break; case 0xD6: if (e.skin == 0) { enemies.emplace_back(EnemyType::MERICAROL); } else { enemies.emplace_back(((e.skin % 3) == 2) ? EnemyType::MERICUS : EnemyType::MERIKLE); } break; case 0xD7: enemies.emplace_back((e.skin & 0x01) ? EnemyType::ZOL_GIBBON : EnemyType::UL_GIBBON); break; case 0xD8: enemies.emplace_back(EnemyType::GIBBLES); break; case 0xD9: enemies.emplace_back(EnemyType::GEE); break; case 0xDA: enemies.emplace_back(EnemyType::GI_GUE); break; case 0xDB: enemies.emplace_back(EnemyType::DELDEPTH); break; case 0xDC: enemies.emplace_back(EnemyType::DELBITER); break; case 0xDD: enemies.emplace_back((e.skin & 0x01) ? EnemyType::DOLMDARL : EnemyType::DOLMOLM); break; case 0xDE: enemies.emplace_back(EnemyType::MORFOS); break; case 0xDF: enemies.emplace_back(EnemyType::RECOBOX); for (size_t x = 0; x < e.num_clones; x++) { enemies.emplace_back(EnemyType::RECON); } break; case 0xE0: if ((episode == Episode::EP2) && (e.area > 0x0F)) { enemies.emplace_back(EnemyType::EPSILON); for (size_t z = 0; z < 4; z++) { enemies.emplace_back(EnemyType::EPSIGUARD); } } else { enemies.emplace_back((e.skin & 0x01) ? EnemyType::SINOW_ZELE : EnemyType::SINOW_ZOA); } break; case 0xE1: enemies.emplace_back(EnemyType::ILL_GILL); break; case 0x0110: enemies.emplace_back(EnemyType::ASTARK); break; case 0x0111: if (e.area > 0x05) { enemies.emplace_back(e.unknown_a4 ? EnemyType::YOWIE_ALT : EnemyType::SATELLITE_LIZARD_ALT); } else { enemies.emplace_back(e.unknown_a4 ? EnemyType::YOWIE : EnemyType::SATELLITE_LIZARD); } break; case 0x0112: enemies.emplace_back((e.skin & 0x01) ? EnemyType::MERISSA_AA : EnemyType::MERISSA_A); break; case 0x0113: enemies.emplace_back(EnemyType::GIRTABLULU); break; case 0x0114: if (e.area > 0x05) { enemies.emplace_back((e.skin & 1) ? EnemyType::PAZUZU_ALT : EnemyType::ZU_ALT); } else { enemies.emplace_back((e.skin & 1) ? EnemyType::PAZUZU : EnemyType::ZU); } break; case 0x0115: if (e.skin & 2) { enemies.emplace_back(EnemyType::BA_BOOTA); } else { enemies.emplace_back((e.skin & 1) ? EnemyType::ZE_BOOTA : EnemyType::BOOTA); } break; case 0x0116: enemies.emplace_back((e.skin & 0x01) ? EnemyType::DORPHON_ECLAIR : EnemyType::DORPHON); break; case 0x0117: { static const EnemyType types[3] = {EnemyType::GORAN, EnemyType::PYRO_GORAN, EnemyType::GORAN_DETONATOR}; enemies.emplace_back(types[e.skin % 3]); break; } case 0x0119: // Saint-Million, Shambertin, Kondrieu if (e.unknown_a4) { enemies.emplace_back(EnemyType::KONDRIEU); } else { enemies.emplace_back((e.skin & 1) ? EnemyType::SHAMBERTIN : EnemyType::SAINT_MILLION); } break; default: for (size_t z = 0; z < e.num_clones + 1; z++) { enemies.emplace_back(EnemyType::UNKNOWN); } static_game_data_log.warning( "(Entry %zu, offset %zX in file) Unknown enemy type %04hX", y, y * sizeof(EnemyEntry), e.base_type.load()); break; } } } SetDataTable::SetDataTable(shared_ptr data, bool big_endian) { if (big_endian) { this->load_table_t(data); } else { this->load_table_t(data); } } template void SetDataTable::load_table_t(shared_ptr data) { using U32T = typename conditional::type; StringReader r(*data); struct Footer { U32T table3_offset; U32T table3_count; // In le_uint16_ts (so *2 for size in bytes) U32T unknown_a3; // == 1 U32T unknown_a4; // == 0 U32T root_table_offset_offset; U32T unknown_a6; // == 0 U32T unknown_a7; // == 0 U32T unknown_a8; // == 0 } __attribute__((packed)); if (r.size() < sizeof(Footer)) { throw runtime_error("set data table is too small"); } auto& footer = r.pget