2022-01-22 11:47:09 +05:00
|
|
|
|
using System;
|
2021-10-29 13:20:27 +05:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.IO.Compression;
|
2021-11-06 14:25:25 +05:00
|
|
|
|
using System.Linq;
|
2022-01-18 02:47:56 +05:00
|
|
|
|
using System.Net.Http;
|
2021-11-29 03:14:32 +05:00
|
|
|
|
using System.Text;
|
2022-02-12 04:25:18 +05:00
|
|
|
|
using System.Threading;
|
2022-01-22 02:10:15 +05:00
|
|
|
|
using System.Threading.Tasks;
|
2021-11-12 23:12:21 +05:00
|
|
|
|
using System.Windows.Forms;
|
2021-10-29 13:20:27 +05:00
|
|
|
|
|
2022-02-07 06:17:51 +05:00
|
|
|
|
using CreamInstaller.Resources;
|
|
|
|
|
|
2022-01-22 11:47:09 +05:00
|
|
|
|
using Gameloop.Vdf.Linq;
|
|
|
|
|
|
2022-03-03 16:38:17 +05:00
|
|
|
|
namespace CreamInstaller.Steam;
|
2022-01-25 00:10:29 +05:00
|
|
|
|
|
|
|
|
|
internal static class SteamCMD
|
2021-10-29 13:20:27 +05:00
|
|
|
|
{
|
2022-02-12 04:25:18 +05:00
|
|
|
|
internal static readonly int ProcessLimit = 20;
|
2022-02-14 23:53:09 +05:00
|
|
|
|
internal static readonly Version MinimumAppInfoVersion = Version.Parse("2.4.0.0");
|
2022-02-12 04:25:18 +05:00
|
|
|
|
|
2022-02-12 13:22:30 +05:00
|
|
|
|
internal static readonly string DirectoryPathOld = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\CreamInstaller";
|
|
|
|
|
internal static readonly string DirectoryPath = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + @"\CreamInstaller";
|
2022-01-25 00:10:29 +05:00
|
|
|
|
internal static readonly string FilePath = DirectoryPath + @"\steamcmd.exe";
|
2022-01-22 02:10:15 +05:00
|
|
|
|
|
2022-02-12 04:25:18 +05:00
|
|
|
|
private static readonly int[] locks = new int[ProcessLimit];
|
2022-01-25 00:10:29 +05:00
|
|
|
|
internal static async Task<string> Run(string command) => await Task.Run(() =>
|
|
|
|
|
{
|
2022-02-12 04:25:18 +05:00
|
|
|
|
wait_for_lock:
|
|
|
|
|
if (Program.Canceled) return "";
|
|
|
|
|
for (int i = 0; i < locks.Length; i++)
|
|
|
|
|
{
|
2022-02-11 05:13:51 +05:00
|
|
|
|
if (Program.Canceled) return "";
|
2022-02-12 04:25:18 +05:00
|
|
|
|
if (Interlocked.CompareExchange(ref locks[i], 1, 0) == 0)
|
2022-02-11 05:13:51 +05:00
|
|
|
|
{
|
|
|
|
|
if (Program.Canceled) return "";
|
2022-02-12 04:25:18 +05:00
|
|
|
|
List<string> logs = new();
|
|
|
|
|
ProcessStartInfo processStartInfo = new()
|
|
|
|
|
{
|
|
|
|
|
FileName = FilePath,
|
|
|
|
|
RedirectStandardOutput = true,
|
|
|
|
|
RedirectStandardInput = true,
|
|
|
|
|
RedirectStandardError = true,
|
|
|
|
|
UseShellExecute = false,
|
|
|
|
|
Arguments = command,
|
|
|
|
|
CreateNoWindow = true,
|
|
|
|
|
StandardInputEncoding = Encoding.UTF8,
|
|
|
|
|
StandardOutputEncoding = Encoding.UTF8,
|
|
|
|
|
StandardErrorEncoding = Encoding.UTF8
|
|
|
|
|
};
|
|
|
|
|
using Process process = Process.Start(processStartInfo);
|
|
|
|
|
process.OutputDataReceived += (object sender, DataReceivedEventArgs e) => logs.Add(e.Data);
|
|
|
|
|
process.BeginOutputReadLine();
|
|
|
|
|
process.ErrorDataReceived += (object sender, DataReceivedEventArgs e) => logs.Add(e.Data);
|
|
|
|
|
process.BeginErrorReadLine();
|
|
|
|
|
process.WaitForExit();
|
|
|
|
|
Interlocked.Decrement(ref locks[i]);
|
|
|
|
|
return string.Join("\r\n", logs);
|
|
|
|
|
}
|
|
|
|
|
Thread.Sleep(0);
|
|
|
|
|
}
|
2022-02-11 06:33:28 +05:00
|
|
|
|
Thread.Sleep(200);
|
2022-02-12 04:25:18 +05:00
|
|
|
|
goto wait_for_lock;
|
2022-01-25 00:10:29 +05:00
|
|
|
|
});
|
2021-10-29 13:20:27 +05:00
|
|
|
|
|
2022-02-12 04:25:18 +05:00
|
|
|
|
internal static readonly string ArchivePath = DirectoryPath + @"\steamcmd.zip";
|
|
|
|
|
internal static readonly string DllPath = DirectoryPath + @"\steamclient.dll";
|
|
|
|
|
internal static readonly string AppInfoPath = DirectoryPath + @"\appinfo";
|
|
|
|
|
internal static readonly string AppInfoVersionPath = AppInfoPath + @"\version.txt";
|
|
|
|
|
|
2022-02-10 20:50:53 +05:00
|
|
|
|
internal static async Task Setup(IProgress<int> progress = null)
|
2022-01-25 00:10:29 +05:00
|
|
|
|
{
|
2022-02-12 04:25:18 +05:00
|
|
|
|
await Cleanup();
|
2022-02-12 13:22:30 +05:00
|
|
|
|
if (Directory.Exists(DirectoryPathOld))
|
|
|
|
|
{
|
|
|
|
|
if (Directory.Exists(DirectoryPath)) Directory.Delete(DirectoryPath, true);
|
|
|
|
|
Directory.Move(DirectoryPathOld, DirectoryPath);
|
|
|
|
|
}
|
2022-02-10 20:50:53 +05:00
|
|
|
|
if (!Directory.Exists(DirectoryPath)) Directory.CreateDirectory(DirectoryPath);
|
2022-01-25 00:10:29 +05:00
|
|
|
|
if (!File.Exists(FilePath))
|
2021-10-29 13:20:27 +05:00
|
|
|
|
{
|
2022-01-25 00:10:29 +05:00
|
|
|
|
using (HttpClient httpClient = new())
|
2021-11-12 23:12:21 +05:00
|
|
|
|
{
|
2022-01-25 00:10:29 +05:00
|
|
|
|
byte[] file = await httpClient.GetByteArrayAsync("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip");
|
|
|
|
|
file.Write(ArchivePath);
|
2021-11-12 23:12:21 +05:00
|
|
|
|
}
|
2022-01-25 00:10:29 +05:00
|
|
|
|
ZipFile.ExtractToDirectory(ArchivePath, DirectoryPath);
|
|
|
|
|
File.Delete(ArchivePath);
|
|
|
|
|
}
|
|
|
|
|
if (!File.Exists(AppInfoVersionPath) || !Version.TryParse(File.ReadAllText(AppInfoVersionPath, Encoding.UTF8), out Version version) || version < MinimumAppInfoVersion)
|
|
|
|
|
{
|
|
|
|
|
if (Directory.Exists(AppInfoPath)) Directory.Delete(AppInfoPath, true);
|
|
|
|
|
Directory.CreateDirectory(AppInfoPath);
|
|
|
|
|
File.WriteAllText(AppInfoVersionPath, Application.ProductVersion, Encoding.UTF8);
|
2021-10-29 13:20:27 +05:00
|
|
|
|
}
|
2022-02-10 20:50:53 +05:00
|
|
|
|
if (!File.Exists(DllPath))
|
|
|
|
|
{
|
|
|
|
|
FileSystemWatcher watcher = new(DirectoryPath);
|
|
|
|
|
watcher.Filter = "*";
|
|
|
|
|
watcher.IncludeSubdirectories = true;
|
|
|
|
|
watcher.EnableRaisingEvents = true;
|
|
|
|
|
if (File.Exists(DllPath)) progress.Report(-15); // update (not used at the moment)
|
|
|
|
|
else progress.Report(-1660); // install
|
|
|
|
|
int cur = 0;
|
|
|
|
|
progress.Report(cur);
|
|
|
|
|
watcher.Changed += (sender, e) => progress.Report(++cur);
|
|
|
|
|
await Run($@"+quit");
|
|
|
|
|
watcher.Dispose();
|
|
|
|
|
}
|
2022-01-25 00:10:29 +05:00
|
|
|
|
}
|
2021-10-29 13:20:27 +05:00
|
|
|
|
|
2022-02-12 04:25:18 +05:00
|
|
|
|
internal static readonly string AppCachePath = DirectoryPath + @"\appcache";
|
|
|
|
|
internal static readonly string ConfigPath = DirectoryPath + @"\config";
|
|
|
|
|
internal static readonly string DumpsPath = DirectoryPath + @"\dumps";
|
|
|
|
|
internal static readonly string LogsPath = DirectoryPath + @"\logs";
|
|
|
|
|
internal static readonly string SteamAppsPath = DirectoryPath + @"\steamapps";
|
|
|
|
|
internal static readonly string UserDataPath = DirectoryPath + @"\userdata";
|
|
|
|
|
|
|
|
|
|
internal static async Task Cleanup() => await Task.Run(async () =>
|
2022-02-11 05:13:51 +05:00
|
|
|
|
{
|
2022-03-03 16:38:17 +05:00
|
|
|
|
if (!Directory.Exists(DirectoryPath)) return;
|
2022-02-12 04:25:18 +05:00
|
|
|
|
await Kill();
|
2022-02-11 05:13:51 +05:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
string[] oldFiles = Directory.GetFiles(DirectoryPath, "*.old");
|
|
|
|
|
foreach (string file in oldFiles) File.Delete(file);
|
2022-02-12 04:25:18 +05:00
|
|
|
|
}
|
|
|
|
|
catch { }
|
|
|
|
|
try
|
|
|
|
|
{
|
2022-02-11 05:13:51 +05:00
|
|
|
|
string[] deleteFiles = Directory.GetFiles(DirectoryPath, "*.delete");
|
|
|
|
|
foreach (string file in deleteFiles) File.Delete(file);
|
|
|
|
|
}
|
|
|
|
|
catch { }
|
2022-02-12 04:25:18 +05:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
string[] crashFiles = Directory.GetFiles(DirectoryPath, "*.crash");
|
|
|
|
|
foreach (string file in crashFiles) File.Delete(file);
|
|
|
|
|
}
|
|
|
|
|
catch { }
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (Directory.Exists(AppCachePath)) Directory.Delete(AppCachePath, true);
|
|
|
|
|
}
|
|
|
|
|
catch { }
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (Directory.Exists(ConfigPath)) Directory.Delete(ConfigPath, true);
|
|
|
|
|
}
|
|
|
|
|
catch { }
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (Directory.Exists(DumpsPath)) Directory.Delete(DumpsPath, true);
|
|
|
|
|
}
|
|
|
|
|
catch { }
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (Directory.Exists(LogsPath)) Directory.Delete(LogsPath, true);
|
|
|
|
|
}
|
|
|
|
|
catch { }
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (Directory.Exists(SteamAppsPath)) Directory.Delete(SteamAppsPath, true);
|
|
|
|
|
}
|
|
|
|
|
catch { }
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (Directory.Exists(UserDataPath)) Directory.Delete(UserDataPath, true);
|
|
|
|
|
}
|
|
|
|
|
catch { }
|
2022-02-11 05:13:51 +05:00
|
|
|
|
});
|
|
|
|
|
|
2022-03-03 16:38:17 +05:00
|
|
|
|
internal static async Task<VProperty> GetAppInfo(string appId, string branch = "public", int buildId = 0)
|
2022-01-25 00:10:29 +05:00
|
|
|
|
{
|
|
|
|
|
if (Program.Canceled) return null;
|
|
|
|
|
string output;
|
2022-02-12 04:25:18 +05:00
|
|
|
|
string appUpdateFile = $@"{AppInfoPath}\{appId}.vdf";
|
2022-01-25 00:10:29 +05:00
|
|
|
|
restart:
|
|
|
|
|
if (Program.Canceled) return null;
|
2022-01-28 01:59:02 +05:00
|
|
|
|
if (File.Exists(appUpdateFile)) output = File.ReadAllText(appUpdateFile, Encoding.UTF8);
|
2022-01-25 00:10:29 +05:00
|
|
|
|
else
|
2021-10-29 13:20:27 +05:00
|
|
|
|
{
|
2022-02-11 05:13:51 +05:00
|
|
|
|
// we add app_update 4 to allow the app_info_print to finish
|
2022-02-12 04:25:18 +05:00
|
|
|
|
output = await Run($@"@ShutdownOnFailedCommand 0 +force_install_dir {DirectoryPath} +login anonymous +app_info_print {appId} +app_update 4 +quit");
|
2022-01-25 00:10:29 +05:00
|
|
|
|
int openBracket = output.IndexOf("{");
|
|
|
|
|
int closeBracket = output.LastIndexOf("}");
|
|
|
|
|
if (openBracket != -1 && closeBracket != -1)
|
2021-11-06 13:55:37 +05:00
|
|
|
|
{
|
2022-01-25 00:10:29 +05:00
|
|
|
|
output = $"\"{appId}\"\n" + output[openBracket..(1 + closeBracket)];
|
2022-02-14 23:53:09 +05:00
|
|
|
|
output = output.Replace("ERROR! Failed to install app '4' (Invalid platform)", "");
|
2022-01-25 00:10:29 +05:00
|
|
|
|
File.WriteAllText(appUpdateFile, output, Encoding.UTF8);
|
2021-11-06 13:55:37 +05:00
|
|
|
|
}
|
2022-01-25 00:10:29 +05:00
|
|
|
|
}
|
|
|
|
|
if (Program.Canceled || output is null) return null;
|
|
|
|
|
if (!ValveDataFile.TryDeserialize(output, out VProperty appInfo))
|
|
|
|
|
{
|
2022-02-12 04:25:18 +05:00
|
|
|
|
File.Delete(appUpdateFile);
|
|
|
|
|
//new DialogForm(null).Show("GetAppInfo", SystemIcons.Information, "Deserialize exception:\n\n" + output, "OK");
|
2022-02-11 05:13:51 +05:00
|
|
|
|
goto restart;
|
|
|
|
|
}
|
|
|
|
|
if (appInfo.Value is VValue)
|
2022-02-12 04:25:18 +05:00
|
|
|
|
//new DialogForm(null).Show("GetAppInfo", SystemIcons.Information, "VValue exception:\n\n" + output, "OK");
|
2022-01-28 01:59:02 +05:00
|
|
|
|
goto restart;
|
2022-01-25 00:10:29 +05:00
|
|
|
|
if (appInfo is null || appInfo.Value?.Children()?.ToList()?.Count == 0) return appInfo;
|
|
|
|
|
VToken type = appInfo.Value?.GetChild("common")?.GetChild("type");
|
|
|
|
|
if (type is null || type.ToString() == "Game")
|
|
|
|
|
{
|
|
|
|
|
string buildid = appInfo.Value?.GetChild("depots")?.GetChild("branches")?.GetChild(branch)?.GetChild("buildid")?.ToString();
|
|
|
|
|
if (buildid is null && type is not null) return appInfo;
|
2022-01-26 03:57:01 +05:00
|
|
|
|
if (type is null || int.TryParse(buildid, out int gamebuildId) && gamebuildId < buildId)
|
2021-10-29 13:20:27 +05:00
|
|
|
|
{
|
2022-03-03 16:38:17 +05:00
|
|
|
|
List<string> dlcAppIds = await ParseDlcAppIds(appInfo);
|
|
|
|
|
foreach (string id in dlcAppIds)
|
2021-10-29 13:20:27 +05:00
|
|
|
|
{
|
2022-02-12 04:25:18 +05:00
|
|
|
|
string dlcAppUpdateFile = $@"{AppInfoPath}\{id}.vdf";
|
|
|
|
|
if (File.Exists(dlcAppUpdateFile)) File.Delete(dlcAppUpdateFile);
|
2021-10-29 13:20:27 +05:00
|
|
|
|
}
|
2022-02-12 04:25:18 +05:00
|
|
|
|
if (File.Exists(appUpdateFile)) File.Delete(appUpdateFile);
|
2022-01-25 00:10:29 +05:00
|
|
|
|
goto restart;
|
2021-10-29 13:20:27 +05:00
|
|
|
|
}
|
|
|
|
|
}
|
2022-01-25 00:10:29 +05:00
|
|
|
|
return appInfo;
|
|
|
|
|
}
|
2021-10-29 13:20:27 +05:00
|
|
|
|
|
2022-03-03 16:38:17 +05:00
|
|
|
|
internal static async Task<List<string>> ParseDlcAppIds(VProperty appInfo) => await Task.Run(() =>
|
2022-01-25 00:10:29 +05:00
|
|
|
|
{
|
2022-03-03 16:38:17 +05:00
|
|
|
|
List<string> dlcIds = new();
|
2022-02-10 20:50:53 +05:00
|
|
|
|
#pragma warning disable IDE0150 // Prefer 'null' check over type check
|
2022-01-25 00:10:29 +05:00
|
|
|
|
if (Program.Canceled || appInfo is not VProperty) return dlcIds;
|
2022-02-10 20:50:53 +05:00
|
|
|
|
#pragma warning restore IDE0150 // Prefer 'null' check over type check
|
2022-01-25 00:10:29 +05:00
|
|
|
|
VToken extended = appInfo.Value.GetChild("extended");
|
|
|
|
|
if (extended is not null)
|
|
|
|
|
foreach (VProperty property in extended)
|
2022-02-05 12:04:22 +05:00
|
|
|
|
if (property.Key == "listofdlc")
|
2022-01-25 00:10:29 +05:00
|
|
|
|
foreach (string id in property.Value.ToString().Split(","))
|
2022-03-03 16:38:17 +05:00
|
|
|
|
if (int.TryParse(id, out int appId)
|
|
|
|
|
&& !dlcIds.Contains("" + appId))
|
|
|
|
|
dlcIds.Add("" + appId);
|
2022-01-25 00:10:29 +05:00
|
|
|
|
VToken depots = appInfo.Value.GetChild("depots");
|
|
|
|
|
if (depots is not null) foreach (VProperty property in depots)
|
2022-02-05 12:04:22 +05:00
|
|
|
|
if (int.TryParse(property.Key, out int _)
|
2022-01-25 00:10:29 +05:00
|
|
|
|
&& int.TryParse(property.Value.GetChild("dlcappid")?.ToString(), out int appid)
|
2022-03-03 16:38:17 +05:00
|
|
|
|
&& !dlcIds.Contains("" + appid))
|
|
|
|
|
dlcIds.Add("" + appid);
|
2022-01-25 00:10:29 +05:00
|
|
|
|
return dlcIds;
|
|
|
|
|
});
|
2021-11-12 23:12:21 +05:00
|
|
|
|
|
2022-01-25 00:10:29 +05:00
|
|
|
|
internal static async Task Kill()
|
|
|
|
|
{
|
|
|
|
|
List<Task> tasks = new();
|
|
|
|
|
foreach (Process process in Process.GetProcessesByName("steamcmd"))
|
2022-02-12 04:25:18 +05:00
|
|
|
|
tasks.Add(Task.Run(() =>
|
2022-02-11 05:13:51 +05:00
|
|
|
|
{
|
2022-02-12 04:25:18 +05:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
process.Kill(true);
|
|
|
|
|
process.WaitForExit();
|
|
|
|
|
}
|
|
|
|
|
catch { }
|
|
|
|
|
}));
|
2022-01-25 00:10:29 +05:00
|
|
|
|
foreach (Task task in tasks) await task;
|
|
|
|
|
}
|
2021-10-29 13:20:27 +05:00
|
|
|
|
|
2022-01-25 00:10:29 +05:00
|
|
|
|
internal static void Dispose()
|
|
|
|
|
{
|
|
|
|
|
Kill().Wait();
|
|
|
|
|
if (Directory.Exists(DirectoryPath))
|
|
|
|
|
Directory.Delete(DirectoryPath, true);
|
2021-10-29 13:20:27 +05:00
|
|
|
|
}
|
2022-01-25 00:10:29 +05:00
|
|
|
|
}
|