fix concurrency error, add better exception handling, create folders

This commit is contained in:
pointfeev 2021-11-11 02:29:59 -05:00
parent d84809ec2c
commit cdb9d6a658
No known key found for this signature in database
GPG key ID: AA14DC36C4D7D13C
20 changed files with 113 additions and 57 deletions

View file

@ -24,6 +24,7 @@
<Authors>pointfeev</Authors> <Authors>pointfeev</Authors>
<PackageId>pointfeev.creaminstaller</PackageId> <PackageId>pointfeev.creaminstaller</PackageId>
<StartupObject>CreamInstaller.Program</StartupObject> <StartupObject>CreamInstaller.Program</StartupObject>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
@ -38,11 +39,11 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="steam_api64.dll" /> <EmbeddedResource Include="Resources\steam_api64.dll" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="steam_api.dll" /> <EmbeddedResource Include="Resources\steam_api.dll" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -51,8 +52,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Update="MainForm.cs" /> <Compile Update="Forms\MainForm.cs" />
<Compile Update="SelectForm.cs" /> <Compile Update="Forms\SelectForm.cs" />
<Compile Update="Properties\Resources.Designer.cs"> <Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime> <DesignTime>True</DesignTime>
<AutoGen>True</AutoGen> <AutoGen>True</AutoGen>

View file

@ -1,17 +0,0 @@
using System;
namespace CreamInstaller
{
public class CustomMessageException : Exception
{
private string message;
public override string Message => message ?? "CustomMessageException";
public override string ToString() => Message;
public CustomMessageException(string message)
{
this.message = message;
}
}
}

View file

@ -0,0 +1,55 @@
using System;
using System.Windows.Forms;
namespace CreamInstaller
{
public static class ExceptionHandler
{
public static bool OutputException(Exception e)
{
while (!(e.InnerException is null)) e = e.InnerException;
string output = "";
string[] stackTrace = e.StackTrace?.Split('\n');
if (!(stackTrace is null) && stackTrace.Length > 0)
{
output += "STACK TRACE\n";
for (int i = 0; i < Math.Min(stackTrace.Length, 3); i++)
{
string line = stackTrace[i];
if (!(line is null))
{
output += "\n " + line.Substring(line.IndexOf("at"));
}
}
}
string[] messageLines = e.Message?.Split('\n');
if (!(messageLines is null) && messageLines.Length > 0)
{
if (output.Length > 0) output += "\n\n";
output += "MESSAGE\n";
for (int i = 0; i < messageLines.Length; i++)
{
string line = messageLines[i];
if (!(line is null))
{
output += "\n " + messageLines[i];
}
}
}
return MessageBox.Show(output, caption: "CreamInstaller encountered an exception", buttons: MessageBoxButtons.RetryCancel, icon: MessageBoxIcon.Error) == DialogResult.Retry;
}
}
public class CustomMessageException : Exception
{
private string message;
public override string Message => message ?? "CustomMessageException";
public override string ToString() => Message;
public CustomMessageException(string message)
{
this.message = message;
}
}
}

View file

@ -2,6 +2,7 @@
using Gameloop.Vdf.Linq; using Gameloop.Vdf.Linq;
using Microsoft.Win32; using Microsoft.Win32;
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Drawing; using System.Drawing;
@ -75,7 +76,7 @@ namespace CreamInstaller
return true; return true;
} }
private bool GetGamesFromLibraryDirectory(string libraryDirectory, out List<Tuple<int, string, int, string>> games) private bool GetGamesFromLibraryDirectory(string libraryDirectory, out List<Tuple<int, string, string, int, string>> games)
{ {
games = new(); games = new();
if (Program.Canceled) return false; if (Program.Canceled) return false;
@ -91,14 +92,16 @@ namespace CreamInstaller
string installdir = property.Value.installdir.ToString(); string installdir = property.Value.installdir.ToString();
string name = property.Value.name.ToString(); string name = property.Value.name.ToString();
string _buildid = property.Value.buildid.ToString(); string _buildid = property.Value.buildid.ToString();
if (string.IsNullOrWhiteSpace(_appid) if (string.IsNullOrWhiteSpace(_appid)
|| string.IsNullOrWhiteSpace(installdir) || string.IsNullOrWhiteSpace(installdir)
|| string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(name)
|| string.IsNullOrWhiteSpace(_buildid)) continue; || string.IsNullOrWhiteSpace(_buildid)) continue;
string gameDirectory = libraryDirectory + @"\common\" + installdir; string branch = property.Value.UserConfig?.betakey?.ToString();
if (string.IsNullOrWhiteSpace(branch)) branch = "public";
string gameDirectory = libraryDirectory + @"\common\" + installdir;
if (!int.TryParse(_appid, out int appid)) continue; if (!int.TryParse(_appid, out int appid)) continue;
if (!int.TryParse(_buildid, out int buildid)) continue; if (!int.TryParse(_buildid, out int buildid)) continue;
games.Add(new(appid, name, buildid, gameDirectory)); games.Add(new(appid, name, branch, buildid, gameDirectory));
} }
catch {} catch {}
} }
@ -109,46 +112,39 @@ namespace CreamInstaller
private readonly List<TreeNode> treeNodes = new(); private readonly List<TreeNode> treeNodes = new();
internal readonly Dictionary<int, Dictionary<int, string>> DLC = new(); internal List<Task> RunningTasks = new();
internal List<Task> RunningTasks = null;
private void GetCreamApiApplicablePrograms(IProgress<int> progress) private void GetCreamApiApplicablePrograms(IProgress<int> progress)
{ {
int cur = 0; int cur = 0;
if (Program.Canceled) return; if (Program.Canceled) return;
List<Tuple<int, string, int, string>> applicablePrograms = new(); List<Tuple<int, string, string, int, string>> applicablePrograms = new();
string launcherRootDirectory = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\Programs\\Paradox Interactive"; string launcherRootDirectory = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\Programs\\Paradox Interactive";
if (Directory.Exists(launcherRootDirectory)) applicablePrograms.Add(new(0, "Paradox Launcher", 0, launcherRootDirectory)); if (Directory.Exists(launcherRootDirectory)) applicablePrograms.Add(new(0, "Paradox Launcher", "", 0, launcherRootDirectory));
foreach (string libraryDirectory in GameLibraryDirectories) foreach (string libraryDirectory in GameLibraryDirectories)
if (GetGamesFromLibraryDirectory(libraryDirectory, out List<Tuple<int, string, int, string>> games)) if (GetGamesFromLibraryDirectory(libraryDirectory, out List<Tuple<int, string, string, int, string>> games))
foreach (Tuple<int, string, int, string> game in games) foreach (Tuple<int, string, string, int, string> game in games)
applicablePrograms.Add(game); applicablePrograms.Add(game);
RunningTasks = new(); RunningTasks.Clear();
foreach (Tuple<int, string, int, string> program in applicablePrograms) foreach (Tuple<int, string, string, int, string> program in applicablePrograms)
{ {
int appId = program.Item1; int appId = program.Item1;
string name = program.Item2; string name = program.Item2;
int buildId = program.Item3; string branch = program.Item3;
string directory = program.Item4; int buildId = program.Item4;
string directory = program.Item5;
if (Program.Canceled) return; if (Program.Canceled) return;
// easy anti cheat detects DLL changes, so skip those games // easy anti cheat detects DLL changes, so skip those games
if (Directory.Exists(directory + @"\EasyAntiCheat")) continue; if (Directory.Exists(directory + @"\EasyAntiCheat")) continue;
// battleye in DayZ detects DLL changes, but not in Arma3? // battleye in DayZ detects DLL changes, but not in Arma3?
//if (Directory.Exists(directory + @"\BattlEye")) continue; if (name != "Arma 3" && Directory.Exists(directory + @"\BattlEye")) continue;
if (name == "DayZ") continue;
Task task = new(() => Task task = new(() =>
{ {
if (Program.Canceled || !GetDllDirectoriesFromGameDirectory(directory, out List<string> dllDirectories)) return; if (Program.Canceled || !GetDllDirectoriesFromGameDirectory(directory, out List<string> dllDirectories)) return;
VProperty appInfo = null; VProperty appInfo = null;
if (Program.Canceled || (name != "Paradox Launcher" && !SteamCMD.GetAppInfo(appId, buildId, out appInfo))) return; if (Program.Canceled || (name != "Paradox Launcher" && !SteamCMD.GetAppInfo(appId, out appInfo, branch, buildId))) return;
Dictionary<int, string> dlc = null;
if (!DLC.TryGetValue(appId, out dlc))
{
dlc = new();
DLC.Add(appId, dlc);
}
if (Program.Canceled) return; if (Program.Canceled) return;
ConcurrentDictionary<int, string> dlc = new();
List<Task> dlcTasks = new(); List<Task> dlcTasks = new();
List<int> dlcIds = new(); List<int> dlcIds = new();
if (!(appInfo is null)) if (!(appInfo is null))
@ -175,9 +171,9 @@ namespace CreamInstaller
if (Program.Canceled) return; if (Program.Canceled) return;
string dlcName = null; string dlcName = null;
VProperty dlcAppInfo = null; VProperty dlcAppInfo = null;
if (SteamCMD.GetAppInfo(id, 0, out dlcAppInfo)) dlcName = dlcAppInfo?.Value?["common"]?["name"]?.ToString(); if (SteamCMD.GetAppInfo(id, out dlcAppInfo)) dlcName = dlcAppInfo?.Value?["common"]?["name"]?.ToString();
if (Program.Canceled) return; if (Program.Canceled) return;
if (string.IsNullOrWhiteSpace(dlcName)) dlcName = $"Unnamed DLC ({id})"; if (string.IsNullOrWhiteSpace(dlcName)) dlcName = $"Unknown DLC ({id})";
dlc[id] = dlcName; dlc[id] = dlcName;
}); });
dlcTasks.Add(task); dlcTasks.Add(task);

View file

@ -29,7 +29,15 @@ namespace CreamInstaller
Application.EnableVisualStyles(); Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false); Application.SetCompatibleTextRenderingDefault(false);
Application.ApplicationExit += new EventHandler(OnApplicationExit); Application.ApplicationExit += new EventHandler(OnApplicationExit);
Application.Run(new MainForm()); retry:
try
{
Application.Run(new MainForm());
}
catch (Exception e)
{
if (ExceptionHandler.OutputException(e)) goto retry;
}
} }
mutex.Close(); mutex.Close();
} }

View file

@ -7,7 +7,7 @@ namespace CreamInstaller
{ {
public static void WriteResourceToFile(string resourceName, string filePath) public static void WriteResourceToFile(string resourceName, string filePath)
{ {
using Stream resource = Assembly.GetExecutingAssembly().GetManifestResourceStream("CreamInstaller." + resourceName); using Stream resource = Assembly.GetExecutingAssembly().GetManifestResourceStream(@"CreamInstaller.Resources." + resourceName);
using FileStream file = new(filePath, FileMode.Create, FileAccess.Write); using FileStream file = new(filePath, FileMode.Create, FileAccess.Write);
resource.CopyTo(file); resource.CopyTo(file);
} }

View file

@ -1,5 +1,6 @@
using Gameloop.Vdf; using Gameloop.Vdf;
using Gameloop.Vdf.Linq; using Gameloop.Vdf.Linq;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
@ -15,7 +16,9 @@ namespace CreamInstaller
public static string FilePath = DirectoryPath + @"\steamcmd.exe"; public static string FilePath = DirectoryPath + @"\steamcmd.exe";
public static string ArchivePath = DirectoryPath + @"\steamcmd.zip"; public static string ArchivePath = DirectoryPath + @"\steamcmd.zip";
public static string DllPath = DirectoryPath + @"\steamclient.dll"; public static string DllPath = DirectoryPath + @"\steamclient.dll";
public static string AppInfoCachePath = DirectoryPath + @"\appinfocache"; public static string AppCachePath = DirectoryPath + @"\appcache";
public static string AppCacheAppInfoPath = AppCachePath + @"\appinfo.vdf";
public static string AppInfoPath = DirectoryPath + @"\appinfo";
public static bool Run(string command, out string output) public static bool Run(string command, out string output)
{ {
@ -52,16 +55,18 @@ namespace CreamInstaller
ZipFile.ExtractToDirectory(ArchivePath, DirectoryPath); ZipFile.ExtractToDirectory(ArchivePath, DirectoryPath);
File.Delete(ArchivePath); File.Delete(ArchivePath);
} }
if (File.Exists(AppCacheAppInfoPath)) File.Delete(AppCacheAppInfoPath);
if (!File.Exists(DllPath)) Run($@"+quit", out _); if (!File.Exists(DllPath)) Run($@"+quit", out _);
} }
public static bool GetAppInfo(int appId, int buildId, out VProperty appInfo) public static bool GetAppInfo(int appId, out VProperty appInfo, string branch = "public", int buildId = 0)
{ {
appInfo = null; appInfo = null;
if (Program.Canceled) return false; if (Program.Canceled) return false;
string output; string output;
string appUpdatePath = $@"{AppInfoCachePath}\{appId}"; string appUpdatePath = $@"{AppInfoPath}\{appId}";
string appUpdateFile = $@"{appUpdatePath}\appinfo.txt"; string appUpdateFile = $@"{appUpdatePath}\appinfo.txt";
restart:
if (Directory.Exists(appUpdatePath) && File.Exists(appUpdateFile)) output = File.ReadAllText(appUpdateFile); if (Directory.Exists(appUpdatePath) && File.Exists(appUpdateFile)) output = File.ReadAllText(appUpdateFile);
else else
{ {
@ -76,18 +81,26 @@ namespace CreamInstaller
} }
} }
if (Program.Canceled || output is null) return false; if (Program.Canceled || output is null) return false;
try { appInfo = VdfConvert.Deserialize(output); } catch { } try { appInfo = VdfConvert.Deserialize(output); }
catch
{
if (File.Exists(appUpdateFile))
{
File.Delete(appUpdateFile);
goto restart;
}
}
if (!(appInfo is null) && appInfo.Value is VValue) goto restart;
if (appInfo is null || (!(appInfo.Value is VValue) && appInfo.Value.Children().ToList().Count == 0)) return true; if (appInfo is null || (!(appInfo.Value is VValue) && appInfo.Value.Children().ToList().Count == 0)) return true;
VToken type = appInfo.Value is VValue ? null : appInfo.Value?["common"]?["type"]; VToken type = appInfo.Value is VValue ? null : appInfo.Value?["common"]?["type"];
if (type is null || type.ToString() == "Game") if (type is null || type.ToString() == "Game")
{ {
string buildid = appInfo.Value is VValue ? null : appInfo.Value["depots"]?["public"]?["buildid"]?.ToString(); string buildid = appInfo.Value is VValue ? null : appInfo.Value["depots"]?["branches"]?[branch]?["buildid"]?.ToString();
if (buildid is null && !(type is null)) return true; if (buildid is null && !(type is null)) return true;
if (type is null || int.Parse(buildid) < buildId) if (type is null || int.Parse(buildid) < buildId)
{ {
File.Delete(appUpdateFile); if (File.Exists(appUpdateFile)) File.Delete(appUpdateFile);
bool success = GetAppInfo(appId, buildId, out appInfo); goto restart;
return success;
} }
} }
return true; return true;