reformatting

This commit is contained in:
pointfeev 2024-04-20 17:16:29 -04:00
parent 3a2d2f9e01
commit 4927a079c7
35 changed files with 1138 additions and 455 deletions

View file

@ -23,17 +23,20 @@ internal sealed class ContextMenuItem : ToolStripMenuItem
internal ContextMenuItem(string text, string imageIdentifier, EventHandler onClick = null) : this(text, onClick) internal ContextMenuItem(string text, string imageIdentifier, EventHandler onClick = null) : this(text, onClick)
=> _ = TryImageIdentifier(this, imageIdentifier); => _ = TryImageIdentifier(this, imageIdentifier);
internal ContextMenuItem(string text, (string id, string iconUrl) imageIdentifierInfo, EventHandler onClick = null) : this(text, onClick) internal ContextMenuItem(string text, (string id, string iconUrl) imageIdentifierInfo, EventHandler onClick = null)
: this(text, onClick)
=> _ = TryImageIdentifierInfo(this, imageIdentifierInfo); => _ = TryImageIdentifierInfo(this, imageIdentifierInfo);
internal ContextMenuItem(string text, (string id, string iconUrl) imageIdentifierInfo, string imageIdentifierFallback, EventHandler onClick = null) : internal ContextMenuItem(string text, (string id, string iconUrl) imageIdentifierInfo,
string imageIdentifierFallback, EventHandler onClick = null) :
this(text, onClick) this(text, onClick)
{ {
async void OnFail() => await TryImageIdentifier(this, imageIdentifierFallback); async void OnFail() => await TryImageIdentifier(this, imageIdentifierFallback);
_ = TryImageIdentifierInfo(this, imageIdentifierInfo, OnFail); _ = TryImageIdentifierInfo(this, imageIdentifierInfo, OnFail);
} }
internal ContextMenuItem(string text, (string id, string iconUrl) imageIdentifierInfo, (string id, string iconUrl) imageIdentifierInfoFallback, internal ContextMenuItem(string text, (string id, string iconUrl) imageIdentifierInfo,
(string id, string iconUrl) imageIdentifierInfoFallback,
EventHandler onClick = null) : this(text, onClick) EventHandler onClick = null) : this(text, onClick)
{ {
async void OnFail() => await TryImageIdentifierInfo(this, imageIdentifierInfoFallback); async void OnFail() => await TryImageIdentifierInfo(this, imageIdentifierInfoFallback);
@ -56,6 +59,7 @@ internal sealed class ContextMenuItem : ToolStripMenuItem
image = file.GetFileIconImage(); image = file.GetFileIconImage();
break; break;
} }
break; break;
case "Notepad": case "Notepad":
image = IconGrabber.GetNotepadImage(); image = IconGrabber.GetNotepadImage();
@ -67,26 +71,33 @@ internal sealed class ContextMenuItem : ToolStripMenuItem
image = IconGrabber.GetFileExplorerImage(); image = IconGrabber.GetFileExplorerImage();
break; break;
case "SteamDB": case "SteamDB":
image = await HttpClientManager.GetImageFromUrl(IconGrabber.GetDomainFaviconUrl("steamdb.info")); image = await HttpClientManager.GetImageFromUrl(
IconGrabber.GetDomainFaviconUrl("steamdb.info"));
break; break;
case "Steam Store": case "Steam Store":
image = await HttpClientManager.GetImageFromUrl(IconGrabber.GetDomainFaviconUrl("store.steampowered.com")); image = await HttpClientManager.GetImageFromUrl(
IconGrabber.GetDomainFaviconUrl("store.steampowered.com"));
break; break;
case "Steam Community": case "Steam Community":
image = await HttpClientManager.GetImageFromUrl(IconGrabber.GetDomainFaviconUrl("steamcommunity.com")); image = await HttpClientManager.GetImageFromUrl(
IconGrabber.GetDomainFaviconUrl("steamcommunity.com"));
break; break;
case "ScreamDB": case "ScreamDB":
image = await HttpClientManager.GetImageFromUrl(IconGrabber.GetDomainFaviconUrl("scream-db.web.app")); image = await HttpClientManager.GetImageFromUrl(
IconGrabber.GetDomainFaviconUrl("scream-db.web.app"));
break; break;
case "Epic Games": case "Epic Games":
image = await HttpClientManager.GetImageFromUrl(IconGrabber.GetDomainFaviconUrl("epicgames.com")); image = await HttpClientManager.GetImageFromUrl(
IconGrabber.GetDomainFaviconUrl("epicgames.com"));
break; break;
case "Ubisoft Store": case "Ubisoft Store":
image = await HttpClientManager.GetImageFromUrl(IconGrabber.GetDomainFaviconUrl("store.ubi.com")); image = await HttpClientManager.GetImageFromUrl(
IconGrabber.GetDomainFaviconUrl("store.ubi.com"));
break; break;
default: default:
return; return;
} }
if (image is not null) if (image is not null)
{ {
Images[imageIdentifier] = image; Images[imageIdentifier] = image;
@ -95,7 +106,8 @@ internal sealed class ContextMenuItem : ToolStripMenuItem
} }
}); });
private static async Task TryImageIdentifierInfo(ContextMenuItem item, (string id, string iconUrl) imageIdentifierInfo, Action onFail = null) private static async Task TryImageIdentifierInfo(ContextMenuItem item,
(string id, string iconUrl) imageIdentifierInfo, Action onFail = null)
=> await Task.Run(async () => => await Task.Run(async () =>
{ {
try try

View file

@ -50,33 +50,34 @@ internal class CustomForm : Form
string repository = $"https://github.com/{Program.RepositoryOwner}/{Program.RepositoryName}"; string repository = $"https://github.com/{Program.RepositoryOwner}/{Program.RepositoryName}";
_ = helpDialog.Show(SystemIcons.Information, _ = helpDialog.Show(SystemIcons.Information,
"Automatically finds all installed Steam, Epic and Ubisoft games with their respective DLC-related DLL locations on the user's computer,\n" "Automatically finds all installed Steam, Epic and Ubisoft games with their respective DLC-related DLL locations on the user's computer,\n"
+ "parses SteamCMD, Steam Store and Epic Games Store for user-selected games' DLCs, then provides a very simple graphical interface\n" + "parses SteamCMD, Steam Store and Epic Games Store for user-selected games' DLCs, then provides a very simple graphical interface\n"
+ "utilizing the gathered information for the maintenance of DLC unlockers.\n\n" + "utilizing the gathered information for the maintenance of DLC unlockers.\n\n"
+ $"The program utilizes the latest versions of [Koaloader]({acidicoala}/Koaloader), [SmokeAPI]({acidicoala}/SmokeAPI), [ScreamAPI]({acidicoala}/ScreamAPI), [Uplay R1 Unlocker]({acidicoala}/UplayR1Unlocker) and [Uplay R2 Unlocker]({acidicoala}/UplayR2Unlocker), all by\n" + $"The program utilizes the latest versions of [Koaloader]({acidicoala}/Koaloader), [SmokeAPI]({acidicoala}/SmokeAPI), [ScreamAPI]({acidicoala}/ScreamAPI), [Uplay R1 Unlocker]({acidicoala}/UplayR1Unlocker) and [Uplay R2 Unlocker]({acidicoala}/UplayR2Unlocker), all by\n"
+ $"the wonderful [acidicoala]({acidicoala}), and all downloaded and embedded into the program itself; no further downloads necessary on your part!\n\n" + $"the wonderful [acidicoala]({acidicoala}), and all downloaded and embedded into the program itself; no further downloads necessary on your part!\n\n"
+ "USAGE:\n" + " 1. Choose which programs and/or games the program should scan for DLC.\n" + "USAGE:\n" + " 1. Choose which programs and/or games the program should scan for DLC.\n"
+ " The program automatically gathers all installed games from Steam, Epic and Ubisoft directories.\n" + " The program automatically gathers all installed games from Steam, Epic and Ubisoft directories.\n"
+ " 2. Wait for the program to download and install SteamCMD (if you chose a Steam game).\n" + " 2. Wait for the program to download and install SteamCMD (if you chose a Steam game).\n"
+ " 3. Wait for the program to gather and cache the chosen games' information && DLCs.\n" + " 3. Wait for the program to gather and cache the chosen games' information && DLCs.\n"
+ " May take some time on the first run; depends on how many DLCs the games you chose have.\n" + " May take some time on the first run; depends on how many DLCs the games you chose have.\n"
+ " 4. CAREFULLY select which games' DLCs you wish to unlock.\n" + " 4. CAREFULLY select which games' DLCs you wish to unlock.\n"
+ " Obviously none of the DLC unlockers are tested for every single game!\n" + " Obviously none of the DLC unlockers are tested for every single game!\n"
+ " 5. Choose whether or not to install with Koaloader, and if so then also pick the proxy DLL to use.\n" + " 5. Choose whether or not to install with Koaloader, and if so then also pick the proxy DLL to use.\n"
+ " If the default \'version.dll\' doesn't work, then see [here](https://cs.rin.ru/forum/viewtopic.php?p=2552172#p2552172) to find one that does.\n" + " If the default \'version.dll\' doesn't work, then see [here](https://cs.rin.ru/forum/viewtopic.php?p=2552172#p2552172) to find one that does.\n"
+ " 6. Click the \"Generate and Install\" button.\n" + " 7. Click the \"OK\" button to close the program.\n" + " 6. Click the \"Generate and Install\" button.\n" +
+ " 8. If any of the DLC unlockers cause problems with any of the games you installed them on, simply go back\n" " 7. Click the \"OK\" button to close the program.\n"
+ " to step 5 and select what games you wish you revert changes to, and instead click the \"Uninstall\" button this time.\n\n" + " 8. If any of the DLC unlockers cause problems with any of the games you installed them on, simply go back\n"
+ "NOTE: This program does not automatically download nor install actual DLC files for you; as the title of the program states, this program\n" + " to step 5 and select what games you wish you revert changes to, and instead click the \"Uninstall\" button this time.\n\n"
+ "is only a DLC Unlocker installer. Should the game you wish to unlock DLC for not already come with the DLCs installed, as is the case with\n" + "NOTE: This program does not automatically download nor install actual DLC files for you; as the title of the program states, this program\n"
+ "a good majority of games, you must find, download and install those to the game yourself. This process includes manually installing new\n" + "is only a DLC Unlocker installer. Should the game you wish to unlock DLC for not already come with the DLCs installed, as is the case with\n"
+ "DLCs and manually updating the previously manually installed DLCs after game updates.\n\n" + "a good majority of games, you must find, download and install those to the game yourself. This process includes manually installing new\n"
+ $"For reliable and quick assistance, all bugs, crashes and other issues should be referred to the [GitHub Issues]({repository}/issues) page!\n\n" + "DLCs and manually updating the previously manually installed DLCs after game updates.\n\n"
+ $"HOWEVER: Please read the [FAQ entry]({repository}#faq--common-issues) and/or [template issue]({repository}/issues/new/choose) corresponding to your problem should one exist! Also, note that the [GitHub Issues]({repository}/issues)\n" + $"For reliable and quick assistance, all bugs, crashes and other issues should be referred to the [GitHub Issues]({repository}/issues) page!\n\n"
+ "page is not your personal assistance hotline, rather it is for genuine bugs/crashes/issues with the program itself. If you post an issue which\n" + $"HOWEVER: Please read the [FAQ entry]({repository}#faq--common-issues) and/or [template issue]({repository}/issues/new/choose) corresponding to your problem should one exist! Also, note that the [GitHub Issues]({repository}/issues)\n"
+ "has already been explained within the FAQ, template issues, and/or within this text in general, I will just close it and you will be ignored.\n\n" + "page is not your personal assistance hotline, rather it is for genuine bugs/crashes/issues with the program itself. If you post an issue which\n"
+ "SteamCMD installation and appinfo cache can be found at [C:\\ProgramData\\CreamInstaller]().\n" + "has already been explained within the FAQ, template issues, and/or within this text in general, I will just close it and you will be ignored.\n\n"
+ $"The program automatically and very quickly updates from [GitHub]({repository}) by choice of the user through a dialog on startup.\n" + "SteamCMD installation and appinfo cache can be found at [C:\\ProgramData\\CreamInstaller]().\n"
+ $"The program source and other information can be found on [GitHub]({repository})."); + $"The program automatically and very quickly updates from [GitHub]({repository}) by choice of the user through a dialog on startup.\n"
+ $"The program source and other information can be found on [GitHub]({repository}).");
} }
private void OnActivation(object sender, EventArgs args) => Activate(); private void OnActivation(object sender, EventArgs args) => Activate();
@ -85,10 +86,12 @@ internal class CustomForm : Form
{ {
bool topMost = TopMost; bool topMost = TopMost;
NativeImports.SetWindowPos(Handle, NativeImports.HWND_TOPMOST, 0, 0, 0, 0, NativeImports.SetWindowPos(Handle, NativeImports.HWND_TOPMOST, 0, 0, 0, 0,
NativeImports.SWP_NOACTIVATE | NativeImports.SWP_SHOWWINDOW | NativeImports.SWP_NOMOVE | NativeImports.SWP_NOSIZE); NativeImports.SWP_NOACTIVATE | NativeImports.SWP_SHOWWINDOW | NativeImports.SWP_NOMOVE |
NativeImports.SWP_NOSIZE);
if (!topMost) if (!topMost)
NativeImports.SetWindowPos(Handle, NativeImports.HWND_NOTOPMOST, 0, 0, 0, 0, NativeImports.SetWindowPos(Handle, NativeImports.HWND_NOTOPMOST, 0, 0, 0, 0,
NativeImports.SWP_NOACTIVATE | NativeImports.SWP_SHOWWINDOW | NativeImports.SWP_NOMOVE | NativeImports.SWP_NOSIZE); NativeImports.SWP_NOACTIVATE | NativeImports.SWP_SHOWWINDOW | NativeImports.SWP_NOMOVE |
NativeImports.SWP_NOSIZE);
} }
internal void InheritLocation(Form fromForm) internal void InheritLocation(Form fromForm)

View file

@ -94,6 +94,7 @@ internal sealed class CustomTreeView : TreeView
} }
else else
text = platform.ToString(); text = platform.ToString();
Size size = TextRenderer.MeasureText(graphics, text, font); Size size = TextRenderer.MeasureText(graphics, text, font);
bounds = bounds with { X = bounds.X + bounds.Width, Width = size.Width }; bounds = bounds with { X = bounds.X + bounds.Width, Width = size.Width };
selectionBounds = new(selectionBounds.Location, selectionBounds.Size + bounds.Size with { Height = 0 }); selectionBounds = new(selectionBounds.Location, selectionBounds.Size + bounds.Size with { Height = 0 });
@ -111,11 +112,13 @@ internal sealed class CustomTreeView : TreeView
size = TextRenderer.MeasureText(graphics, text, font); size = TextRenderer.MeasureText(graphics, text, font);
const int left = -4; const int left = -4;
bounds = bounds with { X = bounds.X + bounds.Width + left, Width = size.Width }; bounds = bounds with { X = bounds.X + bounds.Width + left, Width = size.Width };
selectionBounds = new(selectionBounds.Location, selectionBounds.Size + new Size(bounds.Size.Width + left, 0)); selectionBounds = new(selectionBounds.Location,
selectionBounds.Size + new Size(bounds.Size.Width + left, 0));
graphics.FillRectangle(brush, bounds); graphics.FillRectangle(brush, bounds);
point = new(bounds.Location.X - 1, bounds.Location.Y + 1); point = new(bounds.Location.X - 1, bounds.Location.Y + 1);
TextRenderer.DrawText(graphics, text, font, point, color, TextFormatFlags.Default); TextRenderer.DrawText(graphics, text, font, point, color, TextFormatFlags.Default);
} }
if (form is SelectForm) if (form is SelectForm)
{ {
Selection selection = Selection.FromId(platform, id); Selection selection = Selection.FromId(platform, id);
@ -127,6 +130,7 @@ internal sealed class CustomTreeView : TreeView
bounds = bounds with { X = bounds.X + bounds.Width, Width = size.Width }; bounds = bounds with { X = bounds.X + bounds.Width, Width = size.Width };
graphics.FillRectangle(brush, bounds); graphics.FillRectangle(brush, bounds);
} }
CheckBoxState checkBoxState = selection.Koaloader CheckBoxState checkBoxState = selection.Koaloader
? Enabled ? CheckBoxState.CheckedPressed : CheckBoxState.CheckedDisabled ? Enabled ? CheckBoxState.CheckedPressed : CheckBoxState.CheckedDisabled
: Enabled : Enabled
@ -151,21 +155,25 @@ internal sealed class CustomTreeView : TreeView
this.checkBoxBounds[selection] = RectangleToClient(checkBoxBounds); this.checkBoxBounds[selection] = RectangleToClient(checkBoxBounds);
if (selection.Koaloader) if (selection.Koaloader)
{ {
comboBoxFont ??= new(font.FontFamily, 6, font.Style, font.Unit, font.GdiCharSet, font.GdiVerticalFont); comboBoxFont ??= new(font.FontFamily, 6, font.Style, font.Unit, font.GdiCharSet,
font.GdiVerticalFont);
ComboBoxState comboBoxState = Enabled ? ComboBoxState.Normal : ComboBoxState.Disabled; ComboBoxState comboBoxState = Enabled ? ComboBoxState.Normal : ComboBoxState.Disabled;
text = (selection.KoaloaderProxy ?? Selection.DefaultKoaloaderProxy) + ".dll"; text = (selection.KoaloaderProxy ?? Selection.DefaultKoaloaderProxy) + ".dll";
size = TextRenderer.MeasureText(graphics, text, comboBoxFont) + new Size(6, 0); size = TextRenderer.MeasureText(graphics, text, comboBoxFont) + new Size(6, 0);
const int padding = 2; const int padding = 2;
bounds = new(bounds.X + bounds.Width, bounds.Y + padding / 2, size.Width, bounds.Height - padding); bounds = new(bounds.X + bounds.Width, bounds.Y + padding / 2, size.Width, bounds.Height - padding);
selectionBounds = new(selectionBounds.Location, selectionBounds.Size + bounds.Size with { Height = 0 }); selectionBounds = new(selectionBounds.Location,
selectionBounds.Size + bounds.Size with { Height = 0 });
Rectangle comboBoxBounds = bounds; Rectangle comboBoxBounds = bounds;
graphics.FillRectangle(backBrush, bounds); graphics.FillRectangle(backBrush, bounds);
ComboBoxRenderer.DrawTextBox(graphics, bounds, text, comboBoxFont, comboBoxState); ComboBoxRenderer.DrawTextBox(graphics, bounds, text, comboBoxFont, comboBoxState);
size = new(14, 0); size = new(14, 0);
left = -1; left = -1;
bounds = bounds with { X = bounds.X + bounds.Width + left, Width = size.Width }; bounds = bounds with { X = bounds.X + bounds.Width + left, Width = size.Width };
selectionBounds = new(selectionBounds.Location, selectionBounds.Size + new Size(bounds.Size.Width + left, 0)); selectionBounds = new(selectionBounds.Location,
comboBoxBounds = new(comboBoxBounds.Location, comboBoxBounds.Size + new Size(bounds.Size.Width + left, 0)); selectionBounds.Size + new Size(bounds.Size.Width + left, 0));
comboBoxBounds = new(comboBoxBounds.Location,
comboBoxBounds.Size + new Size(bounds.Size.Width + left, 0));
ComboBoxRenderer.DrawDropDownButton(graphics, bounds, comboBoxState); ComboBoxRenderer.DrawDropDownButton(graphics, bounds, comboBoxState);
this.comboBoxBounds[selection] = RectangleToClient(comboBoxBounds); this.comboBoxBounds[selection] = RectangleToClient(comboBoxBounds);
} }
@ -173,6 +181,7 @@ internal sealed class CustomTreeView : TreeView
_ = comboBoxBounds.Remove(selection); _ = comboBoxBounds.Remove(selection);
} }
} }
this.selectionBounds[node] = RectangleToClient(selectionBounds); this.selectionBounds[node] = RectangleToClient(selectionBounds);
} }
@ -192,6 +201,7 @@ internal sealed class CustomTreeView : TreeView
selectForm.OnNodeRightClick(pair.Key, e.Location); selectForm.OnNodeRightClick(pair.Key, e.Location);
break; break;
} }
if (e.Button is not MouseButtons.Left) if (e.Button is not MouseButtons.Left)
return; return;
if (comboBoxBounds.Count > 0 && selectForm is not null) if (comboBoxBounds.Count > 0 && selectForm is not null)
@ -200,11 +210,12 @@ internal sealed class CustomTreeView : TreeView
_ = comboBoxBounds.Remove(pair.Key); _ = comboBoxBounds.Remove(pair.Key);
else if (pair.Value.Contains(clickPoint)) else if (pair.Value.Contains(clickPoint))
{ {
HashSet<string> proxies = EmbeddedResources.Where(r => r.StartsWith("Koaloader", StringComparison.Ordinal)).Select(p => HashSet<string> proxies = EmbeddedResources
{ .Where(r => r.StartsWith("Koaloader", StringComparison.Ordinal)).Select(p =>
p.GetProxyInfoFromIdentifier(out string proxyName, out _); {
return proxyName; p.GetProxyInfoFromIdentifier(out string proxyName, out _);
}).ToHashSet(); return proxyName;
}).ToHashSet();
comboBoxDropDown ??= new(); comboBoxDropDown ??= new();
comboBoxDropDown.ShowItemToolTips = false; comboBoxDropDown.ShowItemToolTips = false;
comboBoxDropDown.Items.Clear(); comboBoxDropDown.Items.Clear();
@ -219,6 +230,7 @@ internal sealed class CustomTreeView : TreeView
canUse = false; canUse = false;
break; break;
} }
if (canUse) if (canUse)
_ = comboBoxDropDown.Items.Add(new ToolStripButton(proxy + ".dll", null, (_, _) => _ = comboBoxDropDown.Items.Add(new ToolStripButton(proxy + ".dll", null, (_, _) =>
{ {
@ -226,9 +238,11 @@ internal sealed class CustomTreeView : TreeView
selectForm.OnKoaloaderChanged(); selectForm.OnKoaloaderChanged();
}) { Font = comboBoxFont }); }) { Font = comboBoxFont });
} }
comboBoxDropDown.Show(this, PointToScreen(new(pair.Value.Left, pair.Value.Bottom - 1))); comboBoxDropDown.Show(this, PointToScreen(new(pair.Value.Left, pair.Value.Bottom - 1)));
break; break;
} }
foreach (KeyValuePair<Selection, Rectangle> pair in checkBoxBounds) foreach (KeyValuePair<Selection, Rectangle> pair in checkBoxBounds)
if (!Selection.All.ContainsKey(pair.Key)) if (!Selection.All.ContainsKey(pair.Key))
_ = checkBoxBounds.Remove(pair.Key); _ = checkBoxBounds.Remove(pair.Key);

View file

@ -4,7 +4,7 @@
<TargetFramework>net8.0-windows10.0.22621.0</TargetFramework> <TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
<UseWindowsForms>True</UseWindowsForms> <UseWindowsForms>True</UseWindowsForms>
<ApplicationIcon>Resources\program.ico</ApplicationIcon> <ApplicationIcon>Resources\program.ico</ApplicationIcon>
<Version>4.10.1</Version> <Version>4.10.2</Version>
<Copyright>2021, pointfeev (https://github.com/pointfeev)</Copyright> <Copyright>2021, pointfeev (https://github.com/pointfeev)</Copyright>
<Company>CreamInstaller</Company> <Company>CreamInstaller</Company>
<Product>Automatic DLC Unlocker Installer &amp; Configuration Generator</Product> <Product>Automatic DLC Unlocker Installer &amp; Configuration Generator</Product>

View file

@ -36,6 +36,7 @@ internal sealed partial class DebugForm : CustomForm
if (command == 0xF010) // SC_MOVE if (command == 0xF010) // SC_MOVE
return; return;
} }
base.WndProc(ref message); base.WndProc(ref message);
} }
@ -48,6 +49,7 @@ internal sealed partial class DebugForm : CustomForm
attachedForm.SizeChanged -= OnChange; attachedForm.SizeChanged -= OnChange;
attachedForm.VisibleChanged -= OnChange; attachedForm.VisibleChanged -= OnChange;
} }
attachedForm = form; attachedForm = form;
attachedForm.Activated += OnChange; attachedForm.Activated += OnChange;
attachedForm.LocationChanged += OnChange; attachedForm.LocationChanged += OnChange;

View file

@ -11,7 +11,8 @@ internal sealed partial class DialogForm : CustomForm
{ {
internal DialogForm(IWin32Window owner) : base(owner) => InitializeComponent(); internal DialogForm(IWin32Window owner) : base(owner) => InitializeComponent();
internal DialogResult Show(Icon descriptionIcon, string descriptionText, string acceptButtonText = "OK", string cancelButtonText = null, internal DialogResult Show(Icon descriptionIcon, string descriptionText, string acceptButtonText = "OK",
string cancelButtonText = null,
string customFormText = null, Icon customFormIcon = null) string customFormText = null, Icon customFormIcon = null)
{ {
descriptionIcon ??= Icon; descriptionIcon ??= Icon;
@ -33,6 +34,7 @@ internal sealed partial class DialogForm : CustomForm
descriptionText = descriptionText.Remove(i, linkRight + 1 - i).Insert(i, text); descriptionText = descriptionText.Remove(i, linkRight + 1 - i).Insert(i, text);
links.Add(new(i, text.Length, link)); links.Add(new(i, text.Length, link));
} }
descriptionLabel.Text = descriptionText; descriptionLabel.Text = descriptionText;
acceptButton.Text = acceptButtonText; acceptButton.Text = acceptButtonText;
if (cancelButtonText is null) if (cancelButtonText is null)
@ -42,6 +44,7 @@ internal sealed partial class DialogForm : CustomForm
} }
else else
cancelButton.Text = cancelButtonText; cancelButton.Text = cancelButtonText;
if (customFormText is not null) if (customFormText is not null)
Text = customFormText; Text = customFormText;
else else
@ -49,6 +52,7 @@ internal sealed partial class DialogForm : CustomForm
OnResize(null, null); OnResize(null, null);
Resize += OnResize; Resize += OnResize;
} }
if (customFormIcon is not null) if (customFormIcon is not null)
Icon = customFormIcon; Icon = customFormIcon;
if (links.Count < 1) if (links.Count < 1)
@ -65,6 +69,8 @@ internal sealed partial class DialogForm : CustomForm
private void OnResize(object s, EventArgs e) private void OnResize(object s, EventArgs e)
=> Text = TextRenderer.MeasureText(Program.ApplicationName, Font).Width > Size.Width - 100 => Text = TextRenderer.MeasureText(Program.ApplicationName, Font).Width > Size.Width - 100
? TextRenderer.MeasureText(Program.ApplicationNameShort, Font).Width > Size.Width - 100 ? Program.Name : Program.ApplicationNameShort ? TextRenderer.MeasureText(Program.ApplicationNameShort, Font).Width > Size.Width - 100
? Program.Name
: Program.ApplicationNameShort
: Program.ApplicationName; : Program.ApplicationName;
} }

View file

@ -63,11 +63,14 @@ internal sealed partial class InstallForm : CustomForm
UpdateUser("Repairing Paradox Launcher . . . ", LogTextBox.Operation); UpdateUser("Repairing Paradox Launcher . . . ", LogTextBox.Operation);
_ = await Repair(this, selection); _ = await Repair(this, selection);
} }
UpdateUser( UpdateUser(
$"{(uninstalling ? "Uninstalling" : "Installing")}" + $" {(uninstalling ? "from" : "for")} " + selection.Name $"{(uninstalling ? "Uninstalling" : "Installing")}" + $" {(uninstalling ? "from" : "for")} " +
+ $" with root directory \"{selection.RootDirectory}\" . . . ", LogTextBox.Operation); selection.Name
+ $" with root directory \"{selection.RootDirectory}\" . . . ", LogTextBox.Operation);
IEnumerable<string> invalidDirectories = (await selection.RootDirectory.GetExecutables()) IEnumerable<string> invalidDirectories = (await selection.RootDirectory.GetExecutables())
?.Where(d => selection.ExecutableDirectories.All(s => s.directory != Path.GetDirectoryName(d.path))).Select(d => Path.GetDirectoryName(d.path)); ?.Where(d => selection.ExecutableDirectories.All(s => s.directory != Path.GetDirectoryName(d.path)))
.Select(d => Path.GetDirectoryName(d.path));
if (selection.ExecutableDirectories.All(s => s.directory != selection.RootDirectory)) if (selection.ExecutableDirectories.All(s => s.directory != selection.RootDirectory))
invalidDirectories = invalidDirectories?.Append(selection.RootDirectory); invalidDirectories = invalidDirectories?.Append(selection.RootDirectory);
invalidDirectories = invalidDirectories?.Distinct(); invalidDirectories = invalidDirectories?.Distinct();
@ -77,27 +80,37 @@ internal sealed partial class InstallForm : CustomForm
if (Program.Canceled) if (Program.Canceled)
return; return;
directory.GetKoaloaderComponents(out string old_config, out string config); directory.GetKoaloaderComponents(out string old_config, out string config);
if (directory.GetKoaloaderProxies().Any(proxy => proxy.FileExists() && proxy.IsResourceFile(ResourceIdentifier.Koaloader)) if (directory.GetKoaloaderProxies().Any(proxy =>
|| directory != selection.RootDirectory && Koaloader.AutoLoadDLLs.Any(pair => (directory + @"\" + pair.dll).FileExists()) proxy.FileExists() && proxy.IsResourceFile(ResourceIdentifier.Koaloader))
|| old_config.FileExists() || config.FileExists()) || directory != selection.RootDirectory &&
Koaloader.AutoLoadDLLs.Any(pair => (directory + @"\" + pair.dll).FileExists())
|| old_config.FileExists() || config.FileExists())
{ {
UpdateUser("Uninstalling Koaloader from " + selection.Name + $" in incorrect directory \"{directory}\" . . . ", LogTextBox.Operation); UpdateUser(
"Uninstalling Koaloader from " + selection.Name +
$" in incorrect directory \"{directory}\" . . . ", LogTextBox.Operation);
await Koaloader.Uninstall(directory, selection.RootDirectory, this); await Koaloader.Uninstall(directory, selection.RootDirectory, this);
} }
} }
if (uninstalling || !selection.Koaloader) if (uninstalling || !selection.Koaloader)
foreach ((string directory, BinaryType _) in selection.ExecutableDirectories) foreach ((string directory, BinaryType _) in selection.ExecutableDirectories)
{ {
if (Program.Canceled) if (Program.Canceled)
return; return;
directory.GetKoaloaderComponents(out string old_config, out string config); directory.GetKoaloaderComponents(out string old_config, out string config);
if (directory.GetKoaloaderProxies().Any(proxy => proxy.FileExists() && proxy.IsResourceFile(ResourceIdentifier.Koaloader)) if (directory.GetKoaloaderProxies().Any(proxy =>
|| Koaloader.AutoLoadDLLs.Any(pair => (directory + @"\" + pair.dll).FileExists()) || old_config.FileExists() || config.FileExists()) proxy.FileExists() && proxy.IsResourceFile(ResourceIdentifier.Koaloader))
|| Koaloader.AutoLoadDLLs.Any(pair => (directory + @"\" + pair.dll).FileExists()) ||
old_config.FileExists() || config.FileExists())
{ {
UpdateUser("Uninstalling Koaloader from " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation); UpdateUser(
"Uninstalling Koaloader from " + selection.Name + $" in directory \"{directory}\" . . . ",
LogTextBox.Operation);
await Koaloader.Uninstall(directory, selection.RootDirectory, this); await Koaloader.Uninstall(directory, selection.RootDirectory, this);
} }
} }
bool uninstallProxy = uninstalling || selection.Koaloader; bool uninstallProxy = uninstalling || selection.Koaloader;
int count = selection.DllDirectories.Count, cur = 0; int count = selection.DllDirectories.Count, cur = 0;
foreach (string directory in selection.DllDirectories) foreach (string directory in selection.DllDirectories)
@ -106,77 +119,93 @@ internal sealed partial class InstallForm : CustomForm
return; return;
if (selection.Platform is Platform.Steam or Platform.Paradox) if (selection.Platform is Platform.Steam or Platform.Paradox)
{ {
directory.GetSmokeApiComponents(out string api32, out string api32_o, out string api64, out string api64_o, out string old_config, directory.GetSmokeApiComponents(out string api32, out string api32_o, out string api64,
out string api64_o, out string old_config,
out string config, out string old_log, out string log, out string cache); out string config, out string old_log, out string log, out string cache);
if (uninstallProxy if (uninstallProxy
? api32_o.FileExists() || api64_o.FileExists() || old_config.FileExists() || config.FileExists() || old_log.FileExists() || log.FileExists() ? api32_o.FileExists() || api64_o.FileExists() || old_config.FileExists() ||
|| cache.FileExists() config.FileExists() || old_log.FileExists() || log.FileExists()
: api32.FileExists() || api64.FileExists()) || cache.FileExists()
: api32.FileExists() || api64.FileExists())
{ {
UpdateUser( UpdateUser(
$"{(uninstallProxy ? "Uninstalling" : "Installing")} SmokeAPI" + $" {(uninstallProxy ? "from" : "for")} " + selection.Name $"{(uninstallProxy ? "Uninstalling" : "Installing")} SmokeAPI" +
+ $" in directory \"{directory}\" . . . ", LogTextBox.Operation); $" {(uninstallProxy ? "from" : "for")} " + selection.Name
+ $" in directory \"{directory}\" . . . ", LogTextBox.Operation);
if (uninstallProxy) if (uninstallProxy)
await SmokeAPI.Uninstall(directory, this); await SmokeAPI.Uninstall(directory, this);
else else
await SmokeAPI.Install(directory, selection, this); await SmokeAPI.Install(directory, selection, this);
} }
} }
if (selection.Platform is Platform.Epic or Platform.Paradox) if (selection.Platform is Platform.Epic or Platform.Paradox)
{ {
directory.GetScreamApiComponents(out string api32, out string api32_o, out string api64, out string api64_o, out string config, out string log); directory.GetScreamApiComponents(out string api32, out string api32_o, out string api64,
out string api64_o, out string config, out string log);
if (uninstallProxy if (uninstallProxy
? api32_o.FileExists() || api64_o.FileExists() || config.FileExists() || log.FileExists() ? api32_o.FileExists() || api64_o.FileExists() || config.FileExists() || log.FileExists()
: api32.FileExists() || api64.FileExists()) : api32.FileExists() || api64.FileExists())
{ {
UpdateUser( UpdateUser(
$"{(uninstallProxy ? "Uninstalling" : "Installing")} ScreamAPI" + $" {(uninstallProxy ? "from" : "for")} " + selection.Name $"{(uninstallProxy ? "Uninstalling" : "Installing")} ScreamAPI" +
+ $" in directory \"{directory}\" . . . ", LogTextBox.Operation); $" {(uninstallProxy ? "from" : "for")} " + selection.Name
+ $" in directory \"{directory}\" . . . ", LogTextBox.Operation);
if (uninstallProxy) if (uninstallProxy)
await ScreamAPI.Uninstall(directory, this); await ScreamAPI.Uninstall(directory, this);
else else
await ScreamAPI.Install(directory, selection, this); await ScreamAPI.Install(directory, selection, this);
} }
} }
if (selection.Platform is Platform.Ubisoft) if (selection.Platform is Platform.Ubisoft)
{ {
directory.GetUplayR1Components(out string api32, out string api32_o, out string api64, out string api64_o, out string config, out string log); directory.GetUplayR1Components(out string api32, out string api32_o, out string api64,
out string api64_o, out string config, out string log);
if (uninstallProxy if (uninstallProxy
? api32_o.FileExists() || api64_o.FileExists() || config.FileExists() || log.FileExists() ? api32_o.FileExists() || api64_o.FileExists() || config.FileExists() || log.FileExists()
: api32.FileExists() || api64.FileExists()) : api32.FileExists() || api64.FileExists())
{ {
UpdateUser( UpdateUser(
$"{(uninstallProxy ? "Uninstalling" : "Installing")} Uplay R1 Unlocker" + $" {(uninstallProxy ? "from" : "for")} " + selection.Name $"{(uninstallProxy ? "Uninstalling" : "Installing")} Uplay R1 Unlocker" +
+ $" in directory \"{directory}\" . . . ", LogTextBox.Operation); $" {(uninstallProxy ? "from" : "for")} " + selection.Name
+ $" in directory \"{directory}\" . . . ", LogTextBox.Operation);
if (uninstallProxy) if (uninstallProxy)
await UplayR1.Uninstall(directory, this); await UplayR1.Uninstall(directory, this);
else else
await UplayR1.Install(directory, selection, this); await UplayR1.Install(directory, selection, this);
} }
directory.GetUplayR2Components(out string old_api32, out string old_api64, out api32, out api32_o, out api64, out api64_o, out config, out log);
directory.GetUplayR2Components(out string old_api32, out string old_api64, out api32, out api32_o,
out api64, out api64_o, out config, out log);
if (uninstallProxy if (uninstallProxy
? api32_o.FileExists() || api64_o.FileExists() || config.FileExists() || log.FileExists() ? api32_o.FileExists() || api64_o.FileExists() || config.FileExists() || log.FileExists()
: old_api32.FileExists() || old_api64.FileExists() || api32.FileExists() || api64.FileExists()) : old_api32.FileExists() || old_api64.FileExists() || api32.FileExists() || api64.FileExists())
{ {
UpdateUser( UpdateUser(
$"{(uninstallProxy ? "Uninstalling" : "Installing")} Uplay R2 Unlocker" + $" {(uninstallProxy ? "from" : "for")} " + selection.Name $"{(uninstallProxy ? "Uninstalling" : "Installing")} Uplay R2 Unlocker" +
+ $" in directory \"{directory}\" . . . ", LogTextBox.Operation); $" {(uninstallProxy ? "from" : "for")} " + selection.Name
+ $" in directory \"{directory}\" . . . ", LogTextBox.Operation);
if (uninstallProxy) if (uninstallProxy)
await UplayR2.Uninstall(directory, this); await UplayR2.Uninstall(directory, this);
else else
await UplayR2.Install(directory, selection, this); await UplayR2.Install(directory, selection, this);
} }
} }
UpdateProgress(++cur / count * 100); UpdateProgress(++cur / count * 100);
} }
if (selection.Koaloader && !uninstalling) if (selection.Koaloader && !uninstalling)
foreach ((string directory, BinaryType binaryType) in selection.ExecutableDirectories) foreach ((string directory, BinaryType binaryType) in selection.ExecutableDirectories)
{ {
if (Program.Canceled) if (Program.Canceled)
return; return;
UpdateUser("Installing Koaloader to " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation); UpdateUser("Installing Koaloader to " + selection.Name + $" in directory \"{directory}\" . . . ",
LogTextBox.Operation);
await Koaloader.Install(directory, binaryType, selection, selection.RootDirectory, this); await Koaloader.Install(directory, binaryType, selection, selection.RootDirectory, this);
} }
UpdateProgress(100); UpdateProgress(100);
} }
@ -200,8 +229,10 @@ internal sealed partial class InstallForm : CustomForm
{ {
UpdateUser($"Operation failed for {selection.Name}: " + exception, LogTextBox.Error); UpdateUser($"Operation failed for {selection.Name}: " + exception, LogTextBox.Error);
} }
++completeOperationsCount; ++completeOperationsCount;
} }
Program.Cleanup(); Program.Cleanup();
int activeCount = activeSelections.Count; int activeCount = activeSelections.Count;
if (activeCount > 0) if (activeCount > 0)
@ -222,14 +253,19 @@ internal sealed partial class InstallForm : CustomForm
try try
{ {
await Operate(); await Operate();
UpdateUser($"DLC unlocker(s) successfully {(uninstalling ? "uninstalled" : "installed and generated")} for " + selectionCount + " program(s).", UpdateUser(
$"DLC unlocker(s) successfully {(uninstalling ? "uninstalled" : "installed and generated")} for " +
selectionCount + " program(s).",
LogTextBox.Success); LogTextBox.Success);
} }
catch (Exception exception) catch (Exception exception)
{ {
UpdateUser($"DLC unlocker {(uninstalling ? "uninstallation" : "installation and/or generation")} failed: " + exception, LogTextBox.Error); UpdateUser(
$"DLC unlocker {(uninstalling ? "uninstallation" : "installation and/or generation")} failed: " +
exception, LogTextBox.Error);
retryButton.Enabled = true; retryButton.Enabled = true;
} }
userProgressBar.Value = userProgressBar.Maximum; userProgressBar.Value = userProgressBar.Maximum;
acceptButton.Enabled = true; acceptButton.Enabled = true;
cancelButton.Enabled = false; cancelButton.Enabled = false;
@ -238,7 +274,7 @@ internal sealed partial class InstallForm : CustomForm
private void OnLoad(object sender, EventArgs a) private void OnLoad(object sender, EventArgs a)
{ {
retry: retry:
try try
{ {
userInfoLabel.Text = "Loading . . . "; userInfoLabel.Text = "Loading . . . ";
@ -249,6 +285,7 @@ internal sealed partial class InstallForm : CustomForm
selectionCount++; selectionCount++;
_ = activeSelections.Add(selection); _ = activeSelections.Add(selection);
} }
Start(); Start();
} }
catch (Exception e) catch (Exception e)

View file

@ -12,7 +12,8 @@ internal sealed partial class SelectDialogForm : CustomForm
private readonly List<(Platform platform, string id, string name)> selected = new(); private readonly List<(Platform platform, string id, string name)> selected = new();
internal SelectDialogForm(IWin32Window owner) : base(owner) => InitializeComponent(); internal SelectDialogForm(IWin32Window owner) : base(owner) => InitializeComponent();
internal DialogResult QueryUser(string groupBoxText, List<(Platform platform, string id, string name, bool alreadySelected)> potentialChoices, internal DialogResult QueryUser(string groupBoxText,
List<(Platform platform, string id, string name, bool alreadySelected)> potentialChoices,
out List<(Platform platform, string id, string name)> choices) out List<(Platform platform, string id, string name)> choices)
{ {
choices = null; choices = null;
@ -28,6 +29,7 @@ internal sealed partial class SelectDialogForm : CustomForm
OnTreeNodeChecked(node); OnTreeNodeChecked(node);
_ = selectionTreeView.Nodes.Add(node); _ = selectionTreeView.Nodes.Add(node);
} }
if (selected.Count < 1) if (selected.Count < 1)
OnLoad(null, null); OnLoad(null, null);
allCheckBox.CheckedChanged -= OnAllCheckBoxChanged; allCheckBox.CheckedChanged -= OnAllCheckBoxChanged;
@ -64,10 +66,13 @@ internal sealed partial class SelectDialogForm : CustomForm
} }
private void OnResize(object s, EventArgs e) private void OnResize(object s, EventArgs e)
=> Text = TextRenderer.MeasureText(Program.ApplicationName, Font).Width > Size.Width - 100 ? Program.ApplicationNameShort : Program.ApplicationName; => Text = TextRenderer.MeasureText(Program.ApplicationName, Font).Width > Size.Width - 100
? Program.ApplicationNameShort
: Program.ApplicationName;
private void OnSortCheckBoxChanged(object sender, EventArgs e) private void OnSortCheckBoxChanged(object sender, EventArgs e)
=> selectionTreeView.TreeViewNodeSorter = sortCheckBox.Checked ? PlatformIdComparer.NodeText : PlatformIdComparer.NodeName; => selectionTreeView.TreeViewNodeSorter =
sortCheckBox.Checked ? PlatformIdComparer.NodeText : PlatformIdComparer.NodeName;
private void OnAllCheckBoxChanged(object sender, EventArgs e) private void OnAllCheckBoxChanged(object sender, EventArgs e)
{ {
@ -77,6 +82,7 @@ internal sealed partial class SelectDialogForm : CustomForm
node.Checked = shouldCheck; node.Checked = shouldCheck;
OnTreeNodeChecked(node); OnTreeNodeChecked(node);
} }
allCheckBox.CheckedChanged -= OnAllCheckBoxChanged; allCheckBox.CheckedChanged -= OnAllCheckBoxChanged;
allCheckBox.Checked = shouldCheck; allCheckBox.Checked = shouldCheck;
allCheckBox.CheckedChanged += OnAllCheckBoxChanged; allCheckBox.CheckedChanged += OnAllCheckBoxChanged;
@ -96,7 +102,8 @@ internal sealed partial class SelectDialogForm : CustomForm
private void OnSave(object sender, EventArgs e) private void OnSave(object sender, EventArgs e)
{ {
ProgramData.WriteProgramChoices(selectionTreeView.Nodes.Cast<TreeNode>().Where(n => n.Checked).Select(node => ((Platform)node.Tag, node.Name))); ProgramData.WriteProgramChoices(selectionTreeView.Nodes.Cast<TreeNode>().Where(n => n.Checked)
.Select(node => ((Platform)node.Tag, node.Name)));
loadButton.Enabled = ProgramData.ReadProgramChoices() is not null; loadButton.Enabled = ProgramData.ReadProgramChoices() is not null;
} }
} }

View file

@ -50,7 +50,9 @@ internal sealed partial class SelectForm : CustomForm
} }
private static void UpdateRemaining(Label label, ConcurrentDictionary<string, string> list, string descriptor) private static void UpdateRemaining(Label label, ConcurrentDictionary<string, string> list, string descriptor)
=> label.Text = list.IsEmpty ? "" : $"Remaining {descriptor} ({list.Count}): " + string.Join(", ", list.Values).Replace("&", "&&"); => label.Text = list.IsEmpty
? ""
: $"Remaining {descriptor} ({list.Count}): " + string.Join(", ", list.Values).Replace("&", "&&");
private void UpdateRemainingGames() => UpdateRemaining(progressLabelGames, remainingGames, "games"); private void UpdateRemainingGames() => UpdateRemaining(progressLabelGames, remainingGames, "games");
@ -114,17 +116,20 @@ internal sealed partial class SelectForm : CustomForm
return; return;
int totalGameCount = 0; int totalGameCount = 0;
int completeGameCount = 0; int completeGameCount = 0;
void AddToRemainingGames(string gameName) void AddToRemainingGames(string gameName)
{ {
this.AddToRemainingGames(gameName); this.AddToRemainingGames(gameName);
progress.Report(-Interlocked.Increment(ref totalGameCount)); progress.Report(-Interlocked.Increment(ref totalGameCount));
progress.Report(completeGameCount); progress.Report(completeGameCount);
} }
void RemoveFromRemainingGames(string gameName) void RemoveFromRemainingGames(string gameName)
{ {
this.RemoveFromRemainingGames(gameName); this.RemoveFromRemainingGames(gameName);
progress.Report(Interlocked.Increment(ref completeGameCount)); progress.Report(Interlocked.Increment(ref completeGameCount));
} }
if (Program.Canceled) if (Program.Canceled)
return; return;
remainingGames.Clear(); // for display purposes only, otherwise ignorable remainingGames.Clear(); // for display purposes only, otherwise ignorable
@ -133,11 +138,14 @@ internal sealed partial class SelectForm : CustomForm
if (uninstallAll || programsToScan.Any(c => c.platform is Platform.Paradox)) if (uninstallAll || programsToScan.Any(c => c.platform is Platform.Paradox))
{ {
AddToRemainingGames("Paradox Launcher"); AddToRemainingGames("Paradox Launcher");
HashSet<string> dllDirectories = await ParadoxLauncher.InstallPath.GetDllDirectoriesFromGameDirectory(Platform.Paradox); HashSet<string> dllDirectories =
await ParadoxLauncher.InstallPath.GetDllDirectoriesFromGameDirectory(Platform.Paradox);
if (dllDirectories is not null) if (dllDirectories is not null)
{ {
Selection selection = Selection.GetOrCreate(Platform.Paradox, "PL", "Paradox Launcher", ParadoxLauncher.InstallPath, dllDirectories, Selection selection = Selection.GetOrCreate(Platform.Paradox, "PL", "Paradox Launcher",
await ParadoxLauncher.InstallPath.GetExecutableDirectories(validFunc: path => !Path.GetFileName(path).Contains("bootstrapper"))); ParadoxLauncher.InstallPath, dllDirectories,
await ParadoxLauncher.InstallPath.GetExecutableDirectories(validFunc: path =>
!Path.GetFileName(path).Contains("bootstrapper")));
if (uninstallAll) if (uninstallAll)
selection.Enabled = true; selection.Enabled = true;
else if (selection.TreeNode.TreeView is null) else if (selection.TreeNode.TreeView is null)
@ -145,40 +153,48 @@ internal sealed partial class SelectForm : CustomForm
RemoveFromRemainingGames("Paradox Launcher"); RemoveFromRemainingGames("Paradox Launcher");
} }
} }
int steamGamesToCheck; int steamGamesToCheck;
if (uninstallAll || programsToScan.Any(c => c.platform is Platform.Steam)) if (uninstallAll || programsToScan.Any(c => c.platform is Platform.Steam))
{ {
List<(string appId, string name, string branch, int buildId, string gameDirectory)> steamGames = await SteamLibrary.GetGames(); List<(string appId, string name, string branch, int buildId, string gameDirectory)> steamGames =
await SteamLibrary.GetGames();
steamGamesToCheck = steamGames.Count; steamGamesToCheck = steamGames.Count;
foreach ((string appId, string name, string branch, int buildId, string gameDirectory) in steamGames) foreach ((string appId, string name, string branch, int buildId, string gameDirectory) in steamGames)
{ {
if (Program.Canceled) if (Program.Canceled)
return; return;
if (!uninstallAll && (Program.IsGameBlocked(name, gameDirectory) || !programsToScan.Any(c => c.platform is Platform.Steam && c.id == appId))) if (!uninstallAll && (Program.IsGameBlocked(name, gameDirectory) ||
!programsToScan.Any(c => c.platform is Platform.Steam && c.id == appId)))
{ {
_ = Interlocked.Decrement(ref steamGamesToCheck); _ = Interlocked.Decrement(ref steamGamesToCheck);
continue; continue;
} }
AddToRemainingGames(name); AddToRemainingGames(name);
Task task = Task.Run(async () => Task task = Task.Run(async () =>
{ {
if (Program.Canceled) if (Program.Canceled)
return; return;
HashSet<string> dllDirectories = await gameDirectory.GetDllDirectoriesFromGameDirectory(Platform.Steam); HashSet<string> dllDirectories =
await gameDirectory.GetDllDirectoriesFromGameDirectory(Platform.Steam);
if (dllDirectories is null) if (dllDirectories is null)
{ {
_ = Interlocked.Decrement(ref steamGamesToCheck); _ = Interlocked.Decrement(ref steamGamesToCheck);
RemoveFromRemainingGames(name); RemoveFromRemainingGames(name);
return; return;
} }
if (uninstallAll) if (uninstallAll)
{ {
Selection bareSelection = Selection.GetOrCreate(Platform.Steam, appId, name, gameDirectory, dllDirectories, Selection bareSelection = Selection.GetOrCreate(Platform.Steam, appId, name, gameDirectory,
dllDirectories,
await gameDirectory.GetExecutableDirectories(true)); await gameDirectory.GetExecutableDirectories(true));
bareSelection.Enabled = true; bareSelection.Enabled = true;
RemoveFromRemainingGames(name); RemoveFromRemainingGames(name);
return; return;
} }
if (Program.Canceled) if (Program.Canceled)
return; return;
AppData appData = await SteamStore.QueryStoreAPI(appId); AppData appData = await SteamStore.QueryStoreAPI(appId);
@ -189,6 +205,7 @@ internal sealed partial class SelectForm : CustomForm
RemoveFromRemainingGames(name); RemoveFromRemainingGames(name);
return; return;
} }
if (Program.Canceled) if (Program.Canceled)
return; return;
ConcurrentDictionary<SelectionDLC, byte> dlc = new(); ConcurrentDictionary<SelectionDLC, byte> dlc = new();
@ -233,14 +250,20 @@ internal sealed partial class SelectForm : CustomForm
if (dlcAppInfo is not null) if (dlcAppInfo is not null)
{ {
dlcName = dlcAppInfo.Value.GetChild("common")?.GetChild("name")?.ToString(); dlcName = dlcAppInfo.Value.GetChild("common")?.GetChild("name")?.ToString();
string dlcIconStaticId = dlcAppInfo.Value.GetChild("common")?.GetChild("icon")?.ToString(); string dlcIconStaticId = dlcAppInfo.Value.GetChild("common")?.GetChild("icon")
dlcIconStaticId ??= dlcAppInfo.Value.GetChild("common")?.GetChild("logo_small")?.ToString(); ?.ToString();
dlcIconStaticId ??= dlcAppInfo.Value.GetChild("common")?.GetChild("logo")?.ToString(); dlcIconStaticId ??= dlcAppInfo.Value.GetChild("common")?.GetChild("logo_small")
?.ToString();
dlcIconStaticId ??= dlcAppInfo.Value.GetChild("common")?.GetChild("logo")
?.ToString();
if (dlcIconStaticId is not null) if (dlcIconStaticId is not null)
dlcIcon = IconGrabber.SteamAppImagesPath + @$"\{dlcAppId}\{dlcIconStaticId}.jpg"; dlcIcon = IconGrabber.SteamAppImagesPath +
fullGameAppId = dlcAppInfo.Value.GetChild("common")?.GetChild("parent")?.ToString(); @$"\{dlcAppId}\{dlcIconStaticId}.jpg";
fullGameAppId = dlcAppInfo.Value.GetChild("common")?.GetChild("parent")
?.ToString();
} }
} }
if (fullGameAppId != null && fullGameAppId != appId) if (fullGameAppId != null && fullGameAppId != appId)
{ {
string fullGameName = null; string fullGameName = null;
@ -258,29 +281,38 @@ internal sealed partial class SelectForm : CustomForm
VProperty fullGameAppInfo = await SteamCMD.GetAppInfo(fullGameAppId); VProperty fullGameAppInfo = await SteamCMD.GetAppInfo(fullGameAppId);
if (fullGameAppInfo is not null) if (fullGameAppInfo is not null)
{ {
fullGameName = fullGameAppInfo.Value.GetChild("common")?.GetChild("name")?.ToString(); fullGameName = fullGameAppInfo.Value.GetChild("common")?.GetChild("name")
string fullGameIconStaticId = fullGameAppInfo.Value.GetChild("common")?.GetChild("icon")?.ToString(); ?.ToString();
fullGameIconStaticId ??= fullGameAppInfo.Value.GetChild("common")?.GetChild("logo_small")?.ToString(); string fullGameIconStaticId = fullGameAppInfo.Value.GetChild("common")
fullGameIconStaticId ??= fullGameAppInfo.Value.GetChild("common")?.GetChild("logo")?.ToString(); ?.GetChild("icon")?.ToString();
fullGameIconStaticId ??= fullGameAppInfo.Value.GetChild("common")
?.GetChild("logo_small")?.ToString();
fullGameIconStaticId ??= fullGameAppInfo.Value.GetChild("common")
?.GetChild("logo")?.ToString();
if (fullGameIconStaticId is not null) if (fullGameIconStaticId is not null)
dlcIcon = IconGrabber.SteamAppImagesPath + @$"\{fullGameAppId}\{fullGameIconStaticId}.jpg"; dlcIcon = IconGrabber.SteamAppImagesPath +
@$"\{fullGameAppId}\{fullGameIconStaticId}.jpg";
} }
} }
if (Program.Canceled) if (Program.Canceled)
return; return;
if (!string.IsNullOrWhiteSpace(fullGameName)) if (!string.IsNullOrWhiteSpace(fullGameName))
{ {
SelectionDLC fullGameDlc = SelectionDLC.GetOrCreate(fullGameOnSteamStore ? DLCType.Steam : DLCType.SteamHidden, appId, SelectionDLC fullGameDlc = SelectionDLC.GetOrCreate(
fullGameOnSteamStore ? DLCType.Steam : DLCType.SteamHidden, appId,
fullGameAppId, fullGameName); fullGameAppId, fullGameName);
fullGameDlc.Icon = fullGameIcon; fullGameDlc.Icon = fullGameIcon;
_ = dlc.TryAdd(fullGameDlc, default); _ = dlc.TryAdd(fullGameDlc, default);
} }
} }
if (Program.Canceled) if (Program.Canceled)
return; return;
if (string.IsNullOrWhiteSpace(dlcName)) if (string.IsNullOrWhiteSpace(dlcName))
dlcName = "Unknown"; dlcName = "Unknown";
SelectionDLC _dlc = SelectionDLC.GetOrCreate(onSteamStore ? DLCType.Steam : DLCType.SteamHidden, appId, dlcAppId, dlcName); SelectionDLC _dlc = SelectionDLC.GetOrCreate(
onSteamStore ? DLCType.Steam : DLCType.SteamHidden, appId, dlcAppId, dlcName);
_dlc.Icon = dlcIcon; _dlc.Icon = dlcIcon;
_ = dlc.TryAdd(_dlc, default); _ = dlc.TryAdd(_dlc, default);
RemoveFromRemainingDLCs(dlcAppId); RemoveFromRemainingDLCs(dlcAppId);
@ -292,6 +324,7 @@ internal sealed partial class SelectForm : CustomForm
RemoveFromRemainingGames(name); RemoveFromRemainingGames(name);
return; return;
} }
if (Program.Canceled) if (Program.Canceled)
return; return;
foreach (Task task in dlcTasks) foreach (Task task in dlcTasks)
@ -300,19 +333,24 @@ internal sealed partial class SelectForm : CustomForm
return; return;
await task; await task;
} }
steamGamesToCheck = 0; steamGamesToCheck = 0;
if (dlc.IsEmpty) if (dlc.IsEmpty)
{ {
RemoveFromRemainingGames(name); RemoveFromRemainingGames(name);
return; return;
} }
Selection selection = Selection.GetOrCreate(Platform.Steam, appId, appData?.Name ?? name, gameDirectory, dllDirectories,
Selection selection = Selection.GetOrCreate(Platform.Steam, appId, appData?.Name ?? name,
gameDirectory, dllDirectories,
await gameDirectory.GetExecutableDirectories(true)); await gameDirectory.GetExecutableDirectories(true));
selection.Product = "https://store.steampowered.com/app/" + appId; selection.Product = "https://store.steampowered.com/app/" + appId;
selection.Icon = IconGrabber.SteamAppImagesPath + @$"\{appId}\{appInfo?.Value.GetChild("common")?.GetChild("icon")}.jpg"; selection.Icon = IconGrabber.SteamAppImagesPath +
@$"\{appId}\{appInfo?.Value.GetChild("common")?.GetChild("icon")}.jpg";
selection.SubIcon = appData?.HeaderImage ?? IconGrabber.SteamAppImagesPath selection.SubIcon = appData?.HeaderImage ?? IconGrabber.SteamAppImagesPath
+ @$"\{appId}\{appInfo?.Value.GetChild("common")?.GetChild("clienticon")}.ico"; + @$"\{appId}\{appInfo?.Value.GetChild("common")?.GetChild("clienticon")}.ico";
selection.Publisher = appData?.Publishers[0] ?? appInfo?.Value.GetChild("extended")?.GetChild("publisher")?.ToString(); selection.Publisher = appData?.Publishers[0] ??
appInfo?.Value.GetChild("extended")?.GetChild("publisher")?.ToString();
selection.Website = appData?.Website; selection.Website = appData?.Website;
if (Program.Canceled) if (Program.Canceled)
return; return;
@ -336,6 +374,7 @@ internal sealed partial class SelectForm : CustomForm
appTasks.Add(task); appTasks.Add(task);
} }
} }
if (uninstallAll || programsToScan.Any(c => c.platform is Platform.Epic)) if (uninstallAll || programsToScan.Any(c => c.platform is Platform.Epic))
{ {
List<Manifest> epicGames = await EpicLibrary.GetGames(); List<Manifest> epicGames = await EpicLibrary.GetGames();
@ -346,7 +385,8 @@ internal sealed partial class SelectForm : CustomForm
string directory = manifest.InstallLocation; string directory = manifest.InstallLocation;
if (Program.Canceled) if (Program.Canceled)
return; return;
if (!uninstallAll && (Program.IsGameBlocked(name, directory) || !programsToScan.Any(c => c.platform is Platform.Epic && c.id == @namespace))) if (!uninstallAll && (Program.IsGameBlocked(name, directory) ||
!programsToScan.Any(c => c.platform is Platform.Epic && c.id == @namespace)))
continue; continue;
AddToRemainingGames(name); AddToRemainingGames(name);
Task task = Task.Run(async () => Task task = Task.Run(async () =>
@ -359,19 +399,23 @@ internal sealed partial class SelectForm : CustomForm
RemoveFromRemainingGames(name); RemoveFromRemainingGames(name);
return; return;
} }
if (uninstallAll) if (uninstallAll)
{ {
Selection bareSelection = Selection.GetOrCreate(Platform.Epic, @namespace, name, directory, dllDirectories, Selection bareSelection = Selection.GetOrCreate(Platform.Epic, @namespace, name, directory,
dllDirectories,
await directory.GetExecutableDirectories(true)); await directory.GetExecutableDirectories(true));
bareSelection.Enabled = true; bareSelection.Enabled = true;
RemoveFromRemainingGames(name); RemoveFromRemainingGames(name);
return; return;
} }
if (Program.Canceled) if (Program.Canceled)
return; return;
List<Task> dlcTasks = new(); List<Task> dlcTasks = new();
ConcurrentDictionary<SelectionDLC, byte> catalogItems = new(); ConcurrentDictionary<SelectionDLC, byte> catalogItems = new();
List<(string id, string name, string product, string icon, string developer)> catalogIds = await EpicStore.QueryCatalog(@namespace); List<(string id, string name, string product, string icon, string developer)> catalogIds =
await EpicStore.QueryCatalog(@namespace);
if (catalogIds.Count > 0) if (catalogIds.Count > 0)
foreach ((string id, string name, string product, string icon, string developer) in catalogIds) foreach ((string id, string name, string product, string icon, string developer) in catalogIds)
{ {
@ -391,6 +435,7 @@ internal sealed partial class SelectForm : CustomForm
}); });
dlcTasks.Add(task); dlcTasks.Add(task);
} }
if (Program.Canceled) if (Program.Canceled)
return; return;
foreach (Task task in dlcTasks) foreach (Task task in dlcTasks)
@ -399,12 +444,15 @@ internal sealed partial class SelectForm : CustomForm
return; return;
await task; await task;
} }
if (catalogItems.IsEmpty) if (catalogItems.IsEmpty)
{ {
RemoveFromRemainingGames(name); RemoveFromRemainingGames(name);
return; return;
} }
Selection selection = Selection.GetOrCreate(Platform.Epic, @namespace, name, directory, dllDirectories,
Selection selection = Selection.GetOrCreate(Platform.Epic, @namespace, name, directory,
dllDirectories,
await directory.GetExecutableDirectories(true)); await directory.GetExecutableDirectories(true));
foreach ((SelectionDLC dlc, _) in catalogItems.Where(dlc => dlc.Key.Name == selection.Name)) foreach ((SelectionDLC dlc, _) in catalogItems.Where(dlc => dlc.Key.Name == selection.Name))
{ {
@ -414,6 +462,7 @@ internal sealed partial class SelectForm : CustomForm
selection.Icon = dlc.Icon; selection.Icon = dlc.Icon;
selection.Publisher = dlc.Publisher; selection.Publisher = dlc.Publisher;
} }
if (Program.Canceled) if (Program.Canceled)
return; return;
Invoke(delegate Invoke(delegate
@ -438,6 +487,7 @@ internal sealed partial class SelectForm : CustomForm
appTasks.Add(task); appTasks.Add(task);
} }
} }
if (uninstallAll || programsToScan.Any(c => c.platform is Platform.Ubisoft)) if (uninstallAll || programsToScan.Any(c => c.platform is Platform.Ubisoft))
{ {
List<(string gameId, string name, string gameDirectory)> ubisoftGames = await UbisoftLibrary.GetGames(); List<(string gameId, string name, string gameDirectory)> ubisoftGames = await UbisoftLibrary.GetGames();
@ -445,30 +495,36 @@ internal sealed partial class SelectForm : CustomForm
{ {
if (Program.Canceled) if (Program.Canceled)
return; return;
if (!uninstallAll && (Program.IsGameBlocked(name, gameDirectory) || !programsToScan.Any(c => c.platform is Platform.Ubisoft && c.id == gameId))) if (!uninstallAll && (Program.IsGameBlocked(name, gameDirectory) ||
!programsToScan.Any(c => c.platform is Platform.Ubisoft && c.id == gameId)))
continue; continue;
AddToRemainingGames(name); AddToRemainingGames(name);
Task task = Task.Run(async () => Task task = Task.Run(async () =>
{ {
if (Program.Canceled) if (Program.Canceled)
return; return;
HashSet<string> dllDirectories = await gameDirectory.GetDllDirectoriesFromGameDirectory(Platform.Ubisoft); HashSet<string> dllDirectories =
await gameDirectory.GetDllDirectoriesFromGameDirectory(Platform.Ubisoft);
if (dllDirectories is null) if (dllDirectories is null)
{ {
RemoveFromRemainingGames(name); RemoveFromRemainingGames(name);
return; return;
} }
if (uninstallAll) if (uninstallAll)
{ {
Selection bareSelection = Selection.GetOrCreate(Platform.Ubisoft, gameId, name, gameDirectory, dllDirectories, Selection bareSelection = Selection.GetOrCreate(Platform.Ubisoft, gameId, name, gameDirectory,
dllDirectories,
await gameDirectory.GetExecutableDirectories(true)); await gameDirectory.GetExecutableDirectories(true));
bareSelection.Enabled = true; bareSelection.Enabled = true;
RemoveFromRemainingGames(name); RemoveFromRemainingGames(name);
return; return;
} }
if (Program.Canceled) if (Program.Canceled)
return; return;
Selection selection = Selection.GetOrCreate(Platform.Ubisoft, gameId, name, gameDirectory, dllDirectories, Selection selection = Selection.GetOrCreate(Platform.Ubisoft, gameId, name, gameDirectory,
dllDirectories,
await gameDirectory.GetExecutableDirectories(true)); await gameDirectory.GetExecutableDirectories(true));
selection.Icon = IconGrabber.GetDomainFaviconUrl("store.ubi.com"); selection.Icon = IconGrabber.GetDomainFaviconUrl("store.ubi.com");
Invoke(delegate Invoke(delegate
@ -485,12 +541,14 @@ internal sealed partial class SelectForm : CustomForm
appTasks.Add(task); appTasks.Add(task);
} }
} }
foreach (Task task in appTasks) foreach (Task task in appTasks)
{ {
if (Program.Canceled) if (Program.Canceled)
return; return;
await task; await task;
} }
steamGamesToCheck = 0; steamGamesToCheck = 0;
} }
@ -522,19 +580,26 @@ internal sealed partial class SelectForm : CustomForm
List<(Platform platform, string id, string name, bool alreadySelected)> gameChoices = new(); List<(Platform platform, string id, string name, bool alreadySelected)> gameChoices = new();
if (ParadoxLauncher.InstallPath.DirectoryExists()) if (ParadoxLauncher.InstallPath.DirectoryExists())
gameChoices.Add((Platform.Paradox, "PL", "Paradox Launcher", gameChoices.Add((Platform.Paradox, "PL", "Paradox Launcher",
programsToScan is not null && programsToScan.Any(p => p.platform is Platform.Paradox && p.id == "PL"))); programsToScan is not null &&
programsToScan.Any(p => p.platform is Platform.Paradox && p.id == "PL")));
if (SteamLibrary.InstallPath.DirectoryExists()) if (SteamLibrary.InstallPath.DirectoryExists())
foreach ((string appId, string name, string _, int _, string _) in (await SteamLibrary.GetGames()).Where(g foreach ((string appId, string name, string _, int _, string _) in
=> !Program.IsGameBlocked(g.name, g.gameDirectory))) (await SteamLibrary.GetGames()).Where(g
=> !Program.IsGameBlocked(g.name, g.gameDirectory)))
gameChoices.Add((Platform.Steam, appId, name, gameChoices.Add((Platform.Steam, appId, name,
programsToScan is not null && programsToScan.Any(p => p.platform is Platform.Steam && p.id == appId))); programsToScan is not null &&
programsToScan.Any(p => p.platform is Platform.Steam && p.id == appId)));
if (EpicLibrary.EpicManifestsPath.DirectoryExists() || HeroicLibrary.HeroicLibraryPath.DirectoryExists()) if (EpicLibrary.EpicManifestsPath.DirectoryExists() || HeroicLibrary.HeroicLibraryPath.DirectoryExists())
gameChoices.AddRange((await EpicLibrary.GetGames()).Where(m => !Program.IsGameBlocked(m.DisplayName, m.InstallLocation)).Select(manifest gameChoices.AddRange((await EpicLibrary.GetGames())
=> (Platform.Epic, manifest.CatalogNamespace, manifest.DisplayName, .Where(m => !Program.IsGameBlocked(m.DisplayName, m.InstallLocation)).Select(manifest
programsToScan is not null && programsToScan.Any(p => p.platform is Platform.Epic && p.id == manifest.CatalogNamespace)))); => (Platform.Epic, manifest.CatalogNamespace, manifest.DisplayName,
foreach ((string gameId, string name, string _) in (await UbisoftLibrary.GetGames()).Where(g => !Program.IsGameBlocked(g.name, g.gameDirectory))) programsToScan is not null && programsToScan.Any(p =>
p.platform is Platform.Epic && p.id == manifest.CatalogNamespace))));
foreach ((string gameId, string name, string _) in (await UbisoftLibrary.GetGames()).Where(g =>
!Program.IsGameBlocked(g.name, g.gameDirectory)))
gameChoices.Add((Platform.Ubisoft, gameId, name, gameChoices.Add((Platform.Ubisoft, gameId, name,
programsToScan is not null && programsToScan.Any(p => p.platform is Platform.Ubisoft && p.id == gameId))); programsToScan is not null &&
programsToScan.Any(p => p.platform is Platform.Ubisoft && p.id == gameId)));
if (gameChoices.Count > 0) if (gameChoices.Count > 0)
{ {
using SelectDialogForm form = new(this); using SelectDialogForm form = new(this);
@ -569,6 +634,7 @@ internal sealed partial class SelectForm : CustomForm
} }
else else
scan = selectResult == DialogResult.OK && choices is not null && choices.Count > 0; scan = selectResult == DialogResult.OK && choices is not null && choices.Count > 0;
const string retry = "\n\nPress the \"Rescan\" button to re-choose."; const string retry = "\n\nPress the \"Rescan\" button to re-choose.";
if (scan) if (scan)
{ {
@ -581,6 +647,7 @@ internal sealed partial class SelectForm : CustomForm
else else
noneFoundLabel.Text = "No applicable programs nor games were found on your computer!"; noneFoundLabel.Text = "No applicable programs nor games were found on your computer!";
} }
if (scan) if (scan)
{ {
bool setup = true; bool setup = true;
@ -597,10 +664,14 @@ internal sealed partial class SelectForm : CustomForm
else else
curProgress = _progress; curProgress = _progress;
int p = Math.Max(Math.Min((int)((float)curProgress / maxProgress * 100), 100), 0); int p = Math.Max(Math.Min((int)((float)curProgress / maxProgress * 100), 100), 0);
progressLabel.Text = setup ? $"Setting up SteamCMD . . . {p}%" : $"Gathering and caching your applicable games and their DLCs . . . {p}%"; progressLabel.Text =
setup
? $"Setting up SteamCMD . . . {p}%"
: $"Gathering and caching your applicable games and their DLCs . . . {p}%";
progressBar.Value = p; progressBar.Value = p;
}; };
if (SteamLibrary.InstallPath.DirectoryExists() && programsToScan is not null && programsToScan.Any(c => c.platform is Platform.Steam)) if (SteamLibrary.InstallPath.DirectoryExists() && programsToScan is not null &&
programsToScan.Any(c => c.platform is Platform.Steam))
{ {
progressLabel.Text = "Setting up SteamCMD . . . "; progressLabel.Text = "Setting up SteamCMD . . . ";
if (!await SteamCMD.Setup(iProgress)) if (!await SteamCMD.Setup(iProgress))
@ -610,6 +681,7 @@ internal sealed partial class SelectForm : CustomForm
return; return;
} }
} }
setup = false; setup = false;
progressLabel.Text = "Gathering and caching your applicable games and their DLCs . . . "; progressLabel.Text = "Gathering and caching your applicable games and their DLCs . . . ";
Selection.ValidateAll(programsToScan); Selection.ValidateAll(programsToScan);
@ -618,6 +690,7 @@ internal sealed partial class SelectForm : CustomForm
await GetApplicablePrograms(iProgress); await GetApplicablePrograms(iProgress);
await SteamCMD.Cleanup(); await SteamCMD.Cleanup();
} }
OnLoadDlc(null, null); OnLoadDlc(null, null);
OnLoadKoaloader(null, null); OnLoadKoaloader(null, null);
HideProgressBar(); HideProgressBar();
@ -649,7 +722,8 @@ internal sealed partial class SelectForm : CustomForm
SyncNodeAncestors(node); SyncNodeAncestors(node);
SyncNodeDescendants(node); SyncNodeDescendants(node);
allCheckBox.CheckedChanged -= OnAllCheckBoxChanged; allCheckBox.CheckedChanged -= OnAllCheckBoxChanged;
allCheckBox.Checked = EnumerateTreeNodes(selectionTreeView.Nodes).All(node => node.Text == "Unknown" || node.Checked); allCheckBox.Checked = EnumerateTreeNodes(selectionTreeView.Nodes)
.All(node => node.Text == "Unknown" || node.Checked);
allCheckBox.CheckedChanged += OnAllCheckBoxChanged; allCheckBox.CheckedChanged += OnAllCheckBoxChanged;
installButton.Enabled = Selection.AllEnabled.Any(); installButton.Enabled = Selection.AllEnabled.Any();
uninstallButton.Enabled = installButton.Enabled; uninstallButton.Enabled = installButton.Enabled;
@ -699,8 +773,9 @@ internal sealed partial class SelectForm : CustomForm
progressBar.Visible = true; progressBar.Visible = true;
programsGroupBox.Size = programsGroupBox.Size with programsGroupBox.Size = programsGroupBox.Size with
{ {
Height = programsGroupBox.Size.Height - 3 - progressLabel.Size.Height - progressLabelGames.Size.Height - progressLabelDLCs.Size.Height Height = programsGroupBox.Size.Height - 3 - progressLabel.Size.Height - progressLabelGames.Size.Height -
- progressBar.Size.Height progressLabelDLCs.Size.Height
- progressBar.Size.Height
}; };
} }
@ -713,8 +788,9 @@ internal sealed partial class SelectForm : CustomForm
progressBar.Visible = false; progressBar.Visible = false;
programsGroupBox.Size = programsGroupBox.Size with programsGroupBox.Size = programsGroupBox.Size with
{ {
Height = programsGroupBox.Size.Height + 3 + progressLabel.Size.Height + progressLabelGames.Size.Height + progressLabelDLCs.Size.Height Height = programsGroupBox.Size.Height + 3 + progressLabel.Size.Height + progressLabelGames.Size.Height +
+ progressBar.Size.Height progressLabelDLCs.Size.Height
+ progressBar.Size.Height
}; };
} }
@ -753,10 +829,13 @@ internal sealed partial class SelectForm : CustomForm
: selection.Platform is Platform.Epic : selection.Platform is Platform.Epic
? "Epic GraphQL " ? "Epic GraphQL "
: ""; : "";
queries.Add(new($"Open {platformString}Query", "Notepad", (_, _) => Diagnostics.OpenFileInNotepad(appInfoJSON))); queries.Add(new($"Open {platformString}Query", "Notepad",
(_, _) => Diagnostics.OpenFileInNotepad(appInfoJSON)));
} }
if (appInfoVDF.FileExists()) if (appInfoVDF.FileExists())
queries.Add(new("Open SteamCMD Query", "Notepad", (_, _) => Diagnostics.OpenFileInNotepad(appInfoVDF))); queries.Add(new("Open SteamCMD Query", "Notepad",
(_, _) => Diagnostics.OpenFileInNotepad(appInfoVDF)));
if (queries.Count > 0) if (queries.Count > 0)
{ {
_ = items.Add(new ToolStripSeparator()); _ = items.Add(new ToolStripSeparator());
@ -774,62 +853,78 @@ internal sealed partial class SelectForm : CustomForm
})); }));
} }
} }
if (selection is not null) if (selection is not null)
{ {
if (id == "PL") if (id == "PL")
{ {
_ = items.Add(new ToolStripSeparator()); _ = items.Add(new ToolStripSeparator());
async void EventHandler(object sender, EventArgs e) async void EventHandler(object sender, EventArgs e)
{ {
_ = await ParadoxLauncher.Repair(this, selection); _ = await ParadoxLauncher.Repair(this, selection);
Program.Canceled = false; Program.Canceled = false;
} }
_ = items.Add(new ContextMenuItem("Repair", "Command Prompt", EventHandler)); _ = items.Add(new ContextMenuItem("Repair", "Command Prompt", EventHandler));
} }
_ = items.Add(new ToolStripSeparator()); _ = items.Add(new ToolStripSeparator());
_ = items.Add(new ContextMenuItem("Open Root Directory", "File Explorer", _ = items.Add(new ContextMenuItem("Open Root Directory", "File Explorer",
(_, _) => Diagnostics.OpenDirectoryInFileExplorer(selection.RootDirectory))); (_, _) => Diagnostics.OpenDirectoryInFileExplorer(selection.RootDirectory)));
int executables = 0; int executables = 0;
foreach ((string directory, BinaryType binaryType) in selection.ExecutableDirectories) foreach ((string directory, BinaryType binaryType) in selection.ExecutableDirectories)
_ = items.Add(new ContextMenuItem($"Open Executable Directory #{++executables} ({(binaryType == BinaryType.BIT32 ? "32" : "64")}-bit)", _ = items.Add(new ContextMenuItem(
$"Open Executable Directory #{++executables} ({(binaryType == BinaryType.BIT32 ? "32" : "64")}-bit)",
"File Explorer", (_, _) => Diagnostics.OpenDirectoryInFileExplorer(directory))); "File Explorer", (_, _) => Diagnostics.OpenDirectoryInFileExplorer(directory)));
HashSet<string> directories = selection.DllDirectories; HashSet<string> directories = selection.DllDirectories;
int steam = 0, epic = 0, r1 = 0, r2 = 0; int steam = 0, epic = 0, r1 = 0, r2 = 0;
if (selection.Platform is Platform.Steam or Platform.Paradox) if (selection.Platform is Platform.Steam or Platform.Paradox)
foreach (string directory in directories) foreach (string directory in directories)
{ {
directory.GetSmokeApiComponents(out string api32, out string api32_o, out string api64, out string api64_o, out string old_config, directory.GetSmokeApiComponents(out string api32, out string api32_o, out string api64,
out string api64_o, out string old_config,
out string config, out string old_log, out string log, out string cache); out string config, out string old_log, out string log, out string cache);
if (api32.FileExists() || api32_o.FileExists() || api64.FileExists() || api64_o.FileExists() || old_config.FileExists() if (api32.FileExists() || api32_o.FileExists() || api64.FileExists() || api64_o.FileExists() ||
|| config.FileExists() || old_log.FileExists() || log.FileExists() || cache.FileExists()) old_config.FileExists()
|| config.FileExists() || old_log.FileExists() || log.FileExists() || cache.FileExists())
_ = items.Add(new ContextMenuItem($"Open Steamworks Directory #{++steam}", "File Explorer", _ = items.Add(new ContextMenuItem($"Open Steamworks Directory #{++steam}", "File Explorer",
(_, _) => Diagnostics.OpenDirectoryInFileExplorer(directory))); (_, _) => Diagnostics.OpenDirectoryInFileExplorer(directory)));
} }
if (selection.Platform is Platform.Epic or Platform.Paradox) if (selection.Platform is Platform.Epic or Platform.Paradox)
foreach (string directory in directories) foreach (string directory in directories)
{ {
directory.GetScreamApiComponents(out string api32, out string api32_o, out string api64, out string api64_o, out string config, directory.GetScreamApiComponents(out string api32, out string api32_o, out string api64,
out string api64_o, out string config,
out string log); out string log);
if (api32.FileExists() || api32_o.FileExists() || api64.FileExists() || api64_o.FileExists() || config.FileExists() || log.FileExists()) if (api32.FileExists() || api32_o.FileExists() || api64.FileExists() || api64_o.FileExists() ||
config.FileExists() || log.FileExists())
_ = items.Add(new ContextMenuItem($"Open EOS Directory #{++epic}", "File Explorer", _ = items.Add(new ContextMenuItem($"Open EOS Directory #{++epic}", "File Explorer",
(_, _) => Diagnostics.OpenDirectoryInFileExplorer(directory))); (_, _) => Diagnostics.OpenDirectoryInFileExplorer(directory)));
} }
if (selection.Platform is Platform.Ubisoft) if (selection.Platform is Platform.Ubisoft)
foreach (string directory in directories) foreach (string directory in directories)
{ {
directory.GetUplayR1Components(out string api32, out string api32_o, out string api64, out string api64_o, out string config, directory.GetUplayR1Components(out string api32, out string api32_o, out string api64,
out string api64_o, out string config,
out string log); out string log);
if (api32.FileExists() || api32_o.FileExists() || api64.FileExists() || api64_o.FileExists() || config.FileExists() || log.FileExists()) if (api32.FileExists() || api32_o.FileExists() || api64.FileExists() || api64_o.FileExists() ||
config.FileExists() || log.FileExists())
_ = items.Add(new ContextMenuItem($"Open Uplay R1 Directory #{++r1}", "File Explorer", _ = items.Add(new ContextMenuItem($"Open Uplay R1 Directory #{++r1}", "File Explorer",
(_, _) => Diagnostics.OpenDirectoryInFileExplorer(directory))); (_, _) => Diagnostics.OpenDirectoryInFileExplorer(directory)));
directory.GetUplayR2Components(out string old_api32, out string old_api64, out api32, out api32_o, out api64, out api64_o, out config, directory.GetUplayR2Components(out string old_api32, out string old_api64, out api32,
out api32_o, out api64, out api64_o, out config,
out log); out log);
if (old_api32.FileExists() || old_api64.FileExists() || api32.FileExists() || api32_o.FileExists() || api64.FileExists() if (old_api32.FileExists() || old_api64.FileExists() || api32.FileExists() ||
|| api64_o.FileExists() || config.FileExists() || log.FileExists()) api32_o.FileExists() || api64.FileExists()
|| api64_o.FileExists() || config.FileExists() || log.FileExists())
_ = items.Add(new ContextMenuItem($"Open Uplay R2 Directory #{++r2}", "File Explorer", _ = items.Add(new ContextMenuItem($"Open Uplay R2 Directory #{++r2}", "File Explorer",
(_, _) => Diagnostics.OpenDirectoryInFileExplorer(directory))); (_, _) => Diagnostics.OpenDirectoryInFileExplorer(directory)));
} }
} }
if (id != "PL") if (id != "PL")
{ {
if (selection?.Platform is Platform.Steam || dlcParentSelection?.Platform is Platform.Steam) if (selection?.Platform is Platform.Steam || dlcParentSelection?.Platform is Platform.Steam)
@ -838,19 +933,23 @@ internal sealed partial class SelectForm : CustomForm
_ = items.Add(new ContextMenuItem("Open SteamDB", "SteamDB", _ = items.Add(new ContextMenuItem("Open SteamDB", "SteamDB",
(_, _) => Diagnostics.OpenUrlInInternetBrowser("https://steamdb.info/app/" + id))); (_, _) => Diagnostics.OpenUrlInInternetBrowser("https://steamdb.info/app/" + id)));
} }
if (selection is not null) if (selection is not null)
switch (selection.Platform) switch (selection.Platform)
{ {
case Platform.Steam: case Platform.Steam:
_ = items.Add(new ContextMenuItem("Open Steam Store", "Steam Store", _ = items.Add(new ContextMenuItem("Open Steam Store", "Steam Store",
(_, _) => Diagnostics.OpenUrlInInternetBrowser(selection.Product))); (_, _) => Diagnostics.OpenUrlInInternetBrowser(selection.Product)));
_ = items.Add(new ContextMenuItem("Open Steam Community", ("Sub_" + id, selection.SubIcon), "Steam Community", _ = items.Add(new ContextMenuItem("Open Steam Community", ("Sub_" + id, selection.SubIcon),
(_, _) => Diagnostics.OpenUrlInInternetBrowser("https://steamcommunity.com/app/" + id))); "Steam Community",
(_, _) => Diagnostics.OpenUrlInInternetBrowser("https://steamcommunity.com/app/" +
id)));
break; break;
case Platform.Epic: case Platform.Epic:
_ = items.Add(new ToolStripSeparator()); _ = items.Add(new ToolStripSeparator());
_ = items.Add(new ContextMenuItem("Open ScreamDB", "ScreamDB", _ = items.Add(new ContextMenuItem("Open ScreamDB", "ScreamDB",
(_, _) => Diagnostics.OpenUrlInInternetBrowser("https://scream-db.web.app/offers/" + id))); (_, _) => Diagnostics.OpenUrlInInternetBrowser("https://scream-db.web.app/offers/" +
id)));
_ = items.Add(new ContextMenuItem("Open Epic Games Store", "Epic Games", _ = items.Add(new ContextMenuItem("Open Epic Games Store", "Epic Games",
(_, _) => Diagnostics.OpenUrlInInternetBrowser(selection.Product))); (_, _) => Diagnostics.OpenUrlInInternetBrowser(selection.Product)));
break; break;
@ -858,12 +957,15 @@ internal sealed partial class SelectForm : CustomForm
_ = items.Add(new ToolStripSeparator()); _ = items.Add(new ToolStripSeparator());
_ = items.Add(new ContextMenuItem("Open Ubisoft Store", "Ubisoft Store", _ = items.Add(new ContextMenuItem("Open Ubisoft Store", "Ubisoft Store",
(_, _) => Diagnostics.OpenUrlInInternetBrowser( (_, _) => Diagnostics.OpenUrlInInternetBrowser(
"https://store.ubi.com/us/" + selection.Name.Replace(" ", "-").ToLowerInvariant()))); "https://store.ubi.com/us/" +
selection.Name.Replace(" ", "-").ToLowerInvariant())));
break; break;
} }
} }
if (selection?.Website is not null) if (selection?.Website is not null)
_ = items.Add(new ContextMenuItem("Open Official Website", ("Web_" + id, IconGrabber.GetDomainFaviconUrl(selection.Website)), _ = items.Add(new ContextMenuItem("Open Official Website",
("Web_" + id, IconGrabber.GetDomainFaviconUrl(selection.Website)),
(_, _) => Diagnostics.OpenUrlInInternetBrowser(selection.Website))); (_, _) => Diagnostics.OpenUrlInInternetBrowser(selection.Website)));
contextMenuStrip.Show(selectionTreeView, location); contextMenuStrip.Show(selectionTreeView, location);
contextMenuStrip.Refresh(); contextMenuStrip.Refresh();
@ -871,7 +973,7 @@ internal sealed partial class SelectForm : CustomForm
private void OnLoad(object sender, EventArgs _) private void OnLoad(object sender, EventArgs _)
{ {
retry: retry:
try try
{ {
HideProgressBar(); HideProgressBar();
@ -934,6 +1036,7 @@ internal sealed partial class SelectForm : CustomForm
selection.Enabled = shouldEnable; selection.Enabled = shouldEnable;
OnTreeViewNodeCheckedChanged(null, new(selection.TreeNode, TreeViewAction.ByMouse)); OnTreeViewNodeCheckedChanged(null, new(selection.TreeNode, TreeViewAction.ByMouse));
} }
allCheckBox.CheckedChanged -= OnAllCheckBoxChanged; allCheckBox.CheckedChanged -= OnAllCheckBoxChanged;
allCheckBox.Checked = shouldEnable; allCheckBox.Checked = shouldEnable;
allCheckBox.CheckedChanged += OnAllCheckBoxChanged; allCheckBox.CheckedChanged += OnAllCheckBoxChanged;
@ -953,19 +1056,23 @@ internal sealed partial class SelectForm : CustomForm
private bool AreSelectionsDefault() private bool AreSelectionsDefault()
=> EnumerateTreeNodes(selectionTreeView.Nodes).All(node => EnumerateTreeNodes(selectionTreeView.Nodes).All(node
=> node.Parent is null || node.Tag is not Platform and not DLCType || (node.Text == "Unknown" ? !node.Checked : node.Checked)); => node.Parent is null || node.Tag is not Platform and not DLCType ||
(node.Text == "Unknown" ? !node.Checked : node.Checked));
private bool CanSaveDlc() => installButton.Enabled && (ProgramData.ReadDlcChoices().Any() || !AreSelectionsDefault()); private bool CanSaveDlc() =>
installButton.Enabled && (ProgramData.ReadDlcChoices().Any() || !AreSelectionsDefault());
private void OnSaveDlc(object sender, EventArgs e) private void OnSaveDlc(object sender, EventArgs e)
{ {
List<(Platform platform, string gameId, string dlcId)> choices = ProgramData.ReadDlcChoices().ToList(); List<(Platform platform, string gameId, string dlcId)> choices = ProgramData.ReadDlcChoices().ToList();
foreach (SelectionDLC dlc in SelectionDLC.All.Keys) foreach (SelectionDLC dlc in SelectionDLC.All.Keys)
if ((dlc.Name == "Unknown" ? dlc.Enabled : !dlc.Enabled) if ((dlc.Name == "Unknown" ? dlc.Enabled : !dlc.Enabled)
&& !choices.Any(c => c.platform == dlc.Selection.Platform && c.gameId == dlc.Selection.Id && c.dlcId == dlc.Id)) && !choices.Any(c =>
c.platform == dlc.Selection.Platform && c.gameId == dlc.Selection.Id && c.dlcId == dlc.Id))
choices.Add((dlc.Selection.Platform, dlc.Selection.Id, dlc.Id)); choices.Add((dlc.Selection.Platform, dlc.Selection.Id, dlc.Id));
else else
_ = choices.RemoveAll(n => n.platform == dlc.Selection.Platform && n.gameId == dlc.Selection.Id && n.dlcId == dlc.Id); _ = choices.RemoveAll(n =>
n.platform == dlc.Selection.Platform && n.gameId == dlc.Selection.Id && n.dlcId == dlc.Id);
ProgramData.WriteDlcChoices(choices); ProgramData.WriteDlcChoices(choices);
loadButton.Enabled = CanLoadDlc(); loadButton.Enabled = CanLoadDlc();
saveButton.Enabled = CanSaveDlc(); saveButton.Enabled = CanSaveDlc();
@ -978,7 +1085,8 @@ internal sealed partial class SelectForm : CustomForm
List<(Platform platform, string gameId, string dlcId)> choices = ProgramData.ReadDlcChoices().ToList(); List<(Platform platform, string gameId, string dlcId)> choices = ProgramData.ReadDlcChoices().ToList();
foreach (SelectionDLC dlc in SelectionDLC.All.Keys) foreach (SelectionDLC dlc in SelectionDLC.All.Keys)
{ {
dlc.Enabled = choices.Any(c => c.platform == dlc.Selection?.Platform && c.gameId == dlc.Selection?.Id && c.dlcId == dlc.Id) dlc.Enabled = choices.Any(c =>
c.platform == dlc.Selection?.Platform && c.gameId == dlc.Selection?.Id && c.dlcId == dlc.Id)
? dlc.Name == "Unknown" ? dlc.Name == "Unknown"
: dlc.Name != "Unknown"; : dlc.Name != "Unknown";
OnTreeViewNodeCheckedChanged(null, new(dlc.TreeNode, TreeViewAction.ByMouse)); OnTreeViewNodeCheckedChanged(null, new(dlc.TreeNode, TreeViewAction.ByMouse));
@ -994,23 +1102,29 @@ internal sealed partial class SelectForm : CustomForm
dlc.Enabled = dlc.Name != "Unknown"; dlc.Enabled = dlc.Name != "Unknown";
OnTreeViewNodeCheckedChanged(null, new(dlc.TreeNode, TreeViewAction.ByMouse)); OnTreeViewNodeCheckedChanged(null, new(dlc.TreeNode, TreeViewAction.ByMouse));
} }
resetButton.Enabled = CanResetDlc(); resetButton.Enabled = CanResetDlc();
} }
private static bool AreKoaloaderSelectionsDefault() => Selection.All.Keys.All(selection => selection.Koaloader && selection.KoaloaderProxy is null); private static bool AreKoaloaderSelectionsDefault() =>
Selection.All.Keys.All(selection => selection.Koaloader && selection.KoaloaderProxy is null);
private static bool CanSaveKoaloader() => ProgramData.ReadKoaloaderChoices().Any() || !AreKoaloaderSelectionsDefault(); private static bool CanSaveKoaloader() =>
ProgramData.ReadKoaloaderChoices().Any() || !AreKoaloaderSelectionsDefault();
private void OnSaveKoaloader(object sender, EventArgs e) private void OnSaveKoaloader(object sender, EventArgs e)
{ {
List<(Platform platform, string id, string proxy, bool enabled)> choices = ProgramData.ReadKoaloaderChoices().ToList(); List<(Platform platform, string id, string proxy, bool enabled)> choices =
ProgramData.ReadKoaloaderChoices().ToList();
foreach (Selection selection in Selection.All.Keys) foreach (Selection selection in Selection.All.Keys)
{ {
_ = choices.RemoveAll(c => c.platform == selection.Platform && c.id == selection.Id); _ = choices.RemoveAll(c => c.platform == selection.Platform && c.id == selection.Id);
if (selection.KoaloaderProxy is not null and not Selection.DefaultKoaloaderProxy || !selection.Koaloader) if (selection.KoaloaderProxy is not null and not Selection.DefaultKoaloaderProxy || !selection.Koaloader)
choices.Add((selection.Platform, selection.Id, selection.KoaloaderProxy == Selection.DefaultKoaloaderProxy ? null : selection.KoaloaderProxy, choices.Add((selection.Platform, selection.Id,
selection.KoaloaderProxy == Selection.DefaultKoaloaderProxy ? null : selection.KoaloaderProxy,
selection.Koaloader)); selection.Koaloader));
} }
ProgramData.WriteKoaloaderProxyChoices(choices); ProgramData.WriteKoaloaderProxyChoices(choices);
saveKoaloaderButton.Enabled = CanSaveKoaloader(); saveKoaloaderButton.Enabled = CanSaveKoaloader();
loadKoaloaderButton.Enabled = CanLoadKoaloader(); loadKoaloaderButton.Enabled = CanLoadKoaloader();
@ -1020,7 +1134,8 @@ internal sealed partial class SelectForm : CustomForm
private void OnLoadKoaloader(object sender, EventArgs e) private void OnLoadKoaloader(object sender, EventArgs e)
{ {
List<(Platform platform, string id, string proxy, bool enabled)> choices = ProgramData.ReadKoaloaderChoices().ToList(); List<(Platform platform, string id, string proxy, bool enabled)> choices =
ProgramData.ReadKoaloaderChoices().ToList();
foreach (Selection selection in Selection.All.Keys) foreach (Selection selection in Selection.All.Keys)
if (choices.Any(c => c.platform == selection.Platform && c.id == selection.Id)) if (choices.Any(c => c.platform == selection.Platform && c.id == selection.Id))
{ {
@ -1045,6 +1160,7 @@ internal sealed partial class SelectForm : CustomForm
selection.Koaloader = true; selection.Koaloader = true;
selection.KoaloaderProxy = null; selection.KoaloaderProxy = null;
} }
ProgramData.WriteKoaloaderProxyChoices(choices); ProgramData.WriteKoaloaderProxyChoices(choices);
loadKoaloaderButton.Enabled = CanLoadKoaloader(); loadKoaloaderButton.Enabled = CanLoadKoaloader();
OnKoaloaderChanged(); OnKoaloaderChanged();
@ -1059,6 +1175,7 @@ internal sealed partial class SelectForm : CustomForm
selection.Koaloader = true; selection.Koaloader = true;
selection.KoaloaderProxy = null; selection.KoaloaderProxy = null;
} }
OnKoaloaderChanged(); OnKoaloaderChanged();
} }
@ -1092,13 +1209,19 @@ internal sealed partial class SelectForm : CustomForm
using DialogForm form = new(this); using DialogForm form = new(this);
_ = form.Show(SystemIcons.Information, _ = form.Show(SystemIcons.Information,
"Blocks the program from caching and displaying games protected by anti-cheats." "Blocks the program from caching and displaying games protected by anti-cheats."
+ "\nYou disable this option and install DLC unlockers to protected games at your own risk!" + "\n\nBlocked games: " + "\nYou disable this option and install DLC unlockers to protected games at your own risk!" +
+ (string.IsNullOrWhiteSpace(blockedGames.ToString()) ? "(none)" : blockedGames) + "\n\nBlocked game sub-directories: " "\n\nBlocked games: "
+ (string.IsNullOrWhiteSpace(blockedDirectories.ToString()) ? "(none)" : blockedDirectories) + "\n\nBlocked game sub-directory exceptions: " + (string.IsNullOrWhiteSpace(blockedGames.ToString()) ? "(none)" : blockedGames) +
+ (string.IsNullOrWhiteSpace(blockedDirectoryExceptions.ToString()) ? "(none)" : blockedDirectoryExceptions), "\n\nBlocked game sub-directories: "
+ (string.IsNullOrWhiteSpace(blockedDirectories.ToString()) ? "(none)" : blockedDirectories) +
"\n\nBlocked game sub-directory exceptions: "
+ (string.IsNullOrWhiteSpace(blockedDirectoryExceptions.ToString())
? "(none)"
: blockedDirectoryExceptions),
customFormText: "Block Protected Games"); customFormText: "Block Protected Games");
} }
private void OnSortCheckBoxChanged(object sender, EventArgs e) private void OnSortCheckBoxChanged(object sender, EventArgs e)
=> selectionTreeView.TreeViewNodeSorter = sortCheckBox.Checked ? PlatformIdComparer.NodeText : PlatformIdComparer.NodeName; => selectionTreeView.TreeViewNodeSorter =
sortCheckBox.Checked ? PlatformIdComparer.NodeText : PlatformIdComparer.NodeName;
} }

View file

@ -50,16 +50,21 @@ internal sealed partial class UpdateForm : CustomForm
updateButton.Click -= OnUpdateCancel; updateButton.Click -= OnUpdateCancel;
progressLabel.Text = "Checking for updates . . ."; progressLabel.Text = "Checking for updates . . .";
changelogTreeView.Visible = false; changelogTreeView.Visible = false;
changelogTreeView.Location = progressLabel.Location with { Y = progressLabel.Location.Y + progressLabel.Size.Height + 13 }; changelogTreeView.Location = progressLabel.Location with
{
Y = progressLabel.Location.Y + progressLabel.Size.Height + 13
};
Refresh(); Refresh();
#if !DEBUG #if !DEBUG
Version currentVersion = new(Program.Version); Version currentVersion = new(Program.Version);
#endif #endif
List<ProgramRelease> releases = null; List<ProgramRelease> releases = null;
string response = await HttpClientManager.EnsureGet($"https://api.github.com/repos/{Program.RepositoryOwner}/{Program.RepositoryName}/releases"); string response =
await HttpClientManager.EnsureGet(
$"https://api.github.com/repos/{Program.RepositoryOwner}/{Program.RepositoryName}/releases");
if (response is not null) if (response is not null)
releases = JsonConvert.DeserializeObject<List<ProgramRelease>>(response) releases = JsonConvert.DeserializeObject<List<ProgramRelease>>(response)
?.Where(release => !release.Draft && !release.Prerelease && release.Asset is not null).ToList(); ?.Where(release => !release.Draft && !release.Prerelease && release.Asset is not null).ToList();
latestRelease = releases?.FirstOrDefault(); latestRelease = releases?.FirstOrDefault();
#if DEBUG #if DEBUG
if (latestRelease?.Version is not { } latestVersion) if (latestRelease?.Version is not { } latestVersion)
@ -101,7 +106,7 @@ internal sealed partial class UpdateForm : CustomForm
private void OnLoad(object sender, EventArgs _) private void OnLoad(object sender, EventArgs _)
{ {
retry: retry:
try try
{ {
UpdaterPath.DeleteFile(); UpdaterPath.DeleteFile();
@ -125,7 +130,8 @@ internal sealed partial class UpdateForm : CustomForm
updateButton.Text = "Cancel"; updateButton.Text = "Cancel";
updateButton.Click -= OnUpdate; updateButton.Click -= OnUpdate;
updateButton.Click += OnUpdateCancel; updateButton.Click += OnUpdateCancel;
changelogTreeView.Location = progressBar.Location with { Y = progressBar.Location.Y + progressBar.Size.Height + 6 }; changelogTreeView.Location =
progressBar.Location with { Y = progressBar.Location.Y + progressBar.Size.Height + 6 };
Refresh(); Refresh();
Progress<int> progress = new(); Progress<int> progress = new();
IProgress<int> iProgress = progress; IProgress<int> iProgress = progress;
@ -144,7 +150,8 @@ internal sealed partial class UpdateForm : CustomForm
{ {
if (cancellation is null || Program.Canceled) if (cancellation is null || Program.Canceled)
throw new TaskCanceledException(); throw new TaskCanceledException();
using HttpResponseMessage response = await HttpClientManager.HttpClient.GetAsync(latestRelease.Asset.BrowserDownloadUrl, using HttpResponseMessage response = await HttpClientManager.HttpClient.GetAsync(
latestRelease.Asset.BrowserDownloadUrl,
HttpCompletionOption.ResponseHeadersRead, cancellation.Token); HttpCompletionOption.ResponseHeadersRead, cancellation.Token);
_ = response.EnsureSuccessStatusCode(); _ = response.EnsureSuccessStatusCode();
if (cancellation is null || Program.Canceled) if (cancellation is null || Program.Canceled)
@ -155,7 +162,8 @@ internal sealed partial class UpdateForm : CustomForm
long bytesRead = 0; long bytesRead = 0;
int newBytes; int newBytes;
while (cancellation is not null && !Program.Canceled while (cancellation is not null && !Program.Canceled
&& (newBytes = await download.ReadAsync(buffer.AsMemory(0, buffer.Length), cancellation.Token)) != 0) && (newBytes = await download.ReadAsync(buffer.AsMemory(0, buffer.Length),
cancellation.Token)) != 0)
{ {
if (cancellation is null || Program.Canceled) if (cancellation is null || Program.Canceled)
throw new TaskCanceledException(); throw new TaskCanceledException();
@ -166,6 +174,7 @@ internal sealed partial class UpdateForm : CustomForm
continue; continue;
iProgress.Report(report); iProgress.Report(report);
} }
iProgress.Report((int)(bytesRead / bytes * 100)); iProgress.Report((int)(bytesRead / bytes * 100));
if (cancellation is null || Program.Canceled) if (cancellation is null || Program.Canceled)
throw new TaskCanceledException(); throw new TaskCanceledException();
@ -179,6 +188,7 @@ internal sealed partial class UpdateForm : CustomForm
retry = ex.HandleException(this, Program.Name + " encountered an exception while updating"); retry = ex.HandleException(this, Program.Name + " encountered an exception while updating");
success = false; success = false;
} }
cancellation?.Dispose(); cancellation?.Dispose();
cancellation = null; cancellation = null;
await update.DisposeAsync(); await update.DisposeAsync();
@ -209,13 +219,15 @@ internal sealed partial class UpdateForm : CustomForm
Process process = new(); Process process = new();
ProcessStartInfo startInfo = new() ProcessStartInfo startInfo = new()
{ {
WorkingDirectory = ProgramData.DirectoryPath, FileName = "cmd.exe", Arguments = $"/C START \"UPDATER\" /B {Path.GetFileName(UpdaterPath)}", WorkingDirectory = ProgramData.DirectoryPath, FileName = "cmd.exe",
Arguments = $"/C START \"UPDATER\" /B {Path.GetFileName(UpdaterPath)}",
CreateNoWindow = true CreateNoWindow = true
}; };
process.StartInfo = startInfo; process.StartInfo = startInfo;
_ = process.Start(); _ = process.Start();
return; return;
} }
if (!retry) if (!retry)
StartProgram(); StartProgram();
else else

View file

@ -17,8 +17,11 @@ internal static class EpicLibrary
{ {
get get
{ {
epicManifestsPath ??= Registry.GetValue(@"HKEY_CURRENT_USER\Software\Epic Games\EOS", "ModSdkMetadataDir", null) as string; epicManifestsPath ??=
epicManifestsPath ??= Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Epic Games\EpicGamesLauncher", "AppDataPath", null) as string; Registry.GetValue(@"HKEY_CURRENT_USER\Software\Epic Games\EOS", "ModSdkMetadataDir", null) as string;
epicManifestsPath ??=
Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Epic Games\EpicGamesLauncher", "AppDataPath",
null) as string;
if (epicManifestsPath is not null && epicManifestsPath.EndsWith(@"\Data", StringComparison.Ordinal)) if (epicManifestsPath is not null && epicManifestsPath.EndsWith(@"\Data", StringComparison.Ordinal))
epicManifestsPath += @"\Manifests"; epicManifestsPath += @"\Manifests";
return epicManifestsPath.ResolvePath(); return epicManifestsPath.ResolvePath();
@ -39,8 +42,9 @@ internal static class EpicLibrary
try try
{ {
Manifest manifest = JsonConvert.DeserializeObject<Manifest>(json); Manifest manifest = JsonConvert.DeserializeObject<Manifest>(json);
if (manifest is not null && (manifest.InstallLocation = manifest.InstallLocation.ResolvePath()) is not null if (manifest is not null && (manifest.InstallLocation = manifest.InstallLocation.ResolvePath())
&& games.All(g => g.CatalogNamespace != manifest.CatalogNamespace)) is not null
&& games.All(g => g.CatalogNamespace != manifest.CatalogNamespace))
games.Add(manifest); games.Add(manifest);
} }
catch catch
@ -48,6 +52,7 @@ internal static class EpicLibrary
// ignored // ignored
} }
} }
if (Program.Canceled) if (Program.Canceled)
return games; return games;
await HeroicLibrary.GetGames(games); await HeroicLibrary.GetGames(games);

View file

@ -14,7 +14,8 @@ internal static class EpicStore
{ {
private const int Cooldown = 600; private const int Cooldown = 600;
internal static async Task<List<(string id, string name, string product, string icon, string developer)>> QueryCatalog(string categoryNamespace) internal static async Task<List<(string id, string name, string product, string icon, string developer)>>
QueryCatalog(string categoryNamespace)
{ {
List<(string id, string name, string product, string icon, string developer)> dlcIds = []; List<(string id, string name, string product, string icon, string developer)> dlcIds = [];
string cacheFile = ProgramData.AppInfoPath + @$"\{categoryNamespace}.json"; string cacheFile = ProgramData.AppInfoPath + @$"\{categoryNamespace}.json";
@ -41,13 +42,16 @@ internal static class EpicStore
{ {
cacheFile.DeleteFile(); cacheFile.DeleteFile();
} }
if (response is null) if (response is null)
return dlcIds; return dlcIds;
List<Element> searchStore = [..response.Data.Catalog.SearchStore.Elements]; List<Element> searchStore = [..response.Data.Catalog.SearchStore.Elements];
foreach (Element element in searchStore) foreach (Element element in searchStore)
{ {
string title = element.Title; string title = element.Title;
string product = element.CatalogNs is not null && element.CatalogNs.Mappings.Length > 0 ? element.CatalogNs.Mappings.First().PageSlug : null; string product = element.CatalogNs is not null && element.CatalogNs.Mappings.Length > 0
? element.CatalogNs.Mappings.First().PageSlug
: null;
string icon = null; string icon = null;
for (int i = 0; i < element.KeyImages?.Length; i++) for (int i = 0; i < element.KeyImages?.Length; i++)
{ {
@ -57,14 +61,18 @@ internal static class EpicStore
icon = keyImage.Url.ToString(); icon = keyImage.Url.ToString();
break; break;
} }
foreach (Item item in element.Items) foreach (Item item in element.Items)
dlcIds.Populate(item.Id, title, product, icon, null, element.Items.Length == 1); dlcIds.Populate(item.Id, title, product, icon, null, element.Items.Length == 1);
} }
List<Element> catalogOffers = [..response.Data.Catalog.CatalogOffers.Elements]; List<Element> catalogOffers = [..response.Data.Catalog.CatalogOffers.Elements];
foreach (Element element in catalogOffers) foreach (Element element in catalogOffers)
{ {
string title = element.Title; string title = element.Title;
string product = element.CatalogNs is not null && element.CatalogNs.Mappings.Length > 0 ? element.CatalogNs.Mappings.First().PageSlug : null; string product = element.CatalogNs is not null && element.CatalogNs.Mappings.Length > 0
? element.CatalogNs.Mappings.First().PageSlug
: null;
string icon = null; string icon = null;
for (int i = 0; i < element.KeyImages?.Length; i++) for (int i = 0; i < element.KeyImages?.Length; i++)
{ {
@ -74,13 +82,17 @@ internal static class EpicStore
icon = keyImage.Url.ToString(); icon = keyImage.Url.ToString();
break; break;
} }
foreach (Item item in element.Items) foreach (Item item in element.Items)
dlcIds.Populate(item.Id, title, product, icon, item.Developer, element.Items.Length == 1); dlcIds.Populate(item.Id, title, product, icon, item.Developer, element.Items.Length == 1);
} }
return dlcIds; return dlcIds;
} }
private static void Populate(this List<(string id, string name, string product, string icon, string developer)> dlcIds, string id, string title, private static void Populate(
this List<(string id, string name, string product, string icon, string developer)> dlcIds, string id,
string title,
string product, string icon, string developer, bool canOverwrite = false) string product, string icon, string developer, bool canOverwrite = false)
{ {
if (id == null) if (id == null)
@ -97,6 +109,7 @@ internal static class EpicStore
: (app.id, app.name ?? title, app.product ?? product, app.icon ?? icon, app.developer ?? developer); : (app.id, app.name ?? title, app.product ?? product, app.icon ?? icon, app.developer ?? developer);
break; break;
} }
if (!found) if (!found)
dlcIds.Add((id, title, product, icon, developer)); dlcIds.Add((id, title, product, icon, developer));
} }
@ -113,7 +126,8 @@ internal static class EpicStore
HttpClient client = HttpClientManager.HttpClient; HttpClient client = HttpClientManager.HttpClient;
if (client is null) if (client is null)
return null; return null;
HttpResponseMessage httpResponse = await client.PostAsync(new Uri("https://graphql.epicgames.com/graphql"), content); HttpResponseMessage httpResponse =
await client.PostAsync(new Uri("https://graphql.epicgames.com/graphql"), content);
_ = httpResponse.EnsureSuccessStatusCode(); _ = httpResponse.EnsureSuccessStatusCode();
string response = await httpResponse.Content.ReadAsStringAsync(); string response = await httpResponse.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<Response>(response); return JsonConvert.DeserializeObject<Response>(response);

View file

@ -10,7 +10,8 @@ namespace CreamInstaller.Platforms.Epic.Heroic;
internal static class HeroicLibrary internal static class HeroicLibrary
{ {
internal static readonly string HeroicLibraryPath internal static readonly string HeroicLibraryPath
= Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\heroic\store_cache\legendary_library.json"; = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) +
@"\heroic\store_cache\legendary_library.json";
internal static async Task GetGames(List<Manifest> games) internal static async Task GetGames(List<Manifest> games)
=> await Task.Run(() => => await Task.Run(() =>
@ -28,11 +29,13 @@ internal static class HeroicLibrary
try try
{ {
HeroicAppData appData = token.ToObject<HeroicAppData>(); HeroicAppData appData = token.ToObject<HeroicAppData>();
if (appData is null || string.IsNullOrWhiteSpace(appData.Install.InstallPath = appData.Install.InstallPath.ResolvePath())) if (appData is null || string.IsNullOrWhiteSpace(appData.Install.InstallPath =
appData.Install.InstallPath.ResolvePath()))
continue; continue;
Manifest manifest = new() Manifest manifest = new()
{ {
DisplayName = appData.Title, CatalogNamespace = appData.Namespace, InstallLocation = appData.Install.InstallPath DisplayName = appData.Title, CatalogNamespace = appData.Namespace,
InstallLocation = appData.Install.InstallPath
}; };
if (games.All(g => g.CatalogNamespace != manifest.CatalogNamespace)) if (games.All(g => g.CatalogNamespace != manifest.CatalogNamespace))
games.Add(manifest); games.Add(manifest);

View file

@ -13,7 +13,12 @@ namespace CreamInstaller.Platforms.Paradox;
internal static class ParadoxLauncher internal static class ParadoxLauncher
{ {
public enum RepairResult { Failure = -1, Unnecessary = 0, Success } public enum RepairResult
{
Failure = -1,
Unnecessary = 0,
Success
}
private static string installPath; private static string installPath;
@ -21,7 +26,8 @@ internal static class ParadoxLauncher
{ {
get get
{ {
installPath ??= Registry.GetValue(@"HKEY_CURRENT_USER\Software\Paradox Interactive\Paradox Launcher v2", "LauncherInstallation", null) as string; installPath ??= Registry.GetValue(@"HKEY_CURRENT_USER\Software\Paradox Interactive\Paradox Launcher v2",
"LauncherInstallation", null) as string;
return installPath.ResolvePath(); return installPath.ResolvePath();
} }
} }
@ -32,11 +38,13 @@ internal static class ParadoxLauncher
if (paradoxLauncher is null) if (paradoxLauncher is null)
return; return;
paradoxLauncher.ExtraSelections.Clear(); paradoxLauncher.ExtraSelections.Clear();
foreach (Selection selection in Selection.AllEnabled.Where(s => !s.Equals(paradoxLauncher) && s.Publisher == "Paradox Interactive")) foreach (Selection selection in Selection.AllEnabled.Where(s =>
!s.Equals(paradoxLauncher) && s.Publisher == "Paradox Interactive"))
_ = paradoxLauncher.ExtraSelections.Add(selection); _ = paradoxLauncher.ExtraSelections.Add(selection);
if (paradoxLauncher.ExtraSelections.Count > 0) if (paradoxLauncher.ExtraSelections.Count > 0)
return; return;
foreach (Selection selection in Selection.All.Keys.Where(s => !s.Equals(paradoxLauncher) && s.Publisher == "Paradox Interactive")) foreach (Selection selection in Selection.All.Keys.Where(s =>
!s.Equals(paradoxLauncher) && s.Publisher == "Paradox Interactive"))
_ = paradoxLauncher.ExtraSelections.Add(selection); _ = paradoxLauncher.ExtraSelections.Add(selection);
} }
@ -51,7 +59,8 @@ internal static class ParadoxLauncher
using DialogForm dialogForm = new(form); using DialogForm dialogForm = new(form);
return dialogForm.Show(SystemIcons.Warning, return dialogForm.Show(SystemIcons.Warning,
"WARNING: There are no scanned games with DLC that can be added to the Paradox Launcher!" "WARNING: There are no scanned games with DLC that can be added to the Paradox Launcher!"
+ "\n\nInstalling DLC unlockers for the Paradox Launcher alone can cause existing configurations to be deleted!", "Ignore", "Cancel", + "\n\nInstalling DLC unlockers for the Paradox Launcher alone can cause existing configurations to be deleted!",
"Ignore", "Cancel",
"Paradox Launcher") != DialogResult.OK; "Paradox Launcher") != DialogResult.OK;
} }
@ -64,6 +73,7 @@ internal static class ParadoxLauncher
Program.Canceled = false; Program.Canceled = false;
dialogText = new(); dialogText = new();
} }
using DialogForm dialogForm = new(form); using DialogForm dialogForm = new(form);
bool smokeInstalled = false; bool smokeInstalled = false;
byte[] steamOriginalSdk32 = null; byte[] steamOriginalSdk32 = null;
@ -73,36 +83,46 @@ internal static class ParadoxLauncher
byte[] epicOriginalSdk64 = null; byte[] epicOriginalSdk64 = null;
foreach (string directory in selection.DllDirectories.TakeWhile(_ => !Program.Canceled)) foreach (string directory in selection.DllDirectories.TakeWhile(_ => !Program.Canceled))
{ {
bool koaloaderInstalled = Koaloader.AutoLoadDLLs.Select(pair => (pair.unlocker, path: directory + @"\" + pair.dll)) bool koaloaderInstalled = Koaloader.AutoLoadDLLs
.Any(pair => pair.path.FileExists() && pair.path.IsResourceFile()); .Select(pair => (pair.unlocker, path: directory + @"\" + pair.dll))
directory.GetSmokeApiComponents(out string api32, out string api32_o, out string api64, out string api64_o, out string old_config, .Any(pair => pair.path.FileExists() && pair.path.IsResourceFile());
directory.GetSmokeApiComponents(out string api32, out string api32_o, out string api64, out string api64_o,
out string old_config,
out string config, out _, out _, out _); out string config, out _, out _, out _);
smokeInstalled = smokeInstalled || api32_o.FileExists() || api64_o.FileExists() smokeInstalled = smokeInstalled || api32_o.FileExists() || api64_o.FileExists()
|| (old_config.FileExists() || config.FileExists()) && !koaloaderInstalled || (old_config.FileExists() || config.FileExists()) && !koaloaderInstalled
|| api32.FileExists() && api32.IsResourceFile(ResourceIdentifier.Steamworks32) || api32.FileExists() && api32.IsResourceFile(ResourceIdentifier.Steamworks32)
|| api64.FileExists() && api64.IsResourceFile(ResourceIdentifier.Steamworks64); || api64.FileExists() && api64.IsResourceFile(ResourceIdentifier.Steamworks64);
await SmokeAPI.Uninstall(directory, deleteOthers: false); await SmokeAPI.Uninstall(directory, deleteOthers: false);
if (steamOriginalSdk32 is null && api32.FileExists() && !api32.IsResourceFile(ResourceIdentifier.Steamworks32)) if (steamOriginalSdk32 is null && api32.FileExists() &&
!api32.IsResourceFile(ResourceIdentifier.Steamworks32))
steamOriginalSdk32 = api32.ReadFileBytes(true); steamOriginalSdk32 = api32.ReadFileBytes(true);
if (steamOriginalSdk64 is null && api64.FileExists() && !api64.IsResourceFile(ResourceIdentifier.Steamworks64)) if (steamOriginalSdk64 is null && api64.FileExists() &&
!api64.IsResourceFile(ResourceIdentifier.Steamworks64))
steamOriginalSdk64 = api64.ReadFileBytes(true); steamOriginalSdk64 = api64.ReadFileBytes(true);
directory.GetScreamApiComponents(out api32, out api32_o, out api64, out api64_o, out config, out string log); directory.GetScreamApiComponents(out api32, out api32_o, out api64, out api64_o, out config,
out string log);
screamInstalled = screamInstalled || api32_o.FileExists() || api64_o.FileExists() screamInstalled = screamInstalled || api32_o.FileExists() || api64_o.FileExists()
|| (config.FileExists() || log.FileExists()) && !koaloaderInstalled || (config.FileExists() || log.FileExists()) && !koaloaderInstalled
|| api32.FileExists() && api32.IsResourceFile(ResourceIdentifier.EpicOnlineServices32) || api32.FileExists() && api32.IsResourceFile(ResourceIdentifier.EpicOnlineServices32)
|| api64.FileExists() && api64.IsResourceFile(ResourceIdentifier.EpicOnlineServices64); || api64.FileExists() && api64.IsResourceFile(ResourceIdentifier.EpicOnlineServices64);
await ScreamAPI.Uninstall(directory, deleteOthers: false); await ScreamAPI.Uninstall(directory, deleteOthers: false);
if (epicOriginalSdk32 is null && api32.FileExists() && !api32.IsResourceFile(ResourceIdentifier.EpicOnlineServices32)) if (epicOriginalSdk32 is null && api32.FileExists() &&
!api32.IsResourceFile(ResourceIdentifier.EpicOnlineServices32))
epicOriginalSdk32 = api32.ReadFileBytes(true); epicOriginalSdk32 = api32.ReadFileBytes(true);
if (epicOriginalSdk64 is null && api64.FileExists() && !api64.IsResourceFile(ResourceIdentifier.EpicOnlineServices64)) if (epicOriginalSdk64 is null && api64.FileExists() &&
!api64.IsResourceFile(ResourceIdentifier.EpicOnlineServices64))
epicOriginalSdk64 = api64.ReadFileBytes(true); epicOriginalSdk64 = api64.ReadFileBytes(true);
} }
if (steamOriginalSdk32 is not null || steamOriginalSdk64 is not null || epicOriginalSdk32 is not null || epicOriginalSdk64 is not null)
if (steamOriginalSdk32 is not null || steamOriginalSdk64 is not null || epicOriginalSdk32 is not null ||
epicOriginalSdk64 is not null)
{ {
bool neededRepair = false; bool neededRepair = false;
foreach (string directory in selection.DllDirectories.TakeWhile(_ => !Program.Canceled)) foreach (string directory in selection.DllDirectories.TakeWhile(_ => !Program.Canceled))
{ {
directory.GetSmokeApiComponents(out string api32, out _, out string api64, out _, out _, out _, out _, out _, out _); directory.GetSmokeApiComponents(out string api32, out _, out string api64, out _, out _, out _, out _,
out _, out _);
if (steamOriginalSdk32 is not null && api32.IsResourceFile(ResourceIdentifier.Steamworks32)) if (steamOriginalSdk32 is not null && api32.IsResourceFile(ResourceIdentifier.Steamworks32))
{ {
steamOriginalSdk32.WriteResource(api32); steamOriginalSdk32.WriteResource(api32);
@ -112,6 +132,7 @@ internal static class ParadoxLauncher
dialogText.AppendLine("Corrected Steamworks: " + api32); dialogText.AppendLine("Corrected Steamworks: " + api32);
neededRepair = true; neededRepair = true;
} }
if (steamOriginalSdk64 is not null && api64.IsResourceFile(ResourceIdentifier.Steamworks64)) if (steamOriginalSdk64 is not null && api64.IsResourceFile(ResourceIdentifier.Steamworks64))
{ {
steamOriginalSdk64.WriteResource(api64); steamOriginalSdk64.WriteResource(api64);
@ -121,6 +142,7 @@ internal static class ParadoxLauncher
dialogText.AppendLine("Corrected Steamworks: " + api64); dialogText.AppendLine("Corrected Steamworks: " + api64);
neededRepair = true; neededRepair = true;
} }
if (smokeInstalled) if (smokeInstalled)
await SmokeAPI.Install(directory, selection, generateConfig: false); await SmokeAPI.Install(directory, selection, generateConfig: false);
directory.GetScreamApiComponents(out api32, out _, out api64, out _, out _, out _); directory.GetScreamApiComponents(out api32, out _, out api64, out _, out _, out _);
@ -133,6 +155,7 @@ internal static class ParadoxLauncher
dialogText.AppendLine("Corrected Epic Online Services: " + api32); dialogText.AppendLine("Corrected Epic Online Services: " + api32);
neededRepair = true; neededRepair = true;
} }
if (epicOriginalSdk64 is not null && api64.IsResourceFile(ResourceIdentifier.EpicOnlineServices64)) if (epicOriginalSdk64 is not null && api64.IsResourceFile(ResourceIdentifier.EpicOnlineServices64))
{ {
epicOriginalSdk64.WriteResource(api64); epicOriginalSdk64.WriteResource(api64);
@ -142,9 +165,11 @@ internal static class ParadoxLauncher
dialogText.AppendLine("Corrected Epic Online Services: " + api64); dialogText.AppendLine("Corrected Epic Online Services: " + api64);
neededRepair = true; neededRepair = true;
} }
if (screamInstalled) if (screamInstalled)
await ScreamAPI.Install(directory, selection, generateConfig: false); await ScreamAPI.Install(directory, selection, generateConfig: false);
} }
if (!Program.Canceled) if (!Program.Canceled)
{ {
if (neededRepair) if (neededRepair)
@ -156,28 +181,36 @@ internal static class ParadoxLauncher
dialogText.AppendLine("\nParadox Launcher successfully repaired!"); dialogText.AppendLine("\nParadox Launcher successfully repaired!");
_ = dialogForm.Show(form.Icon, dialogText.ToString(), customFormText: "Paradox Launcher"); _ = dialogForm.Show(form.Icon, dialogText.ToString(), customFormText: "Paradox Launcher");
} }
return RepairResult.Success; return RepairResult.Success;
} }
if (installForm is not null) if (installForm is not null)
installForm.UpdateUser("Paradox Launcher did not need to be repaired.", LogTextBox.Success); installForm.UpdateUser("Paradox Launcher did not need to be repaired.", LogTextBox.Success);
else else
_ = dialogForm.Show(SystemIcons.Information, "Paradox Launcher does not need to be repaired.", customFormText: "Paradox Launcher"); _ = dialogForm.Show(SystemIcons.Information, "Paradox Launcher does not need to be repaired.",
customFormText: "Paradox Launcher");
return RepairResult.Unnecessary; return RepairResult.Unnecessary;
} }
} }
if (Program.Canceled) if (Program.Canceled)
{ {
_ = form is InstallForm _ = form is InstallForm
? throw new CustomMessageException("Repair failed! The operation was canceled.") ? throw new CustomMessageException("Repair failed! The operation was canceled.")
: dialogForm.Show(SystemIcons.Error, "Paradox Launcher repair failed! The operation was canceled.", customFormText: "Paradox Launcher"); : dialogForm.Show(SystemIcons.Error, "Paradox Launcher repair failed! The operation was canceled.",
customFormText: "Paradox Launcher");
return RepairResult.Failure; return RepairResult.Failure;
} }
_ = form is InstallForm _ = form is InstallForm
? throw new CustomMessageException("Repair failed! " + "An original Steamworks and/or Epic Online Services file could not be found. " ? throw new CustomMessageException(
+ "You will likely have to reinstall Paradox Launcher to fix this issue.") "Repair failed! " + "An original Steamworks and/or Epic Online Services file could not be found. "
+ "You will likely have to reinstall Paradox Launcher to fix this issue.")
: dialogForm.Show(SystemIcons.Error, : dialogForm.Show(SystemIcons.Error,
"Paradox Launcher repair failed!" + "\n\nAn original Steamworks and/or Epic Online Services file could not be found." "Paradox Launcher repair failed!" + "\n\nAn original Steamworks and/or Epic Online Services file could not be found."
+ "\nYou will likely have to reinstall Paradox Launcher to fix this issue.", customFormText: "Paradox Launcher"); + "\nYou will likely have to reinstall Paradox Launcher to fix this issue.",
customFormText: "Paradox Launcher");
return RepairResult.Failure; return RepairResult.Failure;
} }
} }

View file

@ -23,7 +23,8 @@ internal static class SteamCMD
private static readonly string FilePath = DirectoryPath + @"\steamcmd.exe"; private static readonly string FilePath = DirectoryPath + @"\steamcmd.exe";
private static readonly ConcurrentDictionary<string, int> AttemptCount = new(); // the more app_updates, the longer SteamCMD should wait for app_info_print private static readonly ConcurrentDictionary<string, int>
AttemptCount = new(); // the more app_updates, the longer SteamCMD should wait for app_info_print
private static readonly int[] Locks = new int[ProcessLimit]; private static readonly int[] Locks = new int[ProcessLimit];
@ -42,13 +43,13 @@ internal static class SteamCMD
private static string GetArguments(string appId) private static string GetArguments(string appId)
=> AttemptCount.TryGetValue(appId, out int attempts) => AttemptCount.TryGetValue(appId, out int attempts)
? $@"@ShutdownOnFailedCommand 0 +force_install_dir {DirectoryPath} +login anonymous +app_info_print {appId} " ? $@"@ShutdownOnFailedCommand 0 +force_install_dir {DirectoryPath} +login anonymous +app_info_print {appId} "
+ string.Concat(Enumerable.Repeat("+app_update 4 ", attempts)) + "+quit" + string.Concat(Enumerable.Repeat("+app_update 4 ", attempts)) + "+quit"
: $"+login anonymous +app_info_print {appId} +quit"; : $"+login anonymous +app_info_print {appId} +quit";
private static async Task<string> Run(string appId) private static async Task<string> Run(string appId)
=> await Task.Run(() => => await Task.Run(() =>
{ {
wait_for_lock: wait_for_lock:
if (Program.Canceled) if (Program.Canceled)
return ""; return "";
for (int i = 0; i < Locks.Length; i++) for (int i = 0; i < Locks.Length; i++)
@ -62,13 +63,17 @@ internal static class SteamCMD
_ = AttemptCount.TryGetValue(appId, out int count); _ = AttemptCount.TryGetValue(appId, out int count);
AttemptCount[appId] = ++count; AttemptCount[appId] = ++count;
} }
if (Program.Canceled) if (Program.Canceled)
return ""; return "";
ProcessStartInfo processStartInfo = new() ProcessStartInfo processStartInfo = new()
{ {
FileName = FilePath, RedirectStandardOutput = true, RedirectStandardInput = true, RedirectStandardError = true, FileName = FilePath, RedirectStandardOutput = true, RedirectStandardInput = true,
UseShellExecute = false, Arguments = appId is null ? "+quit" : GetArguments(appId), CreateNoWindow = true, RedirectStandardError = true,
StandardInputEncoding = Encoding.UTF8, StandardOutputEncoding = Encoding.UTF8, StandardErrorEncoding = Encoding.UTF8 UseShellExecute = false, Arguments = appId is null ? "+quit" : GetArguments(appId),
CreateNoWindow = true,
StandardInputEncoding = Encoding.UTF8, StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8
}; };
Process process = Process.Start(processStartInfo); Process process = Process.Start(processStartInfo);
StringBuilder output = new(); StringBuilder output = new();
@ -83,6 +88,7 @@ internal static class SteamCMD
process.Close(); process.Close();
break; break;
} }
int c = process.StandardOutput.Read(); int c = process.StandardOutput.Read();
if (c != -1) if (c != -1)
{ {
@ -92,13 +98,15 @@ internal static class SteamCMD
appInfoStarted = true; appInfoStarted = true;
_ = appInfoStarted ? appInfo.Append(ch) : output.Append(ch); _ = appInfoStarted ? appInfo.Append(ch) : output.Append(ch);
} }
DateTime now = DateTime.UtcNow; DateTime now = DateTime.UtcNow;
TimeSpan timeDiff = now - lastOutput; TimeSpan timeDiff = now - lastOutput;
if (!(timeDiff.TotalSeconds > 0.1)) if (!(timeDiff.TotalSeconds > 0.1))
continue; continue;
process.Kill(true); process.Kill(true);
process.Close(); process.Close();
if (appId != null && output.ToString().Contains($"No app info for AppID {appId} found, requesting...")) if (appId != null &&
output.ToString().Contains($"No app info for AppID {appId} found, requesting..."))
{ {
AttemptCount[appId]++; AttemptCount[appId]++;
processStartInfo.Arguments = GetArguments(appId); processStartInfo.Arguments = GetArguments(appId);
@ -110,9 +118,11 @@ internal static class SteamCMD
else else
break; break;
} }
_ = Interlocked.Decrement(ref Locks[i]); _ = Interlocked.Decrement(ref Locks[i]);
return appInfo.ToString(); return appInfo.ToString();
} }
Thread.Sleep(200); Thread.Sleep(200);
goto wait_for_lock; goto wait_for_lock;
}); });
@ -122,14 +132,16 @@ internal static class SteamCMD
await Cleanup(); await Cleanup();
if (!FilePath.FileExists()) if (!FilePath.FileExists())
{ {
retryDownload: retryDownload:
HttpClient httpClient = HttpClientManager.HttpClient; HttpClient httpClient = HttpClientManager.HttpClient;
if (httpClient is null) if (httpClient is null)
return false; return false;
while (!Program.Canceled) while (!Program.Canceled)
try try
{ {
byte[] file = await httpClient.GetByteArrayAsync(new Uri("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip")); byte[] file =
await httpClient.GetByteArrayAsync(
new Uri("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip"));
_ = file.WriteResource(ArchivePath); _ = file.WriteResource(ArchivePath);
ArchivePath.ExtractZip(DirectoryPath); ArchivePath.ExtractZip(DirectoryPath);
ArchivePath.DeleteFile(); ArchivePath.DeleteFile();
@ -142,9 +154,11 @@ internal static class SteamCMD
return false; return false;
} }
} }
if (DllPath.FileExists()) if (DllPath.FileExists())
return true; return true;
FileSystemWatcher watcher = new(DirectoryPath) { Filter = "*", IncludeSubdirectories = true, EnableRaisingEvents = true }; FileSystemWatcher watcher = new(DirectoryPath)
{ Filter = "*", IncludeSubdirectories = true, EnableRaisingEvents = true };
if (DllPath.FileExists()) if (DllPath.FileExists())
progress.Report(-15); // update (not used at the moment) progress.Report(-15); // update (not used at the moment)
else else
@ -176,7 +190,8 @@ internal static class SteamCMD
file.DeleteFile(); file.DeleteFile();
foreach (string file in DirectoryPath.EnumerateDirectory("*.ntfs_transaction_failed")) foreach (string file in DirectoryPath.EnumerateDirectory("*.ntfs_transaction_failed"))
file.DeleteFile(); file.DeleteFile();
AppCachePath.DeleteDirectory(); // this is definitely needed, so SteamCMD gets the latest information for us AppCachePath
.DeleteDirectory(); // this is definitely needed, so SteamCMD gets the latest information for us
DumpsPath.DeleteDirectory(); DumpsPath.DeleteDirectory();
LogsPath.DeleteDirectory(); LogsPath.DeleteDirectory();
SteamAppsPath.DeleteDirectory(); // this is just a useless folder created from +app_update 4 SteamAppsPath.DeleteDirectory(); // this is just a useless folder created from +app_update 4
@ -196,10 +211,12 @@ internal static class SteamCMD
if (attempts > 10) if (attempts > 10)
{ {
#if DEBUG #if DEBUG
DebugForm.Current.Log("Failed to query SteamCMD after 10 tries: " + appId + " (" + branch + ")", LogTextBox.Warning); DebugForm.Current.Log("Failed to query SteamCMD after 10 tries: " + appId + " (" + branch + ")",
LogTextBox.Warning);
#endif #endif
break; break;
} }
string appUpdateFile = $@"{AppInfoPath}\{appId}.vdf"; string appUpdateFile = $@"{AppInfoPath}\{appId}.vdf";
string output = appUpdateFile.ReadFile(); string output = appUpdateFile.ReadFile();
if (output is null) if (output is null)
@ -216,27 +233,34 @@ internal static class SteamCMD
else else
{ {
#if DEBUG #if DEBUG
DebugForm.Current.Log("SteamCMD query failed on attempt #" + attempts + " for " + appId + " (" + branch + "): Bad output", DebugForm.Current.Log(
"SteamCMD query failed on attempt #" + attempts + " for " + appId + " (" + branch +
"): Bad output",
LogTextBox.Warning); LogTextBox.Warning);
#endif #endif
continue; continue;
} }
} }
if (!ValveDataFile.TryDeserialize(output, out VProperty appInfo) || appInfo.Value is VValue) if (!ValveDataFile.TryDeserialize(output, out VProperty appInfo) || appInfo.Value is VValue)
{ {
appUpdateFile.DeleteFile(); appUpdateFile.DeleteFile();
#if DEBUG #if DEBUG
DebugForm.Current.Log("SteamCMD query failed on attempt #" + attempts + " for " + appId + " (" + branch + "): Deserialization failed", DebugForm.Current.Log(
"SteamCMD query failed on attempt #" + attempts + " for " + appId + " (" + branch +
"): Deserialization failed",
LogTextBox.Warning); LogTextBox.Warning);
#endif #endif
continue; continue;
} }
if (!appInfo.Value.Children().Any()) if (!appInfo.Value.Children().Any())
return appInfo; return appInfo;
VToken type = appInfo.Value.GetChild("common")?.GetChild("type"); VToken type = appInfo.Value.GetChild("common")?.GetChild("type");
if (type is not null && type.ToString() != "Game") if (type is not null && type.ToString() != "Game")
return appInfo; return appInfo;
string buildid = appInfo.Value.GetChild("depots")?.GetChild("branches")?.GetChild(branch)?.GetChild("buildid")?.ToString(); string buildid = appInfo.Value.GetChild("depots")?.GetChild("branches")?.GetChild(branch)
?.GetChild("buildid")?.ToString();
if (buildid is null && type is not null) if (buildid is null && type is not null)
return appInfo; return appInfo;
if (type is not null && (!int.TryParse(buildid, out int gamebuildId) || gamebuildId >= buildId)) if (type is not null && (!int.TryParse(buildid, out int gamebuildId) || gamebuildId >= buildId))
@ -246,9 +270,12 @@ internal static class SteamCMD
dlcAppUpdateFile.DeleteFile(); dlcAppUpdateFile.DeleteFile();
appUpdateFile.DeleteFile(); appUpdateFile.DeleteFile();
#if DEBUG #if DEBUG
DebugForm.Current.Log("SteamCMD query skipped on attempt #" + attempts + " for " + appId + " (" + branch + "): Outdated cache", LogTextBox.Warning); DebugForm.Current.Log(
"SteamCMD query skipped on attempt #" + attempts + " for " + appId + " (" + branch +
"): Outdated cache", LogTextBox.Warning);
#endif #endif
} }
return null; return null;
} }
@ -267,15 +294,18 @@ internal static class SteamCMD
if (int.TryParse(id, out int appId) && appId > 0) if (int.TryParse(id, out int appId) && appId > 0)
_ = dlcIds.Add("" + appId); _ = dlcIds.Add("" + appId);
} }
VToken depots = appInfo.Value.GetChild("depots"); VToken depots = appInfo.Value.GetChild("depots");
if (depots is null) if (depots is null)
return dlcIds; return dlcIds;
foreach (VToken vToken in depots.Where(p => p is VProperty property && int.TryParse(property.Key, out int _))) foreach (VToken vToken in depots.Where(
p => p is VProperty property && int.TryParse(property.Key, out int _)))
{ {
VProperty property = (VProperty)vToken; VProperty property = (VProperty)vToken;
if (int.TryParse(property.Value.GetChild("dlcappid")?.ToString(), out int appId) && appId > 0) if (int.TryParse(property.Value.GetChild("dlcappid")?.ToString(), out int appId) && appId > 0)
_ = dlcIds.Add("" + appId); _ = dlcIds.Add("" + appId);
} }
return dlcIds; return dlcIds;
}); });

View file

@ -16,12 +16,14 @@ internal static class SteamLibrary
get get
{ {
installPath ??= Registry.GetValue(@"HKEY_CURRENT_USER\Software\Valve\Steam", "SteamPath", null) as string; installPath ??= Registry.GetValue(@"HKEY_CURRENT_USER\Software\Valve\Steam", "SteamPath", null) as string;
installPath ??= Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Valve\Steam", "InstallPath", null) as string; installPath ??=
Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Valve\Steam", "InstallPath", null) as string;
return installPath.ResolvePath(); return installPath.ResolvePath();
} }
} }
internal static async Task<List<(string appId, string name, string branch, int buildId, string gameDirectory)>> GetGames() internal static async Task<List<(string appId, string name, string branch, int buildId, string gameDirectory)>>
GetGames()
=> await Task.Run(async () => => await Task.Run(async () =>
{ {
List<(string appId, string name, string branch, int buildId, string gameDirectory)> games = new(); List<(string appId, string name, string branch, int buildId, string gameDirectory)> games = new();
@ -30,10 +32,12 @@ internal static class SteamLibrary
{ {
if (Program.Canceled) if (Program.Canceled)
return games; return games;
foreach ((string appId, string name, string branch, int buildId, string gameDirectory) game in (await GetGamesFromLibraryDirectory( foreach ((string appId, string name, string branch, int buildId, string gameDirectory) game in (await
libraryDirectory)).Where(game => games.All(_game => _game.appId != game.appId))) GetGamesFromLibraryDirectory(
libraryDirectory)).Where(game => games.All(_game => _game.appId != game.appId)))
games.Add(game); games.Add(game);
} }
return games; return games;
}); });
@ -54,11 +58,13 @@ internal static class SteamLibrary
string installdir = result.Value.GetChild("installdir")?.ToString(); string installdir = result.Value.GetChild("installdir")?.ToString();
string name = result.Value.GetChild("name")?.ToString(); string name = result.Value.GetChild("name")?.ToString();
string buildId = result.Value.GetChild("buildid")?.ToString(); string buildId = result.Value.GetChild("buildid")?.ToString();
if (string.IsNullOrWhiteSpace(appId) || string.IsNullOrWhiteSpace(installdir) || string.IsNullOrWhiteSpace(name) if (string.IsNullOrWhiteSpace(appId) || string.IsNullOrWhiteSpace(installdir) ||
|| string.IsNullOrWhiteSpace(buildId)) string.IsNullOrWhiteSpace(name)
|| string.IsNullOrWhiteSpace(buildId))
continue; continue;
string gameDirectory = (libraryDirectory + @"\common\" + installdir).ResolvePath(); string gameDirectory = (libraryDirectory + @"\common\" + installdir).ResolvePath();
if (gameDirectory is null || !int.TryParse(appId, out int _) || !int.TryParse(buildId, out int buildIdInt) || games.Any(g => g.appId == appId)) if (gameDirectory is null || !int.TryParse(appId, out int _) ||
!int.TryParse(buildId, out int buildIdInt) || games.Any(g => g.appId == appId))
continue; continue;
VToken userConfig = result.Value.GetChild("UserConfig"); VToken userConfig = result.Value.GetChild("UserConfig");
string branch = userConfig?.GetChild("BetaKey")?.ToString(); string branch = userConfig?.GetChild("BetaKey")?.ToString();
@ -69,10 +75,12 @@ internal static class SteamLibrary
branch = mountedConfig?.GetChild("BetaKey")?.ToString(); branch = mountedConfig?.GetChild("BetaKey")?.ToString();
branch ??= mountedConfig?.GetChild("betakey")?.ToString(); branch ??= mountedConfig?.GetChild("betakey")?.ToString();
} }
if (string.IsNullOrWhiteSpace(branch)) if (string.IsNullOrWhiteSpace(branch))
branch = "public"; branch = "public";
games.Add((appId, name, branch, buildIdInt, gameDirectory)); games.Add((appId, name, branch, buildIdInt, gameDirectory));
} }
return games; return games;
}); });
@ -90,9 +98,11 @@ internal static class SteamLibrary
return libraryDirectories; return libraryDirectories;
_ = libraryDirectories.Add(libraryFolder); _ = libraryDirectories.Add(libraryFolder);
string libraryFolders = libraryFolder + @"\libraryfolders.vdf"; string libraryFolders = libraryFolder + @"\libraryfolders.vdf";
if (!libraryFolders.FileExists() || !ValveDataFile.TryDeserialize(libraryFolders.ReadFile(), out VProperty result)) if (!libraryFolders.FileExists() ||
!ValveDataFile.TryDeserialize(libraryFolders.ReadFile(), out VProperty result))
return libraryDirectories; return libraryDirectories;
foreach (VToken vToken in result.Value.Where(p => p is VProperty property && int.TryParse(property.Key, out int _))) foreach (VToken vToken in result.Value.Where(p =>
p is VProperty property && int.TryParse(property.Key, out int _)))
{ {
VProperty property = (VProperty)vToken; VProperty property = (VProperty)vToken;
string path = property.Value.GetChild("path")?.ToString(); string path = property.Value.GetChild("path")?.ToString();
@ -102,6 +112,7 @@ internal static class SteamLibrary
if (path.DirectoryExists()) if (path.DirectoryExists())
_ = libraryDirectories.Add(path); _ = libraryDirectories.Add(path);
} }
return libraryDirectories; return libraryDirectories;
}); });
} }

View file

@ -24,7 +24,9 @@ internal static class SteamStore
HashSet<string> dlcIds = new(); HashSet<string> dlcIds = new();
if (appData.DLC is null) if (appData.DLC is null)
return dlcIds; return dlcIds;
foreach (string dlcId in from appId in appData.DLC where appId > 0 select appId.ToString(CultureInfo.InvariantCulture)) foreach (string dlcId in from appId in appData.DLC
where appId > 0
select appId.ToString(CultureInfo.InvariantCulture))
_ = dlcIds.Add(dlcId); _ = dlcIds.Add(dlcId);
return dlcIds; return dlcIds;
}); });
@ -38,10 +40,12 @@ internal static class SteamStore
bool cachedExists = cacheFile.FileExists(); bool cachedExists = cacheFile.FileExists();
if (!cachedExists || ProgramData.CheckCooldown(appId, isDlc ? CooldownDlc : CooldownGame)) if (!cachedExists || ProgramData.CheckCooldown(appId, isDlc ? CooldownDlc : CooldownGame))
{ {
string response = await HttpClientManager.EnsureGet($"https://store.steampowered.com/api/appdetails?appids={appId}"); string response =
await HttpClientManager.EnsureGet($"https://store.steampowered.com/api/appdetails?appids={appId}");
if (response is not null) if (response is not null)
{ {
Dictionary<string, JToken> apps = JsonConvert.DeserializeObject<Dictionary<string, JToken>>(response); Dictionary<string, JToken> apps =
JsonConvert.DeserializeObject<Dictionary<string, JToken>>(response);
if (apps is not null) if (apps is not null)
foreach (KeyValuePair<string, JToken> app in apps) foreach (KeyValuePair<string, JToken> app in apps)
try try
@ -54,12 +58,15 @@ internal static class SteamStore
{ {
#if DEBUG #if DEBUG
DebugForm.Current.Log( DebugForm.Current.Log(
"Steam store query failed on attempt #" + attempts + " for " + appId + (isDlc ? " (DLC)" : "") "Steam store query failed on attempt #" + attempts + " for " + appId +
+ ": Query unsuccessful (" + app.Value.ToString(Formatting.None) + ")", LogTextBox.Warning); (isDlc ? " (DLC)" : "")
+ ": Query unsuccessful (" + app.Value.ToString(Formatting.None) + ")",
LogTextBox.Warning);
#endif #endif
if (data is null) if (data is null)
return null; return null;
} }
if (data is not null) if (data is not null)
{ {
try try
@ -70,8 +77,9 @@ internal static class SteamStore
#if DEBUG #if DEBUG
(Exception e) (Exception e)
{ {
DebugForm.Current.Log("Steam store query failed on attempt #" + attempts + " for " + appId + (isDlc ? " (DLC)" : "") DebugForm.Current.Log("Steam store query failed on attempt #" + attempts +
+ ": Unsuccessful serialization (" + e.Message + ")"); " for " + appId + (isDlc ? " (DLC)" : "")
+ ": Unsuccessful serialization (" + e.Message + ")");
} }
#else #else
{ {
@ -81,22 +89,27 @@ internal static class SteamStore
return data; return data;
} }
#if DEBUG #if DEBUG
DebugForm.Current.Log("Steam store query failed on attempt #" + attempts + " for " + appId + (isDlc ? " (DLC)" : "") DebugForm.Current.Log("Steam store query failed on attempt #" + attempts + " for " +
+ ": Response data null (" + app.Value.ToString(Formatting.None) + ")"); appId + (isDlc ? " (DLC)" : "")
+ ": Response data null (" +
app.Value.ToString(Formatting.None) + ")");
#endif #endif
} }
#if DEBUG #if DEBUG
else else
DebugForm.Current.Log("Steam store query failed on attempt #" + attempts + " for " + appId + (isDlc ? " (DLC)" : "") DebugForm.Current.Log("Steam store query failed on attempt #" + attempts + " for " +
+ ": Response details null (" + app.Value.ToString(Formatting.None) + ")"); appId + (isDlc ? " (DLC)" : "")
+ ": Response details null (" +
app.Value.ToString(Formatting.None) + ")");
#endif #endif
} }
catch catch
#if DEBUG #if DEBUG
(Exception e) (Exception e)
{ {
DebugForm.Current.Log("Steam store query failed on attempt #" + attempts + " for " + appId + (isDlc ? " (DLC)" : "") DebugForm.Current.Log("Steam store query failed on attempt #" + attempts + " for " +
+ ": Unsuccessful deserialization (" + e.Message + ")"); appId + (isDlc ? " (DLC)" : "")
+ ": Unsuccessful deserialization (" + e.Message + ")");
} }
#else #else
{ {
@ -105,18 +118,22 @@ internal static class SteamStore
#endif #endif
#if DEBUG #if DEBUG
else else
DebugForm.Current.Log("Steam store query failed on attempt #" + attempts + " for " + appId + (isDlc ? " (DLC)" : "") DebugForm.Current.Log("Steam store query failed on attempt #" + attempts + " for " + appId +
+ ": Response deserialization null"); (isDlc ? " (DLC)" : "")
+ ": Response deserialization null");
#endif #endif
} }
else else
{ {
#if DEBUG #if DEBUG
DebugForm.Current.Log("Steam store query failed on attempt #" + attempts + " for " + appId + (isDlc ? " (DLC)" : "") + ": Response null", DebugForm.Current.Log(
"Steam store query failed on attempt #" + attempts + " for " + appId + (isDlc ? " (DLC)" : "") +
": Response null",
LogTextBox.Warning); LogTextBox.Warning);
#endif #endif
} }
} }
if (cachedExists) if (cachedExists)
try try
{ {
@ -126,6 +143,7 @@ internal static class SteamStore
{ {
cacheFile.DeleteFile(); cacheFile.DeleteFile();
} }
if (isDlc) if (isDlc)
break; break;
if (attempts > 10) if (attempts > 10)
@ -135,8 +153,10 @@ internal static class SteamStore
#endif #endif
break; break;
} }
Thread.Sleep(1000); Thread.Sleep(1000);
} }
return null; return null;
} }
} }

View file

@ -17,6 +17,7 @@ internal static class ValveDataFile
{ {
// ignored // ignored
} }
return false; return false;
} }
@ -30,6 +31,7 @@ internal static class ValveDataFile
{ {
// ignored // ignored
} }
return null; return null;
} }
} }

View file

@ -34,6 +34,7 @@ internal static class UbisoftLibrary
if (installDir is not null && games.All(g => g.gameId != gameId)) if (installDir is not null && games.All(g => g.gameId != gameId))
games.Add((gameId, new DirectoryInfo(installDir).Name, installDir)); games.Add((gameId, new DirectoryInfo(installDir).Name, installDir));
} }
return games; return games;
}); });
} }

View file

@ -13,7 +13,11 @@ internal static class Program
{ {
internal static readonly string Name = Application.CompanyName; internal static readonly string Name = Application.CompanyName;
private static readonly string Description = Application.ProductName; private static readonly string Description = Application.ProductName;
internal static readonly string Version = Application.ProductVersion[..(Application.ProductVersion.IndexOf('+') is var index && index != -1 ? index : Application.ProductVersion.Length)];
internal static readonly string Version = Application.ProductVersion[
..(Application.ProductVersion.IndexOf('+') is var index && index != -1
? index
: Application.ProductVersion.Length)];
internal const string RepositoryOwner = "pointfeev"; internal const string RepositoryOwner = "pointfeev";
internal static readonly string RepositoryName = Name; internal static readonly string RepositoryName = Name;
@ -37,8 +41,9 @@ internal static class Program
internal static readonly string[] ProtectedGameDirectoryExceptions = []; internal static readonly string[] ProtectedGameDirectoryExceptions = [];
internal static bool IsGameBlocked(string name, string directory = null) internal static bool IsGameBlocked(string name, string directory = null)
=> BlockProtectedGames && (ProtectedGames.Contains(name) || directory is not null && !ProtectedGameDirectoryExceptions.Contains(name) => BlockProtectedGames && (ProtectedGames.Contains(name) || directory is not null &&
&& ProtectedGameDirectories.Any(path => (directory + path).DirectoryExists())); !ProtectedGameDirectoryExceptions.Contains(name)
&& ProtectedGameDirectories.Any(path => (directory + path).DirectoryExists()));
[STAThread] [STAThread]
private static void Main() private static void Main()
@ -52,8 +57,9 @@ internal static class Program
Application.ApplicationExit += OnApplicationExit; Application.ApplicationExit += OnApplicationExit;
Application.ThreadException += (_, e) => e.Exception.HandleFatalException(); Application.ThreadException += (_, e) => e.Exception.HandleFatalException();
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
AppDomain.CurrentDomain.UnhandledException += (_, e) => (e.ExceptionObject as Exception)?.HandleFatalException(); AppDomain.CurrentDomain.UnhandledException +=
retry: (_, e) => (e.ExceptionObject as Exception)?.HandleFatalException();
retry:
try try
{ {
HttpClientManager.Setup(); HttpClientManager.Setup();
@ -71,6 +77,7 @@ internal static class Program
return; return;
} }
} }
mutex.Close(); mutex.Close();
} }

View file

@ -15,11 +15,16 @@ internal static class Koaloader
{ {
internal static readonly List<(string unlocker, string dll)> AutoLoadDLLs = internal static readonly List<(string unlocker, string dll)> AutoLoadDLLs =
[ [
("Koaloader", "Unlocker.dll"), ("Koaloader", "Unlocker32.dll"), ("Koaloader", "Unlocker64.dll"), ("Lyptus", "Lyptus.dll"), ("Koaloader", "Unlocker.dll"), ("Koaloader", "Unlocker32.dll"), ("Koaloader", "Unlocker64.dll"),
("Lyptus", "Lyptus32.dll"), ("Lyptus", "Lyptus64.dll"), ("SmokeAPI", "SmokeAPI.dll"), ("SmokeAPI", "SmokeAPI32.dll"), ("Lyptus", "Lyptus.dll"),
("SmokeAPI", "SmokeAPI64.dll"), ("ScreamAPI", "ScreamAPI.dll"), ("ScreamAPI", "ScreamAPI32.dll"), ("ScreamAPI", "ScreamAPI64.dll"), ("Lyptus", "Lyptus32.dll"), ("Lyptus", "Lyptus64.dll"), ("SmokeAPI", "SmokeAPI.dll"),
("Uplay R1 Unlocker", "UplayR1Unlocker.dll"), ("Uplay R1 Unlocker", "UplayR1Unlocker32.dll"), ("Uplay R1 Unlocker", "UplayR1Unlocker64.dll"), ("SmokeAPI", "SmokeAPI32.dll"),
("Uplay R2 Unlocker", "UplayR2Unlocker.dll"), ("Uplay R2 Unlocker", "UplayR2Unlocker32.dll"), ("Uplay R2 Unlocker", "UplayR2Unlocker64.dll") ("SmokeAPI", "SmokeAPI64.dll"), ("ScreamAPI", "ScreamAPI.dll"), ("ScreamAPI", "ScreamAPI32.dll"),
("ScreamAPI", "ScreamAPI64.dll"),
("Uplay R1 Unlocker", "UplayR1Unlocker.dll"), ("Uplay R1 Unlocker", "UplayR1Unlocker32.dll"),
("Uplay R1 Unlocker", "UplayR1Unlocker64.dll"),
("Uplay R2 Unlocker", "UplayR2Unlocker.dll"), ("Uplay R2 Unlocker", "UplayR2Unlocker32.dll"),
("Uplay R2 Unlocker", "UplayR2Unlocker64.dll")
]; ];
internal static IEnumerable<string> GetKoaloaderProxies(this string directory) internal static IEnumerable<string> GetKoaloaderProxies(this string directory)
@ -38,7 +43,8 @@ internal static class Koaloader
private static void WriteProxy(this string path, string proxyName, BinaryType binaryType) private static void WriteProxy(this string path, string proxyName, BinaryType binaryType)
{ {
foreach (string resourceIdentifier in EmbeddedResources.Where(r => r.StartsWith("Koaloader", StringComparison.Ordinal))) foreach (string resourceIdentifier in EmbeddedResources.Where(r =>
r.StartsWith("Koaloader", StringComparison.Ordinal)))
{ {
resourceIdentifier.GetProxyInfoFromIdentifier(out string _proxyName, out BinaryType _binaryType); resourceIdentifier.GetProxyInfoFromIdentifier(out string _proxyName, out BinaryType _binaryType);
if (_proxyName != proxyName || _binaryType != binaryType) if (_proxyName != proxyName || _binaryType != binaryType)
@ -48,7 +54,8 @@ internal static class Koaloader
} }
} }
internal static void GetProxyInfoFromIdentifier(this string resourceIdentifier, out string proxyName, out BinaryType binaryType) internal static void GetProxyInfoFromIdentifier(this string resourceIdentifier, out string proxyName,
out BinaryType binaryType)
{ {
string baseIdentifier = resourceIdentifier[(resourceIdentifier.IndexOf('.') + 1)..]; string baseIdentifier = resourceIdentifier[(resourceIdentifier.IndexOf('.') + 1)..];
baseIdentifier = baseIdentifier[..baseIdentifier.IndexOf('.')]; baseIdentifier = baseIdentifier[..baseIdentifier.IndexOf('.')];
@ -65,14 +72,18 @@ internal static class Koaloader
if (!config.FileExists()) if (!config.FileExists())
{ {
old_config.MoveFile(config!); old_config.MoveFile(config!);
installForm?.UpdateUser($"Converted old configuration: {Path.GetFileName(old_config)} -> {Path.GetFileName(config)}", LogTextBox.Action, false); installForm?.UpdateUser(
$"Converted old configuration: {Path.GetFileName(old_config)} -> {Path.GetFileName(config)}",
LogTextBox.Action, false);
} }
else else
{ {
old_config.DeleteFile(); old_config.DeleteFile();
installForm?.UpdateUser($"Deleted old configuration: {Path.GetFileName(old_config)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted old configuration: {Path.GetFileName(old_config)}", LogTextBox.Action,
false);
} }
} }
SortedList<string, string> targets = new(PlatformIdComparer.String); SortedList<string, string> targets = new(PlatformIdComparer.String);
SortedList<string, string> modules = new(PlatformIdComparer.String); SortedList<string, string> modules = new(PlatformIdComparer.String);
if (targets.Count > 0 || modules.Count > 0) if (targets.Count > 0 || modules.Count > 0)
@ -88,11 +99,13 @@ internal static class Koaloader
else if (config.FileExists()) else if (config.FileExists())
{ {
config.DeleteFile(); config.DeleteFile();
installForm?.UpdateUser($"Deleted unnecessary configuration: {Path.GetFileName(config)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted unnecessary configuration: {Path.GetFileName(config)}", LogTextBox.Action,
false);
} }
} }
private static void WriteConfig(StreamWriter writer, SortedList<string, string> targets, SortedList<string, string> modules, InstallForm installForm = null) private static void WriteConfig(StreamWriter writer, SortedList<string, string> targets,
SortedList<string, string> modules, InstallForm installForm = null)
{ {
writer.WriteLine("{"); writer.WriteLine("{");
writer.WriteLine(" \"logging\": false,"); writer.WriteLine(" \"logging\": false,");
@ -108,10 +121,12 @@ internal static class Koaloader
writer.WriteLine($" \"{path}\"{(pair.Equals(lastTarget) ? "" : ",")}"); writer.WriteLine($" \"{path}\"{(pair.Equals(lastTarget) ? "" : ",")}");
installForm?.UpdateUser($"Added target to Koaloader.json with path {path}", LogTextBox.Action, false); installForm?.UpdateUser($"Added target to Koaloader.json with path {path}", LogTextBox.Action, false);
} }
writer.WriteLine(" ]"); writer.WriteLine(" ]");
} }
else else
writer.WriteLine(" \"targets\": []"); writer.WriteLine(" \"targets\": []");
if (modules.Count > 0) if (modules.Count > 0)
{ {
writer.WriteLine(" \"modules\": ["); writer.WriteLine(" \"modules\": [");
@ -125,39 +140,48 @@ internal static class Koaloader
writer.WriteLine(" }" + (pair.Equals(lastModule) ? "" : ",")); writer.WriteLine(" }" + (pair.Equals(lastModule) ? "" : ","));
installForm?.UpdateUser($"Added module to Koaloader.json with path {path}", LogTextBox.Action, false); installForm?.UpdateUser($"Added module to Koaloader.json with path {path}", LogTextBox.Action, false);
} }
writer.WriteLine(" ]"); writer.WriteLine(" ]");
} }
else else
writer.WriteLine(" \"modules\": []"); writer.WriteLine(" \"modules\": []");
writer.WriteLine("}"); writer.WriteLine("}");
} }
internal static async Task Uninstall(string directory, string rootDirectory = null, InstallForm installForm = null, bool deleteConfig = true) internal static async Task Uninstall(string directory, string rootDirectory = null, InstallForm installForm = null,
bool deleteConfig = true)
=> await Task.Run(async () => => await Task.Run(async () =>
{ {
directory.GetKoaloaderComponents(out string old_config, out string config); directory.GetKoaloaderComponents(out string old_config, out string config);
foreach (string proxyPath in directory.GetKoaloaderProxies().Where(proxyPath foreach (string proxyPath in directory.GetKoaloaderProxies().Where(proxyPath
=> proxyPath.FileExists() && proxyPath.IsResourceFile(ResourceIdentifier.Koaloader))) => proxyPath.FileExists() && proxyPath.IsResourceFile(ResourceIdentifier.Koaloader)))
{ {
proxyPath.DeleteFile(true); proxyPath.DeleteFile(true);
installForm?.UpdateUser($"Deleted Koaloader: {Path.GetFileName(proxyPath)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted Koaloader: {Path.GetFileName(proxyPath)}", LogTextBox.Action, false);
} }
foreach ((string unlocker, string path) in AutoLoadDLLs.Select(pair => (pair.unlocker, path: directory + @"\" + pair.dll))
.Where(pair => pair.path.FileExists() && pair.path.IsResourceFile())) foreach ((string unlocker, string path) in AutoLoadDLLs
.Select(pair => (pair.unlocker, path: directory + @"\" + pair.dll))
.Where(pair => pair.path.FileExists() && pair.path.IsResourceFile()))
{ {
path.DeleteFile(true); path.DeleteFile(true);
installForm?.UpdateUser($"Deleted {unlocker}: {Path.GetFileName(path)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted {unlocker}: {Path.GetFileName(path)}", LogTextBox.Action, false);
} }
if (deleteConfig && old_config.FileExists()) if (deleteConfig && old_config.FileExists())
{ {
old_config.DeleteFile(); old_config.DeleteFile();
installForm?.UpdateUser($"Deleted configuration: {Path.GetFileName(old_config)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted configuration: {Path.GetFileName(old_config)}", LogTextBox.Action,
false);
} }
if (deleteConfig && config.FileExists()) if (deleteConfig && config.FileExists())
{ {
config.DeleteFile(); config.DeleteFile();
installForm?.UpdateUser($"Deleted configuration: {Path.GetFileName(config)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted configuration: {Path.GetFileName(config)}", LogTextBox.Action, false);
} }
await SmokeAPI.Uninstall(directory, installForm, deleteConfig); await SmokeAPI.Uninstall(directory, installForm, deleteConfig);
await ScreamAPI.Uninstall(directory, installForm, deleteConfig); await ScreamAPI.Uninstall(directory, installForm, deleteConfig);
await UplayR1.Uninstall(directory, installForm, deleteConfig); await UplayR1.Uninstall(directory, installForm, deleteConfig);
@ -166,21 +190,27 @@ internal static class Koaloader
await Uninstall(rootDirectory, null, installForm, deleteConfig); await Uninstall(rootDirectory, null, installForm, deleteConfig);
}); });
internal static async Task Install(string directory, BinaryType binaryType, Selection selection, string rootDirectory = null, internal static async Task Install(string directory, BinaryType binaryType, Selection selection,
string rootDirectory = null,
InstallForm installForm = null, bool generateConfig = true) InstallForm installForm = null, bool generateConfig = true)
=> await Task.Run(() => => await Task.Run(() =>
{ {
string proxy = selection.KoaloaderProxy ?? Selection.DefaultKoaloaderProxy; string proxy = selection.KoaloaderProxy ?? Selection.DefaultKoaloaderProxy;
string path = directory + @"\" + proxy + ".dll"; string path = directory + @"\" + proxy + ".dll";
foreach (string _path in directory.GetKoaloaderProxies().Where(p => p != path && p.FileExists() && p.IsResourceFile(ResourceIdentifier.Koaloader))) foreach (string _path in directory.GetKoaloaderProxies().Where(p =>
p != path && p.FileExists() && p.IsResourceFile(ResourceIdentifier.Koaloader)))
{ {
_path.DeleteFile(true); _path.DeleteFile(true);
installForm?.UpdateUser($"Deleted Koaloader: {Path.GetFileName(_path)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted Koaloader: {Path.GetFileName(_path)}", LogTextBox.Action, false);
} }
if (path.FileExists() && !path.IsResourceFile(ResourceIdentifier.Koaloader)) if (path.FileExists() && !path.IsResourceFile(ResourceIdentifier.Koaloader))
throw new CustomMessageException("A non-Koaloader DLL named " + proxy + ".dll already exists in this directory!"); throw new CustomMessageException("A non-Koaloader DLL named " + proxy +
".dll already exists in this directory!");
path.WriteProxy(proxy, binaryType); path.WriteProxy(proxy, binaryType);
installForm?.UpdateUser($"Wrote {(binaryType == BinaryType.BIT32 ? "32-bit" : "64-bit")} Koaloader: {Path.GetFileName(path)}", LogTextBox.Action, installForm?.UpdateUser(
$"Wrote {(binaryType == BinaryType.BIT32 ? "32-bit" : "64-bit")} Koaloader: {Path.GetFileName(path)}",
LogTextBox.Action,
false); false);
bool bit32 = false, bit64 = false; bool bit32 = false, bit64 = false;
foreach (string executable in directory.EnumerateDirectory("*.exe")) foreach (string executable in directory.EnumerateDirectory("*.exe"))
@ -195,9 +225,11 @@ internal static class Koaloader
bit64 = true; bit64 = true;
break; break;
} }
if (bit32 && bit64) if (bit32 && bit64)
break; break;
} }
if (selection.Platform is Platform.Steam or Platform.Paradox) if (selection.Platform is Platform.Steam or Platform.Paradox)
{ {
if (bit32) if (bit32)
@ -208,15 +240,20 @@ internal static class Koaloader
if (path.FileExists()) if (path.FileExists())
{ {
path.DeleteFile(); path.DeleteFile();
installForm?.UpdateUser($"Deleted SmokeAPI from non-root directory: {Path.GetFileName(path)}", LogTextBox.Action, false); installForm?.UpdateUser(
$"Deleted SmokeAPI from non-root directory: {Path.GetFileName(path)}",
LogTextBox.Action, false);
} }
path = rootDirectory + @"\SmokeAPI32.dll"; path = rootDirectory + @"\SmokeAPI32.dll";
} }
"SmokeAPI.steam_api.dll".WriteManifestResource(path); "SmokeAPI.steam_api.dll".WriteManifestResource(path);
installForm?.UpdateUser( installForm?.UpdateUser(
$"Wrote SmokeAPI{(rootDirectory is not null && directory != rootDirectory ? " to root directory" : "")}: {Path.GetFileName(path)}", $"Wrote SmokeAPI{(rootDirectory is not null && directory != rootDirectory ? " to root directory" : "")}: {Path.GetFileName(path)}",
LogTextBox.Action, false); LogTextBox.Action, false);
} }
if (bit64) if (bit64)
{ {
path = directory + @"\SmokeAPI64.dll"; path = directory + @"\SmokeAPI64.dll";
@ -225,17 +262,23 @@ internal static class Koaloader
if (path.FileExists()) if (path.FileExists())
{ {
path.DeleteFile(); path.DeleteFile();
installForm?.UpdateUser($"Deleted SmokeAPI from non-root directory: {Path.GetFileName(path)}", LogTextBox.Action, false); installForm?.UpdateUser(
$"Deleted SmokeAPI from non-root directory: {Path.GetFileName(path)}",
LogTextBox.Action, false);
} }
path = rootDirectory + @"\SmokeAPI64.dll"; path = rootDirectory + @"\SmokeAPI64.dll";
} }
"SmokeAPI.steam_api64.dll".WriteManifestResource(path); "SmokeAPI.steam_api64.dll".WriteManifestResource(path);
installForm?.UpdateUser( installForm?.UpdateUser(
$"Wrote SmokeAPI{(rootDirectory is not null && directory != rootDirectory ? " to root directory" : "")}: {Path.GetFileName(path)}", $"Wrote SmokeAPI{(rootDirectory is not null && directory != rootDirectory ? " to root directory" : "")}: {Path.GetFileName(path)}",
LogTextBox.Action, false); LogTextBox.Action, false);
} }
SmokeAPI.CheckConfig(rootDirectory ?? directory, selection, installForm); SmokeAPI.CheckConfig(rootDirectory ?? directory, selection, installForm);
} }
switch (selection.Platform) switch (selection.Platform)
{ {
case Platform.Epic or Platform.Paradox: case Platform.Epic or Platform.Paradox:
@ -248,15 +291,20 @@ internal static class Koaloader
if (path.FileExists()) if (path.FileExists())
{ {
path.DeleteFile(); path.DeleteFile();
installForm?.UpdateUser($"Deleted ScreamAPI from non-root directory: {Path.GetFileName(path)}", LogTextBox.Action, false); installForm?.UpdateUser(
$"Deleted ScreamAPI from non-root directory: {Path.GetFileName(path)}",
LogTextBox.Action, false);
} }
path = rootDirectory + @"\ScreamAPI32.dll"; path = rootDirectory + @"\ScreamAPI32.dll";
} }
"ScreamAPI.EOSSDK-Win32-Shipping.dll".WriteManifestResource(path); "ScreamAPI.EOSSDK-Win32-Shipping.dll".WriteManifestResource(path);
installForm?.UpdateUser( installForm?.UpdateUser(
$"Wrote ScreamAPI{(rootDirectory is not null && directory != rootDirectory ? " to root directory" : "")}: {Path.GetFileName(path)}", $"Wrote ScreamAPI{(rootDirectory is not null && directory != rootDirectory ? " to root directory" : "")}: {Path.GetFileName(path)}",
LogTextBox.Action, false); LogTextBox.Action, false);
} }
if (bit64) if (bit64)
{ {
path = directory + @"\ScreamAPI64.dll"; path = directory + @"\ScreamAPI64.dll";
@ -265,15 +313,20 @@ internal static class Koaloader
if (path.FileExists()) if (path.FileExists())
{ {
path.DeleteFile(); path.DeleteFile();
installForm?.UpdateUser($"Deleted ScreamAPI from non-root directory: {Path.GetFileName(path)}", LogTextBox.Action, false); installForm?.UpdateUser(
$"Deleted ScreamAPI from non-root directory: {Path.GetFileName(path)}",
LogTextBox.Action, false);
} }
path = rootDirectory + @"\ScreamAPI64.dll"; path = rootDirectory + @"\ScreamAPI64.dll";
} }
"ScreamAPI.EOSSDK-Win64-Shipping.dll".WriteManifestResource(path); "ScreamAPI.EOSSDK-Win64-Shipping.dll".WriteManifestResource(path);
installForm?.UpdateUser( installForm?.UpdateUser(
$"Wrote ScreamAPI{(rootDirectory is not null && directory != rootDirectory ? " to root directory" : "")}: {Path.GetFileName(path)}", $"Wrote ScreamAPI{(rootDirectory is not null && directory != rootDirectory ? " to root directory" : "")}: {Path.GetFileName(path)}",
LogTextBox.Action, false); LogTextBox.Action, false);
} }
ScreamAPI.CheckConfig(rootDirectory ?? directory, selection, installForm); ScreamAPI.CheckConfig(rootDirectory ?? directory, selection, installForm);
break; break;
} }
@ -287,16 +340,21 @@ internal static class Koaloader
if (path.FileExists()) if (path.FileExists())
{ {
path.DeleteFile(); path.DeleteFile();
installForm?.UpdateUser($"Deleted Uplay R1 Unlocker from non-root directory: {Path.GetFileName(path)}", LogTextBox.Action, installForm?.UpdateUser(
$"Deleted Uplay R1 Unlocker from non-root directory: {Path.GetFileName(path)}",
LogTextBox.Action,
false); false);
} }
path = rootDirectory + @"\UplayR1Unlocker32.dll"; path = rootDirectory + @"\UplayR1Unlocker32.dll";
} }
"UplayR1.uplay_r1_loader.dll".WriteManifestResource(path); "UplayR1.uplay_r1_loader.dll".WriteManifestResource(path);
installForm?.UpdateUser( installForm?.UpdateUser(
$"Wrote Uplay R1 Unlocker{(rootDirectory is not null && directory != rootDirectory ? " to root directory" : "")}: {Path.GetFileName(path)}", $"Wrote Uplay R1 Unlocker{(rootDirectory is not null && directory != rootDirectory ? " to root directory" : "")}: {Path.GetFileName(path)}",
LogTextBox.Action, false); LogTextBox.Action, false);
} }
if (bit64) if (bit64)
{ {
path = directory + @"\UplayR1Unlocker64.dll"; path = directory + @"\UplayR1Unlocker64.dll";
@ -305,16 +363,21 @@ internal static class Koaloader
if (path.FileExists()) if (path.FileExists())
{ {
path.DeleteFile(); path.DeleteFile();
installForm?.UpdateUser($"Deleted Uplay R1 Unlocker from non-root directory: {Path.GetFileName(path)}", LogTextBox.Action, installForm?.UpdateUser(
$"Deleted Uplay R1 Unlocker from non-root directory: {Path.GetFileName(path)}",
LogTextBox.Action,
false); false);
} }
path = rootDirectory + @"\UplayR1Unlocker64.dll"; path = rootDirectory + @"\UplayR1Unlocker64.dll";
} }
"UplayR1.uplay_r1_loader64.dll".WriteManifestResource(path); "UplayR1.uplay_r1_loader64.dll".WriteManifestResource(path);
installForm?.UpdateUser( installForm?.UpdateUser(
$"Wrote Uplay R1 Unlocker{(rootDirectory is not null && directory != rootDirectory ? " to root directory" : "")}: {Path.GetFileName(path)}", $"Wrote Uplay R1 Unlocker{(rootDirectory is not null && directory != rootDirectory ? " to root directory" : "")}: {Path.GetFileName(path)}",
LogTextBox.Action, false); LogTextBox.Action, false);
} }
UplayR1.CheckConfig(rootDirectory ?? directory, selection, installForm); UplayR1.CheckConfig(rootDirectory ?? directory, selection, installForm);
if (bit32) if (bit32)
{ {
@ -324,16 +387,21 @@ internal static class Koaloader
if (path.FileExists()) if (path.FileExists())
{ {
path.DeleteFile(); path.DeleteFile();
installForm?.UpdateUser($"Deleted Uplay R2 Unlocker from non-root directory: {Path.GetFileName(path)}", LogTextBox.Action, installForm?.UpdateUser(
$"Deleted Uplay R2 Unlocker from non-root directory: {Path.GetFileName(path)}",
LogTextBox.Action,
false); false);
} }
path = rootDirectory + @"\UplayR2Unlocker32.dll"; path = rootDirectory + @"\UplayR2Unlocker32.dll";
} }
"UplayR2.upc_r2_loader.dll".WriteManifestResource(path); "UplayR2.upc_r2_loader.dll".WriteManifestResource(path);
installForm?.UpdateUser( installForm?.UpdateUser(
$"Wrote Uplay R2 Unlocker{(rootDirectory is not null && directory != rootDirectory ? " to root directory" : "")}: {Path.GetFileName(path)}", $"Wrote Uplay R2 Unlocker{(rootDirectory is not null && directory != rootDirectory ? " to root directory" : "")}: {Path.GetFileName(path)}",
LogTextBox.Action, false); LogTextBox.Action, false);
} }
if (bit64) if (bit64)
{ {
path = directory + @"\UplayR2Unlocker64.dll"; path = directory + @"\UplayR2Unlocker64.dll";
@ -342,20 +410,26 @@ internal static class Koaloader
if (path.FileExists()) if (path.FileExists())
{ {
path.DeleteFile(); path.DeleteFile();
installForm?.UpdateUser($"Deleted Uplay R2 Unlocker from non-root directory: {Path.GetFileName(path)}", LogTextBox.Action, installForm?.UpdateUser(
$"Deleted Uplay R2 Unlocker from non-root directory: {Path.GetFileName(path)}",
LogTextBox.Action,
false); false);
} }
path = rootDirectory + @"\UplayR2Unlocker64.dll"; path = rootDirectory + @"\UplayR2Unlocker64.dll";
} }
"UplayR2.upc_r2_loader64.dll".WriteManifestResource(path); "UplayR2.upc_r2_loader64.dll".WriteManifestResource(path);
installForm?.UpdateUser( installForm?.UpdateUser(
$"Wrote Uplay R2 Unlocker{(rootDirectory is not null && directory != rootDirectory ? " to root directory" : "")}: {Path.GetFileName(path)}", $"Wrote Uplay R2 Unlocker{(rootDirectory is not null && directory != rootDirectory ? " to root directory" : "")}: {Path.GetFileName(path)}",
LogTextBox.Action, false); LogTextBox.Action, false);
} }
UplayR2.CheckConfig(rootDirectory ?? directory, selection, installForm); UplayR2.CheckConfig(rootDirectory ?? directory, selection, installForm);
break; break;
} }
} }
if (generateConfig) if (generateConfig)
CheckConfig(directory, installForm); CheckConfig(directory, installForm);
}); });

View file

@ -432,7 +432,8 @@ internal static class Resources
return embeddedResources; return embeddedResources;
string[] names = Assembly.GetExecutingAssembly().GetManifestResourceNames(); string[] names = Assembly.GetExecutingAssembly().GetManifestResourceNames();
embeddedResources = []; embeddedResources = [];
foreach (string resourceName in names.Where(n => n.StartsWith("CreamInstaller.Resources.", StringComparison.Ordinal))) foreach (string resourceName in names.Where(n =>
n.StartsWith("CreamInstaller.Resources.", StringComparison.Ordinal)))
_ = embeddedResources.Add(resourceName[25..]); _ = embeddedResources.Add(resourceName[25..]);
return embeddedResources; return embeddedResources;
} }
@ -443,14 +444,16 @@ internal static class Resources
while (!Program.Canceled) while (!Program.Canceled)
try try
{ {
using Stream resource = Assembly.GetExecutingAssembly().GetManifestResourceStream("CreamInstaller.Resources." + resourceIdentifier); using Stream resource = Assembly.GetExecutingAssembly()
.GetManifestResourceStream("CreamInstaller.Resources." + resourceIdentifier);
using FileStream file = new(filePath, FileMode.Create, FileAccess.Write); using FileStream file = new(filePath, FileMode.Create, FileAccess.Write);
resource?.CopyTo(file); resource?.CopyTo(file);
break; break;
} }
catch (Exception e) catch (Exception e)
{ {
if (filePath.IOWarn("Failed to write a crucial manifest resource (" + resourceIdentifier + ")", e) is not DialogResult.OK) if (filePath.IOWarn("Failed to write a crucial manifest resource (" + resourceIdentifier + ")", e) is
not DialogResult.OK)
break; break;
} }
} }
@ -469,22 +472,26 @@ internal static class Resources
if (filePath.IOWarn("Failed to write a crucial resource", e) is not DialogResult.OK) if (filePath.IOWarn("Failed to write a crucial resource", e) is not DialogResult.OK)
break; break;
} }
return false; return false;
} }
internal static bool TryGetFileBinaryType(this string path, out BinaryType binaryType) => NativeImports.GetBinaryType(path, out binaryType); internal static bool TryGetFileBinaryType(this string path, out BinaryType binaryType) =>
NativeImports.GetBinaryType(path, out binaryType);
internal static async Task<List<(string directory, BinaryType binaryType)>> GetExecutableDirectories(this string rootDirectory, bool filterCommon = false, internal static async Task<List<(string directory, BinaryType binaryType)>> GetExecutableDirectories(
this string rootDirectory, bool filterCommon = false,
Func<string, bool> validFunc = null) Func<string, bool> validFunc = null)
=> await Task.Run(async () => await Task.Run(async ()
=> (await rootDirectory.GetExecutables(filterCommon, validFunc) => (await rootDirectory.GetExecutables(filterCommon, validFunc)
?? (filterCommon || validFunc is not null ? await rootDirectory.GetExecutables() : null))?.Select(e => ?? (filterCommon || validFunc is not null ? await rootDirectory.GetExecutables() : null))?.Select(e =>
{ {
e.path = Path.GetDirectoryName(e.path); e.path = Path.GetDirectoryName(e.path);
return e; return e;
}).DistinctBy(e => e.path).ToList() ?? []); }).DistinctBy(e => e.path).ToList() ?? []);
internal static async Task<List<(string path, BinaryType binaryType)>> GetExecutables(this string rootDirectory, bool filterCommon = false, internal static async Task<List<(string path, BinaryType binaryType)>> GetExecutables(this string rootDirectory,
bool filterCommon = false,
Func<string, bool> validFunc = null) Func<string, bool> validFunc = null)
=> await Task.Run(() => => await Task.Run(() =>
{ {
@ -495,18 +502,26 @@ internal static class Resources
{ {
if (Program.Canceled) if (Program.Canceled)
return null; return null;
if (executables.All(e => e.path != path) && (!filterCommon || !rootDirectory.IsCommonIncorrectExecutable(path)) if (executables.All(e => e.path != path) && (!filterCommon ||
&& (validFunc is null || validFunc(path)) && path.TryGetFileBinaryType(out BinaryType binaryType) && binaryType is BinaryType.BIT64) !rootDirectory.IsCommonIncorrectExecutable(path))
&& (validFunc is null || validFunc(path)) &&
path.TryGetFileBinaryType(out BinaryType binaryType) &&
binaryType is BinaryType.BIT64)
executables.Add((path, binaryType)); executables.Add((path, binaryType));
} }
foreach (string path in rootDirectory.EnumerateDirectory("*.exe", true)) foreach (string path in rootDirectory.EnumerateDirectory("*.exe", true))
{ {
if (Program.Canceled) if (Program.Canceled)
return null; return null;
if (executables.All(e => e.path != path) && (!filterCommon || !rootDirectory.IsCommonIncorrectExecutable(path)) if (executables.All(e => e.path != path) && (!filterCommon ||
&& (validFunc is null || validFunc(path)) && path.TryGetFileBinaryType(out BinaryType binaryType) && binaryType is BinaryType.BIT32) !rootDirectory.IsCommonIncorrectExecutable(path))
&& (validFunc is null || validFunc(path)) &&
path.TryGetFileBinaryType(out BinaryType binaryType) &&
binaryType is BinaryType.BIT32)
executables.Add((path, binaryType)); executables.Add((path, binaryType));
} }
return executables.Count > 0 ? executables : null; return executables.Count > 0 ? executables : null;
}); });
@ -514,12 +529,16 @@ internal static class Resources
{ {
string subPath = path[rootDirectory.Length..].ToUpperInvariant(); string subPath = path[rootDirectory.Length..].ToUpperInvariant();
return subPath.Contains("SETUP") || subPath.Contains("REDIST") || subPath.Contains("SUPPORT") return subPath.Contains("SETUP") || subPath.Contains("REDIST") || subPath.Contains("SUPPORT")
|| subPath.Contains("CRASH") && (subPath.Contains("PAD") || subPath.Contains("REPORT")) || subPath.Contains("HELPER") || subPath.Contains("CEFPROCESS") || subPath.Contains("CRASH") && (subPath.Contains("PAD") || subPath.Contains("REPORT")) ||
|| subPath.Contains("ZFGAMEBROWSER") || subPath.Contains("MONO") || subPath.Contains("PLUGINS") || subPath.Contains("MODDING") subPath.Contains("HELPER") || subPath.Contains("CEFPROCESS")
|| subPath.Contains("MOD") && subPath.Contains("MANAGER") || subPath.Contains("BATTLEYE") || subPath.Contains("ANTICHEAT"); || subPath.Contains("ZFGAMEBROWSER") || subPath.Contains("MONO") || subPath.Contains("PLUGINS") ||
subPath.Contains("MODDING")
|| subPath.Contains("MOD") && subPath.Contains("MANAGER") || subPath.Contains("BATTLEYE") ||
subPath.Contains("ANTICHEAT");
} }
internal static async Task<HashSet<string>> GetDllDirectoriesFromGameDirectory(this string gameDirectory, Platform platform) internal static async Task<HashSet<string>> GetDllDirectoriesFromGameDirectory(this string gameDirectory,
Platform platform)
=> await Task.Run(() => => await Task.Run(() =>
{ {
HashSet<string> dllDirectories = []; HashSet<string> dllDirectories = [];
@ -532,43 +551,54 @@ internal static class Resources
string subDirectory = directory.ResolvePath(); string subDirectory = directory.ResolvePath();
if (subDirectory is null || dllDirectories.Contains(subDirectory)) if (subDirectory is null || dllDirectories.Contains(subDirectory))
continue; continue;
bool koaloaderInstalled = Koaloader.AutoLoadDLLs.Select(pair => (pair.unlocker, path: directory + @"\" + pair.dll)) bool koaloaderInstalled = Koaloader.AutoLoadDLLs
.Any(pair => pair.path.FileExists() && pair.path.IsResourceFile()); .Select(pair => (pair.unlocker, path: directory + @"\" + pair.dll))
.Any(pair => pair.path.FileExists() && pair.path.IsResourceFile());
if (platform is Platform.Steam or Platform.Paradox) if (platform is Platform.Steam or Platform.Paradox)
{ {
subDirectory.GetSmokeApiComponents(out string api, out string api_o, out string api64, out string api64_o, out string old_config, subDirectory.GetSmokeApiComponents(out string api, out string api_o, out string api64,
out string api64_o, out string old_config,
out string config, out string old_log, out string log, out string cache); out string config, out string old_log, out string log, out string cache);
if (api.FileExists() || api_o.FileExists() || api64.FileExists() || api64_o.FileExists() if (api.FileExists() || api_o.FileExists() || api64.FileExists() || api64_o.FileExists()
|| (old_config.FileExists() || config.FileExists() || old_log.FileExists() || log.FileExists() || cache.FileExists()) || (old_config.FileExists() || config.FileExists() || old_log.FileExists() ||
&& !koaloaderInstalled) log.FileExists() || cache.FileExists())
&& !koaloaderInstalled)
_ = dllDirectories.Add(subDirectory); _ = dllDirectories.Add(subDirectory);
} }
if (platform is Platform.Epic or Platform.Paradox) if (platform is Platform.Epic or Platform.Paradox)
{ {
subDirectory.GetScreamApiComponents(out string api32, out string api32_o, out string api64, out string api64_o, out string config, subDirectory.GetScreamApiComponents(out string api32, out string api32_o, out string api64,
out string api64_o, out string config,
out string log); out string log);
if (api32.FileExists() || api32_o.FileExists() || api64.FileExists() || api64_o.FileExists() if (api32.FileExists() || api32_o.FileExists() || api64.FileExists() || api64_o.FileExists()
|| (config.FileExists() || log.FileExists()) && !koaloaderInstalled) || (config.FileExists() || log.FileExists()) && !koaloaderInstalled)
_ = dllDirectories.Add(subDirectory); _ = dllDirectories.Add(subDirectory);
} }
if (platform is Platform.Ubisoft) if (platform is Platform.Ubisoft)
{ {
subDirectory.GetUplayR1Components(out string api32, out string api32_o, out string api64, out string api64_o, out string config, subDirectory.GetUplayR1Components(out string api32, out string api32_o, out string api64,
out string api64_o, out string config,
out string log); out string log);
if (api32.FileExists() || api32_o.FileExists() || api64.FileExists() || api64_o.FileExists() if (api32.FileExists() || api32_o.FileExists() || api64.FileExists() || api64_o.FileExists()
|| (config.FileExists() || log.FileExists()) && !koaloaderInstalled) || (config.FileExists() || log.FileExists()) && !koaloaderInstalled)
_ = dllDirectories.Add(subDirectory); _ = dllDirectories.Add(subDirectory);
subDirectory.GetUplayR2Components(out string old_api32, out string old_api64, out api32, out api32_o, out api64, out api64_o, out config, subDirectory.GetUplayR2Components(out string old_api32, out string old_api64, out api32,
out api32_o, out api64, out api64_o, out config,
out log); out log);
if (old_api32.FileExists() || old_api64.FileExists() || api32.FileExists() || api32_o.FileExists() || api64.FileExists() if (old_api32.FileExists() || old_api64.FileExists() || api32.FileExists() ||
|| api64_o.FileExists() || (config.FileExists() || log.FileExists()) && !koaloaderInstalled) api32_o.FileExists() || api64.FileExists()
|| api64_o.FileExists() || (config.FileExists() || log.FileExists()) && !koaloaderInstalled)
_ = dllDirectories.Add(subDirectory); _ = dllDirectories.Add(subDirectory);
} }
} }
return dllDirectories.Count > 0 ? dllDirectories : null; return dllDirectories.Count > 0 ? dllDirectories : null;
}); });
internal static void GetCreamApiComponents(this string directory, out string api32, out string api32_o, out string api64, out string api64_o, internal static void GetCreamApiComponents(this string directory, out string api32, out string api32_o,
out string api64, out string api64_o,
out string config) out string config)
{ {
api32 = directory + @"\steam_api.dll"; api32 = directory + @"\steam_api.dll";
@ -588,14 +618,27 @@ internal static class Resources
internal static bool IsResourceFile(this string filePath, ResourceIdentifier identifier) internal static bool IsResourceFile(this string filePath, ResourceIdentifier identifier)
=> filePath.ComputeMD5() is { } hash && ResourceMD5s[identifier].Contains(hash); => filePath.ComputeMD5() is { } hash && ResourceMD5s[identifier].Contains(hash);
internal static bool IsResourceFile(this string filePath) => filePath.ComputeMD5() is { } hash && ResourceMD5s.Values.Any(hashes => hashes.Contains(hash)); internal static bool IsResourceFile(this string filePath) => filePath.ComputeMD5() is { } hash &&
ResourceMD5s.Values.Any(
hashes => hashes.Contains(hash));
internal enum BinaryType { Unknown = -1, BIT32 = 0, BIT64 = 6 } internal enum BinaryType
{
Unknown = -1,
BIT32 = 0,
BIT64 = 6
}
internal enum ResourceIdentifier internal enum ResourceIdentifier
{ {
Koaloader, Steamworks32, Steamworks64, Koaloader,
EpicOnlineServices32, EpicOnlineServices64, Uplay32, Steamworks32,
Uplay64, Upc32, Upc64 Steamworks64,
EpicOnlineServices32,
EpicOnlineServices64,
Uplay32,
Uplay64,
Upc32,
Upc64
} }
} }

View file

@ -11,7 +11,8 @@ namespace CreamInstaller.Resources;
internal static class ScreamAPI internal static class ScreamAPI
{ {
internal static void GetScreamApiComponents(this string directory, out string api32, out string api32_o, out string api64, out string api64_o, internal static void GetScreamApiComponents(this string directory, out string api32, out string api32_o,
out string api64, out string api64_o,
out string config, out string log) out string config, out string log)
{ {
api32 = directory + @"\EOSSDK-Win32-Shipping.dll"; api32 = directory + @"\EOSSDK-Win32-Shipping.dll";
@ -25,7 +26,8 @@ internal static class ScreamAPI
internal static void CheckConfig(string directory, Selection selection, InstallForm installForm = null) internal static void CheckConfig(string directory, Selection selection, InstallForm installForm = null)
{ {
directory.GetScreamApiComponents(out _, out _, out _, out _, out string config, out _); directory.GetScreamApiComponents(out _, out _, out _, out _, out string config, out _);
HashSet<SelectionDLC> overrideCatalogItems = selection.DLC.Where(dlc => dlc.Type is DLCType.Epic && !dlc.Enabled).ToHashSet(); HashSet<SelectionDLC> overrideCatalogItems =
selection.DLC.Where(dlc => dlc.Type is DLCType.Epic && !dlc.Enabled).ToHashSet();
int entitlementCount = 0; int entitlementCount = 0;
HashSet<SelectionDLC> injectedEntitlements = []; HashSet<SelectionDLC> injectedEntitlements = [];
foreach (SelectionDLC dlc in selection.DLC.Where(dlc => dlc.Type is DLCType.EpicEntitlement)) foreach (SelectionDLC dlc in selection.DLC.Where(dlc => dlc.Type is DLCType.EpicEntitlement))
@ -34,6 +36,7 @@ internal static class ScreamAPI
_ = injectedEntitlements.Add(dlc); _ = injectedEntitlements.Add(dlc);
entitlementCount++; entitlementCount++;
} }
foreach (Selection extraSelection in selection.ExtraSelections) foreach (Selection extraSelection in selection.ExtraSelections)
{ {
foreach (SelectionDLC extraDlc in extraSelection.DLC.Where(dlc => dlc.Type is DLCType.Epic && !dlc.Enabled)) foreach (SelectionDLC extraDlc in extraSelection.DLC.Where(dlc => dlc.Type is DLCType.Epic && !dlc.Enabled))
@ -45,6 +48,7 @@ internal static class ScreamAPI
entitlementCount++; entitlementCount++;
} }
} }
if (injectedEntitlements.Count == entitlementCount) if (injectedEntitlements.Count == entitlementCount)
injectedEntitlements.Clear(); injectedEntitlements.Clear();
if (overrideCatalogItems.Count > 0 || injectedEntitlements.Count > 0) if (overrideCatalogItems.Count > 0 || injectedEntitlements.Count > 0)
@ -53,15 +57,18 @@ internal static class ScreamAPI
installForm.UpdateUser("Generating ScreamAPI configuration for " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);*/ installForm.UpdateUser("Generating ScreamAPI configuration for " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);*/
config.CreateFile(true, installForm)?.Close(); config.CreateFile(true, installForm)?.Close();
StreamWriter writer = new(config, true, Encoding.UTF8); StreamWriter writer = new(config, true, Encoding.UTF8);
WriteConfig(writer, new(overrideCatalogItems.ToDictionary(dlc => dlc.Id, dlc => dlc), PlatformIdComparer.String), WriteConfig(writer,
new(injectedEntitlements.ToDictionary(dlc => dlc.Id, dlc => dlc), PlatformIdComparer.String), installForm); new(overrideCatalogItems.ToDictionary(dlc => dlc.Id, dlc => dlc), PlatformIdComparer.String),
new(injectedEntitlements.ToDictionary(dlc => dlc.Id, dlc => dlc), PlatformIdComparer.String),
installForm);
writer.Flush(); writer.Flush();
writer.Close(); writer.Close();
} }
else if (config.FileExists()) else if (config.FileExists())
{ {
config.DeleteFile(); config.DeleteFile();
installForm?.UpdateUser($"Deleted unnecessary configuration: {Path.GetFileName(config)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted unnecessary configuration: {Path.GetFileName(config)}", LogTextBox.Action,
false);
} }
} }
@ -83,13 +90,17 @@ internal static class ScreamAPI
{ {
SelectionDLC selectionDlc = pair.Value; SelectionDLC selectionDlc = pair.Value;
writer.WriteLine($" \"{selectionDlc.Id}\"{(pair.Equals(lastOverrideCatalogItem) ? "" : ",")}"); writer.WriteLine($" \"{selectionDlc.Id}\"{(pair.Equals(lastOverrideCatalogItem) ? "" : ",")}");
installForm?.UpdateUser($"Added locked catalog item to ScreamAPI.json with id {selectionDlc.Id} ({selectionDlc.Name})", LogTextBox.Action, installForm?.UpdateUser(
$"Added locked catalog item to ScreamAPI.json with id {selectionDlc.Id} ({selectionDlc.Name})",
LogTextBox.Action,
false); false);
} }
writer.WriteLine(" ]"); writer.WriteLine(" ]");
} }
else else
writer.WriteLine(" \"override\": []"); writer.WriteLine(" \"override\": []");
writer.WriteLine(" },"); writer.WriteLine(" },");
writer.WriteLine(" \"entitlements\": {"); writer.WriteLine(" \"entitlements\": {");
if (injectedEntitlements.Count > 0) if (injectedEntitlements.Count > 0)
@ -102,9 +113,12 @@ internal static class ScreamAPI
{ {
SelectionDLC selectionDlc = pair.Value; SelectionDLC selectionDlc = pair.Value;
writer.WriteLine($" \"{selectionDlc.Id}\"{(pair.Equals(lastEntitlement) ? "" : ",")}"); writer.WriteLine($" \"{selectionDlc.Id}\"{(pair.Equals(lastEntitlement) ? "" : ",")}");
installForm?.UpdateUser($"Added injected entitlement to ScreamAPI.json with id {selectionDlc.Id} ({selectionDlc.Name})", LogTextBox.Action, installForm?.UpdateUser(
$"Added injected entitlement to ScreamAPI.json with id {selectionDlc.Id} ({selectionDlc.Name})",
LogTextBox.Action,
false); false);
} }
writer.WriteLine(" ]"); writer.WriteLine(" ]");
} }
else else
@ -113,6 +127,7 @@ internal static class ScreamAPI
writer.WriteLine(" \"auto_inject\": true,"); writer.WriteLine(" \"auto_inject\": true,");
writer.WriteLine(" \"inject\": []"); writer.WriteLine(" \"inject\": []");
} }
writer.WriteLine(" }"); writer.WriteLine(" }");
writer.WriteLine("}"); writer.WriteLine("}");
} }
@ -120,7 +135,8 @@ internal static class ScreamAPI
internal static async Task Uninstall(string directory, InstallForm installForm = null, bool deleteOthers = true) internal static async Task Uninstall(string directory, InstallForm installForm = null, bool deleteOthers = true)
=> await Task.Run(() => => await Task.Run(() =>
{ {
directory.GetScreamApiComponents(out string api32, out string api32_o, out string api64, out string api64_o, out string config, out string log); directory.GetScreamApiComponents(out string api32, out string api32_o, out string api64, out string api64_o,
out string config, out string log);
if (api32_o.FileExists()) if (api32_o.FileExists())
{ {
if (api32.FileExists()) if (api32.FileExists())
@ -128,9 +144,12 @@ internal static class ScreamAPI
api32.DeleteFile(true); api32.DeleteFile(true);
installForm?.UpdateUser($"Deleted ScreamAPI: {Path.GetFileName(api32)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted ScreamAPI: {Path.GetFileName(api32)}", LogTextBox.Action, false);
} }
api32_o.MoveFile(api32!); api32_o.MoveFile(api32!);
installForm?.UpdateUser($"Restored EOS: {Path.GetFileName(api32_o)} -> {Path.GetFileName(api32)}", LogTextBox.Action, false); installForm?.UpdateUser($"Restored EOS: {Path.GetFileName(api32_o)} -> {Path.GetFileName(api32)}",
LogTextBox.Action, false);
} }
if (api64_o.FileExists()) if (api64_o.FileExists())
{ {
if (api64.FileExists()) if (api64.FileExists())
@ -138,9 +157,12 @@ internal static class ScreamAPI
api64.DeleteFile(true); api64.DeleteFile(true);
installForm?.UpdateUser($"Deleted ScreamAPI: {Path.GetFileName(api64)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted ScreamAPI: {Path.GetFileName(api64)}", LogTextBox.Action, false);
} }
api64_o.MoveFile(api64!); api64_o.MoveFile(api64!);
installForm?.UpdateUser($"Restored EOS: {Path.GetFileName(api64_o)} -> {Path.GetFileName(api64)}", LogTextBox.Action, false); installForm?.UpdateUser($"Restored EOS: {Path.GetFileName(api64_o)} -> {Path.GetFileName(api64)}",
LogTextBox.Action, false);
} }
if (!deleteOthers) if (!deleteOthers)
return; return;
if (config.FileExists()) if (config.FileExists())
@ -148,6 +170,7 @@ internal static class ScreamAPI
config.DeleteFile(); config.DeleteFile();
installForm?.UpdateUser($"Deleted configuration: {Path.GetFileName(config)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted configuration: {Path.GetFileName(config)}", LogTextBox.Action, false);
} }
if (log.FileExists()) if (log.FileExists())
{ {
log.DeleteFile(); log.DeleteFile();
@ -155,30 +178,38 @@ internal static class ScreamAPI
} }
}); });
internal static async Task Install(string directory, Selection selection, InstallForm installForm = null, bool generateConfig = true) internal static async Task Install(string directory, Selection selection, InstallForm installForm = null,
bool generateConfig = true)
=> await Task.Run(() => => await Task.Run(() =>
{ {
directory.GetScreamApiComponents(out string api32, out string api32_o, out string api64, out string api64_o, out _, out _); directory.GetScreamApiComponents(out string api32, out string api32_o, out string api64, out string api64_o,
out _, out _);
if (api32.FileExists() && !api32_o.FileExists()) if (api32.FileExists() && !api32_o.FileExists())
{ {
api32.MoveFile(api32_o!, true); api32.MoveFile(api32_o!, true);
installForm?.UpdateUser($"Renamed EOS: {Path.GetFileName(api32)} -> {Path.GetFileName(api32_o)}", LogTextBox.Action, false); installForm?.UpdateUser($"Renamed EOS: {Path.GetFileName(api32)} -> {Path.GetFileName(api32_o)}",
LogTextBox.Action, false);
} }
if (api32_o.FileExists()) if (api32_o.FileExists())
{ {
"ScreamAPI.EOSSDK-Win32-Shipping.dll".WriteManifestResource(api32); "ScreamAPI.EOSSDK-Win32-Shipping.dll".WriteManifestResource(api32);
installForm?.UpdateUser($"Wrote ScreamAPI: {Path.GetFileName(api32)}", LogTextBox.Action, false); installForm?.UpdateUser($"Wrote ScreamAPI: {Path.GetFileName(api32)}", LogTextBox.Action, false);
} }
if (api64.FileExists() && !api64_o.FileExists()) if (api64.FileExists() && !api64_o.FileExists())
{ {
api64.MoveFile(api64_o!, true); api64.MoveFile(api64_o!, true);
installForm?.UpdateUser($"Renamed EOS: {Path.GetFileName(api64)} -> {Path.GetFileName(api64_o)}", LogTextBox.Action, false); installForm?.UpdateUser($"Renamed EOS: {Path.GetFileName(api64)} -> {Path.GetFileName(api64_o)}",
LogTextBox.Action, false);
} }
if (api64_o.FileExists()) if (api64_o.FileExists())
{ {
"ScreamAPI.EOSSDK-Win64-Shipping.dll".WriteManifestResource(api64); "ScreamAPI.EOSSDK-Win64-Shipping.dll".WriteManifestResource(api64);
installForm?.UpdateUser($"Wrote ScreamAPI: {Path.GetFileName(api64)}", LogTextBox.Action, false); installForm?.UpdateUser($"Wrote ScreamAPI: {Path.GetFileName(api64)}", LogTextBox.Action, false);
} }
if (generateConfig) if (generateConfig)
CheckConfig(directory, selection, installForm); CheckConfig(directory, selection, installForm);
}); });

View file

@ -11,7 +11,8 @@ namespace CreamInstaller.Resources;
internal static class SmokeAPI internal static class SmokeAPI
{ {
internal static void GetSmokeApiComponents(this string directory, out string api32, out string api32_o, out string api64, out string api64_o, internal static void GetSmokeApiComponents(this string directory, out string api32, out string api32_o,
out string api64, out string api64_o,
out string old_config, out string config, out string old_log, out string log, out string cache) out string old_config, out string config, out string old_log, out string log, out string cache)
{ {
api32 = directory + @"\steam_api.dll"; api32 = directory + @"\steam_api.dll";
@ -27,35 +28,46 @@ internal static class SmokeAPI
internal static void CheckConfig(string directory, Selection selection, InstallForm installForm = null) internal static void CheckConfig(string directory, Selection selection, InstallForm installForm = null)
{ {
directory.GetSmokeApiComponents(out _, out _, out _, out _, out string old_config, out string config, out _, out _, out _); directory.GetSmokeApiComponents(out _, out _, out _, out _, out string old_config, out string config, out _,
out _, out _);
HashSet<SelectionDLC> overrideDlc = selection.DLC.Where(dlc => !dlc.Enabled).ToHashSet(); HashSet<SelectionDLC> overrideDlc = selection.DLC.Where(dlc => !dlc.Enabled).ToHashSet();
foreach (SelectionDLC extraDlc in selection.ExtraSelections.SelectMany(extraSelection => extraSelection.DLC.Where(dlc => !dlc.Enabled))) foreach (SelectionDLC extraDlc in selection.ExtraSelections.SelectMany(extraSelection =>
extraSelection.DLC.Where(dlc => !dlc.Enabled)))
_ = overrideDlc.Add(extraDlc); _ = overrideDlc.Add(extraDlc);
HashSet<SelectionDLC> injectDlc = []; HashSet<SelectionDLC> injectDlc = [];
if (selection.DLC.Count() > 64) if (selection.DLC.Count() > 64)
foreach (SelectionDLC hiddenDlc in selection.DLC.Where(dlc => dlc.Enabled && dlc.Type is DLCType.SteamHidden)) foreach (SelectionDLC hiddenDlc in selection.DLC.Where(
dlc => dlc.Enabled && dlc.Type is DLCType.SteamHidden))
_ = injectDlc.Add(hiddenDlc); _ = injectDlc.Add(hiddenDlc);
List<KeyValuePair<string, (string name, SortedList<string, SelectionDLC> injectDlc)>> extraApps = []; List<KeyValuePair<string, (string name, SortedList<string, SelectionDLC> injectDlc)>> extraApps = [];
foreach (Selection extraSelection in selection.ExtraSelections.Where(extraSelection => extraSelection.DLC.Count() > 64)) foreach (Selection extraSelection in selection.ExtraSelections.Where(extraSelection =>
extraSelection.DLC.Count() > 64))
{ {
SortedList<string, SelectionDLC> extraInjectDlc = new(PlatformIdComparer.String); SortedList<string, SelectionDLC> extraInjectDlc = new(PlatformIdComparer.String);
foreach (SelectionDLC extraDlc in extraSelection.DLC.Where(extraDlc => extraDlc.Enabled && extraDlc.Type is DLCType.SteamHidden)) foreach (SelectionDLC extraDlc in extraSelection.DLC.Where(extraDlc =>
extraDlc.Enabled && extraDlc.Type is DLCType.SteamHidden))
extraInjectDlc.Add(extraDlc.Id, extraDlc); extraInjectDlc.Add(extraDlc.Id, extraDlc);
if (extraInjectDlc.Count > 0) if (extraInjectDlc.Count > 0)
extraApps.Add(new(extraSelection.Id, (extraSelection.Name, extraInjectDlc))); extraApps.Add(new(extraSelection.Id, (extraSelection.Name, extraInjectDlc)));
} }
if (old_config.FileExists()) if (old_config.FileExists())
{ {
old_config.DeleteFile(); old_config.DeleteFile();
installForm?.UpdateUser($"Deleted old configuration: {Path.GetFileName(old_config)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted old configuration: {Path.GetFileName(old_config)}", LogTextBox.Action,
false);
} }
if (selection.ExtraSelections.Any(extraSelection => extraSelection.DLC.Any()) || overrideDlc.Count > 0 || injectDlc.Count > 0)
if (selection.ExtraSelections.Any(extraSelection => extraSelection.DLC.Any()) || overrideDlc.Count > 0 ||
injectDlc.Count > 0)
{ {
/*if (installForm is not null) /*if (installForm is not null)
installForm.UpdateUser("Generating SmokeAPI configuration for " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);*/ installForm.UpdateUser("Generating SmokeAPI configuration for " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);*/
config.CreateFile(true, installForm)?.Close(); config.CreateFile(true, installForm)?.Close();
StreamWriter writer = new(config, true, Encoding.UTF8); StreamWriter writer = new(config, true, Encoding.UTF8);
WriteConfig(writer, selection.Id, new(extraApps.ToDictionary(extraApp => extraApp.Key, extraApp => extraApp.Value), PlatformIdComparer.String), WriteConfig(writer, selection.Id,
new(extraApps.ToDictionary(extraApp => extraApp.Key, extraApp => extraApp.Value),
PlatformIdComparer.String),
new(overrideDlc.ToDictionary(dlc => dlc.Id, dlc => dlc), PlatformIdComparer.String), new(overrideDlc.ToDictionary(dlc => dlc.Id, dlc => dlc), PlatformIdComparer.String),
new(injectDlc.ToDictionary(dlc => dlc.Id, dlc => dlc), PlatformIdComparer.String), installForm); new(injectDlc.ToDictionary(dlc => dlc.Id, dlc => dlc), PlatformIdComparer.String), installForm);
writer.Flush(); writer.Flush();
@ -64,12 +76,15 @@ internal static class SmokeAPI
else if (config.FileExists()) else if (config.FileExists())
{ {
config.DeleteFile(); config.DeleteFile();
installForm?.UpdateUser($"Deleted unnecessary configuration: {Path.GetFileName(config)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted unnecessary configuration: {Path.GetFileName(config)}", LogTextBox.Action,
false);
} }
} }
private static void WriteConfig(StreamWriter writer, string appId, SortedList<string, (string name, SortedList<string, SelectionDLC> injectDlc)> extraApps, private static void WriteConfig(StreamWriter writer, string appId,
SortedList<string, SelectionDLC> overrideDlc, SortedList<string, SelectionDLC> injectDlc, InstallForm installForm = null) SortedList<string, (string name, SortedList<string, SelectionDLC> injectDlc)> extraApps,
SortedList<string, SelectionDLC> overrideDlc, SortedList<string, SelectionDLC> injectDlc,
InstallForm installForm = null)
{ {
writer.WriteLine("{"); writer.WriteLine("{");
writer.WriteLine(" \"$version\": 2,"); writer.WriteLine(" \"$version\": 2,");
@ -85,13 +100,17 @@ internal static class SmokeAPI
{ {
SelectionDLC selectionDlc = pair.Value; SelectionDLC selectionDlc = pair.Value;
writer.WriteLine($" \"{selectionDlc.Id}\": \"locked\"{(pair.Equals(lastOverrideDlc) ? "" : ",")}"); writer.WriteLine($" \"{selectionDlc.Id}\": \"locked\"{(pair.Equals(lastOverrideDlc) ? "" : ",")}");
installForm?.UpdateUser($"Added locked DLC to SmokeAPI.config.json with appid {selectionDlc.Id} ({selectionDlc.Name})", LogTextBox.Action, installForm?.UpdateUser(
$"Added locked DLC to SmokeAPI.config.json with appid {selectionDlc.Id} ({selectionDlc.Name})",
LogTextBox.Action,
false); false);
} }
writer.WriteLine(" },"); writer.WriteLine(" },");
} }
else else
writer.WriteLine(" \"override_dlc_status\": {},"); writer.WriteLine(" \"override_dlc_status\": {},");
writer.WriteLine(" \"auto_inject_inventory\": true,"); writer.WriteLine(" \"auto_inject_inventory\": true,");
writer.WriteLine(" \"extra_inventory_items\": [],"); writer.WriteLine(" \"extra_inventory_items\": [],");
if (injectDlc.Count > 0 || extraApps.Count > 0) if (injectDlc.Count > 0 || extraApps.Count > 0)
@ -105,17 +124,24 @@ internal static class SmokeAPI
foreach (KeyValuePair<string, SelectionDLC> pair in injectDlc) foreach (KeyValuePair<string, SelectionDLC> pair in injectDlc)
{ {
SelectionDLC selectionDlc = pair.Value; SelectionDLC selectionDlc = pair.Value;
writer.WriteLine($" \"{selectionDlc.Id}\": \"{selectionDlc.Name}\"{(pair.Equals(lastInjectDlc) ? "" : ",")}"); writer.WriteLine(
installForm?.UpdateUser($"Added extra DLC to SmokeAPI.config.json with appid {selectionDlc.Id} ({selectionDlc.Name})", LogTextBox.Action, $" \"{selectionDlc.Id}\": \"{selectionDlc.Name}\"{(pair.Equals(lastInjectDlc) ? "" : ",")}");
installForm?.UpdateUser(
$"Added extra DLC to SmokeAPI.config.json with appid {selectionDlc.Id} ({selectionDlc.Name})",
LogTextBox.Action,
false); false);
} }
writer.WriteLine(" }"); writer.WriteLine(" }");
writer.WriteLine(extraApps.Count > 0 ? " }," : " }"); writer.WriteLine(extraApps.Count > 0 ? " }," : " }");
} }
if (extraApps.Count > 0) if (extraApps.Count > 0)
{ {
KeyValuePair<string, (string name, SortedList<string, SelectionDLC> injectDlc)> lastExtraApp = extraApps.Last(); KeyValuePair<string, (string name, SortedList<string, SelectionDLC> injectDlc)> lastExtraApp =
foreach (KeyValuePair<string, (string name, SortedList<string, SelectionDLC> injectDlc)> pair in extraApps) extraApps.Last();
foreach (KeyValuePair<string, (string name, SortedList<string, SelectionDLC> injectDlc)> pair in
extraApps)
{ {
string extraAppId = pair.Key; string extraAppId = pair.Key;
(string _ /*extraAppName*/, SortedList<string, SelectionDLC> extraInjectDlc) = pair.Value; (string _ /*extraAppName*/, SortedList<string, SelectionDLC> extraInjectDlc) = pair.Value;
@ -126,18 +152,23 @@ internal static class SmokeAPI
foreach (KeyValuePair<string, SelectionDLC> extraPair in extraInjectDlc) foreach (KeyValuePair<string, SelectionDLC> extraPair in extraInjectDlc)
{ {
SelectionDLC selectionDlc = extraPair.Value; SelectionDLC selectionDlc = extraPair.Value;
writer.WriteLine($" \"{selectionDlc.Id}\": \"{selectionDlc.Name}\"{(extraPair.Equals(lastExtraAppDlc) ? "" : ",")}"); writer.WriteLine(
installForm?.UpdateUser($"Added extra DLC to SmokeAPI.config.json with appid {selectionDlc.Id} ({selectionDlc.Name})", $" \"{selectionDlc.Id}\": \"{selectionDlc.Name}\"{(extraPair.Equals(lastExtraAppDlc) ? "" : ",")}");
installForm?.UpdateUser(
$"Added extra DLC to SmokeAPI.config.json with appid {selectionDlc.Id} ({selectionDlc.Name})",
LogTextBox.Action, false); LogTextBox.Action, false);
} }
writer.WriteLine(" }"); writer.WriteLine(" }");
writer.WriteLine(pair.Equals(lastExtraApp) ? " }" : " },"); writer.WriteLine(pair.Equals(lastExtraApp) ? " }" : " },");
} }
} }
writer.WriteLine(" },"); writer.WriteLine(" },");
} }
else else
writer.WriteLine(" \"extra_dlcs\": {},"); writer.WriteLine(" \"extra_dlcs\": {},");
writer.WriteLine(" \"store_config\": null"); writer.WriteLine(" \"store_config\": null");
writer.WriteLine("}"); writer.WriteLine("}");
} }
@ -149,9 +180,12 @@ internal static class SmokeAPI
if (oldConfig.FileExists()) if (oldConfig.FileExists())
{ {
oldConfig.DeleteFile(); oldConfig.DeleteFile();
installForm?.UpdateUser($"Deleted old CreamAPI configuration: {Path.GetFileName(oldConfig)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted old CreamAPI configuration: {Path.GetFileName(oldConfig)}",
LogTextBox.Action, false);
} }
directory.GetSmokeApiComponents(out string api32, out string api32_o, out string api64, out string api64_o, out string old_config,
directory.GetSmokeApiComponents(out string api32, out string api32_o, out string api64, out string api64_o,
out string old_config,
out string config, out string old_log, out string log, out string cache); out string config, out string old_log, out string log, out string cache);
if (api32_o.FileExists()) if (api32_o.FileExists())
{ {
@ -160,9 +194,13 @@ internal static class SmokeAPI
api32.DeleteFile(true); api32.DeleteFile(true);
installForm?.UpdateUser($"Deleted SmokeAPI: {Path.GetFileName(api32)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted SmokeAPI: {Path.GetFileName(api32)}", LogTextBox.Action, false);
} }
api32_o.MoveFile(api32!); api32_o.MoveFile(api32!);
installForm?.UpdateUser($"Restored Steamworks: {Path.GetFileName(api32_o)} -> {Path.GetFileName(api32)}", LogTextBox.Action, false); installForm?.UpdateUser(
$"Restored Steamworks: {Path.GetFileName(api32_o)} -> {Path.GetFileName(api32)}", LogTextBox.Action,
false);
} }
if (api64_o.FileExists()) if (api64_o.FileExists())
{ {
if (api64.FileExists()) if (api64.FileExists())
@ -170,31 +208,40 @@ internal static class SmokeAPI
api64.DeleteFile(true); api64.DeleteFile(true);
installForm?.UpdateUser($"Deleted SmokeAPI: {Path.GetFileName(api64)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted SmokeAPI: {Path.GetFileName(api64)}", LogTextBox.Action, false);
} }
api64_o.MoveFile(api64!); api64_o.MoveFile(api64!);
installForm?.UpdateUser($"Restored Steamworks: {Path.GetFileName(api64_o)} -> {Path.GetFileName(api64)}", LogTextBox.Action, false); installForm?.UpdateUser(
$"Restored Steamworks: {Path.GetFileName(api64_o)} -> {Path.GetFileName(api64)}", LogTextBox.Action,
false);
} }
if (!deleteOthers) if (!deleteOthers)
return; return;
if (old_config.FileExists()) if (old_config.FileExists())
{ {
old_config.DeleteFile(); old_config.DeleteFile();
installForm?.UpdateUser($"Deleted configuration: {Path.GetFileName(old_config)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted configuration: {Path.GetFileName(old_config)}", LogTextBox.Action,
false);
} }
if (config.FileExists()) if (config.FileExists())
{ {
config.DeleteFile(); config.DeleteFile();
installForm?.UpdateUser($"Deleted configuration: {Path.GetFileName(config)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted configuration: {Path.GetFileName(config)}", LogTextBox.Action, false);
} }
if (cache.FileExists()) if (cache.FileExists())
{ {
cache.DeleteFile(); cache.DeleteFile();
installForm?.UpdateUser($"Deleted cache: {Path.GetFileName(cache)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted cache: {Path.GetFileName(cache)}", LogTextBox.Action, false);
} }
if (old_log.FileExists()) if (old_log.FileExists())
{ {
old_log.DeleteFile(); old_log.DeleteFile();
installForm?.UpdateUser($"Deleted log: {Path.GetFileName(old_log)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted log: {Path.GetFileName(old_log)}", LogTextBox.Action, false);
} }
if (log.FileExists()) if (log.FileExists())
{ {
log.DeleteFile(); log.DeleteFile();
@ -202,36 +249,46 @@ internal static class SmokeAPI
} }
}); });
internal static async Task Install(string directory, Selection selection, InstallForm installForm = null, bool generateConfig = true) internal static async Task Install(string directory, Selection selection, InstallForm installForm = null,
bool generateConfig = true)
=> await Task.Run(() => => await Task.Run(() =>
{ {
directory.GetCreamApiComponents(out _, out _, out _, out _, out string oldConfig); directory.GetCreamApiComponents(out _, out _, out _, out _, out string oldConfig);
if (oldConfig.FileExists()) if (oldConfig.FileExists())
{ {
oldConfig.DeleteFile(); oldConfig.DeleteFile();
installForm?.UpdateUser($"Deleted old CreamAPI configuration: {Path.GetFileName(oldConfig)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted old CreamAPI configuration: {Path.GetFileName(oldConfig)}",
LogTextBox.Action, false);
} }
directory.GetSmokeApiComponents(out string api32, out string api32_o, out string api64, out string api64_o, out _, out _, out _, out _, out _);
directory.GetSmokeApiComponents(out string api32, out string api32_o, out string api64, out string api64_o,
out _, out _, out _, out _, out _);
if (api32.FileExists() && !api32_o.FileExists()) if (api32.FileExists() && !api32_o.FileExists())
{ {
api32.MoveFile(api32_o!, true); api32.MoveFile(api32_o!, true);
installForm?.UpdateUser($"Renamed Steamworks: {Path.GetFileName(api32)} -> {Path.GetFileName(api32_o)}", LogTextBox.Action, false); installForm?.UpdateUser($"Renamed Steamworks: {Path.GetFileName(api32)} -> {Path.GetFileName(api32_o)}",
LogTextBox.Action, false);
} }
if (api32_o.FileExists()) if (api32_o.FileExists())
{ {
"SmokeAPI.steam_api.dll".WriteManifestResource(api32); "SmokeAPI.steam_api.dll".WriteManifestResource(api32);
installForm?.UpdateUser($"Wrote SmokeAPI: {Path.GetFileName(api32)}", LogTextBox.Action, false); installForm?.UpdateUser($"Wrote SmokeAPI: {Path.GetFileName(api32)}", LogTextBox.Action, false);
} }
if (api64.FileExists() && !api64_o.FileExists()) if (api64.FileExists() && !api64_o.FileExists())
{ {
api64.MoveFile(api64_o!, true); api64.MoveFile(api64_o!, true);
installForm?.UpdateUser($"Renamed Steamworks: {Path.GetFileName(api64)} -> {Path.GetFileName(api64_o)}", LogTextBox.Action, false); installForm?.UpdateUser($"Renamed Steamworks: {Path.GetFileName(api64)} -> {Path.GetFileName(api64_o)}",
LogTextBox.Action, false);
} }
if (api64_o.FileExists()) if (api64_o.FileExists())
{ {
"SmokeAPI.steam_api64.dll".WriteManifestResource(api64); "SmokeAPI.steam_api64.dll".WriteManifestResource(api64);
installForm?.UpdateUser($"Wrote SmokeAPI: {Path.GetFileName(api64)}", LogTextBox.Action, false); installForm?.UpdateUser($"Wrote SmokeAPI: {Path.GetFileName(api64)}", LogTextBox.Action, false);
} }
if (generateConfig) if (generateConfig)
CheckConfig(directory, selection, installForm); CheckConfig(directory, selection, installForm);
}); });

View file

@ -11,7 +11,8 @@ namespace CreamInstaller.Resources;
internal static class UplayR1 internal static class UplayR1
{ {
internal static void GetUplayR1Components(this string directory, out string api32, out string api32_o, out string api64, out string api64_o, internal static void GetUplayR1Components(this string directory, out string api32, out string api32_o,
out string api64, out string api64_o,
out string config, out string log) out string config, out string log)
{ {
api32 = directory + @"\uplay_r1_loader.dll"; api32 = directory + @"\uplay_r1_loader.dll";
@ -26,7 +27,8 @@ internal static class UplayR1
{ {
directory.GetUplayR1Components(out _, out _, out _, out _, out string config, out _); directory.GetUplayR1Components(out _, out _, out _, out _, out string config, out _);
HashSet<SelectionDLC> blacklistDlc = selection.DLC.Where(dlc => !dlc.Enabled).ToHashSet(); HashSet<SelectionDLC> blacklistDlc = selection.DLC.Where(dlc => !dlc.Enabled).ToHashSet();
foreach (SelectionDLC extraDlc in selection.ExtraSelections.SelectMany(extraSelection => extraSelection.DLC.Where(dlc => !dlc.Enabled))) foreach (SelectionDLC extraDlc in selection.ExtraSelections.SelectMany(extraSelection =>
extraSelection.DLC.Where(dlc => !dlc.Enabled)))
_ = blacklistDlc.Add(extraDlc); _ = blacklistDlc.Add(extraDlc);
if (blacklistDlc.Count > 0) if (blacklistDlc.Count > 0)
{ {
@ -34,18 +36,21 @@ internal static class UplayR1
installForm.UpdateUser("Generating Uplay R1 Unlocker configuration for " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);*/ installForm.UpdateUser("Generating Uplay R1 Unlocker configuration for " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);*/
config.CreateFile(true, installForm)?.Close(); config.CreateFile(true, installForm)?.Close();
StreamWriter writer = new(config, true, Encoding.UTF8); StreamWriter writer = new(config, true, Encoding.UTF8);
WriteConfig(writer, new(blacklistDlc.ToDictionary(dlc => dlc.Id, dlc => dlc), PlatformIdComparer.String), installForm); WriteConfig(writer, new(blacklistDlc.ToDictionary(dlc => dlc.Id, dlc => dlc), PlatformIdComparer.String),
installForm);
writer.Flush(); writer.Flush();
writer.Close(); writer.Close();
} }
else if (config.FileExists()) else if (config.FileExists())
{ {
config.DeleteFile(); config.DeleteFile();
installForm?.UpdateUser($"Deleted unnecessary configuration: {Path.GetFileName(config)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted unnecessary configuration: {Path.GetFileName(config)}", LogTextBox.Action,
false);
} }
} }
private static void WriteConfig(StreamWriter writer, SortedList<string, SelectionDLC> blacklistDlc, InstallForm installForm = null) private static void WriteConfig(StreamWriter writer, SortedList<string, SelectionDLC> blacklistDlc,
InstallForm installForm = null)
{ {
writer.WriteLine("{"); writer.WriteLine("{");
writer.WriteLine(" \"logging\": false,"); writer.WriteLine(" \"logging\": false,");
@ -59,40 +64,53 @@ internal static class UplayR1
{ {
SelectionDLC selectionDlc = pair.Value; SelectionDLC selectionDlc = pair.Value;
writer.WriteLine($" {selectionDlc.Id}{(pair.Equals(lastBlacklistDlc) ? "" : ",")}"); writer.WriteLine($" {selectionDlc.Id}{(pair.Equals(lastBlacklistDlc) ? "" : ",")}");
installForm?.UpdateUser($"Added blacklist DLC to UplayR1Unlocker.jsonc with appid {selectionDlc.Id} ({selectionDlc.Name})", LogTextBox.Action, installForm?.UpdateUser(
$"Added blacklist DLC to UplayR1Unlocker.jsonc with appid {selectionDlc.Id} ({selectionDlc.Name})",
LogTextBox.Action,
false); false);
} }
writer.WriteLine(" ],"); writer.WriteLine(" ],");
} }
else else
writer.WriteLine(" \"blacklist\": [],"); writer.WriteLine(" \"blacklist\": [],");
writer.WriteLine("}"); writer.WriteLine("}");
} }
internal static async Task Uninstall(string directory, InstallForm installForm = null, bool deleteOthers = true) internal static async Task Uninstall(string directory, InstallForm installForm = null, bool deleteOthers = true)
=> await Task.Run(() => => await Task.Run(() =>
{ {
directory.GetUplayR1Components(out string api32, out string api32_o, out string api64, out string api64_o, out string config, out string log); directory.GetUplayR1Components(out string api32, out string api32_o, out string api64, out string api64_o,
out string config, out string log);
if (api32_o.FileExists()) if (api32_o.FileExists())
{ {
if (api32.FileExists()) if (api32.FileExists())
{ {
api32.DeleteFile(true); api32.DeleteFile(true);
installForm?.UpdateUser($"Deleted Uplay R1 Unlocker: {Path.GetFileName(api32)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted Uplay R1 Unlocker: {Path.GetFileName(api32)}", LogTextBox.Action,
false);
} }
api32_o.MoveFile(api32!); api32_o.MoveFile(api32!);
installForm?.UpdateUser($"Restored Uplay R1: {Path.GetFileName(api32_o)} -> {Path.GetFileName(api32)}", LogTextBox.Action, false); installForm?.UpdateUser($"Restored Uplay R1: {Path.GetFileName(api32_o)} -> {Path.GetFileName(api32)}",
LogTextBox.Action, false);
} }
if (api64_o.FileExists()) if (api64_o.FileExists())
{ {
if (api64.FileExists()) if (api64.FileExists())
{ {
api64.DeleteFile(true); api64.DeleteFile(true);
installForm?.UpdateUser($"Deleted Uplay R1 Unlocker: {Path.GetFileName(api64)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted Uplay R1 Unlocker: {Path.GetFileName(api64)}", LogTextBox.Action,
false);
} }
api64_o.MoveFile(api64!); api64_o.MoveFile(api64!);
installForm?.UpdateUser($"Restored Uplay R1: {Path.GetFileName(api64_o)} -> {Path.GetFileName(api64)}", LogTextBox.Action, false); installForm?.UpdateUser($"Restored Uplay R1: {Path.GetFileName(api64_o)} -> {Path.GetFileName(api64)}",
LogTextBox.Action, false);
} }
if (!deleteOthers) if (!deleteOthers)
return; return;
if (config.FileExists()) if (config.FileExists())
@ -100,36 +118,47 @@ internal static class UplayR1
config.DeleteFile(); config.DeleteFile();
installForm?.UpdateUser($"Deleted configuration: {Path.GetFileName(config)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted configuration: {Path.GetFileName(config)}", LogTextBox.Action, false);
} }
if (!log.FileExists()) if (!log.FileExists())
return; return;
log.DeleteFile(); log.DeleteFile();
installForm?.UpdateUser($"Deleted log: {Path.GetFileName(log)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted log: {Path.GetFileName(log)}", LogTextBox.Action, false);
}); });
internal static async Task Install(string directory, Selection selection, InstallForm installForm = null, bool generateConfig = true) internal static async Task Install(string directory, Selection selection, InstallForm installForm = null,
bool generateConfig = true)
=> await Task.Run(() => => await Task.Run(() =>
{ {
directory.GetUplayR1Components(out string api32, out string api32_o, out string api64, out string api64_o, out _, out _); directory.GetUplayR1Components(out string api32, out string api32_o, out string api64, out string api64_o,
out _, out _);
if (api32.FileExists() && !api32_o.FileExists()) if (api32.FileExists() && !api32_o.FileExists())
{ {
api32.MoveFile(api32_o!, true); api32.MoveFile(api32_o!, true);
installForm?.UpdateUser($"Renamed Uplay R1: {Path.GetFileName(api32)} -> {Path.GetFileName(api32_o)}", LogTextBox.Action, false); installForm?.UpdateUser($"Renamed Uplay R1: {Path.GetFileName(api32)} -> {Path.GetFileName(api32_o)}",
LogTextBox.Action, false);
} }
if (api32_o.FileExists()) if (api32_o.FileExists())
{ {
"UplayR1.uplay_r1_loader.dll".WriteManifestResource(api32); "UplayR1.uplay_r1_loader.dll".WriteManifestResource(api32);
installForm?.UpdateUser($"Wrote Uplay R1 Unlocker: {Path.GetFileName(api32)}", LogTextBox.Action, false); installForm?.UpdateUser($"Wrote Uplay R1 Unlocker: {Path.GetFileName(api32)}", LogTextBox.Action,
false);
} }
if (api64.FileExists() && !api64_o.FileExists()) if (api64.FileExists() && !api64_o.FileExists())
{ {
api64.MoveFile(api64_o!, true); api64.MoveFile(api64_o!, true);
installForm?.UpdateUser($"Renamed Uplay R1: {Path.GetFileName(api64)} -> {Path.GetFileName(api64_o)}", LogTextBox.Action, false); installForm?.UpdateUser($"Renamed Uplay R1: {Path.GetFileName(api64)} -> {Path.GetFileName(api64_o)}",
LogTextBox.Action, false);
} }
if (api64_o.FileExists()) if (api64_o.FileExists())
{ {
"UplayR1.uplay_r1_loader64.dll".WriteManifestResource(api64); "UplayR1.uplay_r1_loader64.dll".WriteManifestResource(api64);
installForm?.UpdateUser($"Wrote Uplay R1 Unlocker: {Path.GetFileName(api64)}", LogTextBox.Action, false); installForm?.UpdateUser($"Wrote Uplay R1 Unlocker: {Path.GetFileName(api64)}", LogTextBox.Action,
false);
} }
if (generateConfig) if (generateConfig)
CheckConfig(directory, selection, installForm); CheckConfig(directory, selection, installForm);
}); });

View file

@ -11,7 +11,8 @@ namespace CreamInstaller.Resources;
internal static class UplayR2 internal static class UplayR2
{ {
internal static void GetUplayR2Components(this string directory, out string old_api32, out string old_api64, out string api32, out string api32_o, internal static void GetUplayR2Components(this string directory, out string old_api32, out string old_api64,
out string api32, out string api32_o,
out string api64, out string api64_o, out string config, out string log) out string api64, out string api64_o, out string config, out string log)
{ {
old_api32 = directory + @"\uplay_r2_loader.dll"; old_api32 = directory + @"\uplay_r2_loader.dll";
@ -28,7 +29,8 @@ internal static class UplayR2
{ {
directory.GetUplayR2Components(out _, out _, out _, out _, out _, out _, out string config, out _); directory.GetUplayR2Components(out _, out _, out _, out _, out _, out _, out string config, out _);
HashSet<SelectionDLC> blacklistDlc = selection.DLC.Where(dlc => !dlc.Enabled).ToHashSet(); HashSet<SelectionDLC> blacklistDlc = selection.DLC.Where(dlc => !dlc.Enabled).ToHashSet();
foreach (SelectionDLC extraDlc in selection.ExtraSelections.SelectMany(extraSelection => extraSelection.DLC.Where(dlc => !dlc.Enabled))) foreach (SelectionDLC extraDlc in selection.ExtraSelections.SelectMany(extraSelection =>
extraSelection.DLC.Where(dlc => !dlc.Enabled)))
_ = blacklistDlc.Add(extraDlc); _ = blacklistDlc.Add(extraDlc);
if (blacklistDlc.Count > 0) if (blacklistDlc.Count > 0)
{ {
@ -36,18 +38,21 @@ internal static class UplayR2
installForm.UpdateUser("Generating Uplay R2 Unlocker configuration for " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);*/ installForm.UpdateUser("Generating Uplay R2 Unlocker configuration for " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);*/
config.CreateFile(true, installForm)?.Close(); config.CreateFile(true, installForm)?.Close();
StreamWriter writer = new(config, true, Encoding.UTF8); StreamWriter writer = new(config, true, Encoding.UTF8);
WriteConfig(writer, new(blacklistDlc.ToDictionary(dlc => dlc.Id, dlc => dlc), PlatformIdComparer.String), installForm); WriteConfig(writer, new(blacklistDlc.ToDictionary(dlc => dlc.Id, dlc => dlc), PlatformIdComparer.String),
installForm);
writer.Flush(); writer.Flush();
writer.Close(); writer.Close();
} }
else if (config.FileExists()) else if (config.FileExists())
{ {
config.DeleteFile(); config.DeleteFile();
installForm?.UpdateUser($"Deleted unnecessary configuration: {Path.GetFileName(config)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted unnecessary configuration: {Path.GetFileName(config)}", LogTextBox.Action,
false);
} }
} }
private static void WriteConfig(StreamWriter writer, SortedList<string, SelectionDLC> blacklistDlc, InstallForm installForm = null) private static void WriteConfig(StreamWriter writer, SortedList<string, SelectionDLC> blacklistDlc,
InstallForm installForm = null)
{ {
writer.WriteLine("{"); writer.WriteLine("{");
writer.WriteLine(" \"logging\": false,"); writer.WriteLine(" \"logging\": false,");
@ -63,20 +68,25 @@ internal static class UplayR2
{ {
SelectionDLC selectionDlc = pair.Value; SelectionDLC selectionDlc = pair.Value;
writer.WriteLine($" {selectionDlc.Id}{(pair.Equals(lastBlacklistDlc) ? "" : ",")}"); writer.WriteLine($" {selectionDlc.Id}{(pair.Equals(lastBlacklistDlc) ? "" : ",")}");
installForm?.UpdateUser($"Added blacklist DLC to UplayR2Unlocker.jsonc with appid {selectionDlc.Id} ({selectionDlc.Name})", LogTextBox.Action, installForm?.UpdateUser(
$"Added blacklist DLC to UplayR2Unlocker.jsonc with appid {selectionDlc.Id} ({selectionDlc.Name})",
LogTextBox.Action,
false); false);
} }
writer.WriteLine(" ],"); writer.WriteLine(" ],");
} }
else else
writer.WriteLine(" \"blacklist\": [],"); writer.WriteLine(" \"blacklist\": [],");
writer.WriteLine("}"); writer.WriteLine("}");
} }
internal static async Task Uninstall(string directory, InstallForm installForm = null, bool deleteOthers = true) internal static async Task Uninstall(string directory, InstallForm installForm = null, bool deleteOthers = true)
=> await Task.Run(() => => await Task.Run(() =>
{ {
directory.GetUplayR2Components(out string old_api32, out string old_api64, out string api32, out string api32_o, out string api64, directory.GetUplayR2Components(out string old_api32, out string old_api64, out string api32,
out string api32_o, out string api64,
out string api64_o, out string config, out string log); out string api64_o, out string config, out string log);
if (api32_o.FileExists()) if (api32_o.FileExists())
{ {
@ -84,22 +94,30 @@ internal static class UplayR2
if (api.FileExists()) if (api.FileExists())
{ {
api.DeleteFile(true); api.DeleteFile(true);
installForm?.UpdateUser($"Deleted Uplay R2 Unlocker: {Path.GetFileName(api)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted Uplay R2 Unlocker: {Path.GetFileName(api)}", LogTextBox.Action,
false);
} }
api32_o.MoveFile(api!); api32_o.MoveFile(api!);
installForm?.UpdateUser($"Restored Uplay R2: {Path.GetFileName(api32_o)} -> {Path.GetFileName(api)}", LogTextBox.Action, false); installForm?.UpdateUser($"Restored Uplay R2: {Path.GetFileName(api32_o)} -> {Path.GetFileName(api)}",
LogTextBox.Action, false);
} }
if (api64_o.FileExists()) if (api64_o.FileExists())
{ {
string api = old_api64.FileExists() ? old_api64 : api64; string api = old_api64.FileExists() ? old_api64 : api64;
if (api.FileExists()) if (api.FileExists())
{ {
api.DeleteFile(true); api.DeleteFile(true);
installForm?.UpdateUser($"Deleted Uplay R2 Unlocker: {Path.GetFileName(api)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted Uplay R2 Unlocker: {Path.GetFileName(api)}", LogTextBox.Action,
false);
} }
api64_o.MoveFile(api!); api64_o.MoveFile(api!);
installForm?.UpdateUser($"Restored Uplay R2: {Path.GetFileName(api64_o)} -> {Path.GetFileName(api)}", LogTextBox.Action, false); installForm?.UpdateUser($"Restored Uplay R2: {Path.GetFileName(api64_o)} -> {Path.GetFileName(api)}",
LogTextBox.Action, false);
} }
if (!deleteOthers) if (!deleteOthers)
return; return;
if (config.FileExists()) if (config.FileExists())
@ -107,39 +125,48 @@ internal static class UplayR2
config.DeleteFile(); config.DeleteFile();
installForm?.UpdateUser($"Deleted configuration: {Path.GetFileName(config)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted configuration: {Path.GetFileName(config)}", LogTextBox.Action, false);
} }
if (!log.FileExists()) if (!log.FileExists())
return; return;
log.DeleteFile(); log.DeleteFile();
installForm?.UpdateUser($"Deleted log: {Path.GetFileName(log)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted log: {Path.GetFileName(log)}", LogTextBox.Action, false);
}); });
internal static async Task Install(string directory, Selection selection, InstallForm installForm = null, bool generateConfig = true) internal static async Task Install(string directory, Selection selection, InstallForm installForm = null,
bool generateConfig = true)
=> await Task.Run(() => => await Task.Run(() =>
{ {
directory.GetUplayR2Components(out string old_api32, out string old_api64, out string api32, out string api32_o, out string api64, directory.GetUplayR2Components(out string old_api32, out string old_api64, out string api32,
out string api32_o, out string api64,
out string api64_o, out _, out _); out string api64_o, out _, out _);
string api = old_api32.FileExists() ? old_api32 : api32; string api = old_api32.FileExists() ? old_api32 : api32;
if (api.FileExists() && !api32_o.FileExists()) if (api.FileExists() && !api32_o.FileExists())
{ {
api.MoveFile(api32_o!, true); api.MoveFile(api32_o!, true);
installForm?.UpdateUser($"Renamed Uplay R2: {Path.GetFileName(api)} -> {Path.GetFileName(api32_o)}", LogTextBox.Action, false); installForm?.UpdateUser($"Renamed Uplay R2: {Path.GetFileName(api)} -> {Path.GetFileName(api32_o)}",
LogTextBox.Action, false);
} }
if (api32_o.FileExists()) if (api32_o.FileExists())
{ {
"UplayR2.upc_r2_loader.dll".WriteManifestResource(api); "UplayR2.upc_r2_loader.dll".WriteManifestResource(api);
installForm?.UpdateUser($"Wrote Uplay R2 Unlocker: {Path.GetFileName(api)}", LogTextBox.Action, false); installForm?.UpdateUser($"Wrote Uplay R2 Unlocker: {Path.GetFileName(api)}", LogTextBox.Action, false);
} }
api = old_api64.FileExists() ? old_api64 : api64; api = old_api64.FileExists() ? old_api64 : api64;
if (api.FileExists() && !api64_o.FileExists()) if (api.FileExists() && !api64_o.FileExists())
{ {
api.MoveFile(api64_o!, true); api.MoveFile(api64_o!, true);
installForm?.UpdateUser($"Renamed Uplay R2: {Path.GetFileName(api)} -> {Path.GetFileName(api64_o)}", LogTextBox.Action, false); installForm?.UpdateUser($"Renamed Uplay R2: {Path.GetFileName(api)} -> {Path.GetFileName(api64_o)}",
LogTextBox.Action, false);
} }
if (api64_o.FileExists()) if (api64_o.FileExists())
{ {
"UplayR2.upc_r2_loader64.dll".WriteManifestResource(api); "UplayR2.upc_r2_loader64.dll".WriteManifestResource(api);
installForm?.UpdateUser($"Wrote Uplay R2 Unlocker: {Path.GetFileName(api)}", LogTextBox.Action, false); installForm?.UpdateUser($"Wrote Uplay R2 Unlocker: {Path.GetFileName(api)}", LogTextBox.Action, false);
} }
if (generateConfig) if (generateConfig)
CheckConfig(directory, selection, installForm); CheckConfig(directory, selection, installForm);
}); });

View file

@ -11,8 +11,11 @@ namespace CreamInstaller;
public enum Platform public enum Platform
{ {
None = 0, Paradox, Steam, None = 0,
Epic, Ubisoft Paradox,
Steam,
Epic,
Ubisoft
} }
internal sealed class Selection : IEquatable<Selection> internal sealed class Selection : IEquatable<Selection>
@ -57,15 +60,23 @@ internal sealed class Selection : IEquatable<Selection>
internal static IEnumerable<Selection> AllEnabled => All.Keys.Where(s => s.Enabled); internal static IEnumerable<Selection> AllEnabled => All.Keys.Where(s => s.Enabled);
internal bool Enabled { get => TreeNode.Checked; set => TreeNode.Checked = value; } internal bool Enabled
{
get => TreeNode.Checked;
set => TreeNode.Checked = value;
}
internal IEnumerable<SelectionDLC> DLC => SelectionDLC.All.Keys.Where(dlc => Equals(dlc.Selection, this)); internal IEnumerable<SelectionDLC> DLC => SelectionDLC.All.Keys.Where(dlc => Equals(dlc.Selection, this));
public bool Equals(Selection other) => other is not null && (ReferenceEquals(this, other) || Id == other.Id && Platform == other.Platform); public bool Equals(Selection other) => other is not null &&
(ReferenceEquals(this, other) ||
Id == other.Id && Platform == other.Platform);
internal static Selection GetOrCreate(Platform platform, string id, string name, string rootDirectory, HashSet<string> dllDirectories, internal static Selection GetOrCreate(Platform platform, string id, string name, string rootDirectory,
HashSet<string> dllDirectories,
List<(string directory, BinaryType binaryType)> executableDirectories) List<(string directory, BinaryType binaryType)> executableDirectories)
=> FromId(platform, id) ?? new Selection(platform, id, name, rootDirectory, dllDirectories, executableDirectories); => FromId(platform, id) ??
new Selection(platform, id, name, rootDirectory, dllDirectories, executableDirectories);
internal void Remove() internal void Remove()
{ {
@ -82,16 +93,19 @@ internal sealed class Selection : IEquatable<Selection>
Remove(); Remove();
return; return;
} }
if (Program.IsGameBlocked(Name, RootDirectory)) if (Program.IsGameBlocked(Name, RootDirectory))
{ {
Remove(); Remove();
return; return;
} }
if (!RootDirectory.DirectoryExists()) if (!RootDirectory.DirectoryExists())
{ {
Remove(); Remove();
return; return;
} }
_ = DllDirectories.RemoveWhere(directory => !directory.DirectoryExists()); _ = DllDirectories.RemoveWhere(directory => !directory.DirectoryExists());
if (DllDirectories.Count < 1) if (DllDirectories.Count < 1)
Remove(); Remove();
@ -103,7 +117,8 @@ internal sealed class Selection : IEquatable<Selection>
selection.Validate(programsToScan); selection.Validate(programsToScan);
} }
internal static Selection FromId(Platform platform, string gameId) => All.Keys.FirstOrDefault(s => s.Platform == platform && s.Id == gameId); internal static Selection FromId(Platform platform, string gameId) =>
All.Keys.FirstOrDefault(s => s.Platform == platform && s.Id == gameId);
public override bool Equals(object obj) => ReferenceEquals(this, obj) || obj is Selection other && Equals(other); public override bool Equals(object obj) => ReferenceEquals(this, obj) || obj is Selection other && Equals(other);

View file

@ -7,8 +7,11 @@ namespace CreamInstaller;
public enum DLCType public enum DLCType
{ {
None = 0, Steam, SteamHidden, None = 0,
Epic, EpicEntitlement Steam,
SteamHidden,
Epic,
EpicEntitlement
} }
internal sealed class SelectionDLC : IEquatable<SelectionDLC> internal sealed class SelectionDLC : IEquatable<SelectionDLC>
@ -32,7 +35,11 @@ internal sealed class SelectionDLC : IEquatable<SelectionDLC>
TreeNode = new() { Tag = Type, Name = Id, Text = Name }; TreeNode = new() { Tag = Type, Name = Id, Text = Name };
} }
internal bool Enabled { get => TreeNode.Checked; set => TreeNode.Checked = value; } internal bool Enabled
{
get => TreeNode.Checked;
set => TreeNode.Checked = value;
}
internal Selection Selection internal Selection Selection
{ {
@ -57,7 +64,8 @@ internal sealed class SelectionDLC : IEquatable<SelectionDLC>
} }
public bool Equals(SelectionDLC other) public bool Equals(SelectionDLC other)
=> other is not null && (ReferenceEquals(this, other) || Type == other.Type && Selection?.Id == other.Selection?.Id && Id == other.Id); => other is not null && (ReferenceEquals(this, other) ||
Type == other.Type && Selection?.Id == other.Selection?.Id && Id == other.Id);
internal static SelectionDLC GetOrCreate(DLCType type, string gameId, string id, string name) internal static SelectionDLC GetOrCreate(DLCType type, string gameId, string id, string name)
=> FromId(type, gameId, id) ?? new SelectionDLC(type, id, name); => FromId(type, gameId, id) ?? new SelectionDLC(type, id, name);

View file

@ -34,13 +34,17 @@ internal static class Diagnostics
OpenFileInWindowsNotepad(path); OpenFileInWindowsNotepad(path);
} }
private static void OpenFileInNotepadPlusPlus(string npp, string path) => Process.Start(new ProcessStartInfo { FileName = npp, Arguments = path }); private static void OpenFileInNotepadPlusPlus(string npp, string path) =>
Process.Start(new ProcessStartInfo { FileName = npp, Arguments = path });
private static void OpenFileInWindowsNotepad(string path) => Process.Start(new ProcessStartInfo { FileName = "notepad.exe", Arguments = path }); private static void OpenFileInWindowsNotepad(string path) => Process.Start(new ProcessStartInfo
{ FileName = "notepad.exe", Arguments = path });
internal static void OpenDirectoryInFileExplorer(string path) => Process.Start(new ProcessStartInfo { FileName = "explorer.exe", Arguments = path }); internal static void OpenDirectoryInFileExplorer(string path) => Process.Start(new ProcessStartInfo
{ FileName = "explorer.exe", Arguments = path });
internal static void OpenUrlInInternetBrowser(string url) => Process.Start(new ProcessStartInfo { FileName = url, UseShellExecute = true }); internal static void OpenUrlInInternetBrowser(string url) =>
Process.Start(new ProcessStartInfo { FileName = url, UseShellExecute = true });
internal static string ResolvePath(this string path) internal static string ResolvePath(this string path)
{ {

View file

@ -30,20 +30,27 @@ internal static class ExceptionHandler
int ciNum = line.LastIndexOf(@"CreamInstaller\", StringComparison.Ordinal); int ciNum = line.LastIndexOf(@"CreamInstaller\", StringComparison.Ordinal);
int lineNum = line.LastIndexOf(":line ", StringComparison.Ordinal); int lineNum = line.LastIndexOf(":line ", StringComparison.Ordinal);
if (atNum != -1) if (atNum != -1)
_ = output.Append("\n " + (inNum != -1 ? line[atNum..(inNum - 1)] : line[atNum..]) + (inNum != -1 _ = output.Append("\n " + (inNum != -1 ? line[atNum..(inNum - 1)] : line[atNum..]) +
? "\n " + (ciNum != -1 (inNum != -1
? "in " + (lineNum != -1 ? line[ciNum..lineNum] + "\n on " + line[(lineNum + 1)..] : line[ciNum..]) ? "\n " + (ciNum != -1
: line[inNum..]) ? "in " + (lineNum != -1
: null)); ? line[ciNum..lineNum] + "\n on " +
line[(lineNum + 1)..]
: line[ciNum..])
: line[inNum..])
: null));
} }
} }
e = e.InnerException; e = e.InnerException;
stackDepth++; stackDepth++;
} }
return output.ToString(); return output.ToString();
} }
internal static bool HandleException(this Exception e, Form form = null, string caption = null, string acceptButtonText = "Retry", internal static bool HandleException(this Exception e, Form form = null, string caption = null,
string acceptButtonText = "Retry",
string cancelButtonText = "Cancel") string cancelButtonText = "Cancel")
{ {
caption ??= Program.Name + " encountered an exception"; caption ??= Program.Name + " encountered an exception";
@ -51,12 +58,14 @@ internal static class ExceptionHandler
if (string.IsNullOrWhiteSpace(outputString)) if (string.IsNullOrWhiteSpace(outputString))
outputString = e?.ToString() ?? "Unknown exception"; outputString = e?.ToString() ?? "Unknown exception";
using DialogForm dialogForm = new(form ?? Form.ActiveForm); using DialogForm dialogForm = new(form ?? Form.ActiveForm);
return dialogForm.Show(SystemIcons.Error, outputString, acceptButtonText, cancelButtonText, caption) is DialogResult.OK; return dialogForm.Show(SystemIcons.Error, outputString, acceptButtonText, cancelButtonText, caption) is
DialogResult.OK;
} }
internal static void HandleFatalException(this Exception e) internal static void HandleFatalException(this Exception e)
{ {
e.HandleException(caption: Program.Name + " encountered a fatal exception", acceptButtonText: "OK", cancelButtonText: null); e.HandleException(caption: Program.Name + " encountered a fatal exception", acceptButtonText: "OK",
cancelButtonText: null);
Application.Exit(); Application.Exit();
} }
} }

View file

@ -29,8 +29,10 @@ internal static class HttpClientManager
try try
{ {
using HttpRequestMessage request = new(HttpMethod.Get, url); using HttpRequestMessage request = new(HttpMethod.Get, url);
using HttpResponseMessage response = await HttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); using HttpResponseMessage response =
if (response.StatusCode is HttpStatusCode.NotModified && HttpContentCache.TryGetValue(url, out string content)) await HttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
if (response.StatusCode is HttpStatusCode.NotModified &&
HttpContentCache.TryGetValue(url, out string content))
return content; return content;
_ = response.EnsureSuccessStatusCode(); _ = response.EnsureSuccessStatusCode();
content = await response.Content.ReadAsStringAsync(); content = await response.Content.ReadAsStringAsync();

View file

@ -5,7 +5,8 @@ namespace CreamInstaller.Utility;
internal static class IconGrabber internal static class IconGrabber
{ {
internal const string SteamAppImagesPath = "https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/apps/"; internal const string SteamAppImagesPath =
"https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/apps/";
private const string GoogleFaviconsApiUrl = "https://www.google.com/s2/favicons"; private const string GoogleFaviconsApiUrl = "https://www.google.com/s2/favicons";
@ -15,13 +16,16 @@ internal static class IconGrabber
return Icon.FromHandle(dialogIconBitmap.GetHicon()); return Icon.FromHandle(dialogIconBitmap.GetHicon());
} }
internal static string GetDomainFaviconUrl(string domain, int size = 16) => GoogleFaviconsApiUrl + $"?domain={domain}&sz={size}"; internal static string GetDomainFaviconUrl(string domain, int size = 16) =>
GoogleFaviconsApiUrl + $"?domain={domain}&sz={size}";
internal static Image GetFileIconImage(this string path) => path.FileExists() ? Icon.ExtractAssociatedIcon(path)?.ToBitmap() : null; internal static Image GetFileIconImage(this string path) =>
path.FileExists() ? Icon.ExtractAssociatedIcon(path)?.ToBitmap() : null;
internal static Image GetNotepadImage() => GetFileIconImage(Diagnostics.GetNotepadPath()); internal static Image GetNotepadImage() => GetFileIconImage(Diagnostics.GetNotepadPath());
internal static Image GetCommandPromptImage() => GetFileIconImage(Environment.SystemDirectory + @"\cmd.exe"); internal static Image GetCommandPromptImage() => GetFileIconImage(Environment.SystemDirectory + @"\cmd.exe");
internal static Image GetFileExplorerImage() => GetFileIconImage(Environment.GetFolderPath(Environment.SpecialFolder.Windows) + @"\explorer.exe"); internal static Image GetFileExplorerImage() =>
GetFileIconImage(Environment.GetFolderPath(Environment.SpecialFolder.Windows) + @"\explorer.exe");
} }

View file

@ -15,8 +15,10 @@ internal static partial class NativeImports
[LibraryImport("kernel32.dll", SetLastError = true), DefaultDllImportSearchPaths(DllImportSearchPath.System32)] [LibraryImport("kernel32.dll", SetLastError = true), DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool GetBinaryType([MarshalAs(UnmanagedType.LPStr)] string lpApplicationName, out BinaryType lpBinaryType); internal static partial bool GetBinaryType([MarshalAs(UnmanagedType.LPStr)] string lpApplicationName,
out BinaryType lpBinaryType);
[LibraryImport("user32.dll", SetLastError = true), DefaultDllImportSearchPaths(DllImportSearchPath.System32)] [LibraryImport("user32.dll", SetLastError = true), DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
internal static partial void SetWindowPos(nint hWnd, nint hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags); internal static partial void SetWindowPos(nint hWnd, nint hWndInsertAfter, int x, int y, int cx, int cy,
uint uFlags);
} }

View file

@ -10,8 +10,11 @@ namespace CreamInstaller.Utility;
internal static class ProgramData internal static class ProgramData
{ {
private static readonly string DirectoryPathOld = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\CreamInstaller"; private static readonly string DirectoryPathOld =
internal static readonly string DirectoryPath = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + @"\CreamInstaller"; Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\CreamInstaller";
internal static readonly string DirectoryPath =
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + @"\CreamInstaller";
internal static readonly string AppInfoPath = DirectoryPath + @"\appinfo"; internal static readonly string AppInfoPath = DirectoryPath + @"\appinfo";
private static readonly string AppInfoVersionPath = AppInfoPath + @"\version.txt"; private static readonly string AppInfoVersionPath = AppInfoPath + @"\version.txt";
@ -33,13 +36,17 @@ internal static class ProgramData
DirectoryPath.DeleteDirectory(); DirectoryPath.DeleteDirectory();
DirectoryPathOld.MoveDirectory(DirectoryPath, true, form); DirectoryPathOld.MoveDirectory(DirectoryPath, true, form);
} }
DirectoryPath.CreateDirectory(); DirectoryPath.CreateDirectory();
if (!AppInfoVersionPath.FileExists() || !Version.TryParse(AppInfoVersionPath.ReadFile(), out Version version) || version < MinimumAppInfoVersion) if (!AppInfoVersionPath.FileExists() ||
!Version.TryParse(AppInfoVersionPath.ReadFile(), out Version version) ||
version < MinimumAppInfoVersion)
{ {
AppInfoPath.DeleteDirectory(); AppInfoPath.DeleteDirectory();
AppInfoPath.CreateDirectory(); AppInfoPath.CreateDirectory();
AppInfoVersionPath.WriteFile(Program.Version); AppInfoVersionPath.WriteFile(Program.Version);
} }
CooldownPath.CreateDirectory(); CooldownPath.CreateDirectory();
if (OldProgramChoicesPath.FileExists()) if (OldProgramChoicesPath.FileExists())
OldProgramChoicesPath.DeleteFile(); OldProgramChoicesPath.DeleteFile();
@ -71,6 +78,7 @@ internal static class ProgramData
{ {
// ignored // ignored
} }
return null; return null;
} }
@ -93,7 +101,8 @@ internal static class ProgramData
if (ProgramChoicesPath.FileExists()) if (ProgramChoicesPath.FileExists())
try try
{ {
if (JsonConvert.DeserializeObject(ProgramChoicesPath.ReadFile(), typeof(List<(Platform platform, string id)>)) is if (JsonConvert.DeserializeObject(ProgramChoicesPath.ReadFile(),
typeof(List<(Platform platform, string id)>)) is
List<(Platform platform, string id)> choices) List<(Platform platform, string id)> choices)
return choices; return choices;
} }
@ -101,6 +110,7 @@ internal static class ProgramData
{ {
// ignored // ignored
} }
return Enumerable.Empty<(Platform platform, string id)>(); return Enumerable.Empty<(Platform platform, string id)>();
} }
@ -124,7 +134,8 @@ internal static class ProgramData
if (DlcChoicesPath.FileExists()) if (DlcChoicesPath.FileExists())
try try
{ {
if (JsonConvert.DeserializeObject(DlcChoicesPath.ReadFile(), typeof(IEnumerable<(Platform platform, string gameId, string dlcId)>)) is if (JsonConvert.DeserializeObject(DlcChoicesPath.ReadFile(),
typeof(IEnumerable<(Platform platform, string gameId, string dlcId)>)) is
IEnumerable<(Platform platform, string gameId, string dlcId)> choices) IEnumerable<(Platform platform, string gameId, string dlcId)> choices)
return choices; return choices;
} }
@ -132,6 +143,7 @@ internal static class ProgramData
{ {
// ignored // ignored
} }
return Enumerable.Empty<(Platform platform, string gameId, string dlcId)>(); return Enumerable.Empty<(Platform platform, string gameId, string dlcId)>();
} }
@ -164,10 +176,12 @@ internal static class ProgramData
{ {
// ignored // ignored
} }
return Enumerable.Empty<(Platform platform, string id, string proxy, bool enabled)>(); return Enumerable.Empty<(Platform platform, string id, string proxy, bool enabled)>();
} }
internal static void WriteKoaloaderProxyChoices(IEnumerable<(Platform platform, string id, string proxy, bool enabled)> choices) internal static void WriteKoaloaderProxyChoices(
IEnumerable<(Platform platform, string id, string proxy, bool enabled)> choices)
{ {
try try
{ {

View file

@ -26,12 +26,14 @@ internal static class SafeIO
} }
catch (Exception e) catch (Exception e)
{ {
if (!crucial || directoryPath.DirectoryExists() || directoryPath.IOWarn("Failed to create a crucial directory", e, form) is not DialogResult.OK) if (!crucial || directoryPath.DirectoryExists() ||
directoryPath.IOWarn("Failed to create a crucial directory", e, form) is not DialogResult.OK)
break; break;
} }
} }
internal static void MoveDirectory(this string directoryPath, string newDirectoryPath, bool crucial = false, Form form = null) internal static void MoveDirectory(this string directoryPath, string newDirectoryPath, bool crucial = false,
Form form = null)
{ {
if (!directoryPath.DirectoryExists()) if (!directoryPath.DirectoryExists())
return; return;
@ -43,7 +45,8 @@ internal static class SafeIO
} }
catch (Exception e) catch (Exception e)
{ {
if (!crucial || !directoryPath.DirectoryExists() || directoryPath.IOWarn("Failed to move a crucial directory", e, form) is not DialogResult.OK) if (!crucial || !directoryPath.DirectoryExists() ||
directoryPath.IOWarn("Failed to move a crucial directory", e, form) is not DialogResult.OK)
break; break;
} }
} }
@ -61,12 +64,14 @@ internal static class SafeIO
catch (Exception e) catch (Exception e)
{ {
if (!crucial || !directoryPath.DirectoryExists() if (!crucial || !directoryPath.DirectoryExists()
|| directoryPath.IOWarn("Failed to delete a crucial directory", e, form) is not DialogResult.OK) || directoryPath.IOWarn("Failed to delete a crucial directory", e, form) is not
DialogResult.OK)
break; break;
} }
} }
internal static IEnumerable<string> EnumerateDirectory(this string directoryPath, string filePattern, bool subdirectories = false, bool crucial = false, internal static IEnumerable<string> EnumerateDirectory(this string directoryPath, string filePattern,
bool subdirectories = false, bool crucial = false,
Form form = null) Form form = null)
{ {
if (!directoryPath.DirectoryExists()) if (!directoryPath.DirectoryExists())
@ -75,19 +80,23 @@ internal static class SafeIO
try try
{ {
return subdirectories return subdirectories
? Directory.EnumerateFiles(directoryPath, filePattern, new EnumerationOptions { RecurseSubdirectories = true }) ? Directory.EnumerateFiles(directoryPath, filePattern,
new EnumerationOptions { RecurseSubdirectories = true })
: Directory.EnumerateFiles(directoryPath, filePattern); : Directory.EnumerateFiles(directoryPath, filePattern);
} }
catch (Exception e) catch (Exception e)
{ {
if (!crucial || !directoryPath.DirectoryExists() if (!crucial || !directoryPath.DirectoryExists()
|| directoryPath.IOWarn("Failed to enumerate a crucial directory's files", e, form) is not DialogResult.OK) || directoryPath.IOWarn("Failed to enumerate a crucial directory's files", e, form) is not
DialogResult.OK)
break; break;
} }
return Enumerable.Empty<string>(); return Enumerable.Empty<string>();
} }
internal static IEnumerable<string> EnumerateSubdirectories(this string directoryPath, string directoryPattern, bool subdirectories = false, internal static IEnumerable<string> EnumerateSubdirectories(this string directoryPath, string directoryPattern,
bool subdirectories = false,
bool crucial = false, Form form = null) bool crucial = false, Form form = null)
{ {
if (!directoryPath.DirectoryExists()) if (!directoryPath.DirectoryExists())
@ -96,15 +105,18 @@ internal static class SafeIO
try try
{ {
return subdirectories return subdirectories
? Directory.EnumerateDirectories(directoryPath, directoryPattern, new EnumerationOptions { RecurseSubdirectories = true }) ? Directory.EnumerateDirectories(directoryPath, directoryPattern,
new EnumerationOptions { RecurseSubdirectories = true })
: Directory.EnumerateDirectories(directoryPath, directoryPattern); : Directory.EnumerateDirectories(directoryPath, directoryPattern);
} }
catch (Exception e) catch (Exception e)
{ {
if (!crucial || !directoryPath.DirectoryExists() if (!crucial || !directoryPath.DirectoryExists()
|| directoryPath.IOWarn("Failed to enumerate a crucial directory's subdirectories", e, form) is not DialogResult.OK) || directoryPath.IOWarn("Failed to enumerate a crucial directory's subdirectories", e,
form) is not DialogResult.OK)
break; break;
} }
return Enumerable.Empty<string>(); return Enumerable.Empty<string>();
} }
@ -122,6 +134,7 @@ internal static class SafeIO
if (!crucial || filePath.IOWarn("Failed to create a crucial file", e, form) is not DialogResult.OK) if (!crucial || filePath.IOWarn("Failed to create a crucial file", e, form) is not DialogResult.OK)
break; break;
} }
return null; return null;
} }
@ -137,7 +150,8 @@ internal static class SafeIO
} }
catch (Exception e) catch (Exception e)
{ {
if (!crucial || !filePath.FileExists() || filePath.IOWarn("Failed to move a crucial file", e, form) is not DialogResult.OK) if (!crucial || !filePath.FileExists() ||
filePath.IOWarn("Failed to move a crucial file", e, form) is not DialogResult.OK)
break; break;
} }
} }
@ -154,7 +168,8 @@ internal static class SafeIO
} }
catch (Exception e) catch (Exception e)
{ {
if (!crucial || !filePath.FileExists() || filePath.IOWarn("Failed to delete a crucial file", e, form) is not DialogResult.OK) if (!crucial || !filePath.FileExists() ||
filePath.IOWarn("Failed to delete a crucial file", e, form) is not DialogResult.OK)
break; break;
} }
} }
@ -170,9 +185,11 @@ internal static class SafeIO
} }
catch (Exception e) catch (Exception e)
{ {
if (!crucial || !filePath.FileExists() || filePath.IOWarn("Failed to read a crucial file", e, form) is not DialogResult.OK) if (!crucial || !filePath.FileExists() ||
filePath.IOWarn("Failed to read a crucial file", e, form) is not DialogResult.OK)
break; break;
} }
return null; return null;
} }
@ -187,9 +204,11 @@ internal static class SafeIO
} }
catch (Exception e) catch (Exception e)
{ {
if (!crucial || !filePath.FileExists() || filePath.IOWarn("Failed to read a crucial file", e, form) is not DialogResult.OK) if (!crucial || !filePath.FileExists() ||
filePath.IOWarn("Failed to read a crucial file", e, form) is not DialogResult.OK)
break; break;
} }
return null; return null;
} }
@ -208,7 +227,8 @@ internal static class SafeIO
} }
} }
internal static void ExtractZip(this string archivePath, string destinationPath, bool crucial = false, Form form = null) internal static void ExtractZip(this string archivePath, string destinationPath, bool crucial = false,
Form form = null)
{ {
if (!archivePath.FileExists()) if (!archivePath.FileExists())
return; return;
@ -220,7 +240,8 @@ internal static class SafeIO
} }
catch (Exception e) catch (Exception e)
{ {
if (!crucial || !archivePath.FileExists() || archivePath.IOWarn("Failed to extract a crucial zip file", e, form) is not DialogResult.OK) if (!crucial || !archivePath.FileExists() ||
archivePath.IOWarn("Failed to extract a crucial zip file", e, form) is not DialogResult.OK)
break; break;
} }
} }
@ -255,6 +276,7 @@ internal static class SafeIO
description += e.FormatException(); description += e.FormatException();
break; break;
} }
DialogResult result = dialogForm.Show(SystemIcons.Warning, description, "Retry", "Cancel"); DialogResult result = dialogForm.Show(SystemIcons.Warning, description, "Retry", "Cancel");
if (result is not DialogResult.OK) if (result is not DialogResult.OK)
Program.Canceled = true; Program.Canceled = true;