Răsfoiți Sursa

Fixes in singleton handling. The installer now asks for an ISO image

Iñigo Valentin 1 lună în urmă
părinte
comite
017cbea257

+ 1 - 0
.gitignore

@@ -12,6 +12,7 @@
 **/ogre.cfg
 **/CMakeCache.txt
 build/
+build-*/
 tools/
 _OLD/
 output/share

+ 5 - 3
lib/luabind/src/class_registry.cpp

@@ -147,9 +147,11 @@ namespace luabind { namespace detail {
 
     void class_registry::add_class(type_id const& info, class_rep* crep)
     {
-        // class is already registered
-        assert((m_classes.find(info) == m_classes.end()) 
-            && "you are trying to register a class twice");
+        // If the class is already registered, don't abort — a plugin or other
+        // component may attempt to register bindings for the same C++ type.
+        if (m_classes.find(info) != m_classes.end()){
+            return;
+        }
         m_classes[info] = crep;
     }
 

+ 13 - 0
src/common/VGearsApplication.h

@@ -93,6 +93,19 @@ namespace VGears{
              */
             Ogre::ResourceGroupManager* ResMgr() {return res_mgr_;}
 
+            /**
+             * Re-finalizes singleton registration after full object construction.
+             * 
+             * Call this method immediately after constructing the Application
+             * to ensure the singleton pointer is properly registered after the
+             * object's vptr is fully initialized (avoids UBSAN vptr warnings).
+             * 
+             * @param[in] instance Pointer to the Application instance.
+             */
+            static void FinalizeSingletonRegistration(Application* instance) {
+                msSingleton = instance;
+            }
+
         protected:
 
             /**

+ 8 - 6
src/core/AudioManager.cpp

@@ -279,8 +279,10 @@ const char* AudioManager::ALCError(const ALCdevice* device){
 
 AudioManager::Player::Player(boost::recursive_mutex* mutex):
   loop_(-1.0), vorbis_info_(nullptr), vorbis_section_(0),
-  stream_finished_(false), update_mutex_(mutex)
-{buffer_ = new char[1024 * 96];}
+  stream_finished_(false), update_mutex_(mutex), source_(0), file_()
+{
+    buffer_ = new char[1024 * 96];
+}
 
 AudioManager::Player::~Player(){Stop();}
 
@@ -369,7 +371,7 @@ void AudioManager::Player::SetLoop(const float loop){
 
 void AudioManager::Player::Update(){
     // Try to fill processed buffers
-    int processed;
+    int processed = 0;
     alGetSourcei(source_, AL_BUFFERS_PROCESSED, &processed);
     for(int i = 0; i < processed; ++i){
         // Try to fill buffer
@@ -398,11 +400,11 @@ void AudioManager::Player::Update(){
 
     // Manage source state
     alGetSourcei(source_, AL_BUFFERS_PROCESSED, &processed);
-    int queued;
+    int queued = 0;
     alGetSourcei(source_, AL_BUFFERS_QUEUED, &queued);
     if (stream_finished_ && processed == queued) Stop();
     else{
-        ALenum source_state;
+        ALenum source_state = AL_NONE;
         alGetSourcei(source_, AL_SOURCE_STATE, &source_state);
         if (source_state == AL_STOPPED) alSourcePlay(source_);
     }
@@ -444,7 +446,7 @@ ALsizei AudioManager::Player::FillBuffer(){
 
 float AudioManager::Player::GetPosition(){
     boost::recursive_mutex::scoped_lock lock(*update_mutex_);
-    int play_offset;
+    int play_offset = 0;
     alGetSourcei(source_, AL_SAMPLE_OFFSET, &play_offset);
     return (
       ov_pcm_tell(&vorbis_file_)

+ 15 - 0
src/core/Savemap.cpp

@@ -22,6 +22,21 @@
 #include "core/XmlFile.h"
 #include "common/TypeDefine.h"
 
+constexpr unsigned int Savemap::MAX_CHARACTERS;
+constexpr unsigned int Savemap::MAX_ITEM_SLOTS;
+constexpr unsigned int Savemap::MAX_KEY_ITEM_SLOTS;
+constexpr unsigned int Savemap::MAX_MATERIA_SLOTS;
+constexpr unsigned int Savemap::MAX_STASH_SLOTS;
+constexpr unsigned int Savemap::MAX_EQUIP_SLOTS;
+constexpr unsigned int Savemap::MAX_ENEMY_SKILLS;
+constexpr unsigned int Savemap::MAX_LIMIT_LEVELS;
+constexpr unsigned int Savemap::MAX_LIMIT_TECHNIQUES;
+constexpr unsigned int Savemap::MAX_LIMIT_BAR;
+constexpr unsigned int Savemap::MAX_PARTY_MEMBERS;
+constexpr unsigned int Savemap::BANK_COUNT;
+constexpr unsigned int Savemap::BANK_ADDRESS_COUNT;
+constexpr unsigned int Savemap::MAX_COLOUR;
+
 Savemap::Savemap():
   empty_(true), control_(""), money_(0), seconds_(0), countdown_(0), slot_(-1)
 {

+ 29 - 29
src/core/Savemap.h

@@ -780,42 +780,42 @@ class Savemap{
                 /**
                  * Strength stat.
                  */
-                static const unsigned int STR = 0;
+                static constexpr unsigned int STR = 0;
 
                 /**
                  * Vtatlity stat.
                  */
-                static const unsigned int VIT = 1;
+                static constexpr unsigned int VIT = 1;
 
                 /**
                  * Magic stat.
                  */
-                static const unsigned int MAG = 2;
+                static constexpr unsigned int MAG = 2;
 
                 /**
                  * Spirit stat.
                  */
-                static const unsigned int SPR = 3;
+                static constexpr unsigned int SPR = 3;
 
                 /**
                  * Dexterity stat.
                  */
-                static const unsigned int DEX = 4;
+                static constexpr unsigned int DEX = 4;
 
                 /**
                  * Luck stat.
                  */
-                static const unsigned int LCK = 5;
+                static constexpr unsigned int LCK = 5;
 
                 /**
                  * HP stat.
                  */
-                static const unsigned int HP = 6;
+                static constexpr unsigned int HP = 6;
 
                 /**
                  * MP stat.
                  */
-                static const unsigned int MP = 7;
+                static constexpr unsigned int MP = 7;
         };
 
         /**
@@ -828,22 +828,22 @@ class Savemap{
                 /**
                  * Top left window corner.
                  */
-                static const unsigned int T_L = 0;
+                static constexpr unsigned int T_L = 0;
 
                 /**
                  * Top right window corner.
                  */
-                static const unsigned int T_R = 1;
+                static constexpr unsigned int T_R = 1;
 
                 /**
                  * Bottom right window corner.
                  */
-                static const unsigned int B_R = 2;
+                static constexpr unsigned int B_R = 2;
 
                 /**
                  * Bottom left window corner.
                  */
-                static const unsigned int B_L = 3;
+                static constexpr unsigned int B_L = 3;
         };
 
         /**
@@ -856,17 +856,17 @@ class Savemap{
                 /**
                  * Red colour.
                  */
-                static const unsigned int R = 0;
+                static constexpr unsigned int R = 0;
 
                 /**
                  * Green colour.
                  */
-                static const unsigned int G = 1;
+                static constexpr unsigned int G = 1;
 
                 /**
                  * Blue colour.
                  */
-                static const unsigned int B = 2;
+                static constexpr unsigned int B = 2;
         };
 
     private:
@@ -874,72 +874,72 @@ class Savemap{
         /**
          * Maximum number of characters.
          */
-        static const unsigned int MAX_CHARACTERS = 11;
+        static constexpr unsigned int MAX_CHARACTERS = 11;
 
         /**
          * Maximum number of inventory slots.
          */
-        static const unsigned int MAX_ITEM_SLOTS = 500;
+        static constexpr unsigned int MAX_ITEM_SLOTS = 500;
 
         /**
          * Maximum number of inventory slots for key items.
          */
-        static const unsigned int MAX_KEY_ITEM_SLOTS = 100;
+        static constexpr unsigned int MAX_KEY_ITEM_SLOTS = 100;
 
         /**
          * Maximum number of materia slots.
          */
-        static const unsigned int MAX_MATERIA_SLOTS = 500;
+        static constexpr unsigned int MAX_MATERIA_SLOTS = 500;
 
         /**
          * Maximum number of materia slots in the stash.
          */
-        static const unsigned int MAX_STASH_SLOTS = 500;
+        static constexpr unsigned int MAX_STASH_SLOTS = 500;
 
         /**
          * Maximum number of materia slots in a weapon or armor.
          */
-        static const unsigned int MAX_EQUIP_SLOTS = 10;
+        static constexpr unsigned int MAX_EQUIP_SLOTS = 10;
 
         /**
          * Maximum number of skills in an Enemy Skill materia.
          */
-        static const unsigned int MAX_ENEMY_SKILLS = 32;
+        static constexpr unsigned int MAX_ENEMY_SKILLS = 32;
 
         /**
          * Maximum limit level.
          */
-        static const unsigned int MAX_LIMIT_LEVELS = 4;
+        static constexpr unsigned int MAX_LIMIT_LEVELS = 4;
 
         /**
          * Maximum limit techniques per level.
          */
-        static const unsigned int MAX_LIMIT_TECHNIQUES = 4;
+        static constexpr unsigned int MAX_LIMIT_TECHNIQUES = 4;
 
         /**
          * Level at which the limit level is full.
          */
-        static const unsigned int MAX_LIMIT_BAR = 254;
+        static constexpr unsigned int MAX_LIMIT_BAR = 254;
 
         /**
          * Maximum number of party member.
          */
-        static const unsigned int MAX_PARTY_MEMBERS = 3;
+        static constexpr unsigned int MAX_PARTY_MEMBERS = 3;
 
         /**
          * Number of data banks.
          */
-        static const unsigned int BANK_COUNT = 16;
+        static constexpr unsigned int BANK_COUNT = 16;
 
         /**
          * Number of addresses in each data bank.
          */
-        static const unsigned int BANK_ADDRESS_COUNT = 256;
+        static constexpr unsigned int BANK_ADDRESS_COUNT = 256;
 
         /**
          * MAx colour component value.
          */
-        static const unsigned int MAX_COLOUR = 254;
+        static constexpr unsigned int MAX_COLOUR = 254;
 
 
         /**

+ 5 - 0
src/core/ScriptManager.cpp

@@ -33,6 +33,11 @@ ConfigVar cv_debug_script(
  */
 template<>ScriptManager *Ogre::Singleton<ScriptManager>::msSingleton = nullptr;
 
+/**
+ * Global guard used by ScriptManagerBinds to prevent multiple luabind class registrations.
+ */
+bool g_luabind_binds_registered = false;
+
 Ogre::String script_entity_type[] = {"SYSTEM", "ENTITY", "UI", "BATTLE"};
 
 /**

+ 22 - 2
src/core/ScriptManagerBinds.h

@@ -32,6 +32,11 @@
 #include "XmlMapsFile.h"
 #include "DialogsManager.h"
 #include "TextHandler.h"
+#include <luabind/detail/class_registry.hpp>
+#include <luabind/typeid.hpp>
+
+// Global guard for luabind registrations (defined in ScriptManagerBinds.cpp)
+extern bool g_luabind_binds_registered;
 
 
 /*
@@ -86,8 +91,24 @@ void ScriptConsole(const char* text){
 }
 
 void ScriptManager::InitBinds(){
+  if (g_luabind_binds_registered) return;
+
+  // If luabind has already registered any class in this Lua state (for
+  // example a plugin performed bindings earlier), skip the whole binds
+  // registration to avoid the "register a class twice" assertion.
+  luabind::detail::class_registry* registry = luabind::detail::class_registry::get_registry(lua_state_);
+  if (registry) {
+    if (!registry->get_classes().empty()){
+      std::cerr << "[ScriptManager] InitBinds() skipped: luabind registry already contains classes" << std::endl;
+      g_luabind_binds_registered = true;
+      return;
+    }
+  }
 
-    // Global functions.
+  g_luabind_binds_registered = true;
+
+  std::cerr << "[ScriptManager] InitBinds() starting registration" << std::endl;
+  // Global functions.
     luabind::module(lua_state_)[
         luabind::def("print", (void(*)(const char*)) &ScriptPrint),
         luabind::def("map", (void(*)(const char*)) &ScriptMap),
@@ -101,7 +122,6 @@ void ScriptManager::InitBinds(){
             "set_position",
             (void(Entity::*)(const float, const float, const float)) &Entity::ScriptSetPosition
           )
-          // Internally returns 3 values:
           .def("get_position", (void(Entity::*)()) &Entity::ScriptGetPosition)
           .def("set_rotation", (void(Entity::*)(const float)) &Entity::ScriptSetRotation)
           .def("get_rotation", (float(Entity::*)()) &Entity::ScriptGetRotation)

+ 2 - 0
src/installer/CMakeLists.txt

@@ -13,6 +13,7 @@ set(INSTALLER_SOURCE_FILES
     FieldTextWriter.cpp
     KernelDataInstaller.cpp
     MainWindow.cpp
+    Release.cpp
     MediaDataInstaller.cpp
     ModelsAndAnimationsDb.cpp
     ScopedLgp.cpp
@@ -133,6 +134,7 @@ target_link_libraries(v-gears-installer
     libvgears
     Qt5::Widgets
     Qt5::Core
+    archive
     ${OIS_LIBRARIES}
     ${TinyXML_LIBRARIES}
     ${BOOST_LINK_LIBS}

+ 27 - 3
src/installer/MainWindow.cpp

@@ -26,12 +26,15 @@
 #include "DataInstaller.h"
 #include "MainWindow.h"
 #include "ui_MainWindow.h"
+#include "Release.h"
 
 /**
  * Indicates if an installer has already been created.
  */
 static bool installer_created = false;
 
+Release release;
+
 MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), main_window_(new Ui::MainWindow){
     main_window_->setupUi(this);
     // Goto the data tab by default.
@@ -133,10 +136,31 @@ void MainWindow::on_btn_vgears_run_clicked(){
 }
 
 void MainWindow::on_btn_data_src_clicked(){
-    QString temp = QFileDialog::getExistingDirectory(
-      this, tr("Location of extracted original game data"), QDir::homePath()
+    const QString filter = tr("ISO images (*.iso)");
+    QString temp = QFileDialog::getOpenFileName(
+      this,
+      tr("Select ISO image"),
+      settings_->value("DataDir").toString(),
+      filter
     );
-    main_window_->line_data_src->setText(temp);
+    std::cout << "Selected ISO: " << temp.toStdString() << std::endl;
+    if (!temp.isNull()){
+        main_window_->line_data_src->setText(temp);
+        release = Release(temp.toStdString());
+        main_window_->isoData->setText(QString::fromStdString(release.getId()));
+        if (!release.isValid()){
+            main_window_->isoError->setStyleSheet("QLabel { color : red; }");
+            main_window_->isoError->setText(QString::fromStdString(release.getErrorMessage()));
+        }
+        else if (!release.isSupported()){
+            main_window_->isoError->setStyleSheet("QLabel { color : orange; }");
+            main_window_->isoError->setText(QString::fromStdString(release.getWarningMessage()));
+        }
+        else{
+            main_window_->isoError->setStyleSheet("QLabel { color : green; }");
+            main_window_->isoError->setText(tr("ISO is valid and supported."));
+        }
+    }
 }
 
 void MainWindow::on_line_data_dst_editingFinished(){

+ 36 - 12
src/installer/MainWindow.ui

@@ -18,7 +18,7 @@
     <item>
      <widget class="QTabWidget" name="tabWidget">
       <property name="tabPosition">
-       <enum>QTabWidget::North</enum>
+       <enum>QTabWidget::TabPosition::North</enum>
       </property>
       <property name="currentIndex">
        <number>0</number>
@@ -29,7 +29,7 @@
        </attribute>
        <layout class="QVBoxLayout" name="verticalLayout_3">
         <item>
-         <layout class="QHBoxLayout" name="horizontalLayout_4">
+         <layout class="QHBoxLayout" name="isoLocator">
           <item>
            <widget class="QLabel" name="label_4">
             <property name="minimumSize">
@@ -45,10 +45,10 @@
              </size>
             </property>
             <property name="text">
-             <string>Original FFVII extracted data:</string>
+             <string>Final Fantasy VII ISO file: </string>
             </property>
             <property name="alignment">
-             <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+             <set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter</set>
             </property>
            </widget>
           </item>
@@ -93,7 +93,31 @@
          </layout>
         </item>
         <item>
-         <layout class="QHBoxLayout" name="horizontalLayout_3">
+         <layout class="QVBoxLayout" name="isoInfo">
+          <property name="leftMargin">
+           <number>30</number>
+          </property>
+          <item>
+           <widget class="QLabel" name="isoData">
+            <property name="text">
+             <string>ISO Info: </string>
+            </property>
+            <property name="margin">
+             <number>0</number>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QLabel" name="isoError">
+            <property name="text">
+             <string>Message</string>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </item>
+        <item>
+         <layout class="QHBoxLayout" name="output">
           <item>
            <widget class="QLabel" name="label_3">
             <property name="minimumSize">
@@ -112,7 +136,7 @@
              <string>VGears installation directory:</string>
             </property>
             <property name="alignment">
-             <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+             <set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter</set>
             </property>
            </widget>
           </item>
@@ -351,10 +375,10 @@
         <item>
          <spacer name="verticalSpacer">
           <property name="orientation">
-           <enum>Qt::Vertical</enum>
+           <enum>Qt::Orientation::Vertical</enum>
           </property>
           <property name="sizeType">
-           <enum>QSizePolicy::Fixed</enum>
+           <enum>QSizePolicy::Policy::Fixed</enum>
           </property>
           <property name="sizeHint" stdset="0">
            <size>
@@ -377,7 +401,7 @@
            <number>0</number>
           </property>
           <property name="alignment">
-           <set>Qt::AlignCenter</set>
+           <set>Qt::AlignmentFlag::AlignCenter</set>
           </property>
           <property name="textVisible">
            <bool>false</bool>
@@ -414,7 +438,7 @@
            <string/>
           </property>
           <property name="alignment">
-           <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+           <set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
           </property>
          </widget>
         </item>
@@ -448,7 +472,7 @@
            <string/>
           </property>
           <property name="alignment">
-           <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+           <set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
           </property>
          </widget>
         </item>
@@ -483,7 +507,7 @@
            </font>
           </property>
           <property name="verticalScrollBarPolicy">
-           <enum>Qt::ScrollBarAlwaysOn</enum>
+           <enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOn</enum>
           </property>
           <property name="undoRedoEnabled">
            <bool>false</bool>

+ 338 - 0
src/installer/Release.cpp

@@ -0,0 +1,338 @@
+/*
+ * 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 <string>
+#include <vector>
+#include <regex>
+#include <archive.h>
+#include <archive_entry.h>
+#include "Release.h"
+
+Release::Release(){
+    id = "";
+    platform = PLATFORM_UNKNOWN;
+    region = REGION_UNKNOWN;
+    language = LANGUAGE_UNKNOWN;
+    disk = DISK_UNKNOWN;
+    valid = false;
+    supported = false;
+    error_message = "";
+    warning_message = "";
+};
+
+
+
+Release::Release(std::string iso_path){
+    Release();
+    struct archive* a = archive_read_new();
+    struct archive_entry* entry;
+    archive_read_support_format_iso9660(a);
+    archive_read_support_format_all(a); // Fail-safe fallback
+
+    if (archive_read_open_filename(a, iso_path.c_str(), 10240) != ARCHIVE_OK) {
+        archive_read_free(a);
+        // Error readding iso
+        error_message = "Failed to read ISO file: " + std::string(archive_error_string(a));
+        return;
+    }
+    std::string game_id = "";
+
+    // Scan headers without extracting data to disk
+    while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
+        std::string path = archive_entry_pathname(entry);
+        
+        // Convert to uppercase to handle varied ISO naming conventions
+        for (char &c : path) c = std::toupper(c);
+
+        // We only care about SYSTEM.CNF at the root
+        if (path == "SYSTEM.CNF" || path == "/SYSTEM.CNF" || path.find("SYSTEM.CNF;") != std::string::npos) {
+            size_t size = archive_entry_size(entry);
+            if (size > 0) {
+                std::vector<char> buffer(size);
+                archive_read_data(a, buffer.data(), size);
+                
+                std::string cnf_content(buffer.begin(), buffer.end());
+                // Get the game ID from the SYSTEM.CNF content using regex
+                // Looks for patterns like SLUS_123.45, SCUS_944.44, SLES_001.23, etc.
+                std::regex code_regex(R"(([A-Z]{4})_(\d{3})\.(\d{2}))");
+                std::smatch match;
+
+                if (std::regex_search(cnf_content, match, code_regex)) {
+                    // match[1] = ABCD, match[2] = 123, match[3] = 45
+                    // Standardize it to the common "ABCD-12345" format
+                    game_id = match[1].str() + "-" + match[2].str() + match[3].str();
+                }
+                else game_id = "";
+            }
+            break; // Found a PC release
+            std::string path = archive_entry_pathname(entry);
+            if (path.find("diski.x") != std::string::npos) {
+                id = "Final Fantasy VII (PC) Disc 1";
+                platform = PLATFORM_PC;
+                region = REGION_UNKNOWN;
+                language = LANGUAGE_UNKNOWN;
+                disk = DISK_1;
+                supported = true;
+                valid = true;
+            }
+            else if (path.find("diskii.x") != std::string::npos) {
+                id = "Final Fantasy VII (PC) Disc 3";
+                platform = PLATFORM_PC;
+                region = REGION_UNKNOWN;
+                language = LANGUAGE_UNKNOWN;
+                disk = DISK_3;
+                supported = true;
+                valid = true;
+            }
+            else if (path.find("diskiii.x") != std::string::npos) {
+                id = "Final Fantasy VII (PC) Disc 3";
+                platform = PLATFORM_PC;
+                region = REGION_UNKNOWN;
+                language = LANGUAGE_UNKNOWN;
+                disk = DISK_3;
+                supported = true;
+                valid = true;
+            }
+            return;
+        }
+    }
+    archive_read_close(a);
+    archive_read_free(a);
+
+    // If it's a PC release, no need to check the game ID further
+    if (platform == PLATFORM_PC) {
+        return;
+    }
+
+    if (game_id.empty()) {
+        error_message = "Game ID not found in ISO file: " + iso_path;
+        return;
+    }
+    
+    if (game_id == "SCUS-94163") {
+        id = "Final Fantasy VII (USA) Disc 1";
+        platform = PLATFORM_PS1;
+        region = REGION_NORTH_AMERICA;
+        language = LANGUAGE_ENGLISH;
+        disk = DISK_1;
+        supported = true;
+        valid = true;
+    }
+    else if (game_id == "SCUS-94164") {
+        id = "Final Fantasy VII (USA) Disc 2";
+        platform = PLATFORM_PS1;
+        region = REGION_NORTH_AMERICA;
+        language = LANGUAGE_ENGLISH;
+        disk = DISK_2;
+        supported = true;
+        valid = true;
+    }
+    else if (game_id == "SCUS-94165") {
+        id = "Final Fantasy VII (USA) Disc 3";
+        platform = PLATFORM_PS1;
+        region = REGION_NORTH_AMERICA;
+        language = LANGUAGE_ENGLISH;
+        disk = DISK_3;
+        supported = true;
+        valid = true;
+    }
+    else if (game_id == "SCES-00867") {
+        id = "Final Fantasy VII (Europe, English) Disc 1";
+        platform = PLATFORM_PS1;
+        region = REGION_EUROPE;
+        language = LANGUAGE_ENGLISH;
+        disk = DISK_1;
+        supported = true;
+        valid = true;
+    }
+    else if (game_id == "SCES-10867") {
+        id = "Final Fantasy VII (Europe, English) Disc 2";
+        platform = PLATFORM_PS1;
+        region = REGION_EUROPE;
+        language = LANGUAGE_ENGLISH;
+        disk = DISK_2;
+        supported = true;
+        valid = true;
+    }
+    else if (game_id == "SCES-20867") {
+        id = "Final Fantasy VII (Europe, English) Disc 3";
+        platform = PLATFORM_PS1;
+        region = REGION_EUROPE;
+        language = LANGUAGE_ENGLISH;
+        disk = DISK_3;
+        supported = true;
+        valid = true;
+    }
+    else if (game_id == "SCES-00868") {
+        id = "Final Fantasy VII (Europe, French) Disc 1";
+        platform = PLATFORM_PS1;
+        region = REGION_EUROPE;
+        language = LANGUAGE_FRENCH;
+        disk = DISK_1;
+        supported = false;
+        warning_message = "Spanish language is not fully supported in V-Gears. Some features may not work as expected.";
+        valid = true;
+    }
+    else if (game_id == "SCES-10868") {
+        id = "Final Fantasy VII (Europe, French) Disc 2";
+        platform = PLATFORM_PS1;
+        region = REGION_EUROPE;
+        language = LANGUAGE_FRENCH;
+        disk = DISK_2;
+        supported = false;
+        warning_message = "Spanish language is not fully supported in V-Gears. Some features may not work as expected.";
+        valid = true;
+    }
+    else if (game_id == "SCES-20868") {
+        id = "Final Fantasy VII (Europe, French) Disc 3";
+        platform = PLATFORM_PS1;
+        region = REGION_EUROPE;
+        language = LANGUAGE_FRENCH;
+        disk = DISK_3;
+        supported = false;
+        warning_message = "Spanish language is not fully supported in V-Gears. Some features may not work as expected.";
+        valid = true;
+    }
+    else if (game_id == "SCES-00900") {
+        id = "Final Fantasy VII (Europe, Spanish) Disc 1";
+        platform = PLATFORM_PS1;
+        region = REGION_EUROPE;
+        language = LANGUAGE_SPANISH;
+        disk = DISK_1;
+        supported = true;supported = false;
+        warning_message = "Spanish language is not fully supported in V-Gears. Some features may not work as expected.";
+        valid = true;
+    }
+    else if (game_id == "SCES-10900") {
+        id = "Final Fantasy VII (Europe, Spanish) Disc 2";
+        platform = PLATFORM_PS1;
+        region = REGION_EUROPE;
+        language = LANGUAGE_SPANISH;
+        disk = DISK_2;
+        supported = false;
+        warning_message = "Spanish language is not fully supported in V-Gears. Some features may not work as expected.";
+        valid = true;
+    }
+    else if (game_id == "SCES-20900") {
+        id = "Final Fantasy VII (Europe, Spanish) Disc 3";
+        platform = PLATFORM_PS1;
+        region = REGION_EUROPE;
+        language = LANGUAGE_SPANISH;
+        disk = DISK_3;
+        supported = false;
+        warning_message = "Spanish language is not fully supported in V-Gears. Some features may not work as expected.";
+        valid = true;
+    }
+    else if (game_id == "SCES-00869") {
+        id = "Final Fantasy VII (Europe, German) Disc 1";
+        platform = PLATFORM_PS1;
+        region = REGION_EUROPE;
+        language = LANGUAGE_GERMAN;
+        disk = DISK_1;
+        supported = true;supported = false;
+        warning_message = "German language is not fully supported in V-Gears. Some features may not work as expected.";
+        valid = true;
+    }
+    else if (game_id == "SCES-10869") {
+        id = "Final Fantasy VII (Europe, German) Disc 2";
+        platform = PLATFORM_PS1;
+        region = REGION_EUROPE;
+        language = LANGUAGE_GERMAN;
+        disk = DISK_2;
+        supported = false;
+        warning_message = "German language is not fully supported in V-Gears. Some features may not work as expected.";
+        valid = true;
+    }
+    else if (game_id == "SCES-20869") {
+        id = "Final Fantasy VII (Europe, German) Disc 3";
+        platform = PLATFORM_PS1;
+        region = REGION_EUROPE;
+        language = LANGUAGE_GERMAN;
+        disk = DISK_3;
+        supported = false;
+        warning_message = "German language is not fully supported in V-Gears. Some features may not work as expected.";
+        valid = true;
+    }
+    else if (game_id == "SCES-00901") {
+        id = "Final Fantasy VII (Europe, Italian) Disc 1";
+        platform = PLATFORM_PS1;
+        region = REGION_EUROPE;
+        language = LANGUAGE_ITALIAN;
+        disk = DISK_1;
+        supported = true;supported = false;
+        warning_message = "Italian language is not fully supported in V-Gears. Some features may not work as expected.";
+        valid = true;
+    }
+    else if (game_id == "SCES-10901") {
+        id = "Final Fantasy VII (Europe, Italian) Disc 2";
+        platform = PLATFORM_PS1;
+        region = REGION_EUROPE;
+        language = LANGUAGE_ITALIAN;
+        disk = DISK_2;
+        supported = false;
+        warning_message = "Italian language is not fully supported in V-Gears. Some features may not work as expected.";
+        valid = true;
+    }
+    else if (game_id == "SCES-20901") {
+        id = "Final Fantasy VII (Europe, Italian) Disc 3";
+        platform = PLATFORM_PS1;
+        region = REGION_EUROPE;
+        language = LANGUAGE_ITALIAN;
+        disk = DISK_3;
+        supported = false;
+        warning_message = "Italian language is not fully supported in V-Gears. Some features may not work as expected.";
+        valid = true;
+    }
+    return;
+};
+
+Release::~Release(){};
+
+std::string Release::getId(){
+    return id;
+};
+
+Release::Platform Release::getPlatform(){
+    return platform;
+};
+
+Release::Region Release::getRegion(){
+    return region;
+};
+
+Release::Language Release::getLanguage(){
+    return language;
+};
+
+Release::Disk Release::getDisk(){
+    return disk;
+};
+
+bool Release::isValid(){
+    return valid;
+};
+
+bool Release::isSupported(){
+    return supported;
+};
+
+std::string Release::getErrorMessage(){
+    return error_message;
+};
+
+std::string Release::getWarningMessage(){
+    return warning_message;
+};

+ 201 - 0
src/installer/Release.h

@@ -0,0 +1,201 @@
+/*
+ * 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.
+ */
+
+#pragma once
+#include <string>
+
+/**
+ * Information about Final Fantasy VII releases.
+ */
+struct Release{
+
+    public:
+
+        /**
+         * The platform for which the release is intended.
+         */
+        enum Platform{
+
+            /** The release is for PC. */
+            PLATFORM_PC,
+            /** The release is for PS1. */
+            PLATFORM_PS1,
+            /** The release is for an unknown platform. */
+            PLATFORM_UNKNOWN
+        };
+
+        /**
+         * The region for which the release is intended.
+         */
+        enum Region{
+            /** The release is for Japan. */
+            REGION_JAPAN,
+            /** The release is for North America. */
+            REGION_NORTH_AMERICA,
+            /** The release is for Europe. */
+            REGION_EUROPE,
+            /** The release is for an unknown region. */
+            REGION_UNKNOWN
+        };
+
+        /**
+         * The language for which the release is intended.
+         */
+        enum Language{
+            /** The release is in Japanese. */
+            LANGUAGE_JAPANESE,
+            /** The release is in English. */
+            LANGUAGE_ENGLISH,
+            /** The release is in French. */
+            LANGUAGE_FRENCH,
+            /** The release is in Spanish. */
+            LANGUAGE_SPANISH,
+            /** The release is in German. */
+            LANGUAGE_GERMAN,
+            /** The release is in Italian. */
+            LANGUAGE_ITALIAN,
+            /** The release is for an unknown language. */
+            LANGUAGE_UNKNOWN
+        };
+
+        /**
+         * The disk for which the release is intended.
+         */
+        enum Disk{
+            /** The release is for disk 1. */
+            DISK_1,
+            /** The release is for disk 2. */
+            DISK_2,
+            /** The release is for disk 3. */
+            DISK_3,
+            /** The release is for disk 4. */
+            DISK_4,
+            /** The release is for an unknown disk. */
+            DISK_UNKNOWN
+        };
+
+        /**
+         * Constructs a new release.
+         */
+        Release();
+
+        /**
+         * Constructs a new release from an ISO path.
+         *
+         * @param iso_path The path to the ISO file.
+         */
+        Release(std::string iso_path);
+
+        /**
+         * Destroys the release.
+         */
+        ~Release();
+
+        /**
+         * Gets the human readable ID of the release.
+         *
+         * @return The ID of the release.
+         */
+        std::string getId();
+
+        /**
+         * Gets the platform for which the release is intended.
+         *
+         * @return The platform of the release.
+         */
+        Platform getPlatform();
+
+        /**
+         * Gets the region for which the release is intended.
+         *
+         * @return The region of the release.
+         */
+        Region getRegion();
+
+        /**
+         * Gets the language for which the release is intended.
+         *
+         * @return The language of the release.
+         */
+        Language getLanguage();
+
+        /**
+         * Gets the disk for which the release is intended.
+         *
+         * @return The disk of the release.
+         */
+        Disk getDisk();
+
+        /**
+         * Checks if the release is a valid Final Fantasy VII release.
+         *
+         * @return True if the release is valid, false otherwise.
+         */
+        bool isValid();
+
+        /**
+         * Gets the error message for the release.
+         *
+         * @return The error message.
+         */
+        std::string getErrorMessage();
+
+        /**
+         * Checks if the release is supported and tested in V-Gears.
+         *
+         * @return True if the release is supported, false otherwise.
+         */
+        bool isSupported();
+
+        /**
+         * Gets a warning in case the release is not fully supported.
+         *
+         * @return The warning message.
+         */
+        std::string getWarningMessage();
+
+    private:
+
+        /** A human readable ID of the release. */
+        std::string id;
+
+        /** The platform for which the release is intended. */
+        Platform platform;
+
+        /** The region for which the release is intended. */
+        Region region;
+
+        /** The language for which the release is intended. */
+        Language language;
+
+        /** The disk for which the release is intended. */
+        Disk disk;
+
+        /** Whether the release is valid. */
+        bool valid;
+
+        /** Whether the release is supported. */
+        bool supported;
+
+        /**
+         * The error message for the release. Empty if no error.
+         */
+        std::string error_message;
+
+        /**
+         * The warning message for the release. Empty if no warning.
+         */
+        std::string warning_message;
+};

+ 589 - 0
src/installer/ui_MainWindow.h

@@ -0,0 +1,589 @@
+/********************************************************************************
+** Form generated from reading UI file 'MainWindow.ui'
+**
+** Created by: Qt User Interface Compiler version 5.15.17
+**
+** WARNING! All changes made in this file will be lost when recompiling UI file!
+********************************************************************************/
+
+#ifndef UI_MAINWINDOW_H
+#define UI_MAINWINDOW_H
+
+#include <QtCore/QVariant>
+#include <QtGui/QIcon>
+#include <QtWidgets/QAction>
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QCheckBox>
+#include <QtWidgets/QGroupBox>
+#include <QtWidgets/QHBoxLayout>
+#include <QtWidgets/QLabel>
+#include <QtWidgets/QLineEdit>
+#include <QtWidgets/QMainWindow>
+#include <QtWidgets/QMenu>
+#include <QtWidgets/QMenuBar>
+#include <QtWidgets/QProgressBar>
+#include <QtWidgets/QPushButton>
+#include <QtWidgets/QSpacerItem>
+#include <QtWidgets/QTabWidget>
+#include <QtWidgets/QTextEdit>
+#include <QtWidgets/QVBoxLayout>
+#include <QtWidgets/QWidget>
+
+QT_BEGIN_NAMESPACE
+
+class Ui_MainWindow
+{
+public:
+    QAction *actionExit;
+    QWidget *centralWidget;
+    QVBoxLayout *verticalLayout;
+    QTabWidget *tabWidget;
+    QWidget *tab_installer;
+    QVBoxLayout *verticalLayout_3;
+    QHBoxLayout *isoLocator;
+    QLabel *label_4;
+    QLineEdit *line_data_src;
+    QPushButton *btn_data_src;
+    QPushButton *help_data_src;
+    QVBoxLayout *isoInfo;
+    QLabel *isoData;
+    QLabel *isoError;
+    QHBoxLayout *output;
+    QLabel *label_3;
+    QLineEdit *line_data_dst;
+    QPushButton *btn_data_dst;
+    QPushButton *help_data_dest;
+    QCheckBox *chk_advanced_options;
+    QGroupBox *advancedOptions;
+    QVBoxLayout *verticalLayout_5;
+    QHBoxLayout *horizontalLayout_6;
+    QCheckBox *chk_no_battle_data;
+    QCheckBox *chk_no_battle_models;
+    QHBoxLayout *horizontalLayout_61;
+    QCheckBox *chk_no_kernel;
+    QCheckBox *chk_no_images;
+    QHBoxLayout *horizontalLayout_7;
+    QCheckBox *chk_no_sounds;
+    QCheckBox *chk_no_music;
+    QHBoxLayout *horizontalLayout_8;
+    QCheckBox *chk_no_fields;
+    QCheckBox *chk_no_field_models;
+    QHBoxLayout *horizontalLayout_9;
+    QCheckBox *chk_no_wm;
+    QCheckBox *chk_no_wm_models;
+    QHBoxLayout *horizontalLayout_5;
+    QCheckBox *chk_no_ffmpeg;
+    QCheckBox *chk_no_timidity;
+    QHBoxLayout *horizontalLayout_91;
+    QCheckBox *chk_keep_original;
+    QSpacerItem *verticalSpacer;
+    QPushButton *btn_data_run;
+    QProgressBar *data_progress_bar;
+    QLabel *label_percent;
+    QLabel *label_progress;
+    QLabel *label_5;
+    QTextEdit *data_log;
+    QWidget *tab_vgears;
+    QVBoxLayout *verticalLayout_2;
+    QHBoxLayout *horizontalLayout;
+    QLabel *label_2;
+    QLineEdit *line_vgears_exe;
+    QPushButton *btn_vgears_exe;
+    QHBoxLayout *horizontalLayout_2;
+    QLabel *label;
+    QLineEdit *line_vgears_config;
+    QPushButton *btn_vgears_config;
+    QPushButton *btn_vgears_launch;
+    QMenuBar *menuBar;
+    QMenu *menuFile;
+
+    void setupUi(QMainWindow *MainWindow)
+    {
+        if (MainWindow->objectName().isEmpty())
+            MainWindow->setObjectName(QString::fromUtf8("MainWindow"));
+        MainWindow->resize(720, 703);
+        actionExit = new QAction(MainWindow);
+        actionExit->setObjectName(QString::fromUtf8("actionExit"));
+        centralWidget = new QWidget(MainWindow);
+        centralWidget->setObjectName(QString::fromUtf8("centralWidget"));
+        verticalLayout = new QVBoxLayout(centralWidget);
+        verticalLayout->setSpacing(6);
+        verticalLayout->setContentsMargins(11, 11, 11, 11);
+        verticalLayout->setObjectName(QString::fromUtf8("verticalLayout"));
+        tabWidget = new QTabWidget(centralWidget);
+        tabWidget->setObjectName(QString::fromUtf8("tabWidget"));
+        tabWidget->setTabPosition(QTabWidget::TabPosition::North);
+        tab_installer = new QWidget();
+        tab_installer->setObjectName(QString::fromUtf8("tab_installer"));
+        verticalLayout_3 = new QVBoxLayout(tab_installer);
+        verticalLayout_3->setSpacing(6);
+        verticalLayout_3->setContentsMargins(11, 11, 11, 11);
+        verticalLayout_3->setObjectName(QString::fromUtf8("verticalLayout_3"));
+        isoLocator = new QHBoxLayout();
+        isoLocator->setSpacing(6);
+        isoLocator->setObjectName(QString::fromUtf8("isoLocator"));
+        label_4 = new QLabel(tab_installer);
+        label_4->setObjectName(QString::fromUtf8("label_4"));
+        label_4->setMinimumSize(QSize(200, 0));
+        label_4->setMaximumSize(QSize(200, 50));
+        label_4->setAlignment(Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter);
+
+        isoLocator->addWidget(label_4);
+
+        line_data_src = new QLineEdit(tab_installer);
+        line_data_src->setObjectName(QString::fromUtf8("line_data_src"));
+
+        isoLocator->addWidget(line_data_src);
+
+        btn_data_src = new QPushButton(tab_installer);
+        btn_data_src->setObjectName(QString::fromUtf8("btn_data_src"));
+        btn_data_src->setMaximumSize(QSize(24, 24));
+
+        isoLocator->addWidget(btn_data_src);
+
+        help_data_src = new QPushButton(tab_installer);
+        help_data_src->setObjectName(QString::fromUtf8("help_data_src"));
+        QIcon icon;
+        QString iconThemeName = QString::fromUtf8("dialog-information");
+        if (QIcon::hasThemeIcon(iconThemeName)) {
+            icon = QIcon::fromTheme(iconThemeName);
+        } else {
+            icon.addFile(QString::fromUtf8("Workspace/Software/V-Gears/V-Gears-Installer/src"), QSize(), QIcon::Normal, QIcon::Off);
+        }
+        help_data_src->setIcon(icon);
+        help_data_src->setCheckable(false);
+        help_data_src->setFlat(true);
+
+        isoLocator->addWidget(help_data_src);
+
+
+        verticalLayout_3->addLayout(isoLocator);
+
+        isoInfo = new QVBoxLayout();
+        isoInfo->setSpacing(6);
+        isoInfo->setObjectName(QString::fromUtf8("isoInfo"));
+        isoData = new QLabel(tab_installer);
+        isoData->setObjectName(QString::fromUtf8("isoData"));
+
+        isoInfo->addWidget(isoData);
+
+        isoError = new QLabel(tab_installer);
+        isoError->setObjectName(QString::fromUtf8("isoError"));
+
+        isoInfo->addWidget(isoError);
+
+
+        verticalLayout_3->addLayout(isoInfo);
+
+        output = new QHBoxLayout();
+        output->setSpacing(6);
+        output->setObjectName(QString::fromUtf8("output"));
+        label_3 = new QLabel(tab_installer);
+        label_3->setObjectName(QString::fromUtf8("label_3"));
+        label_3->setMinimumSize(QSize(200, 0));
+        label_3->setMaximumSize(QSize(200, 50));
+        label_3->setAlignment(Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter);
+
+        output->addWidget(label_3);
+
+        line_data_dst = new QLineEdit(tab_installer);
+        line_data_dst->setObjectName(QString::fromUtf8("line_data_dst"));
+
+        output->addWidget(line_data_dst);
+
+        btn_data_dst = new QPushButton(tab_installer);
+        btn_data_dst->setObjectName(QString::fromUtf8("btn_data_dst"));
+        btn_data_dst->setMaximumSize(QSize(24, 24));
+
+        output->addWidget(btn_data_dst);
+
+        help_data_dest = new QPushButton(tab_installer);
+        help_data_dest->setObjectName(QString::fromUtf8("help_data_dest"));
+        help_data_dest->setIcon(icon);
+        help_data_dest->setCheckable(false);
+        help_data_dest->setFlat(true);
+
+        output->addWidget(help_data_dest);
+
+
+        verticalLayout_3->addLayout(output);
+
+        chk_advanced_options = new QCheckBox(tab_installer);
+        chk_advanced_options->setObjectName(QString::fromUtf8("chk_advanced_options"));
+
+        verticalLayout_3->addWidget(chk_advanced_options);
+
+        advancedOptions = new QGroupBox(tab_installer);
+        advancedOptions->setObjectName(QString::fromUtf8("advancedOptions"));
+        QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
+        sizePolicy.setHorizontalStretch(0);
+        sizePolicy.setVerticalStretch(0);
+        sizePolicy.setHeightForWidth(advancedOptions->sizePolicy().hasHeightForWidth());
+        advancedOptions->setSizePolicy(sizePolicy);
+        advancedOptions->setMaximumSize(QSize(16777215, 0));
+        verticalLayout_5 = new QVBoxLayout(advancedOptions);
+        verticalLayout_5->setSpacing(5);
+        verticalLayout_5->setContentsMargins(11, 11, 11, 11);
+        verticalLayout_5->setObjectName(QString::fromUtf8("verticalLayout_5"));
+        verticalLayout_5->setContentsMargins(11, -1, -1, -1);
+        horizontalLayout_6 = new QHBoxLayout();
+        horizontalLayout_6->setSpacing(6);
+        horizontalLayout_6->setObjectName(QString::fromUtf8("horizontalLayout_6"));
+        chk_no_battle_data = new QCheckBox(advancedOptions);
+        chk_no_battle_data->setObjectName(QString::fromUtf8("chk_no_battle_data"));
+
+        horizontalLayout_6->addWidget(chk_no_battle_data);
+
+        chk_no_battle_models = new QCheckBox(advancedOptions);
+        chk_no_battle_models->setObjectName(QString::fromUtf8("chk_no_battle_models"));
+
+        horizontalLayout_6->addWidget(chk_no_battle_models);
+
+
+        verticalLayout_5->addLayout(horizontalLayout_6);
+
+        horizontalLayout_61 = new QHBoxLayout();
+        horizontalLayout_61->setSpacing(6);
+        horizontalLayout_61->setObjectName(QString::fromUtf8("horizontalLayout_61"));
+        chk_no_kernel = new QCheckBox(advancedOptions);
+        chk_no_kernel->setObjectName(QString::fromUtf8("chk_no_kernel"));
+
+        horizontalLayout_61->addWidget(chk_no_kernel);
+
+        chk_no_images = new QCheckBox(advancedOptions);
+        chk_no_images->setObjectName(QString::fromUtf8("chk_no_images"));
+
+        horizontalLayout_61->addWidget(chk_no_images);
+
+
+        verticalLayout_5->addLayout(horizontalLayout_61);
+
+        horizontalLayout_7 = new QHBoxLayout();
+        horizontalLayout_7->setSpacing(6);
+        horizontalLayout_7->setObjectName(QString::fromUtf8("horizontalLayout_7"));
+        chk_no_sounds = new QCheckBox(advancedOptions);
+        chk_no_sounds->setObjectName(QString::fromUtf8("chk_no_sounds"));
+
+        horizontalLayout_7->addWidget(chk_no_sounds);
+
+        chk_no_music = new QCheckBox(advancedOptions);
+        chk_no_music->setObjectName(QString::fromUtf8("chk_no_music"));
+
+        horizontalLayout_7->addWidget(chk_no_music);
+
+
+        verticalLayout_5->addLayout(horizontalLayout_7);
+
+        horizontalLayout_8 = new QHBoxLayout();
+        horizontalLayout_8->setSpacing(6);
+        horizontalLayout_8->setObjectName(QString::fromUtf8("horizontalLayout_8"));
+        chk_no_fields = new QCheckBox(advancedOptions);
+        chk_no_fields->setObjectName(QString::fromUtf8("chk_no_fields"));
+
+        horizontalLayout_8->addWidget(chk_no_fields);
+
+        chk_no_field_models = new QCheckBox(advancedOptions);
+        chk_no_field_models->setObjectName(QString::fromUtf8("chk_no_field_models"));
+
+        horizontalLayout_8->addWidget(chk_no_field_models);
+
+
+        verticalLayout_5->addLayout(horizontalLayout_8);
+
+        horizontalLayout_9 = new QHBoxLayout();
+        horizontalLayout_9->setSpacing(6);
+        horizontalLayout_9->setObjectName(QString::fromUtf8("horizontalLayout_9"));
+        chk_no_wm = new QCheckBox(advancedOptions);
+        chk_no_wm->setObjectName(QString::fromUtf8("chk_no_wm"));
+
+        horizontalLayout_9->addWidget(chk_no_wm);
+
+        chk_no_wm_models = new QCheckBox(advancedOptions);
+        chk_no_wm_models->setObjectName(QString::fromUtf8("chk_no_wm_models"));
+
+        horizontalLayout_9->addWidget(chk_no_wm_models);
+
+
+        verticalLayout_5->addLayout(horizontalLayout_9);
+
+        horizontalLayout_5 = new QHBoxLayout();
+        horizontalLayout_5->setSpacing(6);
+        horizontalLayout_5->setObjectName(QString::fromUtf8("horizontalLayout_5"));
+        chk_no_ffmpeg = new QCheckBox(advancedOptions);
+        chk_no_ffmpeg->setObjectName(QString::fromUtf8("chk_no_ffmpeg"));
+
+        horizontalLayout_5->addWidget(chk_no_ffmpeg);
+
+        chk_no_timidity = new QCheckBox(advancedOptions);
+        chk_no_timidity->setObjectName(QString::fromUtf8("chk_no_timidity"));
+
+        horizontalLayout_5->addWidget(chk_no_timidity);
+
+
+        verticalLayout_5->addLayout(horizontalLayout_5);
+
+        horizontalLayout_91 = new QHBoxLayout();
+        horizontalLayout_91->setSpacing(6);
+        horizontalLayout_91->setObjectName(QString::fromUtf8("horizontalLayout_91"));
+        chk_keep_original = new QCheckBox(advancedOptions);
+        chk_keep_original->setObjectName(QString::fromUtf8("chk_keep_original"));
+
+        horizontalLayout_91->addWidget(chk_keep_original);
+
+
+        verticalLayout_5->addLayout(horizontalLayout_91);
+
+
+        verticalLayout_3->addWidget(advancedOptions);
+
+        verticalSpacer = new QSpacerItem(20, 40, QSizePolicy::Policy::Fixed, QSizePolicy::Minimum);
+
+        verticalLayout_3->addItem(verticalSpacer);
+
+        btn_data_run = new QPushButton(tab_installer);
+        btn_data_run->setObjectName(QString::fromUtf8("btn_data_run"));
+
+        verticalLayout_3->addWidget(btn_data_run);
+
+        data_progress_bar = new QProgressBar(tab_installer);
+        data_progress_bar->setObjectName(QString::fromUtf8("data_progress_bar"));
+        data_progress_bar->setValue(0);
+        data_progress_bar->setAlignment(Qt::AlignmentFlag::AlignCenter);
+        data_progress_bar->setTextVisible(false);
+
+        verticalLayout_3->addWidget(data_progress_bar);
+
+        label_percent = new QLabel(tab_installer);
+        label_percent->setObjectName(QString::fromUtf8("label_percent"));
+        QSizePolicy sizePolicy1(QSizePolicy::Expanding, QSizePolicy::Fixed);
+        sizePolicy1.setHorizontalStretch(1);
+        sizePolicy1.setVerticalStretch(0);
+        sizePolicy1.setHeightForWidth(label_percent->sizePolicy().hasHeightForWidth());
+        label_percent->setSizePolicy(sizePolicy1);
+        label_percent->setMinimumSize(QSize(0, 0));
+        label_percent->setMaximumSize(QSize(16777215, 10));
+        QFont font;
+        font.setPointSize(8);
+        font.setItalic(true);
+        label_percent->setFont(font);
+        label_percent->setAlignment(Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter);
+
+        verticalLayout_3->addWidget(label_percent);
+
+        label_progress = new QLabel(tab_installer);
+        label_progress->setObjectName(QString::fromUtf8("label_progress"));
+        sizePolicy1.setHeightForWidth(label_progress->sizePolicy().hasHeightForWidth());
+        label_progress->setSizePolicy(sizePolicy1);
+        label_progress->setMinimumSize(QSize(0, 0));
+        label_progress->setMaximumSize(QSize(16777215, 10));
+        label_progress->setFont(font);
+        label_progress->setAlignment(Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter);
+
+        verticalLayout_3->addWidget(label_progress);
+
+        label_5 = new QLabel(tab_installer);
+        label_5->setObjectName(QString::fromUtf8("label_5"));
+        label_5->setMaximumSize(QSize(16777215, 15));
+        QFont font1;
+        font1.setFamily(QString::fromUtf8("Courier"));
+        font1.setPointSize(8);
+        label_5->setFont(font1);
+        label_5->setMargin(-1);
+
+        verticalLayout_3->addWidget(label_5);
+
+        data_log = new QTextEdit(tab_installer);
+        data_log->setObjectName(QString::fromUtf8("data_log"));
+        data_log->setFont(font1);
+        data_log->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOn);
+        data_log->setUndoRedoEnabled(false);
+        data_log->setReadOnly(true);
+
+        verticalLayout_3->addWidget(data_log);
+
+        tabWidget->addTab(tab_installer, QString());
+        tab_vgears = new QWidget();
+        tab_vgears->setObjectName(QString::fromUtf8("tab_vgears"));
+        verticalLayout_2 = new QVBoxLayout(tab_vgears);
+        verticalLayout_2->setSpacing(6);
+        verticalLayout_2->setContentsMargins(11, 11, 11, 11);
+        verticalLayout_2->setObjectName(QString::fromUtf8("verticalLayout_2"));
+        horizontalLayout = new QHBoxLayout();
+        horizontalLayout->setSpacing(6);
+        horizontalLayout->setObjectName(QString::fromUtf8("horizontalLayout"));
+        label_2 = new QLabel(tab_vgears);
+        label_2->setObjectName(QString::fromUtf8("label_2"));
+
+        horizontalLayout->addWidget(label_2);
+
+        line_vgears_exe = new QLineEdit(tab_vgears);
+        line_vgears_exe->setObjectName(QString::fromUtf8("line_vgears_exe"));
+
+        horizontalLayout->addWidget(line_vgears_exe);
+
+        btn_vgears_exe = new QPushButton(tab_vgears);
+        btn_vgears_exe->setObjectName(QString::fromUtf8("btn_vgears_exe"));
+        QSizePolicy sizePolicy2(QSizePolicy::Fixed, QSizePolicy::Fixed);
+        sizePolicy2.setHorizontalStretch(0);
+        sizePolicy2.setVerticalStretch(0);
+        sizePolicy2.setHeightForWidth(btn_vgears_exe->sizePolicy().hasHeightForWidth());
+        btn_vgears_exe->setSizePolicy(sizePolicy2);
+        btn_vgears_exe->setMaximumSize(QSize(24, 24));
+
+        horizontalLayout->addWidget(btn_vgears_exe);
+
+
+        verticalLayout_2->addLayout(horizontalLayout);
+
+        horizontalLayout_2 = new QHBoxLayout();
+        horizontalLayout_2->setSpacing(6);
+        horizontalLayout_2->setObjectName(QString::fromUtf8("horizontalLayout_2"));
+        label = new QLabel(tab_vgears);
+        label->setObjectName(QString::fromUtf8("label"));
+
+        horizontalLayout_2->addWidget(label);
+
+        line_vgears_config = new QLineEdit(tab_vgears);
+        line_vgears_config->setObjectName(QString::fromUtf8("line_vgears_config"));
+
+        horizontalLayout_2->addWidget(line_vgears_config);
+
+        btn_vgears_config = new QPushButton(tab_vgears);
+        btn_vgears_config->setObjectName(QString::fromUtf8("btn_vgears_config"));
+        sizePolicy2.setHeightForWidth(btn_vgears_config->sizePolicy().hasHeightForWidth());
+        btn_vgears_config->setSizePolicy(sizePolicy2);
+        btn_vgears_config->setMaximumSize(QSize(24, 24));
+
+        horizontalLayout_2->addWidget(btn_vgears_config);
+
+
+        verticalLayout_2->addLayout(horizontalLayout_2);
+
+        btn_vgears_launch = new QPushButton(tab_vgears);
+        btn_vgears_launch->setObjectName(QString::fromUtf8("btn_vgears_launch"));
+
+        verticalLayout_2->addWidget(btn_vgears_launch);
+
+        tabWidget->addTab(tab_vgears, QString());
+
+        verticalLayout->addWidget(tabWidget);
+
+        MainWindow->setCentralWidget(centralWidget);
+        menuBar = new QMenuBar(MainWindow);
+        menuBar->setObjectName(QString::fromUtf8("menuBar"));
+        menuBar->setGeometry(QRect(0, 0, 720, 23));
+        menuFile = new QMenu(menuBar);
+        menuFile->setObjectName(QString::fromUtf8("menuFile"));
+        MainWindow->setMenuBar(menuBar);
+
+        menuBar->addAction(menuFile->menuAction());
+        menuFile->addAction(actionExit);
+
+        retranslateUi(MainWindow);
+        QObject::connect(actionExit, SIGNAL(triggered()), MainWindow, SLOT(close()));
+
+        tabWidget->setCurrentIndex(0);
+
+
+        QMetaObject::connectSlotsByName(MainWindow);
+    } // setupUi
+
+    void retranslateUi(QMainWindow *MainWindow)
+    {
+        MainWindow->setWindowTitle(QCoreApplication::translate("MainWindow", "VGears Installer", nullptr));
+        actionExit->setText(QCoreApplication::translate("MainWindow", "Exit", nullptr));
+        label_4->setText(QCoreApplication::translate("MainWindow", "Original FFVII extracted data:", nullptr));
+        btn_data_src->setText(QCoreApplication::translate("MainWindow", "...", nullptr));
+#if QT_CONFIG(tooltip)
+        help_data_src->setToolTip(QCoreApplication::translate("MainWindow", "\n"
+"              <html><head/><body><b>Path to the extracted FFVII (PC version) data.</b><br><br>It must contain (at least) the folders 'kernel' and 'field'.</body></html>\n"
+"             ", nullptr));
+#endif // QT_CONFIG(tooltip)
+        help_data_src->setText(QString());
+        isoData->setText(QCoreApplication::translate("MainWindow", "ISO Info: ", nullptr));
+        isoError->setText(QCoreApplication::translate("MainWindow", "Message", nullptr));
+        label_3->setText(QCoreApplication::translate("MainWindow", "VGears installation directory:", nullptr));
+        btn_data_dst->setText(QCoreApplication::translate("MainWindow", "...", nullptr));
+#if QT_CONFIG(tooltip)
+        help_data_dest->setToolTip(QCoreApplication::translate("MainWindow", "\n"
+"              <html><head/><body><b>V-Gears data installation directory.</b><br><br>The data will be installed to this location. Existing installations will be overwritten.</body></html>\n"
+"             ", nullptr));
+#endif // QT_CONFIG(tooltip)
+        help_data_dest->setText(QString());
+        chk_advanced_options->setText(QCoreApplication::translate("MainWindow", "Show advanced options", nullptr));
+        advancedOptions->setTitle(QString());
+#if QT_CONFIG(tooltip)
+        chk_no_battle_data->setToolTip(QCoreApplication::translate("MainWindow", "<html><head/><body><b>Don't extract battle data.</b><br><br>If checked, battle data such as enemies, attacks or enemy formations will not be extracted. Data from previous installations will not be deleted.<br><br>This installation step is usually quite fast, so there is no reason to skip it unless you have manually changed data.</body></html>", nullptr));
+#endif // QT_CONFIG(tooltip)
+        chk_no_battle_data->setText(QCoreApplication::translate("MainWindow", "Don't extract battle data", nullptr));
+#if QT_CONFIG(tooltip)
+        chk_no_battle_models->setToolTip(QCoreApplication::translate("MainWindow", "<html><head/><body><b>Don't extract battle models.</b><br><br>If checked, battle 3D models will not be extracted. This includes models for players, enemies, attacks, battle scenarios... Models from previous installations will not be deleted.<br><br>This installation step is usually quite fast, so there is no reason to skip it unless you have manually edited the models.</body></html>", nullptr));
+#endif // QT_CONFIG(tooltip)
+        chk_no_battle_models->setText(QCoreApplication::translate("MainWindow", "Don't extract battle models", nullptr));
+#if QT_CONFIG(tooltip)
+        chk_no_kernel->setToolTip(QCoreApplication::translate("MainWindow", "<html><head/><body><b>Don't extract kernel data.</b><br><br>If checked, game data such as items, materia, character information or initial savemap will not be extracted. Data from previous installations will not be deleted.<br><br>This installation step is usually quite fast, so there is no reason to skip it unless you have manually changed data.</body></html>", nullptr));
+#endif // QT_CONFIG(tooltip)
+        chk_no_kernel->setText(QCoreApplication::translate("MainWindow", "Don't extract kernel data", nullptr));
+#if QT_CONFIG(tooltip)
+        chk_no_images->setToolTip(QCoreApplication::translate("MainWindow", "<html><head/><body><b>Don't extract images.</b><br><br>If checked, images will not be extracted. This includes window decorations, menus icons, character portraits... It doesn' include backgrounds or textures. Images from previous installations will not be deleted.<br><br>This installation step is usually quite fast, so there is no reason to skip it unless you have manually edited the images.</body></html>", nullptr));
+#endif // QT_CONFIG(tooltip)
+        chk_no_images->setText(QCoreApplication::translate("MainWindow", "Don't extract images", nullptr));
+#if QT_CONFIG(tooltip)
+        chk_no_sounds->setToolTip(QCoreApplication::translate("MainWindow", "<html><head/><body><b>Don't extract sounds.</b><br><br>If checked, sound effects will not be extracted. This doesn' include music tracks. Sounds from previous installations will not be deleted.<br><br>Installing sounds can take a few minutes, and it's OK to skip this step if they are already installed. You can also check this if you are getting errors installing sounds or if FFmpeg is not installed in your system.</body></html>", nullptr));
+#endif // QT_CONFIG(tooltip)
+        chk_no_sounds->setText(QCoreApplication::translate("MainWindow", "Don't extract sounds", nullptr));
+#if QT_CONFIG(tooltip)
+        chk_no_music->setToolTip(QCoreApplication::translate("MainWindow", "<html><head/><body><b>Don't extract audio tracks.</b><br><br>If checked, audio tracks will not be extracted. This doesn' include sound effects. Tracks from previous installations will not be deleted.<br><br>Installing music can take a few minutes, and it's OK to skip this step if they are already installed. You can also check this if you are getting errors installing sounds or if FFmpeg or TiMidity are not installed in your system.</body></html>", nullptr));
+#endif // QT_CONFIG(tooltip)
+        chk_no_music->setText(QCoreApplication::translate("MainWindow", "Don't extract music tracks", nullptr));
+#if QT_CONFIG(tooltip)
+        chk_no_fields->setToolTip(QCoreApplication::translate("MainWindow", "<html><head/><body><b>Don't extract field maps.</b><br><br>If checked, map fields will not be extracted. Field maps from previous installations will not be deleted.<br><br>This installation step is not usually very long, so there is no reason to skip it unless you have manually edited the fields. Skipping field map installation will also skip the installation of field 3D models.</body></html>", nullptr));
+#endif // QT_CONFIG(tooltip)
+        chk_no_fields->setText(QCoreApplication::translate("MainWindow", "Don't extract field maps", nullptr));
+#if QT_CONFIG(tooltip)
+        chk_no_field_models->setToolTip(QCoreApplication::translate("MainWindow", "<html><head/><body><b>Don't extract field 3D models.</b><br><br>If checked, field 3D models and textures will not be extracted. Models from previous installations will not be deleted.<br><br>This installation step is not usually very long, so there is no reason to skip it unless you have manually edited the models. Skipping field map installation will also skip the installation of field 3D models.</body></html>", nullptr));
+#endif // QT_CONFIG(tooltip)
+        chk_no_field_models->setText(QCoreApplication::translate("MainWindow", "Dont't extract field 3D models", nullptr));
+#if QT_CONFIG(tooltip)
+        chk_no_wm->setToolTip(QCoreApplication::translate("MainWindow", "<html><head/><body><b>Don't extract world map data.</b><br><br>If checked, world map models and data will not be extracted. Data from previous installations will not be deleted.<br><br>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.</body></html>", nullptr));
+#endif // QT_CONFIG(tooltip)
+        chk_no_wm->setText(QCoreApplication::translate("MainWindow", "Don't extract world map data", nullptr));
+#if QT_CONFIG(tooltip)
+        chk_no_wm_models->setToolTip(QCoreApplication::translate("MainWindow", "<html><head/><body><b>Don't extract world map 3D models.</b><br><br>If checked, world map 3D models and textures will not be extracted. Models from previous installations will not be deleted.<br><br>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.</body></html>", nullptr));
+#endif // QT_CONFIG(tooltip)
+        chk_no_wm_models->setText(QCoreApplication::translate("MainWindow", "Dont't extract world 3D models", nullptr));
+#if QT_CONFIG(tooltip)
+        chk_no_ffmpeg->setToolTip(QCoreApplication::translate("MainWindow", "<html><head/><body><b>Don't use the ffmpeg command.</b><br><br>If checked, sound effects and background music will not be available to V-Gears.<br><br>Sound effects and music indexes will still be built, but you will need to provide your own ogg files.</body></html>", nullptr));
+#endif // QT_CONFIG(tooltip)
+        chk_no_ffmpeg->setText(QCoreApplication::translate("MainWindow", "Prevent FFmpeg calls", nullptr));
+#if QT_CONFIG(tooltip)
+        chk_no_timidity->setToolTip(QCoreApplication::translate("MainWindow", "<html><head/><body><b>Don't use the timidity command.</b><br><br>If checked, background music will not be available to V-Gears.<br><br>Music indexes will still be built, but you will need to provide your own ogg tracks.</body></html>", nullptr));
+#endif // QT_CONFIG(tooltip)
+        chk_no_timidity->setText(QCoreApplication::translate("MainWindow", "Prevent TiMidity calls", nullptr));
+#if QT_CONFIG(tooltip)
+        chk_keep_original->setToolTip(QCoreApplication::translate("MainWindow", "<html><head/><body><b>Don't delete original data after installation.</b><br><br>The installer extracts some original data from the installation disk, such as MIDI sounds, TEX images, and some LGP archives. If checked, this data will not be deleted when the installation is complete.<br><br>This data is never used by V-Gears, and there is usually no need to check this.</body></html>", nullptr));
+#endif // QT_CONFIG(tooltip)
+        chk_keep_original->setText(QCoreApplication::translate("MainWindow", "Preserve original data", nullptr));
+        btn_data_run->setText(QCoreApplication::translate("MainWindow", "Install data", nullptr));
+        label_percent->setText(QString());
+        label_progress->setText(QString());
+        label_5->setText(QCoreApplication::translate("MainWindow", "Installer log:", nullptr));
+        tabWidget->setTabText(tabWidget->indexOf(tab_installer), QCoreApplication::translate("MainWindow", "Data Installer", nullptr));
+        label_2->setText(QCoreApplication::translate("MainWindow", "VGears Path", nullptr));
+        btn_vgears_exe->setText(QCoreApplication::translate("MainWindow", "...", nullptr));
+        label->setText(QCoreApplication::translate("MainWindow", "Config Directory", nullptr));
+        btn_vgears_config->setText(QCoreApplication::translate("MainWindow", "...", nullptr));
+        btn_vgears_launch->setText(QCoreApplication::translate("MainWindow", "Run V-Gears", nullptr));
+        tabWidget->setTabText(tabWidget->indexOf(tab_vgears), QCoreApplication::translate("MainWindow", "V-Gears Config", nullptr));
+        menuFile->setTitle(QCoreApplication::translate("MainWindow", "File", nullptr));
+    } // retranslateUi
+
+};
+
+namespace Ui {
+    class MainWindow: public Ui_MainWindow {};
+} // namespace Ui
+
+QT_END_NAMESPACE
+
+#endif // UI_MAINWINDOW_H

+ 3 - 0
src/main.cpp

@@ -56,6 +56,9 @@
 int main(int argc, char *argv[]){
     try{
         VGears::Application app(argc, argv);
+        // Re-finalize singleton registration after object construction is complete
+        // to avoid UBSAN vptr initialization warnings during base class constructor
+        VGears::Application::FinalizeSingletonRegistration(&app);
         if (!app.initOgre()) return 0;
         Ogre::Root *root(app.getRoot());
         Ogre::RenderWindow *window(app.getRenderWindow());