Bulk restoration of deleted mod files

This commit is contained in:
pizzaboxer 2024-09-08 01:23:35 +01:00
parent 1bdf761d07
commit 2795ccf92e
No known key found for this signature in database
GPG Key ID: 59D4A1DBAD0F2BA8
2 changed files with 152 additions and 136 deletions

View File

@ -48,7 +48,7 @@ namespace Bloxstrap
private long _totalDownloadedBytes = 0; private long _totalDownloadedBytes = 0;
private bool _mustUpgrade => File.Exists(AppData.LockFilePath) || !File.Exists(AppData.ExecutablePath); private bool _mustUpgrade => File.Exists(AppData.LockFilePath) || !File.Exists(AppData.ExecutablePath);
private bool _skipUpgrade = false; private bool _noConnection = false;
public IBootstrapperDialog? Dialog = null; public IBootstrapperDialog? Dialog = null;
@ -165,10 +165,11 @@ namespace Bloxstrap
await GetLatestVersionInfo(); await GetLatestVersionInfo();
// install/update roblox if we're running for the first time, needs updating, or the player location doesn't exist // install/update roblox if we're running for the first time, needs updating, or the player location doesn't exist
if (!_skipUpgrade && (AppData.State.VersionGuid != _latestVersionGuid || _mustUpgrade)) if (!_noConnection && (AppData.State.VersionGuid != _latestVersionGuid || _mustUpgrade))
await UpgradeRoblox(); await UpgradeRoblox();
//await ApplyModifications(); if (!_noConnection)
await ApplyModifications();
// check if launch uri is set to our bootstrapper // check if launch uri is set to our bootstrapper
// this doesn't go under register, so we check every launch // this doesn't go under register, so we check every launch
@ -729,144 +730,164 @@ namespace Bloxstrap
_isInstalling = false; _isInstalling = false;
} }
//private async Task ApplyModifications() private async Task ApplyModifications()
//{ {
// const string LOG_IDENT = "Bootstrapper::ApplyModifications"; const string LOG_IDENT = "Bootstrapper::ApplyModifications";
// if (Process.GetProcessesByName(AppData.ExecutableName[..^4]).Any()) SetStatus(Strings.Bootstrapper_Status_ApplyingModifications);
// {
// App.Logger.WriteLine(LOG_IDENT, "Roblox is running, aborting mod check");
// return;
// }
// SetStatus(Strings.Bootstrapper_Status_ApplyingModifications); // handle file mods
App.Logger.WriteLine(LOG_IDENT, "Checking file mods...");
// // handle file mods // manifest has been moved to State.json
// App.Logger.WriteLine(LOG_IDENT, "Checking file mods..."); File.Delete(Path.Combine(Paths.Base, "ModManifest.txt"));
// // manifest has been moved to State.json List<string> modFolderFiles = new();
// File.Delete(Path.Combine(Paths.Base, "ModManifest.txt"));
// List<string> modFolderFiles = new(); if (!Directory.Exists(Paths.Modifications))
Directory.CreateDirectory(Paths.Modifications);
// if (!Directory.Exists(Paths.Modifications)) // check custom font mod
// Directory.CreateDirectory(Paths.Modifications); // instead of replacing the fonts themselves, we'll just alter the font family manifests
// // check custom font mod string modFontFamiliesFolder = Path.Combine(Paths.Modifications, "content\\fonts\\families");
// // instead of replacing the fonts themselves, we'll just alter the font family manifests
// string modFontFamiliesFolder = Path.Combine(Paths.Modifications, "content\\fonts\\families"); if (File.Exists(Paths.CustomFont))
{
App.Logger.WriteLine(LOG_IDENT, "Begin font check");
// if (File.Exists(Paths.CustomFont)) Directory.CreateDirectory(modFontFamiliesFolder);
// {
// App.Logger.WriteLine(LOG_IDENT, "Begin font check");
// Directory.CreateDirectory(modFontFamiliesFolder); const string path = "rbxasset://fonts/CustomFont.ttf";
// foreach (string jsonFilePath in Directory.GetFiles(Path.Combine(_versionFolder, "content\\fonts\\families"))) foreach (string jsonFilePath in Directory.GetFiles(Path.Combine(AppData.Directory, "content\\fonts\\families")))
// { {
// string jsonFilename = Path.GetFileName(jsonFilePath); string jsonFilename = Path.GetFileName(jsonFilePath);
// string modFilepath = Path.Combine(modFontFamiliesFolder, jsonFilename); string modFilepath = Path.Combine(modFontFamiliesFolder, jsonFilename);
// if (File.Exists(modFilepath)) if (File.Exists(modFilepath))
// continue; continue;
// App.Logger.WriteLine(LOG_IDENT, $"Setting font for {jsonFilename}"); App.Logger.WriteLine(LOG_IDENT, $"Setting font for {jsonFilename}");
// FontFamily? fontFamilyData = JsonSerializer.Deserialize<FontFamily>(File.ReadAllText(jsonFilePath)); var fontFamilyData = JsonSerializer.Deserialize<FontFamily>(File.ReadAllText(jsonFilePath));
// if (fontFamilyData is null) if (fontFamilyData is null)
// continue; continue;
// foreach (FontFace fontFace in fontFamilyData.Faces) bool shouldWrite = false;
// fontFace.AssetId = "rbxasset://fonts/CustomFont.ttf";
// // TODO: writing on every launch is not necessary foreach (var fontFace in fontFamilyData.Faces)
// File.WriteAllText(modFilepath, JsonSerializer.Serialize(fontFamilyData, new JsonSerializerOptions { WriteIndented = true })); {
// } if (fontFace.AssetId != path)
{
fontFace.AssetId = path;
shouldWrite = true;
}
}
// App.Logger.WriteLine(LOG_IDENT, "End font check"); if (shouldWrite)
// } File.WriteAllText(modFilepath, JsonSerializer.Serialize(fontFamilyData, new JsonSerializerOptions { WriteIndented = true }));
// else if (Directory.Exists(modFontFamiliesFolder)) }
// {
// Directory.Delete(modFontFamiliesFolder, true);
// }
// foreach (string file in Directory.GetFiles(Paths.Modifications, "*.*", SearchOption.AllDirectories)) App.Logger.WriteLine(LOG_IDENT, "End font check");
// { }
// // get relative directory path else if (Directory.Exists(modFontFamiliesFolder))
// string relativeFile = file.Substring(Paths.Modifications.Length + 1); {
Directory.Delete(modFontFamiliesFolder, true);
}
// // v1.7.0 - README has been moved to the preferences menu now foreach (string file in Directory.GetFiles(Paths.Modifications, "*.*", SearchOption.AllDirectories))
// if (relativeFile == "README.txt") {
// { // get relative directory path
// File.Delete(file); string relativeFile = file.Substring(Paths.Modifications.Length + 1);
// continue;
// }
// if (!App.Settings.Prop.UseFastFlagManager && String.Equals(relativeFile, "ClientSettings\\ClientAppSettings.json", StringComparison.OrdinalIgnoreCase)) // v1.7.0 - README has been moved to the preferences menu now
// continue; if (relativeFile == "README.txt")
{
File.Delete(file);
continue;
}
// if (relativeFile.EndsWith(".lock")) if (!App.Settings.Prop.UseFastFlagManager && String.Equals(relativeFile, "ClientSettings\\ClientAppSettings.json", StringComparison.OrdinalIgnoreCase))
// continue; continue;
// modFolderFiles.Add(relativeFile); if (relativeFile.EndsWith(".lock"))
continue;
// string fileModFolder = Path.Combine(Paths.Modifications, relativeFile); modFolderFiles.Add(relativeFile);
// string fileVersionFolder = Path.Combine(_versionFolder, relativeFile);
// if (File.Exists(fileVersionFolder) && MD5Hash.FromFile(fileModFolder) == MD5Hash.FromFile(fileVersionFolder)) string fileModFolder = Path.Combine(Paths.Modifications, relativeFile);
// { string fileVersionFolder = Path.Combine(AppData.Directory, relativeFile);
// App.Logger.WriteLine(LOG_IDENT, $"{relativeFile} already exists in the version folder, and is a match");
// continue;
// }
// Directory.CreateDirectory(Path.GetDirectoryName(fileVersionFolder)!); if (File.Exists(fileVersionFolder) && MD5Hash.FromFile(fileModFolder) == MD5Hash.FromFile(fileVersionFolder))
{
App.Logger.WriteLine(LOG_IDENT, $"{relativeFile} already exists in the version folder, and is a match");
continue;
}
// Filesystem.AssertReadOnly(fileVersionFolder); Directory.CreateDirectory(Path.GetDirectoryName(fileVersionFolder)!);
// File.Copy(fileModFolder, fileVersionFolder, true);
// Filesystem.AssertReadOnly(fileVersionFolder);
// App.Logger.WriteLine(LOG_IDENT, $"{relativeFile} has been copied to the version folder"); Filesystem.AssertReadOnly(fileVersionFolder);
// } File.Copy(fileModFolder, fileVersionFolder, true);
Filesystem.AssertReadOnly(fileVersionFolder);
// // the manifest is primarily here to keep track of what files have been App.Logger.WriteLine(LOG_IDENT, $"{relativeFile} has been copied to the version folder");
// // deleted from the modifications folder, so that we know when to restore the original files from the downloaded packages }
// // now check for files that have been deleted from the mod folder according to the manifest
// // TODO: this needs to extract the files from packages in bulk, this is way too slow // the manifest is primarily here to keep track of what files have been
// foreach (string fileLocation in App.State.Prop.ModManifest) // deleted from the modifications folder, so that we know when to restore the original files from the downloaded packages
// { // now check for files that have been deleted from the mod folder according to the manifest
// if (modFolderFiles.Contains(fileLocation))
// continue;
// var package = AppData.PackageDirectoryMap.SingleOrDefault(x => x.Value != "" && fileLocation.StartsWith(x.Value)); var fileRestoreMap = new Dictionary<string, List<string>>();
// // package doesn't exist, likely mistakenly placed file foreach (string fileLocation in App.State.Prop.ModManifest)
// if (String.IsNullOrEmpty(package.Key)) {
// { if (modFolderFiles.Contains(fileLocation))
// App.Logger.WriteLine(LOG_IDENT, $"{fileLocation} was removed as a mod but does not belong to a package"); continue;
// string versionFileLocation = Path.Combine(_versionFolder, fileLocation); var packageMapEntry = AppData.PackageDirectoryMap.SingleOrDefault(x => !String.IsNullOrEmpty(x.Value) && fileLocation.StartsWith(x.Value));
string packageName = packageMapEntry.Key;
// if (File.Exists(versionFileLocation)) // package doesn't exist, likely mistakenly placed file
// File.Delete(versionFileLocation); if (String.IsNullOrEmpty(packageName))
{
App.Logger.WriteLine(LOG_IDENT, $"{fileLocation} was removed as a mod but does not belong to a package");
// continue; string versionFileLocation = Path.Combine(AppData.Directory, fileLocation);
// }
// // restore original file if (File.Exists(versionFileLocation))
// string fileName = fileLocation.Substring(package.Value.Length); File.Delete(versionFileLocation);
// await ExtractFileFromPackage(package.Key, fileName);
// App.Logger.WriteLine(LOG_IDENT, $"{fileLocation} was removed as a mod, restored from {package.Key}"); continue;
// } }
// App.State.Prop.ModManifest = modFolderFiles; string fileName = fileLocation.Substring(packageMapEntry.Value.Length);
// App.State.Save();
// App.Logger.WriteLine(LOG_IDENT, $"Finished checking file mods"); if (!fileRestoreMap.ContainsKey(packageName))
//} fileRestoreMap[packageName] = new();
fileRestoreMap[packageName].Add(fileName);
App.Logger.WriteLine(LOG_IDENT, $"{fileLocation} was removed as a mod, restoring from {packageName}");
}
foreach (var entry in fileRestoreMap)
{
var package = _versionPackageManifest.Find(x => x.Name == entry.Key);
if (package is not null)
{
await DownloadPackage(package);
ExtractPackage(package, entry.Value);
}
}
App.State.Prop.ModManifest = modFolderFiles;
App.State.Save();
App.Logger.WriteLine(LOG_IDENT, $"Finished checking file mods");
}
private async Task DownloadPackage(Package package) private async Task DownloadPackage(Package package)
{ {
@ -876,14 +897,13 @@ namespace Bloxstrap
return; return;
string packageUrl = RobloxDeployment.GetLocation($"/{_latestVersionGuid}-{package.Name}"); string packageUrl = RobloxDeployment.GetLocation($"/{_latestVersionGuid}-{package.Name}");
string packageLocation = Path.Combine(Paths.Downloads, package.Signature);
string robloxPackageLocation = Path.Combine(Paths.LocalAppData, "Roblox", "Downloads", package.Signature); string robloxPackageLocation = Path.Combine(Paths.LocalAppData, "Roblox", "Downloads", package.Signature);
if (File.Exists(packageLocation)) if (File.Exists(package.DownloadPath))
{ {
var file = new FileInfo(packageLocation); var file = new FileInfo(package.DownloadPath);
string calculatedMD5 = MD5Hash.FromFile(packageLocation); string calculatedMD5 = MD5Hash.FromFile(package.DownloadPath);
if (calculatedMD5 != package.Signature) if (calculatedMD5 != package.Signature)
{ {
@ -906,7 +926,7 @@ namespace Bloxstrap
// then we can just copy the one from there // then we can just copy the one from there
App.Logger.WriteLine(LOG_IDENT, $"Found existing copy at '{robloxPackageLocation}'! Copying to Downloads folder..."); App.Logger.WriteLine(LOG_IDENT, $"Found existing copy at '{robloxPackageLocation}'! Copying to Downloads folder...");
File.Copy(robloxPackageLocation, packageLocation); File.Copy(robloxPackageLocation, package.DownloadPath);
_totalDownloadedBytes += package.PackedSize; _totalDownloadedBytes += package.PackedSize;
UpdateProgressBar(); UpdateProgressBar();
@ -914,7 +934,7 @@ namespace Bloxstrap
return; return;
} }
if (File.Exists(packageLocation)) if (File.Exists(package.DownloadPath))
return; return;
// TODO: telemetry for this. chances are that this is completely unnecessary and that it can be removed. // TODO: telemetry for this. chances are that this is completely unnecessary and that it can be removed.
@ -937,7 +957,7 @@ namespace Bloxstrap
{ {
var response = await App.HttpClient.GetAsync(packageUrl, HttpCompletionOption.ResponseHeadersRead, _cancelTokenSource.Token); var response = await App.HttpClient.GetAsync(packageUrl, HttpCompletionOption.ResponseHeadersRead, _cancelTokenSource.Token);
await using var stream = await response.Content.ReadAsStreamAsync(_cancelTokenSource.Token); await using var stream = await response.Content.ReadAsStreamAsync(_cancelTokenSource.Token);
await using var fileStream = new FileStream(packageLocation, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.Delete); await using var fileStream = new FileStream(package.DownloadPath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.Delete);
while (true) while (true)
{ {
@ -987,8 +1007,8 @@ namespace Bloxstrap
else if (i >= maxTries) else if (i >= maxTries)
throw; throw;
if (File.Exists(packageLocation)) if (File.Exists(package.DownloadPath))
File.Delete(packageLocation); File.Delete(package.DownloadPath);
_totalDownloadedBytes -= totalBytesRead; _totalDownloadedBytes -= totalBytesRead;
UpdateProgressBar(); UpdateProgressBar();
@ -1005,40 +1025,31 @@ namespace Bloxstrap
} }
} }
private void ExtractPackage(Package package) private void ExtractPackage(Package package, List<string>? files = null)
{ {
const string LOG_IDENT = "Bootstrapper::ExtractPackage"; const string LOG_IDENT = "Bootstrapper::ExtractPackage";
string packageLocation = Path.Combine(Paths.Downloads, package.Signature);
string packageFolder = Path.Combine(AppData.Directory, AppData.PackageDirectoryMap[package.Name]); string packageFolder = Path.Combine(AppData.Directory, AppData.PackageDirectoryMap[package.Name]);
string? fileFilter = null;
// for sharpziplib, each file in the filter
if (files is not null)
{
var regexList = new List<string>();
foreach (string file in files)
regexList.Add("^" + file.Replace("\\", "\\\\") + "$");
fileFilter = String.Join(';', regexList);
}
App.Logger.WriteLine(LOG_IDENT, $"Extracting {package.Name}..."); App.Logger.WriteLine(LOG_IDENT, $"Extracting {package.Name}...");
var fastZip = new ICSharpCode.SharpZipLib.Zip.FastZip(); var fastZip = new ICSharpCode.SharpZipLib.Zip.FastZip();
fastZip.ExtractZip(packageLocation, packageFolder, null); fastZip.ExtractZip(package.DownloadPath, packageFolder, fileFilter);
App.Logger.WriteLine(LOG_IDENT, $"Finished extracting {package.Name}"); App.Logger.WriteLine(LOG_IDENT, $"Finished extracting {package.Name}");
} }
#endregion
//private async Task ExtractFileFromPackage(string packageName, string fileName)
//{
// Package? package = _versionPackageManifest.Find(x => x.Name == packageName);
// if (package is null)
// return;
// await DownloadPackage(package);
// using ZipArchive archive = ZipFile.OpenRead(Path.Combine(Paths.Downloads, package.Signature));
// ZipArchiveEntry? entry = archive.Entries.FirstOrDefault(x => x.FullName == fileName);
// if (entry is null)
// return;
// string extractionPath = Path.Combine(_versionFolder, AppData.PackageDirectoryMap[package.Name], entry.FullName);
// entry.ExtractToFile(extractionPath, true);
//}
#endregion
} }
} }

View File

@ -9,10 +9,15 @@ namespace Bloxstrap.Models.Manifest
public class Package public class Package
{ {
public string Name { get; set; } = ""; public string Name { get; set; } = "";
public string Signature { get; set; } = ""; public string Signature { get; set; } = "";
public int PackedSize { get; set; } public int PackedSize { get; set; }
public int Size { get; set; } public int Size { get; set; }
public string DownloadPath => Path.Combine(Paths.Downloads, Signature);
public override string ToString() public override string ToString()
{ {
return $"[{Signature}] {Name}"; return $"[{Signature}] {Name}";