瀏覽代碼

Installer: Battle spell models and animations extracted during installation (untextured).

Iñigo Valentin 3 年之前
父節點
當前提交
80b636e14d

+ 1 - 0
src/core/ConfigVar.cpp

@@ -13,6 +13,7 @@
  * GNU General Public License for more details.
  * GNU General Public License for more details.
  */
  */
 
 
+#include <cstdint>
 #include <OgreStringConverter.h>
 #include <OgreStringConverter.h>
 #include "core/Assert.h"
 #include "core/Assert.h"
 #include "core/ConfigVar.h"
 #include "core/ConfigVar.h"

+ 1 - 0
src/core/ConfigVarHandler.cpp

@@ -13,6 +13,7 @@
  * GNU General Public License for more details.
  * GNU General Public License for more details.
  */
  */
 
 
+#include <cstdint>
 #include "ConfigVarHandler.h"
 #include "ConfigVarHandler.h"
 
 
 /**
 /**

+ 166 - 111
src/installer/BattleDataInstaller.cpp

@@ -192,7 +192,7 @@ unsigned int BattleDataInstaller::ProcessBattleModel(){
             }
             }
         }
         }
         if (found == false){
         if (found == false){
-            Model model;
+            BattleModel model;
             model.id = id;
             model.id = id;
             model.hrc = battle_lgp_file_names_[next_model_to_process_] + ".hrc";
             model.hrc = battle_lgp_file_names_[next_model_to_process_] + ".hrc";
             battle_models_.push_back(model);
             battle_models_.push_back(model);
@@ -213,7 +213,7 @@ unsigned int BattleDataInstaller::ProcessBattleModel(){
             }
             }
         }
         }
         if (found == false){
         if (found == false){
-            Model model;
+            BattleModel model;
             model.id = id;
             model.id = id;
             model.script = battle_lgp_file_names_[next_model_to_process_] + ".script";
             model.script = battle_lgp_file_names_[next_model_to_process_] + ".script";
             battle_models_.push_back(model);
             battle_models_.push_back(model);
@@ -234,7 +234,7 @@ unsigned int BattleDataInstaller::ProcessBattleModel(){
             }
             }
         }
         }
         if (found == false){
         if (found == false){
-            Model model;
+            BattleModel model;
             model.id = id;
             model.id = id;
             model.anim = battle_lgp_file_names_[next_model_to_process_] + ".anim";
             model.anim = battle_lgp_file_names_[next_model_to_process_] + ".anim";
             battle_models_.push_back(model);
             battle_models_.push_back(model);
@@ -308,7 +308,7 @@ unsigned int BattleDataInstaller::ProcessBattleModel(){
             }
             }
         }
         }
         if (found == false){
         if (found == false){
-            Model model;
+            BattleModel model;
             model.id = id;
             model.id = id;
             model.tex.push_back(id + type + "_" + info.name_normal + ".png");
             model.tex.push_back(id + type + "_" + info.name_normal + ".png");
             battle_models_.push_back(model);
             battle_models_.push_back(model);
@@ -329,7 +329,7 @@ unsigned int BattleDataInstaller::ProcessBattleModel(){
             }
             }
         }
         }
         if (found == false){
         if (found == false){
-            Model model;
+            BattleModel model;
             model.id = id;
             model.id = id;
             model.p.push_back(battle_lgp_file_names_[next_model_to_process_] + ".p");
             model.p.push_back(battle_lgp_file_names_[next_model_to_process_] + ".p");
             battle_models_.push_back(model);
             battle_models_.push_back(model);
@@ -342,125 +342,108 @@ unsigned int BattleDataInstaller::ProcessBattleModel(){
 unsigned int BattleDataInstaller::ProcessSpellModel(){
 unsigned int BattleDataInstaller::ProcessSpellModel(){
     if (next_model_to_process_ >= magic_lgp_files_.size()) return magic_lgp_files_.size();
     if (next_model_to_process_ >= magic_lgp_files_.size()) return magic_lgp_files_.size();
     // TODO: Do something with the files.
     // 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)
+    // A little explanation. There are no patterns on file names, and there are multiple extensions.
+    //  - d: Skeleton file, similar to aa from battle models.
+    //  - b: Unknown.
+    //  - p*: Polygon files, for each bone.
+    //  - tex: Texture files, TIM format.
+    //  - rsd: Resource files
+    //  - s: Unknown.
+    //  - a00: Animation files, similar to da from battle models.
     //std::cout << "SPELL MODEL FILE: " << magic_lgp_file_names_[next_model_to_process_] << std::endl;
     //std::cout << "SPELL MODEL FILE: " << magic_lgp_file_names_[next_model_to_process_] << std::endl;
-    std::string id = magic_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 fname = magic_lgp_file_names_[next_model_to_process_];
     std::string fname = magic_lgp_file_names_[next_model_to_process_];
+    std::string id = fname.substr(0, fname.find_last_of("."));
+    std::string ext = fname.substr(fname.find_last_of(".") + 1);
+
+
     FF7Data::SpellModelInfo info = FF7Data::GetSpellModelInfo(id);
     FF7Data::SpellModelInfo info = FF7Data::GetSpellModelInfo(id);
-    magic_lgp_files_[next_model_to_process_].WriteFile(output_dir_ + "temp/spell_models/" + fname);
-    // Decompile skeleton files
-    if (fname.length() > 2 && fname.substr(fname.length() - 2, 2) == ".d"){
-        std::string out_name = fname.substr(0, fname.length() - 2) + ".hrc";
-        /*DecompileHrc(
-          File(output_dir_ + "temp/spell_models/" + fname), model,
-          output_dir_ + "temp/spell_models/" + out_name,
-          model.id + "_" + info.name_normal
-        );*/
-    }
-    /*if (fname.substr(fname.length() - 2, 2) == ".d"){
-        // HRC file, skeleton
+    //magic_lgp_files_[next_model_to_process_].WriteFile(output_dir_ + "temp/spell_models/" + fname);
+
+    if (ext == "d"){
+        // Skeleton file.
         magic_lgp_files_[next_model_to_process_].WriteFile(
         magic_lgp_files_[next_model_to_process_].WriteFile(
-          output_dir_ + "temp/spell_models/"
-          + magic_lgp_file_names_[next_model_to_process_] + ".hrcbin"
+          output_dir_ + "temp/spell_models/" + fname
         );
         );
         bool found = false;
         bool found = false;
         for (int m = 0; m < spell_models_.size(); m ++){
         for (int m = 0; m < spell_models_.size(); m ++){
             if (spell_models_[m].id == id){
             if (spell_models_[m].id == id){
-                spell_models_[m].hrc = magic_lgp_file_names_[next_model_to_process_] + ".hrc";
+                spell_models_[m].hrc = fname;
                 found = true;
                 found = true;
                 break;
                 break;
             }
             }
         }
         }
         if (found == false){
         if (found == false){
-            Model model;
+            SpellModel model;
             model.id = id;
             model.id = id;
-            model.hrc = magic_lgp_file_names_[next_model_to_process_] + ".hrc";
+            model.hrc = fname;
             spell_models_.push_back(model);
             spell_models_.push_back(model);
         }
         }
     }
     }
-    else if (fname.substr(fname.length() - 4, 2) == ".a"){
-        // .a Animation file.
+    else if (ext.at(0) == 'p'){
+        // Bone file.
+        // If the extension is just ".p", use the file name.
+        std::string p_name;
+        if (ext == "p") p_name = fname;
+        // If the extension is ".pXX", save it with altered name.
+        else p_name =
+          fname.substr(0, fname.length() - 4) + "_" + fname.substr(fname.length() - 2, 2) + ".p";
         magic_lgp_files_[next_model_to_process_].WriteFile(
         magic_lgp_files_[next_model_to_process_].WriteFile(
-          output_dir_ + "temp/spell_models/"
-          + magic_lgp_file_names_[next_model_to_process_] + ".anim"
+          output_dir_ + "temp/spell_models/" + p_name
         );
         );
+        std::cout << "[BONE RENAMED] " << fname << " --> " << p_name << std::endl;
         bool found = false;
         bool found = false;
         for (int m = 0; m < spell_models_.size(); m ++){
         for (int m = 0; m < spell_models_.size(); m ++){
             if (spell_models_[m].id == id){
             if (spell_models_[m].id == id){
-                spell_models_[m].anim = magic_lgp_file_names_[next_model_to_process_] + ".anim";
+                spell_models_[m].p.push_back(p_name);
                 found = true;
                 found = true;
                 break;
                 break;
             }
             }
         }
         }
         if (found == false){
         if (found == false){
-            Model model;
+            SpellModel model;
             model.id = id;
             model.id = id;
-            model.anim = magic_lgp_file_names_[next_model_to_process_] + ".anim";
+            model.p.push_back(p_name);
             spell_models_.push_back(model);
             spell_models_.push_back(model);
         }
         }
     }
     }
-    else if (fname.substr(fname.length() - 4, 4) == ".tex"){
-        // .tex texture files. Save directly to their directory.
+    if (ext == "tex"){
+        // Texture files. They can be shared among various spells, so just convert them and don't
+        // add them to a particular model.
         magic_lgp_files_[next_model_to_process_].WriteFile(
         magic_lgp_files_[next_model_to_process_].WriteFile(
-          output_dir_ + "temp/spell_models/"
-          + magic_lgp_file_names_[next_model_to_process_] + ".tex"
+          output_dir_ + "temp/spell_models/" + fname
         );
         );
-        TexFile tex(magic_lgp_files_[next_model_to_process_]);
-        std::string path = output_dir_ + "models/battle/attacks/";
-        path += (id + "_" + info.name_normal + ".png");
-        tex.SavePng(path);
-        res_mgr_->declareResource(
-          id + "_" + info.name_normal + ".png", "Texture", "FFVIITextures"
-        );
-        bool found = false;
-        for (int m = 0; m < spell_models_.size(); m ++){
-            if (spell_models_[m].id == id){
-                spell_models_[m].tex.push_back(id + "_" + info.name_normal + ".png");
-                found = true;
-                break;
-            }
+        TexFile tex(output_dir_ + "temp/spell_models/" + fname);
+        std::string path = output_dir_ + "models/battle/attacks/" + id + ".png";
+        // Many of these tex files are wrong and dummied out in the OG, so try/catch.
+        try{
+            tex.SavePng(path);
         }
         }
-        if (found == false){
-            Model model;
-            model.id = id;
-            model.tex.push_back(id + "_" + info.name_normal + ".png");
-            spell_models_.push_back(model);
+        catch(const std::exception& e){
+            std::cerr << "Error converting texture " << (output_dir_ + "temp/spell_models/" + fname)
+              << " to png: " << e.what() << std::endl;
         }
         }
     }
     }
-    else if (
-      fname.substr(fname.length() - 4, 2) == ".p" || fname.substr(fname.length() - 2, 2) == ".p"
-    ){
-        // .p polygon file.
+    if (ext == "a00"){
+        // Animation file.
         magic_lgp_files_[next_model_to_process_].WriteFile(
         magic_lgp_files_[next_model_to_process_].WriteFile(
-          output_dir_ + "temp/spell_models/"
-          + magic_lgp_file_names_[next_model_to_process_] + ".p"
+          output_dir_ + "temp/spell_models/" + fname
         );
         );
         bool found = false;
         bool found = false;
         for (int m = 0; m < spell_models_.size(); m ++){
         for (int m = 0; m < spell_models_.size(); m ++){
             if (spell_models_[m].id == id){
             if (spell_models_[m].id == id){
-                spell_models_[m].p.push_back(magic_lgp_file_names_[next_model_to_process_] + ".p");
+                spell_models_[m].anim = fname;
                 found = true;
                 found = true;
                 break;
                 break;
             }
             }
         }
         }
         if (found == false){
         if (found == false){
-            Model model;
+            SpellModel model;
             model.id = id;
             model.id = id;
-            model.p.push_back(magic_lgp_file_names_[next_model_to_process_] + ".p");
+            model.anim = fname;
             spell_models_.push_back(model);
             spell_models_.push_back(model);
         }
         }
-    }*/
+    }
+    // TODO: b files? a files? s files?
     next_model_to_process_ ++;
     next_model_to_process_ ++;
     return next_model_to_process_;
     return next_model_to_process_;
 }
 }
@@ -477,7 +460,7 @@ unsigned int BattleDataInstaller::ConvertSpellModelsInit(){
 
 
 unsigned int BattleDataInstaller::ConvertBattleModel(){
 unsigned int BattleDataInstaller::ConvertBattleModel(){
     if (next_model_to_convert_ >= battle_models_.size()) return battle_models_.size();
     if (next_model_to_convert_ >= battle_models_.size()) return battle_models_.size();
-    Model model = battle_models_[next_model_to_convert_];
+    BattleModel model = battle_models_[next_model_to_convert_];
     try{
     try{
         std::string path = "";
         std::string path = "";
         FF7Data::BattleModelInfo info = FF7Data::GetBattleModelInfo(model.id);
         FF7Data::BattleModelInfo info = FF7Data::GetBattleModelInfo(model.id);
@@ -485,14 +468,14 @@ unsigned int BattleDataInstaller::ConvertBattleModel(){
         else if (info.is_scene) path += "scenes/";
         else if (info.is_scene) path += "scenes/";
         else path += "enemies/";
         else path += "enemies/";
         if (info.is_scene){
         if (info.is_scene){
-            DecompileSceneHrc(
+            DecompileSceneModelHrc(
               File(output_dir_ + "temp/battle_models/" + model.hrc + "bin"), model,
               File(output_dir_ + "temp/battle_models/" + model.hrc + "bin"), model,
               output_dir_ + "temp/battle_models/" + model.id + "_" + info.name_normal + ".hrc",
               output_dir_ + "temp/battle_models/" + model.id + "_" + info.name_normal + ".hrc",
               model.id + "_" + info.name_normal
               model.id + "_" + info.name_normal
             );
             );
         }
         }
         else{
         else{
-            DecompileHrc(
+            DecompileBattleModelHrc(
               File(output_dir_ + "temp/battle_models/" + model.hrc + "bin"), model,
               File(output_dir_ + "temp/battle_models/" + model.hrc + "bin"), model,
               output_dir_ + "temp/battle_models/" + model.id + "_" + info.name_normal + ".hrc",
               output_dir_ + "temp/battle_models/" + model.id + "_" + info.name_normal + ".hrc",
               model.id + "_" + info.name_normal
               model.id + "_" + info.name_normal
@@ -509,7 +492,6 @@ unsigned int BattleDataInstaller::ConvertBattleModel(){
         }
         }
 
 
         if ("" != model.script){ // Skip non scripted models
         if ("" != model.script){ // Skip non scripted models
-            std::cout << "AB FILE: " << model.script << std::endl;
             AbFile ab(File(output_dir_ + "temp/battle_models/" + model.script), info.is_enemy);
             AbFile ab(File(output_dir_ + "temp/battle_models/" + model.script), info.is_enemy);
             ab.GenerateScripts(
             ab.GenerateScripts(
                 model.id + "_" + info.name_normal, output_dir_ + "scripts/battle_models/"
                 model.id + "_" + info.name_normal, output_dir_ + "scripts/battle_models/"
@@ -557,18 +539,25 @@ unsigned int BattleDataInstaller::ConvertBattleModel(){
 
 
 unsigned int BattleDataInstaller::ConvertSpellModel(){
 unsigned int BattleDataInstaller::ConvertSpellModel(){
     if (next_model_to_convert_ >= spell_models_.size()) return spell_models_.size();
     if (next_model_to_convert_ >= spell_models_.size()) return spell_models_.size();
-    Model model = spell_models_[next_model_to_convert_];
+    SpellModel model = spell_models_[next_model_to_convert_];
     try{
     try{
         std::string path = "attacks/";
         std::string path = "attacks/";
         FF7Data::SpellModelInfo info = FF7Data::GetSpellModelInfo(model.id);
         FF7Data::SpellModelInfo info = FF7Data::GetSpellModelInfo(model.id);
-        DecompileHrc(
-          File(output_dir_ + "temp/spell_models/" + model.hrc + "bin"), model,
-          output_dir_ + "temp/spell_models/" + model.id + "_" + info.name_normal + ".hrc",
-          model.id + "_" + info.name_normal
-        );
+
+        // Decompile the .hrc file (if any)
+        if (model.hrc != ""){
+            std::cout << "[DECOMPILE] " << output_dir_ + "temp/spell_models/" + model.hrc << std::endl;
+            DecompileSpellModelHrc(
+              File(output_dir_ + "temp/spell_models/" + model.hrc), model,
+              output_dir_ + "temp/spell_models/" + info.name_normal + ".hrc", model.id
+            );
+            model.hrc = info.name_normal + ".hrc";
+            std::cout << "    TO: " << (output_dir_ + "temp/spell_models/" + info.name_normal + ".hrc") << std::endl;
+        }
         GenerateRsdFiles(model, output_dir_ + "temp/spell_models/");
         GenerateRsdFiles(model, output_dir_ + "temp/spell_models/");
 
 
-        if ("" != model.anim){ // Skip for non-animated, ie battle backgrounds
+        // Decompile the .a00 file (if any) into a .a file.
+        if ("" != model.anim){ // Skip for non-animated.
             DaFile da(File(output_dir_ + "temp/spell_models/" + model.anim));
             DaFile da(File(output_dir_ + "temp/spell_models/" + model.anim));
             std::vector<std::string> a_files = da.GenerateAFiles(
             std::vector<std::string> a_files = da.GenerateAFiles(
               model.id, output_dir_ + "temp/spell_models/"
               model.id, output_dir_ + "temp/spell_models/"
@@ -583,32 +572,36 @@ unsigned int BattleDataInstaller::ConvertSpellModel(){
           output_dir_ + "temp/spell_models/", "FileSystem", "FFVII", true, true
           output_dir_ + "temp/spell_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<VGears::AFile>();
-            // 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));
+        // Export meshes thet have an .hrc skeleton.
+        if (model.hrc != ""){
+            std::cout << "[EXPORT MESH] " << model.hrc << std::endl;
+            Ogre::ResourcePtr hrc = VGears::HRCFileManager::GetSingleton().load(
+              model.id + ".hrc", "FFVII"
+            );
+            Ogre::String base_name;
+            VGears::StringUtil::splitBase(model.hrc, base_name);
+            auto mesh_name = model.id + ".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<VGears::AFile>();
+                // 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);
         }
         }
-        ExportMesh(path, mesh);
     }
     }
     catch (const Ogre::Exception& ex){
     catch (const Ogre::Exception& ex){
-        std::cerr << "[ERROR] Ogre exception converting battle model "
+        std::cerr << "[ERROR] Ogre exception converting spell model "
           << model.hrc <<": " << ex.what() << std::endl;
           << model.hrc <<": " << ex.what() << std::endl;
     }
     }
     catch (const std::exception& ex){
     catch (const std::exception& ex){
-        std::cerr << "[ERROR] Exception converting battle model "
+        std::cerr << "[ERROR] Exception converting spell model "
           << model.hrc << ": " << ex.what() << std::endl;
           << model.hrc << ": " << ex.what() << std::endl;
     }
     }
     next_model_to_convert_ ++;
     next_model_to_convert_ ++;
@@ -1131,7 +1124,7 @@ void BattleDataInstaller::ExportMesh(const std::string outdir, const Ogre::MeshP
     );
     );
 }
 }
 
 
-void BattleDataInstaller::GenerateRsdFiles(Model model, std::string path){
+void BattleDataInstaller::GenerateRsdFiles(BattleModel model, std::string path){
     std::sort(model.tex.begin(), model.tex.end());
     std::sort(model.tex.begin(), model.tex.end());
     // For each p file, generate a rsd file.
     // For each p file, generate a rsd file.
     for (std::string p : model.p){
     for (std::string p : model.p){
@@ -1150,8 +1143,27 @@ void BattleDataInstaller::GenerateRsdFiles(Model model, std::string path){
     }
     }
 }
 }
 
 
-void BattleDataInstaller::DecompileHrc(
-  File compiled, Model model, std::string path, std::string skeleton_name
+void BattleDataInstaller::GenerateRsdFiles(SpellModel 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, p.length() - 2);
+        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=0\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::DecompileBattleModelHrc(
+  File compiled, BattleModel model, std::string path, std::string skeleton_name
 ){
 ){
     compiled.SetOffset(0);
     compiled.SetOffset(0);
     compiled.readU32LE();
     compiled.readU32LE();
@@ -1192,8 +1204,51 @@ void BattleDataInstaller::DecompileHrc(
     out.close();
     out.close();
 }
 }
 
 
-void BattleDataInstaller::DecompileSceneHrc(
-  File compiled, Model model, std::string path, std::string skeleton_name
+void BattleDataInstaller::DecompileSpellModelHrc(
+  File compiled, SpellModel 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_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_index < model.p.size())
+                decompiled += model.p[p_index].substr(0, model.p[p_index].length() - 2);
+            p_index ++;
+            decompiled += "\n\n";
+        }
+    }
+    std::ofstream out(path);
+    out << decompiled;
+    out.close();
+}
+
+void BattleDataInstaller::DecompileSceneModelHrc(
+  File compiled, BattleModel model, std::string path, std::string skeleton_name
 ){
 ){
     compiled.SetOffset(0);
     compiled.SetOffset(0);
     compiled.readU32LE(); // Skip 4 bytes
     compiled.readU32LE(); // Skip 4 bytes

+ 95 - 21
src/installer/BattleDataInstaller.h

@@ -156,34 +156,53 @@ class BattleDataInstaller{
     private:
     private:
 
 
         /**
         /**
-         * Files to compose a model.
+         * A model.
+         *
+         * It can be a battle model or an spell model.
          */
          */
         struct Model{
         struct Model{
 
 
             /**
             /**
-             * Model identifier, two letters.
+             * Model identifier.
+             *
+             * For battle models, a two letters string. For spell models, anything.
              */
              */
             std::string id;
             std::string id;
 
 
             /**
             /**
-             * Name of the model .hrc file
+             * Name of the model .hrc file.
+             *
+             * For battle models, it will be the *aa file. For spell models, the *.d file.
              */
              */
             std::string hrc;
             std::string hrc;
 
 
             /**
             /**
-             * Name of the "da" animation file.
+             * Name of the animation file.
+             *
+             * For battle models, it will be the *da file. For spell models, the *.b file.
              */
              */
             std::string anim;
             std::string anim;
 
 
             /**
             /**
-             * Name of the "ab" scripts file.
+             * List of .p polygon files associated to the model
              */
              */
-            std::string script;
+            std::vector<std::string> p;
+
+            Model(): id(""), hrc(""), anim(""){
+                p.clear();
+            }
+
+        };
+
+        /**
+         * Files to compose a battle model.
+         */
+        struct BattleModel: Model{
 
 
             /**
             /**
-             * List of .p polygon files associated to the model
+             * Name of the "ab" scripts file.
              */
              */
-            std::vector<std::string> p;
+            std::string script;
 
 
             /**
             /**
              * List of .tex texture files associated to the model
              * List of .tex texture files associated to the model
@@ -198,8 +217,7 @@ class BattleDataInstaller{
             /**
             /**
              * Constructor, initializes default values.
              * Constructor, initializes default values.
              */
              */
-            Model(): id(""), hrc(""), anim(""), script(""){
-                p.clear();
+            BattleModel(): script(""){
                 tex.clear();
                 tex.clear();
                 a.clear();
                 a.clear();
             }
             }
@@ -259,6 +277,30 @@ class BattleDataInstaller{
             std::vector<CharacterModel> models;
             std::vector<CharacterModel> models;
         };
         };
 
 
+        /**
+         * Files to compose a spell model.
+         */
+        struct SpellModel: Model{
+
+            /**
+             * List of .a files associated to the model
+             */
+            std::vector<std::string> a;
+
+            /**
+             * List of .a files associated to the model
+             */
+            std::vector<std::string> s;
+
+            /**
+             * Constructor, initializes default values.
+             */
+            SpellModel(){
+                a.clear();
+                s.clear();
+            }
+        };
+
         /**
         /**
          * Extract a gzipped file.
          * Extract a gzipped file.
          *
          *
@@ -305,41 +347,73 @@ class BattleDataInstaller{
         /**
         /**
          * Generates all required .rsd models for a model.
          * Generates all required .rsd models for a model.
          *
          *
+         * To be used for enemy, playable character and scene models (anything in battle.lgp).
+         *
          * @param[in] model Model information.
          * @param[in] model Model information.
          * @param[in] path Output path.
          * @param[in] path Output path.
          */
          */
-        void GenerateRsdFiles(Model model, std::string path);
+        void GenerateRsdFiles(BattleModel model, std::string path);
+
+        /**
+         * Generates all required .rsd models for a model.
+         *
+         * To be used for spell models (anything in magic.lgp).
+         *
+         * @param[in] model Model information.
+         * @param[in] path Output path.
+         */
+        void GenerateRsdFiles(SpellModel model, std::string path);
 
 
         /**
         /**
          * Decompiles a compiled HRC file.
          * Decompiles a compiled HRC file.
          *
          *
          * To be used for battle enemy and playable character models, since they have a
          * To be used for battle enemy and playable character models, since they have a
-         * well-defined bone hierarchy. For battle background models, use {@see DecompileSceneHrc}.
+         * well-defined bone hierarchy. For battle background models, use
+         * {@see DecompileSceneSceneHrc}, and for spell models, use {@see DecompileSpellModelHrc}.
          * Note that this is not a general purpose HRC decompiler, it only works for files in
          * Note that this is not a general purpose HRC decompiler, it only works for files in
          * battle.lgp.
          * battle.lgp.
          *
          *
          * @param[in] compiled The compiles HRC file.
          * @param[in] compiled The compiles HRC file.
-         * @param[in] model The model.
+         * @param[in] model The model, it can be a battle model or an spell model.
          * @param[in] path Path to save the decompiled file to.
          * @param[in] path Path to save the decompiled file to.
          * @param[in] skeleton_name Name for the skeleton.
          * @param[in] skeleton_name Name for the skeleton.
          */
          */
-        void DecompileHrc(File compiled, Model model, std::string path, std::string skeleton_name);
+        void DecompileBattleModelHrc(
+          File compiled, BattleModel model, std::string path, std::string skeleton_name
+        );
+
+        /**
+         * Decompiles a compiled HRC file.
+         *
+         * To be used for spell models, since they have a well-defined bone hierarchy. For battle
+         * background models, use {@see DecompileSceneSceneHrc}, and for enemy and playable
+         * character models, use {@see DecompileBattleModelHrc}. Note that this is not a general
+         * purpose HRC decompiler, it only works for files in magic.lgp.
+         *
+         * @param[in] compiled The compiles HRC file.
+         * @param[in] model The model, it can be a battle model or an spell model.
+         * @param[in] path Path to save the decompiled file to.
+         * @param[in] skeleton_name Name for the skeleton.
+         */
+        void DecompileSpellModelHrc(
+          File compiled, SpellModel model, std::string path, std::string skeleton_name
+        );
 
 
         /**
         /**
          * Decompiles a compiled HRC file.
          * Decompiles a compiled HRC file.
          *
          *
          * To be used only for battle background HRC files, since they don't have a defined bone
          * To be used only for battle background HRC files, since they don't have a defined bone
          * hierarchy, and every bone is assumed to be a child of the previous one. For enemy and
          * hierarchy, and every bone is assumed to be a child of the previous one. For enemy and
-         * playable character models, use {@see DecompileHrc}. Note that this is not a general
-         * purpose HRC decompiler, it only works for files in battle.lgp.
+         * playable character models, use {@see DecompileBattleModelHrc}. Note that this is not a
+         * general purpose HRC decompiler, it only works for files in battle.lgp.
          *
          *
          * @param[in] compiled The compiles HRC file.
          * @param[in] compiled The compiles HRC file.
-         * @param[in] model The model.
+         * @param[in] model The model, it can be a battle model or an spell model.
          * @param[in] path Path to save the decompiled file to.
          * @param[in] path Path to save the decompiled file to.
          * @param[in] skeleton_name Name for the skeleton.
          * @param[in] skeleton_name Name for the skeleton.
          */
          */
-        void DecompileSceneHrc(
-          File compiled, Model model, std::string path, std::string skeleton_name
+        void DecompileSceneModelHrc(
+          File compiled, BattleModel model, std::string path, std::string skeleton_name
         );
         );
 
 
         /**
         /**
@@ -460,7 +534,7 @@ class BattleDataInstaller{
         /**
         /**
          * List of battle models files found in battle.lgp
          * List of battle models files found in battle.lgp
          */
          */
-        std::vector<Model> battle_models_;
+        std::vector<BattleModel> battle_models_;
 
 
         /**
         /**
          * List of battle model materials;
          * List of battle model materials;
@@ -470,7 +544,7 @@ class BattleDataInstaller{
         /**
         /**
          * List of spell models files found in magic.lgp
          * List of spell models files found in magic.lgp
          */
          */
-        std::vector<Model> spell_models_;
+        std::vector<SpellModel> spell_models_;
 
 
         /**
         /**
          * List of spell model materials;
          * List of spell model materials;

+ 3 - 2
src/installer/TexFile.cpp

@@ -32,8 +32,8 @@ void TexFile::Read(File file){
     file.SetOffset(0);
     file.SetOffset(0);
     version_ = file.readU32LE();
     version_ = file.readU32LE();
     if (version_ != 0x00000001){
     if (version_ != 0x00000001){
-        std::cout << "[WARNING] Tried to read an invalid TEX file to png. Version ID: "
-          << version_ << std::endl;
+        std::cout << "[WARNING] Tried to read an invalid TEX file " << file.GetFileName()
+          << " to png. Version ID: " << version_ << std::endl;
         return;
         return;
     }
     }
     unknown_0_ = file.readU32LE();
     unknown_0_ = file.readU32LE();
@@ -185,6 +185,7 @@ void TexFile::SavePng(
 }
 }
 
 
 void TexFile::SavePng(std::string file_name, unsigned int palette){
 void TexFile::SavePng(std::string file_name, unsigned int palette){
+    //if (invalid_) return;
     SavePng(file_name, 0, 0, width_, height_, palette);
     SavePng(file_name, 0, 0, width_, height_, palette);
 }
 }
 
 

+ 6 - 1
src/installer/TexFile.h

@@ -45,6 +45,8 @@ class TexFile{
         /**
         /**
          * Saves a fragment of the TEX file as a PNG file.
          * Saves a fragment of the TEX file as a PNG file.
          *
          *
+         * If any of the read data invalidates the file, it will silently fail and do nothing.
+         *
          * @param[in] file_name Full path of the destination file.
          * @param[in] file_name Full path of the destination file.
          * @param[in] x X coordinate of the image to extract from the TEX file.
          * @param[in] x X coordinate of the image to extract from the TEX file.
          * @param[in] y Y coordinate of the image to extract from the TEX file.
          * @param[in] y Y coordinate of the image to extract from the TEX file.
@@ -61,6 +63,8 @@ class TexFile{
         /**
         /**
          * Saves the TEX file as a PNG file.
          * Saves the TEX file as a PNG file.
          *
          *
+         * If any of the read data invalidates the file, it will silently fail and do nothing.
+         *
          * @param[in] file_name Full path of the destination file.
          * @param[in] file_name Full path of the destination file.
          * @param[in] palette Index of the color palette to use. For non paletted files it's
          * @param[in] palette Index of the color palette to use. For non paletted files it's
          * ignored.
          * ignored.
@@ -71,7 +75,8 @@ class TexFile{
          * Saves two fragments of the TEX file as a single PNG image.
          * Saves two fragments of the TEX file as a single PNG image.
          *
          *
          * The second fragment will be at the right of the first one. They must have the same
          * The second fragment will be at the right of the first one. They must have the same
-         * height.
+         * height. If any of the read data invalidates the file, it will silently fail and do
+         * nothing.
          *
          *
          * @param[in] file_name Full path of the destination file.
          * @param[in] file_name Full path of the destination file.
          * @param[in] x1 X coordinate of the first image to extract from the TEX file.
          * @param[in] x1 X coordinate of the first image to extract from the TEX file.

+ 3 - 7
src/installer/data/FF7Data.h

@@ -3520,14 +3520,10 @@ class FF7Data{
             std::transform(
             std::transform(
               alphanumeric_id.begin(), alphanumeric_id.end(), alphanumeric_id.begin(), ::tolower
               alphanumeric_id.begin(), alphanumeric_id.end(), alphanumeric_id.begin(), ::tolower
             );
             );
-            if (alphanumeric_id.size() < 2) return info; // Empty
-            if (alphanumeric_id.size() > 4 && alphanumeric_id.substr(0, 4) == "btl_")
-                alphanumeric_id = alphanumeric_id.substr(4, alphanumeric_id.size() - 4);
-            if (alphanumeric_id.size() > 4) alphanumeric_id = alphanumeric_id.substr(0, 2);
             info.alphanumeric_id = alphanumeric_id;
             info.alphanumeric_id = alphanumeric_id;
             info.name = alphanumeric_id;
             info.name = alphanumeric_id;
-            info.name_normal = "btl_" + alphanumeric_id;
-            if ("aa" == alphanumeric_id){
+            info.name_normal = alphanumeric_id;
+            /*if ("aa" == alphanumeric_id){
                 info.numeric_id = 0;
                 info.numeric_id = 0;
                 info.name = "Unused Pyramid";
                 info.name = "Unused Pyramid";
                 info.name_normal = "unused_pyramid";
                 info.name_normal = "unused_pyramid";
@@ -3536,7 +3532,7 @@ class FF7Data{
                 info.numeric_id = 1;
                 info.numeric_id = 1;
                 info.name = "Unused Pyramid";
                 info.name = "Unused Pyramid";
                 info.name_normal = "unused_pyramid";
                 info.name_normal = "unused_pyramid";
-            }
+            }*/
             return info;
             return info;
         }
         }
 };
 };