Просмотр исходного кода

The installer now parses the whole initial savemap. All data from the kernel is now parsed during installation.
Also, if the user provides an executable from the OG, item and materia prices are extracted from there (if not, they default to a fixed price).
The UI of the installer has been reworked. There is an input for the OG executable, help tooltips have been added, labels and error messages are now more descriptive, and, in general, it has a more pleasant look.
Some small optimization improvements in the installer code.

Iñigo Valentin 3 лет назад
Родитель
Сommit
8ec38b6ba0

+ 33 - 21
V-Gears-Installer/include/DataInstaller.h

@@ -58,13 +58,15 @@ class DataInstaller{
          *
          * @param[in] input_dir Path to the directory containing the original
          * data to parse.
+         * @param[in] exe_path Path to the ff7.exe file. It's optional and it may be empty.
          * @param[in] output_dir Path to the directory to write generated data
          * to.
          * @param[in] write_output_line Pointer to function to write output.
          */
         DataInstaller(
-          const std::string input_dir, const std::string output_dir,
-          std::function<void(std::string)> write_output_line
+          const std::string input_dir, const std::string exe_path, const std::string output_dir,
+          std::function<void(std::string)> write_output_line,
+          std::function<void(std::string)> set_progress_label
         );
 
         /**
@@ -86,7 +88,7 @@ class DataInstaller{
          *
          * @return Installation progress [0-100]
          */
-        int CalcProgress();
+        const int CalcProgress();
 
         /**
          * Creates a directory in the outputh path.
@@ -223,10 +225,30 @@ class DataInstaller{
         std::string input_dir_;
 
         /**
-         * The path to the directory where to sve the V-Gears data.
+         * The path to original PC game executable.
+         */
+        std::string exe_path_;
+
+        /**
+         * The path to the directory where to save the V-Gears data.
          */
         std::string output_dir_;
 
+        /**
+         * Iterator counter.
+         */
+        size_t iterator_counter_;
+
+        /**
+         * Helper variable to indicate internal progress of installation steps.
+         */
+        size_t conversion_step_;
+
+        /**
+         * Helper variable to indicate internal progress of installation steps.
+         */
+        size_t progress_step_num_elements_;
+
         /**
          * The installer application.
          *
@@ -291,21 +313,6 @@ class DataInstaller{
          */
         MapCollection::iterator converted_map_list_iterator_;
 
-        /**
-         * Iterator counter.
-         */
-        size_t iterator_counter_;
-
-        /**
-         * Helper variable to indicate internal progress of installation steps.
-         */
-        size_t conversion_step_;
-
-        /**
-         * Helper variable to indicate internal progress of installation steps.
-         */
-        size_t progress_step_num_elements_;
-
         /**
          * Field currently being processed.
          */
@@ -336,9 +343,14 @@ class DataInstaller{
         ModelAnimationMap::iterator model_animation_map_iterator_;
 
         /**
-         * Function used to print text.
+         * Function used to print text to the log output, line by line.
+         */
+        std::function<void(std::string)> write_output_line_;
+
+        /**
+         * Function used to print set the current installation progress text.
          */
-        std::function<void(std::string)> write_output_line;
+        std::function<void(std::string)> set_progress_label_;
 
         /**
          * Field text writer.

+ 13 - 0
V-Gears-Installer/include/KernelDataInstaller.h

@@ -36,6 +36,8 @@ class KernelDataInstaller{
          */
         ~KernelDataInstaller();
 
+        void ReadPrices(std::string menu_path);
+
         /**
          * Reads the command data from the kernel.
          *
@@ -2433,4 +2435,15 @@ class KernelDataInstaller{
          */
         BinGZipFile kernel_;
 
+        /**
+         * Item and materia shop prices.
+         *
+         * Indexes 0 - 127: Items.
+         * Indexes 128 - 255: Weapons.
+         * Indexes 256 - 287: Armors.
+         * Indexes 288 - 319: Accessories
+         * Indxes 320 - 415: Materia.
+         */
+        u32 prices_[416];
+
 };

+ 7 - 0
V-Gears-Installer/include/MainWindow.h

@@ -86,6 +86,13 @@ class MainWindow : public QMainWindow{
          */
         void on_btn_data_src_clicked();
 
+        /**
+         * Triggered when the ff7.exe file button is clicked.
+         *
+         * Opens a file manager for file selection.
+         */
+        void on_btn_exe_src_clicked();
+
         /**
          * Triggered when the output data directory has done being edited.
          *

+ 5 - 0
V-Gears-Installer/include/SaveMap.h

@@ -419,6 +419,11 @@ struct SaveMap{
          */
         bool back_row;
 
+        /**
+         * Indicates which limits the character have learned.
+         */
+        bool limits_learned[4][2];
+
     };
 
     /**

+ 1 - 1
V-Gears-Installer/include/decompiler/Value.h

@@ -610,7 +610,7 @@ class ArrayValue : public VarValue {
          * @param[in] indexes List of stack entries representing the indexes
          * used (left-to-right).
          */
-        ArrayValue(const std::string name, const ValueList indexes);
+        ArrayValue(const std::string& name, const ValueList& indexes);
 
         /**
          * Print the value to a stream.

+ 39 - 28
V-Gears-Installer/src/DataInstaller.cpp

@@ -57,15 +57,17 @@
 float DataInstaller::LINE_SCALE_FACTOR = 0.0078124970964f;
 
 DataInstaller::DataInstaller(
-  const std::string input_dir, const std::string output_dir,
-  std::function<void(std::string)> write_output_line
+  const std::string input_dir, const std::string exe_path, const std::string output_dir,
+  std::function<void(std::string)> write_output_line,
+  std::function<void(std::string)> set_progress_label
 )
 #ifdef _DEBUG
     : application_("plugins_d.cfg", "resources_d.cfg", "install_d.log"),
 #else
   : application_("plugins.cfg", "resources.cfg", "install.log"),
 #endif
-  input_dir_(input_dir), output_dir_(output_dir), write_output_line(write_output_line),
+  input_dir_(input_dir), exe_path_(exe_path), output_dir_(output_dir),
+  write_output_line_(write_output_line), set_progress_label_(set_progress_label),
   iterator_counter_(0), conversion_step_(0), progress_step_num_elements_(0)
 {
     if (!application_.initOgre(true)) throw std::runtime_error("Ogre init failure");
@@ -77,7 +79,7 @@ DataInstaller::DataInstaller(
 
 DataInstaller::~DataInstaller(){}
 
-int DataInstaller::CalcProgress(){
+const int DataInstaller::CalcProgress(){
     // TODO: Make more accurate with iterator_counter_ and
     // progress_step_num_elements_.
     float curr_step = installation_state_ / static_cast<float>(STATE_COUNT);
@@ -88,15 +90,18 @@ int DataInstaller::CalcProgress(){
 int DataInstaller::Progress(){
     switch (installation_state_){
         case IDLE:
-            write_output_line("Loading flevel.lgp");
+            write_output_line_("Loading flevel.lgp");
+            set_progress_label_("Loading flevel.lgp");
             progress_step_num_elements_ = 1;
             iterator_counter_ = 0;
             fields_lgp_ = std::make_unique<ScopedLgp>(
               application_.getRoot(), input_dir_ + "field/flevel.lgp", "LGP", "FFVIIFields"
             );
+            set_progress_label_("Installing kernel data...");
             kernel_installer_ = std::make_unique<KernelDataInstaller>(
-              input_dir_ += "kernel/KERNEL.BIN"
+              input_dir_ + "kernel/KERNEL.BIN"
             );
+            kernel_installer_->ReadPrices(exe_path_);
             if (kernel_installer_->ReadCommands() > 0){
                 CreateDir("game");
                 kernel_installer_->WriteCommands(output_dir_ + "game/commands.xml");
@@ -139,14 +144,17 @@ int DataInstaller::Progress(){
                 CreateDir("game");
                 kernel_installer_->WriteSummonNames(output_dir_ + "game/summons.xml");
             }
-            exit(0);
+            kernel_installer_->ReadInitialSaveMap();
+            kernel_installer_->WriteInitialSaveMap(output_dir_ + "game/initial_savemap.xml");
+            //exit(0);
 
             installation_state_ = SPAWN_POINTS_AND_SCALE_FACTORS_INIT;
             // TODO: DEBUG:
             //installation_state_ = CONVERT_FIELDS;
             return CalcProgress();
         case SPAWN_POINTS_AND_SCALE_FACTORS_INIT:
-            write_output_line("Collecting spawn points and scale factors...");
+            write_output_line_("Collecting spawn points and scale factors...");
+            set_progress_label_("Collecting spawn points and scale factors...");
             InitCollectSpawnAndScaleFactors();
             return CalcProgress();
         case SPAWN_POINTS_AND_SCALE_FACTORS:
@@ -156,7 +164,8 @@ int DataInstaller::Progress(){
             ConvertFieldsIteration();
             return CalcProgress();
         case WRITE_MAPS_INIT:
-            write_output_line("Writing fields...");
+            write_output_line_("Writing fields...");
+            set_progress_label_("Writing fields...");
             WriteMapsXmlBegin();
             return CalcProgress();
         case WRITE_MAPS:
@@ -166,7 +175,8 @@ int DataInstaller::Progress(){
             EndWriteMapsXml();
             return CalcProgress();
         case CONVERT_FIELD_MODELS_INIT:
-            write_output_line("Converting field models...");
+            write_output_line_("Converting field models...");
+            set_progress_label_("Converting field models...");
             ConvertFieldModelsBegin();
             return CalcProgress();
         case CONVERT_FIELD_MODELS:
@@ -642,7 +652,7 @@ static float FieldScaleFactor(
  * @param[in] model_animation_db Database of model animations.
  * @param[out] maps The list of maps. The converted one will be added at the
  * end.
- * @param[in] write_output_line Functon to print output to console.
+ * @param[in] write_output_line_ Functon to print output to console.
  */
 static void FF7PcFieldToVGearsField(
   FieldTextWriter& field_text_writter,
@@ -653,7 +663,7 @@ static void FF7PcFieldToVGearsField(
   const FieldScaleFactorMap& scale_factor_map,
   ModelsAndAnimationsDb& model_animation_db,
   MapCollection& maps,
-  std::function<void(std::string)> write_output_line
+  std::function<void(std::string)> write_output_line_
 ){
     // Generate triggers script to insert into main
     // decompiled FF7 field -> LUA script.
@@ -689,7 +699,7 @@ static void FF7PcFieldToVGearsField(
             script_file << decompiled.luaScript;
             try{field_text_writter.Write(raw_field_data, field->getName());}
             catch (const std::out_of_range& ex){
-                write_output_line(
+                write_output_line_(
                   "[ERROR] Failed to read texts from field " + field->getName() + ": " + ex.what()
                 );
                 std::cerr << "[ERROR] Failed to read texts from field "
@@ -697,7 +707,7 @@ static void FF7PcFieldToVGearsField(
             }
         }
         else{
-            write_output_line(
+            write_output_line_(
               "[ERROR] Failed to open script file from field " + field->getName() + " for writing."
             );
             std::cerr << "[ERROR] Failed to open script file from field "
@@ -705,7 +715,7 @@ static void FF7PcFieldToVGearsField(
         }
     }
     catch (const ::DecompilerException& ex){
-        write_output_line(
+        write_output_line_(
           "[ERROR] Internal decompiler error in field " + field->getName() + ": " + ex.what()
         );
         std::cerr << "[ERROR] Internal decompiler error in field "
@@ -786,7 +796,7 @@ static void FF7PcFieldToVGearsField(
                 if (
                   triangle_index >= field->GetWalkmesh()->GetTriangles().size()
                 ){
-                    write_output_line(
+                    write_output_line_(
                       "[WARNING] In field " + field->getName() + ": Map jump triangle ("
                       + std::to_string(triangle_index) + ") out of bounds ("
                       + std::to_string(field->GetWalkmesh()->GetTriangles().size()) + ")"
@@ -1309,16 +1319,16 @@ void DataInstaller::CollectionFieldSpawnAndScaleFactors(){
                 field_ = VGears::LZSFLevelFileManager::GetSingleton().load(
                   resource_name, "FFVIIFields"
                 ).staticCast<VGears::FLevelFile>();
-                //write_output_line("Load field " + resource_name);
+                //write_output_line_("Load field " + resource_name);
                 return;
             }
             if (conversion_step_ == 2){
-                //write_output_line("Read spawn points from scripts");
+                //write_output_line_("Read spawn points from scripts");
                 CollectSpawnPoints(field_, map_list_, spawn_points_);
                 return;
             }
             if (conversion_step_ == 3){
-                //write_output_line("Read scale factor");
+                //write_output_line_("Read scale factor");
                 CollectFieldScaleFactors(field_, scale_factors_, map_list_);
                 conversion_step_ = 0;
                 iterator_counter_ ++;
@@ -1345,7 +1355,7 @@ void DataInstaller::ConvertFieldsIteration(){
             //if (/*IsTestField(resource_name) &&*/ !WillCrash(resource_name)){
             // TODO: DEBUG: Only test fields
             if (IsTestField(resource_name) && !WillCrash(resource_name)){
-                //write_output_line("Converting field " + resource_name);
+                //write_output_line_("Converting field " + resource_name);
                 std::cout << " - Converting field: " << resource_name << std::endl;
                 CreateDir(FieldMapDir() + "/" + resource_name);
                 VGears::FLevelFilePtr field
@@ -1354,13 +1364,13 @@ void DataInstaller::ConvertFieldsIteration(){
                   ).staticCast<VGears::FLevelFile>();
                 FF7PcFieldToVGearsField(
                   field_text_writer_, field, output_dir_, map_list_, spawn_points_, scale_factors_,
-                  used_models_and_anims_, converted_map_list_, write_output_line
+                  used_models_and_anims_, converted_map_list_, write_output_line_
                 );
             }
             else{
-                write_output_line(
+                /*write_output_line_(
                   "[ERROR] Skip field " + resource_name + " due to crash or hang issue."
-                );
+                );*/
                 std::cerr << "[ERROR] Skip field: " << resource_name
                   << " due to crash or hang issue." << std::endl;
             }
@@ -1390,7 +1400,7 @@ void DataInstaller::WriteMapsXmlIteration(){
     progress_step_num_elements_ = converted_map_list_.size();
     if (converted_map_list_iterator_ != converted_map_list_.end()){
         auto map = *converted_map_list_iterator_;
-        write_output_line("Writing field " + FieldName(map));
+        write_output_line_("Writing field " + FieldName(map));
         std::unique_ptr<TiXmlElement> xml_element(new TiXmlElement("map"));
         xml_element->SetAttribute("name", FieldName(map));
         xml_element->SetAttribute("file_name", FieldMapDir() + "/" + map + "/map.xml");
@@ -1426,7 +1436,7 @@ void DataInstaller::ConvertFieldModelsIteration(){
     progress_step_num_elements_ = used_models_and_anims_.map.size();
     if (model_animation_map_iterator_ != used_models_and_anims_.map.end()){
         if (conversion_step_ == 0){
-            write_output_line("Converting model " + model_animation_map_iterator_->first);
+            write_output_line_("Converting model " + model_animation_map_iterator_->first);
             conversion_step_ ++;
         }
         else{
@@ -1449,7 +1459,7 @@ void DataInstaller::ConvertFieldModelsIteration(){
                 ExportMesh(output_dir_ + "/" + FieldModelDir() + "/", mesh);
             }
             catch (const Ogre::Exception& ex){
-                write_output_line(
+                write_output_line_(
                   "[ERROR] Ogre exception converting model "
                   + model_animation_map_iterator_->first + ": " + ex.what()
                 );
@@ -1457,7 +1467,7 @@ void DataInstaller::ConvertFieldModelsIteration(){
                   << model_animation_map_iterator_->first <<": " << ex.what() << std::endl;
             }
             catch (const std::exception& ex){
-                write_output_line(
+                write_output_line_(
                   "[ERROR] Exception converting model "
                   + model_animation_map_iterator_->first + ": " + ex.what()
                 );
@@ -1471,7 +1481,8 @@ void DataInstaller::ConvertFieldModelsIteration(){
     }
     else{
         std::cout << "Installation finished." << std::endl;
-        write_output_line("Installation finished.");
+        write_output_line_("Installation finished.");
+        set_progress_label_("Installation finished.");
         installation_state_ = DONE;
     }
 }

+ 221 - 64
V-Gears-Installer/src/KernelDataInstaller.cpp

@@ -18,10 +18,20 @@
 #include "KernelDataInstaller.h"
 #include "common/FinalFantasy7/FF7NameLookup.h"
 
-KernelDataInstaller::KernelDataInstaller(std::string path): kernel_(path){}
+KernelDataInstaller::KernelDataInstaller(std::string path): kernel_(path){
+    for (int i = 0; i < 416; i ++) prices_[i] = 50;
+}
 
 KernelDataInstaller::~KernelDataInstaller(){}
 
+void KernelDataInstaller::ReadPrices(std::string exe_path){
+    File exe_file(exe_path);
+    exe_file.SetOffset(0x00514518); // Items, weapons, armors, accessories start here
+    for (int i = 0; i < 320; i ++) prices_[i] = exe_file.readU32LE();
+    exe_file.SetOffset(0x00514B18); // Materias start here
+    for (int i = 320; i < 416; i ++) prices_[i] = exe_file.readU32LE();
+}
+
 int KernelDataInstaller::ReadCommands(){
 
     // Empty the item list
@@ -836,6 +846,8 @@ void KernelDataInstaller::WriteItems(std::string file){
     for (ItemData item : items_){
         std::unique_ptr<TiXmlElement> xml_item(new TiXmlElement("item"));
         xml_item->SetAttribute("id", item.id);
+        xml_item->SetAttribute("inventory_id", item.id);
+        xml_item->SetAttribute("price", prices_[item.id]);
         xml_item->SetAttribute("name", item.name);
         xml_item->SetAttribute("description", item.description);
         xml_item->SetAttribute("camera", item.camera);
@@ -1066,6 +1078,8 @@ void KernelDataInstaller::WriteWeapons(std::string file){
     for (WeaponData weapon : weapons_){
         std::unique_ptr<TiXmlElement> xml_weapon(new TiXmlElement("weapon"));
         xml_weapon->SetAttribute("id", weapon.id);
+        xml_weapon->SetAttribute("inventory_id", weapon.id + 128);
+        xml_weapon->SetAttribute("price", prices_[weapon.id + 128]);
         xml_weapon->SetAttribute("name", weapon.name);
         xml_weapon->SetAttribute("description", weapon.description);
         xml_weapon->SetAttribute("camera", weapon.camera);
@@ -1149,7 +1163,6 @@ int KernelDataInstaller::ReadArmors(){
     armors_.clear();
 
     File armor_file = kernel_.ExtractGZip(KERNEL_ARMOR_DATA);
-    armor_file.WriteFile("/home/ivalentin/armor");
     File armor_name_file = kernel_.ExtractGZip(KERNEL_ARMOR_NAMES);
     File armor_desc_file = kernel_.ExtractGZip(KERNEL_ARMOR_DESCRIPTIONS);
     int name_offset = 0;
@@ -1300,6 +1313,8 @@ void KernelDataInstaller::WriteArmors(std::string file){
     for (ArmorData armor : armors_){
         std::unique_ptr<TiXmlElement> xml_armor(new TiXmlElement("armor"));
         xml_armor->SetAttribute("id", armor.id);
+        xml_armor->SetAttribute("inventory_id", armor.id + 256);
+        xml_armor->SetAttribute("price", prices_[armor.id + 256]);
         xml_armor->SetAttribute("name", armor.name);
         xml_armor->SetAttribute("description", armor.description);
         xml_armor->SetAttribute("sell", armor.sellable);
@@ -1531,6 +1546,8 @@ void KernelDataInstaller::WriteAccessories(std::string file){
     for (AccessoryData accessory : accessories_){
         std::unique_ptr<TiXmlElement> xml_accessory(new TiXmlElement("accessory"));
         xml_accessory->SetAttribute("id", accessory.id);
+        xml_accessory->SetAttribute("inventory_id", accessory.id + 287);
+        xml_accessory->SetAttribute("price", prices_[accessory.id + 287]);
         xml_accessory->SetAttribute("name", accessory.name);
         xml_accessory->SetAttribute("description", accessory.description);
         xml_accessory->SetAttribute("sell", accessory.sellable);
@@ -1840,6 +1857,7 @@ void KernelDataInstaller::WriteMateria(std::string file){
     for (MateriaData materia : materia_){
         std::unique_ptr<TiXmlElement> xml_materia(new TiXmlElement("materia"));
         xml_materia->SetAttribute("id", materia.id);
+        xml_materia->SetAttribute("price", prices_[materia.id + 320]);
         xml_materia->SetAttribute("name", materia.name);
         xml_materia->SetAttribute("description", materia.description);
         xml_materia->SetAttribute("type", materia.type);
@@ -2088,22 +2106,10 @@ void KernelDataInstaller::ReadInitialSaveMap(){
     // Empty the item list
     attacks_.clear();
 
-    File savemap_file = kernel_.ExtractGZip(KERNEL_ATTACK_DATA);
-
-    savemap_.checksum = savemap_file.readU32LE();
-    for (int i = 0; i < 68; i ++) savemap_.preview[i] = savemap_file.readU8();
-    savemap_.window_colour.top_left.red = savemap_file.readU8();
-    savemap_.window_colour.top_left.green = savemap_file.readU8();
-    savemap_.window_colour.top_left.blue = savemap_file.readU8();
-    savemap_.window_colour.top_right.red = savemap_file.readU8();
-    savemap_.window_colour.top_right.green = savemap_file.readU8();
-    savemap_.window_colour.top_right.blue = savemap_file.readU8();
-    savemap_.window_colour.top_left.red = savemap_file.readU8();
-    savemap_.window_colour.bottom_left.green = savemap_file.readU8();
-    savemap_.window_colour.bottom_left.blue = savemap_file.readU8();
-    savemap_.window_colour.bottom_right.red = savemap_file.readU8();
-    savemap_.window_colour.bottom_right.green = savemap_file.readU8();
-    savemap_.window_colour.bottom_right.blue = savemap_file.readU8();
+    File savemap_file = kernel_.ExtractGZip(KERNEL_INITIALIZATION_DATA);
+    savemap_file.WriteFile("/home/ivalentin/initialsavemap");
+    // The initial savemap in KERNEL.BIN startas at 0x0054, directly into Cloud's level.
+    // It skips the header (PC and PSX, checksum, preview data and window colour).
     for (int i = 0; i < 9; i ++){
         savemap_.characters[i].identifier = savemap_file.readU8();
         savemap_.characters[i].level = savemap_file.readU8();
@@ -2154,62 +2160,213 @@ void KernelDataInstaller::ReadInitialSaveMap(){
         else savemap_.characters[i].back_row = false;
         savemap_.characters[i].level_progress = savemap_file.readU8();
         savemap_.characters[i].learned_limits_raw = savemap_file.readU16LE();
+        for (int j = 0; j < 4; j ++){
+            savemap_.characters[i].limits_learned[j][0] = false;
+            savemap_.characters[i].limits_learned[j][1] = false;
+        }
+        if (savemap_.characters[i].learned_limits_raw == (savemap_.characters[i].learned_limits_raw | 0x0001))
+            savemap_.characters[i].limits_learned[0][0] = true;
+        if (savemap_.characters[i].learned_limits_raw == (savemap_.characters[i].learned_limits_raw | 0x0002))
+            savemap_.characters[i].limits_learned[0][1] = true;
+        if (savemap_.characters[i].learned_limits_raw == (savemap_.characters[i].learned_limits_raw | 0x0008))
+            savemap_.characters[i].limits_learned[1][0] = true;
+        if (savemap_.characters[i].learned_limits_raw == (savemap_.characters[i].learned_limits_raw | 0x0010))
+            savemap_.characters[i].limits_learned[1][1] = true;
+        if (savemap_.characters[i].learned_limits_raw == (savemap_.characters[i].learned_limits_raw | 0x0040))
+            savemap_.characters[i].limits_learned[2][0] = true;
+        if (savemap_.characters[i].learned_limits_raw == (savemap_.characters[i].learned_limits_raw | 0x0080))
+            savemap_.characters[i].limits_learned[2][1] = true;
+        if (savemap_.characters[i].learned_limits_raw == (savemap_.characters[i].learned_limits_raw | 0x0200))
+            savemap_.characters[i].limits_learned[3][0] = true;
+        savemap_.characters[i].kills = savemap_file.readU16LE();
+        savemap_.characters[i].limit_uses[0] = savemap_file.readU16LE();
+        savemap_.characters[i].limit_uses[1] = savemap_file.readU16LE();
+        savemap_.characters[i].limit_uses[2] = savemap_file.readU16LE();
+        savemap_.characters[i].hp = savemap_file.readU16LE();
+        savemap_.characters[i].base_hp = savemap_file.readU16LE();
+        savemap_.characters[i].mp = savemap_file.readU16LE();
+        savemap_.characters[i].base_mp = savemap_file.readU16LE();
+        savemap_.characters[i].unknown = savemap_file.readU32LE();
+        savemap_.characters[i].max_hp = savemap_file.readU16LE();
+        savemap_.characters[i].max_mp = savemap_file.readU16LE();
+        savemap_.characters[i].exp = savemap_file.readU32LE();
+        for (int m = 0; m < 8; m ++){
+            savemap_.characters[i].weapon_materia[m].id = savemap_file.readU8();
+            // TODO: Identify Enemy Skill, do special treatment
+            savemap_.characters[i].weapon_materia[m].ap // WARNING: AP has only three bytes.
+              = savemap_file.readU8()
+              + (savemap_file.readU8() << 8)
+              + (savemap_file.readU8() << 16);
+        }
+        for (int m = 0; m < 8; m ++){
+            savemap_.characters[i].armor_materia[m].id = savemap_file.readU8();
+            // TODO: Identify Enemy Skill, do special treatment
+            savemap_.characters[i].armor_materia[m].ap // WARNING: AP has only three bytes.
+              = savemap_file.readU8()
+              + (savemap_file.readU8() << 8)
+              + (savemap_file.readU8() << 16);
+        }
+        savemap_.characters[i].exp_to_next = savemap_file.readU32LE();
     }
-
+    // The initial savemap ends up after character data, end now.
 }
 
 void KernelDataInstaller::WriteInitialSaveMap(std::string file){
+    // The initial save map only contains character data.
     TiXmlDocument xml;
-    std::unique_ptr<TiXmlElement> container(new TiXmlElement("attacks"));
-    for (AttackData attack : attacks_){
-        std::unique_ptr<TiXmlElement> xml_attack(new TiXmlElement("attack"));
-        xml_attack->SetAttribute("id", attack.id);
-        xml_attack->SetAttribute("accuracy", attack.accuracy);
-        xml_attack->SetAttribute("impact_effect", attack.impact_effect);
-        xml_attack->SetAttribute("hurt_anim", attack.hurt_anim);
-        xml_attack->SetAttribute("mp", attack.mp);
-        xml_attack->SetAttribute("sounds", attack.sound);
-        xml_attack->SetAttribute("camera_1", attack.camera_single);
-        xml_attack->SetAttribute("camera_x", attack.camera_multiple);
-        xml_attack->SetAttribute("effect", attack.effect);
-        xml_attack->SetAttribute("dmg_formula", attack.damage_formula);
-        xml_attack->SetAttribute("dmg_modifier", attack.damage_modifier);
-        xml_attack->SetAttribute("power", attack.power);
-        xml_attack->SetAttribute("restore", attack.restore_type);
-        xml_attack->SetAttribute("target_select", attack.target.selection_enabled);
-        xml_attack->SetAttribute("target_default_enemy", attack.target.default_enemy);
-        xml_attack->SetAttribute("target_default_multiple", attack.target.default_multiple);
-        xml_attack->SetAttribute("target_toggle_multiple", attack.target.toggle_multiple);
-        xml_attack->SetAttribute("target_fixed", attack.target.fixed_row);
-        xml_attack->SetAttribute("target_short_range", attack.target.short_range);
-        xml_attack->SetAttribute("target_all", attack.target.all_rows);
-        xml_attack->SetAttribute("target_random", attack.target.random);
-        // Statuses.
-        if (attack.status.status.size() > 0){
-            std::unique_ptr<TiXmlElement> xml_status(new TiXmlElement("statuses"));
-            xml_status->SetAttribute("mode", attack.status.mode);
-            xml_status->SetAttribute("chance", attack.status.chance);
-            for (int s : attack.status.status){
-                std::unique_ptr<TiXmlElement> xml_status_status(new TiXmlElement("status"));
-                xml_status_status->SetAttribute("id", s);
-                xml_status->LinkEndChild(xml_status_status.release());
+    std::unique_ptr<TiXmlElement> container(new TiXmlElement("savemap"));
+    std::unique_ptr<TiXmlElement> xml_characters(new TiXmlElement("characters"));
+    int total_count = 0; // Utility to count in loops.
+    // Characters.
+    for (int c = 0; c < 9; c ++){
+        std::unique_ptr<TiXmlElement> xml_character(new TiXmlElement("character"));
+        xml_character->SetAttribute("id", c);
+        xml_character->SetAttribute("char_id", savemap_.characters[c].identifier);
+        xml_character->SetAttribute("name", savemap_.characters[c].name);
+        xml_character->SetAttribute("level", savemap_.characters[c].level);
+        xml_character->SetAttribute("kills", savemap_.characters[c].kills);
+        xml_character->SetAttribute("exp", savemap_.characters[c].exp);
+        xml_character->SetAttribute("exp_to_next_lv", savemap_.characters[c].exp_to_next);
+        // Character stats.
+        std::unique_ptr<TiXmlElement> xml_stats(new TiXmlElement("stats"));
+        // TODO: Lookup stat names or IDs (one or the other, don't hardcode both).
+        std::unique_ptr<TiXmlElement> xml_stat_str(new TiXmlElement("stat"));
+        xml_stat_str->SetAttribute("id", 0);
+        xml_stat_str->SetAttribute("name", "str");
+        xml_stat_str->SetAttribute("value", savemap_.characters[c].str);
+        xml_stat_str->SetAttribute("bonus", savemap_.characters[c].str_bonus);
+        xml_stats->LinkEndChild(xml_stat_str.release());
+        std::unique_ptr<TiXmlElement> xml_stat_vit(new TiXmlElement("stat"));
+        xml_stat_vit->SetAttribute("id", 1);
+        xml_stat_vit->SetAttribute("name", "vit");
+        xml_stat_vit->SetAttribute("value", savemap_.characters[c].vit);
+        xml_stat_vit->SetAttribute("bonus", savemap_.characters[c].vit_bonus);
+        xml_stats->LinkEndChild(xml_stat_vit.release());
+        std::unique_ptr<TiXmlElement> xml_stat_mag(new TiXmlElement("stat"));
+        xml_stat_mag->SetAttribute("id", 2);
+        xml_stat_mag->SetAttribute("name", "mag");
+        xml_stat_mag->SetAttribute("value", savemap_.characters[c].mag);
+        xml_stat_mag->SetAttribute("bonus", savemap_.characters[c].mag_bonus);
+        xml_stats->LinkEndChild(xml_stat_mag.release());
+        std::unique_ptr<TiXmlElement> xml_stat_spr(new TiXmlElement("stat"));
+        xml_stat_spr->SetAttribute("id", 3);
+        xml_stat_spr->SetAttribute("name", "spr");
+        xml_stat_spr->SetAttribute("value", savemap_.characters[c].spr);
+        xml_stat_spr->SetAttribute("bonus", savemap_.characters[c].spr_bonus);
+        xml_stats->LinkEndChild(xml_stat_spr.release());
+        std::unique_ptr<TiXmlElement> xml_stat_dex(new TiXmlElement("stat"));
+        xml_stat_dex->SetAttribute("id", 4);
+        xml_stat_dex->SetAttribute("name", "dex");
+        xml_stat_dex->SetAttribute("value", savemap_.characters[c].dex);
+        xml_stat_dex->SetAttribute("bonus", savemap_.characters[c].dex_bonus);
+        xml_stats->LinkEndChild(xml_stat_dex.release());
+        std::unique_ptr<TiXmlElement> xml_stat_lck(new TiXmlElement("stat"));
+        xml_stat_lck->SetAttribute("id", 5);
+        xml_stat_lck->SetAttribute("name", "lck");
+        xml_stat_lck->SetAttribute("value", savemap_.characters[c].lck);
+        xml_stat_lck->SetAttribute("bonus", savemap_.characters[c].lck_bonus);
+        xml_stats->LinkEndChild(xml_stat_lck.release());
+        std::unique_ptr<TiXmlElement> xml_stat_hp(new TiXmlElement("stat"));
+        xml_stat_hp->SetAttribute("id", 6);
+        xml_stat_hp->SetAttribute("name", "hp");
+        xml_stat_hp->SetAttribute("value", savemap_.characters[c].hp);
+        xml_stat_hp->SetAttribute("base", savemap_.characters[c].base_hp);
+        // TODO: MAX HP always as 0xFF, must be calculated manually.
+        xml_stat_hp->SetAttribute("max", savemap_.characters[c].max_hp);
+        xml_stats->LinkEndChild(xml_stat_hp.release());
+        std::unique_ptr<TiXmlElement> xml_stat_mp(new TiXmlElement("stat"));
+        xml_stat_mp->SetAttribute("id", 6);
+        xml_stat_mp->SetAttribute("name", "mp");
+        xml_stat_mp->SetAttribute("value", savemap_.characters[c].mp);
+        xml_stat_mp->SetAttribute("base", savemap_.characters[c].base_mp);
+        // TODO: MAX MP always as 0xFF, must be calculated manually.
+        xml_stat_mp->SetAttribute("max", savemap_.characters[c].max_mp);
+        xml_stats->LinkEndChild(xml_stat_mp.release());
+        xml_character->LinkEndChild(xml_stats.release());
+        // Character limits
+        std::unique_ptr<TiXmlElement> xml_limits(new TiXmlElement("limits"));
+        xml_limits->SetAttribute("selected", savemap_.characters[c].limit_level);
+        xml_limits->SetAttribute("bar", savemap_.characters[c].limit_bar);
+        for (int l = 0; l < 4; l ++){
+            if (savemap_.characters[c].limits_learned[l][0]){
+                std::unique_ptr<TiXmlElement> xml_limit_level(new TiXmlElement("level"));
+                xml_limit_level->SetAttribute("id", l + 1);
+                std::unique_ptr<TiXmlElement> xml_limit_technique(new TiXmlElement("technique"));
+                xml_limit_technique->SetAttribute("id", 1);
+                if (l < 3)
+                    xml_limit_technique->SetAttribute("uses", savemap_.characters[c].limit_uses[l]);
+                else xml_limit_technique->SetAttribute("uses", 0);
+                xml_limit_level->LinkEndChild(xml_limit_technique.release());
+                if (savemap_.characters[c].limits_learned[l][1]){
+                    std::unique_ptr<TiXmlElement> xml_limit_technique(
+                      new TiXmlElement("technique")
+                    );
+                    xml_limit_technique->SetAttribute("id", 2);
+                    xml_limit_technique->SetAttribute("uses", 0);
+                    xml_limit_level->LinkEndChild(xml_limit_technique.release());
+                }
+                xml_limits->LinkEndChild(xml_limit_level.release());
             }
-            xml_attack->LinkEndChild(xml_status.release());
         }
-        // Add elements.
-        if (attack.elements.size() > 0){
-            std::unique_ptr<TiXmlElement> xml_elements(new TiXmlElement("elements"));
-            for (int s : attack.elements){
-                std::unique_ptr<TiXmlElement> xml_element(new TiXmlElement("element"));
-                xml_element->SetAttribute("id", s);
-                xml_elements->LinkEndChild(xml_element.release());
+        xml_character->LinkEndChild(xml_limits.release());
+        // Character equipments.
+        std::unique_ptr<TiXmlElement> xml_equipment(new TiXmlElement("equipment"));
+        std::unique_ptr<TiXmlElement> xml_weapon(new TiXmlElement("weapon"));
+        xml_weapon->SetAttribute("id", savemap_.characters[c].weapon);
+        std::unique_ptr<TiXmlElement> xml_weapon_materias(new TiXmlElement("materias"));
+        total_count = 0;
+        for (int m = 0; m < 8; m ++){
+            if (savemap_.characters[c].weapon_materia[m].id < 255){
+                total_count ++;
+                std::unique_ptr<TiXmlElement> xml_weapon_materia(new TiXmlElement("materia"));
+                xml_weapon_materia->SetAttribute("id", savemap_.characters[c].weapon_materia[m].id);
+                xml_weapon_materia->SetAttribute("ap", savemap_.characters[c].weapon_materia[m].ap);
+                xml_weapon_materias->LinkEndChild(xml_weapon_materia.release());
             }
-            xml_attack->LinkEndChild(xml_elements.release());
         }
-
-        container->LinkEndChild(xml_attack.release());
-
+        // Only add materia section to equipment if at least one is equipped, else ignore section.
+        if (total_count > 0) xml_weapon->LinkEndChild(xml_weapon_materias.release());
+        xml_equipment->LinkEndChild(xml_weapon.release());
+        std::unique_ptr<TiXmlElement> xml_armor(new TiXmlElement("armor"));
+        xml_armor->SetAttribute("id", savemap_.characters[c].armor);
+        std::unique_ptr<TiXmlElement> xml_armor_materias(new TiXmlElement("materias"));
+        total_count = 0;
+        for (int m = 0; m < 8; m ++){
+            if (savemap_.characters[c].armor_materia[m].id < 255){
+                total_count ++;
+                std::unique_ptr<TiXmlElement> xml_armor_materia(new TiXmlElement("materia"));
+                xml_armor_materia->SetAttribute("id", savemap_.characters[c].weapon_materia[m].id);
+                xml_armor_materia->SetAttribute("ap", savemap_.characters[c].weapon_materia[m].ap);
+                xml_armor_materias->LinkEndChild(xml_armor_materia.release());
+            }
+        }
+        // Only add materia section to equipment if at least one is equipped, else ignore section.
+        if (total_count > 0) xml_armor->LinkEndChild(xml_armor_materias.release());
+        xml_equipment->LinkEndChild(xml_armor.release());
+        if (savemap_.characters[c].accessory < 255){
+            std::unique_ptr<TiXmlElement> xml_accessory(new TiXmlElement("accessory"));
+            xml_accessory->SetAttribute("id", savemap_.characters[c].accessory);
+            xml_equipment->LinkEndChild(xml_accessory.release());
+        }
+        xml_character->LinkEndChild(xml_equipment.release());
+        // Statuses (Only saddness/fury)
+        if (savemap_.characters[c].fury || savemap_.characters[c].sadness){
+            std::unique_ptr<TiXmlElement> xml_statuses(new TiXmlElement("statuses"));
+            if (savemap_.characters[c].fury){
+                std::unique_ptr<TiXmlElement> xml_status(new TiXmlElement("status"));
+                xml_status->SetAttribute("id", FURY);
+                xml_statuses->LinkEndChild(xml_status.release());
+            }
+            if (savemap_.characters[c].sadness){
+                std::unique_ptr<TiXmlElement> xml_status(new TiXmlElement("status"));
+                xml_status->SetAttribute("id", SADNESS);
+                xml_statuses->LinkEndChild(xml_status.release());
+            }
+            xml_character->LinkEndChild(xml_statuses.release());
+        }
+        xml_characters->LinkEndChild(xml_character.release());
     }
+    container->LinkEndChild(xml_characters.release());
     xml.LinkEndChild(container.release());
     xml.SaveFile(file);
 }

+ 41 - 29
V-Gears-Installer/src/MainWindow.cpp

@@ -13,7 +13,6 @@
  * GNU General Public License for more details.
  */
 
-
 #include <iostream>
 #include <QtCore/QProcess>
 #include <QtWidgets/QFileDialog>
@@ -40,13 +39,10 @@ MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), main_window_(new U
     main_window_->btn_vgears_config->setText(settings_->value("ConfigDir").toString());
     main_window_->line_data_dst->setText(settings_->value("DataDir").toString());
     main_window_->line_vgears_exe->setText(settings_->value("VGearsEXE").toString());
-#ifdef _DEBUG
-    // Hard coded prebaked paths for debugging to save time
-    //main_window_->line_data_src->setText("C:\\Games\\FF7\\data");
-    //main_window_->line_data_dst->setText(
-    //  "C:\\Users\\paul\\Desktop\\v-gears\\output\\data"
-    //);
-#endif
+    // TODO" Hard coded paths for debugging to save time. Remove them.
+    main_window_->line_data_src->setText("/home/ivalentin/data/");
+    main_window_->line_exe_src->setText("/home/ivalentin/data/ff7.exe");
+    //main_window_->line_data_dst->setText("/home/ivalentin/.v-gears/data/");
     timer_ = new QTimer(this);
     connect(timer_, SIGNAL(timeout()), this, SLOT(DoProgress()));
 }
@@ -136,18 +132,26 @@ void MainWindow::on_btn_vgears_run_clicked(){
 
 void MainWindow::on_btn_data_src_clicked(){
     QString temp = QFileDialog::getExistingDirectory(
-      this, tr("Location of Game Data),"),QDir::homePath()
+      this, tr("Location of extracted original game data"), QDir::homePath()
     );
     main_window_->line_data_src->setText(temp);
 }
 
+void MainWindow::on_btn_exe_src_clicked(){
+    QString temp = QFileDialog::getOpenFileName(
+      this, tr("Location of original executable (ff7.exe)"),
+      QDir::homePath(), "PC executable (ff7.exe)", 0
+    );
+    main_window_->line_exe_src->setText(temp);
+}
+
 void MainWindow::on_line_data_dst_editingFinished(){
-    settings_->setValue("DataDir",main_window_->line_data_dst->text());
+    settings_->setValue("DataDir", main_window_->line_data_dst->text());
 }
 
 void MainWindow::on_btn_data_dst_clicked(){
     QString temp = QFileDialog::getExistingDirectory(
-      this, tr("Location of VGears Data),"), settings_->value("DataDir").toString()
+      this, tr("V-Gears data installation directory"), settings_->value("DataDir").toString()
     );
     if (!temp.isNull()){
         settings_->setValue("DataDir",temp);
@@ -157,42 +161,48 @@ void MainWindow::on_btn_data_dst_clicked(){
 
 void MainWindow::on_btn_data_run_clicked(){
     if (main_window_->line_data_src->text().isEmpty()){
-        // TODO IVV: Default path, remove
-        main_window_->line_data_src->setText("/home/ivalentin/data/");
+        QMessageBox::critical(
+          this, tr("Input error"),
+          tr(
+            "The installation needs the original game data.\n\n"
+            "Select a directory with the extracted data from Final Fantasy VII "
+            "(PC version, install disk)."
+          )
+        );
     }
-    //{
-    //    QMessageBox::critical(
-    //      this, tr("Input error"),
-    //      tr("No input to installed FF7 PC data provided")
-    //    );
-    //}
     else if (main_window_->line_data_dst->text().isEmpty())
         QMessageBox::critical(this, tr("Output error"), tr("No output path provided"));
     else{
         // Normalize the paths so its in / format separators.
         QString input = QDir::fromNativeSeparators(main_window_->line_data_src->text());
         if (!input.endsWith("/")) input += "/";
+        QString input_exe = QDir::fromNativeSeparators(main_window_->line_exe_src->text());
         QString output = QDir::fromNativeSeparators(main_window_->line_data_dst->text());
         if (!output.endsWith("/")) output += "/";
         // TODO: Enumerate files or find some better way to do this.
-        const std::vector<std::string> required_files = {"field/char.lgp", "field/flevel.lgp"};
+        const std::vector<std::string> required_files = {
+          "field/char.lgp", "field/flevel.lgp", "kernel/KERNEL.BIN"
+        };
         // Ensure required files are in the input dir
         for (auto& file : required_files){
             QString full_path = input + QString::fromStdString(file);
             if (!QFile::exists(full_path)){
                 QMessageBox::critical(
-                  this, tr("Missing input file"), tr("File not found: ") + full_path);
+                  this, tr("Missing input file"),
+                  tr(
+                    "Some of the required files were not found among the extracted data.\n\n"
+                    " Make sure that the file '"
+                  ) + file.c_str() + tr("' is in the extracted data.")
+                );
                 return;
             }
         }
         if (installer_created){
+            // Due to the use of singletons, the installer can't be re-run.
+            // TODO: Verify that this is true, and if so, try to fix it.
             QMessageBox::critical(
-              this,
-              tr("Error"),
-              tr(
-                "Due to use of singletons install function can only be used "
-                "once, please restart the application."
-              )
+              this, tr("Error"),
+              tr("Please, fix the errors, and then close the installer before trying again.")
             );
             return;
         }
@@ -201,9 +211,11 @@ void MainWindow::on_btn_data_run_clicked(){
             installer_created = true;
             installer_ = std::make_unique<DataInstaller>(
               QDir::toNativeSeparators(input).toStdString(),
+              QDir::toNativeSeparators(input_exe).toStdString(),
               QDir::toNativeSeparators(output).toStdString(),
-              [this](const std::string outputLine){
-                main_window_->data_log->append(outputLine.c_str());
+              [this](const std::string log_line){main_window_->data_log->append(log_line.c_str());},
+              [this](const std::string progress){
+                main_window_->label_progress->setText(progress.c_str());
               }
             );
             OnInstallStarted();

+ 199 - 3
V-Gears-Installer/src/MainWindow.ui

@@ -32,8 +32,23 @@
          <layout class="QHBoxLayout" name="horizontalLayout_4">
           <item>
            <widget class="QLabel" name="label_4">
+            <property name="minimumSize">
+             <size>
+              <width>200</width>
+              <height>0</height>
+             </size>
+            </property>
+            <property name="maximumSize">
+             <size>
+              <width>200</width>
+              <height>50</height>
+             </size>
+            </property>
             <property name="text">
-             <string>Import Data Source:</string>
+             <string>Original FFVII extracted data:</string>
+            </property>
+            <property name="alignment">
+             <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
             </property>
            </widget>
           </item>
@@ -53,14 +68,115 @@
             </property>
            </widget>
           </item>
+          <item>
+           <widget class="QPushButton" name="help_data_src">
+            <property name="toolTip">
+             <string>
+              &lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;b&gt;Path to the extracted FFVII (PC version) data.&lt;/b&gt;&lt;br&gt;&lt;br&gt;It must contain (at least) the folders 'kernel' and 'field'.&lt;/body&gt;&lt;/html&gt;
+             </string>
+            </property>
+            <property name="text">
+             <string/>
+            </property>
+            <property name="icon">
+             <iconset theme="dialog-information">
+              <normaloff>.</normaloff>.</iconset>
+            </property>
+            <property name="checkable">
+             <bool>false</bool>
+            </property>
+            <property name="flat">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </item>
+        <item>
+         <layout class="QHBoxLayout" name="horizontalLayout_5">
+          <item>
+           <widget class="QLabel" name="label_14">
+            <property name="minimumSize">
+             <size>
+              <width>200</width>
+              <height>0</height>
+             </size>
+            </property>
+            <property name="maximumSize">
+             <size>
+              <width>200</width>
+              <height>50</height>
+             </size>
+            </property>
+            <property name="text">
+             <string>Original FFVII executable:</string>
+            </property>
+            <property name="alignment">
+             <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QLineEdit" name="line_exe_src"/>
+          </item>
+          <item>
+           <widget class="QPushButton" name="btn_exe_src">
+            <property name="maximumSize">
+             <size>
+              <width>24</width>
+              <height>24</height>
+             </size>
+            </property>
+            <property name="text">
+             <string>...</string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QPushButton" name="help_exe_src">
+            <property name="toolTip">
+             <string>
+              &lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;b&gt;Path to the FFVII (PC version) ff7.exe.&lt;/b&gt;&lt;br&gt;&lt;br&gt;It's not mandatory, but if not set, prices for all items and materiain shops will be set to 50 gil.&lt;/body&gt;&lt;/html&gt;
+             </string>
+            </property>
+            <property name="text">
+             <string/>
+            </property>
+            <property name="icon">
+             <iconset theme="dialog-information">
+              <normaloff>.</normaloff>.</iconset>
+            </property>
+            <property name="checkable">
+             <bool>false</bool>
+            </property>
+            <property name="flat">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
          </layout>
         </item>
         <item>
          <layout class="QHBoxLayout" name="horizontalLayout_3">
           <item>
            <widget class="QLabel" name="label_3">
+            <property name="minimumSize">
+             <size>
+              <width>200</width>
+              <height>0</height>
+             </size>
+            </property>
+            <property name="maximumSize">
+             <size>
+              <width>200</width>
+              <height>50</height>
+             </size>
+            </property>
             <property name="text">
-             <string>VGears Data Path:</string>
+             <string>VGears installation directory:</string>
+            </property>
+            <property name="alignment">
+             <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
             </property>
            </widget>
           </item>
@@ -80,6 +196,28 @@
             </property>
            </widget>
           </item>
+          <item>
+           <widget class="QPushButton" name="help_data_dest">
+            <property name="toolTip">
+             <string>
+              &lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;b&gt;V-Gears data installation directory.&lt;/b&gt;&lt;br&gt;&lt;br&gt;The data will be installed to this location. Existing installations will be overwritten.&lt;/body&gt;&lt;/html&gt;
+             </string>
+            </property>
+            <property name="text">
+             <string/>
+            </property>
+            <property name="icon">
+             <iconset theme="dialog-information">
+              <normaloff>.</normaloff>.</iconset>
+            </property>
+            <property name="checkable">
+             <bool>false</bool>
+            </property>
+            <property name="flat">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
          </layout>
         </item>
         <item>
@@ -92,15 +230,73 @@
           </property>
          </widget>
         </item>
+        <item>
+         <widget class="QLabel" name="label_progress">
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+            <horstretch>1</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+          <property name="minimumSize">
+           <size>
+            <width>0</width>
+            <height>0</height>
+           </size>
+          </property>
+          <property name="maximumSize">
+           <size>
+            <width>16777215</width>
+            <height>10</height>
+           </size>
+          </property>
+          <property name="font">
+           <font>
+            <pointsize>8</pointsize>
+            <italic>true</italic>
+           </font>
+          </property>
+          <property name="text">
+           <string/>
+          </property>
+          <property name="alignment">
+           <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+          </property>
+         </widget>
+        </item>
         <item>
          <widget class="QLabel" name="label_5">
+          <property name="maximumSize">
+           <size>
+            <width>16777215</width>
+            <height>15</height>
+           </size>
+          </property>
+          <property name="font">
+           <font>
+            <family>Courier</family>
+            <pointsize>8</pointsize>
+           </font>
+          </property>
           <property name="text">
-           <string>Output</string>
+           <string>Installer log:</string>
+          </property>
+          <property name="margin">
+           <number>-1</number>
           </property>
          </widget>
         </item>
         <item>
          <widget class="QTextEdit" name="data_log">
+          <property name="font">
+           <font>
+            <family>Courier</family>
+            <pointsize>8</pointsize>
+           </font>
+          </property>
+          <property name="verticalScrollBarPolicy">
+           <enum>Qt::ScrollBarAlwaysOn</enum>
+          </property>
           <property name="undoRedoEnabled">
            <bool>false</bool>
           </property>

+ 3 - 3
V-Gears-Installer/src/decompiler/Value.cpp

@@ -135,7 +135,7 @@ std::ostream &DupValue::Print(std::ostream &output) const{return output << "temp
 
 StringValue::StringValue(std::string str): str_(str){}
 
-std::ostream &StringValue::Print(std::ostream &output) const{
+std::ostream &StringValue::Print(std::ostream& output) const{
     return output << "\"" << str_ << "\"";
 }
 
@@ -145,9 +145,9 @@ std::ostream& UnquotedStringValue::Print(std::ostream& output) const{return outp
 
 VarValue::VarValue(std::string name): name_(name){}
 
-std::ostream &VarValue::Print(std::ostream &output) const{return output << name_;}
+std::ostream &VarValue::Print(std::ostream& output) const{return output << name_;}
 
-ArrayValue::ArrayValue(const std::string name, const ValueList indexes):
+ArrayValue::ArrayValue(const std::string& name, const ValueList& indexes):
   VarValue(name), indexes_(indexes){}
 
 std::ostream &ArrayValue::Print(std::ostream &output) const {