/*
* V-Gears
* Copyright (C) 2022 V-Gears Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include "BattleDataInstaller.h"
#include "TexFile.h"
#include "data/VGearsHRCFileManager.h"
#include "data/VGearsAFileManager.h"
#include "data/DaFile.h"
#include "data/FF7Data.h"
#include "common/VGearsStringUtil.h"
#include "common/FinalFantasy7/FF7NameLookup.h"
BattleDataInstaller::BattleDataInstaller(
const std::string input_dir, const std::string output_dir, Ogre::ResourceGroupManager* res_mgr
):
input_dir_(input_dir), output_dir_(output_dir),
scene_bin_(input_dir + "data/battle/scene.bin"), total_scenes_(0), next_scene_(0),
res_mgr_(res_mgr)
{}
BattleDataInstaller::~BattleDataInstaller(){}
unsigned int BattleDataInstaller::InitializeScenes(){
next_scene_ = 0;
scenes_.clear();
enemies_.clear();
attacks_.clear();
// Get all scenes from scene.bin.
for (int i = 0; i < 32; i ++){
File scene_bin_block(&scene_bin_, i * 0x2000, 0x2000);
for (int o = 0; o < 10; o ++){
scene_bin_block.SetOffset(4 * o);
u32 start_offset = scene_bin_block.readU32LE() * 4;
u32 end_offset = scene_bin_block.readU32LE() * 4;
if (end_offset > start_offset + 0x2000) end_offset = start_offset + 0x2000;
if (start_offset >= 0 && start_offset < 0x2000){
File scene_bin_fragment(
&scene_bin_block, start_offset, end_offset - start_offset - 1
);
File* extracted = ExtractGZipScene(scene_bin_fragment);
if (extracted != NULL){
scenes_uncompressed_.push_back(extracted);
total_scenes_ ++;
}
}
else break;
}
}
return total_scenes_;
}
unsigned int BattleDataInstaller::InitializeModels(){
next_model_to_process_ = 0;
next_model_to_convert_ = 0;
battle_lgp_files_.clear();
battle_lgp_file_names_.clear();
models_.clear();
// Open battle.lgp
File battle_lgp_file(input_dir_ + "data/battle/battle.lgp");
// Also, open it as a LGP archive.
VGears::LGPArchive battle_lgp(input_dir_ + "data/battle/battle.lgp", "LGP");
battle_lgp.open(input_dir_ + "data/battle/battle.lgp", true);
battle_lgp.load();
VGears::LGPArchive::FileList file_list = battle_lgp.GetFiles();
for (int i = 0; i < file_list.size(); i ++){
VGears::LGPArchive::FileEntry f = file_list.at(i);
if (f.data_offset + f.data_size <= battle_lgp_file.GetFileSize()){
File b_lgp_file(&battle_lgp_file, f.data_offset, f.data_size);
battle_lgp_files_.push_back(b_lgp_file);
battle_lgp_file_names_.push_back(f.file_name);
}
}
return battle_lgp_files_.size();
}
unsigned int BattleDataInstaller::ProcessScene(){
if (next_scene_ >= scenes_uncompressed_.size()) return total_scenes_;
BattleSceneFile scene(next_scene_, scenes_uncompressed_[next_scene_]);
scenes_.push_back(scene);
// Add new enemies to the list.
std::vector enemies = scene.GetEnemies();
for (int e = 0; e < enemies.size(); e ++){
bool already_added = false;
for (int l = 0; l < enemies_.size(); l ++){
if (enemies_[l].id == enemies[e].id){
already_added = true;
break;
}
}
if (already_added) continue;
enemies_.push_back(enemies[e]);
}
// Add new attacks to the list.
std::vector attacks = scene.GetAttacks();
for (int a = 0; a < attacks.size(); a ++){
bool already_added = false;
for (int l = 0; l < attacks_.size(); l ++){
if (attacks_[l].id == attacks[a].id){
already_added = true;
break;
}
}
if (already_added) continue;
attacks_.push_back(attacks[a]);
}
// Add new formations to the list.
std::vector formations = scene.GetFormations();
for (Formation f : formations){
f.id = formations_.size(); // Assign the next ID.
formations_.push_back(f);
}
next_scene_ ++;
return next_scene_;
}
unsigned int BattleDataInstaller::ProcessModel(){
if (next_model_to_process_ >= battle_lgp_files_.size()) return battle_lgp_files_.size();
// TODO: Do something with the files.
// A little explanation. Each file has a 4 letter name, for example: 1234
// 12 is the model identifier.
// 34 if the type of file:
// - aa: Skeleton file (.hrc)
// - ab: Unknown
// - ac - al: Textures (.tex)
// - am - cz: Polygon files (.p)
// - da: Animations (.anim)
std::string id = battle_lgp_file_names_[next_model_to_process_].substr(0, 2);
/*if (id != "rt"){ // TODO DEBUG REMOVE
next_model_to_process_ ++;
return next_model_to_process_;
}*/
std::string type = battle_lgp_file_names_[next_model_to_process_].substr(2, 2);
FF7Data::BattleModelInfo info = FF7Data::GetBattleModelInfo(id);
if (type == "aa"){
// HRC file.
battle_lgp_files_[next_model_to_process_].WriteFile(
output_dir_ + "temp/battle_models/"
+ battle_lgp_file_names_[next_model_to_process_] + ".hrcbin"
);
bool found = false;
for (int m = 0; m < models_.size(); m ++){
if (models_[m].id == id){
models_[m].hrc = battle_lgp_file_names_[next_model_to_process_] + ".hrc";
found = true;
break;
}
}
if (found == false){
Model model;
model.id = id;
model.hrc = battle_lgp_file_names_[next_model_to_process_] + ".hrc";
models_.push_back(model);
}
}
else if (type == "ab"){
battle_lgp_files_[next_model_to_process_].WriteFile(
output_dir_ + "temp/battle_models/"
+ battle_lgp_file_names_[next_model_to_process_] + ".unknown"
);
} // Unknown and not needed.
else if (type == "da"){
// .a Animation file.
battle_lgp_files_[next_model_to_process_].WriteFile(
output_dir_ + "temp/battle_models/"
+ battle_lgp_file_names_[next_model_to_process_] + ".anim"
);
bool found = false;
for (int m = 0; m < models_.size(); m ++){
if (models_[m].id == id){
models_[m].anim = battle_lgp_file_names_[next_model_to_process_] + ".anim";
found = true;
break;
}
}
if (found == false){
Model model;
model.id = id;
model.anim = battle_lgp_file_names_[next_model_to_process_] + ".anim";
models_.push_back(model);
}
}
else if (
type == "ac" || type == "ad" || type == "ae" || type == "af" || type == "ag"
|| type == "ah"|| type == "ai" || type == "aj" || type == "ak" || type == "al"
){
// .tex texture files. Save directly to their directory.
battle_lgp_files_[next_model_to_process_].WriteFile(
output_dir_ + "temp/battle_models/"
+ battle_lgp_file_names_[next_model_to_process_] + ".tex"
);
TexFile tex(battle_lgp_files_[next_model_to_process_]);
std::string path = output_dir_ + "models/battle/";
if (info.is_player){
path += "characters/";
// Add or update on character_data_
bool found = false;
for (auto data: character_data_){
if (data.id == info.numeric_id){
// Check if the model already exists for this character
bool model_found = false;
for (auto mod: data.models){
if (mod.model == info.alphanumeric_id + "_" + info.name_normal + ".mesh"){
model_found = true;
break;
}
}
if (model_found) break;
CharacterModel model;
model.name = info.name;
model.model = info.alphanumeric_id + "_" + info.name_normal + ".mesh";
data.models.push_back(model);
character_data_.push_back(data);
found = true;
break;
}
}
if (!found){
CharacterData character_data;
character_data.id = info.numeric_id;
CharacterModel model;
model.name = info.name;
model.model = info.alphanumeric_id + "_" + info.name_normal + ".mesh";
character_data.models.push_back(model);
character_data_.push_back(character_data);
}
}
else if (info.is_scene){
SceneModel scene_data;
scene_data.id = info.numeric_id;
scene_data.model = info.alphanumeric_id + "_" + info.name_normal + ".mesh";
scene_data.description = info.name;
scene_data_.push_back(scene_data);
path += "scenes/";
}
else path += "enemies/";
path += (id + type + "_" + info.name_normal + ".png");
tex.SavePng(path);
res_mgr_->declareResource(
id + type + "_" + info.name_normal + ".png", "Texture", "FFVIITextures"
);
bool found = false;
for (int m = 0; m < models_.size(); m ++){
if (models_[m].id == id){
models_[m].tex.push_back(id + type+ "_" + info.name_normal + ".png");
found = true;
break;
}
}
if (found == false){
Model model;
model.id = id;
model.tex.push_back(id + type + "_" + info.name_normal + ".png");
models_.push_back(model);
}
}
else{
// .p polygon file.
battle_lgp_files_[next_model_to_process_].WriteFile(
output_dir_ + "temp/battle_models/"
+ battle_lgp_file_names_[next_model_to_process_] + ".p"
);
bool found = false;
for (int m = 0; m < models_.size(); m ++){
if (models_[m].id == id){
models_[m].p.push_back(battle_lgp_file_names_[next_model_to_process_] + ".p");
found = true;
break;
}
}
if (found == false){
Model model;
model.id = id;
model.p.push_back(battle_lgp_file_names_[next_model_to_process_] + ".p");
models_.push_back(model);
}
}
next_model_to_process_ ++;
return next_model_to_process_;
}
unsigned int BattleDataInstaller::ConvertModelsInit(){
next_model_to_convert_ = 0;
return models_.size();
}
unsigned int BattleDataInstaller::ConvertModel(){
if (next_model_to_convert_ >= models_.size()) return models_.size();
Model model = models_[next_model_to_convert_];
try{
std::string path = "";
FF7Data::BattleModelInfo info = FF7Data::GetBattleModelInfo(model.id);
if (info.is_player) path += "characters/";
else if (info.is_scene) path += "scenes/";
else path += "enemies/";
if (info.is_scene){
DecompileSceneHrc(
File(output_dir_ + "temp/battle_models/" + model.hrc + "bin"), model,
output_dir_ + "temp/battle_models/" + model.id + "_" + info.name_normal + ".hrc",
model.id + "_" + info.name_normal
);
}
else{
DecompileHrc(
File(output_dir_ + "temp/battle_models/" + model.hrc + "bin"), model,
output_dir_ + "temp/battle_models/" + model.id + "_" + info.name_normal + ".hrc",
model.id + "_" + info.name_normal
);
}
GenerateRsdFiles(model, output_dir_ + "temp/battle_models/");
if ("" != model.anim){ // Skip for non-animated, ie battle backgrounds
//std::cout << " GENERATE ANIMATIONS FOR " << model.id << std::endl;
DaFile da(File(output_dir_ + "temp/battle_models/" + model.anim));
std::vector a_files = da.GenerateAFiles(
model.id, output_dir_ + "temp/battle_models/"
);
for (std::string file_name : a_files) model.a.push_back(file_name);
}
// Reload the resources for the newly created hrc and rsd.
// TODO: Could this be done with res_mgr_->declareResource() ? Probably faster
res_mgr_->removeResourceLocation(output_dir_ + "temp/battle_models/", "FFVII");
res_mgr_->addResourceLocation(
output_dir_ + "temp/battle_models/", "FileSystem", "FFVII", true, true
);
Ogre::ResourcePtr hrc = VGears::HRCFileManager::GetSingleton().load(
model.id + "_" + info.name_normal + ".hrc", "FFVII"
);
Ogre::String base_name;
VGears::StringUtil::splitBase(model.hrc, base_name);
auto mesh_name = model.id + "_" + info.name_normal + ".mesh";
Ogre::MeshPtr mesh(Ogre::MeshManager::getSingleton().load(mesh_name, "FFVII"));
Ogre::SkeletonPtr skeleton(mesh->getSkeleton());
for (std::string anim : model.a){
VGears::AFileManager &afl_mgr(VGears::AFileManager::GetSingleton());
Ogre::String a_base_name;
VGears::StringUtil::splitBase(anim, a_base_name);
VGears::AFilePtr a
= afl_mgr.load(a_base_name + ".a", "FFVII").staticCast();
// Convert the FF7 name to a more readable name set in the meta data.
VGears::StringUtil::splitBase(anim, base_name);
a->AddTo(skeleton, VGears::NameLookup::Animation(base_name));
}
ExportMesh(path, mesh);
}
catch (const Ogre::Exception& ex){
std::cerr << "[ERROR] Ogre exception converting battle model "
<< model.hrc <<": " << ex.what() << std::endl;
}
catch (const std::exception& ex){
std::cerr << "[ERROR] Exception converting battle model "
<< model.hrc << ": " << ex.what() << std::endl;
}
next_model_to_convert_ ++;
return next_model_to_convert_;
}
void BattleDataInstaller::WriteEnemies(){
for (Enemy enemy : enemies_){
TiXmlDocument xml;
std::unique_ptr container(new TiXmlElement("enemy"));
container->SetAttribute("id", enemy.id);
container->SetAttribute("model", enemy.model);
container->SetAttribute("name", enemy.name);
container->SetAttribute("level", enemy.level);
container->SetAttribute("exp", enemy.exp);
container->SetAttribute("ap", enemy.ap);
container->SetAttribute("money", enemy.money);
container->SetAttribute("morph", enemy.morph);
container->SetDoubleAttribute("back_damage", enemy.back_damage);
std::unique_ptr stats(new TiXmlElement("stats"));
std::unique_ptr stat_str(new TiXmlElement("stat"));
stat_str->SetAttribute("id", "str");
stat_str->SetAttribute("value", enemy.str);
stats->LinkEndChild(stat_str.release());
std::unique_ptr stat_mag(new TiXmlElement("stat"));
stat_mag->SetAttribute("id", "mag");
stat_mag->SetAttribute("value", enemy.mag);
stats->LinkEndChild(stat_mag.release());
std::unique_ptr stat_def(new TiXmlElement("stat"));
stat_def->SetAttribute("id", "def");
stat_def->SetAttribute("value", enemy.def);
stats->LinkEndChild(stat_def.release());
std::unique_ptr stat_mdef(new TiXmlElement("stat"));
stat_mdef->SetAttribute("id", "mdef");
stat_mdef->SetAttribute("value", enemy.mdef);
stats->LinkEndChild(stat_mdef.release());
std::unique_ptr stat_spd(new TiXmlElement("stat"));
stat_spd->SetAttribute("id", "spd");
stat_spd->SetAttribute("value", enemy.spd);
stats->LinkEndChild(stat_spd.release());
std::unique_ptr stat_lck(new TiXmlElement("stat"));
stat_lck->SetAttribute("id", "lck");
stat_lck->SetAttribute("value", enemy.lck);
stats->LinkEndChild(stat_lck.release());
std::unique_ptr stat_eva(new TiXmlElement("stat"));
stat_eva->SetAttribute("id", "eva");
stat_eva->SetAttribute("value", enemy.eva);
stats->LinkEndChild(stat_eva.release());
container->LinkEndChild(stats.release());
std::unique_ptr elements(new TiXmlElement("elements"));
for (Enemy::Element element : enemy.elements){
std::unique_ptr xml_element(new TiXmlElement("element"));
xml_element->SetAttribute("id", element.id);
xml_element->SetDoubleAttribute("factor", element.factor);
elements->LinkEndChild(xml_element.release());
}
container->LinkEndChild(elements.release());
std::unique_ptr immunities(new TiXmlElement("immunities"));
for (Enemy::Immunity immunity : enemy.immunities){
std::unique_ptr xml_immunity(new TiXmlElement("immunity"));
xml_immunity->SetAttribute("status", immunity.status);
xml_immunity->SetDoubleAttribute("rate", immunity.rate);
immunities->LinkEndChild(xml_immunity.release());
}
container->LinkEndChild(immunities.release());
std::unique_ptr attacks(new TiXmlElement("attacks"));
for (Enemy::Attack attack : enemy.attacks){
std::unique_ptr xml_attack(new TiXmlElement("attack"));
xml_attack->SetAttribute("status", attack.id);
xml_attack->SetAttribute("camera", attack.camera);
attacks->LinkEndChild(xml_attack.release());
}
container->LinkEndChild(attacks.release());
std::unique_ptr manip_attacks(new TiXmlElement("manipulate"));
manip_attacks->SetAttribute("manipulable", enemy.manipulate_attacks.size() > 0 ? 1 : 0);
for (u16 attack : enemy.manipulate_attacks){
std::unique_ptr xml_manip_attack(new TiXmlElement("attack"));
xml_manip_attack->SetAttribute("id", attack);
manip_attacks->LinkEndChild(xml_manip_attack.release());
}
container->LinkEndChild(manip_attacks.release());
std::unique_ptr steals(new TiXmlElement("steal"));
for (Enemy::Item item : enemy.steal){
std::unique_ptr xml_steal(new TiXmlElement("item"));
xml_steal->SetAttribute("id", item.id);
xml_steal->SetDoubleAttribute("rate", item.rate);
steals->LinkEndChild(xml_steal.release());
}
container->LinkEndChild(steals.release());
std::unique_ptr drops(new TiXmlElement("drop"));
for (Enemy::Item item : enemy.drop){
std::unique_ptr xml_drop(new TiXmlElement("item"));
xml_drop->SetAttribute("id", item.id);
xml_drop->SetDoubleAttribute("rate", item.rate);
drops->LinkEndChild(xml_drop.release());
}
container->LinkEndChild(drops.release());
std::unique_ptr animations(new TiXmlElement("animations"));
for (unsigned int animation : enemy.animations){
std::unique_ptr xml_animation(new TiXmlElement("animation"));
xml_animation->SetAttribute("id", animation);
animations->LinkEndChild(xml_animation.release());
}
container->LinkEndChild(animations.release());
xml.LinkEndChild(container.release());
std::string file_name = BuildEnemyFileName(enemy);
xml.SaveFile(output_dir_ + file_name);
enemy_map_[enemy.id] = file_name;
}
}
void BattleDataInstaller::WriteAttacks(){
for (Attack attack : attacks_){
TiXmlDocument xml;
std::unique_ptr container(new TiXmlElement("Attack"));
container->SetAttribute("id", attack.id);
container->SetAttribute("name", attack.name);
container->SetAttribute("mp", attack.mp);
container->SetAttribute("reflectable", attack.reflectable);
std::unique_ptr damage(new TiXmlElement("Damage"));
std::unique_ptr damage_power(new TiXmlElement("Parameter"));
damage_power->SetAttribute("id", "power");
damage_power->SetAttribute("value", attack.damage.power);
damage->LinkEndChild(damage_power.release());
std::unique_ptr damage_formula(new TiXmlElement("Parameter"));
damage_formula->SetAttribute("id", "formula");
damage_formula->SetAttribute("value", attack.damage.formula);
damage->LinkEndChild(damage_formula.release());
std::unique_ptr damage_modifier(new TiXmlElement("Parameter"));
damage_modifier->SetAttribute("id", "modifier");
damage_modifier->SetAttribute("value", attack.damage.modifier);
damage->LinkEndChild(damage_modifier.release());
std::unique_ptr damage_acc(new TiXmlElement("Parameter"));
damage_acc->SetAttribute("id", "accuracy");
damage_acc->SetAttribute("value", attack.damage.accuracy);
damage->LinkEndChild(damage_acc.release());
std::unique_ptr damage_darkness(new TiXmlElement("Parameter"));
damage_darkness->SetAttribute("id", "affected_by_darkness");
damage_darkness->SetAttribute("value", attack.damage.affected_by_darkness);
damage->LinkEndChild(damage_darkness.release());
std::unique_ptr damage_restore(new TiXmlElement("Parameter"));
damage_restore->SetAttribute("id", "restore");
damage_restore->SetAttribute("value", static_cast(attack.damage.restore));
damage->LinkEndChild(damage_restore.release());
std::unique_ptr damage_mp(new TiXmlElement("Parameter"));
damage_mp->SetAttribute("id", "damage_mp");
damage_mp->SetAttribute("value", attack.damage.damage_mp);
damage->LinkEndChild(damage_mp.release());
std::unique_ptr damage_drain_hp(new TiXmlElement("Parameter"));
damage_drain_hp->SetAttribute("id", "drain_hp");
damage_drain_hp->SetAttribute("value", attack.damage.drain_hp);
damage->LinkEndChild(damage_drain_hp.release());
std::unique_ptr damage_drain_mp(new TiXmlElement("Parameter"));
damage_drain_mp->SetAttribute("id", "drain_mp");
damage_drain_mp->SetAttribute("value", attack.damage.drain_mp);
damage->LinkEndChild(damage_drain_mp.release());
std::unique_ptr damage_ignore(new TiXmlElement("Parameter"));
damage_ignore->SetAttribute("id", "ignore_status_defense");
damage_ignore->SetAttribute("value", attack.damage.ignore_status_defense);
damage->LinkEndChild(damage_ignore.release());
std::unique_ptr damage_critical(new TiXmlElement("Parameter"));
damage_critical->SetAttribute("id", "always_critical");
damage_critical->SetAttribute("value", attack.damage.critical);
damage->LinkEndChild(damage_critical.release());
container->LinkEndChild(damage.release());
std::unique_ptr target_opts(new TiXmlElement("Target"));
std::unique_ptr target_se(new TiXmlElement("Parameter"));
target_se->SetAttribute("id", "selection_enabled");
target_se->SetAttribute("value", attack.target.selection_enabled);
target_opts->LinkEndChild(target_se.release());
std::unique_ptr target_de(new TiXmlElement("Parameter"));
target_de->SetAttribute("id", "default_enemy");
target_de->SetAttribute("value", attack.target.default_enemy);
target_opts->LinkEndChild(target_de.release());
std::unique_ptr target_dm(new TiXmlElement("Parameter"));
target_dm->SetAttribute("id", "default_multiple");
target_dm->SetAttribute("value", attack.target.default_multiple);
target_opts->LinkEndChild(target_dm.release());
std::unique_ptr target_tm(new TiXmlElement("Parameter"));
target_tm->SetAttribute("id", "toggle_multiple");
target_tm->SetAttribute("value", attack.target.toggle_multiple);
target_opts->LinkEndChild(target_tm.release());
std::unique_ptr target_fr(new TiXmlElement("Parameter"));
target_fr->SetAttribute("id", "fixed_row");
target_fr->SetAttribute("value", attack.target.fixed_row);
target_opts->LinkEndChild(target_fr.release());
std::unique_ptr target_sr(new TiXmlElement("Parameter"));
target_sr->SetAttribute("id", "short_range");
target_sr->SetAttribute("value", attack.target.short_range);
target_opts->LinkEndChild(target_sr.release());
std::unique_ptr target_ar(new TiXmlElement("Parameter"));
target_ar->SetAttribute("id", "all_rows");
target_ar->SetAttribute("value", attack.target.all_rows);
target_opts->LinkEndChild(target_ar.release());
std::unique_ptr target_rd(new TiXmlElement("Parameter"));
target_rd->SetAttribute("id", "random");
target_rd->SetAttribute("value", attack.target.random);
target_opts->LinkEndChild(target_rd.release());
std::unique_ptr target_od(new TiXmlElement("Parameter"));
target_od->SetAttribute("id", "only_for_dead");
target_od->SetAttribute("value", attack.target.only_for_dead);
target_opts->LinkEndChild(target_od.release());
std::unique_ptr target_nr(new TiXmlElement("Parameter"));
target_nr->SetAttribute("id", "no_retarget");
target_nr->SetAttribute("value", attack.target.no_re_target);
target_opts->LinkEndChild(target_nr.release());
container->LinkEndChild(target_opts.release());
std::unique_ptr effects(new TiXmlElement("Effects"));
for (Attack::StatusEffect effect : attack.status_effects){
std::unique_ptr xml_effect(new TiXmlElement("Effect"));
xml_effect->SetAttribute("id", effect.id);
xml_effect->SetDoubleAttribute("chance", effect.chance);
xml_effect->SetAttribute("mode", GameData::STATUS_EFFECT_MODE::to_string(effect.type));
effects->LinkEndChild(xml_effect.release());
}
container->LinkEndChild(effects.release());
std::unique_ptr elements(new TiXmlElement("Elements"));
for (unsigned int element : attack.elements){
std::unique_ptr xml_element(new TiXmlElement("Element"));
xml_element->SetAttribute("id", element);
elements->LinkEndChild(xml_element.release());
}
container->LinkEndChild(elements.release());
std::unique_ptr anim(new TiXmlElement("Animation"));
std::unique_ptr anim_e(new TiXmlElement("Parameter"));
anim_e->SetAttribute("id", "effect");
anim_e->SetAttribute("value", attack.animation.effect);
anim->LinkEndChild(anim_e.release());
std::unique_ptr anim_ie(new TiXmlElement("Parameter"));
anim_ie->SetAttribute("id", "impact_effect");
anim_ie->SetAttribute("value", attack.animation.impact_effect);
anim->LinkEndChild(anim_ie.release());
std::unique_ptr anim_s(new TiXmlElement("Parameter"));
anim_s->SetAttribute("id", "sound");
anim_s->SetAttribute("value", attack.animation.sound);
anim->LinkEndChild(anim_s.release());
std::unique_ptr anim_ha(new TiXmlElement("Parameter"));
anim_ha->SetAttribute("id", "hurt_anim");
anim_ha->SetAttribute("value", attack.animation.hurt_anim);
anim->LinkEndChild(anim_ha.release());
std::unique_ptr anim_c(new TiXmlElement("Parameter"));
anim_c->SetAttribute("id", "camera_single");
anim_c->SetAttribute("value", attack.animation.camera_single);
anim->LinkEndChild(anim_c.release());
std::unique_ptr anim_cm(new TiXmlElement("Parameter"));
anim_cm->SetAttribute("id", "camera_multiple");
anim_cm->SetAttribute("value", attack.animation.camera_multiple);
anim->LinkEndChild(anim_cm.release());
container->LinkEndChild(anim.release());
xml.LinkEndChild(container.release());
std::string file_name = BuildAttackFileName(attack);
xml.SaveFile(output_dir_ + file_name);
attack_map_[attack.id] = file_name;
}
}
void BattleDataInstaller::WriteFormations(){
for (Formation formation : formations_){
TiXmlDocument xml;
std::unique_ptr container(new TiXmlElement("formation"));
container->SetAttribute("id", formation.id);
container->SetAttribute("next", formation.next_formation);
container->SetDoubleAttribute("escape_difficulty", formation.escape_counter);
container->SetAttribute("escapable", formation.escapable);
container->SetAttribute("skip_victory_pose", formation.skip_victory_pose);
container->SetAttribute("skip_spoils", formation.skip_spoils);
container->SetAttribute("skip_victory_pose", formation.skip_victory_pose);
container->SetAttribute("preemptive_disabled", formation.preemptive_disabled);
container->SetAttribute("layout", static_cast(formation.layout));
std::unique_ptr location(new TiXmlElement("location"));
location->SetAttribute("id", formation.location);
location->SetAttribute("name", formation.location_name);
container->LinkEndChild(location.release());
std::unique_ptr enemies(new TiXmlElement("enemies"));
for (Formation::Enemy enemy : formation.enemies){
std::unique_ptr enemy_xml(new TiXmlElement("enemy"));
enemy_xml->SetAttribute("id", enemy.id);
enemy_xml->SetAttribute("x", enemy.x);
enemy_xml->SetAttribute("y", enemy.y);
enemy_xml->SetAttribute("z", enemy.z);
enemy_xml->SetAttribute("row", enemy.row);
std::string cover = "";
for (int c = 0; c < 5; c ++) cover += enemy.cover[c];
enemy_xml->SetAttribute("cover", cover);
enemy_xml->SetAttribute("visible", enemy.visible);
enemy_xml->SetAttribute("direction", enemy.direction);
enemy_xml->SetAttribute("targeteable", enemy.targeteable);
enemy_xml->SetAttribute("main_script_active", enemy.main_script_active);
enemies->LinkEndChild(enemy_xml.release());
}
container->LinkEndChild(enemies.release());
std::unique_ptr cameras(new TiXmlElement("camera"));
cameras->SetAttribute("initial", formation.initial_camera_position);
int c = 0;
for (Formation::Camera camera : formation.camera_positions){
std::unique_ptr camera_xml(new TiXmlElement("position"));
camera_xml->SetAttribute("id", c);
camera_xml->SetAttribute("x", camera.x);
camera_xml->SetAttribute("y", camera.y);
camera_xml->SetAttribute("z", camera.z);
camera_xml->SetAttribute("direction_x", camera.d_x);
camera_xml->SetAttribute("direction_y", camera.d_y);
camera_xml->SetAttribute("direction_z", camera.d_z);
cameras->LinkEndChild(camera_xml.release());
c ++;
}
container->LinkEndChild(cameras.release());
std::unique_ptr arena(new TiXmlElement("arena"));
arena->SetAttribute("is_arena", formation.is_arena_battle);
for (int next : formation.next_arena_formation_candidates){
std::unique_ptr next_xml(new TiXmlElement("next_candidate"));
next_xml->SetAttribute("id", next);
arena->LinkEndChild(next_xml.release());
}
container->LinkEndChild(arena.release());
xml.LinkEndChild(container.release());
std::string file_name = std::to_string(formation.id);
while (file_name.length() < 4) file_name = "0" + file_name;
file_name = "gamedata/formation/" + file_name + ".xml";
xml.SaveFile(output_dir_ + file_name);
formation_map_[formation.id] = file_name;
}
}
void BattleDataInstaller::WriteCharacterData(){
TiXmlDocument xml;
std::unique_ptr container(new TiXmlElement("characters"));
for (CharacterData data : character_data_){
std::unique_ptr xml_character(new TiXmlElement("character"));
xml_character->SetAttribute("id", data.id);
for (CharacterModel model : data.models){
std::unique_ptr xml_model(new TiXmlElement("model"));
xml_model->SetAttribute("name", model.name);
xml_model->SetAttribute("file", model.model);
xml_character->LinkEndChild(xml_model.release());
}
container->LinkEndChild(xml_character.release());
}
xml.LinkEndChild(container.release());
xml.SaveFile(output_dir_ + "models/battle/characters.xml");
}
void BattleDataInstaller::WriteSceneData(){
TiXmlDocument xml;
std::unique_ptr container(new TiXmlElement("scenes"));
for (SceneModel data : scene_data_){
std::unique_ptr xml_scene(new TiXmlElement("scene"));
xml_scene->SetAttribute("id", data.id);
xml_scene->SetAttribute("file", data.model);
xml_scene->SetAttribute("description", data.description);
container->LinkEndChild(xml_scene.release());
}
xml.LinkEndChild(container.release());
xml.SaveFile(output_dir_ + "models/battle/scenes.xml");
}
std::string BattleDataInstaller::BuildEnemyFileName(Enemy enemy){
std::string file_name = "gamedata/enemy/";
std::string id = std::to_string(enemy.id);
while (id.size() < 4) id = "0" + id;
file_name += id + ".xml";
return file_name;
}
std::string BattleDataInstaller::BuildAttackFileName(Attack attack){
std::string file_name = "gamedata/attack/";
std::string id = std::to_string(attack.id);
while (id.size() < 4) id = "0" + id;
file_name += (id + "_");
for (int n = 0; n < attack.name.size(); n ++){
if (
(attack.name[n] >= '0' && attack.name[n] <= '9')
|| (attack.name[n] >= 'a' && attack.name[n] <= 'z')
|| (attack.name[n] >= 'A' && attack.name[n] <= 'Z')
){
file_name += attack.name[n];
}
}
file_name += ".xml";
return file_name;
}
File* BattleDataInstaller::ExtractGZipScene(File file){
u32 offset = 0;
u8* file_buffer = static_cast(malloc(file.GetFileSize()));
file.GetFileBuffer(file_buffer, 0, file.GetFileSize());
u32 extract_size = 256 * 1024;
u8* extract_buffer = static_cast(malloc(extract_size));
int ret;
z_stream strm;
strm.zalloc = Z_NULL; // Used to allocate the internal state.
strm.zfree = Z_NULL; // Used to free the internal state.
strm.opaque = Z_NULL; // Private data object passed to zalloc and zfree.
strm.next_in = file_buffer + offset; //Next input byte.
strm.avail_in = file.GetFileSize() - offset; // Number of bytes available at next_in.
strm.next_out = extract_buffer; // Next output byte should be put there.
strm.avail_out = extract_size; // Remaining free space at next_out.
ret = inflateInit2(&strm, 15 + 32);
if (ret != Z_OK){
switch (ret){
case Z_MEM_ERROR:
inflateEnd(&strm);
std::cout << "Warning: inflateInit2 - Z_MEM_ERROR in file "
<< file.GetFileName() << std::endl;
break;
case Z_STREAM_ERROR:
inflateEnd(&strm);
std::cout << "Warning: inflateInit2 - Z_STREAM_ERROR in file "
<< file.GetFileName() << std::endl;
break;
default:
inflateEnd(&strm);
std::cout << "Warning: inflateInit2 - unknown error in file "
<< file.GetFileName() << std::endl;
}
return NULL;
}
do{
if (strm.next_out == NULL){
inflateEnd(&strm);
std::cout << "Warning: strm.next_out == NULL in file "
<< file.GetFileName() << std::endl;
return NULL;
}
ret = inflate(&strm, Z_NO_FLUSH);
assert(ret != Z_STREAM_ERROR);
switch (ret){
case Z_NEED_DICT:
inflateEnd(&strm);
std::cout << "Warning: inflate - Z_NEED_DICT in file "
<< file.GetFileName() << std::endl;
return NULL;
case Z_DATA_ERROR:
inflateEnd(&strm);
std::cout << "Warning: inflate - Z_DATA_ERROR in file "
<< file.GetFileName() << std::endl;
return NULL;
case Z_MEM_ERROR:
inflateEnd(&strm);
std::cout << "Warning: inflate - Z_MEM_ERROR in file "
<< file.GetFileName() << std::endl;
return NULL;
}
if (ret != Z_STREAM_END){
extract_buffer = static_cast(realloc(extract_buffer, extract_size * 2));
if (extract_buffer == NULL){
inflateEnd(&strm);
//std::cout << "Warning: extract_buffer == NULL in file "
// << file.GetFileName() << std::endl;
return NULL;
}
strm.next_out = static_cast(extract_buffer + extract_size);
strm.avail_out = extract_size;
extract_size *= 2;
}
} while (ret != Z_STREAM_END);
extract_size = extract_size - strm.avail_out;
(void)inflateEnd(&strm);
if (ret != Z_STREAM_END){
std::cout << "Warning: ret != Z_STREAM_END in file " << file.GetFileName() << std::endl;
return NULL;
}
File* out_file = new File(extract_buffer, 0, extract_size);
return out_file;
}
void BattleDataInstaller::ExportMesh(const std::string outdir, const Ogre::MeshPtr &mesh){
VGears::String base_mesh_name;
VGears::StringUtil::splitFull(mesh->getName(), base_mesh_name);
Ogre::MeshSerializer mesh_serializer;
Ogre::SkeletonPtr skeleton(mesh->getSkeleton());
Ogre::SkeletonSerializer skeleton_serializer;
skeleton_serializer.exportSkeleton(
skeleton.get(), output_dir_ + "models/battle/" + outdir + base_mesh_name + ".skeleton"
);
mesh->setSkeletonName(outdir + base_mesh_name + ".skeleton");
mesh_serializer.exportMesh(
mesh.get(), output_dir_ + "models/battle/" + outdir + mesh->getName()
);
Ogre::MaterialSerializer mat_ser;
std::set textures;
Ogre::Mesh::SubMeshList sub_meshes = mesh->getSubMeshes();
for (Ogre::SubMesh* sub_mesh : sub_meshes){
// Change material name to avoid conflicts with field model materials.
sub_mesh->setMaterialName(sub_mesh->getMaterialName());
Ogre::MaterialPtr mat(
Ogre::MaterialManager::getSingleton().getByName(sub_mesh->getMaterialName())
);
if (mat == nullptr) continue;
for (size_t techs = 0; techs < mat->getNumTechniques(); techs ++){
Ogre::Technique* tech = mat->getTechnique(techs);
if (tech == nullptr) continue;
for (size_t pass_num = 0; pass_num < tech->getNumPasses(); pass_num ++){
Ogre::Pass* pass = tech->getPass(pass_num);
if (pass == nullptr) continue;
for (
size_t texture_unit_num = 0;
texture_unit_num < pass->getNumTextureUnitStates();
texture_unit_num ++
){
Ogre::TextureUnitState* unit = pass->getTextureUnitState(texture_unit_num);
if (unit && unit->getTextureName().empty() == false){
// Ensure the output material script references png files, no tex files.
Ogre::String base_name;
VGears::StringUtil::splitBase(unit->getTextureName(), base_name);
unit->setTextureName(outdir + base_name + ".png");
textures.insert(unit->getTextureName());
}
}
}
}
if (std::count(materials_.begin(), materials_.end(), sub_mesh->getMaterialName()) == 0){
mat_ser.queueForExport(mat);
materials_.push_back(sub_mesh->getMaterialName());
}
}
mat_ser.exportQueued(
output_dir_ + "models/battle/" + outdir + base_mesh_name + VGears::EXT_MATERIAL
);
}
void BattleDataInstaller::GenerateRsdFiles(Model model, std::string path){
std::sort(model.tex.begin(), model.tex.end());
// For each p file, generate a rsd file.
for (std::string p : model.p){
std::string base_name = p.substr(0, 4);
std::string content = "@RSD940102\n";
content += "PLY=" + base_name + ".PLY\n";
content += "MAT=" + base_name + ".MAT\n";
content += "GRP=" + base_name + ".GRP\n";
content += "NTEX=" + std::to_string(model.tex.size()) + "\n";
for (int t = 0; t < model.tex.size(); t ++)
content += "TEX[" + std::to_string(t) + "]=" + model.tex[t] + "\n";
content += "\n";
std::ofstream out(path + base_name + ".rsd");
out << content;
out.close();
}
}
void BattleDataInstaller::DecompileHrc(
File compiled, Model model, std::string path, std::string skeleton_name
){
compiled.SetOffset(0);
compiled.readU32LE();
compiled.readU32LE();
compiled.readU32LE();
std::sort(model.p.begin(), model.p.end());
std::string decompiled(":HEADER_BLOCK 2\n:SKELETON " + skeleton_name + "\n");
int bones = compiled.readU32LE();
compiled.SetOffset(compiled.GetFileSize() - (bones * 12));
decompiled += ":BONES " + std::to_string(bones) + "\n\n";
int p_file_index = 0;
for (int b = 0; b < bones; b ++){
decompiled += "bone" + std::to_string(b) + "\n";
int parent = compiled.readU32LE();
if (parent == 0xFFFFFFFF) decompiled += "root\n";
else decompiled += "bone" + std::to_string(parent) + "\n";
union {
float f;
unsigned long ul;
unsigned char b[4];
} u;
u.b[0] = compiled.readU8();
u.b[1] = compiled.readU8();
u.b[2] = compiled.readU8();
u.b[3] = compiled.readU8();
decompiled += std::to_string(u.f * -1) + "\n";
int offset = compiled.readU32LE();
if (offset == 0) decompiled += "0\n\n";
else {
decompiled += std::to_string(offset) + " ";
if (p_file_index < model.p.size()) decompiled += model.p[p_file_index].substr(0, 4);
p_file_index ++;
decompiled += "\n\n";
}
}
std::ofstream out(path);
out << decompiled;
out.close();
}
void BattleDataInstaller::DecompileSceneHrc(
File compiled, Model model, std::string path, std::string skeleton_name
){
compiled.SetOffset(0);
compiled.readU32LE(); // Skip 4 bytes
compiled.readU32LE(); // Skip 4 bytes
compiled.readU32LE(); // Skip 4 bytes
compiled.readU32LE(); // Supposed to be the number of bones, but for scenes is always 0.
std::sort(model.p.begin(), model.p.end());
std::string decompiled(":HEADER_BLOCK 2\n:SKELETON " + skeleton_name + "\n");
int bones = model.p.size();
decompiled += ":BONES " + std::to_string(bones) + "\n\n";
int p_file_index = 0;
for (int b = 0; b < bones; b ++){
decompiled += "bone" + std::to_string(b) + "\n";
if (b == 0) decompiled += "root\n";
else decompiled += ("bone" + std::to_string(b - 1) + "\n");
decompiled += "100.000000\n";
decompiled += "1 " + model.p[b].substr(0, 4) + "\n\n";
}
std::ofstream out(path);
out << decompiled;
out.close();
}