فهرست منبع

The installer can extract files from some ISO images.

Iñigo Valentin 1 ماه پیش
والد
کامیت
8d03cc41c3

+ 9 - 9
src/installer/BattleDataInstaller.cpp

@@ -33,10 +33,10 @@
 #include "common/FinalFantasy7/FF7NameLookup.h"
 #include "common/FinalFantasy7/FF7NameLookup.h"
 
 
 BattleDataInstaller::BattleDataInstaller(
 BattleDataInstaller::BattleDataInstaller(
-  const std::string input_dir, const std::string output_dir, Ogre::ResourceGroupManager* res_mgr
+  const DiskImage& disk_image, const std::string output_dir, Ogre::ResourceGroupManager* res_mgr
 ):
 ):
-  input_dir_(input_dir), output_dir_(output_dir),
-  scene_bin_(input_dir + "data/battle/scene.bin"), total_scenes_(0), next_scene_(0),
+  disk_image_(disk_image), output_dir_(output_dir),
+  scene_bin_(*const_cast<DiskImage&>(disk_image).fileExists("data/battle/scene.bin")), total_scenes_(0), next_scene_(0),
   res_mgr_(res_mgr)
   res_mgr_(res_mgr)
 {}
 {}
 
 
@@ -78,10 +78,10 @@ unsigned int BattleDataInstaller::InitializeBattleModels(){
     battle_lgp_file_names_.clear();
     battle_lgp_file_names_.clear();
     battle_models_.clear();
     battle_models_.clear();
     // Open battle.lgp
     // Open battle.lgp
-    File battle_lgp_file(input_dir_ + "data/battle/battle.lgp");
+    File battle_lgp_file(*disk_image_.fileExists("data/battle/battle.lgp"));
     // Also, open it as a LGP archive.
     // Also, open it as a LGP archive.
-    VGears::LGPArchive battle_lgp(input_dir_ + "data/battle/battle.lgp", "LGP");
-    battle_lgp.open(input_dir_ + "data/battle/battle.lgp", true);
+    VGears::LGPArchive battle_lgp(*disk_image_.fileExists("data/battle/battle.lgp"), "LGP");
+    battle_lgp.open(*disk_image_.fileExists("data/battle/battle.lgp"), true);
     battle_lgp.load();
     battle_lgp.load();
     VGears::LGPArchive::FileList file_list = battle_lgp.GetFiles();
     VGears::LGPArchive::FileList file_list = battle_lgp.GetFiles();
     for (int i = 0; i < file_list.size(); i ++){
     for (int i = 0; i < file_list.size(); i ++){
@@ -102,10 +102,10 @@ unsigned int BattleDataInstaller::InitializeSpellModels(){
     battle_lgp_file_names_.clear();
     battle_lgp_file_names_.clear();
     battle_models_.clear();
     battle_models_.clear();
     // Open battle.lgp
     // Open battle.lgp
-    File magic_lgp_file(input_dir_ + "data/battle/magic.lgp");
+    File magic_lgp_file(*disk_image_.fileExists("data/battle/magic.lgp"));
     // Also, open it as a LGP archive.
     // Also, open it as a LGP archive.
-    VGears::LGPArchive magic_lgp(input_dir_ + "data/battle/magic.lgp", "LGP");
-    magic_lgp.open(input_dir_ + "data/battle/magic.lgp", true);
+    VGears::LGPArchive magic_lgp(*disk_image_.fileExists("data/battle/magic.lgp"), "LGP");
+    magic_lgp.open(*disk_image_.fileExists("data/battle/magic.lgp"), true);
     magic_lgp.load();
     magic_lgp.load();
     VGears::LGPArchive::FileList file_list = magic_lgp.GetFiles();
     VGears::LGPArchive::FileList file_list = magic_lgp.GetFiles();
     for (int i = 0; i < file_list.size(); i ++){
     for (int i = 0; i < file_list.size(); i ++){

+ 5 - 5
src/installer/BattleDataInstaller.h

@@ -18,6 +18,7 @@
 
 
 #pragma once
 #pragma once
 
 
+#include "DiskImage.h"
 #include "common/BinGZipFile.h"
 #include "common/BinGZipFile.h"
 #include "common/TypeDefine.h"
 #include "common/TypeDefine.h"
 #include "data/BattleSceneFile.h"
 #include "data/BattleSceneFile.h"
@@ -34,13 +35,12 @@ class BattleDataInstaller{
         /**
         /**
          * Constructor
          * Constructor
          *
          *
-         * @param[in] input_dir Path to the directory containing the original data to parse.
+         * @param[in] disk_image Disk image to extract the data from.
          * @param[in] output_dir Path to the directory of the installation data.
          * @param[in] output_dir Path to the directory of the installation data.
          * @param[in] res_mgr The application resource manager..
          * @param[in] res_mgr The application resource manager..
          */
          */
         BattleDataInstaller(
         BattleDataInstaller(
-          const std::string input_dir, const std::string output_dir,
-          Ogre::ResourceGroupManager* res_mgr
+          const DiskImage& disk_image, const std::string output_dir, Ogre::ResourceGroupManager* res_mgr
         );
         );
 
 
         /**
         /**
@@ -417,9 +417,9 @@ class BattleDataInstaller{
         );
         );
 
 
         /**
         /**
-         * The path to the directory from which to read the PC game data.
+         * The disk image to extract the data from.
          */
          */
-        std::string input_dir_;
+        DiskImage disk_image_;
 
 
         /**
         /**
          * The path to the directory where to save the V-Gears data.
          * The path to the directory where to save the V-Gears data.

+ 1 - 1
src/installer/CMakeLists.txt

@@ -9,11 +9,11 @@ set(INSTALLER_SOURCE_FILES
     main.cpp
     main.cpp
     BattleDataInstaller.cpp
     BattleDataInstaller.cpp
     DataInstaller.cpp
     DataInstaller.cpp
+    DiskImage.cpp
     FieldDataInstaller.cpp
     FieldDataInstaller.cpp
     FieldTextWriter.cpp
     FieldTextWriter.cpp
     KernelDataInstaller.cpp
     KernelDataInstaller.cpp
     MainWindow.cpp
     MainWindow.cpp
-    Release.cpp
     MediaDataInstaller.cpp
     MediaDataInstaller.cpp
     ModelsAndAnimationsDb.cpp
     ModelsAndAnimationsDb.cpp
     ScopedLgp.cpp
     ScopedLgp.cpp

+ 17 - 12
src/installer/DataInstaller.cpp

@@ -16,11 +16,12 @@
 #include <QtCore/QDir>
 #include <QtCore/QDir>
 #include <boost/filesystem.hpp>
 #include <boost/filesystem.hpp>
 #include "DataInstaller.h"
 #include "DataInstaller.h"
+#include "DiskImage.h"
 
 
 float DataInstaller::LINE_SCALE_FACTOR = 0.0078124970964f;
 float DataInstaller::LINE_SCALE_FACTOR = 0.0078124970964f;
 
 
 DataInstaller::DataInstaller(
 DataInstaller::DataInstaller(
-  const std::string input_dir, const std::string output_dir, AdvancedOptions options,
+  const DiskImage& disk_image, const std::string output_dir, AdvancedOptions options,
   std::function<void(std::string, int, bool)> write_output_line
   std::function<void(std::string, int, bool)> write_output_line
 )
 )
 #ifdef _DEBUG
 #ifdef _DEBUG
@@ -28,7 +29,7 @@ DataInstaller::DataInstaller(
 #else
 #else
   : application_("plugins.cfg", "resources.cfg", "install.log"),
   : application_("plugins.cfg", "resources.cfg", "install.log"),
 #endif
 #endif
-  input_dir_(input_dir), output_dir_(output_dir),
+  disk_image_(disk_image), output_dir_(output_dir),
   options_(options), write_output_line_(write_output_line)
   options_(options), write_output_line_(write_output_line)
 {
 {
     if (!application_.initOgre(true)) throw std::runtime_error("Ogre init failure");
     if (!application_.initOgre(true)) throw std::runtime_error("Ogre init failure");
@@ -63,22 +64,22 @@ float DataInstaller::Progress(){
             CreateDirectories();
             CreateDirectories();
             installation_state_ = INITIALIZE;
             installation_state_ = INITIALIZE;
             return CalcProgress();
             return CalcProgress();
-        case INITIALIZE:
+        case INITIALIZE:{
             write_output_line_("Initializing installers...", 2, true);
             write_output_line_("Initializing installers...", 2, true);
-            kernel_installer_ = std::make_unique<KernelDataInstaller>(input_dir_);
+            const auto input_dir = disk_image_.getContentPath();
+            if (input_dir.empty()) throw std::runtime_error("Input data directory is empty");
+            kernel_installer_ = std::make_unique<KernelDataInstaller>(disk_image_);
             media_installer_ = std::make_unique<MediaDataInstaller>(
             media_installer_ = std::make_unique<MediaDataInstaller>(
-              input_dir_, output_dir_, options_.keep_originals,
-              options_.no_ffmpeg, options_.no_timidity
-            );
-            field_installer_ = std::make_unique<FieldDataInstaller>(input_dir_, output_dir_);
-            battle_installer_ = std::make_unique<BattleDataInstaller>(
-              input_dir_, output_dir_, application_.ResMgr()
+              disk_image_, output_dir_, options_.keep_originals, options_.no_ffmpeg, options_.no_timidity
             );
             );
+            field_installer_ = std::make_unique<FieldDataInstaller>(disk_image_, output_dir_);
+            battle_installer_ = std::make_unique<BattleDataInstaller>(disk_image_, output_dir_, application_.ResMgr());
             world_installer_ = std::make_unique<WorldInstaller>(
             world_installer_ = std::make_unique<WorldInstaller>(
-              input_dir_, output_dir_, options_.keep_originals, application_.ResMgr()
+              disk_image_, output_dir_, options_.keep_originals, application_.ResMgr()
             );
             );
             installation_state_ = BATTLE_SCENES_INIT;
             installation_state_ = BATTLE_SCENES_INIT;
             return CalcProgress();
             return CalcProgress();
+        }
         case BATTLE_SCENES_INIT:
         case BATTLE_SCENES_INIT:
             // Skip kernel data if option is set.
             // Skip kernel data if option is set.
             if (options_.skip_battle_data){
             if (options_.skip_battle_data){
@@ -515,8 +516,12 @@ void DataInstaller::CreateDirectories(){
     application_.ResMgr()->addResourceLocation(
     application_.ResMgr()->addResourceLocation(
       output_dir_ + "models/world/", "FileSystem", "FFVIITextures", true, true
       output_dir_ + "models/world/", "FileSystem", "FFVIITextures", true, true
     );
     );
+    const auto fields_lgp_path = disk_image_.fileExists("data/field/flevel.lgp");
+    if (!fields_lgp_path){
+            throw std::runtime_error("Missing required file: data/field/flevel.lgp");
+    }
     fields_lgp_ = std::make_unique<ScopedLgp>(
     fields_lgp_ = std::make_unique<ScopedLgp>(
-      application_.getRoot(), input_dir_ + "data/field/flevel.lgp", "LGP", "FFVIIFields"
+            application_.getRoot(), *fields_lgp_path, "LGP", "FFVIIFields"
     );
     );
 }
 }
 
 

+ 5 - 4
src/installer/DataInstaller.h

@@ -19,6 +19,7 @@
 #include <vector>
 #include <vector>
 #include <iostream>
 #include <iostream>
 #include "common/VGearsApplication.h"
 #include "common/VGearsApplication.h"
+#include "DiskImage.h"
 #include "FieldDataInstaller.h"
 #include "FieldDataInstaller.h"
 #include "KernelDataInstaller.h"
 #include "KernelDataInstaller.h"
 #include "MediaDataInstaller.h"
 #include "MediaDataInstaller.h"
@@ -115,13 +116,13 @@ class DataInstaller{
         /**
         /**
          * Installer constructor
          * Installer constructor
          *
          *
-         * @param[in] input_dir Path to the directory containing the original data to parse.
+         * @param[in] disk_image Disk image containing the original data to parse.
          * @param[in] output_dir Path to the directory to write generated data to.
          * @param[in] output_dir Path to the directory to write generated data to.
          * @param[in] options Adavanced options for the installer.
          * @param[in] options Adavanced options for the installer.
          * @param[in] write_output_line Pointer to function to write output.
          * @param[in] write_output_line Pointer to function to write output.
          */
          */
         DataInstaller(
         DataInstaller(
-          const std::string input_dir, const std::string output_dir, AdvancedOptions options,
+          const DiskImage& disk_image, const std::string output_dir, AdvancedOptions options,
           std::function<void(std::string, int, bool)> write_output_line
           std::function<void(std::string, int, bool)> write_output_line
         );
         );
 
 
@@ -470,9 +471,9 @@ class DataInstaller{
         std::vector<std::string> field_model_names_;
         std::vector<std::string> field_model_names_;
 
 
         /**
         /**
-         * The path to the directory from which to read the PC game data.
+         * The disk image from which to read the PC game data.
          */
          */
-        std::string input_dir_;
+        DiskImage disk_image_;
 
 
         /**
         /**
          * The path to the directory where to save the V-Gears data.
          * The path to the directory where to save the V-Gears data.

+ 411 - 84
src/installer/Release.cpp → src/installer/DiskImage.cpp

@@ -19,45 +19,48 @@
 #include <regex>
 #include <regex>
 #include <archive.h>
 #include <archive.h>
 #include <archive_entry.h>
 #include <archive_entry.h>
-#include "Release.h"
+#include "DiskImage.h"
 #include <QtCore/qstring.h>
 #include <QtCore/qstring.h>
 #include <QtCore/qdir.h>
 #include <QtCore/qdir.h>
+#include <QtCore/qdiriterator.h>
 #include <QtCore/qfileinfo.h>
 #include <QtCore/qfileinfo.h>
 #include <QtCore/qglobal.h>
 #include <QtCore/qglobal.h>
 #include <algorithm>
 #include <algorithm>
 #include <cctype>
 #include <cctype>
+#include <cstdint>
 #include <cstdio>
 #include <cstdio>
 #include <fstream>
 #include <fstream>
 #include <regex>
 #include <regex>
+#include <functional>
 #include <stdexcept>
 #include <stdexcept>
 #include <sys/stat.h>
 #include <sys/stat.h>
 #include <vector>
 #include <vector>
 
 
-Release::Release(){
-    id = "Unknown ISO file";
+DiskImage::DiskImage(){
+    id = "Unknown image file";
     platform = PLATFORM_UNKNOWN;
     platform = PLATFORM_UNKNOWN;
     region = REGION_UNKNOWN;
     region = REGION_UNKNOWN;
     language = LANGUAGE_UNKNOWN;
     language = LANGUAGE_UNKNOWN;
     disk = DISK_UNKNOWN;
     disk = DISK_UNKNOWN;
     valid = false;
     valid = false;
     supported = false;
     supported = false;
-    error_message = "The selected file is not a valid Final Fantasy VII ISO file.";
+    error_message = "The selected file is not a valid Final Fantasy VII image file.";
     warning_message = "";
     warning_message = "";
+    is_extracted = false;
 };
 };
 
 
-Release::Release(std::string iso_path) : Release() {
+DiskImage::DiskImage(std::string path) : DiskImage() {
     struct archive* a = archive_read_new();
     struct archive* a = archive_read_new();
     struct archive_entry* entry;
     struct archive_entry* entry;
     archive_read_support_format_iso9660(a);
     archive_read_support_format_iso9660(a);
     archive_read_support_format_all(a);
     archive_read_support_format_all(a);
     archive_read_support_filter_all(a);
     archive_read_support_filter_all(a);
 
 
-    if (archive_read_open_filename(a, iso_path.c_str(), 2048) != ARCHIVE_OK) {
+    if (archive_read_open_filename(a, path.c_str(), 2048) != ARCHIVE_OK) {
         const char* archive_error = archive_error_string(a);
         const char* archive_error = archive_error_string(a);
         const std::string open_error = archive_error ? archive_error : "Unknown archive error";
         const std::string open_error = archive_error ? archive_error : "Unknown archive error";
-        // Error reading iso
-        std::cerr << "Failed to read ISO file: " << open_error << std::endl;
-        error_message = "Failed to read ISO file: " + open_error;
+        // Error reading image
+        error_message = "Failed to read image file: " + open_error;
         archive_read_free(a);
         archive_read_free(a);
         return;
         return;
     }
     }
@@ -69,7 +72,7 @@ Release::Release(std::string iso_path) : Release() {
     };
     };
 
 
     // Normalize a path from the ISO9660 filesystem, removing version suffixes and trailing dots.
     // Normalize a path from the ISO9660 filesystem, removing version suffixes and trailing dots.
-    auto normalize_iso_path = [&](std::string value) {
+    auto normalize_path = [&](std::string value) {
         std::replace(value.begin(), value.end(), '\\', '/');
         std::replace(value.begin(), value.end(), '\\', '/');
         while (
         while (
           !value.empty()
           !value.empty()
@@ -112,7 +115,7 @@ Release::Release(std::string iso_path) : Release() {
           normalized == "FF7CONFIG.EXE" || normalized == "FF7CONFI.EXE"
           normalized == "FF7CONFIG.EXE" || normalized == "FF7CONFI.EXE"
           || normalized == "FF7CON~1.EXE" || normalized == "FF7INST.EXE"
           || normalized == "FF7CON~1.EXE" || normalized == "FF7INST.EXE"
         ) {
         ) {
-            this->iso_path = iso_path;
+            this->path = path;
             id = "Final Fantasy VII (PC, 1998, USA) Install Disk";
             id = "Final Fantasy VII (PC, 1998, USA) Install Disk";
             platform = PLATFORM_PC;
             platform = PLATFORM_PC;
             region = REGION_NORTH_AMERICA;
             region = REGION_NORTH_AMERICA;
@@ -125,7 +128,7 @@ Release::Release(std::string iso_path) : Release() {
             return true;
             return true;
         }
         }
         if (normalized == "FF7/MOVIES/BIKE.AVI") {
         if (normalized == "FF7/MOVIES/BIKE.AVI") {
-            this->iso_path = iso_path;
+            this->path = path;
             id = "Final Fantasy VII (PC, 1998, USA) Disk 1";
             id = "Final Fantasy VII (PC, 1998, USA) Disk 1";
             platform = PLATFORM_PC;
             platform = PLATFORM_PC;
             region = REGION_NORTH_AMERICA;
             region = REGION_NORTH_AMERICA;
@@ -138,7 +141,7 @@ Release::Release(std::string iso_path) : Release() {
             return true;
             return true;
         }
         }
         if (normalized == "FF7/MOVIES/BIGLIGHT.AVI") {
         if (normalized == "FF7/MOVIES/BIGLIGHT.AVI") {
-            this->iso_path = iso_path;
+            this->path = path;
             id = "Final Fantasy VII (PC, 1998, USA) Disk 2";
             id = "Final Fantasy VII (PC, 1998, USA) Disk 2";
             platform = PLATFORM_PC;
             platform = PLATFORM_PC;
             region = REGION_NORTH_AMERICA;
             region = REGION_NORTH_AMERICA;
@@ -151,7 +154,7 @@ Release::Release(std::string iso_path) : Release() {
             return true;
             return true;
         }
         }
         if (normalized == "FF7/MOVIES/ENDING1.AVI") {
         if (normalized == "FF7/MOVIES/ENDING1.AVI") {
-            this->iso_path = iso_path;
+            this->path = path;
             id = "Final Fantasy VII (PC, 1998, USA) Disk 3";
             id = "Final Fantasy VII (PC, 1998, USA) Disk 3";
             platform = PLATFORM_PC;
             platform = PLATFORM_PC;
             region = REGION_NORTH_AMERICA;
             region = REGION_NORTH_AMERICA;
@@ -170,10 +173,8 @@ Release::Release(std::string iso_path) : Release() {
     int r = ARCHIVE_OK;
     int r = ARCHIVE_OK;
     bool saw_header = false;
     bool saw_header = false;
     while (true) {
     while (true) {
-        std::cout << "Reading next header from ISO..." << std::endl;
         r = archive_read_next_header(a, &entry);
         r = archive_read_next_header(a, &entry);
         if (r == ARCHIVE_EOF) {
         if (r == ARCHIVE_EOF) {
-            std::cout << "Reached end of ISO archive." << std::endl;
             break;
             break;
         }
         }
         if (r != ARCHIVE_OK && r != ARCHIVE_WARN) {
         if (r != ARCHIVE_OK && r != ARCHIVE_WARN) {
@@ -187,36 +188,31 @@ Release::Release(std::string iso_path) : Release() {
         saw_header = true;
         saw_header = true;
 
 
         std::string path = archive_entry_pathname(entry);
         std::string path = archive_entry_pathname(entry);
-        std::cout << "Found file in ISO: " << path << std::endl;
 
 
         // Look for know files in the PC release disks.
         // Look for know files in the PC release disks.
-        const std::string normalized = normalize_iso_path(path);
+        const std::string normalized = normalize_path(path);
         if (apply_pc_release_from_path(normalized)) {
         if (apply_pc_release_from_path(normalized)) {
             pc_marker_found = true;
             pc_marker_found = true;
             break;
             break;
         }
         }
-        std::cout << "Finished checking PC releases." << std::endl;
 
 
-        // If we haven't found the PC install marker, look for SYSTEM.CNF
+        // If the PC install marker hasn't been found, look for SYSTEM.CNF
         // to determine if it's a PSX release and extract the game ID.
         // to determine if it's a PSX release and extract the game ID.
         std::string upper_path = to_upper(path);
         std::string upper_path = to_upper(path);
         if (
         if (
           upper_path == "SYSTEM.CNF" || upper_path == "/SYSTEM.CNF"
           upper_path == "SYSTEM.CNF" || upper_path == "/SYSTEM.CNF"
           || upper_path.find("SYSTEM.CNF;") != std::string::npos
           || upper_path.find("SYSTEM.CNF;") != std::string::npos
         ) {
         ) {
-            std::cout << "SYSTEM.CNF FOUND." << std::endl;
             size_t size = archive_entry_size(entry);
             size_t size = archive_entry_size(entry);
             if (size > 0) {
             if (size > 0) {
                 std::vector<char> buffer(size);
                 std::vector<char> buffer(size);
                 la_ssize_t bytes_read = archive_read_data(a, buffer.data(), size);
                 la_ssize_t bytes_read = archive_read_data(a, buffer.data(), size);
                 if (bytes_read > 0) {
                 if (bytes_read > 0) {
                     std::string cnf_content(buffer.begin(), buffer.begin() + bytes_read);
                     std::string cnf_content(buffer.begin(), buffer.begin() + bytes_read);
-                    std::cout << "SYSTEM.CNF content:\n" << cnf_content << std::endl;
                     std::regex code_regex(R"(([A-Z]{4})_(\d{3})\.(\d{2}))");
                     std::regex code_regex(R"(([A-Z]{4})_(\d{3})\.(\d{2}))");
                     std::smatch match;
                     std::smatch match;
                     if (std::regex_search(cnf_content, match, code_regex)) {
                     if (std::regex_search(cnf_content, match, code_regex)) {
                         game_id = match[1].str() + "-" + match[2].str() + match[3].str();
                         game_id = match[1].str() + "-" + match[2].str() + match[3].str();
-                        std::cout << "Detected game ID from SYSTEM.CNF: " << game_id << std::endl;
                         if (game_id == "SCUS-94163") {
                         if (game_id == "SCUS-94163") {
                             id = "Final Fantasy VII (USA) Disc 1";
                             id = "Final Fantasy VII (USA) Disc 1";
                             platform = PLATFORM_PS1;
                             platform = PLATFORM_PS1;
@@ -446,7 +442,7 @@ Release::Release(std::string iso_path) : Release() {
                             "Use the PC release instead.";
                             "Use the PC release instead.";
                         }
                         }
                         else {
                         else {
-                            id = "Unknown Release";
+                            id = "Unknown DiskImage";
                             platform = PLATFORM_UNKNOWN;
                             platform = PLATFORM_UNKNOWN;
                             region = REGION_UNKNOWN;
                             region = REGION_UNKNOWN;
                             language = LANGUAGE_UNKNOWN;
                             language = LANGUAGE_UNKNOWN;
@@ -463,65 +459,105 @@ Release::Release(std::string iso_path) : Release() {
     }
     }
 
 
     if (!saw_header && r == ARCHIVE_EOF) {
     if (!saw_header && r == ARCHIVE_EOF) {
-        std::cout << "No entries found by libarchive, trying isoinfo listing..." << std::endl;
-
-        auto escape_shell = [](const std::string& value) {
-            std::string out;
-            for (char c : value) {
-                if (c == '"' || c == '\\') {
-                    out.push_back('\\');
-                }
-                out.push_back(c);
-            }
-            return out;
-        };
-
-        const std::string escaped_iso_path = escape_shell(iso_path);
-        const std::vector<std::string> list_cmds = {
-            "isoinfo -J -f -i \"" + escaped_iso_path + "\" 2>/dev/null",
-            "isoinfo -R -J -f -i \"" + escaped_iso_path + "\" 2>/dev/null",
-            "isoinfo -R -f -i \"" + escaped_iso_path + "\" 2>/dev/null",
-            "isoinfo -f -i \"" + escaped_iso_path + "\" 2>/dev/null"
+        //std::cout << "libarchive returned no entries, falling back to direct ISO9660 parsing..." << std::endl;
+        auto read_le32 = [](const uint8_t* bytes) -> uint32_t {
+            return static_cast<uint32_t>(bytes[0])
+                | (static_cast<uint32_t>(bytes[1]) << 8)
+                | (static_cast<uint32_t>(bytes[2]) << 16)
+                | (static_cast<uint32_t>(bytes[3]) << 24);
         };
         };
 
 
-        char buffer[4096];
-        for (const auto& list_cmd : list_cmds) {
-            FILE* list_pipe = popen(list_cmd.c_str(), "r");
-            if (!list_pipe) {
-                continue;
-            }
+        std::ifstream file(path, std::ios::binary);
+        if (file) {
+            const std::size_t sector_size = 2048;
+
+            auto read_bytes = [&](std::streamoff offset, std::size_t size) {
+                std::vector<uint8_t> bytes(size);
+                file.seekg(offset, std::ios::beg);
+                file.read(reinterpret_cast<char*>(bytes.data()), static_cast<std::streamsize>(size));
+                const std::size_t bytes_read = static_cast<std::size_t>(file.gcount());
+                bytes.resize(bytes_read);
+                return bytes;
+            };
+
+            const std::vector<uint8_t> pvd = read_bytes(static_cast<std::streamoff>(16) * sector_size, sector_size);
+            const bool pvd_valid =
+                pvd.size() == sector_size
+                && pvd[0] == 1
+                && pvd[1] == 'C' && pvd[2] == 'D'
+                && pvd[3] == '0' && pvd[4] == '0' && pvd[5] == '1';
+
+            if (pvd_valid) {
+                const uint32_t root_lba = read_le32(&pvd[156 + 2]);
+                const uint32_t root_size = read_le32(&pvd[156 + 10]);
+
+                std::function<void(uint32_t, uint32_t, const std::string&)> walk_directory;
+                walk_directory = [&](uint32_t extent_lba, uint32_t extent_size, const std::string& prefix) {
+                    if (pc_marker_found || extent_size == 0) {
+                        return;
+                    }
 
 
-            while (fgets(buffer, sizeof(buffer), list_pipe)) {
-                std::string line(buffer);
-                while (!line.empty() && (line.back() == '\n' || line.back() == '\r')) {
-                    line.pop_back();
-                }
-                if (line.empty()) {
-                    continue;
-                }
-                if (apply_pc_release_from_path(normalize_iso_path(line))) {
-                    pc_marker_found = true;
-                    break;
-                }
-            }
+                    const std::vector<uint8_t> dir_data = read_bytes(
+                        static_cast<std::streamoff>(extent_lba) * sector_size,
+                        extent_size
+                    );
+
+                    std::size_t offset = 0;
+                    while (offset < dir_data.size()) {
+                        const uint8_t record_size = dir_data[offset];
+                        if (record_size == 0) {
+                            offset = ((offset / sector_size) + 1) * sector_size;
+                            continue;
+                        }
+                        if (offset + record_size > dir_data.size() || offset + 33 > dir_data.size()) {
+                            break;
+                        }
 
 
-            pclose(list_pipe);
-            if (pc_marker_found) {
-                break;
+                        const uint32_t entry_lba = read_le32(&dir_data[offset + 2]);
+                        const uint32_t entry_size = read_le32(&dir_data[offset + 10]);
+                        const uint8_t entry_flags = dir_data[offset + 25];
+                        const uint8_t entry_name_len = dir_data[offset + 32];
+
+                        if (entry_name_len > 0 && offset + 33 + entry_name_len <= dir_data.size()) {
+                            const bool is_dot_entry =
+                                entry_name_len == 1
+                                && (dir_data[offset + 33] == 0x00 || dir_data[offset + 33] == 0x01);
+
+                            if (!is_dot_entry) {
+                                const std::string raw_name(
+                                    reinterpret_cast<const char*>(&dir_data[offset + 33]),
+                                    reinterpret_cast<const char*>(&dir_data[offset + 33 + entry_name_len])
+                                );
+                                const std::string normalized_name = normalize_path(raw_name);
+                                const std::string full_path
+                                  = prefix.empty() ? normalized_name : prefix + "/" + normalized_name;
+                                if (apply_pc_release_from_path(full_path)) {
+                                    pc_marker_found = true;
+                                    return;
+                                }
+                                if ((entry_flags & 0x02) != 0) {
+                                    walk_directory(entry_lba, entry_size, full_path);
+                                    if (pc_marker_found) {
+                                        return;
+                                    }
+                                }
+                            }
+                        }
+                        offset += record_size;
+                    }
+                };
+                walk_directory(root_lba, root_size, "");
             }
             }
         }
         }
     }
     }
-
-    std::cout << "Finished checking releases." << std::endl;
-
     archive_read_close(a);
     archive_read_close(a);
     archive_read_free(a);
     archive_read_free(a);
     return;
     return;
 };
 };
 
 
-Release::~Release(){};
+DiskImage::~DiskImage(){};
 
 
-bool Release::extractIso(std::string installation_path){
+bool DiskImage::extractImage(std::string installation_path){
     struct archive* a;
     struct archive* a;
     struct archive* ext;
     struct archive* ext;
     struct archive_entry* entry;
     struct archive_entry* entry;
@@ -562,14 +598,16 @@ bool Release::extractIso(std::string installation_path){
         }
         }
     }
     }
 
 
-    // Open the ISO file
-    if ((r = archive_read_open_filename(a, iso_path.c_str(), 10240))) {
-        std::cerr << "Error: Could not open ISO file: " << archive_error_string(a) << std::endl;
+    // Open the image file
+    if ((r = archive_read_open_filename(a, path.c_str(), 10240))) {
+        std::cerr << "Error: Could not open image file: " << archive_error_string(a) << std::endl;
         return false;
         return false;
     }
     }
 
 
     // Read through the archive entries
     // Read through the archive entries
+    bool saw_header = false;
     while ((r = archive_read_next_header(a, &entry)) == ARCHIVE_OK || r == ARCHIVE_WARN) {
     while ((r = archive_read_next_header(a, &entry)) == ARCHIVE_OK || r == ARCHIVE_WARN) {
+        saw_header = true;
         if (r == ARCHIVE_WARN) {
         if (r == ARCHIVE_WARN) {
             std::cerr << "archive_read_next_header warning during extraction: " << archive_error_string(a) << std::endl;
             std::cerr << "archive_read_next_header warning during extraction: " << archive_error_string(a) << std::endl;
         }
         }
@@ -594,7 +632,7 @@ bool Release::extractIso(std::string installation_path){
             std::cerr << "Warning (Header): " << archive_error_string(ext) << std::endl;
             std::cerr << "Warning (Header): " << archive_error_string(ext) << std::endl;
         }
         }
         else if (archive_entry_size(entry) > 0) {
         else if (archive_entry_size(entry) > 0) {
-            // Copy data from the ISO to the disk
+            // Copy data from the image to the disk
             const void* buff;
             const void* buff;
             size_t size;
             size_t size;
             la_int64_t offset;
             la_int64_t offset;
@@ -615,6 +653,177 @@ bool Release::extractIso(std::string installation_path){
         }
         }
         archive_write_finish_entry(ext);
         archive_write_finish_entry(ext);
     }
     }
+
+    if (!saw_header && r == ARCHIVE_EOF) {
+        archive_read_close(a);
+        archive_read_free(a);
+        archive_write_close(ext);
+        archive_write_free(ext);
+
+        auto read_le32 = [](const uint8_t* bytes) -> uint32_t {
+            return static_cast<uint32_t>(bytes[0])
+                | (static_cast<uint32_t>(bytes[1]) << 8)
+                | (static_cast<uint32_t>(bytes[2]) << 16)
+                | (static_cast<uint32_t>(bytes[3]) << 24);
+        };
+
+        auto normalize_image_name = [](std::string value) {
+            std::replace(value.begin(), value.end(), '\\', '/');
+            std::size_t semi = value.find(';');
+            if (semi != std::string::npos) {
+                bool version_suffix = true;
+                for (std::size_t i = semi + 1; i < value.size(); ++i) {
+                    if (!std::isdigit(static_cast<unsigned char>(value[i]))) {
+                        version_suffix = false;
+                        break;
+                    }
+                }
+                if (version_suffix) {
+                    value = value.substr(0, semi);
+                }
+            }
+            while (!value.empty() && value.back() == '.') {
+                value.pop_back();
+            }
+            return value;
+        };
+
+        std::ifstream image_file(path, std::ios::binary);
+        if (!image_file) {
+            error_message = "Failed to open image file for direct ISO9660 extraction.";
+            return false;
+        }
+
+        const std::size_t sector_size = 2048;
+        bool extraction_ok = true;
+
+        auto read_bytes = [&](std::streamoff offset, std::size_t size) {
+            std::vector<uint8_t> bytes(size);
+            image_file.seekg(offset, std::ios::beg);
+            image_file.read(reinterpret_cast<char*>(bytes.data()), static_cast<std::streamsize>(size));
+            const std::size_t bytes_read = static_cast<std::size_t>(image_file.gcount());
+            bytes.resize(bytes_read);
+            return bytes;
+        };
+
+        const std::vector<uint8_t> pvd = read_bytes(static_cast<std::streamoff>(16) * sector_size, sector_size);
+        const bool pvd_valid =
+            pvd.size() == sector_size
+            && pvd[0] == 1
+            && pvd[1] == 'C' && pvd[2] == 'D'
+            && pvd[3] == '0' && pvd[4] == '0' && pvd[5] == '1';
+
+        if (!pvd_valid) {
+            error_message = "Direct ISO9660 fallback failed: invalid primary volume descriptor.";
+            return false;
+        }
+
+        const uint32_t root_lba = read_le32(&pvd[156 + 2]);
+        const uint32_t root_size = read_le32(&pvd[156 + 10]);
+
+        std::function<void(uint32_t, uint32_t, const std::string&)> extract_directory;
+        extract_directory = [&](uint32_t extent_lba, uint32_t extent_size, const std::string& prefix) {
+            if (!extraction_ok || extent_size == 0) {
+                return;
+            }
+
+            const std::vector<uint8_t> dir_data = read_bytes(
+                static_cast<std::streamoff>(extent_lba) * sector_size,
+                extent_size
+            );
+            if (dir_data.empty() && extent_size > 0) {
+                extraction_ok = false;
+                return;
+            }
+
+            std::size_t offset = 0;
+            while (offset < dir_data.size()) {
+                const uint8_t record_size = dir_data[offset];
+                if (record_size == 0) {
+                    offset = ((offset / sector_size) + 1) * sector_size;
+                    continue;
+                }
+                if (offset + record_size > dir_data.size() || offset + 33 > dir_data.size()) {
+                    break;
+                }
+
+                const uint32_t entry_lba = read_le32(&dir_data[offset + 2]);
+                const uint32_t entry_size = read_le32(&dir_data[offset + 10]);
+                const uint8_t entry_flags = dir_data[offset + 25];
+                const uint8_t entry_name_len = dir_data[offset + 32];
+
+                if (entry_name_len > 0 && offset + 33 + entry_name_len <= dir_data.size()) {
+                    const bool is_dot_entry =
+                        entry_name_len == 1
+                        && (dir_data[offset + 33] == 0x00 || dir_data[offset + 33] == 0x01);
+                    if (!is_dot_entry) {
+                        std::string raw_name(
+                            reinterpret_cast<const char*>(&dir_data[offset + 33]),
+                            reinterpret_cast<const char*>(&dir_data[offset + 33 + entry_name_len])
+                        );
+                        raw_name = normalize_image_name(raw_name);
+                        const std::string relative_path = prefix.empty() ? raw_name : prefix + "/" + raw_name;
+                        const std::string absolute_path = data_dir + "/" + relative_path;
+
+                        if ((entry_flags & 0x02) != 0) {
+                            QDir mkdir_dir;
+                            if (!mkdir_dir.mkpath(QString::fromStdString(absolute_path))) {
+                                extraction_ok = false;
+                                return;
+                            }
+                            extract_directory(entry_lba, entry_size, relative_path);
+                            if (!extraction_ok) {
+                                return;
+                            }
+                        }
+                        else {
+                            QString abs_qpath = QString::fromStdString(absolute_path);
+                            QString abs_parent = QFileInfo(abs_qpath).absolutePath();
+                            QDir parent_dir(abs_parent);
+                            if (!parent_dir.exists() && !parent_dir.mkpath(".")) {
+                                extraction_ok = false;
+                                return;
+                            }
+
+                            std::ofstream out_file(absolute_path, std::ios::binary);
+                            if (!out_file) {
+                                extraction_ok = false;
+                                return;
+                            }
+
+                            const std::streamoff file_offset = static_cast<std::streamoff>(entry_lba) * sector_size;
+                            image_file.seekg(file_offset, std::ios::beg);
+                            std::vector<char> chunk(64 * 1024);
+                            uint32_t remaining = entry_size;
+                            while (remaining > 0) {
+                                const std::size_t to_read = std::min<std::size_t>(chunk.size(), remaining);
+                                image_file.read(chunk.data(), static_cast<std::streamsize>(to_read));
+                                const std::streamsize got = image_file.gcount();
+                                if (got <= 0) {
+                                    extraction_ok = false;
+                                    return;
+                                }
+                                out_file.write(chunk.data(), got);
+                                remaining -= static_cast<uint32_t>(got);
+                            }
+                        }
+                    }
+                }
+                offset += record_size;
+            }
+        };
+
+        extract_directory(root_lba, root_size, "");
+        if (!extraction_ok) {
+            error_message = "Direct ISO9660 fallback failed while extracting files.";
+            return false;
+        }
+
+        content_path = data_dir;
+        is_extracted = true;
+        return true;
+    }
+
     if (r != ARCHIVE_EOF && r != ARCHIVE_OK && r != ARCHIVE_WARN) {
     if (r != ARCHIVE_EOF && r != ARCHIVE_OK && r != ARCHIVE_WARN) {
         std::cerr << "archive_read_next_header error during extraction: " << archive_error_string(a) << " (code=" << r << ")" << std::endl;
         std::cerr << "archive_read_next_header error during extraction: " << archive_error_string(a) << " (code=" << r << ")" << std::endl;
         // continue cleanup and return failure
         // continue cleanup and return failure
@@ -632,45 +841,163 @@ bool Release::extractIso(std::string installation_path){
     archive_write_free(ext);
     archive_write_free(ext);
 
 
     content_path = data_dir;
     content_path = data_dir;
+    is_extracted = true;
     return true;
     return true;
 }
 }
 
 
-const std::string Release::getId(){
+std::unique_ptr<std::string> DiskImage::fileExists(std::string file_path) {
+    if (!is_extracted) {
+        std::cerr << "Error: Attempted to check for file existence before extracting the disk image." << std::endl;
+        return nullptr;
+    }
+    auto normalize_segment = [](QString segment) {
+        segment = segment.trimmed();
+        while (!segment.isEmpty() && (segment.endsWith('.') || segment.endsWith(' '))) {
+            segment.chop(1);
+        }
+        int semi = segment.indexOf(';');
+        if (semi >= 0) {
+            bool numeric_suffix = true;
+            for (int i = semi + 1; i < segment.size(); ++i) {
+                if (!segment.at(i).isDigit()) {
+                    numeric_suffix = false;
+                    break;
+                }
+            }
+            if (numeric_suffix) {
+                segment = segment.left(semi);
+            }
+        }
+        return segment.toUpper();
+    };
+    auto split_segments = [&](const QString& path) {
+        QString normalized = path;
+        normalized.replace('\\', '/');
+        while (normalized.startsWith('/')) {
+            normalized.remove(0, 1);
+        }
+        normalized = QDir::cleanPath(normalized);
+        QStringList parts = normalized.split('/', Qt::SkipEmptyParts);
+        QStringList out;
+        for (const QString& part : parts) {
+            if (part == ".") {
+                continue;
+            }
+            out.push_back(normalize_segment(part));
+        }
+        return out;
+    };
+    auto split_base_ext = [](const QString& name) {
+        const int dot = name.lastIndexOf('.');
+        if (dot <= 0 || dot == name.size() - 1) {
+            return qMakePair(name, QString());
+        }
+        return qMakePair(name.left(dot), name.mid(dot + 1));
+    };
+    auto segment_matches = [&](const QString& requested, const QString& actual) {
+        if (requested == actual) {
+            return true;
+        }
+
+        const auto req = split_base_ext(requested);
+        const auto act = split_base_ext(actual);
+        if (req.second != act.second) {
+            return false;
+        }
+
+        QString req_base = req.first;
+        QString act_base = act.first;
+
+        const int req_tilde = req_base.indexOf('~');
+        const int act_tilde = act_base.indexOf('~');
+        if (req_tilde >= 0) {
+            req_base = req_base.left(req_tilde);
+        }
+        if (act_tilde >= 0) {
+            act_base = act_base.left(act_tilde);
+        }
+
+        if (req_base.isEmpty() || act_base.isEmpty()) {
+            return false;
+        }
+
+        return req_base.startsWith(act_base) || act_base.startsWith(req_base);
+    };
+    const QString base_dir = QDir::cleanPath(QString::fromStdString(content_path).replace('\\', '/'));
+    const QString requested_path = QString::fromStdString(file_path);
+    const QString direct_candidate = QDir(base_dir).filePath(requested_path);
+    QFileInfo direct_file(direct_candidate);
+    if (direct_file.exists() && direct_file.isFile()) {
+        return std::make_unique<std::string>(direct_file.absoluteFilePath().toStdString());
+    }
+    const QStringList requested_segments = split_segments(requested_path);
+    if (requested_segments.isEmpty()) {
+        return nullptr;
+    }
+    QDirIterator it(base_dir, QDir::Files, QDirIterator::Subdirectories);
+    while (it.hasNext()) {
+        const QString file_full_path = it.next();
+        const QString relative_path = QDir(base_dir).relativeFilePath(file_full_path);
+        const QStringList actual_segments = split_segments(relative_path);
+        if (actual_segments.size() < requested_segments.size()) {
+            continue;
+        }
+
+        bool all_segments_match = true;
+        const int offset = actual_segments.size() - requested_segments.size();
+        for (int i = 0; i < requested_segments.size(); ++i) {
+            if (!segment_matches(requested_segments[i], actual_segments[offset + i])) {
+                all_segments_match = false;
+                break;
+            }
+        }
+        if (all_segments_match) {
+            return std::make_unique<std::string>(QFileInfo(file_full_path).absoluteFilePath().toStdString());
+        }
+    }
+    return nullptr;
+}
+
+const std::string DiskImage::getPath(){
+    return path;
+}
+
+const std::string DiskImage::getId(){
     return id;
     return id;
 };
 };
 
 
-const Release::Platform Release::getPlatform(){
+const DiskImage::Platform DiskImage::getPlatform(){
     return platform;
     return platform;
 };
 };
 
 
-const Release::Region Release::getRegion(){
+const DiskImage::Region DiskImage::getRegion(){
     return region;
     return region;
 };
 };
 
 
-const Release::Language Release::getLanguage(){
+const DiskImage::Language DiskImage::getLanguage(){
     return language;
     return language;
 };
 };
 
 
-const Release::Disk Release::getDisk(){
+const DiskImage::Disk DiskImage::getDisk(){
     return disk;
     return disk;
 };
 };
 
 
-const bool Release::isValid(){
+const bool DiskImage::isValid(){
     return valid;
     return valid;
 };
 };
 
 
-const bool Release::isSupported(){
+const bool DiskImage::isSupported(){
     return supported;
     return supported;
 };
 };
 
 
-const std::string Release::getErrorMessage(){
+const std::string DiskImage::getErrorMessage(){
     return error_message;
     return error_message;
 };
 };
 
 
-const std::string Release::getWarningMessage(){
+const std::string DiskImage::getWarningMessage(){
     return warning_message;
     return warning_message;
 };
 };
 
 
-std::string const Release::getContentPath() {
+std::string const DiskImage::getContentPath() {
     return content_path;
     return content_path;
 }
 }

+ 269 - 0
src/installer/DiskImage.h

@@ -0,0 +1,269 @@
+/*
+ * 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 <memory>
+#include <string>
+
+/**
+ * Information about a Final Fantasy VII disk image.
+ */
+struct DiskImage{
+
+    public:
+
+        /**
+         * The platform for which the disk image is for.
+         */
+        enum Platform{
+
+            /** The disk image is for PC. */
+            PLATFORM_PC,
+            /** The disk image is for PS1. */
+            PLATFORM_PS1,
+            /** The disk image is for an unknown platform. */
+            PLATFORM_UNKNOWN
+        };
+
+        /**
+         * The region for which the disk image is for.
+         */
+        enum Region{
+            /** The disk image is for Japan. */
+            REGION_JAPAN,
+            /** The disk image is for North America. */
+            REGION_NORTH_AMERICA,
+            /** The disk image is for Europe. */
+            REGION_EUROPE,
+            /** The disk image is for an unknown region. */
+            REGION_UNKNOWN
+        };
+
+        /**
+         * The language for which the disk image is in.
+         */
+        enum Language{
+            /** The disk image is in Japanese. */
+            LANGUAGE_JAPANESE,
+            /** The disk image is in English. */
+            LANGUAGE_ENGLISH,
+            /** The disk image is in French. */
+            LANGUAGE_FRENCH,
+            /** The disk image is in Spanish. */
+            LANGUAGE_SPANISH,
+            /** The disk image is in German. */
+            LANGUAGE_GERMAN,
+            /** The disk image is in Italian. */
+            LANGUAGE_ITALIAN,
+            /** The disk image is for an unknown language. */
+            LANGUAGE_UNKNOWN
+        };
+
+        /**
+         * The disk for which the disk image corresponds to.
+         */
+        enum Disk{
+            /** The disk image is for the install disk (PC only). */
+            DISK_INSTALL,
+            /** The disk image is for disk 1. */
+            DISK_1,
+            /** The disk image is for disk 2. */
+            DISK_2,
+            /** The disk image is for disk 3. */
+            DISK_3,
+            /** The disk image is for disk 4. */
+            DISK_4,
+            /** The disk image is for an unknown disk. */
+            DISK_UNKNOWN
+        };
+
+        /**
+         * Constructs a new disk image.
+         */
+        DiskImage();
+
+        /**
+         * Constructs a new disk image from an ISO path.
+         *
+         * @param path The path to the ISO file.
+         */
+        DiskImage(std::string path);
+
+        /**
+         * Destroys the disk image.
+         */
+        ~DiskImage();
+
+        /**
+         * Gets the path to the disk image file.
+         *
+         * @return The path to the disk image file.
+         */
+        std::string const getPath();
+
+        /**
+         * Gets the human readable ID of the disk image.
+         *
+         * @return The ID of the disk image.
+         */
+        std::string const getId();
+
+        /**
+         * Gets the platform for which the disk image is intended.
+         *
+         * @return The platform of the disk image.
+         */
+        Platform const getPlatform();
+
+        /**
+         * Gets the region for which the disk image is intended.
+         *
+         * @return The region of the disk image.
+         */
+        Region const getRegion();
+
+        /**
+         * Gets the language for which the disk image is intended.
+         *
+         * @return The language of the disk image.
+         */
+        Language const getLanguage();
+
+        /**
+         * Gets the disk for which the disk image is intended.
+         *
+         * @return The disk of the disk image.
+         */
+        Disk const getDisk();
+
+        /**
+         * Checks if the disk image is a valid Final Fantasy VII release.
+         *
+         * @return True if the disk image is valid, false otherwise.
+         */
+        bool const isValid();
+
+        /**
+         * Gets the error message for the disk image.
+         *
+         * @return The error message.
+         */
+        std::string const getErrorMessage();
+
+        /**
+         * Checks if the disk image is supported and tested in V-Gears.
+         *
+         * @return True if the disk image is supported, false otherwise.
+         */
+        bool const isSupported();
+
+        /**
+         * Gets a warning in case the disk image is not fully supported.
+         *
+         * @return The warning message.
+         */
+        std::string const getWarningMessage();
+
+        /**
+         * Gets the path where the content of the disk image has been extracted.
+         * 
+         * @return The path to the extracted content, null if the content has
+         * not been extracted yet.
+         */
+        std::string const getContentPath();
+
+        /**
+         * Extracts the disk image file to the specified output path. The files
+         * will be extracted to the "original_data" subdirectory of the output
+         * path.
+         *
+         * @param installation_path The v-gears installation directory.
+         * @return True if the extraction was successful, false otherwise.
+         */
+        bool extractImage(std::string installation_path);
+
+        /**
+         * Finds a file in the extracted content of the disk image.
+         *
+         * @param file_path The relative path to the file within the extracted
+         * content.
+         * @return A pointer to the full path of the matched file, or nullptr
+         * if no matching file exists. If the image has not been extracted yet,
+         * this function will always return nullptr.
+         */
+        std::unique_ptr<std::string> fileExists(std::string file_path);
+
+    private:
+
+        /**
+         * The path to the image file.
+         */
+        std::string path;
+
+        /**
+         * A human readable ID of the disk image.
+         */
+        std::string id;
+
+        /**
+         * The platform for which the disk image is intended.
+         */
+        Platform platform;
+
+        /**
+         * The region for which the disk image is intended.
+         */
+        Region region;
+
+        /**
+         * The language for which the disk image is intended.
+         */
+        Language language;
+
+        /**
+         * The disk for which the disk image is intended.
+         */
+        Disk disk;
+
+        /**
+         * Whether the disk image is valid.
+         */
+        bool valid;
+
+        /**
+         * Whether the disk image is supported.
+         */
+        bool supported;
+
+        /**
+         * The error message for the disk image. Empty if no error.
+         */
+        std::string error_message;
+
+        /**
+         * The warning message for the disk image. Empty if no warning.
+         */
+        std::string warning_message;
+
+        /**
+         * The path where the content has been extracted.
+         */
+        std::string content_path;
+
+        /**
+         * Whether the content has been extracted.
+         */
+        bool is_extracted;
+};

+ 6 - 5
src/installer/FieldDataInstaller.cpp

@@ -16,6 +16,7 @@
 #include <string>
 #include <string>
 #include <boost/filesystem.hpp>
 #include <boost/filesystem.hpp>
 #include <QtCore/QDir>
 #include <QtCore/QDir>
+#include "DiskImage.h"
 #include "FieldDataInstaller.h"
 #include "FieldDataInstaller.h"
 #include "TexFile.h"
 #include "TexFile.h"
 #include "common/File.h"
 #include "common/File.h"
@@ -207,8 +208,8 @@ void FieldDataInstaller::CollectSpawnPoints(
     }
     }
 }
 }
 
 
-FieldDataInstaller::FieldDataInstaller(const std::string input_dir, const std::string output_dir):
-  input_dir_(input_dir), output_dir_(output_dir)
+FieldDataInstaller::FieldDataInstaller(const DiskImage& disk_image, const std::string output_dir):
+  disk_image_(disk_image), output_dir_(output_dir)
 {}
 {}
 
 
 FieldDataInstaller::~FieldDataInstaller(){}
 FieldDataInstaller::~FieldDataInstaller(){}
@@ -303,11 +304,11 @@ std::vector<std::string> FieldDataInstaller::ConvertModelsInit(){
     std::vector<std::string> models;
     std::vector<std::string> models;
 
 
     // Open char_lgp as a lgp archive
     // Open char_lgp as a lgp archive
-    VGears::LGPArchive char_lgp(input_dir_ + "data/field/char.lgp", "LGP");
-    char_lgp.open(input_dir_ + "data/field/char.lgp", true);
+    VGears::LGPArchive char_lgp(*disk_image_.fileExists("data/field/char.lgp"), "LGP");
+    char_lgp.open(*disk_image_.fileExists("data/field/char.lgp"), true);
     char_lgp.load();
     char_lgp.load();
     // Also, open it as a file for reading
     // Also, open it as a file for reading
-    File char_file(input_dir_ + "data/field/char.lgp");
+    File char_file(*disk_image_.fileExists("data/field/char.lgp"));
 
 
     //Ogre::StringVectorPtr file_list = char_lgp.list(true, true);
     //Ogre::StringVectorPtr file_list = char_lgp.list(true, true);
     field_model_file_list_ = char_lgp.list(true, true);
     field_model_file_list_ = char_lgp.list(true, true);

+ 4 - 4
src/installer/FieldDataInstaller.h

@@ -20,6 +20,7 @@
 #include <OgreMesh.h>
 #include <OgreMesh.h>
 #include <tinyxml.h>
 #include <tinyxml.h>
 #include "data/VGearsFLevelFile.h"
 #include "data/VGearsFLevelFile.h"
+#include "DiskImage.h"
 #include "ScopedLgp.h"
 #include "ScopedLgp.h"
 #include "SpawnPointDb.h"
 #include "SpawnPointDb.h"
 #include "ModelsAndAnimationsDb.h"
 #include "ModelsAndAnimationsDb.h"
@@ -324,12 +325,11 @@ class FieldDataInstaller{
         /**
         /**
          * Installer constructor
          * Installer constructor
          *
          *
-         * @param[in] input_dir Path to the directory containing the original
-         * data to parse.
+         * @param[in] disk_image Disk image to extract the data from.
          * @param[in] output_dir Path to the directory to write generated data
          * @param[in] output_dir Path to the directory to write generated data
          * to.
          * to.
          */
          */
-        FieldDataInstaller(const std::string input_dir, const std::string output_dir);
+        FieldDataInstaller(const DiskImage& disk_image, const std::string output_dir);
 
 
         /**
         /**
          * Installer destructor.
          * Installer destructor.
@@ -571,7 +571,7 @@ class FieldDataInstaller{
         /**
         /**
          * The path to the directory from which to read the PC game data.
          * The path to the directory from which to read the PC game data.
          */
          */
-        std::string input_dir_;
+        DiskImage disk_image_;
 
 
         /**
         /**
          * The path to the directory where to save the V-Gears data.
          * The path to the directory where to save the V-Gears data.

+ 3 - 2
src/installer/KernelDataInstaller.cpp

@@ -17,12 +17,13 @@
 #include <tinyxml.h>
 #include <tinyxml.h>
 #include <fstream>
 #include <fstream>
 #include <iostream>
 #include <iostream>
+#include "DiskImage.h"
 #include "KernelDataInstaller.h"
 #include "KernelDataInstaller.h"
 #include "common/FinalFantasy7/FF7NameLookup.h"
 #include "common/FinalFantasy7/FF7NameLookup.h"
 #include <boost/algorithm/string/replace.hpp>
 #include <boost/algorithm/string/replace.hpp>
 
 
-KernelDataInstaller::KernelDataInstaller(std::string path):
-  kernel_(path + "data/kernel/KERNEL.BIN"), exe_path_(path + "ff7.exe"){
+KernelDataInstaller::KernelDataInstaller(DiskImage& disk_image):
+  kernel_(*disk_image.fileExists("data/kernel/KERNEL.BIN")), exe_path_(*disk_image.fileExists("ff7.exe")){
     for (int i = 0; i < 416; i ++) prices_[i] = 50;
     for (int i = 0; i < 416; i ++) prices_[i] = 50;
 }
 }
 
 

+ 3 - 2
src/installer/KernelDataInstaller.h

@@ -17,6 +17,7 @@
 
 
 #include "common/BinGZipFile.h"
 #include "common/BinGZipFile.h"
 #include "common/TypeDefine.h"
 #include "common/TypeDefine.h"
+#include "DiskImage.h"
 #include "Characters.h"
 #include "Characters.h"
 #include "SaveMap.h"
 #include "SaveMap.h"
 
 
@@ -27,9 +28,9 @@ class KernelDataInstaller{
         /**
         /**
          * Constructor.
          * Constructor.
          *
          *
-         * @param[in] path Path selected in the installation as source directory.
+         * @param[in] disk_image Disk image to extract the data from.
          */
          */
-        KernelDataInstaller(std::string path);
+        KernelDataInstaller(DiskImage& disk_image);
 
 
         /**
         /**
          * Destructor.
          * Destructor.

+ 14 - 15
src/installer/MainWindow.cpp

@@ -26,15 +26,12 @@
 #include "DataInstaller.h"
 #include "DataInstaller.h"
 #include "MainWindow.h"
 #include "MainWindow.h"
 #include "ui_MainWindow.h"
 #include "ui_MainWindow.h"
-#include "Release.h"
 
 
 /**
 /**
  * Indicates if an installer has already been created.
  * Indicates if an installer has already been created.
  */
  */
 static bool installer_created = false;
 static bool installer_created = false;
 
 
-Release release;
-
 MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), main_window_(new Ui::MainWindow){
 MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), main_window_(new Ui::MainWindow){
     main_window_->setupUi(this);
     main_window_->setupUi(this);
     // Goto the data tab by default.
     // Goto the data tab by default.
@@ -146,15 +143,15 @@ void MainWindow::on_btn_data_src_clicked(){
     std::cout << "Selected ISO: " << temp.toStdString() << std::endl;
     std::cout << "Selected ISO: " << temp.toStdString() << std::endl;
     if (!temp.isNull()){
     if (!temp.isNull()){
         main_window_->line_data_src->setText(temp);
         main_window_->line_data_src->setText(temp);
-        release = Release(temp.toStdString());
-        main_window_->isoData->setText(QString::fromStdString(release.getId()));
-        if (!release.isValid()){
+        disk_image_ = DiskImage(temp.toStdString());
+        main_window_->isoData->setText(QString::fromStdString(disk_image_.getId()));
+        if (!disk_image_.isValid()){
             main_window_->isoError->setStyleSheet("QLabel { color : red; }");
             main_window_->isoError->setStyleSheet("QLabel { color : red; }");
-            main_window_->isoError->setText(QString::fromStdString(release.getErrorMessage()));
+            main_window_->isoError->setText(QString::fromStdString(disk_image_.getErrorMessage()));
         }
         }
-        else if (!release.isSupported()){
+        else if (!disk_image_.isSupported()){
             main_window_->isoError->setStyleSheet("QLabel { color : orange; }");
             main_window_->isoError->setStyleSheet("QLabel { color : orange; }");
-            main_window_->isoError->setText(QString::fromStdString(release.getWarningMessage()));
+            main_window_->isoError->setText(QString::fromStdString(disk_image_.getWarningMessage()));
         }
         }
         else{
         else{
             main_window_->isoError->setStyleSheet("QLabel { color : green; }");
             main_window_->isoError->setStyleSheet("QLabel { color : green; }");
@@ -187,7 +184,7 @@ void MainWindow::on_btn_data_run_clicked(){
           )
           )
         );
         );
     }
     }
-    if (release.isValid() == false){
+    if (disk_image_.isValid() == false){
         QMessageBox::critical(
         QMessageBox::critical(
           this, tr("Input error"),
           this, tr("Input error"),
           tr(
           tr(
@@ -202,13 +199,13 @@ void MainWindow::on_btn_data_run_clicked(){
         QString output = QDir::fromNativeSeparators(main_window_->line_data_dst->text());
         QString output = QDir::fromNativeSeparators(main_window_->line_data_dst->text());
         if (!output.endsWith("/")) output += "/";
         if (!output.endsWith("/")) output += "/";
 
 
-        bool extraction_success = release.extractIso(output.toStdString());
+        bool extraction_success = disk_image_.extractImage(output.toStdString());
         if (!extraction_success) {
         if (!extraction_success) {
             QMessageBox::critical(this, tr("Extraction error"), tr("Failed to extract ISO file."));
             QMessageBox::critical(this, tr("Extraction error"), tr("Failed to extract ISO file."));
             return;
             return;
         }
         }
 
 
-      QString input = QDir::fromNativeSeparators(QString::fromStdString(release.getContentPath()));
+      QString input = QDir::fromNativeSeparators(QString::fromStdString(disk_image_.getContentPath()));
       if (input.isEmpty()) {
       if (input.isEmpty()) {
         QMessageBox::critical(this, tr("Extraction error"), tr("Extracted data path is invalid."));
         QMessageBox::critical(this, tr("Extraction error"), tr("Extracted data path is invalid."));
         return;
         return;
@@ -237,8 +234,9 @@ void MainWindow::on_btn_data_run_clicked(){
 
 
         // Ensure required files are in the input dir
         // Ensure required files are in the input dir
         for (auto& file : required_files){
         for (auto& file : required_files){
-            QString full_path = input + QString::fromStdString(file);
-            if (!QFile::exists(full_path)){
+            std::cout << "Checking for required file: " << file << std::endl;
+            const auto matched_file = disk_image_.fileExists(file);
+            if (!matched_file){
                 QMessageBox::critical(
                 QMessageBox::critical(
                   this, tr("Missing input file"),
                   this, tr("Missing input file"),
                   tr(
                   tr(
@@ -248,6 +246,7 @@ void MainWindow::on_btn_data_run_clicked(){
                 );
                 );
                 return;
                 return;
             }
             }
+            std::cout << "Matched required file to: " << *matched_file << std::endl;
         }
         }
         if (installer_created){
         if (installer_created){
             // Due to the use of singletons, the installer can't be re-run.
             // Due to the use of singletons, the installer can't be re-run.
@@ -282,7 +281,7 @@ void MainWindow::on_btn_data_run_clicked(){
 
 
             installer_created = true;
             installer_created = true;
             installer_ = std::make_unique<DataInstaller>(
             installer_ = std::make_unique<DataInstaller>(
-              QDir::toNativeSeparators(input).toStdString(),
+              disk_image_,
               QDir::toNativeSeparators(output).toStdString(),
               QDir::toNativeSeparators(output).toStdString(),
               options,
               options,
               [this](const std::string log_line, int level, bool as_progress = false){
               [this](const std::string log_line, int level, bool as_progress = false){

+ 6 - 0
src/installer/MainWindow.h

@@ -18,6 +18,7 @@
 #include <QtWidgets/QMainWindow> // IVV fix path #include <QMainWindow>
 #include <QtWidgets/QMainWindow> // IVV fix path #include <QMainWindow>
 #include <QtCore/QSettings>
 #include <QtCore/QSettings>
 #include <memory>
 #include <memory>
+#include "DiskImage.h"
 
 
 namespace Ui {
 namespace Ui {
     class MainWindow;
     class MainWindow;
@@ -171,4 +172,9 @@ class MainWindow : public QMainWindow{
          * The installer.
          * The installer.
          */
          */
         std::unique_ptr<class DataInstaller> installer_;
         std::unique_ptr<class DataInstaller> installer_;
+
+        /**
+         * The selected disk image.
+         */
+        DiskImage disk_image_;
 };
 };

+ 21 - 11
src/installer/MediaDataInstaller.cpp

@@ -25,6 +25,7 @@
 #include <boost/predef/os.h>
 #include <boost/predef/os.h>
 #include <boost/filesystem.hpp>
 #include <boost/filesystem.hpp>
 #include <tinyxml.h>
 #include <tinyxml.h>
+#include "DiskImage.h"
 #include "MediaDataInstaller.h"
 #include "MediaDataInstaller.h"
 #include "data/VGearsLGPArchive.h"
 #include "data/VGearsLGPArchive.h"
 #include "data/VGearsTexFile.h"
 #include "data/VGearsTexFile.h"
@@ -47,14 +48,21 @@
 int MediaDataInstaller::TOTAL_SOUNDS = 750;
 int MediaDataInstaller::TOTAL_SOUNDS = 750;
 
 
 MediaDataInstaller::MediaDataInstaller(
 MediaDataInstaller::MediaDataInstaller(
-  const std::string input_dir, const std::string output_dir, const bool keep_originals,
+  const DiskImage& disk_image, const std::string output_dir, const bool keep_originals,
   const bool no_ffmpeg, const bool no_timidity
   const bool no_ffmpeg, const bool no_timidity
 ):
 ):
-  input_dir_(input_dir), output_dir_(output_dir), keep_originals_(keep_originals),
+    disk_image_(disk_image), output_dir_(output_dir),
+    keep_originals_(keep_originals),
   no_ffmpeg_(no_ffmpeg), no_timidity_(no_timidity),
   no_ffmpeg_(no_ffmpeg), no_timidity_(no_timidity),
-  menu_(input_dir + "data/menu/menu_us.lgp", "LGP"), window_(input_dir + "data/kernel/WINDOW.BIN"),
-  midi_(input_dir + "data/midi/midi.lgp", "LGP")
-{PopulateMaps();}
+    
+    window_(*disk_image_.fileExists("data/kernel/WINDOW.BIN")),
+    processed_sounds_(0),
+    menu_(VGears::LGPArchive(*disk_image_.fileExists("data/menu/menu_us.lgp"), "LGP")),
+    midi_(VGears::LGPArchive(*disk_image_.fileExists("data/midi/midi.lgp"), "LGP")),
+    processed_musics_(0)
+{
+        PopulateMaps();
+}
 
 
 
 
 std::string MediaDataInstaller::GetExecutablePath(){
 std::string MediaDataInstaller::GetExecutablePath(){
@@ -1134,10 +1142,10 @@ MediaDataInstaller::~MediaDataInstaller(){}
 
 
 void MediaDataInstaller::InstallSprites(){
 void MediaDataInstaller::InstallSprites(){
     // Actually open the lgp as a file for reading
     // Actually open the lgp as a file for reading
-    File menu(input_dir_ + "data/menu/menu_us.lgp");
+    File menu(*disk_image_.fileExists("data/menu/menu_us.lgp"));
 
 
     // Also, open it as a LGP archive.
     // Also, open it as a LGP archive.
-    menu_.open(input_dir_ + "data/menu/menu_us.lgp", true);
+    menu_.open(*disk_image_.fileExists("data/menu/menu_us.lgp"), true);
     menu_.load();
     menu_.load();
 
 
 
 
@@ -1377,7 +1385,7 @@ int MediaDataInstaller::InstallSoundsInit(){
     // SFXDump handles the wav conversion
     // SFXDump handles the wav conversion
     std::string command = (boost::format(
     std::string command = (boost::format(
       "%1%/sfxdump %2%data/sound/audio.fmt %2%data/sound/audio.dat %3%audio/sounds/"
       "%1%/sfxdump %2%data/sound/audio.fmt %2%data/sound/audio.dat %3%audio/sounds/"
-    ) % GetExecutablePath() % input_dir_ % output_dir_).str();    
+    ) % GetExecutablePath() % disk_image_.getContentPath() % output_dir_).str();    
     std::system(command.c_str());
     std::system(command.c_str());
     processed_sounds_ = 0;
     processed_sounds_ = 0;
     return TOTAL_SOUNDS;
     return TOTAL_SOUNDS;
@@ -1385,6 +1393,7 @@ int MediaDataInstaller::InstallSoundsInit(){
 
 
 bool MediaDataInstaller::InstallSounds(){
 bool MediaDataInstaller::InstallSounds(){
     if (!no_ffmpeg_){
     if (!no_ffmpeg_){
+        // TODO: check for ffmpeg executable and warn if not found.
         std::string f_path
         std::string f_path
           = output_dir_ + "audio/sounds/" + std::to_string(processed_sounds_) + ".wav";
           = output_dir_ + "audio/sounds/" + std::to_string(processed_sounds_) + ".wav";
         std::ifstream file(f_path);
         std::ifstream file(f_path);
@@ -1439,7 +1448,7 @@ void MediaDataInstaller::WriteSoundIndex(){
 
 
 int MediaDataInstaller::InstallMusicsInit(){
 int MediaDataInstaller::InstallMusicsInit(){
     // Read the music index file.
     // Read the music index file.
-    std::ifstream music_idx(input_dir_ + "data/music/music.idx");
+    std::ifstream music_idx(*disk_image_.fileExists("data/music/music.idx"));
     int i = 0;
     int i = 0;
     std::string name;
     std::string name;
     if (music_idx.is_open()){
     if (music_idx.is_open()){
@@ -1473,7 +1482,7 @@ bool MediaDataInstaller::InstallMusics(){
         }
         }
     }
     }
 
 
-    File midi(input_dir_ + "data/midi/midi.lgp");
+    File midi(*disk_image_.fileExists("data/midi/midi.lgp"));
 
 
     std::fstream out;
     std::fstream out;
     out.open(output_dir_ + "audio/musics/" + std::to_string(index) + ".mid", std::ios::out);
     out.open(output_dir_ + "audio/musics/" + std::to_string(index) + ".mid", std::ios::out);
@@ -1482,6 +1491,7 @@ bool MediaDataInstaller::InstallMusics(){
     out.close();
     out.close();
 
 
     // Convert to ogg (TiMidity + FFMpeg)
     // Convert to ogg (TiMidity + FFMpeg)
+    // TODO: Chek for ffmpeg and timidity executables and warn if not found.
     std::string command = (boost::format(
     std::string command = (boost::format(
       "timidity --quiet=3 %1%audio/musics/%2%.mid -Ow -o - "
       "timidity --quiet=3 %1%audio/musics/%2%.mid -Ow -o - "
       "| ffmpeg -hide_banner -loglevel panic -y -i - %1%audio/musics/%2%.ogg"
       "| ffmpeg -hide_banner -loglevel panic -y -i - %1%audio/musics/%2%.ogg"
@@ -1512,7 +1522,7 @@ void MediaDataInstaller::InstallHQMusics(){
 
 
         std::string command = (boost::format(
         std::string command = (boost::format(
           "ffmpeg -hide_banner -loglevel panic -y -i %1%musics/%2%.wav %3%audio/sounds/%4%.ogg"
           "ffmpeg -hide_banner -loglevel panic -y -i %1%musics/%2%.wav %3%audio/sounds/%4%.ogg"
-        ) % input_dir_ % hq_music % output_dir_ % index).str();
+        ) % disk_image_.getContentPath() % hq_music % output_dir_ % index).str();
         if (!no_ffmpeg_)  std::system(command.c_str());
         if (!no_ffmpeg_)  std::system(command.c_str());
     }
     }
 }
 }

+ 5 - 4
src/installer/MediaDataInstaller.h

@@ -16,6 +16,7 @@
 #pragma once
 #pragma once
 
 
 #include <unordered_map>
 #include <unordered_map>
+#include "DiskImage.h"
 #include "data/VGearsLGPArchive.h"
 #include "data/VGearsLGPArchive.h"
 #include "common/BinGZipFile.h"
 #include "common/BinGZipFile.h"
 #include "TexFile.h"
 #include "TexFile.h"
@@ -31,14 +32,14 @@ class MediaDataInstaller{
         /**
         /**
          * Constructor
          * Constructor
          *
          *
-         * @param[in] input_dir Path to the directory containing the original data to parse.
+         * @param[in] disk_image Disk image to extract the data from.
          * @param[in] output_dir Path to the directory of the installation data.
          * @param[in] output_dir Path to the directory of the installation data.
          * @param[in] keep_originals True to keep original data after conversion, false to remove.
          * @param[in] keep_originals True to keep original data after conversion, false to remove.
          * @param[in] no_ffmpeg True to prevent system calls to ffmpeg command, false to allow.
          * @param[in] no_ffmpeg True to prevent system calls to ffmpeg command, false to allow.
          * @param[in] no_timidity True to prevent system calls to timidity command, false to allow.
          * @param[in] no_timidity True to prevent system calls to timidity command, false to allow.
          */
          */
         MediaDataInstaller(
         MediaDataInstaller(
-          const std::string input_dir, const std::string output_dir, const bool keep_originals,
+          const DiskImage& disk_image, const std::string output_dir, const bool keep_originals,
           const bool no_ffmpeg, const bool no_timidity
           const bool no_ffmpeg, const bool no_timidity
         );
         );
 
 
@@ -121,9 +122,9 @@ class MediaDataInstaller{
 		std::string GetExecutablePath();
 		std::string GetExecutablePath();
 
 
         /**
         /**
-         * The path to the directory from which to read the PC game data.
+         * The disk image to read the original data from.
          */
          */
-        std::string input_dir_;
+        DiskImage disk_image_;
 
 
         /**
         /**
          * The path to the directory where to save the V-Gears data.
          * The path to the directory where to save the V-Gears data.

+ 0 - 246
src/installer/Release.h

@@ -1,246 +0,0 @@
-/*
- * 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 the install disk (PC only). */
-            DISK_INSTALL,
-            /** 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();
-
-        std::string getIsoPath() const { return iso_path; }
-
-        /**
-         * Gets the human readable ID of the release.
-         *
-         * @return The ID of the release.
-         */
-        std::string const getId();
-
-        /**
-         * Gets the platform for which the release is intended.
-         *
-         * @return The platform of the release.
-         */
-        Platform const getPlatform();
-
-        /**
-         * Gets the region for which the release is intended.
-         *
-         * @return The region of the release.
-         */
-        Region const getRegion();
-
-        /**
-         * Gets the language for which the release is intended.
-         *
-         * @return The language of the release.
-         */
-        Language const getLanguage();
-
-        /**
-         * Gets the disk for which the release is intended.
-         *
-         * @return The disk of the release.
-         */
-        Disk const getDisk();
-
-        /**
-         * Checks if the release is a valid Final Fantasy VII release.
-         *
-         * @return True if the release is valid, false otherwise.
-         */
-        bool const isValid();
-
-        /**
-         * Gets the error message for the release.
-         *
-         * @return The error message.
-         */
-        std::string const getErrorMessage();
-
-        /**
-         * Checks if the release is supported and tested in V-Gears.
-         *
-         * @return True if the release is supported, false otherwise.
-         */
-        bool const isSupported();
-
-        /**
-         * Gets a warning in case the release is not fully supported.
-         *
-         * @return The warning message.
-         */
-        std::string const getWarningMessage();
-
-        /**
-         * Gets the path where the content has been extracted.
-         * 
-         * @return The path to the extracted content, null if the content has
-         * not been extracted yet.
-         */
-        std::string const getContentPath();
-
-        /**
-         * Extracts the ISO file to the specified output path. The files will
-         * be extracted to the "original_data" subdirectory of the output path.
-         *
-         * @param installation_path The v-gears installation directory.
-         * @return True if the extraction was successful, false otherwise.
-         */
-        bool extractIso(std::string installation_path);
-
-    private:
-
-        /**
-         * The path to the ISO file.
-         */
-        std::string iso_path;
-
-        /**
-         * 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;
-
-        /**
-         * The path where the content has been extracted.
-         */
-        std::string content_path;
-};

+ 8 - 8
src/installer/WorldInstaller.cpp

@@ -34,10 +34,10 @@ std::string WorldInstaller::ELEMENT_MODELS_DIR("models/world/element");
 std::string WorldInstaller::TERRAIN_MODELS_DIR("models/world/terrain");
 std::string WorldInstaller::TERRAIN_MODELS_DIR("models/world/terrain");
 
 
 WorldInstaller::WorldInstaller(
 WorldInstaller::WorldInstaller(
-  const std::string input_dir, const std::string output_dir,
+  const DiskImage& disk_image, const std::string output_dir,
   const bool keep_originals, Ogre::ResourceGroupManager* res_mgr
   const bool keep_originals, Ogre::ResourceGroupManager* res_mgr
 ):
 ):
-  input_dir_(input_dir), output_dir_(output_dir), keep_originals_(keep_originals), res_mgr_(res_mgr)
+  disk_image_(disk_image), output_dir_(output_dir), keep_originals_(keep_originals), res_mgr_(res_mgr)
 {}
 {}
 
 
 WorldInstaller::~WorldInstaller(){}
 WorldInstaller::~WorldInstaller(){}
@@ -45,9 +45,9 @@ WorldInstaller::~WorldInstaller(){}
 unsigned int WorldInstaller::Initialize(){
 unsigned int WorldInstaller::Initialize(){
     if (wm_map_.size() > 0) wm_map_.clear();
     if (wm_map_.size() > 0) wm_map_.clear();
     processed_maps_ = 0;
     processed_maps_ = 0;
-    wm_map_.push_back(File(input_dir_ + "/data/wm/WM0.MAP"));
-    wm_map_.push_back(File(input_dir_ + "/data/wm/WM2.MAP"));
-    wm_map_.push_back(File(input_dir_ + "/data/wm/WM3.MAP"));
+    wm_map_.push_back(File(*disk_image_.fileExists("data/wm/WM0.MAP")));
+    wm_map_.push_back(File(*disk_image_.fileExists("data/wm/WM2.MAP")));
+    wm_map_.push_back(File(*disk_image_.fileExists("data/wm/WM3.MAP")));
     return wm_map_.size();
     return wm_map_.size();
 }
 }
 
 
@@ -348,9 +348,9 @@ void WorldInstaller::GenerateMaterials(){
 
 
 void WorldInstaller::ProcessModels(){
 void WorldInstaller::ProcessModels(){
     // Open world_us.lgp
     // Open world_us.lgp
-    File world_file(input_dir_ + "data/wm/world_us.lgp");
-    VGears::LGPArchive world_lgp(input_dir_ + "data/wm/world_us.lgp", "LGP");
-    world_lgp.open(input_dir_ + "data/wm/world_us.lgp", true);
+    File world_file(*disk_image_.fileExists("data/wm/world_us.lgp"));
+    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();
     world_lgp.load();
     VGears::LGPArchive::FileList file_list = world_lgp.GetFiles();
     VGears::LGPArchive::FileList file_list = world_lgp.GetFiles();
     for (int i = 0; i < file_list.size(); i ++){
     for (int i = 0; i < file_list.size(); i ++){

+ 5 - 4
src/installer/WorldInstaller.h

@@ -18,6 +18,7 @@
 #include <string>
 #include <string>
 #include <vector>
 #include <vector>
 #include <common/TypeDefine.h>
 #include <common/TypeDefine.h>
+#include "DiskImage.h"
 #include "common/File.h"
 #include "common/File.h"
 
 
 /**
 /**
@@ -30,13 +31,13 @@ class WorldInstaller{
         /**
         /**
          * Constructor.
          * Constructor.
          *
          *
-         * @param[in] input_dir Path to the directory containing the original data to parse.
+         * @param[in] disk_image Disk image to extract the data from.
          * @param[in] output_dir Path to the directory of the installation data.
          * @param[in] output_dir Path to the directory of the installation data.
          * @param[in] keep_originals True to keep original data after conversion, false to remove.
          * @param[in] keep_originals True to keep original data after conversion, false to remove.
          * @param[in] res_mgr The application resource manager.
          * @param[in] res_mgr The application resource manager.
          */
          */
         WorldInstaller(
         WorldInstaller(
-          const std::string input_dir, const std::string output_dir,
+          const DiskImage& disk_image, const std::string output_dir,
           const bool keep_originals, Ogre::ResourceGroupManager* res_mgr
           const bool keep_originals, Ogre::ResourceGroupManager* res_mgr
         );
         );
 
 
@@ -313,9 +314,9 @@ class WorldInstaller{
         std::vector<Texture> texture_;
         std::vector<Texture> texture_;
 
 
         /**
         /**
-         * The path to the directory from which to read the PC game data.
+         * The disk image to extract the PC game data from.
          */
          */
-        std::string input_dir_;
+        DiskImage disk_image_;
 
 
         /**
         /**
          * The path to the directory where to save the V-Gears data.
          * The path to the directory where to save the V-Gears data.