Explorar el Código

The installer now extracts battle models, textures and (partially) animations.

The animations are all wrong, excepts for their first frame. The battle module remains unimplemented, but battle models can be used in the field!
Iñigo Valentin hace 3 años
padre
commit
fee1cd1a11

+ 1 - 3
src/common/VGearsApplication.cpp

@@ -138,9 +138,7 @@ namespace VGears{
             while(it != end){
                 archive_type = it->first;
                 archive_name = it->second;
-                res_mgr_->addResourceLocation(
-                  archive_name, archive_type, section_name, true
-                );
+                res_mgr_->addResourceLocation(archive_name, archive_type, section_name, true);
                 ++ it;
             }
         }

+ 5 - 1
src/core/EntityModel.cpp

@@ -13,6 +13,7 @@
  * GNU General Public License for more details.
  */
 
+#include <iostream>
 #include <OgreEntity.h>
 #include <OgreRoot.h>
 #include "core/EntityModel.h"
@@ -50,7 +51,10 @@ void EntityModel::PlayAnimation(
   Entity::AnimationPlayType play_type, const float start, const float end
 ){
     if (animation_current_ != nullptr) animation_current_->setEnabled(false);
-    if (model_->getAllAnimationStates()->hasAnimationState(animation) == true){
+    if (
+      model_->getAllAnimationStates() != nullptr
+      && model_->getAllAnimationStates()->hasAnimationState(animation) == true
+    ){
         animation_current_name_ = animation;
         animation_current_ = model_->getAnimationState(animation);
         animation_current_->setLoop((play_type == Entity::PLAY_LOOPED) ? true : false);

+ 3 - 7
src/data/VGearsAFileSerializer.cpp

@@ -13,6 +13,7 @@
  * GNU General Public License for more details.
  */
 
+#include <iostream>
 #include <OgreException.h>
 #include <OgreLogManager.h>
 #include "data/VGearsAFileSerializer.h"
@@ -42,20 +43,15 @@ namespace VGears{
         flipFromLittleEndian(&header_, 4, header_size / 4);
     }
 
-    void AFileSerializer::readObject(
-      Ogre::DataStreamPtr &stream, AFile::Frame &dest
-    ){
+    void AFileSerializer::readObject(Ogre::DataStreamPtr &stream, AFile::Frame &dest){
         readObject(stream, dest.root_rotation);
         readObject(stream, dest.root_translation);
         dest.root_translation = dest.root_translation / HRCFile::DOWN_SCALER;
         ReadVector(stream, dest.bone_rotations, header_.bone_count);
     }
 
-    void AFileSerializer::ImportAFile(
-      Ogre::DataStreamPtr &stream, AFile* dest
-    ){
+    void AFileSerializer::ImportAFile(Ogre::DataStreamPtr &stream, AFile* dest){
         ReadFileHeader(stream);
-
         if(header_.version != 1){
             OGRE_EXCEPT(
               Ogre::Exception::ERR_INVALIDPARAMS,

+ 9 - 0
src/data/VGearsHRCFileSerializer.cpp

@@ -88,7 +88,16 @@ namespace VGears{
     void HRCFileSerializer::readObject(Ogre::DataStreamPtr &stream, Bone &dest){
         Block bone_block;
         ReadBlock(stream, bone_block);
+
         Block::const_iterator it(bone_block.begin());
+        if (bone_block.size() < 2){
+            dest.length = 0;
+            dest.parent = "";
+            dest.name = "";
+            dest.rsd_names.clear();
+            return;
+        }
+
         dest.name = *(it ++);
         dest.parent = *(it ++);
         dest.length

+ 44 - 33
src/data/VGearsPFile.cpp

@@ -13,6 +13,7 @@
  * GNU General Public License for more details.
  */
 
+#include <iostream>
 #include <OgreBone.h>
 #include <OgreLogManager.h>
 #include <OgreStringConverter.h>
@@ -28,9 +29,8 @@ namespace VGears{
     const Ogre::Quaternion PFile::STATIC_ROTATION(PFile::CreateStaticRotation());
 
     PFile::PFile(
-      Ogre::ResourceManager *creator, const String &name,
-      Ogre::ResourceHandle handle, const String &group, bool is_manual,
-      Ogre::ManualResourceLoader *loader
+      Ogre::ResourceManager *creator, const String &name, Ogre::ResourceHandle handle,
+      const String &group, bool is_manual, Ogre::ManualResourceLoader *loader
     ) : Ogre::Resource(creator, name, handle, group, is_manual, loader)
     {createParamDictionary(RESOURCE_TYPE);}
 
@@ -39,9 +39,7 @@ namespace VGears{
     void PFile::loadImpl(){
         PFileSerializer serializer;
         Ogre::DataStreamPtr stream(
-          Ogre::ResourceGroupManager::getSingleton().openResource(
-            mName, mGroup, true, this
-          )
+          Ogre::ResourceGroupManager::getSingleton().openResource(mName, mGroup, true, this)
         );
         serializer.ImportPFile(stream, this);
     }
@@ -59,6 +57,30 @@ namespace VGears{
         bounding_boxes_.clear();
     }
 
+    PFile::VertexList& PFile::GetVertices(){return vertices_;}
+
+    PFile::NormalList& PFile::GetNormals(){return normals_;}
+
+    PFile::Unkown1List& PFile::GetUnknown1(){return unknown_1_;}
+
+    PFile::TextureCoordinateList& PFile::GetTextureCoordinates(){
+        return texture_coordinates_;
+    }
+
+    PFile::VertexColorList& PFile::GetVertexColors(){return vertex_colours_;}
+
+    PFile::PolygonColorList& PFile::GetPolygonColors(){return polygon_colours_;}
+
+    PFile::EdgeList& PFile::GetEdges(){return edges_;}
+
+    PFile::PolygonDefinitionList& PFile::GetPolygonDefinitions(){
+        return polygon_definitions_;
+    }
+
+    PFile::GroupList& PFile::GetGroups(){return groups_;}
+
+    PFile::BBoxList& PFile::GetBBoxes(){return bounding_boxes_;}
+
     size_t PFile::calculateSize() const{
         return
           vertices_.size() * sizeof(vertices_.front())
@@ -84,21 +106,18 @@ namespace VGears{
             const PolygonDefinition& def(polygon_definitions_[p]);
             for (int i(3); i --;){
                 if (def.vertex[i] >= vertex_count){
-                    log << "Error: index to vertex is out of Bounds "
-                      << " polygon_definitions_[" << p << "]"
-                      << ".vertex[" << i << "]: " << def.vertex[i];
+                    log << "Error: index to vertex is out of Bounds: polygon_definitions_[" << p
+                      << "].vertex[" << i << "]: " << def.vertex[i];
                     return false;
                 }
                 if (def.normal[i] >= normal_count){
-                    log << "Error: index to normal is out of Bounds "
-                      << " polygon_definitions_[" << p << "]"
-                      << ".normal[" << i << "]: " << def.normal[i];
+                    log << "Error: index to normal is out of Bounds: polygon_definitions_[" << p
+                      << "].normal[" << i << "]: " << def.normal[i];
                     return false;
                 }
                 if (def.edge[i] >= edge_count){
-                    log << "Error: index to edge is out of Bounds "
-                      << " polygon_definitions_[" << p << "]"
-                      << ".edge[" << i << "]: " << def.edge[i];
+                    log << "Error: index to edge is out of Bounds: polygon_definitions_[" << p
+                      << "].edge[" << i << "]: " << def.edge[i];
                     return false;
                 }
             }
@@ -106,9 +125,7 @@ namespace VGears{
         return true;
     }
 
-    void PFile::AddGroups(
-      Ogre::Mesh *mesh, const String &bone_name, const RSDFilePtr &rsd
-    ) const{
+    void PFile::AddGroups(Ogre::Mesh *mesh, const String &bone_name, const RSDFilePtr &rsd) const{
         const Ogre::SkeletonPtr skeleton(mesh->getSkeleton());
         const String material_base_name(rsd->GetMaterialBaseName());
         String rsd_base;
@@ -116,13 +133,9 @@ namespace VGears{
         ManualObject mo(mesh);
         for (size_t g(0); g < groups_.size(); ++ g){
             const String sub_name(
-              bone_name + "/" + rsd_base + "/"
-              + Ogre::StringConverter::toString(g)
-            );
-            AddGroup(
-              groups_[g], mo, sub_name, material_base_name,
-              skeleton->getBone(bone_name)
+              bone_name + "/" + rsd_base + "/" + Ogre::StringConverter::toString(g)
             );
+            AddGroup(groups_[g], mo, sub_name, material_base_name, skeleton->getBone(bone_name));
         }
     }
 
@@ -142,7 +155,9 @@ namespace VGears{
     ) const{
         size_t material_index(0);
         if (group.has_texture) material_index = group.texture_index + 1;
-        String material_name(material_base_name + "/" + Ogre::StringConverter::toString(material_index));
+        String material_name(
+          material_base_name + "/" + Ogre::StringConverter::toString(material_index)
+        );
         const uint16 bone_handle(bone->getHandle());
         const Ogre::Vector3 bone_position(GetPosition(bone));
         size_t index(0);
@@ -155,16 +170,12 @@ namespace VGears{
             for (int i(3); i --;){
                 uint32 v(group.vertex_start_index + polygon.vertex[i]);
                 uint32 n(0 + polygon.normal[i]);
-                uint32 t(
-                  group.texture_coordinate_start_index + polygon.vertex[i]
-                );
+                uint32 t(group.texture_coordinate_start_index + polygon.vertex[i]);
                 Ogre::Vector3 pos(vertices_[v]);
-                mo.position(
-                  (STATIC_ROTATION * (pos / HRCFile::DOWN_SCALER))
-                  + bone_position
-                );
+                mo.position((STATIC_ROTATION * (pos / HRCFile::DOWN_SCALER)) + bone_position);
                 mo.colour(vertex_colours_[v]);
-                mo.normal(STATIC_ROTATION * normals_[n]);
+                if (n < normals_.size()) mo.normal(STATIC_ROTATION * normals_[n]);
+                else mo.normal(STATIC_ROTATION * Ogre::Vector3(1.0f, 1.0f, 1.0f));
                 if (group.has_texture) mo.textureCoord(texture_coordinates_[t]);
                 mo.bone(index, bone_handle);
                 mo.index(index++);

+ 34 - 46
src/data/VGearsPFile.h

@@ -29,11 +29,10 @@ namespace VGears{
     /**
      * Handles P files.
      *
-     * P files are binary files containing data which form 3D model. The files
-     * specify model's vertices, polygons, colors, texture coordinates and
-     * model sub-groups. The files do not specify references to the texture
-     * files, animations, model skeleton or anything else. P-files are used as
-     * parts of field models, battle models, battle locations on PC version of
+     * P files are binary files containing data which form 3D model. The files specify model's
+     * vertices, polygons, colors, texture coordinates and model sub-groups. The files do not
+     * specify references to the texture files, animations, model skeleton or anything else.
+     * P-files are used as parts of field models, battle models, battle locations on PC version of
      * FF7.
      */
     class PFile : public Ogre::Resource{
@@ -43,25 +42,20 @@ namespace VGears{
             /**
              * Constructor.
              *
-             * @param[in] creator Pointer to the ResourceManager that is
-             * creating this resource.
+             * @param[in] creator Pointer to the ResourceManager that is creating this resource.
              * @param[in] name The unique name of the resource.
              * @param[in] handle @todo Understand and document.
-             * @param[in] group The name of the resource group to which this
-             * resource belong.
-             * @param[in] is_manual True if the resource is manually loaded,
-             * false otherwise.
-             * @param[in] loader Pointer to a ManualResourceLoader
-             * implementation which will be called when the Resource wishes to
-             * load (should be supplied if is_manual is set to true). It can be
-             * null, but the Resource will never be able to reload if anything
-             * ever causes it to unload. Therefore provision of a proper
-             * ManualResourceLoader instance is strongly recommended.
+             * @param[in] group The name of the resource group to which this resource belong.
+             * @param[in] is_manual True if the resource is manually loaded, false otherwise.
+             * @param[in] loader Pointer to a ManualResourceLoader implementation which will be
+             * called when the Resource wishes to load (should be supplied if is_manual is set to
+             * true). It can be null, but the Resource will never be able to reload if anything
+             * ever causes it to unload. Therefore provision of a proper ManualResourceLoader
+             * instance is strongly recommended.
              */
             PFile(
-              Ogre::ResourceManager* creator, const String &name,
-              Ogre::ResourceHandle handle, const String& group,
-              bool is_manual = false,
+              Ogre::ResourceManager* creator, const String &name, Ogre::ResourceHandle handle,
+              const String& group, bool is_manual = false,
               Ogre::ManualResourceLoader* loader = nullptr
             );
 
@@ -80,20 +74,18 @@ namespace VGears{
             /**
              * Indicates if the poligons definitions are valid.
              *
-             * @return True if all the poligon definitions are valid, false
-             * otherwise.
+             * @return True if all the poligon definitions are valid, false otherwise.
              */
             virtual bool IsPolygonDefinitionListValid();
 
             /**
              * Adds a resource group.
              *
-             * A resource group includes a bone, a mesh and a graphical
-             * resource.
+             * A resource group includes a bone, a mesh and a graphical resource.
              *
              * @param[in] mesh The mesh to add to the group.
-             * @param[in] bone_name The bone in the skeleton to which to add
-             * the group. The bone must be in a skeleton assigned to MESH.
+             * @param[in] bone_name The bone in the skeleton to which to add the group. The bone
+             * must be in a skeleton assigned to MESH.
              * @param[in] rsd File with the resource to add to the group.
              */
             virtual void AddGroups(
@@ -274,67 +266,66 @@ namespace VGears{
 
             typedef std::vector<BBoxEntry> BBoxList;
 
+            /**
+             * The type of resource.
+             */
+            static const String RESOURCE_TYPE;
+
             /**
              * Retrieves the vertices in the file.
              *
              * @return The vertices in the file.
              */
-            virtual VertexList& GetVertices(){return vertices_;}
+            virtual VertexList& GetVertices();
 
             /**
              * Retrieves the normals in the file.
              *
              * @return The normals in the file.
              */
-            virtual NormalList& GetNormals(){return normals_;}
+            virtual NormalList& GetNormals();
 
             /**
              * Retrieves unknown data from the file.
              *
              * @return Unknown data.
              */
-            virtual Unkown1List& GetUnknown1(){return unknown_1_;}
+            virtual Unkown1List& GetUnknown1();
 
             /**
              * Retrieves the texture coordinates in the file.
              *
              * @return The texture coordinates in the file.
              */
-            virtual TextureCoordinateList& GetTextureCoordinates(){
-                return texture_coordinates_;
-            }
+            virtual TextureCoordinateList& GetTextureCoordinates();
 
             /**
              * Retrieves the vertex colurs in the file.
              *
              * @return The vertex colurs in the file.
              */
-            virtual VertexColorList& GetVertexColors(){return vertex_colours_;}
+            virtual VertexColorList& GetVertexColors();
 
             /**
              * Retrieves the polygon colours in the file.
              *
              * @return The polygon colours in the file.
              */
-            virtual PolygonColorList& GetPolygonColors(){
-                return polygon_colours_;
-            }
+            virtual PolygonColorList& GetPolygonColors();
 
             /**
              * Retrieves the edges in the file.
              *
              * @return The edgesvertices in the file.
              */
-            virtual EdgeList& GetEdges(){return edges_;}
+            virtual EdgeList& GetEdges();
 
             /**
              * Retrieves the polygon definitions in the file.
              *
              * @return The polygon definitions in the file.
              */
-            virtual PolygonDefinitionList& GetPolygonDefinitions(){
-                return polygon_definitions_;
-            }
+            virtual PolygonDefinitionList& GetPolygonDefinitions();
 
             /**
              * Retrieves the groups in the file.
@@ -343,16 +334,14 @@ namespace VGears{
              *
              * @return The groups in the file.
              */
-            virtual GroupList& GetGroups(){return groups_;}
+            virtual GroupList& GetGroups();
 
             /**
              * Retrieves the bounding boxes in the file.
              *
              * @return The vertices in the file.
              */
-            virtual BBoxList& GetBBoxes(){return bounding_boxes_;}
-
-            static const String RESOURCE_TYPE;
+            virtual BBoxList& GetBBoxes();
 
         protected:
             /**
@@ -376,8 +365,7 @@ namespace VGears{
             /**
              * Add a group to the file.
              *
-             * A resource group includes a bone, a mesh and a graphical
-             * resource.
+             * A resource group includes a bone, a mesh and a graphical resource.
              *
              * @param[in] group The group to add to the file.
              * @param[in,out] mo The object to add to the group.

+ 477 - 7
src/installer/BattleDataInstaller.cpp

@@ -17,9 +17,17 @@
  */
 
 #include <iostream>
+#include <fstream>
 #include <zlib.h>
 #include <tinyxml.h>
+#include <boost/filesystem.hpp>
+#include <OgreMesh.h>
 #include "BattleDataInstaller.h"
+#include "TexFile.h"
+#include "data/VGearsHRCFileManager.h"
+#include "data/VGearsAFileManager.h"
+#include "common/VGearsStringUtil.h"
+#include "common/FinalFantasy7/FF7NameLookup.h"
 
 BattleDataInstaller::BattleDataInstaller(const std::string input_dir, const std::string output_dir):
   input_dir_(input_dir), output_dir_(output_dir),
@@ -28,10 +36,12 @@ BattleDataInstaller::BattleDataInstaller(const std::string input_dir, const std:
 
 BattleDataInstaller::~BattleDataInstaller(){}
 
-unsigned int BattleDataInstaller::Initialize(){
+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 ++){
@@ -55,8 +65,32 @@ unsigned int BattleDataInstaller::Initialize(){
     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("btl_" + f.file_name);
+        }
+    }
+    return battle_lgp_files_.size();
+}
+
 unsigned int BattleDataInstaller::ProcessScene(){
-    if (next_scene_ >= scenes_uncompressed_.size()) return false;
+    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.
@@ -95,9 +129,169 @@ unsigned int BattleDataInstaller::ProcessScene(){
     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)
+    // Note that the names in battle_lgp_file_names_ are prefixed with "btl_"
+    std::string id = battle_lgp_file_names_[next_model_to_process_].substr(4, 2);
+    if (id != "at"){next_model_to_process_ ++; return next_model_to_process_;} // TODO: DEBUG
+    std::string type = battle_lgp_file_names_[next_model_to_process_].substr(6, 2);
+    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_]);
+        tex.SavePng(
+          output_dir_ + "models/battle/entities/"
+            + battle_lgp_file_names_[next_model_to_process_] + ".png",
+          0
+          );
+        bool found = false;
+        for (int m = 0; m < models_.size(); m ++){
+            if (models_[m].id == id){
+                models_[m].tex.push_back(battle_lgp_file_names_[next_model_to_process_] + ".tex");
+                found = true;
+                break;
+            }
+        }
+        if (found == false){
+            Model model;
+            model.id = id;
+            model.tex.push_back(battle_lgp_file_names_[next_model_to_process_] + ".tex");
+            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{
+        DecompileHrc(File(output_dir_ + "temp/battle_models/" + model.hrc + "bin"), model, output_dir_ + "temp/battle_models/" + model.hrc);
+        GenerateRsdFiles(model, output_dir_ + "temp/battle_models/");
+        ExtractAFilesFromDAFile(File(output_dir_ + "temp/battle_models/" + model.anim), &model, output_dir_ + "temp/battle_models/");
+        Ogre::ResourcePtr hrc = VGears::HRCFileManager::GetSingleton().load(model.hrc, "FFVII");
+        Ogre::String base_name;
+        VGears::StringUtil::splitBase(model.hrc, base_name);
+        auto mesh_name = VGears::NameLookup::model(base_name) + ".mesh";
+        Ogre::MeshPtr mesh(Ogre::MeshManager::getSingleton().load(mesh_name, "FFVII"));
+        Ogre::SkeletonPtr skeleton(mesh->getSkeleton());
+        for (std::string anim : model.a){
+        //if (model.anim != ""){
+            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(output_dir_ + "models/battle/entities/", 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_){
-        //std::cout << "Writting enemy " << enemy.id << ": " << (output_dir_ + "game/enemy/" + std::to_string(enemy.id) + ".xml") << "\n";
         TiXmlDocument xml;
         std::unique_ptr<TiXmlElement> container(new TiXmlElement("Enemy"));
         container->SetAttribute("id", enemy.id);
@@ -201,7 +395,6 @@ void BattleDataInstaller::WriteEnemies(){
 }
 
 void BattleDataInstaller::WriteAttacks(){
-    //std::cout << "WRITE ATTACKS TOTAL: " << attacks_.size() << "\n";
     for (Attack attack : attacks_){
         TiXmlDocument xml;
         std::unique_ptr<TiXmlElement> container(new TiXmlElement("Attack"));
@@ -348,7 +541,6 @@ void BattleDataInstaller::WriteAttacks(){
 
 void BattleDataInstaller::WriteFormations(){
     for (Formation formation : formations_){
-        //std::cout << "Writting enemy " << enemy.id << ": " << (output_dir_ + "game/enemy/" + std::to_string(enemy.id) + ".xml") << "\n";
         TiXmlDocument xml;
         std::unique_ptr<TiXmlElement> container(new TiXmlElement("Formation"));
         container->SetAttribute("id", formation.id);
@@ -459,7 +651,6 @@ File* BattleDataInstaller::ExtractGZipScene(File file){
     u8* extract_buffer = static_cast<u8*>(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.
@@ -506,7 +697,6 @@ File* BattleDataInstaller::ExtractGZipScene(File file){
                 std::cout << "Warning: inflate - Z_MEM_ERROR in file " << std::endl;
                 return NULL;
         }
-
         if (ret != Z_STREAM_END){
             extract_buffer = static_cast<u8*>(realloc(extract_buffer, extract_size * 2));
             if (extract_buffer == NULL){
@@ -528,3 +718,283 @@ File* BattleDataInstaller::ExtractGZipScene(File file){
     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.getPointer(), outdir + base_mesh_name + ".skeleton"
+    );
+    mesh->setSkeletonName("models/battle/entities/" + base_mesh_name + ".skeleton");
+    mesh_serializer.exportMesh(mesh.getPointer(), outdir + mesh->getName());
+    Ogre::Mesh::SubMeshIterator it(mesh->getSubMeshIterator());
+    Ogre::MaterialSerializer mat_ser;
+    size_t i(0);
+    std::set<std::string> textures;
+    while (it.hasMoreElements()){
+        Ogre::SubMesh *sub_mesh(it.getNext());
+        Ogre::MaterialPtr mat(Ogre::MaterialManager::getSingleton().getByName(
+          sub_mesh->getMaterialName())
+        );
+        if (mat != nullptr){
+            for (size_t techs = 0; techs < mat->getNumTechniques(); techs ++){
+                Ogre::Technique* tech = mat->getTechnique(techs);
+                if (tech){
+                    for (size_t pass_num = 0; pass_num < tech->getNumPasses(); pass_num ++){
+                        Ogre::Pass* pass = tech->getPass(pass_num);
+                        if (pass){
+                            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
+                                    // rather than tex files.
+                                    Ogre::String base_name;
+                                    VGears::StringUtil::splitBase(
+                                      unit->getTextureName(), base_name
+                                    );
+                                    unit->setTextureName(base_name + ".png");
+                                    textures.insert(unit->getTextureName());
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            if (std::count(materials_.begin(), materials_.end(), sub_mesh->getMaterialName()) == 0){
+                 << sub_mesh->getMaterialName() << std::endl;
+                mat_ser.queueForExport(mat);
+                materials_.push_back(sub_mesh->getMaterialName());
+            }
+        }
+        ++ i;
+    }
+    mat_ser.exportQueued(outdir + base_mesh_name + VGears::EXT_MATERIAL);
+    for (auto& texture_name : textures){
+        std::string tex_name = texture_name.c_str();
+        Ogre::String base_name;
+        VGears::StringUtil::splitBase(
+          tex_name, base_name
+        );
+        base_name += ".png";
+        try{
+            Ogre::TexturePtr texture_ptr
+              = Ogre::TextureManager::getSingleton().load(base_name, "FFVIITextures");
+            Ogre::Image image;
+            texture_ptr->convertToImage(image);
+            Ogre::String base_name;
+            VGears::StringUtil::splitBase(texture_name, base_name);
+            image.save(outdir + base_mesh_name + "_" + base_name + ".png");
+        }
+        catch (std::exception const& ex){
+            std::cerr << "[ERROR] Exception: " << ex.what() << std::endl;
+        }
+    }
+}
+
+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, 8);
+        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){
+    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 " + model.id + "aa\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, 8);
+            }
+            p_file_index ++;
+            decompiled += "\n\n";
+        }
+    }
+    std::ofstream out(path);
+    out << decompiled;
+    out.close();
+}
+
+void BattleDataInstaller::ExtractAFilesFromDAFile(File da, Model* model, std::string path){
+
+    struct Bone{
+        int raw[3]; // Z, Y, X.
+        float angle[3]; // Z, Y, X.
+    };
+
+    da.SetOffset(0);
+    int num_animations = da.readU32LE();
+    for (int anim = 0; anim < num_animations; anim ++){
+        std::string file_index_name = std::to_string(anim);
+        while (file_index_name.size() < 2) file_index_name = "0" + file_index_name;
+        std::ofstream a(
+          path + model->id + "_" + file_index_name + ".a", std::ios::out | std::ios::binary
+        );
+        model->a.push_back(path + model->id + "_" + file_index_name + ".a");
+        u32 zero = 0x00000000;
+        u32 version = 0x00000001;
+        u32 rotation_order = 0x00020001;
+        u32 bone_count = da.readU32LE() - 1;
+        u32 frames = da.readU32LE();
+        Bone bones[frames][bone_count];
+        a.write(reinterpret_cast<const char*>(&version), 4);
+        a.write(reinterpret_cast<const char*>(&frames), 4);
+        a.write(reinterpret_cast<const char*>(&bone_count), 4);
+        a.write(reinterpret_cast<const char*>(&rotation_order), 4);
+        a.write(reinterpret_cast<const char*>(&zero), 4);
+        a.write(reinterpret_cast<const char*>(&zero), 4);
+        a.write(reinterpret_cast<const char*>(&zero), 4);
+        a.write(reinterpret_cast<const char*>(&zero), 4);
+        a.write(reinterpret_cast<const char*>(&zero), 4);
+        int next_offset = da.GetCurrentOffset() + da.readU32LE() + 4;
+        da.readU16LE(); // Frames, again.
+        da.readU16LE(); // Size, again.
+        int key = da.readU8(); // Scale decoding key.
+        da.readU8(); // ???
+        da.readU32LE(); // ???
+        da.readU32LE(); // ???
+        int frame = 0;
+        while (frame < frames){
+            a.write(reinterpret_cast<const char*>(&zero), 4);
+            a.write(reinterpret_cast<const char*>(&zero), 4);
+            a.write(reinterpret_cast<const char*>(&zero), 4);
+            a.write(reinterpret_cast<const char*>(&zero), 4);
+            a.write(reinterpret_cast<const char*>(&zero), 4);
+            a.write(reinterpret_cast<const char*>(&zero), 4);
+            int bone = 0;
+            while (bone < bone_count){
+                if (bone == 0){
+                    // Uncompressed, 3 bytes per bone rotations. Read one bone.
+                    for (int d = 0; d < 3; d ++){ // Read Z, Y, X.
+                        int delta = da.readU16LE();
+                        delta = delta << key;
+                        bones[frame][bone].raw[d] = delta;
+                        bones[frame][bone].angle[d] = delta;
+                        if (bones[frame][bone].angle[d] < 0) bones[frame][bone].angle[d] += 0x1000;
+                        bones[frame][bone].angle[d]
+                          = bones[frame][bone].angle[d] / 4096.0f * 360.0f;
+                        a.write(reinterpret_cast<const char*>(&bones[frame][bone].angle[d]), 4);
+                    }
+                    bone ++;
+                }
+                else if (key == 0){
+                    // 12 bits per bone rotation, 36 bits per bone. Read two bones (9 bytes).
+                    u8 data[9];
+                    for (int d = 0; d < 9; d ++) data[d] = da.readU8();
+                    // Read two bones.
+                    float rot[6]; // Z, Y, X, Z, Y, X
+                    bones[frame][bone].raw[0] = data[0] * 16 + (data[1] >> 4);
+                    bones[frame][bone].raw[1] = (data[1] & 15) * 256 + data[2];
+                    bones[frame][bone].raw[2] = data[3] * 16 + (data[4] >> 4);
+                    bones[frame][bone + 1].raw[0] = (data[4] & 15) * 256 + data[5];
+                    bones[frame][bone + 1].raw[1] = data[6] * 16 + (data[7] >> 4);
+                    bones[frame][bone + 1].raw[2] = (data[7] & 15) * 256 + data[8];
+                    if (frame > 0){ // Frames 1 and up, sum to the previous frame.
+                        bones[frame][bone].raw[0] += bones[frame - 1][bone].raw[0];
+                        bones[frame][bone].raw[1] += bones[frame - 1][bone].raw[1];
+                        bones[frame][bone].raw[2] += bones[frame - 1][bone].raw[2];
+                        bones[frame][bone + 1].raw[0] += bones[frame - 1][bone + 1].raw[0];
+                        bones[frame][bone + 1].raw[1] += bones[frame - 1][bone + 1].raw[1];
+                        bones[frame][bone + 1].raw[2] += bones[frame - 1][bone + 1].raw[2];
+                    }
+
+                    // Quaternion to degree.
+                    for (int b = bone; b < bone + 2; b ++){
+                        for (int r = 0; r < 3; r ++){
+                            bones[frame][b].angle[r] = bones[frame][b].raw[r];
+                            if (bones[frame][b].angle[r] < 0) bones[frame][b].angle[r] += 0x1000;
+                            bones[frame][b].angle[r] = bones[frame][b].angle[r] / 4096.0f * 360.0f;
+                            a.write(reinterpret_cast<const char*>(&bones[frame][b].angle[r]), 4);
+                        }
+                    }
+                    bone += 2;
+
+                }
+                else if (key == 2){
+                    // 10 bits per bone rotation, 30 bits per bone. Read four bones (15 bytes).
+                    std::cout << "TODO: Decode bones with key 2.\n";
+                    // TODO
+                    bone += 4;
+                }
+                else if (key == 4){
+                    // 8 bits per bone rotation, 24 bits per bone. Read one bone (3 bytes).
+                    std::cout << "TODO: Decode bones with key 4.\n";
+                    // TODO: Fix this and do the same as key 0.
+                    u8 data[3];
+                    for (int d = 0; d < 3; d ++){
+                        if (bone + d < bone_count) data[d] = da.readU8();
+                        else data[d] = 0;
+                    }
+                    // Read two bones.
+                    float rot[3]; // Z, Y, X
+                    rot[0] = data[0];
+                    rot[1] = data[1];
+                    rot[2] = data[3];
+                    // Quaternion to degree.
+                    for (int d = 0; d < 3; d ++) rot[d] = rot[d] * 360 / 4096;
+                    // Write.
+                    if (bone < bone_count){
+                        a.write(reinterpret_cast<const char*>(&rot[0]), 4);
+                        a.write(reinterpret_cast<const char*>(&rot[1]), 4);
+                        a.write(reinterpret_cast<const char*>(&rot[2]), 4);
+                        bone ++;
+                    }
+                }
+                else{
+                    // This should not happen.
+                    bone ++;
+                }
+            }
+            frame ++;
+        }
+        a.close();
+        da.SetOffset(next_offset);
+    }
+}

+ 146 - 3
src/installer/BattleDataInstaller.h

@@ -21,6 +21,7 @@
 #include "common/BinGZipFile.h"
 #include "common/TypeDefine.h"
 #include "data/BattleSceneFile.h"
+#include "data/VGearsLGPArchive.h"
 
 /**
  * The battle data installer.
@@ -44,11 +45,18 @@ class BattleDataInstaller{
         ~BattleDataInstaller();
 
         /**
-         * Prepares the installer.
+         * Prepares the installer for scene processing.
          *
-         * @return The total number of scenes.
+         * @return The total number of scenes to process.
          */
-        unsigned int Initialize();
+        unsigned int InitializeScenes();
+
+        /**
+         * Prepares the installer for 3D model processing.
+         *
+         * @return The total number of models to process.
+         */
+        unsigned int InitializeModels();
 
         /**
          * Processes the next battle scene.
@@ -57,6 +65,30 @@ class BattleDataInstaller{
          */
         unsigned int ProcessScene();
 
+        /**
+         * Processes the 3D model.
+         *
+         * Saves .hrc, .a and .p files for later conversion. .tex files are converted directly to
+         * png.
+         *
+         * @return The total number of processed models.
+         */
+        unsigned int ProcessModel();
+
+        /**
+         * Prepres the installer for model conversion.
+         *
+         * @return The total number of models to convert.
+         */
+        unsigned int ConvertModelsInit();
+
+        /**
+         * Converts a model.
+         *
+         * @return The total number of converted models.
+         */
+        unsigned int ConvertModel();
+
         /**
          * Writes enemy data to files.
          */
@@ -74,6 +106,52 @@ class BattleDataInstaller{
 
     private:
 
+        /**
+         * Files to compose a model.
+         */
+        struct Model{
+
+            /**
+             * Model identifier, two letters.
+             */
+            std::string id;
+
+            /**
+             * Name of the model .hrc file
+             */
+            std::string hrc;
+
+            /**
+             * Name of the "da" animation file.
+             */
+            std::string anim;
+
+            /**
+             * List of .p polygon files associated to the model
+             */
+            std::vector<std::string> p;
+
+            /**
+             * List of .tex texture files associated to the model
+             */
+            std::vector<std::string> tex;
+
+            /**
+             * List of .a animation files associated to the model
+             */
+            std::vector<std::string> a;
+
+            /**
+             * Constructor, initializes default values.
+             */
+            Model(): id(""), hrc(""), anim(""){
+                p.clear();
+                tex.clear();
+                a.clear();
+            }
+
+        };
+
         /**
          * Extract a gzipped file.
          *
@@ -107,6 +185,35 @@ class BattleDataInstaller{
          */
         std::string BuildAttackFileName(Attack attack);
 
+        /**
+         * Exports a mesh to a file.
+         *
+         * The file will have the mesh name.
+         *
+         * @param[in] outdir Path to the directory where the file will be saved.
+         * @param[in] mesh The mesh to export.
+         */
+        void ExportMesh(const std::string outdir, const Ogre::MeshPtr &mesh);
+
+        /**
+         * Generates all required .rsd models for a model.
+         */
+        void GenerateRsdFiles(Model model, std::string path);
+
+        /**
+         * Decompiles a compiled HRC file.
+         *
+         * 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] model The model.
+         * @param[in] path PAth to save the decompiled file to.
+         */
+        void DecompileHrc(File compiled, Model model, std::string path);
+
+        void ExtractAFilesFromDAFile(File da, Model* model, std::string path);
+
         /**
          * The path to the directory from which to read the PC game data.
          */
@@ -117,6 +224,11 @@ class BattleDataInstaller{
          */
         std::string output_dir_;
 
+        /**
+         * Function used to print text to the log output, line by line.
+         */
+        std::function<void(std::string)> write_output_line_;
+
         /**
          * Next scene file to process;
          */
@@ -172,4 +284,35 @@ class BattleDataInstaller{
          */
         std::map<int, std::string> formation_map_;
 
+        /**
+         * The files in the original battle.lgp file.
+         */
+        std::vector<File> battle_lgp_files_;
+
+        /**
+         * File names for the files in {@see battle_lgp_files_}.
+         */
+        std::vector<std::string> battle_lgp_file_names_;
+
+        /**
+         * Next model to process;
+         */
+        unsigned int next_model_to_process_;
+
+        /**
+         * Next model to convert.
+         */
+        unsigned int next_model_to_convert_;
+
+        /**
+         * List of models files found in battle.lgp
+         */
+        std::vector<Model> models_;
+
+        /**
+         * List of model materials;
+         */
+        std::vector<std::string> materials_;
+
+
 };

+ 49 - 16
src/installer/DataInstaller.cpp

@@ -39,7 +39,6 @@ DataInstaller::DataInstaller(
     // Assign weights.
     for (int i = IDLE; i < STATE_COUNT; i ++) step_weight_[i] = 1;
     step_weight_[IDLE] = 0;
-    step_weight_[BATTLE_SCENES] = 1;
     step_weight_[MEDIA_IMAGES] = 3;
     step_weight_[MEDIA_SOUNDS] = 8;
     step_weight_[MEDIA_MUSICS] = 9;
@@ -70,40 +69,58 @@ float DataInstaller::Progress(){
               options_.no_ffmpeg, options_.no_timidity
             );
             field_installer_ = std::make_unique<FieldDataInstaller>(input_dir_, output_dir_);
-            std::cout << "CREATE BATTLE INSTALLER: " << (input_dir_ + "data/battle/scene.bin") << "\n";
             battle_installer_ = std::make_unique<BattleDataInstaller>(input_dir_, output_dir_);
-            installation_state_ = BATTLE_INIT;
+            installation_state_ = BATTLE_SCENES_INIT;
             return CalcProgress();
-        case BATTLE_INIT:
+        case BATTLE_SCENES_INIT:
             write_output_line_("Parsing battle scenes...", 2, true);
-            substeps_ = battle_installer_->Initialize();
+            substeps_ = battle_installer_->InitializeScenes();
             cur_substep_ = 0;
-            installation_state_ = BATTLE_SCENES;
+            installation_state_ = BATTLE_SCENES_PROCESS;
             return CalcProgress();
-        case BATTLE_SCENES:
+        case BATTLE_SCENES_PROCESS:
             cur_substep_ = battle_installer_->ProcessScene();
-            if (cur_substep_ >= substeps_) installation_state_ = BATTLE_WRITE_ATTACKS;
+            if (cur_substep_ >= substeps_) installation_state_ = BATTLE_SCENES_WRITE_ATTACKS;
             return CalcProgress();
-        case BATTLE_WRITE_ATTACKS:
+        case BATTLE_SCENES_WRITE_ATTACKS:
             write_output_line_("Saving attacks...", 2, true);
             cur_substep_ = 0;
             substeps_ = 0;
             battle_installer_->WriteAttacks();
-            installation_state_ = BATTLE_WRITE_ENEMIES;
+            installation_state_ = BATTLE_SCENES_WRITE_ENEMIES;
             return CalcProgress();
-        case BATTLE_WRITE_ENEMIES:
+        case BATTLE_SCENES_WRITE_ENEMIES:
             write_output_line_("Saving enemies...", 2, true);
             cur_substep_ = 0;
             substeps_ = 0;
             battle_installer_->WriteEnemies();
-            installation_state_ = BATTLE_WRITE_FORMATIONS;
+            installation_state_ = BATTLE_SCENES_WRITE_FORMATIONS;
             return CalcProgress();
-        case BATTLE_WRITE_FORMATIONS:
+        case BATTLE_SCENES_WRITE_FORMATIONS:
             write_output_line_("Saving enemy formations...", 2, true);
             cur_substep_ = 0;
             substeps_ = 0;
             battle_installer_->WriteFormations();
-            installation_state_ = KERNEL_PRICES;
+            installation_state_ = BATTLE_MODELS_INIT;
+            return CalcProgress();
+        case BATTLE_MODELS_INIT:
+            write_output_line_("Extracting battle models...", 2, true);
+            substeps_ = battle_installer_->InitializeModels();
+            cur_substep_ = 0;
+            installation_state_ = BATTLE_MODELS_PROCESS;
+            return CalcProgress();
+        case BATTLE_MODELS_PROCESS:
+            cur_substep_ = battle_installer_->ProcessModel();
+            if (cur_substep_ >= substeps_) installation_state_ = BATTLE_MODELS_CONVERT_INIT;
+            return CalcProgress();
+        case BATTLE_MODELS_CONVERT_INIT:
+            cur_substep_ = 0;
+            substeps_ =  battle_installer_->ConvertModelsInit();
+            installation_state_ = BATTLE_MODELS_CONVERT;
+            return CalcProgress();
+        case BATTLE_MODELS_CONVERT:
+            cur_substep_ = battle_installer_->ConvertModel();
+            if (cur_substep_ >= substeps_) installation_state_ = KERNEL_PRICES;
             return CalcProgress();
         case KERNEL_PRICES:
             // Skip kernel data if option is set.
@@ -351,6 +368,8 @@ const float DataInstaller::CalcProgress(){
 
 void DataInstaller::CreateDirectories(){
     CreateDir("temp");
+    CreateDir("temp/char");
+    CreateDir("temp/battle_models");
     CreateDir("game");
     CreateDir("game/enemy");
     CreateDir("game/attack");
@@ -362,10 +381,24 @@ void DataInstaller::CreateDirectories(){
     CreateDir("images/reels");
     CreateDir("images/window");
     CreateDir("models/fields/entities");
+    CreateDir("models/battle/entities");
     CreateDir("audio/sound");
     CreateDir("audio/music");
-    application_.ResMgr()->addResourceLocation("data/temp/char/", "FileSystem", "FFVII", true, true);
-    application_.ResMgr()->addResourceLocation("data/models/", "FileSystem", "FFVII", true, true);
+    application_.ResMgr()->addResourceLocation(
+      output_dir_ + "temp/char/", "FileSystem", "FFVII", true, true
+    );
+    application_.ResMgr()->addResourceLocation(
+      output_dir_ + "temp/battle_models/", "FileSystem", "FFVII", true, true
+    );
+    application_.ResMgr()->addResourceLocation(
+      output_dir_ + "models/", "FileSystem", "FFVII", true, true
+    );
+    application_.ResMgr()->addResourceLocation(
+      output_dir_ + "models/fields/entities/", "FileSystem", "FFVIITextures", true, true
+    );
+    application_.ResMgr()->addResourceLocation(
+      output_dir_ + "models/battle/entities/", "FileSystem", "FFVIITextures", true, true
+    );
     fields_lgp_ = std::make_unique<ScopedLgp>(
       application_.getRoot(), input_dir_ + "data/field/flevel.lgp", "LGP", "FFVIIFields"
     );

+ 26 - 6
src/installer/DataInstaller.h

@@ -166,29 +166,49 @@ class DataInstaller{
             INITIALIZE,
 
             /**
-             * Initializes the battle installer.
+             * Initializes the battle installer for scene processing.
              */
-            BATTLE_INIT,
+            BATTLE_SCENES_INIT,
 
             /**
              * Extracts battle scenes.
              */
-            BATTLE_SCENES,
+            BATTLE_SCENES_PROCESS,
 
             /**
              * Writes attack data.
              */
-            BATTLE_WRITE_ATTACKS,
+            BATTLE_SCENES_WRITE_ATTACKS,
 
             /**
              * Writes enemy data.
              */
-            BATTLE_WRITE_ENEMIES,
+            BATTLE_SCENES_WRITE_ENEMIES,
 
             /**
              * Writes enemy formation data.
              */
-            BATTLE_WRITE_FORMATIONS,
+            BATTLE_SCENES_WRITE_FORMATIONS,
+
+            /**
+             * Initializes the battle installer for 3D model processing.
+             */
+            BATTLE_MODELS_INIT,
+
+            /**
+             * Extracts 3D battle models.
+             */
+            BATTLE_MODELS_PROCESS,
+
+            /**
+             * Prepares for 3D battle model conversion.
+             */
+            BATTLE_MODELS_CONVERT_INIT,
+
+            /**
+             * Converts 3D battle models.
+             */
+            BATTLE_MODELS_CONVERT,
 
             /**
              * Parses item and materia prices from ff7.exe.

+ 7 - 12
src/installer/FieldDataInstaller.cpp

@@ -219,8 +219,8 @@ int FieldDataInstaller::CollectSpawnAndScaleFactorsInit(Ogre::ResourceGroupManag
     flevel_file_list_ = res_mgr->listResourceNames("FFVIIFields", "*");
     // Load the map list field.
     VGears::MapListFilePtr map_list = VGears::MapListFileManager::GetSingleton().load(
-        "maplist", "FFVIIFields"
-      ).staticCast<VGears::MapListFile>();
+      "maplist", "FFVIIFields"
+    ).staticCast<VGears::MapListFile>();
     map_list_ = map_list->GetMapList();
     return flevel_file_list_->size();
 }
@@ -258,7 +258,6 @@ void FieldDataInstaller::Convert(int field_index){
         // TODO: DEBUG: Only test fields
         if (IsTestField(resource_name) && !WillCrash(resource_name)){
             //write_output_line_("Converting field " + resource_name);
-            std::cout << " - Converting field: " << resource_name << std::endl;
             CreateDir(FIELD_MAPS_DIR + "/" + resource_name);
             VGears::FLevelFilePtr field = VGears::LZSFLevelFileManager::GetSingleton().load(
                 resource_name, "FFVIIFields"
@@ -301,7 +300,6 @@ void FieldDataInstaller::WriteEnd(){
 }
 
 std::vector<std::string> FieldDataInstaller::ConvertModelsInit(){
-
     std::vector<std::string> models;
 
     // Open char_lgp as a lgp archive
@@ -324,7 +322,9 @@ std::vector<std::string> FieldDataInstaller::ConvertModelsInit(){
         out.close();
         //field_model_file_list_->push_back(f.file_name);
     }
-    for (auto it = used_models_and_anims_.map.begin(); it != used_models_and_anims_.map.end(); it ++){
+    for (
+      auto it = used_models_and_anims_.map.begin(); it != used_models_and_anims_.map.end(); it ++
+    ){
         models.push_back(it->first);
     }
     return models;
@@ -351,16 +351,13 @@ void FieldDataInstaller::ConvertModels(std::string model_name){
     }
     catch (const Ogre::Exception& ex){
         write_output_line_(
-          "[ERROR] Ogre exception converting model "
-          + model->first + ": " + ex.what()
+          "[ERROR] Ogre exception converting model " + model->first + ": " + ex.what()
         );
         std::cerr << "[ERROR] Ogre exception converting model "
           << model->first <<": " << ex.what() << std::endl;
     }
     catch (const std::exception& ex){
-        write_output_line_(
-          "[ERROR] Exception converting model " + model->first + ": " + ex.what()
-        );
+        write_output_line_("[ERROR] Exception converting model " + model->first + ": " + ex.what());
         std::cerr << "[ERROR] Exception converting model "
           << model->first << ": " << ex.what() << std::endl;
     }
@@ -440,8 +437,6 @@ void FieldDataInstaller::ExportMesh(const std::string outdir, const Ogre::MeshPt
                 }
             }
             if (std::count(materials_.begin(), materials_.end(), sub_mesh->getMaterialName()) == 0){
-                //std::cout << "[MATERIAL] Writting material "
-                // << sub_mesh->getMaterialName() << std::endl;
                 mat_ser.queueForExport(mat);
                 materials_.push_back(sub_mesh->getMaterialName());
             }

+ 5 - 1
src/installer/MainWindow.cpp

@@ -240,7 +240,10 @@ void MainWindow::on_btn_data_run_clicked(){
             );
             OnInstallStarted();
         }
-        catch (const std::exception& ex){OnInstallStopped();}
+        catch (const std::exception& ex){
+            std::cout << "Exception: " << ex.what() << std::endl;
+            OnInstallStopped();
+        }
     }
 }
 
@@ -277,6 +280,7 @@ void MainWindow::DoProgress(){
         if (progress >= 100) OnInstallStopped();
     }
     catch (const std::exception& ex){
+        std::cout << "Exception: " << ex.what() << std::endl;
         OnInstallStopped();
         QMessageBox::critical(this, tr("Data conversion exception"), ex.what());
     }

+ 44 - 9
src/installer/TexFile.cpp

@@ -17,12 +17,25 @@
 #include <iostream>
 #include <OgreImage.h>
 #include <OgreColourValue.h>
-#include "common/File.h"
 #include "TexFile.h"
 
 TexFile::TexFile(std::string path){
     File file(path);
+    Read(file);
+}
+
+TexFile::TexFile(File file){
+    Read(file);
+}
+
+void TexFile::Read(File file){
+    file.SetOffset(0);
     version_ = file.readU32LE();
+    if (version_ != 0x00000001){
+        std::cout << "[WARNING] Tried to read an invalid TEX file to png. Version ID: "
+          << version_ << std::endl;
+        return;
+    }
     unknown_0_ = file.readU32LE();
     colour_key_ = file.readU32LE();
     unknown_1_ = file.readU32LE();
@@ -83,6 +96,8 @@ TexFile::TexFile(std::string path){
     unknown_9_ = file.readU32LE();
     if (has_palette_ == 0){
         // Non palleted images.
+        // Skip unused pallete data.
+        for (int c = 0; c < palette_size_; c ++) file.readU32LE();
         // Read 32 byte colours in format BGRA.
         for (int p = 0; p < width_ * height_; p ++){
             float b = file.readU8() / 255.0f;
@@ -95,11 +110,11 @@ TexFile::TexFile(std::string path){
     }
     else {
         // Palleted_images.
-
+        palette_count_ = palette_size_ / palette_colour_count_;
         // Read palette data.
         for (int c = 0; c < palette_count_; c ++){
             std::vector<Ogre::ColourValue> palette_colours;
-            for (int p = 0; p < colours_per_palette_; p ++){
+            for (int p = 0; p < palette_colour_count_; p ++){
                 float b = file.readU8() / 255.0f;
                 float g = file.readU8() / 255.0f;
                 float r = file.readU8() / 255.0f;
@@ -110,8 +125,7 @@ TexFile::TexFile(std::string path){
             palettes_.push_back(palette_colours);
         }
         // Read bytes as references to a palette colour.
-        for (int p = 0; p < width_ * height_; p ++)
-            pixel_ref_.push_back(file.readU8());
+        for (int p = 0; p < width_ * height_; p ++) pixel_ref_.push_back(file.readU8());
     }
 }
 
@@ -121,6 +135,12 @@ void TexFile::SavePng(
   std::string file_name, unsigned int x, unsigned int y, unsigned int w, unsigned int h,
   unsigned int palette
 ){
+    if (version_ != 0x00000001){
+        std::cout
+          << "[WARNING] Tried to convert an invalid TEX file to png. Version ID: "
+          << version_ << std::endl;
+        return;
+    }
     // TODO: Warn if wrong palette index.
     Ogre::Image* image = new Ogre::Image(Ogre::PF_R8G8B8A8_UINT, w, h);
     int target_x = 0;
@@ -139,10 +159,19 @@ void TexFile::SavePng(
                 a = pixel_colour_.at(i).a;
             }
             else{
-                r = palettes_.at(palette).at(pixel_ref_.at(i)).r;
-                g = palettes_.at(palette).at(pixel_ref_.at(i)).g;
-                b = palettes_.at(palette).at(pixel_ref_.at(i)).b;
-                a = palettes_.at(palette).at(pixel_ref_.at(i)).a;
+                if (palettes_.at(palette).size() <= pixel_ref_.at(i)){
+                    // If invalid index, use alpha
+                    r = 0;
+                    g = 0;
+                    b = 0;
+                    a = 0;
+                }
+                else{
+                    r = palettes_.at(palette).at(pixel_ref_.at(i)).r;
+                    g = palettes_.at(palette).at(pixel_ref_.at(i)).g;
+                    b = palettes_.at(palette).at(pixel_ref_.at(i)).b;
+                    a = palettes_.at(palette).at(pixel_ref_.at(i)).a;
+                }
             }
             image->setColourAt(Ogre::ColourValue(r, g, b, a), target_x, target_y, 0);
             target_x ++;
@@ -163,6 +192,12 @@ void TexFile::SavePng(
   std::string file_name, unsigned int x1, unsigned int x2, unsigned int y1, unsigned int y2,
   unsigned int w1, unsigned int w2, unsigned int h, unsigned int palette
 ){
+    if (version_ != 0x00000001){
+        std::cout
+          << "[WARNING] Tried to convert an invalid TEX file to png. Version ID: "
+          << version_ << std::endl;
+        return;
+    }
     // TODO: Warn if wrong palette index.
     Ogre::Image* image = new Ogre::Image(Ogre::PF_R8G8B8A8_UINT, w1 + w2, h);
     int target_x = 0;

+ 26 - 2
src/installer/TexFile.h

@@ -16,13 +16,30 @@
 #pragma once
 
 #include "common/TypeDefine.h"
+#include "common/File.h"
 
 class TexFile{
 
     public:
 
+        /**
+         * Constructor.
+         *
+         * @param[in,out] File with the tex data. The file data will not be modified, but it's
+         * offset will.
+         */
+        TexFile(File file);
+
+        /**
+         * Constructor.
+         *
+         * @param[in] path Path to the tex file.
+         */
         TexFile(std::string path);
 
+        /**
+         * Destructor.
+         */
         ~TexFile();
 
         /**
@@ -69,12 +86,19 @@ class TexFile{
          */
         void SavePng(
           std::string file_name, unsigned int x1, unsigned int x2, unsigned int y1, unsigned int y2,
-          unsigned int w1, unsigned int w2, unsigned int h, unsigned int palette
+          unsigned int w1, unsigned int w2, unsigned int h, unsigned int palette = 0
         );
 
     private:
 
-        // HEADER
+        /**
+         * Reads data from a file.
+         *
+         * Called from constructors.
+         *
+         * @param[in] file The file to read from.
+         */
+        void Read(File file);
 
         /**
          * File format version. Always 1. 4 bytes.

+ 1 - 4
src/main.cpp

@@ -54,7 +54,6 @@
  */
 int main(int argc, char *argv[]){
     try{
-        std::cout << "V-Gears Init" << std::endl;
         VGears::Application app(argc, argv);
         if (!app.initOgre()) return 0;
         Ogre::Root *root(app.getRoot());
@@ -72,9 +71,7 @@ int main(int argc, char *argv[]){
         // TODO: Why is this used twice?
         scene_manager->setAmbientLight(Ogre::ColourValue(1, 1, 1));
         scene_manager->setAmbientLight(Ogre::ColourValue(0.5, 0.5, 0.5));
-        Ogre::Light *directionalLight(
-          scene_manager->createLight("directionalLight")
-        );
+        Ogre::Light *directionalLight(scene_manager->createLight("directionalLight"));
         directionalLight->setType(Ogre::Light::LT_DIRECTIONAL);
         directionalLight->setDiffuseColour(Ogre::ColourValue(0.5, 0.5, 0.5));
         directionalLight->setSpecularColour(Ogre::ColourValue(0.0, 0.0, 0.0));