浏览代码

Worls map models extracted from the ISO. The installer can now run untill the end using an ISO file. Added icon and menu entries to the installer.

Iñigo Valentin 1 月之前
父节点
当前提交
15437fc9b1

+ 17 - 0
CMakeLists.txt

@@ -58,4 +58,21 @@ if(BUILD_TESTS)
     add_subdirectory(test)
 endif()
 
+# Install Linux desktop integration assets for the installer so desktop shells
+# can map the running window to its icon.
+if(UNIX AND NOT APPLE AND BUILD_INSTALLER)
+    install(
+        FILES ${CMAKE_SOURCE_DIR}/v-gears-installer.desktop
+        DESTINATION share/applications
+    )
+    install(
+        FILES ${CMAKE_SOURCE_DIR}/v-gears-installer.png
+        DESTINATION share/icons/hicolor/256x256/apps
+    )
+    install(
+        FILES ${CMAKE_SOURCE_DIR}/v-gears-installer.png
+        DESTINATION share/pixmaps
+    )
+endif()
+
 # TODO: Generate Installer (.msi, .deb, .appImage...)

+ 2 - 0
debian/install

@@ -1,4 +1,6 @@
 v-gears.desktop /usr/share/applications
 v-gears.png /usr/share/pixmaps
+v-gears-installer.desktop /usr/share/applications
+v-gears-installer.png /usr/share/pixmaps
 v-gears-launcher /usr/bin
 output/resources.cfg /usr/share/v-gears

+ 64 - 8
src/data/VGearsHRCMeshLoader.cpp

@@ -14,6 +14,7 @@
  */
 
 #include <OgreMesh.h>
+#include <vector>
 #include "data/VGearsHRCMeshLoader.h"
 #include "common/VGearsStringUtil.h"
 #include "data/VGearsPFile.h"
@@ -23,6 +24,49 @@
 
 namespace VGears{
 
+  namespace {
+
+    template<typename ResourcePtrType, typename LoaderFunc>
+    ResourcePtrType loadWithFallbacks(
+      const Ogre::String& group,
+      const std::vector<Ogre::String>& candidates,
+      LoaderFunc loader
+    ){
+      Ogre::Exception last_exception(
+        Ogre::Exception::ERR_ITEM_NOT_FOUND,
+        "No candidate resource could be loaded.",
+        "loadWithFallbacks"
+      );
+      for (const auto& candidate : candidates){
+        if (candidate.empty()) continue;
+        try{
+          return loader(candidate);
+        }
+        catch (const Ogre::Exception& ex){
+          last_exception = ex;
+        }
+      }
+      throw last_exception;
+    }
+
+    std::vector<Ogre::String> rsdCandidates(const Ogre::String& rsd_base){
+      std::vector<Ogre::String> names;
+      names.push_back(rsd_base + EXT_RSD);
+      names.push_back(rsd_base);
+      names.push_back(rsd_base + "rsd");
+      return names;
+    }
+
+    std::vector<Ogre::String> pCandidates(const Ogre::String& pfile_name){
+      std::vector<Ogre::String> names;
+      names.push_back(pfile_name);
+      names.push_back(StringUtil::replaceAll(pfile_name, EXT_P, ""));
+      names.push_back(pfile_name + "p");
+      return names;
+    }
+
+  }
+
     typedef HRCFile::Bone Bone;
 
     typedef HRCFile::BoneList BoneList;
@@ -52,21 +96,33 @@ namespace VGears{
         const String &bone_name(bone.name);
         RSDNameList::const_iterator it(bone.rsd_names.begin());
         RSDNameList::const_iterator end(bone.rsd_names.end());
-        String rsd_base, rsdfile_name_, pfile_name_;
+        String rsd_base, rsdfile_name_;
         while (it != end){
             rsd_base = path + *it;
             StringUtil::toLowerCase(rsd_base);
             rsdfile_name_ = rsd_base + EXT_RSD;
-            RSDFilePtr rsd_file = RSDFileManager::getSingleton().load(
-              rsdfile_name_, hrc_file_.getGroup()
-            ).staticCast<RSDFile>();
+            RSDFilePtr rsd_file = loadWithFallbacks<RSDFilePtr>(
+              hrc_file_.getGroup(),
+              rsdCandidates(rsd_base),
+              [this](const Ogre::String& candidate){
+                  return RSDFileManager::getSingleton().load(
+                    candidate, hrc_file_.getGroup()
+                  ).staticCast<RSDFile>();
+              }
+            );
             assert(rsd_file != nullptr);
-            pfile_name_ = path + rsd_file->GetPolygonName();
+            String pfile_name_ = path + rsd_file->GetPolygonName();
             StringUtil::toLowerCase(pfile_name_);
             pfile_name_ = StringUtil::replaceAll(pfile_name_, EXT_PLY, EXT_P);
-            PFilePtr p_file =  PFileManager::getSingleton().load(
-              pfile_name_, hrc_file_.getGroup()
-            ).staticCast<PFile>();
+            PFilePtr p_file = loadWithFallbacks<PFilePtr>(
+              hrc_file_.getGroup(),
+              pCandidates(pfile_name_),
+              [this](const Ogre::String& candidate){
+                  return PFileManager::getSingleton().load(
+                    candidate, hrc_file_.getGroup()
+                  ).staticCast<PFile>();
+              }
+            );
             assert(p_file != nullptr);
             p_file->AddGroups(mesh, bone_name, rsd_file);
             ++ it;

+ 3 - 1
src/installer/CMakeLists.txt

@@ -110,6 +110,8 @@ set(v-gears-installer_UIS MainWindow.ui)
 QT5_WRAP_UI(UIS ${v-gears-installer_UIS})
 set(v-gears-installer_MOCS MainWindow.h)
 QT5_WRAP_CPP(MOCS ${v-gears-installer_MOCS})
+set(v-gears-installer_RESOURCES installer_resources.qrc)
+qt5_add_resources(RCC_SOURCES ${v-gears-installer_RESOURCES})
 
 
 # Compiler options.
@@ -119,7 +121,7 @@ endif()
 
 
 # Generate v-gears-installer executable.
-add_executable (v-gears-installer ${INSTALLER_SOURCE_FILES} ${UIS} ${MOCS})
+add_executable (v-gears-installer ${INSTALLER_SOURCE_FILES} ${UIS} ${MOCS} ${RCC_SOURCES})
 SET_PROPERTY(TARGET v-gears-installer PROPERTY FOLDER "build/v-gears-installer")
 if (APPLE)
     target_link_libraries(v-gears-installer "-framework CoreFoundation -framework Cocoa -framework IOKit")

+ 3 - 6
src/installer/DataInstaller.cpp

@@ -280,8 +280,7 @@ float DataInstaller::Progress(){
             cur_substep_ = 0;
             return CalcProgress();
         case MEDIA_SOUNDS:
-            if (media_installer_->InstallSounds() == true)
-                installation_state_ = MEDIA_SOUNDS_INDEX;
+            if (media_installer_->InstallSounds() == true) installation_state_ = MEDIA_SOUNDS_INDEX;
             else cur_substep_ ++;
             return CalcProgress();
         case MEDIA_SOUNDS_INDEX:
@@ -302,8 +301,7 @@ float DataInstaller::Progress(){
             cur_substep_ = 0;
             return CalcProgress();
         case MEDIA_MUSICS:
-            if (media_installer_->InstallMusics() == true)
-                installation_state_ = MEDIA_MUSICS_HQ;
+            if (media_installer_->InstallMusics() == true) installation_state_ = MEDIA_MUSICS_HQ;
             else cur_substep_ ++;
             return CalcProgress();
         case MEDIA_MUSICS_HQ:
@@ -409,8 +407,7 @@ float DataInstaller::Progress(){
             }
             return CalcProgress();
         case WM_MODELS:
-            if (options_.skip_wm_models)
-                write_output_line_("Skipping world map model installation...", 2, true);
+            if (options_.skip_wm_models) write_output_line_("Skipping world map model installation...", 2, true);
             else world_installer_->ProcessModels();
             installation_state_ = CLEAN;
             return CalcProgress();

+ 48 - 107
src/installer/FieldDataInstaller.cpp

@@ -52,8 +52,7 @@ std::string FieldDataInstaller::CreateGateWayScript(
       "    on_near = function(self, entity)\n"
       "        if entity == \"Cloud\" then\n"
       "            if not Data.DisableGateways then\n"
-      "                load_field_map_request(\""
-      + target_map_name + "\", \"" + source_spawn_point_name + "\")\n"
+      "                load_field_map_request(\"" + target_map_name + "\", \"" + source_spawn_point_name + "\")\n"
       "            end\n"
       "        end\n"
       "        return 0\n"
@@ -67,8 +66,9 @@ std::string FieldDataInstaller::CreateGateWayScript(
 size_t FieldDataInstaller::GetFieldId(
   const std::string& name, const std::vector<std::string>& field_id_to_name_lookup
 ){
-    for (size_t i = 0; i < field_id_to_name_lookup.size(); i ++)
+    for (size_t i = 0; i < field_id_to_name_lookup.size(); i ++){
         if (field_id_to_name_lookup[i] == name) return i;
+    }
     throw std::runtime_error("No Id found for field name");
 }
 
@@ -95,7 +95,7 @@ bool FieldDataInstaller::WillCrash(const Ogre::String& resource_name){
       || resource_name == "coloin1" // boost::bad_format_string: format-string is ill-formed
       || resource_name == "del3" // Segmentation fault.
       || resource_name == "elmin4_2" // boost::bad_format_string: format-string is ill-formed
-    ){return true;}
+    ) return true;
     return false;
 }
 
@@ -138,7 +138,7 @@ bool FieldDataInstaller::IsTestField(const Ogre::String& resource_name){
       || resource_name == "mds7_w2"
       || resource_name == "mds7_w3"
       || resource_name == "mds7"
-    ){return true;}
+    )return true;
     return false;
 }
 
@@ -266,11 +266,8 @@ void FieldDataInstaller::Convert(int field_index){
             PcFieldToVGearsField(field);
         }
         else{
-            /*write_output_line_(
-              "[ERROR] Skip field " + resource_name + " due to crash or hang issue."
-            );*/
-            std::cerr << "[ERROR] Skip field: " << resource_name
-              << " due to crash or hang issue." << std::endl;
+            //write_output_line_("[ERROR] Skip field " + resource_name + " due to crash or hang issue.");
+            std::cerr << "[ERROR] Skip field: " << resource_name << " due to crash or hang issue." << std::endl;
         }
     }
 }
@@ -323,9 +320,7 @@ 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 +346,12 @@ void FieldDataInstaller::ConvertModels(std::string model_name){
         ExportMesh(output_dir_ + FIELD_MODELS_DIR + "/", mesh);
     }
     catch (const Ogre::Exception& ex){
-        write_output_line_(
-          "[ERROR] Ogre exception converting model " + model->first + ": " + ex.what()
-        );
-        std::cerr << "[ERROR] Ogre exception converting model "
-          << model->first <<": " << ex.what() << std::endl;
+        //write_output_line_("[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());
-        std::cerr << "[ERROR] Exception converting model "
-          << model->first << ": " << ex.what() << std::endl;
+        //write_output_line_("[ERROR] Exception converting model " + model->first + ": " + ex.what());
+        std::cerr << "[ERROR] Exception converting model " << model->first << ": " << ex.what() << std::endl;
     }
 }
 
@@ -373,9 +364,7 @@ void FieldDataInstaller::ExportMesh(const std::string outdir, const Ogre::MeshPt
     Ogre::MeshSerializer mesh_serializer;
     Ogre::SkeletonPtr skeleton(mesh->getSkeleton());
     Ogre::SkeletonSerializer skeleton_serializer;
-    skeleton_serializer.exportSkeleton(
-      skeleton.getPointer(), outdir + base_mesh_name + ".skeleton"
-    );
+    skeleton_serializer.exportSkeleton(skeleton.getPointer(), outdir + base_mesh_name + ".skeleton");
     mesh->setSkeletonName(base_mesh_name + ".skeleton");
     mesh_serializer.exportMesh(mesh.getPointer(), outdir + mesh->getName());
     Ogre::Mesh::SubMeshIterator it(mesh->getSubMeshIterator());
@@ -399,25 +388,18 @@ void FieldDataInstaller::ExportMesh(const std::string outdir, const Ogre::MeshPt
                               texture_unit_num < pass->getNumTextureUnitStates();
                               texture_unit_num ++
                             ){
-                                Ogre::TextureUnitState* unit
-                                  = pass->getTextureUnitState(texture_unit_num);
+                                Ogre::TextureUnitState* unit = pass->getTextureUnitState(texture_unit_num);
                                 if (unit && unit->getTextureName().empty() == false){
                                     // Convert the texture from .tex to .png.
-                                    TexFile tex(
-                                      output_dir_ + "temp/char/" + unit->getTextureName()
-                                    );
+                                    TexFile tex(output_dir_ + "temp/char/" + unit->getTextureName());
 
                                     // Ensure the output material script references png files
                                     // rather than tex files.
                                     Ogre::String base_name;
-                                    VGears::StringUtil::splitBase(
-                                      unit->getTextureName(), base_name
-                                    );
+                                    VGears::StringUtil::splitBase(unit->getTextureName(), base_name);
                                     unit->setTextureName(base_mesh_name + "_" + base_name + ".png");
 
-                                    tex.SavePng(
-                                      output_dir_ + FIELD_MODELS_DIR + "/" + base_name + ".png", 0
-                                    );
+                                    tex.SavePng(output_dir_ + FIELD_MODELS_DIR + "/" + base_name + ".png", 0);
                                     // Copy subtexture (xxxx.png) to model_xxxx.png
                                     // TODO: obtain the "data" folder
                                     // programatically.
@@ -446,8 +428,8 @@ void FieldDataInstaller::ExportMesh(const std::string outdir, const Ogre::MeshPt
     for (auto& texture_name : textures){
         std::string tex_name = texture_name.c_str();
         try{
-            Ogre::TexturePtr texture_ptr
-              = Ogre::TextureManager::getSingleton().load(tex_name, "FFVIITextures" /*"FFVII"*/);
+            Ogre::TexturePtr texture_ptr = Ogre::TextureManager::getSingleton().load(tex_name, "FFVIITextures");
+            //Ogre::TexturePtr texture_ptr = Ogre::TextureManager::getSingleton().load(tex_name, "FFVII");
             Ogre::Image image;
             texture_ptr->convertToImage(image);
             Ogre::String base_name;
@@ -462,8 +444,7 @@ void FieldDataInstaller::ExportMesh(const std::string outdir, const Ogre::MeshPt
 
 float FieldDataInstaller::GetFieldScaleFactor(size_t field_id){
     auto it = scale_factors_.find(field_id);
-    if (it == std::end(scale_factors_))
-        throw std::runtime_error("Scale factor not found for field id");
+    if (it == std::end(scale_factors_)) throw std::runtime_error("Scale factor not found for field id");
     return it->second;
 }
 
@@ -497,8 +478,7 @@ void FieldDataInstaller::PcFieldToVGearsField(VGears::FLevelFilePtr& field){
         if (gateway.destination_field_id != INACTIVE_GATEWAY_ID){
             const std::string gateway_entity_name = "Gateway" + std::to_string(i);
             gateway_script_data += CreateGateWayScript(
-              gateway_entity_name,
-              map_list_.at(gateway.destination_field_id),
+              gateway_entity_name, map_list_.at(gateway.destination_field_id),
               "Spawn_" + field->getName() + "_" + std::to_string(i)
             );
         }
@@ -512,39 +492,28 @@ void FieldDataInstaller::PcFieldToVGearsField(VGears::FLevelFilePtr& field){
 
         // Decompile to LUA.
         decompiled = FieldDecompiler::Decompile(
-          field->getName(), raw_field_data, formatter, gateway_script_data,
-          "EntityContainer = {}\n\n"
-        );
-        std::ofstream script_file(
-          output_dir_ + "/" + FIELD_MAPS_DIR + "/" + field->getName() + "/script.lua"
+          field->getName(), raw_field_data, formatter, gateway_script_data, "EntityContainer = {}\n\n"
         );
+        std::ofstream script_file(output_dir_ + "/" + FIELD_MAPS_DIR + "/" + field->getName() + "/script.lua");
         if (script_file.is_open()){
             script_file << decompiled.luaScript;
-            field_text_writer_.Begin(
-              output_dir_ + "/" + FIELD_MAPS_DIR + "/" + field->getName() + "/text.xml"
-            );
+            field_text_writer_.Begin(output_dir_ + "/" + FIELD_MAPS_DIR + "/" + field->getName() + "/text.xml");
             try{field_text_writer_.Write(raw_field_data, field->getName());}
             catch (const std::out_of_range& ex){
-                write_output_line_(
-                  "[ERROR] Failed to read texts from field " + field->getName() + ": " + ex.what()
-                );
+                write_output_line_("[ERROR] Failed to read texts from field " + field->getName() + ": " + ex.what());
                 std::cerr << "[ERROR] Failed to read texts from field "
                   << field->getName() << ": " << ex.what() << std::endl;
             }
             field_text_writer_.End();
         }
         else{
-            write_output_line_(
-              "[ERROR] Failed to open script file from field " + field->getName() + " for writing."
-            );
+            write_output_line_("[ERROR] Failed to open script file from field " + field->getName() + " for writing.");
             std::cerr << "[ERROR] Failed to open script file from field "
               << field->getName() << "for writing." << std::endl;
         }
     }
     catch (const ::DecompilerException& ex){
-        write_output_line_(
-          "[ERROR] Internal decompiler error in field " + field->getName() + ": " + ex.what()
-        );
+        write_output_line_("[ERROR] Internal decompiler error in field " + field->getName() + ": " + ex.what());
         std::cerr << "[ERROR] Internal decompiler error in field "
           << field->getName() << ": " << ex.what() << std::endl;
     }
@@ -606,17 +575,13 @@ void FieldDataInstaller::PcFieldToVGearsField(VGears::FLevelFilePtr& field){
                         + std::to_string(spawn_point_records[i].gateway_index_or_map_jump_address)
                     );
                 }
-                const float downscaler_next = 128.0f * GetFieldScaleFactor(
-                  gateway.destination_field_id
-                );
+                const float downscaler_next = 128.0f * GetFieldScaleFactor(gateway.destination_field_id);
 
                 // Position Z is actually the target walkmesh triangle ID, so this is tiny bit more
                 // complex. Now "get the Z value of the triangle with that ID". Note that ID
                 // actually just means index.
                 unsigned int triangle_index = static_cast<unsigned int>(gateway.destination.z);
-                if (
-                  triangle_index >= field->GetWalkmesh()->GetTriangles().size()
-                ){
+                if (triangle_index >= field->GetWalkmesh()->GetTriangles().size()){
                     write_output_line_(
                       "[WARNING] In field " + field->getName() + ": Map jump triangle ("
                       + std::to_string(triangle_index) + ") out of bounds ("
@@ -627,17 +592,15 @@ void FieldDataInstaller::PcFieldToVGearsField(VGears::FLevelFilePtr& field){
                       << field->GetWalkmesh()->GetTriangles().size() << ")" << std::endl;
                     triangle_index = 0;
                 }
-                const float z_of_triangle_with_id
-                  = field->GetWalkmesh()->GetTriangles().at(triangle_index).a.z;
+                const float z_of_triangle_with_id = field->GetWalkmesh()->GetTriangles().at(triangle_index).a.z;
                 const Ogre::Vector3 position(
                   gateway.destination.x / downscaler_next, gateway.destination.y / downscaler_next,
                   z_of_triangle_with_id
                 );
-                if (position != Ogre::Vector3::ZERO && first_entity_point == Ogre::Vector3::ZERO)
+                if (position != Ogre::Vector3::ZERO && first_entity_point == Ogre::Vector3::ZERO){
                     first_entity_point = position;
-                xml_entity_point->SetAttribute(
-                  "position", Ogre::StringConverter::toString(position)
-                );
+                }
+                xml_entity_point->SetAttribute("position", Ogre::StringConverter::toString(position));
                 const float rotation = (360.0f * static_cast<float>(gateway.dir)) / 255.0f;
                 xml_entity_point->SetAttribute("rotation", std::to_string(rotation));
                 element->LinkEndChild(xml_entity_point.release());
@@ -671,19 +634,16 @@ void FieldDataInstaller::PcFieldToVGearsField(VGears::FLevelFilePtr& field){
         for (FieldDecompiler::FieldEntity entity : decompiled.entities){
             // If the entity has been added as a line, skip.
             if (line_entities.size() > 0){
-                if (
-                  *std::find(line_entities.begin(), line_entities.end(), entity.name) == entity.name
-                ){
-                    continue;
-                }
+                if (*std::find(line_entities.begin(), line_entities.end(), entity.name) == entity.name) continue;
             }
             const int char_id = entity.char_id;
             if (char_id != -1){
                 const VGears::ModelListFile::ModelDescription& desc
                   = models->GetModels().at(char_id);
                 auto& animations = used_models_and_anims_.ModelAnimations(desc.hrc_name);
-                for (const auto& anim : desc.animations)
+                for (const auto& anim : desc.animations){
                     animations.insert(used_models_and_anims_.NormalizeAnimationName(anim.name));
+                }
                 std::unique_ptr<TiXmlElement> xml_entity_script(new TiXmlElement("entity_model"));
                 xml_entity_script->SetAttribute("name", entity.name);
                 // TODO: Add to list of HRC's to convert, obtain name of converted .mesh file.
@@ -697,22 +657,16 @@ void FieldDataInstaller::PcFieldToVGearsField(VGears::FLevelFilePtr& field){
                     // For player models the position is set manually in the xml because if a map
                     // is loaded manually this is where the player will end up. Hence we set the
                     // position to the first entity_point that we have.
-                    xml_entity_script->SetAttribute(
-                      "position", Ogre::StringConverter::toString(first_entity_point)
-                    );
+                    xml_entity_script->SetAttribute("position", Ogre::StringConverter::toString(first_entity_point));
                 }
                 else{
-                    xml_entity_script->SetAttribute(
-                      "position", Ogre::StringConverter::toString(Ogre::Vector3::ZERO)
-                    );
+                    xml_entity_script->SetAttribute("position", Ogre::StringConverter::toString(Ogre::Vector3::ZERO));
                 }
                 xml_entity_script->SetAttribute("direction", "0");
                 // TODO: This isn't quite right, the models animation
                 // translation seems to be inverted?
                 xml_entity_script->SetAttribute("scale", "0.03125 0.03125 0.03125");
-                xml_entity_script->SetAttribute(
-                  "root_orientation", "0.7071067811865476 0.7071067811865476 0 0"
-                );
+                xml_entity_script->SetAttribute("root_orientation", "0.7071067811865476 0.7071067811865476 0 0");
                 element->LinkEndChild(xml_entity_script.release());
             }
             else{
@@ -726,26 +680,20 @@ void FieldDataInstaller::PcFieldToVGearsField(VGears::FLevelFilePtr& field){
             const VGears::TriggersFile::Gateway& gateway = gateways[i];
             // If non-inactive gateway:
             if (gateway.destination_field_id != INACTIVE_GATEWAY_ID){
-                std::unique_ptr<TiXmlElement> xml_entity_trigger(
-                  new TiXmlElement("entity_trigger")
-                );
+                std::unique_ptr<TiXmlElement> xml_entity_trigger(new TiXmlElement("entity_trigger"));
                 xml_entity_trigger->SetAttribute("name", "Gateway" + std::to_string(i));
                 xml_entity_trigger->SetAttribute(
                   "point1",
                   Ogre::StringConverter::toString(
                     Ogre::Vector3(
-                      gateway.exit_line[0].x,
-                      gateway.exit_line[0].y,
-                      gateway.exit_line[0].z) / downscaler_this
+                      gateway.exit_line[0].x, gateway.exit_line[0].y, gateway.exit_line[0].z) / downscaler_this
                   )
                 );
                 xml_entity_trigger->SetAttribute(
                   "point2",
                   Ogre::StringConverter::toString(
                     Ogre::Vector3(
-                      gateway.exit_line[1].x,
-                      gateway.exit_line[1].y,
-                      gateway.exit_line[1].z) / downscaler_this
+                      gateway.exit_line[1].x, gateway.exit_line[1].y, gateway.exit_line[1].z) / downscaler_this
                     )
                   );
                 // Enabled is hard coded to true.
@@ -784,11 +732,9 @@ void FieldDataInstaller::PcFieldToVGearsField(VGears::FLevelFilePtr& field){
             const int width = bg_image->getWidth();
             const int height = bg_image->getHeight();
             const VGears::CameraMatrixFilePtr& camera_matrix = field->GetCameraMatrix();
-            const Ogre::Vector3 position
-              = camera_matrix->GetPosition() / GetFieldScaleFactor(this_field_id);
+            const Ogre::Vector3 position = camera_matrix->GetPosition() / GetFieldScaleFactor(this_field_id);
             const Ogre::Quaternion orientation = camera_matrix->GetOrientation();
-            const Ogre::Degree fov
-              = camera_matrix->GetFov(static_cast<float>(BG_PSX_SCREEN_HEIGHT));
+            const Ogre::Degree fov = camera_matrix->GetFov(static_cast<float>(BG_PSX_SCREEN_HEIGHT));
             const int min_x = triggers->GetCameraRange().left * BG_SCALE_UP_FACTOR;
             const int min_y = triggers->GetCameraRange().top * BG_SCALE_UP_FACTOR;
             const int max_x = triggers->GetCameraRange().right * BG_SCALE_UP_FACTOR;
@@ -834,15 +780,12 @@ void FieldDataInstaller::PcFieldToVGearsField(VGears::FLevelFilePtr& field){
                     const float v1 = static_cast<float>(sprite.src.y + sprite.height) / height;
                     xml_element->SetAttribute(
                       "uv",
-                      Ogre::StringConverter::toString(u0) + " "
-                      + Ogre::StringConverter::toString(v0) + " "
-                      + Ogre::StringConverter::toString(u1) + " "
-                      + Ogre::StringConverter::toString(v1)
+                      Ogre::StringConverter::toString(u0) + " " + Ogre::StringConverter::toString(v0) + " "
+                      + Ogre::StringConverter::toString(u1) + " " + Ogre::StringConverter::toString(v1)
                     );
                     // TODO: It works (on FFVII PC), but why this number?
                     xml_element->SetAttribute(
-                      "depth",
-                      Ogre::StringConverter::toString(static_cast<float>(sprite.depth) * (0.03125f))
+                      "depth", Ogre::StringConverter::toString(static_cast<float>(sprite.depth) * (0.03125f))
                     );
                     // TODO: Copied from DatFile::AddTile.
                     // Add to common method.
@@ -859,9 +802,7 @@ void FieldDataInstaller::PcFieldToVGearsField(VGears::FLevelFilePtr& field){
                 //}
             }
             bg_doc.LinkEndChild(bg_element.release());
-            bg_doc.SaveFile(
-              output_dir_ + "/" + FIELD_MAPS_DIR + "/" + field->getName() + "/bg.xml"
-            );
+            bg_doc.SaveFile(output_dir_ + "/" + FIELD_MAPS_DIR + "/" + field->getName() + "/bg.xml");
         }
     }
     {

+ 89 - 111
src/installer/WorldInstaller.cpp

@@ -23,6 +23,7 @@
 #include "WorldInstaller.h"
 #include "TexFile.h"
 #include "common/Lzs.h"
+#include "common/FinalFantasy7/FF7NameLookup.h"
 #include "common/VGearsStringUtil.h"
 #include "data/VGearsLGPArchive.h"
 #include "data/WorldMapWalkmesh.h"
@@ -341,7 +342,6 @@ void WorldInstaller::GenerateMaterials(){
         Ogre::Technique* tech = mat->getTechnique(0);
         Ogre::Pass* pass = tech->getPass(0);
         pass->setDiffuse(0.7, 0.7, 0.7, 0.7);
-        std::cout << "SAVING MATERIAL: " << (output_dir_ + "models/world/" + t.name + ".material") << std::endl;
         mSer.exportMaterial(mat, output_dir_ + "models/world/" + t.name + ".material");
     }
 }
@@ -349,74 +349,60 @@ void WorldInstaller::GenerateMaterials(){
 void WorldInstaller::ProcessModels(){
     // Open world_us.lgp
     File world_file(*disk_image_.fileExists("data/wm/world_us.lgp"));
+    boost::filesystem::create_directories(output_dir_ + "temp/world_models/");
+    boost::filesystem::create_directories(output_dir_ + "data/models/world/");
     VGears::LGPArchive world_lgp(*disk_image_.fileExists("data/wm/world_us.lgp"), "LGP");
     world_lgp.open(*disk_image_.fileExists("data/wm/world_us.lgp"), true);
     world_lgp.load();
     VGears::LGPArchive::FileList file_list = world_lgp.GetFiles();
+    std::vector<std::string> hrc_file_names;
+
+    // First pass: extract all files so HRC sidecar dependencies (.rsd, .p, .tex) exist.
     for (int i = 0; i < file_list.size(); i ++){
         VGears::LGPArchive::FileEntry f = file_list.at(i);
         if (f.data_offset + f.data_size <= world_file.GetFileSize()){
             File w_lgp_file(&world_file, f.data_offset, f.data_size);
             std::string file_name = f.file_name;
+            VGears::StringUtil::toLowerCase(file_name);
             w_lgp_file.WriteFile(output_dir_ + "temp/world_models/" + file_name);
-            if (file_name.substr(4, 3) == "hrc"){
-
-                std::string id = file_name.substr(0, 3);
-                std::string model_name = FF7Data::GetWorldMapModelName(id);
-                std::cout << "CONVERTING WORLD MODEL: " << file_name << " " << i
-                  << "/" << file_list.size() << std::endl;
-
-
-
-                // TODO: Can this be done with declare resource?
-                // TODO: If not, do aloop to save all the hrc files, then call this once, then
-                // another loop to process them.
-                /*res_mgr_->declareResource(
-                  output_dir_ + "temp/world_models/" + file_name, "Skeleton", "FFVII"
-                );*/
-                std::cout << "1\n";
-                res_mgr_->removeResourceLocation(output_dir_ + "temp/world_models/", "FFVII");
-                std::cout << "2\n";
-                res_mgr_->addResourceLocation(
-                  output_dir_ + "temp/world_models/", "FileSystem", "FFVII", true, true
-                );
-
-
+            if (file_name.size() >= 7 && file_name.substr(4, 3) == "hrc") hrc_file_names.push_back(file_name);
+        }
+    }
 
-                // TODO: This needs work to assemble all the pieces. Similar to field models.
+    // Second pass: convert HRC models once all dependencies are available on disk.
+    for (int i = 0; i < hrc_file_names.size(); i ++){
+        const std::string& file_name = hrc_file_names.at(i);
 
+        // TODO: Can this be done with declare resource?
+        // TODO: If not, do aloop to save all the hrc files, then call this once, then
+        // another loop to process them.
+        res_mgr_->declareResource(output_dir_ + "temp/world_models/" + file_name, "Skeleton", "FFVII");
+        res_mgr_->removeResourceLocation(output_dir_ + "temp/world_models/", "FFVII");
+        res_mgr_->addResourceLocation(output_dir_ + "temp/world_models/", "FileSystem", "FFVII", true, true);
 
-                std::cout << "3\n";
-                Ogre::ResourcePtr hrc = VGears::HRCFileManager::GetSingleton().load(
-                  file_name, "FFVII"
-                );
-                std::cout << "4\n";
-                auto mesh_name = model_name + ".mesh";
-                std::cout << "5\n";
-                Ogre::MeshPtr mesh(Ogre::MeshManager::getSingleton().load(mesh_name, "FFVII"));
-                std::cout << "6\n";
-                Ogre::SkeletonPtr skeleton(mesh->getSkeleton());
-                // TODO: a files??
-                /*for (std::string anim : model.a){
-                    VGears::AFileManager &afl_mgr(VGears::AFileManager::GetSingleton());
-                    Ogre::String a_base_name;
-                    VGears::StringUtil::splitBase(anim, a_base_name);
-                    VGears::AFilePtr a
-                      = afl_mgr.load(a_base_name + ".a", "FFVII").staticCast<VGears::AFile>();
-                    // Convert the FF7 name to a more readable name set in the meta data.
-                    VGears::StringUtil::splitBase(anim, base_name);
-                    a->AddTo(skeleton, VGears::NameLookup::Animation(base_name));
-                }*/
-                std::cout << "EXPORT MESH: " << (output_dir_ + "data/models/world/" + mesh_name) << std::endl;
-                ExportMesh(output_dir_ + "data/models/world/" + mesh_name, mesh);
-                std::cout << "    EXPORTED" << std::endl;
-            }
-        }
+        // TODO: This needs work to assemble all the pieces. Similar to field models.
+        Ogre::ResourcePtr hrc = VGears::HRCFileManager::GetSingleton().load(file_name, "FFVII");
+        Ogre::String hrc_base_name;
+        VGears::StringUtil::splitBase(file_name, hrc_base_name);
+        auto mesh_name = VGears::NameLookup::model(hrc_base_name) + ".mesh";
+        Ogre::MeshPtr mesh(Ogre::MeshManager::getSingleton().load(mesh_name, "FFVII"));
+        Ogre::SkeletonPtr skeleton(mesh->getSkeleton());
+        // TODO: a files??
+        /*for (std::string anim : model.a){
+            VGears::AFileManager &afl_mgr(VGears::AFileManager::GetSingleton());
+            Ogre::String a_base_name;
+            VGears::StringUtil::splitBase(anim, a_base_name);
+            VGears::AFilePtr a
+              = afl_mgr.load(a_base_name + ".a", "FFVII").staticCast<VGears::AFile>();
+            // Convert the FF7 name to a more readable name set in the meta data.
+            VGears::StringUtil::splitBase(anim, base_name);
+            a->AddTo(skeleton, VGears::NameLookup::Animation(base_name));
+        }*/
+        ExportMesh(output_dir_ + "data/models/world/", mesh);
     }
 }
 
 bool WorldInstaller::ProcessMap(){
-    //std::cout << "[WI] Processing map " << processed_maps_ << "/" << wm_map_.size() << std::endl;
     if (processed_maps_ >= wm_map_.size()) return true;
     // Initialize the xml file.
     TiXmlDocument doc;
@@ -439,7 +425,7 @@ bool WorldInstaller::ProcessMap(){
     xml->LinkEndChild(xml_walkmesh.release());
     // Terrain (don't close yet).
     std::unique_ptr<TiXmlElement> xml_terrain(new TiXmlElement("terrain"));
-
+    
     // Initialize the walkmesh data.
     WorldMapWalkmesh walkmesh(processed_maps_, processed_maps_ == 0 ? true : false);
 
@@ -463,8 +449,7 @@ bool WorldInstaller::ProcessMap(){
             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());
+            for (int d = 0; d < 4; d ++) lzss.push_back(wm_map_[processed_maps_].readU8());
             for (int d = 0; d < mesh_size ; d ++) lzss.push_back(wm_map_[processed_maps_].readU8());
             // Extract the lzss file.
             std::vector<unsigned char> data = Lzs::Decompress(lzss);
@@ -478,8 +463,7 @@ bool WorldInstaller::ProcessMap(){
                 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));
+            for (int d = 0; d < data.size(); d ++) dat.write((char *) &data[d], sizeof(unsigned char));
             dat.close();
 
             // Open and read the decompressed file.
@@ -587,9 +571,7 @@ bool WorldInstaller::ProcessMap(){
                 //  << (int) map.blocks[b].mesh[m].triangles[t].vertex_index[1] << ", "
                 //  << (int) map.blocks[b].mesh[m].triangles[t].vertex_index[2] << std::endl;
                 // TODO: El index deberia incrementar por triangulo por cada material
-                man.setMaterialName(
-                  0, texture_.at(map.blocks[b].mesh[m].triangles[t].texture).name
-                );
+                man.setMaterialName(0, texture_.at(map.blocks[b].mesh[m].triangles[t].texture).name);
                 man.triangle(
                   map.blocks[b].mesh[m].triangles[t].vertex_index[0],
                   map.blocks[b].mesh[m].triangles[t].vertex_index[1],
@@ -604,8 +586,7 @@ bool WorldInstaller::ProcessMap(){
         //  << std::endl;
         mesh_serializer.exportMesh(
           mesh.getPointer(),
-          output_dir_ + TERRAIN_MODELS_DIR + "/" + std::to_string(processed_maps_)
-            + "/" + mesh->getName() + ".mesh"
+          output_dir_ + TERRAIN_MODELS_DIR + "/" + std::to_string(processed_maps_) + "/" + mesh->getName() + ".mesh"
         );
         // WM0 is te main worldmap, its shape changes according to the history progress, it has
         // 68 blocks but only 63 are used at a time
@@ -613,12 +594,9 @@ bool WorldInstaller::ProcessMap(){
             std::unique_ptr<TiXmlElement> xml_block(new TiXmlElement("block"));
             xml_block->SetAttribute("index", std::to_string(b));
             xml_block->SetAttribute(
-              "file_name",
-              "terrain/" + std::to_string(processed_maps_) + "/" + mesh->getName() + ".mesh"
-            );
-            xml_block->SetAttribute(
-              "position", std::to_string(wm_offset_x) + " " + std::to_string(wm_offset_y) + " 0"
+              "file_name", "terrain/" + std::to_string(processed_maps_) + "/" + mesh->getName() + ".mesh"
             );
+            xml_block->SetAttribute("position", std::to_string(wm_offset_x) + " " + std::to_string(wm_offset_y) + " 0");
             if (processed_maps_ == 0){
                 // TODO: Correct history points for each block.
                 switch (b){
@@ -657,8 +635,7 @@ bool WorldInstaller::ProcessMap(){
     doc.LinkEndChild(xml.release());
     // TODO: Add models, entities...
     doc.SaveFile(
-      output_dir_ + "/world/" + std::to_string(processed_maps_)
-      + "/world" + std::to_string(processed_maps_) + ".xml"
+      output_dir_ + "/world/" + std::to_string(processed_maps_) + "/world" + std::to_string(processed_maps_) + ".xml"
     );
     // Generate the walkmesh xml file
     walkmesh.generate(output_dir_ + "/world/" + std::to_string(processed_maps_) + "/wm.xml");
@@ -671,26 +648,33 @@ bool WorldInstaller::ProcessMap(){
 
 void WorldInstaller::ExportMesh(const std::string outdir, const Ogre::MeshPtr &mesh){
 
+    if (mesh.isNull()){
+        std::cerr << "[ERROR] ExportMesh called with null mesh." << std::endl;
+        return;
+    }
+
+  boost::filesystem::create_directories(outdir);
+
     // TODO: Share function with pc model exporter
     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(base_mesh_name + ".skeleton");
+    if (!skeleton.isNull()){
+        Ogre::SkeletonSerializer skeleton_serializer;
+        skeleton_serializer.exportSkeleton(skeleton.getPointer(), outdir + base_mesh_name + ".skeleton");
+        mesh->setSkeletonName(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;
+    std::set<std::string> queued_materials;
+    bool has_queued_materials = false;
     while (it.hasMoreElements()){
         Ogre::SubMesh *sub_mesh(it.getNext());
-        Ogre::MaterialPtr mat(Ogre::MaterialManager::getSingleton().getByName(
-          sub_mesh->getMaterialName())
-        );
+        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);
@@ -703,57 +687,51 @@ void WorldInstaller::ExportMesh(const std::string outdir, const Ogre::MeshPtr &m
                               texture_unit_num < pass->getNumTextureUnitStates();
                               texture_unit_num ++
                             ){
-                                Ogre::TextureUnitState* unit
-                                  = pass->getTextureUnitState(texture_unit_num);
+                                Ogre::TextureUnitState* unit = pass->getTextureUnitState(texture_unit_num);
                                 if (unit && unit->getTextureName().empty() == false){
-                                    // Convert the texture from .tex to .png.
-                                    TexFile tex(
-                                      output_dir_ + "temp/wm/" + unit->getTextureName()
-                                    );
+                                    try{
+                                        // Convert the texture from .tex to .png.
+                                        TexFile tex(output_dir_ + "temp/world_models/" + unit->getTextureName());
 
-                                    // 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_mesh_name + "_" + base_name + ".png");
+                                        // 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_mesh_name + "_" + base_name + ".png");
 
-                                    tex.SavePng(
-                                      output_dir_ + ELEMENT_MODELS_DIR + "/" + base_name + ".png", 0
-                                    );
-                                    // Copy subtexture (xxxx.png) to model_xxxx.png
-                                    // TODO: obtain the "data" folder
-                                    // programatically.
-                                    boost::filesystem::copy_file(
-                                        output_dir_ + ELEMENT_MODELS_DIR + "/" + base_name + ".png",
-                                        output_dir_ + ELEMENT_MODELS_DIR + "/"
-                                        + base_mesh_name + "_" + base_name + ".png",
-                                      boost::filesystem::copy_options::overwrite_existing
-                                    );
-                                    textures.insert(unit->getTextureName());
+                                        const auto plain_png = outdir + base_name + ".png";
+                                        const auto model_png = outdir + base_mesh_name + "_" + base_name + ".png";
+                                        tex.SavePng(plain_png, 0);
+                                        boost::filesystem::copy_file(
+                                          plain_png, model_png, boost::filesystem::copy_options::overwrite_existing
+                                        );
+                                        textures.insert(unit->getTextureName());
+                                    }
+                                    catch (const std::exception& ex){
+                                        std::cerr << "[ERROR] Texture export failed for "
+                                          << unit->getTextureName() << ": " << ex.what() << std::endl;
+                                    }
                                 }
                             }
                         }
                     }
                 }
             }
-            // TODO: Check what to do with materials
-            /**
-            if (std::count(materials_.begin(), materials_.end(), sub_mesh->getMaterialName()) == 0){
+            // Queue each material once. Exporting with an empty queue throws in Ogre.
+            if (queued_materials.insert(sub_mesh->getMaterialName()).second){
                 mat_ser.queueForExport(mat);
-                materials_.push_back(sub_mesh->getMaterialName());
-            }*/
+                has_queued_materials = true;
+            }
 
         }
         ++ i;
     }
-    mat_ser.exportQueued(outdir + base_mesh_name + VGears::EXT_MATERIAL);
+    if (has_queued_materials) mat_ser.exportQueued(outdir + base_mesh_name + VGears::EXT_MATERIAL);
     for (auto& texture_name : textures){
         std::string tex_name = texture_name.c_str();
         try{
-            Ogre::TexturePtr texture_ptr
-              = Ogre::TextureManager::getSingleton().load(tex_name, "FFVIITextures" /*"FFVII"*/);
+            Ogre::TexturePtr texture_ptr = Ogre::TextureManager::getSingleton().load(tex_name, "FFVIITextures");
+            //Ogre::TexturePtr texture_ptr = Ogre::TextureManager::getSingleton().load(tex_name, "FFVII");
             Ogre::Image image;
             texture_ptr->convertToImage(image);
             Ogre::String base_name;

+ 35 - 56
src/installer/decompiler/ControlFlow.cpp

@@ -16,6 +16,7 @@
 #include <algorithm>
 #include <iostream>
 #include <set>
+#include <stdexcept>
 #include <boost/format.hpp>
 #include "decompiler/ControlFlow.h"
 
@@ -62,6 +63,7 @@
 #define GET_EDGE(edge) (boost::get(boost::edge_attribute, graph_, edge))
 
 ControlFlow::ControlFlow(InstVec& insts, Engine& engine): insts_(insts),engine_(engine){
+    if (insts_.empty()) return;
 
     // Automatically add a function if we're not supposed to look for more functions
     // and no functions are defined.
@@ -87,7 +89,6 @@ ControlFlow::ControlFlow(InstVec& insts, Engine& engine): insts_(insts),engine_(
         prev = GET(cur);
     }
     // Add regular edges.
-    FuncMap::iterator fn;
     GraphVertex last ={};
     bool add_edge = false;
     prev = NULL;
@@ -107,7 +108,17 @@ ControlFlow::ControlFlow(InstVec& insts, Engine& engine): insts_(insts),engine_(
     // Add jump edges.
     for (InstIterator it = insts.begin(); it != insts.end(); ++ it){
         if ((*it)->IsJump()){
-            GraphEdge e = boost::add_edge(Find(it), Find((*it)->GetDestAddress()), graph_).first;
+            const uint32 src_addr = (*it)->GetAddress();
+            const uint32 dest_addr = (*it)->GetDestAddress();
+            auto src_it = addr_map_.find(src_addr);
+            auto dest_it = addr_map_.find(dest_addr);
+            if (src_it == addr_map_.end() || dest_it == addr_map_.end()){
+                std::cerr << "Invalid jump edge in control-flow graph: source " << boost::format("0x%08x") % src_addr
+                  << " -> destination " << boost::format("0x%08x") % dest_addr << " (opcode "
+                  << boost::format("0x%02x") % (*it)->GetOpcode() << ")" << std::endl;
+                continue;
+            }
+            GraphEdge e = boost::add_edge(src_it->second, dest_it->second, graph_).first;
             PUT_EDGE(e, true);
         }
     }
@@ -122,8 +133,9 @@ GraphVertex ControlFlow::Find(ConstInstIterator it){return addr_map_[(*it)->GetA
 GraphVertex ControlFlow::Find(uint32 address){
     std::map<uint32, GraphVertex>::iterator it = addr_map_.find(address);
     if (it == addr_map_.end()){
-        std::cerr << "Request for instruction at unknown address "
-          << boost::format("0x%08x") % address << std::endl;
+        throw std::runtime_error(
+            (boost::format("Request for instruction at unknown address 0x%08x") % address).str()
+        );
     }
     return it->second;
 }
@@ -195,11 +207,7 @@ void ControlFlow::CreateGroups(){
         return;
     }
 
-    for (
-      FuncMap::iterator fn = engine_.functions.begin();
-      fn != engine_.functions.end();
-      ++ fn
-    ){
+    for (FuncMap::iterator fn = engine_.functions.begin(); fn != engine_.functions.end(); ++ fn){
         SetStackLevel(fn->second.vertex, 0);
     }
     ConstInstIterator cur_inst, next_inst;
@@ -366,11 +374,7 @@ void ControlFlow::DetectBreak(){
     for (VertexIterator v = vertex_range.first; v != vertex_range.second; ++ v){
         GroupPtr gr = GET(*v);
         // Undetermined block with unconditional jump...
-        if (
-          gr->type == GROUP_TYPE_NORMAL
-          && ((*gr->end)->IsUncondJump())
-          && out_degree(*v, graph_) == 1
-        ){
+        if (gr->type == GROUP_TYPE_NORMAL && ((*gr->end)->IsUncondJump()) && out_degree(*v, graph_) == 1){
             OutEdgeIterator oe = boost::out_edges(*v, graph_).first;
             GraphVertex target = boost::target(*oe, graph_);
             GroupPtr target_gr = GET(target);
@@ -397,11 +401,7 @@ void ControlFlow::DetectContinue(){
     for (VertexIterator v = vertex_range.first; v != vertex_range.second; ++ v){
         GroupPtr gr = GET(*v);
         // Undetermined block with unconditional jump...
-        if (
-          gr->type == GROUP_TYPE_NORMAL
-          && ((*gr->end)->IsUncondJump())
-          && out_degree(*v, graph_) == 1
-        ){
+        if ( gr->type == GROUP_TYPE_NORMAL && ((*gr->end)->IsUncondJump()) && out_degree(*v, graph_) == 1){
             OutEdgeIterator oe = boost::out_edges(*v, graph_).first;
             GraphVertex target = boost::target(*oe, graph_);
             GroupPtr target_gr = GET(target);
@@ -415,24 +415,18 @@ void ControlFlow::DetectContinue(){
                 bool after_jump_jargets = true;
                 for (OutEdgeIterator toe = toer.first; toe != toer.second; ++ toe){
                     // ...it is targeting a while condition which jumps to the next sequential group
-                    if (
-                      target_gr->type == GROUP_TYPE_WHILE
-                      && GET(boost::target(*toe, graph_)) == gr->next
-                    ){
+                    if (target_gr->type == GROUP_TYPE_WHILE && GET(boost::target(*toe, graph_)) == gr->next){
                         is_continue = false;
                     }
                     // ...or the instruction is placed after all jump targets from condition.
-                    if (
-                      (*GET(boost::target(*toe, graph_))->start)->GetAddress()
-                        > (*gr->start)->GetAddress()
-                    ){
+                    if ((*GET(boost::target(*toe, graph_))->start)->GetAddress() > (*gr->start)->GetAddress()){
                         after_jump_jargets = false;
                     }
                 }
                 if (after_jump_jargets) is_continue = false;
-
-                if (is_continue && ValidateBreakOrContinue(gr, target_gr))
+                if (is_continue && ValidateBreakOrContinue(gr, target_gr)){
                     gr->type = GROUP_TYPE_CONTINUE;
+                }
             }
         }
     }
@@ -450,9 +444,7 @@ bool ControlFlow::ValidateBreakOrContinue(GroupPtr group, GroupPtr condition_gro
         to = group;
         from = condition_group->next;
     }
-    GROUP_TYPE ogt = (
-      condition_group->type == GROUP_TYPE_DO_WHILE ? GROUP_TYPE_WHILE : GROUP_TYPE_DO_WHILE
-    );
+    GROUP_TYPE ogt = (condition_group->type == GROUP_TYPE_DO_WHILE ? GROUP_TYPE_WHILE : GROUP_TYPE_DO_WHILE);
     // Verify that destination deals with innermost while/do-while.
     for (cursor = from; cursor->next != NULL && cursor != to; cursor = cursor->next){
         if (cursor->type == condition_group->type){
@@ -474,9 +466,7 @@ bool ControlFlow::ValidateBreakOrContinue(GroupPtr group, GroupPtr condition_gro
                 }
                 InEdgeRange ier_validate = boost::in_edges(v_validate, graph_);
                 for (
-                  InEdgeIterator ie_validate = ier_validate.first;
-                  ie_validate != ier_validate.second;
-                  ++ ie_validate
+                  InEdgeIterator ie_validate = ier_validate.first; ie_validate != ier_validate.second; ++ ie_validate
                 ){
                     GroupPtr ig_validate = GET(boost::source(*ie_validate, graph_));
                     // All loops of other type going into range must be placed within range.
@@ -501,8 +491,9 @@ void ControlFlow::DetectIf(){
     for (VertexIterator v = vr.first; v != vr.second; ++v){
         GroupPtr gr = GET(*v);
         // If: Undetermined block with conditional jump.
-        if (gr->type == GROUP_TYPE_NORMAL && ((*gr->end)->IsCondJump()))
+        if (gr->type == GROUP_TYPE_NORMAL && ((*gr->end)->IsCondJump())){
             gr->type = GROUP_TYPE_IF;
+        }
     }
 }
 
@@ -527,16 +518,11 @@ void ControlFlow::DetectElse(){
             // Else: Jump target of if immediately preceded by an unconditional jump...
             if (!(*target_gr->prev->end)->IsUncondJump()) continue;
             // ...which is not a break or a continue...
-            if (
-              target_gr->prev->type == GROUP_TYPE_CONTINUE
-              || target_gr->prev->type == GROUP_TYPE_BREAK
-            ){
+            if (target_gr->prev->type == GROUP_TYPE_CONTINUE || target_gr->prev->type == GROUP_TYPE_BREAK){
                 continue;
             }
             // ...to later in the code.
-            OutEdgeIterator toe = boost::out_edges(
-              Find((*target_gr->prev->start)->GetAddress()), graph_
-            ).first;
+            OutEdgeIterator toe = boost::out_edges(Find((*target_gr->prev->start)->GetAddress()), graph_).first;
             GroupPtr target_target_gr = GET(boost::target(*toe, graph_));
             if ((*target_target_gr->start)->GetAddress() > (*target_gr->end)->GetAddress()){
                 if (ValidateElseBlock(gr, target_gr, target_target_gr)){
@@ -550,11 +536,7 @@ void ControlFlow::DetectElse(){
 
 bool ControlFlow::ValidateElseBlock(GroupPtr if_group, GroupPtr start, GroupPtr end){
     for (GroupPtr cursor = start; cursor != end; cursor = cursor->next){
-        if (
-          cursor->type == GROUP_TYPE_IF
-          || cursor->type == GROUP_TYPE_WHILE
-          || cursor->type == GROUP_TYPE_DO_WHILE
-        ){
+        if (cursor->type == GROUP_TYPE_IF || cursor->type == GROUP_TYPE_WHILE || cursor->type == GROUP_TYPE_DO_WHILE){
             // Validate outgoing edges of conditions.
             OutEdgeRange oer = boost::out_edges(Find(cursor->start), graph_);
             for (OutEdgeIterator oe = oer.first; oe != oer.second; ++ oe){
@@ -570,12 +552,10 @@ bool ControlFlow::ValidateElseBlock(GroupPtr if_group, GroupPtr start, GroupPtr
             }
         }
         // If previous group ends an else, that else must start inside the range.
-        for (
-          ElseEndIterator it = cursor->prev->end_else.begin();
-          it != cursor->prev->end_else.end();
-          ++ it
-        ){
-            if ((*(*it)->start)->GetAddress() < (*start->start)->GetAddress()) return false;
+        for (ElseEndIterator it = cursor->prev->end_else.begin(); it != cursor->prev->end_else.end(); ++ it){
+            if ((*(*it)->start)->GetAddress() < (*start->start)->GetAddress()){
+                return false;
+            }
         }
         // Unless group is a simple unconditional jump...
         if ((*cursor->start)->IsUncondJump()) continue;
@@ -586,8 +566,7 @@ bool ControlFlow::ValidateElseBlock(GroupPtr if_group, GroupPtr start, GroupPtr
             GroupPtr source_gr = GET(source);
             // Edges going to conditions...
             if (
-              source_gr->type == GROUP_TYPE_IF
-              || source_gr->type == GROUP_TYPE_WHILE
+              source_gr->type == GROUP_TYPE_IF || source_gr->type == GROUP_TYPE_WHILE
               || source_gr->type == GROUP_TYPE_DO_WHILE
             ){
                 // ...must not come from outside the range [start, end]...

+ 1 - 1
src/installer/decompiler/field/FieldDisassembler.h

@@ -724,7 +724,7 @@ class FieldDisassembler : public Disassembler{
         template<typename T> void ParseOpcode(
           int opcode, std::string name, T instruction, int stack_change, const char* argument_format
         ){
-            uint32 full_opcode = (full_opcode << 8) + opcode;
+                        const uint32 full_opcode = static_cast<uint32>(opcode);
             this->insts_.push_back(instruction);
             this->insts_.back()->SetOpcode(full_opcode);
             this->insts_.back()->SetAddress(this->address_);

+ 5 - 0
src/installer/installer_resources.qrc

@@ -0,0 +1,5 @@
+<RCC>
+    <qresource prefix="/icons">
+        <file alias="v-gears-installer.png">../../v-gears-installer.png</file>
+    </qresource>
+</RCC>

+ 11 - 0
src/installer/main.cpp

@@ -14,6 +14,7 @@
  */
 
 #include <QtWidgets/QApplication>
+#include <QtGui/QIcon>
 
 #include "MainWindow.h"
 
@@ -25,8 +26,18 @@
  * @return The application return code. 0 is OK.
  */
 int main(int argc, char *argv[]){
+    QApplication::setApplicationName(QStringLiteral("v-gears-installer"));
+    QApplication::setApplicationDisplayName(QStringLiteral("V-Gears Installer"));
     QApplication application(argc, argv);
+    QApplication::setDesktopFileName(QStringLiteral("v-gears-installer"));
+    const QIcon installerIcon(QStringLiteral(":/icons/v-gears-installer.png"));
+    if (!installerIcon.isNull()) {
+        application.setWindowIcon(installerIcon);
+    }
     MainWindow window;
+    if (!installerIcon.isNull()) {
+        window.setWindowIcon(installerIcon);
+    }
     window.show();
     return application.exec();
 }

+ 12 - 0
v-gears-installer.desktop

@@ -0,0 +1,12 @@
+[Desktop Entry]
+Type=Application
+Version=1.0
+Name=V-Gears Installer
+GenericName=V-Gears Installer
+Comment=Install V-Gears game data
+Exec=v-gears-installer
+Icon=v-gears-installer
+StartupWMClass=v-gears-installer
+Terminal=false
+StartupNotify=true
+Categories=Game;Utility;

二进制
v-gears-installer.png


二进制
v-gears.png