mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-10-31 22:26:41 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			521 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			521 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "unshieldthread.hpp"
 | |
| 
 | |
| #include <fstream>
 | |
| #include <components/misc/stringops.hpp>
 | |
|    
 | |
| namespace bfs = boost::filesystem;
 | |
| 
 | |
| namespace 
 | |
| {        
 | |
|     static bool make_sure_directory_exists(bfs::path directory)
 | |
|     {
 | |
| 
 | |
|         if(!bfs::exists(directory))
 | |
|         {
 | |
|             bfs::create_directories(directory);
 | |
|         }
 | |
| 
 | |
|         return bfs::exists(directory);
 | |
|     }
 | |
| 
 | |
|     void fill_path(bfs::path& path, const std::string& name)
 | |
|     {
 | |
|         size_t start = 0;
 | |
|         
 | |
|         size_t i;
 | |
|         for(i = 0; i < name.length(); i++)
 | |
|         {
 | |
|             switch(name[i])
 | |
|             {
 | |
|                 case '\\':
 | |
|                     path /= name.substr(start, i-start);
 | |
|                     start = i+1;
 | |
|                     break;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         path /= name.substr(start, i-start);
 | |
|     }
 | |
| 
 | |
|     std::string get_setting(const std::string& category, const std::string& setting, const std::string& inx)
 | |
|     {
 | |
|         size_t start = inx.find(category);
 | |
|         start = inx.find(setting, start) + setting.length() + 3;
 | |
| 
 | |
|         size_t end = inx.find("!", start);
 | |
| 
 | |
|         return inx.substr(start, end-start);
 | |
|     }
 | |
| 
 | |
|     std::string read_to_string(const bfs::path& path)
 | |
|     {
 | |
|         std::ifstream strstream(path.c_str(), std::ios::in | std::ios::binary);
 | |
|         std::string str;
 | |
| 
 | |
|         strstream.seekg(0, std::ios::end);
 | |
|         str.resize(strstream.tellg());
 | |
|         strstream.seekg(0, std::ios::beg);
 | |
|         strstream.read(&str[0], str.size());
 | |
|         strstream.close();
 | |
| 
 | |
|         return str;
 | |
|     }
 | |
| 
 | |
|     void add_setting(const std::string& category, const std::string& setting, const std::string& val, std::string& ini)
 | |
|     {                                                                                       
 | |
|         size_t loc;
 | |
|         loc = ini.find("[" + category + "]");
 | |
|         
 | |
|         // If category is not found, create it 
 | |
|         if(loc == std::string::npos)
 | |
|         {
 | |
|             loc = ini.size() + 2;
 | |
|             ini += ("\r\n[" + category + "]\r\n");
 | |
|         }
 | |
|           
 | |
|         loc += category.length() +2 +2;
 | |
|         ini.insert(loc, setting + "=" + val + "\r\n");
 | |
|     }
 | |
| 
 | |
|     #define FIX(setting) add_setting(category, setting, get_setting(category, setting, inx), ini)
 | |
| 
 | |
|     void bloodmoon_fix_ini(std::string& ini, const bfs::path inxPath)
 | |
|     {
 | |
|         std::string inx = read_to_string(inxPath);
 | |
| 
 | |
|         // Remove this one setting (the only one actually changed by bloodmoon, as opposed to just adding new ones)
 | |
|         size_t start = ini.find("[Weather Blight]");
 | |
|         start = ini.find("Ambient Loop Sound ID", start);
 | |
|         size_t end = ini.find("\r\n", start) +2;
 | |
|         ini.erase(start, end-start);
 | |
| 
 | |
|         std::string category;
 | |
| 
 | |
|         category = "General";
 | |
|         {
 | |
|             FIX("Werewolf FOV");
 | |
|         }
 | |
|         category = "Moons";
 | |
|         {
 | |
|             FIX("Script Color");
 | |
|         }
 | |
|         category = "Weather";
 | |
|         {
 | |
|             FIX("Snow Ripples");
 | |
|             FIX("Snow Ripple Radius");
 | |
|             FIX("Snow Ripples Per Flake");
 | |
|             FIX("Snow Ripple Scale");
 | |
|             FIX("Snow Ripple Speed");
 | |
|             FIX("Snow Gravity Scale");
 | |
|             FIX("Snow High Kill");
 | |
|             FIX("Snow Low Kill");
 | |
|         }
 | |
|         category = "Weather Blight";
 | |
|         {
 | |
|             FIX("Ambient Loop Sound ID");
 | |
|         }
 | |
|         category = "Weather Snow";
 | |
|         {
 | |
|             FIX("Sky Sunrise Color");
 | |
|             FIX("Sky Day Color");
 | |
|             FIX("Sky Sunset Color");
 | |
|             FIX("Sky Night Color");
 | |
|             FIX("Fog Sunrise Color");
 | |
|             FIX("Fog Day Color");
 | |
|             FIX("Fog Sunset Color");
 | |
|             FIX("Fog Night Color");
 | |
|             FIX("Ambient Sunrise Color");
 | |
|             FIX("Ambient Day Color");
 | |
|             FIX("Ambient Sunset Color");
 | |
|             FIX("Ambient Night Color");
 | |
|             FIX("Sun Sunrise Color");
 | |
|             FIX("Sun Day Color");
 | |
|             FIX("Sun Sunset Color");
 | |
|             FIX("Sun Night Color");
 | |
|             FIX("Sun Disc Sunset Color");
 | |
|             FIX("Transition Delta");
 | |
|             FIX("Land Fog Day Depth");
 | |
|             FIX("Land Fog Night Depth");
 | |
|             FIX("Clouds Maximum Percent");
 | |
|             FIX("Wind Speed");
 | |
|             FIX("Cloud Speed");
 | |
|             FIX("Glare View");
 | |
|             FIX("Cloud Texture");
 | |
|             FIX("Ambient Loop Sound ID");
 | |
|             FIX("Snow Threshold");
 | |
|             FIX("Snow Diameter");
 | |
|             FIX("Snow Height Min");
 | |
|             FIX("Snow Height Max");
 | |
|             FIX("Snow Entrance Speed");
 | |
|             FIX("Max Snowflakes");
 | |
|         }
 | |
|         category = "Weather Blizzard";
 | |
|         {
 | |
|             FIX("Sky Sunrise Color");
 | |
|             FIX("Sky Day Color");
 | |
|             FIX("Sky Sunset Color");
 | |
|             FIX("Sky Night Color");
 | |
|             FIX("Fog Sunrise Color");
 | |
|             FIX("Fog Day Color");
 | |
|             FIX("Fog Sunset Color");
 | |
|             FIX("Fog Night Color");
 | |
|             FIX("Ambient Sunrise Color");
 | |
|             FIX("Ambient Day Color");
 | |
|             FIX("Ambient Sunset Color");
 | |
|             FIX("Ambient Night Color");
 | |
|             FIX("Sun Sunrise Color");
 | |
|             FIX("Sun Day Color");
 | |
|             FIX("Sun Sunset Color");
 | |
|             FIX("Sun Night Color");
 | |
|             FIX("Sun Disc Sunset Color");
 | |
|             FIX("Transition Delta");
 | |
|             FIX("Land Fog Day Depth");
 | |
|             FIX("Land Fog Night Depth");
 | |
|             FIX("Clouds Maximum Percent");
 | |
|             FIX("Wind Speed");
 | |
|             FIX("Cloud Speed");
 | |
|             FIX("Glare View");
 | |
|             FIX("Cloud Texture");
 | |
|             FIX("Ambient Loop Sound ID");
 | |
|             FIX("Storm Threshold");
 | |
|         }
 | |
|     }
 | |
| 
 | |
| 
 | |
|     void fix_ini(const bfs::path& output_dir, bfs::path cdPath, bool tribunal, bool bloodmoon)
 | |
|     {
 | |
|         bfs::path ini_path = output_dir;
 | |
|         ini_path /= "Morrowind.ini";
 | |
| 
 | |
|         std::string ini = read_to_string(ini_path.string());
 | |
| 
 | |
|         if(tribunal)
 | |
|         {
 | |
|             add_setting("Game Files", "GameFile1", "Tribunal.esm", ini);
 | |
|             add_setting("Archives", "Archive 0", "Tribunal.bsa", ini);
 | |
|         }
 | |
|         if(bloodmoon)
 | |
|         {
 | |
|             bloodmoon_fix_ini(ini, cdPath / "setup.inx");
 | |
|             add_setting("Game Files", "GameFile2", "Bloodmoon.esm", ini);
 | |
|             add_setting("Archives", "Archive 1", "Bloodmoon.bsa", ini);
 | |
|         }
 | |
| 
 | |
|         std::ofstream inistream(ini_path.c_str());
 | |
|         inistream << ini;
 | |
|         inistream.close();
 | |
|     }
 | |
| 
 | |
|     void installToPath(const bfs::path& from, const bfs::path& to, bool copy = false)
 | |
|     {
 | |
|         make_sure_directory_exists(to);
 | |
| 
 | |
|         for ( bfs::directory_iterator end, dir(from); dir != end; ++dir )
 | |
|         {
 | |
|             if(bfs::is_directory(dir->path()))
 | |
|                 installToPath(dir->path(), to / dir->path().filename(), copy);
 | |
|             else
 | |
|             {
 | |
|                 if(copy)
 | |
|                 {
 | |
|                     bfs::path dest = to / dir->path().filename();
 | |
|                     if(bfs::exists(dest))
 | |
|                         bfs::remove_all(dest);
 | |
|                     bfs::copy_file(dir->path(), dest);
 | |
|                 }
 | |
|                 else
 | |
|                     bfs::rename(dir->path(), to / dir->path().filename());
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     bfs::path findFile(const bfs::path& in, std::string filename, bool recursive = true)
 | |
|     {
 | |
|         if(recursive)
 | |
|         {
 | |
|             for ( bfs::recursive_directory_iterator end, dir(in); dir != end; ++dir )
 | |
|             {
 | |
|                 if(Misc::StringUtils::ciEqual(dir->path().filename().string(), filename))
 | |
|                     return dir->path();
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             for ( bfs::directory_iterator end, dir(in); dir != end; ++dir )
 | |
|             {
 | |
|                 if(Misc::StringUtils::ciEqual(dir->path().filename().string(), filename))
 | |
|                     return dir->path();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return "";
 | |
|     }
 | |
| 
 | |
|     bool contains(const bfs::path& in, std::string filename)
 | |
|     {
 | |
|         for(bfs::directory_iterator end, dir(in); dir != end; ++dir)
 | |
|         {
 | |
|             if(Misc::StringUtils::ciEqual(dir->path().filename().string(), filename))
 | |
|                 return true;
 | |
|         }
 | |
| 
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     time_t getTime(const char* time)
 | |
|     {
 | |
|         struct tm tms;
 | |
|         memset(&tms, 0, sizeof(struct tm));
 | |
|         strptime(time, "%d %B %Y", &tms);
 | |
|         return mktime(&tms);
 | |
|     }
 | |
|     
 | |
|     // Some cds have cab files which have the Data Files subfolders outside the Data Files folder
 | |
|     void install_dfiles_outside(const bfs::path& from, const bfs::path& dFiles)
 | |
|     {
 | |
|         bfs::path fonts = findFile(from, "fonts", false);
 | |
|         if(fonts.string() != "")
 | |
|             installToPath(fonts, dFiles / "Fonts"); 
 | |
| 
 | |
|         bfs::path music = findFile(from, "music", false);
 | |
|         if(music.string() != "")
 | |
|             installToPath(music, dFiles / "Music"); 
 | |
| 
 | |
|         bfs::path sound = findFile(from, "sound", false);
 | |
|         if(sound.string() != "")
 | |
|             installToPath(sound, dFiles / "Sound"); 
 | |
|     
 | |
|         bfs::path splash = findFile(from, "splash", false);
 | |
|         if(splash.string() != "")
 | |
|             installToPath(splash, dFiles / "Splash"); 
 | |
|     }
 | |
| 
 | |
| }
 | |
| 
 | |
| bool Launcher::UnshieldThread::SetMorrowindPath(const std::string& path)
 | |
| {
 | |
|     mMorrowindPath = path;
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| bool Launcher::UnshieldThread::SetTribunalPath(const std::string& path)
 | |
| {
 | |
|     mTribunalPath = path;
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| bool Launcher::UnshieldThread::SetBloodmoonPath(const std::string& path)
 | |
| {
 | |
|     mBloodmoonPath = path;
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| void Launcher::UnshieldThread::SetOutputPath(const std::string& path)
 | |
| {
 | |
|     mOutputPath = path;
 | |
| }
 | |
| 
 | |
| bool Launcher::UnshieldThread::extract_file(Unshield* unshield, bfs::path output_dir, const char* prefix, int index)
 | |
| {
 | |
|     bool success;
 | |
|     bfs::path dirname;
 | |
|     bfs::path filename;
 | |
|     int directory = unshield_file_directory(unshield, index);
 | |
| 
 | |
|     dirname = output_dir;
 | |
| 
 | |
|     if (prefix && prefix[0])
 | |
|         dirname /= prefix;
 | |
| 
 | |
|     if (directory >= 0)
 | |
|     {
 | |
|         const char* tmp = unshield_directory_name(unshield, directory);
 | |
|         if (tmp && tmp[0])
 | |
|             fill_path(dirname, tmp);
 | |
|     }
 | |
| 
 | |
|     make_sure_directory_exists(dirname);
 | |
| 
 | |
|     filename = dirname;
 | |
|     filename /= unshield_file_name(unshield, index);
 | |
|   
 | |
|     emit signalGUI(QString("Extracting: ") + QString(filename.c_str()));
 | |
|   
 | |
|     success = unshield_file_save(unshield, index, filename.c_str());
 | |
| 
 | |
|     if (!success)
 | |
|         bfs::remove(filename);
 | |
| 
 | |
|     return success;
 | |
| }
 | |
| 
 | |
| void Launcher::UnshieldThread::extract_cab(const bfs::path& cab, const bfs::path& output_dir, bool extract_ini)
 | |
| {
 | |
|     Unshield * unshield;
 | |
|     unshield = unshield_open(cab.c_str());
 | |
| 
 | |
|     int i;
 | |
|     for (i = 0; i < unshield_file_group_count(unshield); i++)
 | |
|     {
 | |
|         UnshieldFileGroup* file_group = unshield_file_group_get(unshield, i);
 | |
| 
 | |
|         for (size_t j = file_group->first_file; j <= file_group->last_file; j++)
 | |
|         {
 | |
|             if (unshield_file_is_valid(unshield, j)) 
 | |
|                     extract_file(unshield, output_dir, file_group->name, j);
 | |
|         }
 | |
|     }
 | |
|     unshield_close(unshield);
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Launcher::UnshieldThread::extract()
 | |
| {
 | |
|     bfs::path outputDataFilesDir = mOutputPath;
 | |
|     outputDataFilesDir /= "Data Files";
 | |
|     bfs::path extractPath = mOutputPath;
 | |
|     extractPath /= "extract-temp";
 | |
| 
 | |
|     if(!mMorrowindDone && mMorrowindPath.string().length() > 0)
 | |
|     {
 | |
|         mMorrowindDone = true;
 | |
| 
 | |
|         bfs::path mwExtractPath = extractPath / "morrowind";
 | |
|         extract_cab(mMorrowindPath, mwExtractPath, true);
 | |
|         
 | |
|         bfs::path dFilesDir = findFile(mwExtractPath, "morrowind.esm").parent_path();
 | |
|         
 | |
|         installToPath(dFilesDir, outputDataFilesDir); 
 | |
|         
 | |
|         install_dfiles_outside(mwExtractPath, outputDataFilesDir);
 | |
|         
 | |
|         // Videos are often kept uncompressed on the cd
 | |
|         bfs::path videosPath = findFile(mMorrowindPath.parent_path(), "video", false);
 | |
|         if(videosPath.string() != "")
 | |
|         {
 | |
|             emit signalGUI(QString("Installing Videos..."));
 | |
|             installToPath(videosPath, outputDataFilesDir / "Video", true);
 | |
|         }
 | |
| 
 | |
|         bfs::path cdDFiles = findFile(mMorrowindPath.parent_path(), "data files", false);
 | |
|         if(cdDFiles.string() != "")
 | |
|         {
 | |
|             emit signalGUI(QString("Installing Uncompressed Data files from CD..."));
 | |
|             installToPath(cdDFiles, outputDataFilesDir, true);
 | |
|         }
 | |
| 
 | |
| 
 | |
|         bfs::rename(findFile(mwExtractPath, "morrowind.ini"), outputDataFilesDir / "Morrowind.ini");
 | |
| 
 | |
|         mTribunalDone = contains(outputDataFilesDir, "tribunal.esm");
 | |
|         mBloodmoonDone = contains(outputDataFilesDir, "bloodmoon.esm");
 | |
| 
 | |
|     }
 | |
| 
 | |
|     else if(!mTribunalDone && mTribunalPath.string().length() > 0)
 | |
|     {
 | |
|         mTribunalDone = true;
 | |
| 
 | |
|         bfs::path tbExtractPath = extractPath / "tribunal";
 | |
|         extract_cab(mTribunalPath, tbExtractPath, true);
 | |
|         
 | |
|         bfs::path dFilesDir = findFile(tbExtractPath, "tribunal.esm").parent_path();
 | |
|         
 | |
|         installToPath(dFilesDir, outputDataFilesDir); 
 | |
|         
 | |
|         install_dfiles_outside(tbExtractPath, outputDataFilesDir);
 | |
|         
 | |
|         // Mt GOTY CD has Sounds in a seperate folder from the rest of the data files
 | |
|         bfs::path soundsPath = findFile(tbExtractPath, "sounds", false);
 | |
|         if(soundsPath.string() != "")
 | |
|             installToPath(soundsPath, outputDataFilesDir / "Sounds");
 | |
| 
 | |
|         bfs::path cdDFiles = findFile(mTribunalPath.parent_path(), "data files", false);
 | |
|         if(cdDFiles.string() != "")
 | |
|         {
 | |
|             emit signalGUI(QString("Installing Uncompressed Data files from CD..."));
 | |
|             installToPath(cdDFiles, outputDataFilesDir, true);
 | |
|         }
 | |
| 
 | |
|         mBloodmoonDone = contains(outputDataFilesDir, "bloodmoon.esm");
 | |
| 
 | |
|         fix_ini(outputDataFilesDir, bfs::path(mTribunalPath).parent_path(), mTribunalDone, mBloodmoonDone);
 | |
|     }
 | |
| 
 | |
|     else if(!mBloodmoonDone && mBloodmoonPath.string().length() > 0)
 | |
|     {
 | |
|         mBloodmoonDone = true;
 | |
| 
 | |
|         bfs::path bmExtractPath = extractPath / "bloodmoon";
 | |
|         extract_cab(mBloodmoonPath, bmExtractPath, true);
 | |
| 
 | |
|         bfs::path dFilesDir = findFile(bmExtractPath, "bloodmoon.esm").parent_path();
 | |
|         
 | |
|         installToPath(dFilesDir, outputDataFilesDir); 
 | |
|         
 | |
|         install_dfiles_outside(bmExtractPath, outputDataFilesDir);
 | |
| 
 | |
|         // My GOTY CD contains a folder within cab files called Tribunal patch,
 | |
|         // which contains Tribunal.esm
 | |
|         bfs::path tbPatchPath = findFile(bmExtractPath, "tribunal.esm");
 | |
|         if(tbPatchPath.string() != "")
 | |
|             bfs::rename(tbPatchPath, outputDataFilesDir / "Tribunal.esm");
 | |
| 
 | |
|         bfs::path cdDFiles = findFile(mBloodmoonPath.parent_path(), "data files", false);
 | |
|         if(cdDFiles.string() != "")
 | |
|         {
 | |
|             emit signalGUI(QString("Installing Uncompressed Data files from CD..."));
 | |
|             installToPath(cdDFiles, outputDataFilesDir, true);
 | |
|         }
 | |
|         
 | |
|         fix_ini(outputDataFilesDir, bfs::path(mBloodmoonPath).parent_path(), false, mBloodmoonDone);
 | |
|     }
 | |
| 
 | |
|  
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| void Launcher::UnshieldThread::Done()
 | |
| {
 | |
|     // Get rid of unnecessary files
 | |
|     bfs::remove_all(mOutputPath / "extract-temp");
 | |
| 
 | |
|     // Set modified time to release dates, to preserve load order
 | |
|     if(mMorrowindDone)
 | |
|         bfs::last_write_time(findFile(mOutputPath, "morrowind.esm"), getTime("1 May 2002"));
 | |
| 
 | |
|     if(mTribunalDone)
 | |
|         bfs::last_write_time(findFile(mOutputPath, "tribunal.esm"), getTime("6 November 2002"));
 | |
| 
 | |
|     if(mBloodmoonDone)
 | |
|         bfs::last_write_time(findFile(mOutputPath, "bloodmoon.esm"), getTime("3 June 2003"));
 | |
| }
 | |
| 
 | |
| std::string Launcher::UnshieldThread::GetMWEsmPath()
 | |
| {
 | |
|     return findFile(mOutputPath / "Data Files", "morrowind.esm").string();
 | |
| }
 | |
| 
 | |
| bool Launcher::UnshieldThread::TribunalDone()
 | |
| {
 | |
|     return mTribunalDone;
 | |
| }
 | |
| 
 | |
| bool Launcher::UnshieldThread::BloodmoonDone()
 | |
| {
 | |
|     return mBloodmoonDone;
 | |
| }
 | |
| 
 | |
| void Launcher::UnshieldThread::run()
 | |
| {
 | |
|     extract();
 | |
|     emit close();
 | |
| }
 | |
| 
 | |
| Launcher::UnshieldThread::UnshieldThread()
 | |
| {
 | |
|     unshield_set_log_level(0);
 | |
|     mMorrowindDone = false;
 | |
|     mTribunalDone = false;
 | |
|     mBloodmoonDone = false;
 | |
| }
 |