Bläddra i källkod

Installer improvements.

- Added SFXDump library to handle sound effect extraction.
- World map data installer created. Data partially extracted during installation.
- World map options added to the installer menu.
Iñigo Valentin 2 år sedan
förälder
incheckning
f1ae554e9d

+ 4 - 1
.gitignore

@@ -8,9 +8,12 @@
 *.filters
 *.lib
 *.log
+*.xcf
 **/ogre.cfg
 **/CMakeCache.txt
 build/
+tools/
+_OLD/
 output/share
 output/include
 output/lib
@@ -19,4 +22,4 @@ doc/FFVII/RAW/
 .settings/
 .project
 .cproject
-
+v-gears-new*.png

+ 1 - 1
CMakeLists.txt

@@ -19,7 +19,7 @@ project(V-Gears)
 set(CMAKE_PACKAGE_ICON v-gears.png)
 set(VGEARS_VERSION_MAJOR 0)
 set(VGEARS_VERSION_MINOR 1)
-set(VGEARS_VERSION_PATCH 1)
+set(VGEARS_VERSION_PATCH 17)
 set(VGEARS_VERSION ${VGEARS_VERSION_MAJOR}.${VGEARS_VERSION_MINOR}.${VGEARS_VERSION_PATCH})
 
 

+ 3 - 0
data/data/wm/README.txt

@@ -0,0 +1,3 @@
+This directory is intentionally empty. It will be populated by the data installer.
+
+Once the data installation is complete, the 3D world map files will be installed here.

+ 3 - 1
lib/CMakeLists.txt

@@ -7,7 +7,9 @@ include_directories(
 
 add_subdirectory(luajit) # LuaJIT has it's own configuration.
 
+add_subdirectory(sfxdump) # SFXDump has it's own configuration.
+
 aux_source_directory(luabind/luabind LUABIND_HEADER_FILES)
 aux_source_directory(luabind/luabind/details LUABIND_HEADER_DETAILS_FILES)
 aux_source_directory(luabind/src LUABIND_SRC_FILES)
-add_library(libluabind STATIC ${LUABIND_SRC_FILES} ${LUABIND_HEADER_FILES} ${LUABIND_HEADER_DETAILS_FILES})
+add_library(libluabind STATIC ${LUABIND_SRC_FILES} ${LUABIND_HEADER_FILES} ${LUABIND_HEADER_DETAILS_FILES})

+ 1 - 0
lib/sfxdump/CMakeLists.txt

@@ -0,0 +1 @@
+add_executable ( sfxdump sfxdump.c )

+ 120 - 0
lib/sfxdump/sfxdump.c

@@ -0,0 +1,120 @@
+#include "structs.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+
+
+Fmt_chunk read_Fmt_chunk(FILE* fmt)
+{
+	Fmt_chunk chunk = {.size = sizeof chunk.adpcm};
+
+	memcpy(chunk.id, "fmt ", sizeof chunk.id);
+	fread(&chunk.adpcm, sizeof chunk.adpcm, 1, fmt);
+
+	return chunk;
+}
+
+
+
+Loop_chunk read_Loop_chunk(FfWav_header const* header)
+{
+	Loop_chunk chunk = {
+		.size  = sizeof(uint32_t) * 2,
+		.start = header->start,
+		.end   = header->end
+	};
+
+	memcpy(chunk.id, "fflp", sizeof chunk.id);
+
+	return chunk;
+}
+
+
+
+Data_chunk* read_Data_chunk(FfWav_header const* header, FILE* data)
+{
+	Data_chunk* chunk = malloc(sizeof *chunk * header->len);
+
+	memcpy(chunk->id, "data", sizeof chunk->id);
+	chunk->size = header->len;
+	fread(chunk->data, header->len, 1, data);
+
+	return chunk;
+}
+
+
+
+Riff_header init_riff_header()
+{
+	Riff_header riff;
+	memcpy(&riff.id, "RIFF", sizeof riff.id);
+	memcpy(&riff.format, "WAVE", sizeof riff.format);
+	riff.size = 0;
+	return riff;
+}
+
+
+
+
+int main(int argc, char* argv[])
+{
+	if (argc != 4) {
+		printf("Usage: sfxdump fmt_path dat_path target_dir");
+		return 1;
+	}
+
+	FILE* fmt  = fopen(argv[1], "rb");
+	FILE* dat  = fopen(argv[2], "rb");
+
+	if (!fmt || !dat) {
+		printf("Could not open .fmt and / or .dat file");
+		return 1;
+	}
+
+	//printf("Dumping sfx 0 - 750 to %s\n", argv[3]);
+
+	for (int count = 0; count < 750; ++count) {
+
+		FfWav_header header = {0};
+		fread(&header, sizeof header, 1, fmt);
+
+		if (!header.len) {
+			fseek(fmt, sizeof(WAVEFORMATEX), SEEK_CUR);
+			continue;
+		}
+
+		Riff_header riff = init_riff_header();
+		Fmt_chunk format = read_Fmt_chunk(fmt);
+		Loop_chunk loop  = read_Loop_chunk(&header);
+		Data_chunk* data = read_Data_chunk(&header, dat);
+		riff.size = sizeof riff.format + sizeof format + sizeof *data + data->size;
+
+		if (header.loop)
+			riff.size += sizeof loop;
+
+		char path[260];
+		sprintf(path, "%s/%d.wav", argv[3], count);
+		FILE* out_wav = fopen(path, "wb");
+
+		if (!out_wav) {
+			//printf("Error opening %s\n", path);
+			continue;
+		}
+
+		fwrite(&riff, sizeof riff, 1, out_wav);
+		fwrite(&format, sizeof format, 1, out_wav);
+		fwrite(data, sizeof *data + data->size, 1, out_wav);
+
+		if (header.loop) {
+			//printf("Appending loop data for %d\n", count);
+			fwrite(&loop, sizeof loop, 1, out_wav);
+		}
+
+		fclose(out_wav);
+		free(data);
+	}
+
+	fclose(fmt);
+	fclose(dat);
+}

+ 70 - 0
lib/sfxdump/structs.h

@@ -0,0 +1,70 @@
+#pragma once
+#include <stdint.h>
+
+#pragma pack(push, 1)
+
+typedef struct
+{
+	int16_t iCoef1;
+	int16_t iCoef2;
+} ADPCMCOEFSET;
+
+typedef struct
+{
+  uint16_t wFormatTag;
+  uint16_t nChannels;
+  uint32_t nSamplesPerSec;
+  uint32_t nAvgBytesPerSec;
+  uint16_t nBlockAlign;
+  uint16_t wBitsPerSample;
+  uint16_t cbSize;
+} WAVEFORMATEX;
+
+typedef struct
+{
+	WAVEFORMATEX wfx;
+	uint16_t wSamplesPerBlock;
+	uint16_t wNumCoef;
+	ADPCMCOEFSET aCoef[7];
+} ADPCMWAVEFORMAT;
+
+typedef struct
+{
+	char id[4];
+	uint32_t size;
+	ADPCMWAVEFORMAT adpcm;
+} Fmt_chunk;
+
+typedef struct
+{
+	char id[4];
+	uint32_t size;
+	uint32_t start;
+	uint32_t end;
+} Loop_chunk;
+
+typedef struct
+{
+	char id[4];
+	uint32_t size;
+	char data[];
+} Data_chunk;
+
+typedef struct
+{
+	char id[4];
+	uint32_t size;
+	char format[4];
+} Riff_header;
+
+typedef struct
+{
+	uint32_t len;
+	uint32_t offset;
+	uint32_t loop;
+	uint32_t count;
+	uint32_t start;
+	uint32_t end;
+} FfWav_header;
+
+#pragma pack(pop)

+ 3 - 3
src/Version.h

@@ -22,8 +22,8 @@
 /**
  * Application version.
  */
-#define VGEARS_VERSION "0.1.1"
+#define VGEARS_VERSION "0.1.17"
 #define VGEARS_VERSION_MAJOR "0"
 #define VGEARS_VERSION_MINOR "1"
-#define VGEARS_VERSION_PATCH "1"
-#define VGEARS_VERSION_SIGNATURE "V-Gears v0.1.1"
+#define VGEARS_VERSION_PATCH "17"
+#define VGEARS_VERSION_SIGNATURE "V-Gears v0.1.17"

+ 13 - 37
src/data/VGearsHRCFile.cpp

@@ -17,7 +17,6 @@
 #include <OgreMeshManager.h>
 #include <OgreSkeletonManager.h>
 #include "data/VGearsHRCFile.h"
-//#include "common/FF7NameLookup.h"
 #include "common/FinalFantasy7/FF7NameLookup.h"
 #include "common/VGearsStringUtil.h"
 #include "data/VGearsHRCFileSerializer.h"
@@ -33,28 +32,24 @@ namespace VGears{
 
 
     HRCFile::HRCFile(
-      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),
        mesh_loader_(nullptr), skeleton_loader_(nullptr)
     {createParamDictionary(RESOURCE_TYPE);}
 
     HRCFile::~HRCFile(){
-        if(skeleton_loader_){
-            Ogre::SkeletonManager::getSingleton().remove(
-              skeleton_->getHandle()
-            );
+        if (skeleton_loader_){
+            Ogre::SkeletonManager::getSingleton().remove(skeleton_->getHandle());
             delete skeleton_loader_;
             skeleton_loader_ = nullptr;
         }
-        if(mesh_loader_){
+        if (mesh_loader_){
             Ogre::MeshManager::getSingleton().remove(mesh_->getHandle());
             delete mesh_loader_;
             mesh_loader_ = nullptr;
         }
-
         skeleton_.reset();
         mesh_.reset();
         unload();
@@ -63,22 +58,16 @@ namespace VGears{
     void HRCFile::loadImpl(){
         HRCFileSerializer serializer;
         Ogre::DataStreamPtr stream(
-          Ogre::ResourceGroupManager::getSingleton().openResource(
-            mName, mGroup, true, this
-          )
+          Ogre::ResourceGroupManager::getSingleton().openResource(mName, mGroup, true, this)
         );
         serializer.ImportHRCFile(stream, this);
         const String skeletonfile_name_(GetSkeletonFileName());
-        Ogre::SkeletonManager &skeleton_manager(
-          Ogre::SkeletonManager::getSingleton()
-        );
+        Ogre::SkeletonManager &skeleton_manager(Ogre::SkeletonManager::getSingleton());
         skeleton_ = skeleton_manager.getByName(skeletonfile_name_, mGroup);
         if (skeleton_ == nullptr){
             assert(skeleton_loader_ == nullptr);
             skeleton_loader_ = new HRCSkeletonLoader(*this);
-            skeleton_ = skeleton_manager.create(
-              skeletonfile_name_, mGroup, true, skeleton_loader_
-            );
+            skeleton_ = skeleton_manager.create(skeletonfile_name_, mGroup, true, skeleton_loader_);
         }
         const String meshfile_name_(GetMeshFileName());
         Ogre::LogManager::getSingleton().stream()
@@ -88,9 +77,7 @@ namespace VGears{
         if (mesh_ == nullptr){
             assert(mesh_loader_ == nullptr);
             mesh_loader_ = new HRCMeshLoader(*this);
-            mesh_ = mesh_manager.create(
-              meshfile_name_, mGroup, true, mesh_loader_
-            );
+            mesh_ = mesh_manager.create(meshfile_name_, mGroup, true, mesh_loader_);
         }
     }
 
@@ -102,27 +89,16 @@ namespace VGears{
     size_t HRCFile::CalculateSize(const Bone &bone) const{
         size_t size_rsd_names(0);
         for(
-          RSDNameList::const_iterator it(bone.rsd_names.begin());
-          it != bone.rsd_names.end();
-          ++ it
-        ){
-            size_rsd_names += it->size();
-        }
+          RSDNameList::const_iterator it(bone.rsd_names.begin()); it != bone.rsd_names.end(); ++ it
+        ){size_rsd_names += it->size();}
 
-        return
-          bone.name.size() + bone.parent.size() + sizeof(bone.length)
-            + size_rsd_names;
+        return bone.name.size() + bone.parent.size() + sizeof(bone.length) + size_rsd_names;
     }
 
     size_t HRCFile::CalculateSize() const{
         size_t size_bones(0);
-        for(
-          BoneList::const_iterator it(bones_.begin());
-          it != bones_.end();
-          ++ it
-        ){
+        for(BoneList::const_iterator it(bones_.begin()); it != bones_.end(); ++ it)
             size_bones += CalculateSize(*it);
-        }
         return skeleton_name_.size() + size_bones;
     }
 

+ 12 - 19
src/data/VGearsHRCFile.h

@@ -43,25 +43,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.
              */
             HRCFile(
-              Ogre::ResourceManager *creator, const String &name,
-              Ogre::ResourceHandle handle, const String &group,
-              bool is_manual = false, Ogre::ManualResourceLoader *loader = NULL
+              Ogre::ResourceManager *creator, const String &name, Ogre::ResourceHandle handle,
+              const String &group, bool is_manual = false, Ogre::ManualResourceLoader *loader = NULL
             );
 
             /**
@@ -104,7 +99,7 @@ namespace VGears{
             typedef std::vector<Bone> BoneList;
 
             /**
-             * Sets a name for the skelenton.
+             * Sets a name for the skeleton.
              *
              * @param[in] name The name for the skeleton.
              */
@@ -115,9 +110,7 @@ namespace VGears{
              *
              * @return The skeleton name.
              */
-            virtual const String& GetSkeletonName() const{
-                return skeleton_name_;
-            }
+            virtual const String& GetSkeletonName() const{return skeleton_name_;}
 
             /**
              * Retrieves the skeleton file name.

+ 8 - 19
src/data/VGearsHRCFileManager.cpp

@@ -19,8 +19,7 @@
 /**
  * HRC file manager singleton.
  */
-template<> VGears::HRCFileManager
-  *Ogre::Singleton<VGears::HRCFileManager>::msSingleton = nullptr;
+template<> VGears::HRCFileManager *Ogre::Singleton<VGears::HRCFileManager>::msSingleton = nullptr;
 
 namespace VGears{
 
@@ -36,31 +35,21 @@ namespace VGears{
         // Low, because it will likely reference other resources.
         mLoadOrder = 30.0f;
         // this is how the ResourceManager registers with OGRE.
-        Ogre::ResourceGroupManager::getSingleton()._registerResourceManager(
-          mResourceType, this
-        );
+        Ogre::ResourceGroupManager::getSingleton()._registerResourceManager(mResourceType, this);
     }
 
     HRCFileManager::~HRCFileManager(){
-        Ogre::ResourceGroupManager::getSingleton()._unregisterResourceManager(
-          mResourceType
-        );
+        Ogre::ResourceGroupManager::getSingleton()._unregisterResourceManager(mResourceType);
     }
 
-    void HRCFileManager::ParseScript(
-      Ogre::DataStreamPtr &stream, const String &group_name
-    ){
-        HRCFilePtr  hrc
-          = createResource(stream->getName(), group_name).staticCast<HRCFile>();
+    void HRCFileManager::ParseScript( Ogre::DataStreamPtr &stream, const String &group_name){
+        HRCFilePtr hrc = createResource(stream->getName(), group_name).staticCast<HRCFile>();
         hrc->load();
     }
 
     Ogre::Resource* HRCFileManager::createImpl(
-      const String &name, Ogre::ResourceHandle handle, const String &group,
-      bool is_manual, Ogre::ManualResourceLoader *loader,
-      const Ogre::NameValuePairList *create_params
-    ){
-        return new HRCFile(this, name, handle, group, is_manual, loader);
-    }
+      const String &name, Ogre::ResourceHandle handle, const String &group, bool is_manual,
+      Ogre::ManualResourceLoader *loader, const Ogre::NameValuePairList *create_params
+    ){return new HRCFile(this, name, handle, group, is_manual, loader);}
 
 }

+ 11 - 17
src/data/VGearsHRCFileManager.h

@@ -44,11 +44,9 @@ namespace VGears{
              * Parses a HRC script and creates a skeleton.
              *
              * @param[in] stream Script content.
-             * @param[in] group_name GRoup to add the skeleton to.
+             * @param[in] group_name Group to add the skeleton to.
              */
-            virtual void ParseScript(
-              Ogre::DataStreamPtr &stream, const String &group_name
-            );
+            virtual void ParseScript(Ogre::DataStreamPtr &stream, const String &group_name);
 
             /**
              * Retrieves a singleton to the manager.
@@ -67,22 +65,18 @@ namespace VGears{
              *
              * @param[in] name The unique name of the manager.
              * @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.
              * @param[in] create_params Unused.
              */
             virtual Ogre::Resource *createImpl(
-              const Ogre::String &name, Ogre::ResourceHandle handle,
-              const Ogre::String &group, bool is_manual,
-              Ogre::ManualResourceLoader *loader,
+              const Ogre::String &name, Ogre::ResourceHandle handle, const Ogre::String &group,
+              bool is_manual, Ogre::ManualResourceLoader *loader,
               const Ogre::NameValuePairList *create_params
             ) override final;
 

+ 30 - 2
src/installer/DataInstaller.cpp

@@ -74,6 +74,9 @@ float DataInstaller::Progress(){
             battle_installer_ = std::make_unique<BattleDataInstaller>(
               input_dir_, output_dir_, application_.ResMgr()
             );
+            world_installer_ = std::make_unique<WorldInstaller>(
+              input_dir_, output_dir_, options_.keep_originals
+            );
             installation_state_ = BATTLE_SCENES_INIT;
             return CalcProgress();
         case BATTLE_SCENES_INIT:
@@ -317,7 +320,7 @@ float DataInstaller::Progress(){
             // Skip fields if option is set.
             if (options_.skip_fields){
                 write_output_line_("Skipping field maps installation...", 2, true);
-                installation_state_ = CLEAN;
+                installation_state_ = WM_INIT;
                 return CalcProgress();
             }
             write_output_line_("Collecting spawn points and scale factors...", 2, true);
@@ -363,7 +366,7 @@ float DataInstaller::Progress(){
             // Skip fields if option is set.
             if (options_.skip_field_models){
                 write_output_line_("Skipping field maps installation...", 2, true);
-                installation_state_ = CLEAN;
+                installation_state_ = WM_INIT;
                 return CalcProgress();
             }
             write_output_line_("Converting field models...", 2, true);
@@ -376,6 +379,26 @@ float DataInstaller::Progress(){
             field_installer_->ConvertModels(field_model_names_[cur_substep_]);
             cur_substep_ ++;
             if (cur_substep_ == substeps_){
+                installation_state_ = WM_INIT;
+                cur_substep_ = 0;
+            }
+            return CalcProgress();
+        case WM_INIT:
+            // Skip world map if option set
+            if (options_.skip_wm){
+                write_output_line_("Skipping world map data installation...", 2, true);
+                installation_state_ = CLEAN;
+                return CalcProgress();
+            }
+            write_output_line_("Extracting world map models...", 2, true);
+            substeps_ = world_installer_->Initialize();
+            cur_substep_ = 0;
+            installation_state_ = WM_MAPS;
+            return CalcProgress();
+        case WM_MAPS:
+            if (world_installer_->ProcessMap() == false) cur_substep_ ++;
+            else{
+                // TODO: Next step: map scripts, etc
                 installation_state_ = CLEAN;
                 cur_substep_ = 0;
             }
@@ -422,6 +445,7 @@ void DataInstaller::CreateDirectories(){
     CreateDir("temp/char");
     CreateDir("temp/battle_models");
     CreateDir("temp/spell_models");
+    CreateDir("temp/wm");
     CreateDir("gamedata");
     CreateDir("gamedata/enemy");
     CreateDir("gamedata/attack");
@@ -442,6 +466,7 @@ void DataInstaller::CreateDirectories(){
     CreateDir("models/battle/scenes");
     CreateDir("models/battle/enemies");
     CreateDir("models/battle/attacks");
+    CreateDir("models/world/");
 
     application_.ResMgr()->addResourceLocation(
       output_dir_ + "temp/char/", "FileSystem", "FFVII", true, true
@@ -467,6 +492,9 @@ void DataInstaller::CreateDirectories(){
     application_.ResMgr()->addResourceLocation(
       output_dir_ + "models/battle/attacks/", "FileSystem", "FFVIITextures", true, true
     );
+    application_.ResMgr()->addResourceLocation(
+      output_dir_ + "models/world/", "FileSystem", "FFVIITextures", true, true
+    );
     fields_lgp_ = std::make_unique<ScopedLgp>(
       application_.getRoot(), input_dir_ + "data/field/flevel.lgp", "LGP", "FFVIIFields"
     );

+ 26 - 0
src/installer/DataInstaller.h

@@ -23,6 +23,7 @@
 #include "KernelDataInstaller.h"
 #include "MediaDataInstaller.h"
 #include "BattleDataInstaller.h"
+#include "WorldInstaller.h"
 #include "ModelsAndAnimationsDb.h"
 
 /**
@@ -77,6 +78,16 @@ class DataInstaller{
              */
             bool skip_field_models;
 
+            /**
+             * Option to skip world map data installation.
+             */
+            bool skip_wm;
+
+            /**
+             * Option to skip world map models installation.
+             */
+            bool skip_wm_models;
+
             /**
              * Option to keep original data after installation.
              */
@@ -395,6 +406,16 @@ class DataInstaller{
              */
             FIELD_CONVERT_MODELS,
 
+            /**
+             * Prepares the installer for world map data installation.
+             */
+            WM_INIT,
+
+            /**
+             * Step that extracts world map meshes.
+             */
+            WM_MAPS,
+
             /**
              * Cleans up after the installation.
              */
@@ -486,6 +507,11 @@ class DataInstaller{
          */
         std::unique_ptr<BattleDataInstaller> battle_installer_;
 
+        /**
+         * The installer for world map data.
+         */
+        std::unique_ptr<WorldInstaller> world_installer_;
+
         /**
          * Function used to print text to the log output, line by line.
          *

+ 1 - 1
src/installer/FieldDataInstaller.cpp

@@ -378,7 +378,7 @@ void FieldDataInstaller::ExportMesh(const std::string outdir, const Ogre::MeshPt
     mesh->setSkeletonName(base_mesh_name + ".skeleton");
     mesh_serializer.exportMesh(mesh.getPointer(), outdir + mesh->getName());
     Ogre::Mesh::SubMeshIterator it(mesh->getSubMeshIterator());
-    Ogre::MaterialSerializer    mat_ser;
+    Ogre::MaterialSerializer mat_ser;
     size_t i(0);
     std::set<std::string> textures;
     while (it.hasMoreElements()){

+ 5 - 3
src/installer/MainWindow.cpp

@@ -184,6 +184,8 @@ void MainWindow::on_btn_data_run_clicked(){
           "data/music/music.idx",
           "data/midi/midi.lgp",
           "data/battle/scene.bin",
+          "data/battle/battle.lgp",
+          "data/battle/magic.lgp",
           "ff7.exe"
         };
         // Ensure required files are in the input dir
@@ -225,11 +227,12 @@ void MainWindow::on_btn_data_run_clicked(){
             options.skip_fields = (Qt::Checked == main_window_->chk_no_fields->checkState());
             options.skip_field_models
               = (Qt::Checked == main_window_->chk_no_field_models->checkState());
+            options.skip_wm = (Qt::Checked == main_window_->chk_no_wm->checkState());
+            options.skip_wm_models = (Qt::Checked == main_window_->chk_no_wm_models->checkState());
             options.no_ffmpeg = (Qt::Checked == main_window_->chk_no_ffmpeg->checkState());
             options.no_timidity = (Qt::Checked == main_window_->chk_no_timidity->checkState());
             options.keep_originals = (Qt::Checked == main_window_->chk_keep_original->checkState());
 
-
             installer_created = true;
             installer_ = std::make_unique<DataInstaller>(
               QDir::toNativeSeparators(input).toStdString(),
@@ -238,8 +241,7 @@ void MainWindow::on_btn_data_run_clicked(){
               [this](const std::string log_line, int level, bool as_progress = false){
                  main_window_->data_log->append(log_line.c_str());
                  std::cout << log_line << std::endl;
-                 if (as_progress)
-                     main_window_->label_progress->setText(log_line.c_str());
+                 if (as_progress) main_window_->label_progress->setText(log_line.c_str());
               }
             );
             OnInstallStarted();

+ 24 - 0
src/installer/MainWindow.ui

@@ -283,6 +283,30 @@
              </item>
             </layout>
            </item>
+           <item>
+            <layout class="QHBoxLayout" name="horizontalLayout_9">
+             <item>
+              <widget class="QCheckBox" name="chk_no_wm">
+               <property name="toolTip">
+                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;b&gt;Don't extract world map data.&lt;/b&gt;&lt;br&gt;&lt;br&gt;If checked, world map models and data will not be extracted. Data from previous installations will not be deleted.&lt;br&gt;&lt;br&gt;This installation step is not usually very long, so there is no reason to skip it unless you have manually edited the world map data. Skipping world map data installation will also skip the installation of the world map 3D models.&lt;/body&gt;&lt;/html&gt;</string>
+               </property>
+               <property name="text">
+                <string>Don't extract world map data</string>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <widget class="QCheckBox" name="chk_no_wm_models">
+               <property name="toolTip">
+                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;b&gt;Don't extract world map 3D models.&lt;/b&gt;&lt;br&gt;&lt;br&gt;If checked, world map 3D models and textures will not be extracted. Models from previous installations will not be deleted.&lt;br&gt;&lt;br&gt;This installation step is not usually very long, so there is no reason to skip it unless you have manually edited the models. Skipping world map data installation will also skip the installation of world map 3D models.&lt;/body&gt;&lt;/html&gt;</string>
+               </property>
+               <property name="text">
+                <string>Dont't extract world 3D models</string>
+               </property>
+              </widget>
+             </item>
+            </layout>
+           </item>
            <item>
             <layout class="QHBoxLayout" name="horizontalLayout_5">
              <item>

+ 90 - 95
src/installer/MediaDataInstaller.cpp

@@ -22,21 +22,29 @@
 #include <OgreColourValue.h>
 #include <boost/format.hpp>
 #include <boost/algorithm/string.hpp>
+#include <boost/predef/os.h>
+#include <boost/filesystem.hpp>
 #include <tinyxml.h>
 #include "MediaDataInstaller.h"
 #include "data/VGearsLGPArchive.h"
 #include "data/VGearsTexFile.h"
 #include "TexFile.h"
+#if (BOOST_OS_WINDOWS)
+#include <stdlib>
+#elif (BOOST_OS_SOLARIS)
+#include <stdlib>
+#include <limits>
+#elif (BOOST_OS_LINUX)
+#include <unistd.h>
+#include <limits.h>
+#elif (BOOST_OS_MACOS)
+#include <mach-o/dyld.h>
+#elif (BOOST_OS_BSD_FREE)
+#include <sys/types>
+#include <sys/sysctl>
+#endif
 
-int MediaDataInstaller::TOTAL_SOUNDS = 723;
-
-u8 MediaDataInstaller::WAV_HEADER[] = {
-  0x52, 0x49, 0x46, 0x46, 0x70, 0x0B, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6D, 0x74, 0x20,
-  0x32, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x44, 0xAC, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00,
-  0x00, 0x04, 0x04, 0x00, 0x20, 0x00, 0xF4, 0x07, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
-  0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x40, 0x00, 0xF0, 0x00, 0x00, 0x00, 0xCC, 0x01,
-  0x30, 0xFF, 0x88, 0x01, 0x18, 0xFF, 0x64, 0x61, 0x74, 0x61, 0x2A, 0x0B, 0x00, 0x00
-};
+int MediaDataInstaller::TOTAL_SOUNDS = 750;
 
 MediaDataInstaller::MediaDataInstaller(
   const std::string input_dir, const std::string output_dir, const bool keep_originals,
@@ -45,10 +53,53 @@ MediaDataInstaller::MediaDataInstaller(
   input_dir_(input_dir), output_dir_(output_dir), keep_originals_(keep_originals),
   no_ffmpeg_(no_ffmpeg), no_timidity_(no_timidity),
   menu_(input_dir + "data/menu/menu_us.lgp", "LGP"), window_(input_dir + "data/kernel/WINDOW.BIN"),
-  fmt_(input_dir_ + "data/sound/audio.fmt"), dat_(input_dir_ + "data/sound/audio.dat"),
   midi_(input_dir + "data/midi/midi.lgp", "LGP")
 {PopulateMaps();}
 
+
+std::string MediaDataInstaller::GetExecutablePath(){
+    #if (BOOST_OS_WINDOWS)
+    char *exe_path;
+    if (_get_pgmptr(&exe_path) != 0) exe_path = "";
+    #elif (BOOST_OS_SOLARIS)
+    char exe_path[PATH_MAX];
+    if (realpath(getexecname(), exe_path) == NULL) exe_path[0] = '\0';
+    #elif (BOOST_OS_LINUX)
+    char exe_path[PATH_MAX];
+    ssize_t len = ::readlink("/proc/self/exe", exe_path, sizeof(exe_path));
+    if (len == -1 || len == sizeof(exe_path)) len = 0;
+    exe_path[len] = '\0';
+    #elif (BOOST_OS_MACOS)
+    char exe_path[PATH_MAX];
+    uint32_t len = sizeof(exe_path);
+    if (_NSGetExecutablePath(exe_path, &len) != 0){
+        exe_path[0] = '\0'; // buffer too small (!)
+    }
+    else {
+        // resolve symlinks, ., .. if possible
+        char *canonical_path = realpath(exe_path, NULL);
+        if (canonical_path != NULL){
+            strncpy(exe_path,canonical_path,len);
+            free(canonical_path);
+        }
+    }
+    #elif (BOOST_OS_BSD_FREE)
+    char exe_path[2048];
+    int mib[4];
+    mib[0] = CTL_KERN;
+    mib[1] = KERN_PROC;
+    mib[2] = KERN_PROC_PATHNAME;
+    mib[3] = -1;
+    size_t len = sizeof(exe_path);
+    if (sysctl(mib, 4, exe_path, &len, NULL, 0) != 0) exe_path[0] = '\0';
+    #endif
+    return
+      strlen(exe_path) > 0
+      ? boost::filesystem::path(exe_path).remove_filename().make_preferred().string()
+      : std::string();
+}
+
+
 void MediaDataInstaller::PopulateMaps(){
     // Most data here comes from https://forums.qhimm.com/index.php?topic=15786.0
     sound_map_[0] = "Cursor";
@@ -394,7 +445,7 @@ void MediaDataInstaller::PopulateMaps(){
     sound_map_[169] = "DrainingTentacles";
     sound_description_[169] = "Enemy Draining Tentacles (and similar) attacks.";
     sound_map_[170] = "Coin";
-    sound_description_[170] = "Coin sound. In fields, when somebody is paid, a coin is inserted...";
+    sound_description_[170] = "Coin sound. In fields, when somebody is paid, a coin inserted...";
     sound_map_[171] = "Impact";
     sound_description_[171] = "Some kind of impact?";
     sound_map_[172] = "";
@@ -609,7 +660,7 @@ void MediaDataInstaller::PopulateMaps(){
     sound_description_[276] = "Ruby Weapon Flame Thrower attack.";
     sound_map_[277] = "";
     sound_description_[277] = "";
-    sound_map_[278] = "BeamGun";
+    sound_map_[278] = "BeamGun2";
     sound_description_[278] = "Part of an enemy beam gun attack?";
     sound_map_[279] = "";
     sound_description_[279] = "";
@@ -1323,93 +1374,38 @@ void MediaDataInstaller::InstallSprites(){
 }
 
 int MediaDataInstaller::InstallSoundsInit(){
-    fmt_.SetOffset(0);
-    dat_.SetOffset(0);
+    // SFXDump handles the wav conversion
+    std::string command = (boost::format(
+      "%1%/sfxdump %2%data/sound/audio.fmt %2%data/sound/audio.dat %3%audio/sounds/"
+    ) % GetExecutablePath() % input_dir_ % output_dir_).str();    
+    std::system(command.c_str());
     processed_sounds_ = 0;
     return TOTAL_SOUNDS;
 }
 
 bool MediaDataInstaller::InstallSounds(){
-
-    FmtFile header;
-    header.size = fmt_.readU32LE();
-    // If size is 0, this is a bad header. There are 112 bytes of bad data, and after that,
-    // the next header.
-    if (header.size == 0){
-        for (int b = 0; b < 112; b += 4) fmt_.readU32LE();
-        processed_sounds_ ++;
-        if (processed_sounds_ >= TOTAL_SOUNDS) return true;
-        else return false;
-    }
-
-    header.offset = fmt_.readU32LE();
-    // If the offset is less than the previous one, also bad header. 34 bytes of bad data, and
-    // after that, the next header.
-    if (header.offset < dat_.GetCurrentOffset()){
-        for (int b = 0; b < 34; b += 2) fmt_.readU16LE();
-        processed_sounds_ ++;
-        if (processed_sounds_ >= TOTAL_SOUNDS) return true;
-        else return false;
-    }
-
-    // This should never happen, but just in case, never read outside the file
-    if (header.offset + header.size > dat_.GetFileSize()){
-        processed_sounds_ ++;
-        if (processed_sounds_ >= TOTAL_SOUNDS) return true;
-        else return false;
-    }
-
-    for (int l = 0; l < 16; l ++) header.loop_metadata[l] = fmt_.readU8();
-    for (int l = 0; l < 18; l ++) header.wav_header[l] = fmt_.readU8();
-    header.samples_per_block = fmt_.readU16LE();
-    header.adpcm = fmt_.readU16LE();
-    for (int l = 0; l < 28; l ++) header.adpcm_sets[l] = fmt_.readU8();
-
-    dat_.SetOffset(header.offset);
-    std::ofstream out(
-      output_dir_ + "audio/sounds/" + std::to_string(processed_sounds_) + ".wav",
-      std::ios::out | std::ios::binary
-    );
-
-
-    // Write the standard wav header.
-    for (int b = 0; b < 78; b ++) out.put(WAV_HEADER[b]);
-    // Write the data from the dat file.
-    for (int b = 0; b < header.size; b ++) out.put(dat_.readU8());
-    // Set size markers
-    unsigned char bytes[4];
-    int size = out.tellp() - 8;
-    bytes[0] = (size >> 24) & 0xFF;
-    bytes[1] = (size >> 16) & 0xFF;
-    bytes[2] = (size >> 8) & 0xFF;
-    bytes[3] = size & 0xFF;
-    out.seekp(4);
-    for (int b = 0; b < 4; b ++) out.put(bytes[b]);
-    size -= 36;
-    bytes[0] = (size >> 24) & 0xFF;
-    bytes[1] = (size >> 16) & 0xFF;
-    bytes[2] = (size >> 8) & 0xFF;
-    bytes[3] = size & 0xFF;
-    out.seekp(40);
-    for (int b = 0; b < 4; b ++) out.put(bytes[b]);
-    out.close();
-
-    // Convert to OGG.
-    // TODO: Don't use system calls! Integrate libav or something that can do the conversion
-    // natively
-    std::string command = (boost::format(
-      "ffmpeg -hide_banner -loglevel panic -y -i %1%audio/sounds/%2%.wav %1%audio/sounds/%2%.ogg"
-    ) % output_dir_ % processed_sounds_).str();
-    if (!no_ffmpeg_) std::system(command.c_str());
-
-    // Remove the wav file.
-    if (!keep_originals_){
-        std::remove(
-          (output_dir_ + "audio/sounds/" + std::to_string(processed_sounds_) + ".wav").c_str()
-        );
+    if (!no_ffmpeg_){
+        std::string f_path
+          = output_dir_ + "audio/sounds/" + std::to_string(processed_sounds_) + ".wav";
+        std::ifstream file(f_path);
+        if (file.is_open()){
+            file.close();
+             std::string command = (boost::format(
+              "ffmpeg -hide_banner -loglevel panic -y "
+              "-i %1%audio/sounds/%2%.wav %1%audio/sounds/%2%.ogg"
+            ) % output_dir_ % processed_sounds_).str();
+            std::system(command.c_str());
+            std::cout << "    ADD SOUND " << processed_sounds_ << ".ogg" << std::endl;
+            sounds_.push_back("audio/sounds/" + std::to_string(processed_sounds_) + ".ogg");
+            // Remove the wav file.
+            if (!keep_originals_)
+                std::remove((output_dir_ + "audio/sounds/" + std::to_string(processed_sounds_) + ".wav").c_str());
+        }
+        else{
+            sounds_.push_back("audio/sounds/INVALID.ogg");
+            std::cout << "    -- ADD SOUND INVALID.ogg" << std::endl;
+        }
     }
-
-    sounds_.push_back("audio/sounds/" + std::to_string(processed_sounds_) + ".ogg");
     processed_sounds_ ++;
     if (processed_sounds_ >= TOTAL_SOUNDS) return true;
     else return false;
@@ -1426,11 +1422,10 @@ void MediaDataInstaller::WriteSoundIndex(){
         xml_sound->SetAttribute("name", id);
         container->LinkEndChild(xml_sound.release());
         // If there is a friendly name for this sound, add another entry
-        if (sound_map_.count(id) != 0){
+        if (sound_map_.count(id) != 0 && sound_map_[id] != ""){
             std::unique_ptr<TiXmlElement> xml_sound_name(new TiXmlElement("sound"));
             xml_sound_name->SetAttribute("file_name", path);
             std::string name = sound_map_[id];
-            if (name == "") name = std::to_string(id);
             xml_sound_name->SetAttribute("name", name);
             xml_sound_name->SetAttribute("description", sound_description_[id]);
             container->LinkEndChild(xml_sound_name.release());

+ 8 - 60
src/installer/MediaDataInstaller.h

@@ -111,54 +111,14 @@ class MediaDataInstaller{
          * Number of sound files to extract.
          */
         static int TOTAL_SOUNDS;
-
-        /**
-         * The standard WAV header.
-         *
-         * 78 bytes to be written to every wav file before anything else.
-         */
-        static u8 WAV_HEADER[78];
-
-        /**
-         * The structure of each audio file pointer in a sound FMT file. 74 bytes.
-         */
-        struct FmtFile{
-
-            /**
-             * Size of the wav file in the dat. 4 bytes.
-             */
-            u32 size;
-
-            /**
-             * Offset of the wav file in the dat. 4 bytes.
-             */
-            u32 offset;
-
-            /**
-             * Information for sound looping. 12 bytes.
-             */
-            u8 loop_metadata[16];
-
-            /**
-             * Microsoft WAVFORMATEX header for the wav file. 44 bytes.
-             */
-            u8 wav_header[18];
-
-            /**
-             * Samples per block. 2 bytes.
-             */
-            u16 samples_per_block;
-
-            /**
-             * Number of ADPCM coefficients (used for compression, should always be 7). 2 bytes.
-             */
-            u16 adpcm;
-
-            /**
-             * Standard Microsoft ADPCMCoefSets. 28 bytes.
-             */
-            u8 adpcm_sets[28];
-        };
+		
+		/*
+		 * Retrieves the path to the currently running executable.
+		 *
+		 * @return Path to the folder containig the current executable, or an empty string in case
+		 * of failure.
+		 */
+		std::string GetExecutablePath();
 
         /**
          * The path to the directory from which to read the PC game data.
@@ -180,16 +140,6 @@ class MediaDataInstaller{
          */
         BinGZipFile window_;
 
-        /**
-         * The sounds .fmt file.
-         */
-        File fmt_;
-
-        /**
-         * The sounds .dat file.
-         */
-        File dat_;
-
         /**
          * Number of sounds already processed.
          */
@@ -245,6 +195,4 @@ class MediaDataInstaller{
          */
         bool no_timidity_;
 
-
-
 };

+ 184 - 21
src/installer/WorldInstaller.cpp

@@ -1,25 +1,188 @@
-/*#include "WorldInstaller.h"
-#include "common/File.h"
-#include "common/TypeDefine.h"
-
-WorldInstaller::ReadMapFile(std::string file_name){
-    File file = File(file_name);
-    u32 size = file.GetFileSize();
-    if (size % 16 != 0){
-        // TODO: Invalid WM file, skip.
-        return;
-    }
-    int block_count = size / 32;
-    for (int i = 0; i < block_count; i ++){
-        Mesh mesh;
-        mesh.triangle_count = file.readU16LE();
-        mesh.vertex_count = file.readU16LE();
-        for (int d = 0; d < 0xB800; d ++){ // Block size, fixed.
-            mesh.compressed_data[d] = file.readU8();
+/*
+ * Copyright (C) 2022 The V-Gears Team
+ *
+ * This file is part of V-Gears
+ *
+ * V-Gears is free software: you can redistribute it and/or modify it under
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.0 (GPLv3) of the License.
+ *
+ * V-Gears is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <iostream>
+#include <fstream>
+#include <OgreRoot.h>
+#include <OgreMesh.h>
+#include <OgreMeshSerializer.h>
+#include <boost/filesystem.hpp>
+#include "WorldInstaller.h"
+#include "common/Lzs.h"
+
+WorldInstaller::WorldInstaller(
+  std::string input_dir, std::string output_dir, const bool keep_originals
+):
+  input_dir_(input_dir), output_dir_(output_dir), keep_originals_(keep_originals)
+{}
+
+WorldInstaller::~WorldInstaller(){}
+
+unsigned int WorldInstaller::Initialize(){
+    if (wm_map_.size() > 0) wm_map_.clear();
+    processed_maps_ = 0;
+    wm_map_.push_back(File(input_dir_ + "/data/wm/WM0.MAP"));
+    wm_map_.push_back(File(input_dir_ + "/data/wm/WM2.MAP"));
+    wm_map_.push_back(File(input_dir_ + "/data/wm/WM3.MAP"));
+    return wm_map_.size();
+}
+
+bool WorldInstaller::ProcessMap(){
+    //std::cout << "[WI] Processing map " << processed_maps_ << "/" << wm_map_.size() << std::endl;
+    if (processed_maps_ >= wm_map_.size()) return true;
+    // Read WM*.MAP file.
+    Map map;
+    Ogre::MeshSerializer mesh_serializer;
+    boost::filesystem::path p(wm_map_[processed_maps_].GetFileName());
+    std::string map_name = p.stem().native(); // w/o path or extension.
+
+    for (int b = 0; b < wm_map_[processed_maps_].GetFileSize() / 0xB800; b ++){
+        //std::cout << "[WI]    Block " << b << std::endl;
+        Block block;
+        for (int m = 0; m < 16; m ++){
+
+            // Extract lzss compressed mesh to file.
+            std::vector<unsigned char> lzss;
+            wm_map_[processed_maps_].SetOffset(0xB800 * b + m * 4);
+            u32 mesh_offset = 0xB800 * b + wm_map_[processed_maps_].readU32LE();
+            wm_map_[processed_maps_].SetOffset(mesh_offset);
+            u32 mesh_size = wm_map_[processed_maps_].readU32LE();
+
+            // Add size to lzss
+            wm_map_[processed_maps_].SetOffset(mesh_offset);
+            for (int d = 0; d < 4; d ++)
+                lzss.push_back(wm_map_[processed_maps_].readU8());
+
+            //std::cout << "[WI]        Mesh " << m << " Size: " << mesh_size
+            //  << " Offset: " << mesh_offset << " - 0x";
+            //std::cout << std::hex << mesh_offset;
+            //std::cout << std::dec << std::endl;
+
+            for (int d = 0; d < mesh_size ; d ++) lzss.push_back(wm_map_[processed_maps_].readU8());
+
+            //std::cout << "            LZSS size: " << lzss.size() << std::endl;
+
+            // Extract the lzss file.
+            std::vector<unsigned char> data = Lzs::Decompress(lzss);
+            if (data.size() == 0) continue;
+            std::string dat_name = output_dir_ + "temp/wm/" + map_name + "_"
+              + std::to_string(b) + "_" + std::to_string(m) + ".dat";
+            std::ofstream dat(dat_name, std::ios::out | std::ios::binary);
+            if (!dat){
+                std::cerr << "Cannot create temporary world map file " << dat_name << std::endl;
+                processed_maps_ ++;
+                if (processed_maps_ >= wm_map_.size()) return true;
+                else return false;
+            }
+            for (int d = 0; d <= data.size(); d ++)
+                dat.write((char *) &data[d], sizeof(unsigned char));
+            dat.close();
+
+            // Open and read the decompressed file.
+            File mesh_data(dat_name);
+            block.mesh[m].triangle_count = mesh_data.readU16LE();
+            block.mesh[m].vertex_count = mesh_data.readU16LE();
+            for (int tri = 0; tri < block.mesh[m].triangle_count; tri ++){
+                Triangle t;
+                t.vertex_index[0] = mesh_data.readU8();
+                t.vertex_index[1] = mesh_data.readU8();
+                t.vertex_index[2] = mesh_data.readU8();
+                u8 walkability_function = mesh_data.readU8();
+                t.walkability = walkability_function >> 5; // 5 bits
+                t.function_id = walkability_function & 0x7; // 3 bits
+                t.vertex_coord[0].u = mesh_data.readU8();
+                t.vertex_coord[0].v = mesh_data.readU8();
+                t.vertex_coord[1].u = mesh_data.readU8();
+                t.vertex_coord[1].v = mesh_data.readU8();
+                t.vertex_coord[2].u = mesh_data.readU8();
+                t.vertex_coord[2].v = mesh_data.readU8();
+                u16 texture_location = mesh_data.readU16LE();
+                t.texture_info = texture_location >> 9; // 9 bytes
+                t.location = texture_location & ((1 << 7) - 1); // 7 bytes.
+                block.mesh[m].triangles.push_back(t);
+            }
+            for (int ver = 0; ver < block.mesh[m].vertex_count; ver ++){
+                Vertex v;
+                v.x = mesh_data.readU16LE();
+                v.y = mesh_data.readU16LE();
+                v.z = mesh_data.readU16LE();
+                v.unknown = mesh_data.readU16LE();
+                block.mesh[m].vertices.push_back(v);
+            }
+            for (int nor = 0; nor < block.mesh[m].vertex_count; nor ++){
+                Vertex v;
+                v.x = mesh_data.readU16LE();
+                v.y = mesh_data.readU16LE();
+                v.z = mesh_data.readU16LE();
+                v.unknown = mesh_data.readU16LE();
+                block.mesh[m].normals.push_back(v);
+            }
         }
-        // TODO: Flip endiannes, use OGRE.
-        mesh.data = DecompressLZSSData(mesh.compressed_data);
+        map.blocks.push_back(block);
     }
+    // TODO: Generate material.
+    // Generate manual meshes.
+    for (int b = 0; b < map.blocks.size(); b ++){
+        //std::cout << "[MAN] Block " << b << "/" << map.blocks.size() << std::endl;
 
+        std::string name = map_name + "_" + std::to_string(b);
+        Ogre::ManualObject man = Ogre::ManualObject(name);
+        //man.setBoundingBox(Ogre::AxisAlignedBox({-100,-100,0}, {100,100,0})); //TODO;
+          //= Ogre::Root::getSingleton().getSceneManager("Scene")->createManualObject(name);
+        man.begin(name, Ogre::RenderOperation::OT_TRIANGLE_LIST);
+        for (int m = 0; m < 16; m ++){
+            //std::cout << "[MAN]        Mesh " << m << " triangles: "
+            //  << map.blocks[b].mesh[m].triangle_count << "\n";
+            // Add vertices.
+            for (int v = 0; v < map.blocks[b].mesh[m].vertex_count; v ++){
+                man.position(
+                  map.blocks[b].mesh[m].vertices[v].x, map.blocks[b].mesh[m].vertices[v].y,
+                  map.blocks[b].mesh[m].vertices[v].z
+                );
+                man.normal(
+                  map.blocks[b].mesh[m].normals[v].x, map.blocks[b].mesh[m].normals[v].y,
+                  map.blocks[b].mesh[m].normals[v].z
+                );
+                // TODO: How to add texture coordinates at this point?
+                //man->textureCoord(0, 0);
+            }
+            // Conform faces from vertices
+            for (int t = 0; t < map.blocks[b].mesh[m].triangle_count; t ++){
+                //std::cout << "Triangle " << t << " vertizes: "
+                //  << (int) map.blocks[b].mesh[m].triangles[t].vertex_index[0] << ", "
+                //  << (int) map.blocks[b].mesh[m].triangles[t].vertex_index[1] << ", "
+                //  << (int) map.blocks[b].mesh[m].triangles[t].vertex_index[2] << std::endl;
+                man.triangle(
+                  map.blocks[b].mesh[m].triangles[t].vertex_index[0],
+                  map.blocks[b].mesh[m].triangles[t].vertex_index[1],
+                  map.blocks[b].mesh[m].triangles[t].vertex_index[2]
+                );
+            }
+        }
+        // Export the mesh.
+        man.end();
+        Ogre::MeshPtr mesh = man.convertToMesh(name);
+        //std::cout << "[WI] Export mesh " << (output_dir_ + "wm/" + mesh->getName() + ".mesh")
+        //  << std::endl;
+        mesh_serializer.exportMesh(
+          mesh.getPointer(), output_dir_ + "wm/" + mesh->getName() + ".mesh"
+        );
+    }
+    // Export mesh file.
+    processed_maps_ ++;
+    if (processed_maps_ >= wm_map_.size()) return true;
+    else return false;
 }
-*/
+

+ 196 - 11
src/installer/WorldInstaller.h

@@ -16,44 +16,229 @@
 #pragma once
 
 #include <string>
-/*#include <vector>
+#include <vector>
 #include <common/TypeDefine.h>
+#include "common/File.h"
 
+/**
+ * The world map data installer.
+ */
 class WorldInstaller{
 
     public:
 
-        WorldInstaller(std::string input_dir, std::string output_dir);
-
+        /**
+         * Constructor.
+         *
+         * @param[in] input_dir Path to the directory containing the original data to parse.
+         * @param[in] output_dir Path to the directory of the installation data.
+         * @param[in] keep_originals True to keep original data after conversion, false to remove.
+         */
+        WorldInstaller(
+          std::string input_dir, std::string output_dir, const bool keep_originals
+        );
+
+        /**
+         * Destructor.
+         */
         ~WorldInstaller();
 
-        void ReadMapFile(std::string file_name);
+        /**
+         * Initializes the installer.
+         *
+         * @return The number of maps to process.
+         */
+        unsigned int Initialize();
+
+        /**
+         * Processes the next map to process.
+         *
+         * @return True if, once the map has been processed, all maps are processed. False if there
+         * are more maps to process.
+         */
+        bool ProcessMap();
+
+        //void ReadMapFile(std::string file_name);
 
-        void GenerateMap(std::string file_name, std::string output);
+        //void GenerateMap(std::string file_name, std::string output);
 
     private:
 
+        /**
+         * UV coordinates for a vertex.
+         */
+        struct UVCoord{
+
+            /**
+             * U coordinate.
+             */
+            u8 u;
+
+            /**
+             * V coordinate.
+             */
+            u8 v;
+        };
+
+        /**
+         * Mesh triangle data.
+         */
+        struct Triangle{
+
+            /**
+             * Index for each triangle vertex.
+             */
+            u8 vertex_index[3];
+
+            /**
+             * Walkability info.
+             *
+             * Shares byte with {@see function_id}, 5 bytes.
+             */
+            u8 walkability;
+
+            /**
+             * ID of the function triggered when entering the triangle.
+             *
+             * Shares byte with {@see walkability}, 3 bytes.
+             */
+            u8 function_id;
+
+            /**
+             * UV coordinates in texture for each vertex.
+             */
+            UVCoord vertex_coord[3];
+
+            /**
+             * Walkability info.
+             *
+             * Shares bytes with {@see location_id}, 9 bytes.
+             */
+            u16 texture_info;
+
+            /**
+             * Location message ID.
+             *
+             * Shares bytes with {@see texture_info}, 7 bytes.
+             */
+            u16 location;
+        };
+
+        /**
+         * Mesh vertex data.
+         *
+         * Can also be used for nromal nada.
+         */
+        struct Vertex{
+
+            /**
+             * X coordinate.
+             */
+            u16 x;
+
+            /**
+             * Y coordinate.
+             */
+            u16 y;
+
+            /**
+             * Z coordinate.
+             */
+            u16 z;
+
+            /**
+             * Unknown value, unused.
+             */
+            u16 unknown;
+        };
+
+        /**
+         * Mesh data.
+         */
         struct Mesh{ // 0xB800 / 0x0F
 
+            /**
+             * Number of triangles in the mesh.
+             */
             u16 triangle_count;
 
+            /**
+             * Number of vertices in the mesh.
+             */
             u16 vertex_count;
 
-            u8 compressed_data[(0xB800 / 0x0F)];
+            /**
+             * Mesh triangle data.
+             *
+             * Size must equal {@see triangle_count}.
+             */
+            std::vector<Triangle> triangles;
+
+            /**
+             * Mesh vertex data.
+             *
+             * Size must equal {@see vertex_count}.
+             */
+            std::vector<Vertex> vertices;
+
+            /**
+             * Mesh normal data.
+             *
+             * Size must equal {@see vertex_count}.
+             */
+            std::vector<Vertex> normals;
         };
 
-        struct Map{ // 0xB800
+        /**
+         * Each block of a map.
+         *
+         * A block is always 0xB800 bytes and it's always composed of 16 meshes.
+         */
+        struct Block{ // 0xB800
 
+            /**
+             * Block meshes.
+             */
             Mesh mesh[16];
         };
 
-        struct WMFile {
-            Map map[64];
+        /**
+         * A map.
+         *
+         * A map is defined in a WM*.MAP file. They are divided in 0xB800 byte blocks.
+         */
+        struct Map {
+
+            /**
+             * Map blocks.
+             */
+            std::vector<Block> blocks;
         };
 
-        std::vector<uint8> DecompressLZSSData(u32* compressed_data);
+        std::vector<u8> DecompressLZSSData(u32* compressed_data);
 
+        /**
+         * Each of the original WM*.MAP in the installation disk.
+         */
+        std::vector<File> wm_map_;
+
+        /**
+         * The path to the directory from which to read the PC game data.
+         */
         std::string input_dir_;
 
+        /**
+         * The path to the directory where to save the V-Gears data.
+         */
         std::string output_dir_;
-};*/
+
+        /**
+         * If true, original files will not be deleted after conversion.
+         */
+        bool keep_originals_;
+
+        /**
+         * Number of maps already processed.
+         */
+        unsigned int processed_maps_;
+};

+ 13 - 6
src/installer/common/Lzs.h

@@ -21,24 +21,31 @@ namespace Lzs{
      * Decompresses LZS data.
      *
      * @param[in] compressed Compressed data.
-     * @return Decompressed data.
+     * @return Decompressed data. An empty vector if data is not valid lzs.
      */
     inline std::vector<unsigned char> Decompress(const std::vector<unsigned char>& compressed){
-        if (compressed.size() < 4) abort();
-        const unsigned int inpuit_buffer_size = static_cast<unsigned int>(compressed.size());
+        if (compressed.size() < 4){
+            std::cout << "Error decompressing LZS data of size " << compressed.size() << std::endl;
+            return std::vector<unsigned char>(0);
+        }
+        const unsigned int input_buffer_size = static_cast<unsigned int>(compressed.size());
         const unsigned int input_length =
           (((compressed[0] & 0xFF) << 0) |
           ((compressed[1] & 0xFF) << 8) |
           ((compressed[2] & 0xFF) << 16) |
           ((compressed[3] & 0xFF) << 24)) + 4;
-        if (input_length != inpuit_buffer_size) abort();
-        unsigned int extract_size = (inpuit_buffer_size + 255) & ~255;
+        if (input_length != input_buffer_size){
+            std::cerr << "Error decompressing LZS data of invalid size " << input_length
+              << " | " << input_buffer_size << std::endl;
+            return std::vector<unsigned char>(0);
+        }
+        unsigned int extract_size = (input_buffer_size + 255) & ~255;
         std::vector<unsigned char> extract_buffer(extract_size);
         unsigned int input_offset = 4;
         unsigned int output_offset = 0;
         unsigned char control_byte = 0;
         unsigned char control_bit = 0;
-        while (input_offset < inpuit_buffer_size){
+        while (input_offset < input_buffer_size){
             if (control_bit == 0){
                 control_byte = compressed[input_offset ++];
                 control_bit = 8;

+ 28 - 0
test/installer/data/FF7Data.cpp

@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2022 The V-Gears Team
+ *
+ * This file is part of V-Gears
+ *
+ * V-Gears is free software: you can redistribute it and/or modify it under
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.0 (GPLv3) of the License.
+ *
+ * V-Gears is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <boost/test/unit_test.hpp>
+#include "installer/data/FF7Data.h"
+
+
+BOOST_AUTO_TEST_CASE(TestFF7DataFile){
+    BOOST_CHECK(FF7Data::GetEnemyModelId(0) == "aa");
+    BOOST_CHECK(FF7Data::GetEnemyModelId(19) == "at");
+    BOOST_CHECK(FF7Data::GetEnemyModelId(25) == "az");
+    BOOST_CHECK(FF7Data::GetEnemyModelId(26) == "ba");
+    BOOST_CHECK(FF7Data::GetEnemyModelId(650) == "");
+    BOOST_CHECK(FF7Data::GetEnemyModelId(30000) == "");
+}
+