mirror of
https://github.com/FroggMaster/CreamInstaller.git
synced 2026-06-12 19:11:25 -07:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ee19990b5b | |||
| 39097c27ef |
@@ -0,0 +1,49 @@
|
||||
name: Dev CI Builds
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore CreamInstaller.sln
|
||||
|
||||
- name: Build Release
|
||||
run: dotnet build CreamInstaller.sln --configuration Release --no-restore
|
||||
|
||||
- name: Publish single-file
|
||||
run: dotnet publish CreamInstaller.sln -c Release -r win-x64 -p:PublishSingleFile=true --self-contained true --output ./publish
|
||||
|
||||
- name: Set short commit SHA and branch name
|
||||
id: vars
|
||||
run: |
|
||||
$shortSha = $env:GITHUB_SHA.Substring(0,7)
|
||||
$branch = $env:GITHUB_REF_NAME
|
||||
Write-Output "shortSha=$shortSha" >> $env:GITHUB_ENV
|
||||
Write-Output "branch=$branch" >> $env:GITHUB_ENV
|
||||
shell: pwsh
|
||||
|
||||
- name: Rename EXE with branch and short commit SHA
|
||||
run: |
|
||||
Rename-Item -Path ./publish/CreamInstaller.exe -NewName "CreamInstaller-CI-$env:branch-$env:shortSha.exe"
|
||||
shell: pwsh
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: CreamInstaller-CI-${{ env.branch }}-${{ env.shortSha }}
|
||||
path: ./publish/CreamInstaller-CI-${{ env.branch }}-${{ env.shortSha }}.exe
|
||||
@@ -569,6 +569,8 @@ internal sealed partial class SelectForm : CustomForm
|
||||
progressLabel.Text = "Waiting for user to select which programs/games to scan . . .";
|
||||
ShowProgressBar();
|
||||
await ProgramData.Setup(this);
|
||||
ProgramData.ClearLog();
|
||||
ProgramData.Log($"[Scan] CreamInstaller {Program.Version} — scan started at {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} UTC");
|
||||
bool scan = forceScan;
|
||||
if (!scan && (programsToScan is null || programsToScan.Count < 1 || forceProvideChoices))
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CreamInstaller.Utility;
|
||||
@@ -28,16 +29,19 @@ internal static class SteamLibrary
|
||||
{
|
||||
List<(string appId, string name, string branch, int buildId, string gameDirectory)> games = new();
|
||||
HashSet<string> gameLibraryDirectories = await GetLibraryDirectories();
|
||||
ProgramData.Log($"[Steam] Found {gameLibraryDirectories.Count} library folder(s).");
|
||||
foreach (string libraryDirectory in gameLibraryDirectories)
|
||||
{
|
||||
if (Program.Canceled)
|
||||
return games;
|
||||
ProgramData.Log($"[Steam] Scanning library: {libraryDirectory}");
|
||||
foreach ((string appId, string name, string branch, int buildId, string gameDirectory) game in (await
|
||||
GetGamesFromLibraryDirectory(
|
||||
libraryDirectory)).Where(game => games.All(_game => _game.appId != game.appId)))
|
||||
games.Add(game);
|
||||
}
|
||||
|
||||
ProgramData.Log($"[Steam] Total games detected: {games.Count}");
|
||||
return games;
|
||||
});
|
||||
|
||||
@@ -47,13 +51,21 @@ internal static class SteamLibrary
|
||||
{
|
||||
List<(string appId, string name, string branch, int buildId, string gameDirectory)> games = new();
|
||||
if (Program.Canceled || !libraryDirectory.DirectoryExists())
|
||||
{
|
||||
ProgramData.Log($"[Steam] Skipping library (not found or canceled): {libraryDirectory}");
|
||||
return games;
|
||||
}
|
||||
|
||||
foreach (string file in libraryDirectory.EnumerateDirectory("*.acf"))
|
||||
{
|
||||
if (Program.Canceled)
|
||||
return games;
|
||||
if (!ValveDataFile.TryDeserialize(file.ReadFile(), out VProperty result))
|
||||
{
|
||||
ProgramData.Log($"[Steam] Failed to deserialize ACF: {file}");
|
||||
continue;
|
||||
}
|
||||
|
||||
string appId = result.Value.GetChild("appid")?.ToString();
|
||||
string installdir = result.Value.GetChild("installdir")?.ToString();
|
||||
string name = result.Value.GetChild("name")?.ToString();
|
||||
@@ -61,11 +73,23 @@ internal static class SteamLibrary
|
||||
if (string.IsNullOrWhiteSpace(appId) || string.IsNullOrWhiteSpace(installdir) ||
|
||||
string.IsNullOrWhiteSpace(name)
|
||||
|| string.IsNullOrWhiteSpace(buildId))
|
||||
{
|
||||
ProgramData.Log($"[Steam] Skipping ACF with missing fields: {file}");
|
||||
continue;
|
||||
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))
|
||||
}
|
||||
|
||||
string rawGameDirectory = libraryDirectory + @"\common\" + installdir;
|
||||
string gameDirectory = rawGameDirectory.ResolvePath();
|
||||
if (gameDirectory is null)
|
||||
{
|
||||
ProgramData.Log($"[Steam] Game directory not found (drive may be slow or disconnected): {rawGameDirectory} | App: {name} ({appId})");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!int.TryParse(appId, out int _) || !int.TryParse(buildId, out int buildIdInt) ||
|
||||
games.Any(g => g.appId == appId))
|
||||
continue;
|
||||
|
||||
VToken userConfig = result.Value.GetChild("UserConfig");
|
||||
string branch = userConfig?.GetChild("BetaKey")?.ToString();
|
||||
branch ??= userConfig?.GetChild("betakey")?.ToString();
|
||||
@@ -78,6 +102,8 @@ internal static class SteamLibrary
|
||||
|
||||
if (string.IsNullOrWhiteSpace(branch))
|
||||
branch = "public";
|
||||
|
||||
ProgramData.Log($"[Steam] Detected game: {name} ({appId}) | Branch: {branch} | Dir: {gameDirectory}");
|
||||
games.Add((appId, name, branch, buildIdInt, gameDirectory));
|
||||
}
|
||||
|
||||
@@ -92,25 +118,50 @@ internal static class SteamLibrary
|
||||
return libraryDirectories;
|
||||
string steamInstallPath = InstallPath;
|
||||
if (steamInstallPath == null || !steamInstallPath.DirectoryExists())
|
||||
{
|
||||
ProgramData.Log($"[Steam] Steam install path not found or inaccessible: {steamInstallPath ?? "(null)"}");
|
||||
return libraryDirectories;
|
||||
}
|
||||
|
||||
string libraryFolder = steamInstallPath + @"\steamapps";
|
||||
if (!libraryFolder.DirectoryExists())
|
||||
{
|
||||
ProgramData.Log($"[Steam] Default steamapps folder not found: {libraryFolder}");
|
||||
return libraryDirectories;
|
||||
}
|
||||
|
||||
_ = libraryDirectories.Add(libraryFolder);
|
||||
ProgramData.Log($"[Steam] Default library folder: {libraryFolder}");
|
||||
|
||||
string libraryFolders = libraryFolder + @"\libraryfolders.vdf";
|
||||
if (!libraryFolders.FileExists() ||
|
||||
!ValveDataFile.TryDeserialize(libraryFolders.ReadFile(), out VProperty result))
|
||||
{
|
||||
ProgramData.Log($"[Steam] libraryfolders.vdf not found or failed to parse: {libraryFolders}");
|
||||
return libraryDirectories;
|
||||
}
|
||||
|
||||
foreach (VToken vToken in result.Value.Where(p =>
|
||||
p is VProperty property && int.TryParse(property.Key, out int _)))
|
||||
{
|
||||
VProperty property = (VProperty)vToken;
|
||||
string path = property.Value.GetChild("path")?.ToString();
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
string rawPath = property.Value.GetChild("path")?.ToString();
|
||||
if (string.IsNullOrWhiteSpace(rawPath))
|
||||
continue;
|
||||
path += @"\steamapps";
|
||||
if (path.DirectoryExists())
|
||||
_ = libraryDirectories.Add(path);
|
||||
|
||||
// Normalize the path from VDF (may use forward slashes or wrong casing)
|
||||
string normalizedPath = Path.GetFullPath(rawPath);
|
||||
string steamappsPath = normalizedPath + @"\steamapps";
|
||||
string resolvedPath = steamappsPath.ResolvePath();
|
||||
|
||||
if (resolvedPath is null)
|
||||
{
|
||||
ProgramData.Log($"[Steam] External library not accessible (drive may be disconnected or letter changed): {steamappsPath}");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (libraryDirectories.Add(resolvedPath))
|
||||
ProgramData.Log($"[Steam] Additional library folder found: {resolvedPath}");
|
||||
}
|
||||
|
||||
return libraryDirectories;
|
||||
|
||||
@@ -54,7 +54,16 @@ internal static class Diagnostics
|
||||
if (info.Parent is null)
|
||||
return info.Name.ToUpperInvariant();
|
||||
string parent = ResolvePath(info.Parent.FullName);
|
||||
string name = info.Parent.GetFileSystemInfos(info.Name)[0].Name;
|
||||
return parent is null ? name : Path.Combine(parent, name);
|
||||
try
|
||||
{
|
||||
FileSystemInfo[] infos = info.Parent.GetFileSystemInfos(info.Name);
|
||||
string name = infos.Length > 0 ? infos[0].Name : info.Name;
|
||||
return parent is null ? name : Path.Combine(parent, name);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fall back to the raw name if the filesystem call fails (e.g. on a slow external drive)
|
||||
return parent is null ? info.Name : Path.Combine(parent, info.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using Newtonsoft.Json;
|
||||
@@ -28,6 +30,38 @@ internal static class ProgramData
|
||||
private static readonly string DlcChoicesPath = DirectoryPath + @"\dlc.json";
|
||||
private static readonly string KoaloaderProxyChoicesPath = DirectoryPath + @"\proxies.json";
|
||||
|
||||
internal static readonly string LogPath = DirectoryPath + @"\scan.log";
|
||||
|
||||
private static readonly object LogLock = new();
|
||||
|
||||
internal static void Log(string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
string timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture);
|
||||
string entry = $"[{timestamp}] {message}{Environment.NewLine}";
|
||||
lock (LogLock)
|
||||
File.AppendAllText(LogPath, entry, Encoding.UTF8);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored — logging must never crash the application
|
||||
}
|
||||
}
|
||||
|
||||
internal static void ClearLog()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(LogPath))
|
||||
File.Delete(LogPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
internal static async Task Setup(Form form = null)
|
||||
=> await Task.Run(() =>
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user