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>
<PackageId>pointfeev.creaminstaller</PackageId>
<StartupObject>CreamInstaller.Program</StartupObject>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
@ -38,11 +39,11 @@
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="steam_api64.dll" />
<EmbeddedResource Include="Resources\steam_api64.dll" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="steam_api.dll" />
<EmbeddedResource Include="Resources\steam_api.dll" />
</ItemGroup>
<ItemGroup>
@ -51,8 +52,8 @@
</ItemGroup>
<ItemGroup>
<Compile Update="MainForm.cs" />
<Compile Update="SelectForm.cs" />
<Compile Update="Forms\MainForm.cs" />
<Compile Update="Forms\SelectForm.cs" />
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<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 Microsoft.Win32;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
@ -75,7 +76,7 @@ namespace CreamInstaller
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();
if (Program.Canceled) return false;
@ -91,14 +92,16 @@ namespace CreamInstaller
string installdir = property.Value.installdir.ToString();
string name = property.Value.name.ToString();
string _buildid = property.Value.buildid.ToString();
if (string.IsNullOrWhiteSpace(_appid)
if (string.IsNullOrWhiteSpace(_appid)
|| string.IsNullOrWhiteSpace(installdir)
|| string.IsNullOrWhiteSpace(name)
|| 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(_buildid, out int buildid)) continue;
games.Add(new(appid, name, buildid, gameDirectory));
games.Add(new(appid, name, branch, buildid, gameDirectory));
}
catch {}
}
@ -109,46 +112,39 @@ namespace CreamInstaller
private readonly List<TreeNode> treeNodes = new();
internal readonly Dictionary<int, Dictionary<int, string>> DLC = new();
internal List<Task> RunningTasks = null;
internal List<Task> RunningTasks = new();
private void GetCreamApiApplicablePrograms(IProgress<int> progress)
{
int cur = 0;
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";
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)
if (GetGamesFromLibraryDirectory(libraryDirectory, out List<Tuple<int, string, int, string>> games))
foreach (Tuple<int, string, int, string> game in games)
if (GetGamesFromLibraryDirectory(libraryDirectory, out List<Tuple<int, string, string, int, string>> games))
foreach (Tuple<int, string, string, int, string> game in games)
applicablePrograms.Add(game);
RunningTasks = new();
foreach (Tuple<int, string, int, string> program in applicablePrograms)
RunningTasks.Clear();
foreach (Tuple<int, string, string, int, string> program in applicablePrograms)
{
int appId = program.Item1;
string name = program.Item2;
int buildId = program.Item3;
string directory = program.Item4;
string branch = program.Item3;
int buildId = program.Item4;
string directory = program.Item5;
if (Program.Canceled) return;
// easy anti cheat detects DLL changes, so skip those games
if (Directory.Exists(directory + @"\EasyAntiCheat")) continue;
// battleye in DayZ detects DLL changes, but not in Arma3?
//if (Directory.Exists(directory + @"\BattlEye")) continue;
if (name == "DayZ") continue;
if (name != "Arma 3" && Directory.Exists(directory + @"\BattlEye")) continue;
Task task = new(() =>
{
if (Program.Canceled || !GetDllDirectoriesFromGameDirectory(directory, out List<string> dllDirectories)) return;
VProperty appInfo = null;
if (Program.Canceled || (name != "Paradox Launcher" && !SteamCMD.GetAppInfo(appId, buildId, out appInfo))) return;
Dictionary<int, string> dlc = null;
if (!DLC.TryGetValue(appId, out dlc))
{
dlc = new();
DLC.Add(appId, dlc);
}
if (Program.Canceled || (name != "Paradox Launcher" && !SteamCMD.GetAppInfo(appId, out appInfo, branch, buildId))) return;
if (Program.Canceled) return;
ConcurrentDictionary<int, string> dlc = new();
List<Task> dlcTasks = new();
List<int> dlcIds = new();
if (!(appInfo is null))
@ -175,9 +171,9 @@ namespace CreamInstaller
if (Program.Canceled) return;
string dlcName = 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 (string.IsNullOrWhiteSpace(dlcName)) dlcName = $"Unnamed DLC ({id})";
if (string.IsNullOrWhiteSpace(dlcName)) dlcName = $"Unknown DLC ({id})";
dlc[id] = dlcName;
});
dlcTasks.Add(task);

View file

@ -29,7 +29,15 @@ namespace CreamInstaller
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
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();
}

View file

@ -7,7 +7,7 @@ namespace CreamInstaller
{
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);
resource.CopyTo(file);
}

View file

@ -1,5 +1,6 @@
using Gameloop.Vdf;
using Gameloop.Vdf.Linq;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
@ -15,7 +16,9 @@ namespace CreamInstaller
public static string FilePath = DirectoryPath + @"\steamcmd.exe";
public static string ArchivePath = DirectoryPath + @"\steamcmd.zip";
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)
{
@ -52,16 +55,18 @@ namespace CreamInstaller
ZipFile.ExtractToDirectory(ArchivePath, DirectoryPath);
File.Delete(ArchivePath);
}
if (File.Exists(AppCacheAppInfoPath)) File.Delete(AppCacheAppInfoPath);
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;
if (Program.Canceled) return false;
string output;
string appUpdatePath = $@"{AppInfoCachePath}\{appId}";
string appUpdatePath = $@"{AppInfoPath}\{appId}";
string appUpdateFile = $@"{appUpdatePath}\appinfo.txt";
restart:
if (Directory.Exists(appUpdatePath) && File.Exists(appUpdateFile)) output = File.ReadAllText(appUpdateFile);
else
{
@ -76,18 +81,26 @@ namespace CreamInstaller
}
}
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;
VToken type = appInfo.Value is VValue ? null : appInfo.Value?["common"]?["type"];
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 (type is null || int.Parse(buildid) < buildId)
{
File.Delete(appUpdateFile);
bool success = GetAppInfo(appId, buildId, out appInfo);
return success;
if (File.Exists(appUpdateFile)) File.Delete(appUpdateFile);
goto restart;
}
}
return true;