Skip to content

Commit

Permalink
Bulk restoration of deleted mod files
Browse files Browse the repository at this point in the history
  • Loading branch information
pizzaboxer committed Sep 8, 2024
1 parent 1bdf761 commit 2795ccf
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 136 deletions.
283 changes: 147 additions & 136 deletions Bloxstrap/Bootstrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public class Bootstrapper
private long _totalDownloadedBytes = 0;

private bool _mustUpgrade => File.Exists(AppData.LockFilePath) || !File.Exists(AppData.ExecutablePath);
private bool _skipUpgrade = false;
private bool _noConnection = false;

public IBootstrapperDialog? Dialog = null;

Expand Down Expand Up @@ -165,10 +165,11 @@ public async Task Run()
await GetLatestVersionInfo();

// 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 ApplyModifications();
if (!_noConnection)
await ApplyModifications();

// check if launch uri is set to our bootstrapper
// this doesn't go under register, so we check every launch
Expand Down Expand Up @@ -729,144 +730,164 @@ private async Task UpgradeRoblox()
_isInstalling = false;
}

//private async Task ApplyModifications()
//{
// const string LOG_IDENT = "Bootstrapper::ApplyModifications";

// if (Process.GetProcessesByName(AppData.ExecutableName[..^4]).Any())
// {
// App.Logger.WriteLine(LOG_IDENT, "Roblox is running, aborting mod check");
// return;
// }
private async Task ApplyModifications()
{
const string LOG_IDENT = "Bootstrapper::ApplyModifications";

// SetStatus(Strings.Bootstrapper_Status_ApplyingModifications);
SetStatus(Strings.Bootstrapper_Status_ApplyingModifications);

// // handle file mods
// App.Logger.WriteLine(LOG_IDENT, "Checking file mods...");
// handle file mods
App.Logger.WriteLine(LOG_IDENT, "Checking file mods...");

// // manifest has been moved to State.json
// File.Delete(Path.Combine(Paths.Base, "ModManifest.txt"));
// manifest has been moved to State.json
File.Delete(Path.Combine(Paths.Base, "ModManifest.txt"));

// List<string> modFolderFiles = new();
List<string> modFolderFiles = new();

// if (!Directory.Exists(Paths.Modifications))
// Directory.CreateDirectory(Paths.Modifications);
if (!Directory.Exists(Paths.Modifications))
Directory.CreateDirectory(Paths.Modifications);

// // check custom font mod
// // instead of replacing the fonts themselves, we'll just alter the font family manifests
// check custom font mod
// instead of replacing the fonts themselves, we'll just alter the font family manifests

// string modFontFamiliesFolder = Path.Combine(Paths.Modifications, "content\\fonts\\families");
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))
{
App.Logger.WriteLine(LOG_IDENT, "Begin font check");

// Directory.CreateDirectory(modFontFamiliesFolder);
Directory.CreateDirectory(modFontFamiliesFolder);

// foreach (string jsonFilePath in Directory.GetFiles(Path.Combine(_versionFolder, "content\\fonts\\families")))
// {
// string jsonFilename = Path.GetFileName(jsonFilePath);
// string modFilepath = Path.Combine(modFontFamiliesFolder, jsonFilename);
const string path = "rbxasset://fonts/CustomFont.ttf";

// if (File.Exists(modFilepath))
// continue;
foreach (string jsonFilePath in Directory.GetFiles(Path.Combine(AppData.Directory, "content\\fonts\\families")))
{
string jsonFilename = Path.GetFileName(jsonFilePath);
string modFilepath = Path.Combine(modFontFamiliesFolder, jsonFilename);

// App.Logger.WriteLine(LOG_IDENT, $"Setting font for {jsonFilename}");
if (File.Exists(modFilepath))
continue;

// FontFamily? fontFamilyData = JsonSerializer.Deserialize<FontFamily>(File.ReadAllText(jsonFilePath));
App.Logger.WriteLine(LOG_IDENT, $"Setting font for {jsonFilename}");

// if (fontFamilyData is null)
// continue;
var fontFamilyData = JsonSerializer.Deserialize<FontFamily>(File.ReadAllText(jsonFilePath));

// foreach (FontFace fontFace in fontFamilyData.Faces)
// fontFace.AssetId = "rbxasset://fonts/CustomFont.ttf";
if (fontFamilyData is null)
continue;

// // TODO: writing on every launch is not necessary
// File.WriteAllText(modFilepath, JsonSerializer.Serialize(fontFamilyData, new JsonSerializerOptions { WriteIndented = true }));
// }
bool shouldWrite = false;

foreach (var fontFace in fontFamilyData.Faces)
{
if (fontFace.AssetId != path)
{
fontFace.AssetId = path;
shouldWrite = true;
}
}

// App.Logger.WriteLine(LOG_IDENT, "End font check");
// }
// else if (Directory.Exists(modFontFamiliesFolder))
// {
// Directory.Delete(modFontFamiliesFolder, true);
// }
if (shouldWrite)
File.WriteAllText(modFilepath, JsonSerializer.Serialize(fontFamilyData, new JsonSerializerOptions { WriteIndented = true }));
}

// foreach (string file in Directory.GetFiles(Paths.Modifications, "*.*", SearchOption.AllDirectories))
// {
// // get relative directory path
// string relativeFile = file.Substring(Paths.Modifications.Length + 1);
App.Logger.WriteLine(LOG_IDENT, "End font check");
}
else if (Directory.Exists(modFontFamiliesFolder))
{
Directory.Delete(modFontFamiliesFolder, true);
}

// // v1.7.0 - README has been moved to the preferences menu now
// if (relativeFile == "README.txt")
// {
// File.Delete(file);
// continue;
// }
foreach (string file in Directory.GetFiles(Paths.Modifications, "*.*", SearchOption.AllDirectories))
{
// get relative directory path
string relativeFile = file.Substring(Paths.Modifications.Length + 1);

// if (!App.Settings.Prop.UseFastFlagManager && String.Equals(relativeFile, "ClientSettings\\ClientAppSettings.json", StringComparison.OrdinalIgnoreCase))
// continue;
// v1.7.0 - README has been moved to the preferences menu now
if (relativeFile == "README.txt")
{
File.Delete(file);
continue;
}

// if (relativeFile.EndsWith(".lock"))
// continue;
if (!App.Settings.Prop.UseFastFlagManager && String.Equals(relativeFile, "ClientSettings\\ClientAppSettings.json", StringComparison.OrdinalIgnoreCase))
continue;

// modFolderFiles.Add(relativeFile);
if (relativeFile.EndsWith(".lock"))
continue;

// string fileModFolder = Path.Combine(Paths.Modifications, relativeFile);
// string fileVersionFolder = Path.Combine(_versionFolder, relativeFile);
modFolderFiles.Add(relativeFile);

// 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;
// }
string fileModFolder = Path.Combine(Paths.Modifications, relativeFile);
string fileVersionFolder = Path.Combine(AppData.Directory, relativeFile);

// 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);
// File.Copy(fileModFolder, fileVersionFolder, true);
// Filesystem.AssertReadOnly(fileVersionFolder);
Directory.CreateDirectory(Path.GetDirectoryName(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
// // 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
App.Logger.WriteLine(LOG_IDENT, $"{relativeFile} has been copied to the version folder");
}

// // TODO: this needs to extract the files from packages in bulk, this is way too slow
// foreach (string fileLocation in App.State.Prop.ModManifest)
// {
// if (modFolderFiles.Contains(fileLocation))
// continue;
// the manifest is primarily here to keep track of what files have been
// 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

// 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
// if (String.IsNullOrEmpty(package.Key))
// {
// App.Logger.WriteLine(LOG_IDENT, $"{fileLocation} was removed as a mod but does not belong to a package");
foreach (string fileLocation in App.State.Prop.ModManifest)
{
if (modFolderFiles.Contains(fileLocation))
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))
// File.Delete(versionFileLocation);
// package doesn't exist, likely mistakenly placed file
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
// string fileName = fileLocation.Substring(package.Value.Length);
// await ExtractFileFromPackage(package.Key, fileName);
if (File.Exists(versionFileLocation))
File.Delete(versionFileLocation);

// App.Logger.WriteLine(LOG_IDENT, $"{fileLocation} was removed as a mod, restored from {package.Key}");
// }
continue;
}

// App.State.Prop.ModManifest = modFolderFiles;
// App.State.Save();
string fileName = fileLocation.Substring(packageMapEntry.Value.Length);

// 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)
{
Expand All @@ -876,14 +897,13 @@ private async Task DownloadPackage(Package package)
return;

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);

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)
{
Expand All @@ -906,15 +926,15 @@ private async Task DownloadPackage(Package package)
// then we can just copy the one from there

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;
UpdateProgressBar();

return;
}

if (File.Exists(packageLocation))
if (File.Exists(package.DownloadPath))
return;

// TODO: telemetry for this. chances are that this is completely unnecessary and that it can be removed.
Expand All @@ -937,7 +957,7 @@ private async Task DownloadPackage(Package package)
{
var response = await App.HttpClient.GetAsync(packageUrl, HttpCompletionOption.ResponseHeadersRead, _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)
{
Expand Down Expand Up @@ -987,8 +1007,8 @@ private async Task DownloadPackage(Package package)
else if (i >= maxTries)
throw;

if (File.Exists(packageLocation))
File.Delete(packageLocation);
if (File.Exists(package.DownloadPath))
File.Delete(package.DownloadPath);

_totalDownloadedBytes -= totalBytesRead;
UpdateProgressBar();
Expand All @@ -1005,40 +1025,31 @@ private async Task DownloadPackage(Package package)
}
}

private void ExtractPackage(Package package)
private void ExtractPackage(Package package, List<string>? files = null)
{
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? 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}...");

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}");
}

//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
#endregion
}
}
Loading

0 comments on commit 2795ccf

Please sign in to comment.