Ivan Arkhipov 4 năm trước cách đây
mục cha
commit
238bbcd7e9

+ 24 - 0
.gitignore

@@ -0,0 +1,24 @@
+# cmake build directory
+cmake-build*
+cmake-build*
+# ide project directory
+.idea
+
+# Compiled Object files
+*.slo
+*.lo
+*.o
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+
+# Build and stuff dirs
+build*/*
+debug/*
+stuff/*
+debug_build/*
+release_build/*
+bin/*
+install/*
+lib/*

+ 5 - 5
CMakeLists.txt

@@ -5,7 +5,7 @@ set(CMAKE_CXX_STANDARD 14)
 set(PROJECT_BINARY_DIR bin)
 set(PROJECT_VERSION 8.0.0)
 # SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GCC_COVERAGE_COMPILE_FLAGS} -Wall -Wextra -O2")
-SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GCC_COVERAGE_COMPILE_FLAGS} -Wall -Wextra")
+SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GCC_COVERAGE_COMPILE_FLAGS} -Wall -Wextra -O0")
 set(CMAKE_CXX_FLAGS_RELEASE "-O0")
 
 set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)
@@ -62,11 +62,11 @@ add_library(LotroDat_static STATIC ${SOURCE_FILES} ${HEADER_FILES})
 # SHARED LIBRARY
 add_library(LotroDat SHARED ${SOURCE_FILES} ${HEADER_FILES})
 # CONSOLE EXTRACTOR TOOL
-add_executable(LotRO_dat_single_file_import_export ${CMAKE_SOURCE_DIR}/src/utils/single_file_import_export.cpp ${SOURCE_FILES} ${HEADER_FILES})
-add_executable(LotRO_dat_patcher ${CMAKE_SOURCE_DIR}/src/utils/single_file_import_export.cpp ${SOURCE_FILES} ${HEADER_FILES})
-add_executable(LotRO_dat_extractor ${CMAKE_SOURCE_DIR}/src/utils/single_file_import_export.cpp ${SOURCE_FILES} ${HEADER_FILES})
+add_executable(single_file_import_export ${CMAKE_SOURCE_DIR}/src/utils/single_file_import_export.cpp ${SOURCE_FILES} ${HEADER_FILES})
+add_executable(extractor ${CMAKE_SOURCE_DIR}/src/utils/extractor.cpp ${SOURCE_FILES} ${HEADER_FILES})
+add_executable(inserter ${CMAKE_SOURCE_DIR}/src/utils/inserter.cpp ${SOURCE_FILES} ${HEADER_FILES})
 
-foreach (TARGET LotroDat_static LotroDat LotRO_dat_test)
+foreach (TARGET LotroDat_static LotroDat single_file_import_export extractor inserter)
     target_link_libraries(${TARGET} optimized ${CMAKE_SOURCE_DIR}/Third_Party/lib/libyaml-cpp.a
                                     debug ${CMAKE_SOURCE_DIR}/Third_Party/lib/libyaml-cppd.a)
 

+ 28 - 4
include/datfile.h

@@ -3,32 +3,56 @@
 #include "subfile.h"
 
 #include <string>
+#include <functional>
 
 
 namespace LOTRO_DAT {
 
+struct SubfileInfo {
+	int file_id;
+	int size;
+	int iteration;
+
+	friend bool operator<(const SubfileInfo& info1, const SubfileInfo& info2) {
+		return info1.file_id < info2.file_id;
+	}
+};
+
+class DatFile;
+
+typedef std::function<void (const SubfileInfo&)> SubfileOperation;
+
 class DatFile {
   public:
   	DatFile(int file_handle);
   	~DatFile();
   	bool Init(const std::string& filename);
+	void LoadAllFilesInfo();
   	void Deinit();
 
+	bool Initialized() const;
+	const std::string& GetFilename() const;
 	size_t GetFilesNumInDatFile();
 
   	size_t PatchAllFilesFromDatabase(Database& db);
 	void PatchFile(SubfileData file_data);
   	void PatchFile(int file_id, FILE_TYPE type, std::string path_to_file);
 
-  	void ExportFilesByTypeToDatabase(FILE_TYPE type, Database& db);
-  	void ExportFilesByTypeToDirectory(FILE_TYPE type, std::string path_to_directory);
-  	void ExportFileById(int file_id, std::string target_file_path);
-  	void ExportFileByIdToDatabase(int file_id, Database& db);
+	FILE_TYPE GetExistingFileType(int file_id);
+	void PerformOperationOnAllSubfiles(const SubfileOperation& operation);
+
+  	int ExportFilesByType(FILE_TYPE type, Database& db);
+  	int ExportFilesByType(FILE_TYPE type, std::string path_to_directory);
+  	
+  	void ExportFileById(int file_id, Database& db);
+	void ExportFileById(int file_id, std::string target_file_path);
 
   private:
   	static DatExportApi api_;
   	int file_handle_;
   	bool initialized_;
+	std::set<SubfileInfo> files_info_;
+	std::string filename_;
 
   	BinaryData export_data_buf_;
 };

BIN
lib/libLotroDat.dll.a


BIN
lib/libLotroDat_static.a


+ 1 - 1
src/datexportapi.cpp

@@ -74,7 +74,7 @@ int DatExportApi::PurgeSubfileData(int handle, int file_id) {
 }
 
 int DatExportApi::PutSubfileData(int handle, int file_id, void* data, int offset, int size, int version, int iteration, bool compress) {
-  return put_subfile_data_func_(handle, file_id, data, offset, size, version, iteration, 0);
+  return put_subfile_data_func_(handle, file_id, data, offset, size, version, iteration, compress);
 }
 
 void DatExportApi::Flush(int handle) {

+ 80 - 7
src/datfile.cpp

@@ -47,18 +47,37 @@ DatFile::~DatFile() {
 bool DatFile::Init(const std::string& filename) {
     if (api_.OpenDatFile(file_handle_, filename.c_str(), 130) == file_handle_) {
         initialized_ = true;
+        filename_ = filename;
         return true;  
     }
 
     return false;
 }
 
+void DatFile::LoadAllFilesInfo() {
+    int subfiles_num = api_.GetNumSubfiles(file_handle_);
+    for (int i = 0; i < subfiles_num; ++i) {
+        SubfileInfo file_info;
+        api_.GetSubfileSizes(file_handle_, &file_info.file_id, &file_info.size, &file_info.iteration, i, 1);
+        files_info_.insert(file_info);
+    }
+}
+
 void DatFile::Deinit() {
     if (initialized_) {
         api_.CloseDatFile(file_handle_);
     }
 }
 
+bool DatFile::Initialized() const{
+    return initialized_;
+}
+
+const std::string& DatFile::GetFilename() const {
+    return filename_;
+}
+
+
 size_t DatFile::GetFilesNumInDatFile() {
     return api_.GetNumSubfiles(file_handle_);
 }
@@ -67,21 +86,34 @@ size_t DatFile::PatchAllFilesFromDatabase(Database& db) {
     size_t patched_files_num = 0;
 
     SubfileData file;
+    int i = 0;
+    const int total_files = db.CountRows();
+    std::cout << "Patching all files from database..." << std::endl;
     while (!(file = db.GetNextFile()).Empty()) {
+        if (i * 100 / total_files != (i - 1) * 100 / total_files) {
+            std::cout << "Completed " << i * 100 / total_files << "%" << std::endl;
+        }
+        ++i;
+
         if (!file.options["fid"]) {
             LOG(ERROR) << "Incorrect db entry - no file_id specified";
             continue;
         }
 
         PatchFile(file);
+        ++patched_files_num;
     }
-    return 0;
+    return patched_files_num;
 }
 
 void DatFile::PatchFile(SubfileData file_data) {
     int file_id = file_data.options["fid"].as<int>();
     int version = 0;
     int size = api_.GetSubfileData(file_handle_, file_id, export_data_buf_.data(), version);
+    if (size <= 0) {
+        LOG(ERROR) << "Trying to patch file, not existing in .dat file. File id = " << file_id;
+        return;
+    }
     BinaryData old_data = export_data_buf_.CutData(0, size);  
 
     BinaryData file = BuildForImport(old_data, file_data);
@@ -103,31 +135,72 @@ void DatFile::PatchFile(int file_id, FILE_TYPE type, std::string path_to_file) {
     PatchFile(imported_subfile);
 }
 
-void DatFile::ExportFilesByTypeToDatabase(FILE_TYPE, Database&) {
+FILE_TYPE DatFile::GetExistingFileType(int file_id) {
+    int version = 0;
+    api_.GetSubfileData(file_handle_, file_id, export_data_buf_.data(), version);
+    return FileTypeFromFileContents(file_id, export_data_buf_);
+}
 
+void DatFile::PerformOperationOnAllSubfiles(const SubfileOperation& operation) {
+    if (files_info_.empty()) {
+        LoadAllFilesInfo();
+    }
+    std::cout << "Performing operation on all files...";
+    int i = 0;
+    for (const SubfileInfo& info : files_info_) {
+        if (i * 100 / files_info_.size() != (i - 1) * 100 / files_info_.size()) {
+            std::cout << "Completed " << i * 100 / files_info_.size() << "%" << std::endl;
+        }
+        operation(info);
+        ++i;
+    }
 }
 
-void DatFile::ExportFilesByTypeToDirectory(FILE_TYPE, std::string) {
 
+int DatFile::ExportFilesByType(FILE_TYPE type, Database& db) {
+    int num_files = 0;
+    SubfileOperation operation = [this, type, &db, &num_files](const SubfileInfo& info) {
+        FILE_TYPE file_type = GetExistingFileType(info.file_id);
+        if (file_type == type) {
+            ExportFileById(info.file_id, db);
+            ++num_files;
+        }
+    };
+
+    PerformOperationOnAllSubfiles(operation);
+    return num_files;
+}
+
+int DatFile::ExportFilesByType(FILE_TYPE type, std::string path_to_directory) {
+    int num_files = 0;
+    SubfileOperation operation = [this, type, path_to_directory, &num_files](const SubfileInfo& info) {
+        FILE_TYPE file_type = GetExistingFileType(info.file_id);
+        if (file_type == type) {
+            ExportFileById(info.file_id, path_to_directory + "/" + std::to_string(info.file_id));
+            ++num_files;
+        }
+    };
+
+    PerformOperationOnAllSubfiles(operation);
+    return num_files;
 }
 
 void DatFile::ExportFileById(int file_id, std::string target_file_path) {
     int version = 0;
     int size = api_.GetSubfileData(file_handle_, file_id, export_data_buf_.data(), version);
     auto data = export_data_buf_.CutData(0, size);
-    std::cerr << "Exporting file with id = " << file_id << " and size = " << size << std::endl;
+    FILE_TYPE file_type = FileTypeFromFileContents(file_id, data);
     SubfileData file = BuildForExport(file_id, data);
 
-    std::ofstream out(target_file_path, std::ofstream::binary);
+    std::ofstream out(target_file_path + StringFromFileType(file_type), std::ofstream::binary);
     out.write((char*)file.binary_data.data(), file.binary_data.size());
     out.close();
 }
 
-void DatFile::ExportFileByIdToDatabase(int file_id, Database& db) {
+void DatFile::ExportFileById(int file_id, Database& db) {
     int version = 0;
     int size = api_.GetSubfileData(file_handle_, file_id, export_data_buf_.data(), version);
     auto data = export_data_buf_.CutData(0, size);
-    std::cerr << "Exporting file with id = " << file_id << " and size = " << size << std::endl;
     SubfileData file = BuildForExport(file_id, data);
 
     db.PushFile(file);

+ 18 - 9
src/subfile.cpp

@@ -32,12 +32,12 @@ FILE_TYPE FileTypeFromFileContents(int file_id, const BinaryData& file_data) {
     // jpeg / dds check
     if ((file_id >> 24) == 0x41) {
         long long soi = file_data.ToNumber<2>(24);
-        long long marker = file_data.ToNumber<2>(26);
+        // long long marker = file_data.ToNumber<2>(26);
 
         //auto markerSize = header.ToNumber<short>(28);
         //auto four = header.ToNumber<int>(30);
 
-        if ((soi == 0xD8FF && marker == 0xE0FF) || marker == 0xE1FF)
+        if (soi == 0xD8FF)
             return JPG;
         return DDS;
     }
@@ -96,23 +96,32 @@ BinaryData BuildForImport(const BinaryData& old_data, const SubfileData& outer_d
 
 SubfileData BuildForExport(int file_id, const BinaryData& inner_data) {
     FILE_TYPE type = FileTypeFromFileContents(file_id, inner_data);
+    SubfileData result;
     switch (type)
     {
     case TEXT:
-        return Subfiles::Subfile<TEXT>::BuildForExport(inner_data);
+        result = Subfiles::Subfile<TEXT>::BuildForExport(inner_data);
+        break;
     case JPG:
-        return Subfiles::Subfile<JPG>::BuildForExport(inner_data);
+        result = Subfiles::Subfile<JPG>::BuildForExport(inner_data);
+        break;
     case DDS:
-        return Subfiles::Subfile<DDS>::BuildForExport(inner_data);
+        result = Subfiles::Subfile<DDS>::BuildForExport(inner_data);
+        break;
     case WAV:
-        return Subfiles::Subfile<WAV>::BuildForExport(inner_data);
+        result = Subfiles::Subfile<WAV>::BuildForExport(inner_data);
+        break;
     case OGG:
-        return Subfiles::Subfile<OGG>::BuildForExport(inner_data);
+        result = Subfiles::Subfile<OGG>::BuildForExport(inner_data);
+        break;
     case FONT:
-        return Subfiles::Subfile<FONT>::BuildForExport(inner_data);
+        result = Subfiles::Subfile<FONT>::BuildForExport(inner_data);
+        break;
     default:
-        return Subfiles::Subfile<UNKNOWN>::BuildForExport(inner_data);
+        result = Subfiles::Subfile<UNKNOWN>::BuildForExport(inner_data);
     }
+    result.options["fid"] = file_id;
+    return result;
 }
 
 }; // namespace LOTRO_DAT

+ 1 - 1
src/subfiles/ddssubfile.cpp

@@ -122,7 +122,7 @@ SubfileData Subfile<DDS>::BuildForExport(const BinaryData &file_data) {
 
 BinaryData Subfile<DDS>::BuildForImport(const BinaryData &old_data, const SubfileData &data) {
     BinaryData file_size = BinaryData::FromNumber<4>(data.binary_data.size() - 128);
-    return old_data.CutData(0, 28) + file_size + data.binary_data.CutData(128);
+    return old_data.CutData(0, 20) + file_size + data.binary_data.CutData(128);
 }
 
 }; // namespace Subfiles

+ 1 - 1
src/subfiles/fontsubfile.cpp

@@ -13,7 +13,7 @@ SubfileData Subfile<FONT>::BuildForExport(const BinaryData &file_data) {
 }
 
 BinaryData Subfile<FONT>::BuildForImport(const BinaryData &old_data, const SubfileData &data) {
-    return old_data.CutData(0, 16) + data.binary_data.CutData(8);
+    return old_data.CutData(0, 8) + data.binary_data.CutData(8);
 }
 
 }; // namespace Subfiles

+ 1 - 1
src/subfiles/jpgsubfile.cpp

@@ -14,7 +14,7 @@ SubfileData Subfile<JPG>::BuildForExport(const BinaryData &file_data) {
 
 BinaryData Subfile<JPG>::BuildForImport(const BinaryData &old_data, const SubfileData &data) {
     BinaryData file_size = BinaryData::FromNumber<4>(data.binary_data.size());
-    return old_data.CutData(0, 28) + file_size + data.binary_data;
+    return old_data.CutData(0, 20) + file_size + data.binary_data;
 }
 
 }; // namespace Subfiles

+ 1 - 1
src/subfiles/oggsubfile.cpp

@@ -16,7 +16,7 @@ BinaryData Subfile<OGG>::BuildForImport(const BinaryData &old_data, const Subfil
     BinaryData file_size = BinaryData::FromNumber<4>(data.binary_data.size() - 8);
     BinaryData file_id = BinaryData::FromNumber<4>(data.options["fid"].as<int>());
 
-    return old_data.CutData(0, 8) + file_id + file_size + data.binary_data;
+    return file_id + file_size + data.binary_data;
 }
 
 }; // namespace Subfiles

+ 3 - 2
src/subfiles/textsubfile.cpp

@@ -73,7 +73,8 @@ SubfileData Subfile<TEXT>::BuildForExport(const BinaryData &file_data) {
         if (!arg_refs.empty())
             arguments += to_utf16(arg_refs[arg_refs.size() - 1]);
 
-        //auto arg_strings = MakeArgumentStrings(file_data, offset);
+        // Through argument strings are not used, we need to call this function to correctly move offset
+        MakeArgumentStrings(file_data, offset);
 
         if (result.text_data.length() > 0)
             result.text_data += u"|||";
@@ -89,7 +90,7 @@ SubfileData Subfile<TEXT>::BuildForExport(const BinaryData &file_data) {
 
 BinaryData Subfile<TEXT>::BuildForImport(const BinaryData &old_data, const SubfileData &data) {
     BinaryData new_file_data;
-    long long offset = 9 + 8; // first 8 bytes - file_info. After them:
+    long long offset = 9; // first 8 bytes - file_info. After them:
                               // first 4 bytes - file_id, then 4 bytes - unknown, then 1 byte - unknown
     
 

+ 1 - 1
src/subfiles/wavsubfile.cpp

@@ -13,7 +13,7 @@ SubfileData Subfile<WAV>::BuildForExport(const BinaryData& file_data) {
 }
 
 BinaryData Subfile<WAV>::BuildForImport(const BinaryData& old_data, const SubfileData& data) {
-    return old_data.CutData(0, 24) + data.binary_data;
+    return old_data.CutData(0, 16) + data.binary_data;
 }
 
 }; // namespace Subfiles

+ 302 - 0
src/utils/extractor.cpp

@@ -0,0 +1,302 @@
+#include <iostream>
+#include <fstream>
+#include <ctime>
+#include <algorithm>
+
+#ifdef _WIN32
+#include <direct.h>
+#define mkdir(dir, mode) _mkdir(dir)
+#endif
+
+#include "datfile.h"
+using namespace LOTRO_DAT;
+
+// Change these variables to true if you want export category to files.
+bool exportTextsToFiles = false;
+bool exportImagesToFiles = false;
+bool exportFontsToFiles = false;
+bool exportSoundsToFiles = false;
+bool exportTexturesToFiles = false;
+bool exportUnknownToFiles = false;
+
+bool exportTextsToDb = false;
+bool exportImagesToDb = false;
+bool exportFontsToDb = false;
+bool exportSoundsToDb = false;
+bool exportTexturesToDb = false;
+bool exportUnknownToDb = false;
+
+void InitializeFileLoop(DatFile& file) {
+    while (!file.Initialized()) {
+        std::cout << "Please, tell, where the .dat file is\n";
+        std::cout << "Enter path to file (including filename): ";
+        std::string filename;
+        std::getline(std::cin, filename);
+
+        // std::cout << "Enter file id: ";
+        // std::string file_id_str;
+        // std::getline(std::cin, file_id_str);
+        // int file_id = 0;
+        // try {
+        //     file_id = stoi(file_id_str);
+        // } catch (std::invalid_argument) {
+        //     std::cout << "Invalid dat file id entered!\n\n";
+        //     continue;
+        // }
+
+        std::cout << "Opening file " << filename << std::endl;
+
+        bool result = file.Init(filename); // TODO: INIT WITH DAT FILE ID
+        if (!result) {
+            std::cout << "Dat initialisation failed.\nPlease enter path to .dat file once more\n";
+        }
+    }
+}
+
+int main() {
+    std::cout.precision(1);
+    std::cout << std::fixed;
+    std::cout << "Endevir's LotRO .dat extractor ver. 1.0.0" << std::endl;
+
+    std::cout << "Hello! I'm a basic shell version of .dat file extractor. I can open .dat file directly, "
+            "if you write path to it (with name of file) in file \"dat_file_path.txt\"\n";
+    
+    DatFile file(0);
+
+    std::ifstream in_stream("dat_file_path.txt");
+    if (!in_stream.fail()) {
+        std::string filename;
+        std::getline(in_stream, filename);
+
+        // std::string file_id_str;
+        // std::getline(in_stream, file_id_str);
+        // int file_id = 0;
+        // try {
+        //     file_id = stoi(file_id_str);
+        // } catch (std::invalid_argument) {
+        //     file_id = -1;
+        //     std::cout << "Invalid file_id on second line of file dat_file_path.txt!\n\n";
+        // }
+
+        // if (file_id != -1) {
+            std::cout << "Using .dat file from dat_file_path.txt...\n";
+            std::cout << "Opening file " << filename << std::endl;
+            bool result = file.Init(filename);
+            if (!result) {
+                std::cout << "Dat initialisation failed!\n";
+            }
+        // }
+    }
+
+    InitializeFileLoop(file);
+    std::cout << "Great! File initialised successfully!\n";
+    while (true) {
+        std::cout << "Please, choose, what should I do. I can extract .dat file to files (press 1), "
+                    "open another .dat file (press 2), or configure, what should I extract. Choose, what to do or exit (press -1)\n";
+
+        std::cout << "Enter number of command (1-3): ";
+        std::string command;
+        std::getline(std::cin, command);
+
+        int cmd = -10;
+        try {
+            cmd = stoi(command);
+        } catch (std::invalid_argument) {
+            std::cout << "Invalid command entered!\n\n";
+            continue;
+        }
+
+        if (cmd == -1) {
+            std::cout << "Exiting. Thanks for using me!\n";
+            break;
+        }
+
+        if (cmd == 1) {
+            const clock_t begin_time = clock();
+
+            mkdir("Extracted data", 744);
+
+            std::time_t result = std::time(nullptr);
+            char *ttime = std::asctime(std::localtime(&result));
+            auto *out_time = new char[25];
+            memcpy(out_time, ttime, 24);
+            out_time[24] = '\0';
+
+            std::string filename = file.GetFilename();
+            std::cout << "FILENAME = " << filename << std::endl;
+            std::string output_dir = std::string("Extracted data\\") + filename.substr(std::max(filename.length() - 16, 0u), 16) + " " + std::string(out_time) + "\\";
+            std::replace(output_dir.begin(), output_dir.end(), ':', '-');
+
+            mkdir(output_dir.c_str(), 744);
+            
+            std::cout << "Beginning unpacking... Please, wait for some minutes."
+                    "\nMaybe it's a good idea to have a cup of tea, while unpacker is working...\n" << std::flush;
+
+            Database output_db;
+
+            if (exportImagesToDb) {
+                output_db.InitDatabase(output_dir + std::string("Images.db"));
+                int extracted_jpg_files_num = file.ExportFilesByType(JPG, output_db);
+                std::cout << "Extracted " << extracted_jpg_files_num << " .jpg files to Images.db" << std::endl << std::flush;
+                output_db.CloseDatabase();
+            }
+
+            if (exportSoundsToDb) {
+                output_db.InitDatabase(output_dir + std::string("Sounds.db"));
+                int extracted_wav_files_num = file.ExportFilesByType(WAV, output_db);
+                int extracted_ogg_files_num = file.ExportFilesByType(OGG, output_db);
+                std::cout << "Extracted " << extracted_wav_files_num << " .wav files to Sounds.db" << std::endl << std::flush;
+                std::cout << "Extracted " << extracted_ogg_files_num << " .ogg files to Sounds.db" << std::endl << std::flush;
+                output_db.CloseDatabase();
+            }
+
+            if (exportTextsToDb) {
+                output_db.InitDatabase(output_dir + std::string("Texts.db"));
+                int extracted_text_files_num = file.ExportFilesByType(TEXT, output_db);
+                std::cout << "Extracted " << extracted_text_files_num << " text files to Texts.db" << std::endl << std::flush;
+                output_db.CloseDatabase();
+            }
+
+            if (exportFontsToDb) {
+                output_db.InitDatabase(output_dir + std::string("Fonts.db"));
+                int extracted_font_files_num = file.ExportFilesByType(FONT, output_db);
+                std::cout << "Extracted " << extracted_font_files_num << " font files to Fonts.db" << std::endl << std::flush;
+                output_db.CloseDatabase();
+            }
+
+            if (exportTexturesToDb) {
+                output_db.InitDatabase(output_dir + std::string("Textures.db"));
+                int extracted_dds_files_num = file.ExportFilesByType(DDS, output_db);
+                std::cout << "Extracted " << extracted_dds_files_num << " .dds files to Textures.db" << std::endl << std::flush;
+                output_db.CloseDatabase();
+            }
+
+            if (exportImagesToFiles) {
+                mkdir((output_dir + "jpg").c_str(), 744);
+                int extracted_jpg_files_num = file.ExportFilesByType(JPG, output_dir + "jpg\\");
+                std::cout << "Extracted " << extracted_jpg_files_num << " .jpg files to directory" << std::endl << std::flush;
+            }
+
+            if (exportTexturesToFiles) {
+                mkdir((output_dir + "dds").c_str(), 744);
+                int extracted_dds_files_num = file.ExportFilesByType(DDS, output_dir + "dds\\");
+                std::cout << "Extracted " << extracted_dds_files_num << " .dds files to directory" << std::endl << std::flush;
+            }
+
+            if (exportSoundsToFiles) {
+                mkdir((output_dir + "wav").c_str(), 744);
+                int extracted_wav_files_num = file.ExportFilesByType(WAV, output_dir + "wav\\");
+                std::cout << "Extracted " << extracted_wav_files_num << " .wav files to directory" << std::endl << std::flush;
+                mkdir((output_dir + "ogg").c_str(), 744);
+                int extracted_ogg_files_num = file.ExportFilesByType(OGG, output_dir + "ogg\\");
+                std::cout << "Extracted " << extracted_ogg_files_num << " .ogg files to directory" << std::endl << std::flush;
+            }
+
+            if (exportFontsToFiles) {
+                mkdir((output_dir + "fonts").c_str(), 744);
+                int extracted_font_files_num = file.ExportFilesByType(FONT, output_dir + "fonts\\");
+                std::cout << "Extracted " << extracted_font_files_num << " font files to directory" << std::endl << std::flush;
+            }
+
+            if (exportUnknownToFiles) {
+                mkdir((output_dir + "unknown").c_str(), 744);
+                int extracted_unknown_files_num = file.ExportFilesByType(UNKNOWN, output_dir + "unknown\\");
+                std::cout << "Extracted " << extracted_unknown_files_num << " unknown files to directory" << std::endl << std::flush;
+            }
+
+            if (exportTextsToFiles) {
+                mkdir((output_dir + "texts").c_str(), 744);
+                int extracted_text_files_num = file.ExportFilesByType(TEXT, output_dir + "texts\\");
+                std::cout << "Extracted " << extracted_text_files_num << " text files to directory" << std::endl << std::flush;
+            }
+
+            fprintf(stdout, "Spent %f seconds on running unpacker! Thank you for your patience!\n",
+                    float(clock() - begin_time) / CLOCKS_PER_SEC);
+        }
+
+        if (cmd == 2) {
+            std::cout << "Closing file...\n";
+            file.Deinit();
+            InitializeFileLoop(file);
+        }
+
+        if (cmd == 3) {
+            std::cout << "Current parameters:\n";
+            std::cout << "(01) Export texts to files - " << (exportTextsToFiles ? "true\n" : "false\n");
+            std::cout << "(02) Export texts to database - " << (exportTextsToDb ? "true\n" : "false\n");
+
+            std::cout << "(11) Export images to files - " << (exportImagesToFiles ? "true\n" : "false\n");
+            std::cout << "(12) Export images to database - " << (exportImagesToDb ? "true\n" : "false\n");
+
+            std::cout << "(21) Export fonts to files - " << (exportFontsToFiles ? "true\n" : "false\n");
+            std::cout << "(22) Export fonts to database - " << (exportFontsToDb ? "true\n" : "false\n");
+
+            std::cout << "(31) Export sounds to files - " << (exportSoundsToFiles ? "true\n" : "false\n");
+            std::cout << "(32) Export sounds to database - " << (exportSoundsToDb ? "true\n" : "false\n");
+
+            std::cout << "(41) Export textures to files - " << (exportTexturesToFiles ? "true\n" : "false\n");
+            std::cout << "(42) Export textures to database - " << (exportTexturesToDb ? "true\n" : "false\n");
+
+            std::cout << "(51) Export unknown files to files - " << (exportUnknownToFiles ? "true\n" : "false\n");
+            std::cout << "(52) Export unknown files to database - " << (exportUnknownToDb ? "true\n" : "false\n");
+
+            std::cout << "Enter number of parameter to change (or -1 to exit this menu): ";
+
+            int number = 0;
+
+            std::cin >> number;
+            std::string tmp;
+            std::getline(std::cin, tmp);
+
+            if (number == -1) {
+                std::cout << "Returning to main menu..\n";
+                cmd = 0;
+            } else {
+                switch (number) {
+                    case 01:
+                        exportTextsToFiles = !exportTextsToFiles;
+                        break;
+                    case 02:
+                        exportTextsToDb = !exportTextsToDb;
+                        break;
+                    case 11:
+                        exportImagesToFiles = !exportImagesToFiles;
+                        break;
+                    case 12:
+                        exportImagesToDb = !exportImagesToDb;
+                        break;
+                    case 21:
+                        exportFontsToFiles = !exportFontsToFiles;
+                        break;
+                    case 22:
+                        exportFontsToDb = !exportFontsToDb;
+                        break;
+                    case 31:
+                        exportSoundsToFiles = !exportSoundsToFiles;
+                        break;
+                    case 32:
+                        exportSoundsToDb = !exportSoundsToDb;
+                        break;
+                    case 41:
+                        exportTexturesToFiles = !exportTexturesToFiles;
+                        break;
+                    case 42:
+                        exportTexturesToDb = !exportTexturesToDb;
+                        break;
+                    case 51:
+                        exportUnknownToFiles = !exportTexturesToFiles;
+                        break;
+                    case 52:
+                        exportUnknownToDb = !exportTexturesToDb;
+                        break;
+                    default:
+                        std::cout << "Incorrect number. Returning to main menu..\n";
+                }
+            }
+        }
+    }
+
+    file.Deinit();
+    system("pause");
+    return 0;
+}

+ 155 - 0
src/utils/inserter.cpp

@@ -0,0 +1,155 @@
+#include <iostream>
+#include <fstream>
+#include <ctime>
+#include <algorithm>
+
+#ifdef WIN32
+#include <direct.h>
+#define mkdir(dir, mode) _mkdir(dir)
+#endif
+
+#include "datfile.h"
+using namespace LOTRO_DAT;
+
+void InitializeFileLoop(DatFile& file) {
+    while (!file.Initialized()) {
+        std::cout << "Please, tell, where the .dat file is\n";
+        std::cout << "Enter path to file (including filename): ";
+        std::string filename;
+        std::getline(std::cin, filename);
+
+        // std::cout << "Enter file id: ";
+        // std::string file_id_str;
+        // std::getline(std::cin, file_id_str);
+        // int file_id = 0;
+        // try {
+        //     file_id = stoi(file_id_str);
+        // } catch (std::invalid_argument) {
+        //     std::cout << "Invalid dat file id entered!\n\n";
+        //     continue;
+        // }
+
+        std::cout << "Opening file " << filename << std::endl;
+
+        bool result = file.Init(filename); // TODO: INIT WITH DAT FILE ID
+        if (!result) {
+            std::cout << "Dat initialisation failed.\nPlease enter path to .dat file once more\n";
+        }
+    }
+}
+
+int main() {
+    std::cout.precision(1);
+    std::cout << std::fixed;
+    std::cout << "Endevir's LotRO .dat patcher ver. 1.0.0" << std::endl;
+    freopen("patcher_errors.log", "w", stderr);
+
+    setbuf(stdout, nullptr);
+    setbuf(stderr, nullptr);
+
+    setvbuf (stdout, nullptr, _IONBF, BUFSIZ);
+    setvbuf (stderr, nullptr, _IONBF, BUFSIZ);
+
+    std::cout << "Hello! I'm a basic shell version of .dat file patcher. I can open .dat file directly, "
+            "if you write path to it (with name of file) in file \"dat_file_path.txt\"\n";
+    
+    DatFile file(0);
+
+    std::ifstream in("dat_file_path.txt");
+    if (!in.fail()) {
+        std::string filename;
+        std::getline(in, filename);
+
+        std::string file_id_str;
+        std::getline(in, file_id_str);
+        int file_id = 0;
+        try {
+            file_id = stoi(file_id_str);
+        } catch (std::invalid_argument) {
+            file_id = -1;
+            std::cout << "Invalid file_id on second line of file dat_file_path.txt!\n\n";
+        }
+
+        if (file_id != -1) {
+            std::cout << "Using .dat file from dat_file_path.txt...\n";
+            std::cout << "Opening file " << filename << " with id = " << file_id << std::endl;
+            bool result = file.Init(filename);
+            if (!result) {
+                std::cout << "Dat initialisation failed.\n";
+            }
+        }
+    }
+
+    InitializeFileLoop(file);
+    std::cout << "Great! File initialised successfully!\n";
+
+    while (true) {
+        std::cout << "Please, choose, what should I do. I can patch datfile from database to .dat file (enter 1) or exit (enter -1)\n";
+
+        std::cout << "Enter number of command (1-10): ";
+        std::string command;
+        std::getline(std::cin, command);
+        int cmd = -10;
+        try {
+            cmd = stoi(command);
+        } catch (std::invalid_argument) {
+            std::cout << "Invalid command entered!\n\n";
+            continue;
+        }
+
+        if (cmd == -1) {
+            std::cout << "Exiting. Thanks for using me!\n";
+            file.Deinit();
+            break;
+        }
+
+        if (cmd == 1) {
+            std::cout << "You've chosen to patch database! Write name of database file (it should be in the same "
+                    "directory), of enter 0 to return to main dialogue.\n";
+            while (true) {
+                std::cout << "Enter name of file or 0: ";
+                std::string dbname;
+                std::getline(std::cin, dbname);
+
+                if (dbname == std::to_string(0)) {
+                    std::cout << "Okay, returning back...\n\n";
+                    break;
+                }
+
+                Database db;
+
+                std::cout << "Opening database... " << dbname << std::endl;
+
+                if (!db.InitDatabase(dbname)) {
+                    std::cout << "Unfortunately, I cannot open this database. Could you try again please?\n";
+                    continue;
+                };
+
+                if (db.CountRows() == 0) {
+                    std::cout << "There are no files in database or database doesn't exist. "
+                            "Please, try again!\n";
+                    continue;
+                }
+
+                std::cout << "There are " << db.CountRows() << " files in database." << std::endl;
+
+                std::cout << "Successfully opened database! Beginning patching...\n";
+                
+                const clock_t begin_time = clock();
+
+                file.PatchAllFilesFromDatabase(db);
+
+                db.CloseDatabase();
+
+                fprintf(stdout, "Spent %f seconds on patching! Thank you for your patience!\n",
+                        float(clock() - begin_time) / CLOCKS_PER_SEC);
+
+                std::cout << "Great! File was patched successfully!\n\n";
+                break;
+            }
+        }
+    }
+
+    system("pause");
+    return 0;
+}