bloxstrap/Bloxstrap/Helpers/Integrations/ReShade.cs
pizzaboxer fea122f928 Followup on ReShade support
Added ReShade/preset updating, UI fonts and Extravi's ReShade config
2023-01-16 10:06:28 +00:00

457 lines
22 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.IO.Compression;
using Bloxstrap.Models;
using IniParser;
using IniParser.Model;
using System.Diagnostics;
namespace Bloxstrap.Helpers.Integrations
{
internal class ReShade
{
// i havent even started this and i know for a fact this is gonna be a mess of an integration lol
// there's a lot of nuances involved in how reshade functionality is supposed to work (shader management, config management, etc)
// it's gonna be a bit of a pain in the ass, and i'm expecting a lot of bugs to arise from this...
// well, looks like v1.7.0 is gonna be held back for quite a while lol
// also, this is going to be fairly restrictive without a lot of heavy work
// reshade's official installer gives you a list of shader packs and lets you choose which ones you want to install
// and here we're effectively choosing for the user... hm...
// i mean, it should be fine? importing shaders is still gonna be a thing, though maybe not as simple, but most people would be looking to use extravi's presets anyway
// based on the shaders we have installed, we're gonna have to parse and adjust this... yay.............
#region Config
private static readonly string StockConfig =
"[APP]\r\n" +
"ForceFullscreen=0\r\n" +
"ForceVsync=0\r\n" +
"ForceWindowed=0\r\n" +
"\r\n" +
"[GENERAL]\r\n" +
"EffectSearchPaths=..\\..\\ReShade\\Shaders\r\n" +
"PerformanceMode=1\r\n" +
"PreprocessorDefinitions=RESHADE_DEPTH_LINEARIZATION_FAR_PLANE=1000.0,RESHADE_DEPTH_INPUT_IS_UPSIDE_DOWN=0,RESHADE_DEPTH_INPUT_IS_REVERSED=1,RESHADE_DEPTH_INPUT_IS_LOGARITHMIC=0\r\n" +
"PresetPath=..\\..\\ReShade\\Presets\\ReShadePreset.ini\r\n" +
"TextureSearchPaths=..\\..\\ReShade\\Textures\r\n" +
"\r\n" +
"[INPUT]\r\n" +
"ForceShortcutModifiers=1\r\n" +
"InputProcessing=2\r\n" +
"GamepadNavigation=1\r\n" +
"KeyEffects=117,0,1,0\r\n" +
"KeyNextPreset=0,0,0,0\r\n" +
"KeyOverlay=9,0,1,0\r\n" +
"KeyPerformanceMode=0,0,0,0\r\n" +
"KeyPreviousPreset=0,0,0,0\r\n" +
"KeyReload=0,0,0,0\r\n" +
"KeyScreenshot=44,0,0,0\r\n" +
"\r\n" +
"[STYLE]\r\n" +
"Alpha=1.000000\r\n" +
"Border=0.862745,0.862745,0.862745,0.300000\r\n" +
"BorderShadow=0.000000,0.000000,0.000000,0.000000\r\n" +
"Button=0.156863,0.313726,0.941177,0.440000\r\n" +
"ButtonActive=0.156863,0.313726,0.941177,1.000000\r\n" +
"ButtonHovered=0.156863,0.313726,0.941177,0.860000\r\n" +
"CheckMark=0.156863,0.313726,0.941177,0.800000\r\n" +
"ChildBg=0.109804,0.109804,0.109804,0.000000\r\n" +
"ChildRounding=6.000000\r\n" +
"ColFPSText=1.000000,1.000000,0.784314,1.000000\r\n" +
"DockingEmptyBg=0.200000,0.200000,0.200000,1.000000\r\n" +
"DockingPreview=0.156863,0.313726,0.941177,0.532000\r\n" +
"DragDropTarget=1.000000,1.000000,0.000000,0.900000\r\n" +
"EditorFont=..\\..\\ReShade\\Fonts\\Hack-Regular.ttf\r\n" +
"EditorFontSize=18\r\n" +
"EditorStyleIndex=0\r\n" +
"Font=..\\..\\ReShade\\Fonts\\NunitoSans-Regular.ttf\r\n" +
"FontSize=18\r\n" +
"FPSScale=1.000000\r\n" +
"FrameBg=0.109804,0.109804,0.109804,1.000000\r\n" +
"FrameBgActive=0.156863,0.313726,0.941177,1.000000\r\n" +
"FrameBgHovered=0.156863,0.313726,0.941177,0.680000\r\n" +
"FrameRounding=6.000000\r\n" +
"GrabRounding=6.000000\r\n" +
"Header=0.156863,0.313726,0.941177,0.760000\r\n" +
"HeaderActive=0.156863,0.313726,0.941177,1.000000\r\n" +
"HeaderHovered=0.156863,0.313726,0.941177,0.860000\r\n" +
"MenuBarBg=0.109804,0.109804,0.109804,0.570000\r\n" +
"ModalWindowDimBg=0.800000,0.800000,0.800000,0.350000\r\n" +
"NavHighlight=0.260000,0.590000,0.980000,1.000000\r\n" +
"NavWindowingDimBg=0.800000,0.800000,0.800000,0.200000\r\n" +
"NavWindowingHighlight=1.000000,1.000000,1.000000,0.700000\r\n" +
"PlotHistogram=0.862745,0.862745,0.862745,0.630000\r\n" +
"PlotHistogramHovered=0.156863,0.313726,0.941177,1.000000\r\n" +
"PlotLines=0.862745,0.862745,0.862745,0.630000\r\n" +
"PlotLinesHovered=0.156863,0.313726,0.941177,1.000000\r\n" +
"PopupBg=0.047059,0.047059,0.047059,0.920000\r\n" +
"PopupRounding=6.000000\r\n" +
"ResizeGrip=0.156863,0.313726,0.941177,0.200000\r\n" +
"ResizeGripActive=0.156863,0.313726,0.941177,1.000000\r\n" +
"ResizeGripHovered=0.156863,0.313726,0.941177,0.780000\r\n" +
"ScrollbarBg=0.109804,0.109804,0.109804,1.000000\r\n" +
"ScrollbarGrab=0.156863,0.313726,0.941177,0.310000\r\n" +
"ScrollbarGrabActive=0.156863,0.313726,0.941177,1.000000\r\n" +
"ScrollbarGrabHovered=0.156863,0.313726,0.941177,0.780000\r\n" +
"ScrollbarRounding=6.000000\r\n" +
"Separator=0.862745,0.862745,0.862745,0.320000\r\n" +
"SeparatorActive=0.862745,0.862745,0.862745,1.000000\r\n" +
"SeparatorHovered=0.862745,0.862745,0.862745,0.780000\r\n" +
"SliderGrab=0.156863,0.313726,0.941177,0.240000\r\n" +
"SliderGrabActive=0.156863,0.313726,0.941177,1.000000\r\n" +
"StyleIndex=3\r\n" +
"Tab=0.156863,0.313726,0.941177,0.440000\r\n" +
"TabActive=0.156863,0.313726,0.941177,1.000000\r\n" +
"TabHovered=0.156863,0.313726,0.941177,0.860000\r\n" +
"TableBorderLight=0.230000,0.230000,0.250000,1.000000\r\n" +
"TableBorderStrong=0.310000,0.310000,0.350000,1.000000\r\n" +
"TableHeaderBg=0.190000,0.190000,0.200000,1.000000\r\n" +
"TableRowBg=0.000000,0.000000,0.000000,0.000000\r\n" +
"TableRowBgAlt=1.000000,1.000000,1.000000,0.060000\r\n" +
"TabRounding=6.000000\r\n" +
"TabUnfocused=0.156863,0.313726,0.941177,0.448000\r\n" +
"TabUnfocusedActive=0.156863,0.313726,0.941177,0.780000\r\n" +
"Text=0.862745,0.862745,0.862745,1.000000\r\n" +
"TextDisabled=0.862745,0.862745,0.862745,0.580000\r\n" +
"TextSelectedBg=0.156863,0.313726,0.941177,0.430000\r\n" +
"TitleBg=0.156863,0.313726,0.941177,0.450000\r\n" +
"TitleBgActive=0.156863,0.313726,0.941177,0.580000\r\n" +
"TitleBgCollapsed=0.156863,0.313726,0.941177,0.350000\r\n" +
"WindowBg=0.047059,0.047059,0.047059,1.000000\r\n" +
"WindowRounding=6.000000";
#endregion
// this is a list of selectable shaders to download:
// this should be formatted as { FolderName, GithubRepositoryUrl }
private static readonly IReadOnlyDictionary<string, string> Shaders = new Dictionary<string, string>()
{
{ "Stock", "https://github.com/crosire/reshade-shaders/archive/refs/heads/master.zip" },
// shaders required for extravi's presets:
{ "AlucardDH", "https://github.com/AlucardDH/dh-reshade-shaders/archive/refs/heads/master.zip" },
{ "AstrayFX", "https://github.com/BlueSkyDefender/AstrayFX/archive/refs/heads/master.zip" },
{ "Depth3D", "https://github.com/BlueSkyDefender/Depth3D/archive/refs/heads/master.zip" },
{ "Glamarye", "https://github.com/rj200/Glamarye_Fast_Effects_for_ReShade/archive/refs/heads/main.zip" },
{ "NiceGuy", "https://github.com/mj-ehsan/NiceGuy-Shaders/archive/refs/heads/main.zip" },
{ "prod80", "https://github.com/prod80/prod80-ReShade-Repository/archive/refs/heads/master.zip" },
{ "qUINT", "https://github.com/martymcmodding/qUINT/archive/refs/heads/master.zip" },
};
private static readonly string[] ExtraviPresetsShaders = new string[]
{
"AlucardDH",
"AstrayFX",
"Depth3D",
"Glamarye",
"NiceGuy",
"prod80",
"qUINT",
};
private static string GetSearchPath(string type, string name)
{
return $",..\\..\\ReShade\\{type}\\{name}";
}
public static void SynchronizeConfigFile()
{
Debug.WriteLine($"[ReShade] Synchronizing configuration file...");
// yeah, this is going to be a bit of a pain
// keep in mind the config file is going to be in two places: the mod folder and the version folder
// so we have to make sure the two below scenaros work flawlessly:
// - if the user manually updates their reshade config in the mod folder, it must be copied to the version folder
// - if the user updates their reshade settings ingame, the updated config must be copied to the mod folder
// the easiest way to manage this is to just compare the modification dates of the two
// anyway, this is where i'm expecting most of the bugs to arise from
// config synchronization will be done whenever roblox updates or whenever we launch roblox
string modFolderConfigPath = Path.Combine(Directories.Modifications, "ReShade.ini");
string versionFolderConfigPath = Path.Combine(Directories.Versions, Program.Settings.VersionGuid, "ReShade.ini");
// we shouldn't be here if the mod config doesn't already exist
if (!File.Exists(modFolderConfigPath))
{
Debug.WriteLine($"[ReShade] ReShade.ini in modifications folder does not exist, aborting sync");
return;
}
// copy to the version folder if it doesn't already exist there
if (!File.Exists(versionFolderConfigPath))
{
Debug.WriteLine($"[ReShade] ReShade.ini in version folder does not exist, synchronized with modifications folder");
File.Copy(modFolderConfigPath, versionFolderConfigPath);
}
// if both the mod and version configs match, then we don't need to do anything
if (Utilities.MD5File(modFolderConfigPath) == Utilities.MD5File(versionFolderConfigPath))
{
Debug.WriteLine($"[ReShade] ReShade.ini in version and modifications folder match");
return;
}
FileInfo modFolderConfigFile = new(modFolderConfigPath);
FileInfo versionFolderConfigFile = new(versionFolderConfigPath);
if (modFolderConfigFile.LastWriteTime > versionFolderConfigFile.LastWriteTime)
{
// overwrite version config if mod config was modified most recently
Debug.WriteLine($"[ReShade] ReShade.ini in version folder is older, synchronized with modifications folder");
File.Copy(modFolderConfigPath, versionFolderConfigPath, true);
}
else if (versionFolderConfigFile.LastWriteTime > modFolderConfigFile.LastWriteTime)
{
// overwrite mod config if version config was modified most recently
Debug.WriteLine($"[ReShade] ReShade.ini in modifications folder is older, synchronized with version folder");
File.Copy(versionFolderConfigPath, modFolderConfigPath, true);
}
}
public static async Task DownloadShaders(string name)
{
string downloadUrl = Shaders.First(x => x.Key == name).Value;
// not all shader packs have a textures folder, so here we're determining if they exist purely based on if they have a Shaders folder
if (Directory.Exists(Path.Combine(Directories.ReShade, "Shaders", name)))
return;
Debug.WriteLine($"[ReShade] Downloading shaders for {name}");
{
byte[] bytes = await Program.HttpClient.GetByteArrayAsync(downloadUrl);
using MemoryStream zipStream = new(bytes);
using ZipArchive archive = new(zipStream);
foreach (ZipArchiveEntry entry in archive.Entries)
{
if (entry.FullName.EndsWith('/'))
continue;
// github branch zips have a root folder of the name of the branch, so let's just remove that
string fullPath = entry.FullName.Substring(entry.FullName.IndexOf('/') + 1);
// skip file if it's not in the Shaders or Textures folder
if (!fullPath.StartsWith("Shaders") && !fullPath.StartsWith("Textures"))
continue;
// ingore shaders with compiler errors
if (fullPath.EndsWith("dh_Lain.fx") || fullPath.EndsWith("dh_rtgi.fx"))
continue;
// and now we do it again because of how we're handling folder management
// e.g. reshade-shaders-master/Shaders/Vignette.fx should go to ReShade/Shaders/Stock/Vignette.fx
// so in this case, relativePath should just be "Vignette.fx"
string relativePath = fullPath.Substring(fullPath.IndexOf('/') + 1);
// now we stitch it all together
string extractionPath = Path.Combine(
Directories.ReShade,
fullPath.StartsWith("Shaders") ? "Shaders" : "Textures",
name,
relativePath
);
// make sure the folder that we're extracting it to exists
Directory.CreateDirectory(Path.GetDirectoryName(extractionPath)!);
// and now extract
await Task.Run(() => entry.ExtractToFile(extractionPath));
}
}
// now we have to update ReShade.ini and add the installed shaders to the search paths
FileIniDataParser parser = new();
IniData data = parser.ReadFile(Path.Combine(Directories.Modifications, "ReShade.ini"));
if (!data["GENERAL"]["EffectSearchPaths"].Contains(name))
data["GENERAL"]["EffectSearchPaths"] += GetSearchPath("Shaders", name);
// not every shader pack has a textures folder
if (Directory.Exists(Path.Combine(Directories.ReShade, "Textures", name)) && !data["GENERAL"]["TextureSearchPaths"].Contains(name))
data["GENERAL"]["TextureSearchPaths"] += GetSearchPath("Textures", name);
parser.WriteFile(Path.Combine(Directories.Modifications, "ReShade.ini"), data);
}
public static void DeleteShaders(string name)
{
Debug.WriteLine($"[ReShade] Deleting shaders for {name}");
string shadersPath = Path.Combine(Directories.ReShade, "Shaders", name);
string texturesPath = Path.Combine(Directories.ReShade, "Textures", name);
if (Directory.Exists(shadersPath))
Directory.Delete(shadersPath, true);
if (Directory.Exists(texturesPath))
Directory.Delete(texturesPath, true);
string configFile = Path.Combine(Directories.Modifications, "ReShade.ini");
if (!File.Exists(configFile))
return;
// now we have to update ReShade.ini and remove the installed shaders from the search paths
FileIniDataParser parser = new();
IniData data = parser.ReadFile(configFile);
string configShaderSearchPaths = data["GENERAL"]["EffectSearchPaths"];
string configTextureSearchPaths = data["GENERAL"]["TextureSearchPaths"];
if (configShaderSearchPaths.Contains(name))
{
string searchPath = GetSearchPath("Shaders", name);
data["GENERAL"]["EffectSearchPaths"] = configShaderSearchPaths.Remove(configShaderSearchPaths.IndexOf(searchPath), searchPath.Length);
}
if (configTextureSearchPaths.Contains(name))
{
string searchPath = GetSearchPath("Textures", name);
data["GENERAL"]["TextureSearchPaths"] = configTextureSearchPaths.Remove(configTextureSearchPaths.IndexOf(searchPath), searchPath.Length);
}
parser.WriteFile(configFile, data);
}
public static async Task InstallExtraviPresets()
{
Debug.WriteLine("[ReShade] Installing Extravi's presets...");
foreach (string name in ExtraviPresetsShaders)
await DownloadShaders(name);
byte[] bytes = await Program.HttpClient.GetByteArrayAsync("https://github.com/Extravi/extravi.github.io/raw/main/update/reshade-presets.zip");
using MemoryStream zipStream = new(bytes);
using ZipArchive archive = new(zipStream);
foreach (ZipArchiveEntry entry in archive.Entries)
{
if (entry.FullName.EndsWith('/'))
continue;
// remove containing folder
string filename = entry.FullName.Substring(entry.FullName.IndexOf('/') + 1);
await Task.Run(() => entry.ExtractToFile(Path.Combine(Directories.ReShade, "Presets", filename), true));
}
}
public static void UninstallExtraviPresets()
{
Debug.WriteLine("[ReShade] Uninstalling Extravi's presets...");
FileInfo[] presets = new DirectoryInfo(Path.Combine(Directories.ReShade, "Presets")).GetFiles();
foreach (FileInfo preset in presets)
{
if (preset.Name.StartsWith("Extravi"))
preset.Delete();
}
foreach (string name in ExtraviPresetsShaders)
DeleteShaders(name);
}
public static async Task CheckModifications()
{
Debug.WriteLine("[ReShade] Checking ReShade modifications... ");
string injectorLocation = Path.Combine(Directories.Modifications, "dxgi.dll");
string configLocation = Path.Combine(Directories.Modifications, "ReShade.ini");
// initialize directories
Directory.CreateDirectory(Directories.ReShade);
Directory.CreateDirectory(Path.Combine(Directories.ReShade, "Fonts"));
Directory.CreateDirectory(Path.Combine(Directories.ReShade, "Shaders"));
Directory.CreateDirectory(Path.Combine(Directories.ReShade, "Textures"));
Directory.CreateDirectory(Path.Combine(Directories.ReShade, "Presets"));
if (!Program.Settings.UseReShadeExtraviPresets)
{
UninstallExtraviPresets();
Program.Settings.ExtraviPresetsVersion = "";
}
if (!Program.Settings.UseReShade)
{
Debug.WriteLine("[ReShade] Uninstalling ReShade...");
// delete any stock config files
if (File.Exists(injectorLocation))
File.Delete(injectorLocation);
if (File.Exists(configLocation))
File.Delete(configLocation);
DeleteShaders("Stock");
return;
}
// the version manfiest contains the version of reshade available for download and the last date the presets were updated
var versionManifest = await Utilities.GetJson<ReShadeVersionManifest>("https://raw.githubusercontent.com/Extravi/extravi.github.io/main/update/version.json");
bool shouldFetchReShade = false;
if (!File.Exists(injectorLocation))
{
shouldFetchReShade = true;
}
else if (versionManifest is not null)
{
// check if an update for reshade is available
FileVersionInfo injectorVersionInfo = FileVersionInfo.GetVersionInfo(injectorLocation);
if (injectorVersionInfo.ProductVersion != versionManifest.ReShade)
shouldFetchReShade = true;
}
if (shouldFetchReShade)
{
Debug.WriteLine("[ReShade] Installing ReShade...");
{
byte[] bytes = await Program.HttpClient.GetByteArrayAsync("https://github.com/Extravi/extravi.github.io/raw/main/update/dxgi.zip");
using MemoryStream zipStream = new(bytes);
using ZipArchive archive = new(zipStream);
archive.ExtractToDirectory(Directories.Modifications, true);
}
// we also gotta download the editor fonts
if (Utilities.IsDirectoryEmpty(Path.Combine(Directories.ReShade, "Fonts")))
{
byte[] bytes = await Program.HttpClient.GetByteArrayAsync("https://github.com/Extravi/extravi.github.io/raw/main/update/config.zip");
using MemoryStream zipStream = new(bytes);
using ZipArchive archive = new(zipStream);
foreach (ZipArchiveEntry entry in archive.Entries.Where(x => x.FullName.EndsWith(".ttf")))
entry.ExtractToFile(Path.Combine(Directories.ReShade, "Fonts", entry.FullName));
}
}
// and write the stock config if we need to
if (!File.Exists(configLocation))
await File.WriteAllTextAsync(configLocation, StockConfig);
await DownloadShaders("Stock");
if (Program.Settings.UseReShadeExtraviPresets && Program.Settings.ExtraviPresetsVersion != versionManifest!.Presets)
{
await InstallExtraviPresets();
Program.Settings.ExtraviPresetsVersion = versionManifest.Presets;
}
SynchronizeConfigFile();
}
}
}