- Large code refactoring and optimizations
- The more essential SteamCMD directories (config and userdata) will no longer be cleaned up by the program, hopefully to speed up the first call
- Games can now also use SteamCMD as a fallback similar to DLC (previously both steam store AND steamCMD output was required for games, now it's either OR)
This commit is contained in:
pointfeev 2022-03-08 18:58:52 -05:00
parent 0ad6efd7dd
commit 1815f1087d
24 changed files with 138 additions and 226 deletions

View file

@ -10,7 +10,7 @@ internal class AppIdComparer : IComparer<string>
public int Compare(string a, string b) =>
a == "ParadoxLauncher" ? -1
: b == "ParadoxLauncher" ? 1
: !int.TryParse(a, out _) && !int.TryParse(b, out _) ? string.Compare(a, b)
: !int.TryParse(a, out _) && !int.TryParse(b, out _) ? string.Compare(a, b, System.StringComparison.Ordinal)
: !int.TryParse(a, out int A) ? 1
: !int.TryParse(b, out int B) ? -1
: A > B ? 1

View file

@ -29,9 +29,9 @@ internal class CustomTreeView : TreeView
Graphics graphics = e.Graphics;
Color backColor = BackColor;
SolidBrush brush = new(backColor);
using SolidBrush brush = new(backColor);
Font font = Font;
Font subFont = new(font.FontFamily, font.SizeInPoints, FontStyle.Regular, font.Unit, font.GdiCharSet, font.GdiVerticalFont);
using Font subFont = new(font.FontFamily, font.SizeInPoints, FontStyle.Regular, font.Unit, font.GdiCharSet, font.GdiVerticalFont);
string subText = node.Name;
if (string.IsNullOrWhiteSpace(subText) || subText == "ParadoxLauncher"

View file

@ -7,16 +7,12 @@
<IncludeAllContentForSelfExtract>true</IncludeAllContentForSelfExtract>
<Version>3.2.2.0</Version>
<PackageIcon>Resources\ini.ico</PackageIcon>
<PackageIconUrl />
<Description />
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<Copyright>2021, pointfeev (https://github.com/pointfeev)</Copyright>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageProjectUrl>https://github.com/pointfeev/CreamInstaller</PackageProjectUrl>
<RepositoryUrl>https://github.com/pointfeev/CreamInstaller</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageReleaseNotes />
<PackageTags>steam, dlc</PackageTags>
<AssemblyName>CreamInstaller</AssemblyName>
<Company>CreamInstaller</Company>
<Product>CreamAPI/ScreamAPI Installer &amp; Configuration Generator</Product>
@ -25,11 +21,19 @@
<StartupObject>CreamInstaller.Program</StartupObject>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
<SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
<ProduceReferenceAssembly>False</ProduceReferenceAssembly>
<SignAssembly>False</SignAssembly>
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<AnalysisLevel>latest-all</AnalysisLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>embedded</DebugType>
<DebugSymbols>true</DebugSymbols>
<DefineConstants>TRACE</DefineConstants>
<CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Gameloop.Vdf" Version="0.6.1" />

View file

@ -10,7 +10,7 @@ namespace CreamInstaller.Epic;
internal static class EpicLibrary
{
private static string epicAppDataPath = null;
private static string epicAppDataPath;
internal static string EpicAppDataPath
{
get

View file

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
@ -50,7 +51,7 @@ internal static class EpicStore
KeyImage keyImage = element.KeyImages[i];
if (keyImage.Type == "DieselStoreFront")
{
icon = keyImage.Url;
icon = keyImage.Url.ToString();
break;
}
}
@ -69,7 +70,7 @@ internal static class EpicStore
KeyImage keyImage = element.KeyImages[i];
if (keyImage.Type == "Thumbnail")
{
icon = keyImage.Url;
icon = keyImage.Url.ToString();
break;
}
}
@ -105,11 +106,11 @@ internal static class EpicStore
string encoded = HttpUtility.UrlEncode(categoryNamespace);
Request request = new(encoded);
string payload = JsonConvert.SerializeObject(request);
HttpContent content = new StringContent(payload);
using HttpContent content = new StringContent(payload);
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
HttpClient client = HttpClientManager.HttpClient;
if (client is null) return null;
HttpResponseMessage httpResponse = await client.PostAsync("https://graphql.epicgames.com/graphql", content);
HttpResponseMessage httpResponse = await client.PostAsync(new Uri("https://graphql.epicgames.com/graphql"), content);
httpResponse.EnsureSuccessStatusCode();
string response = await httpResponse.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<Response>(response);

View file

@ -1,17 +1,16 @@

#pragma warning disable IDE0051 // Remove unused private members
#pragma warning disable IDE0052 // Remove unread private members
#pragma warning disable CA1812 // Avoid uninstantiated internal classes
#pragma warning disable CA1822 // Mark members as static
using Newtonsoft.Json;
namespace CreamInstaller.Epic.GraphQL;
internal class Request
{
#pragma warning disable IDE0051 // Remove unused private members
#pragma warning disable CA1822 // Mark members as static
#pragma warning disable IDE0052 // Remove unread private members
#pragma warning disable IDE1006 // Naming Styles
[JsonProperty(PropertyName = "query")]
private string _gqlQuery => @"query searchOffers($namespace: String!) {
private string Query => @"query searchOffers($namespace: String!) {
Catalog {
searchStore(category: ""*"", namespace: $namespace){
elements {
@ -57,26 +56,22 @@ internal class Request
}";
[JsonProperty(PropertyName = "variables")]
private Variables _variables { get; set; }
private Variables Vars { get; set; }
internal Request(string _namespace) => _variables = new Variables(_namespace);
internal Request(string @namespace) => Vars = new Variables(@namespace);
private class Headers
{
[JsonProperty(PropertyName = "Content-Type")]
private string _contentType => "application/graphql";
private string ContentType => "application/graphql";
}
private class Variables
{
[JsonProperty(PropertyName = "namespace")]
private string _namespace { get; set; }
private string Namespace { get; set; }
internal Variables(string _namespace) => this._namespace = _namespace;
internal Variables(string @namespace) => Namespace = @namespace;
}
#pragma warning restore IDE0051 // Remove unused private members
#pragma warning restore CA1822 // Mark members as static
#pragma warning restore IDE0052 // Remove unread private members
#pragma warning restore IDE1006 // Naming Styles
}
}

View file

@ -1,4 +1,7 @@

#pragma warning disable CA1819 // Properties should not return arrays
using System;
using Newtonsoft.Json;
namespace CreamInstaller.Epic.GraphQL;
@ -6,10 +9,10 @@ namespace CreamInstaller.Epic.GraphQL;
public class Response
{
[JsonProperty(PropertyName = "data")]
public Data Data { get; protected set; }
public ResponseData Data { get; protected set; }
}
public class Data
public class ResponseData
{
[JsonProperty(PropertyName = "Catalog")]
public Catalog Catalog { get; protected set; }
@ -66,7 +69,7 @@ public class KeyImage
public string Type { get; protected set; }
[JsonProperty(PropertyName = "url")]
public string Url { get; protected set; }
public Uri Url { get; protected set; }
}
public class CatalogNs

View file

@ -1,53 +1,16 @@
using System.Collections.Generic;
namespace CreamInstaller.Epic;
namespace CreamInstaller.Epic;
public class Manifest
{
#pragma warning disable IDE1006 // Naming Styles
public int FormatVersion { get; set; }
public bool bIsIncompleteInstall { get; set; }
public string LaunchCommand { get; set; }
public string LaunchExecutable { get; set; }
public string ManifestLocation { get; set; }
public bool bIsApplication { get; set; }
public bool bIsExecutable { get; set; }
public bool bIsManaged { get; set; }
public bool bNeedsValidation { get; set; }
public bool bRequiresAuth { get; set; }
public bool bAllowMultipleInstances { get; set; }
public bool bCanRunOffline { get; set; }
public bool bAllowUriCmdArgs { get; set; }
public List<string> BaseURLs { get; set; }
public string BuildLabel { get; set; }
public List<string> AppCategories { get; set; }
public List<object> ChunkDbs { get; set; }
public List<object> CompatibleApps { get; set; }
public string DisplayName { get; set; }
public string InstallationGuid { get; set; }
public string InstallLocation { get; set; }
public string InstallSessionId { get; set; }
public List<object> InstallTags { get; set; }
public List<object> InstallComponents { get; set; }
public string HostInstallationGuid { get; set; }
public List<string> PrereqIds { get; set; }
public string StagingLocation { get; set; }
public string TechnicalType { get; set; }
public string VaultThumbnailUrl { get; set; }
public string VaultTitleText { get; set; }
public long InstallSize { get; set; }
public string MainWindowProcessName { get; set; }
public List<object> ProcessNames { get; set; }
public List<object> BackgroundProcessNames { get; set; }
public string MandatoryAppFolderName { get; set; }
public string OwnershipToken { get; set; }
public string CatalogNamespace { get; set; }
public string CatalogItemId { get; set; }
public string AppName { get; set; }
public string AppVersionString { get; set; }
public string MainGameCatalogNamespace { get; set; }
public string MainGameCatalogItemId { get; set; }
public string MainGameAppName { get; set; }
public List<object> AllowedUriEnvVars { get; set; }
#pragma warning restore IDE1006 // Naming Styles
}

View file

@ -0,0 +1,18 @@
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("CodeQuality", "IDE0076:Invalid global 'SuppressMessageAttribute'")]
[assembly: SuppressMessage("CodeQuality", "IDE0079:Remove unnecessary suppression")]
[assembly: SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters")]
[assembly: SuppressMessage("Globalization", "CA1305:Specify IFormatProvider")]
[assembly: SuppressMessage("Globalization", "CA1307:Specify StringComparison for clarity")]
[assembly: SuppressMessage("Globalization", "CA1310:Specify StringComparison for correctness")]
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types")]
[assembly: SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task")]

View file

@ -15,8 +15,8 @@ namespace CreamInstaller;
internal partial class InstallForm : CustomForm
{
internal bool Reselecting = false;
internal bool Uninstalling = false;
internal bool Reselecting;
internal bool Uninstalling;
internal InstallForm(IWin32Window owner, bool uninstall = false) : base(owner)
{

View file

@ -37,12 +37,13 @@ internal partial class MainForm : CustomForm
cancellationTokenSource = null;
}
Hide();
new SelectForm(this).ShowDialog();
using SelectForm form = new(this);
form.ShowDialog();
Close();
}
private static UpdateManager updateManager = null;
private static Version latestVersion = null;
private static UpdateManager updateManager;
private static Version latestVersion;
private static IReadOnlyList<Version> versions;
private async void OnLoad()
@ -127,7 +128,9 @@ internal partial class MainForm : CustomForm
{
string FileName = Path.GetFileName(Program.CurrentProcessFilePath);
if (FileName != "CreamInstaller.exe")
if (new DialogForm(this).Show(SystemIcons.Warning,
{
using DialogForm form = new(this);
if (form.Show(SystemIcons.Warning,
"WARNING: CreamInstaller.exe was renamed!" +
"\n\nThis will cause unwanted behavior when updating the program!",
"Ignore", "Abort") == DialogResult.Cancel)
@ -135,6 +138,7 @@ internal partial class MainForm : CustomForm
Application.Exit();
return;
}
}
OnLoad();
}
catch (Exception e)

View file

@ -8,7 +8,7 @@ namespace CreamInstaller.Paradox;
internal static class ParadoxLauncher
{
private static string installPath = null;
private static string installPath;
internal static string InstallPath
{
get
@ -47,7 +47,8 @@ internal static class ParadoxLauncher
PopulateDlc(paradoxLauncher);
if (!paradoxLauncher.ExtraDlc.Any())
{
return new DialogForm(form).Show(SystemIcons.Warning,
using DialogForm dialogForm = new(form);
return dialogForm.Show(SystemIcons.Warning,
$"WARNING: There are no installed games with DLC that can be added to the Paradox Launcher!" +
"\n\nInstalling CreamAPI/ScreamAPI for the Paradox Launcher is pointless, since no DLC will be added to the configuration!",
"Ignore", "Cancel") != DialogResult.OK;

View file

@ -48,7 +48,8 @@ internal static class Program
{
if (selection.AreDllsLocked)
{
if (new DialogForm(form).Show(SystemIcons.Error,
using DialogForm dialogForm = new(form);
if (dialogForm.Show(SystemIcons.Error,
$"ERROR: {selection.Name} is currently running!" +
"\n\nPlease close the program/game to continue . . . ",
"Retry", "Cancel") == DialogResult.OK)
@ -79,7 +80,7 @@ internal static class Program
[STAThread]
private static void Main()
{
Mutex mutex = new(true, "CreamInstaller", out bool createdNew);
using Mutex mutex = new(true, "CreamInstaller", out bool createdNew);
if (createdNew)
{
Application.SetHighDpiMode(HighDpiMode.SystemAware);
@ -90,7 +91,8 @@ internal static class Program
try
{
HttpClientManager.Setup();
Application.Run(new MainForm());
using MainForm form = new();
Application.Run(form);
}
catch (Exception e)
{
@ -104,7 +106,7 @@ internal static class Program
internal static void Invoke(this Control control, MethodInvoker methodInvoker) => control.Invoke(methodInvoker);
internal static bool Canceled = false;
internal static bool Canceled;
internal static async void Cleanup(bool cancel = true)
{
Canceled = cancel;

View file

@ -16,22 +16,22 @@ internal enum DlcType
internal class ProgramSelection
{
internal bool Enabled = false;
internal bool Enabled;
internal bool Usable = true;
internal string Id = "0";
internal string Name = "Program";
internal string ProductUrl = null;
internal string IconUrl = null;
internal string SubIconUrl = null;
internal string ProductUrl;
internal string IconUrl;
internal string SubIconUrl;
internal string Publisher = null;
internal string Publisher;
internal string RootDirectory = null;
internal List<string> DllDirectories = null;
internal string RootDirectory;
internal List<string> DllDirectories;
internal bool IsSteam = false;
internal bool IsSteam;
internal readonly SortedList<string, (DlcType type, string name, string icon)> AllDlc = new(AppIdComparer.Comparer);
internal readonly SortedList<string, (DlcType type, string name, string icon)> SelectedDlc = new(AppIdComparer.Comparer);

View file

@ -0,0 +1,7 @@
using System;
using System.Resources;
using System.Runtime.InteropServices;
[assembly: ComVisible(false)]
[assembly: CLSCompliant(true)]
[assembly: NeutralResourcesLanguage("en")]

View file

@ -2,6 +2,7 @@
"profiles": {
"CreamInstaller": {
"commandName": "Project",
"hotReloadEnabled": true,
"nativeDebugging": false
}
}

View file

@ -573,10 +573,10 @@ internal partial class SelectForm : CustomForm
contextMenuStrip.Items.Add(new ContextMenuItem("Open AppInfo", "Notepad",
new EventHandler((sender, e) =>
{
if (File.Exists(appInfoVDF))
Diagnostics.OpenFileInNotepad(appInfoVDF);
else if (File.Exists(appInfoJSON))
if (File.Exists(appInfoJSON))
Diagnostics.OpenFileInNotepad(appInfoJSON);
else if (File.Exists(appInfoVDF))
Diagnostics.OpenFileInNotepad(appInfoVDF);
})));
contextMenuStrip.Items.Add(new ContextMenuItem("Refresh AppInfo", "Command Prompt",
new EventHandler((sender, e) =>
@ -728,7 +728,7 @@ internal partial class SelectForm : CustomForm
if (!Program.IsProgramRunningDialog(this, selection)) return;
if (ParadoxLauncher.DlcDialog(this)) return;
Hide();
InstallForm installForm = new(this, uninstall);
using InstallForm installForm = new(this, uninstall);
installForm.ShowDialog();
if (installForm.Reselecting)
{
@ -788,7 +788,8 @@ internal partial class SelectForm : CustomForm
string blockedDirectoryExceptions = "";
foreach (string name in Program.ProtectedGameDirectoryExceptions)
blockedDirectoryExceptions += helpButtonListPrefix + name;
new DialogForm(this).Show(SystemIcons.Information,
using DialogForm form = new(this);
form.Show(SystemIcons.Information,
"Blocks the program from caching and displaying games protected by DLL checks," +
"\nanti-cheats, or that are confirmed not to be working with CreamAPI or ScreamAPI." +
"\n\nBlocked game names:" + blockedGames +

View file

@ -1,147 +1,54 @@
using System.Collections.Generic;
#pragma warning disable IDE1006 // Naming Styles
#pragma warning disable CA1002 // Do not expose generic lists
#pragma warning disable CA1707 // Identifiers should not contain underscores
#pragma warning disable CA2227 // Collection properties should be read only
using System.Collections.Generic;
namespace CreamInstaller.Steam;
#pragma warning disable IDE1006 // Naming Styles
public class PriceOverview
{
public string currency { get; set; }
public int initial { get; set; }
public int final { get; set; }
public int discount_percent { get; set; }
public string initial_formatted { get; set; }
public string final_formatted { get; set; }
}
public class Sub
{
public int packageid { get; set; }
public string percent_savings_text { get; set; }
public int percent_savings { get; set; }
public string option_text { get; set; }
public string option_description { get; set; }
public string can_get_free_license { get; set; }
public bool is_free_license { get; set; }
public int price_in_cents_with_discount { get; set; }
}
public class PackageGroup
{
public string name { get; set; }
public string title { get; set; }
public string description { get; set; }
public string selection_text { get; set; }
public string save_text { get; set; }
public object display_type { get; set; }
public string is_recurring_subscription { get; set; }
public List<Sub> subs { get; set; }
}
public class Platforms
{
public bool windows { get; set; }
public bool mac { get; set; }
public bool linux { get; set; }
}
public class Metacritic
{
public int score { get; set; }
public string url { get; set; }
}
public class Category
{
public int id { get; set; }
public string description { get; set; }
}
public class Genre
{
public string id { get; set; }
public string description { get; set; }
}
public class Screenshot
{
public int id { get; set; }
public string path_thumbnail { get; set; }
public string path_full { get; set; }
}
public class Recommendations
{
public int total { get; set; }
}
public class Highlighted
{
public string name { get; set; }
public string path { get; set; }
}
public class Achievements
{
public int total { get; set; }
public List<Highlighted> highlighted { get; set; }
}
public class ReleaseDate
{
public bool coming_soon { get; set; }
public string date { get; set; }
}
public class SupportInfo
{
public string url { get; set; }
public string email { get; set; }
}
public class ContentDescriptors
{
public List<object> ids { get; set; }
public object notes { get; set; }
}
public class AppData
{
public string type { get; set; }
public string name { get; set; }
public int steam_appid { get; set; }
public int required_age { get; set; }
public bool is_free { get; set; }
public List<int> dlc { get; set; }
public string detailed_description { get; set; }
public string about_the_game { get; set; }
public string short_description { get; set; }
public string supported_languages { get; set; }
public string reviews { get; set; }
public string header_image { get; set; }
public string website { get; set; }
public string legal_notice { get; set; }
public List<string> developers { get; set; }
public List<string> publishers { get; set; }
public PriceOverview price_overview { get; set; }
public List<int> packages { get; set; }
public List<PackageGroup> package_groups { get; set; }
public Platforms platforms { get; set; }
public Metacritic metacritic { get; set; }
public List<Category> categories { get; set; }
public List<Genre> genres { get; set; }
public List<Screenshot> screenshots { get; set; }
public Recommendations recommendations { get; set; }
public Achievements achievements { get; set; }
public ReleaseDate release_date { get; set; }
public SupportInfo support_info { get; set; }
public string background { get; set; }
public string background_raw { get; set; }
public ContentDescriptors content_descriptors { get; set; }
}
public class AppDetails
{
public bool success { get; set; }
public AppData data { get; set; }
}
#pragma warning restore IDE1006 // Naming Styles
}

View file

@ -19,7 +19,7 @@ namespace CreamInstaller.Steam;
internal static class SteamCMD
{
internal static readonly int ProcessLimit = 20;
internal const int ProcessLimit = 20;
internal static string DirectoryPath => ProgramData.DirectoryPath;
internal static string AppInfoPath => ProgramData.AppInfoPath;
@ -123,7 +123,7 @@ internal static class SteamCMD
{
using (HttpClient httpClient = new())
{
byte[] file = await httpClient.GetByteArrayAsync("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.Write(ArchivePath);
}
ZipFile.ExtractToDirectory(ArchivePath, DirectoryPath);

View file

@ -13,7 +13,7 @@ namespace CreamInstaller.Steam;
internal static class SteamLibrary
{
private static string installPath = null;
private static string installPath;
internal static string InstallPath
{
get

View file

@ -44,7 +44,8 @@ internal static class SteamStore
}
catch (Exception e)
{
new DialogForm(null).Show(SystemIcons.Error, "Unsuccessful deserialization of query for appid " + appId + ":\n\n" + e.ToString(), "FUCK");
using DialogForm dialogForm = new(null);
dialogForm.Show(SystemIcons.Error, "Unsuccessful deserialization of query for appid " + appId + ":\n\n" + e.ToString(), "FUCK");
}
}
}

View file

@ -40,12 +40,16 @@ internal static class ExceptionHandler
}
}
internal class CustomMessageException : Exception
public class CustomMessageException : Exception
{
private readonly string message;
public override string Message => message ?? "CustomMessageException";
public override string Message => message;
public override string ToString() => Message;
internal CustomMessageException(string message) => this.message = message;
public CustomMessageException() => message = "CustomMessageException";
public CustomMessageException(string message) => this.message = message;
public CustomMessageException(string message, Exception _) => this.message = message;
}

View file

@ -47,7 +47,7 @@ internal static class HttpClientManager
{
try
{
return new Bitmap(await HttpClient.GetStreamAsync(url));
return new Bitmap(await HttpClient.GetStreamAsync(new Uri(url)));
}
catch
{

View file

@ -8,11 +8,11 @@ internal static class IconGrabber
{
internal static Icon ToIcon(this Image image)
{
Bitmap dialogIconBitmap = new(image, new Size(image.Width, image.Height));
using Bitmap dialogIconBitmap = new(image, new Size(image.Width, image.Height));
return Icon.FromHandle(dialogIconBitmap.GetHicon());
}
internal static readonly string SteamAppImagesPath = "https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/apps/";
internal const string SteamAppImagesPath = "https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/apps/";
internal static Image GetFileIconImage(string path) => File.Exists(path) ? Icon.ExtractAssociatedIcon(path).ToBitmap() : null;