Updated Filestore. Finished Auth and Install Services. Introduced GameOptions and BaseConfig. Adjusted Settings Service to reflect.

This commit is contained in:
2026-04-06 11:41:07 +01:00
parent c148672c08
commit 7b66d8ceac
38 changed files with 2684 additions and 197 deletions

View File

@@ -1,7 +1,7 @@
namespace AlayaCore.Abstractions.Configuration
{
public class BaseConfig
public abstract class BaseConfig
{
public abstract string FileName { get; }
}
}

View File

@@ -1,7 +1,12 @@
using AlayaCore.Utilities.Enums;
namespace AlayaCore.Abstractions.Interfaces
{
public interface IFileStore
{
string Get(FolderLocation location);
string GetOrCreate(FolderLocation location);
string Combine(FolderLocation location, params string[] paths);
bool Exists(FolderLocation location);
}
}

View File

@@ -1,7 +1,10 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using AlayaCore.Models;
using AlayaCore.States;
using CmlLib.Core;
using CmlLib.Core.Installers;
namespace AlayaCore.Abstractions.Interfaces
{
@@ -9,7 +12,11 @@ namespace AlayaCore.Abstractions.Interfaces
{
Task<LaunchPlan> EvaluateAsync(CancellationToken cancellationToken = default);
Task InstallOrUpdateAsync(CancellationToken cancellationToken = default);
Task InstallOrUpdateAsync(CancellationToken cancellationToken = default,
EventHandler<InstallerProgressChangedEventArgs>? minecraftProgess = null,
EventHandler<ByteProgress>? byteProgress = null,
IProgress<InstallerProgressChangedEventArgs>? neoForgeProgress = null,
IProgress<ByteProgress>? neoForgeByteProgress = null);
Task LaunchAsync(CancellationToken cancellationToken = default);

View File

@@ -1,7 +1,14 @@
using System.Threading;
using System.Threading.Tasks;
using CmlLib.Core.Auth;
namespace AlayaCore.Abstractions.Interfaces.Services
{
public interface IAuthService
{
Task<bool> IsAuthenticatedAsync(CancellationToken cancellationToken = default);
Task AuthenticateAsync(CancellationToken cancellationToken = default);
Task SignOutAsync(CancellationToken cancellationToken = default);
Task<MSession> GetSessionAsync(CancellationToken cancellationToken = default);
}
}

View File

@@ -1,13 +1,31 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using AlayaCore.Installation;
using AlayaCore.Models.Manifests;
using CmlLib.Core;
using CmlLib.Core.Installers;
namespace AlayaCore.Abstractions.Interfaces.Services
{
public interface IMinecraftService
public interface IGameInstallService
{
Task EnsureMinecraftInstalledAsync(ManifestModel manifest, InstallEnvironment environment, CancellationToken cancellationToken);
Task EnsureNeoForgeInstalledAsync(ManifestModel manifest, InstallEnvironment environment, CancellationToken cancellationToken);
Task EnsureMinecraftInstalledAsync(
ManifestModel manifest,
InstallEnvironment environment,
CancellationToken cancellationToken = default,
EventHandler<InstallerProgressChangedEventArgs>? minecraftProgess = null,
EventHandler<ByteProgress>? byteProgress = null);
Task EnsureNeoForgeInstalledAsync(
ManifestModel manifest,
InstallEnvironment environment,
CancellationToken cancellationToken = default,
IProgress<InstallerProgressChangedEventArgs>? progress = null,
IProgress<ByteProgress>? byteProgress = null);
Task VerifyFilesAsync(
ManifestModel manifest,
CancellationToken cancellationToken = default);
}
}

View File

@@ -7,11 +7,18 @@ namespace AlayaCore.Abstractions.Interfaces.Services
public interface ISettingsService
{
LauncherOptions LauncherOptions { get; }
GameOptions GameOptions { get; }
Task SetForceReinstallAsync(bool value, CancellationToken cancellationToken = default);
Task UpdateLaunchVersionAsync(string newVersion, CancellationToken cancellationToken = default);
Task SaveLauncherOptionsAsync(CancellationToken cancellationToken = default);
Task LoadLauncherOptionsAsync(CancellationToken cancellationToken = default);
Task SaveGameOptionsAsync(CancellationToken cancellationToken = default);
Task LoadGameOptionsAsync(CancellationToken cancellationToken = default);
Task LoadAllAsync(CancellationToken cancellationToken = default);
Task SaveAllAsync(CancellationToken cancellationToken = default);
}
}

View File

@@ -7,7 +7,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CmlLib.Core" Version="4.0.6" />
<PackageReference Include="CmlLib.Core.Auth.Microsoft" Version="3.3.1" />
<PackageReference Include="CmlLib.Core.Installer.NeoForge" Version="4.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="XboxAuthNet.Game.Msal" Version="0.1.3" />
</ItemGroup>
</Project>

View File

@@ -9,6 +9,9 @@ using AlayaCore.Installation;
using AlayaCore.Models.Configuration;
using AlayaCore.Models.Manifests;
using AlayaCore.States;
using AlayaCore.Utilities.Enums;
using CmlLib.Core;
using CmlLib.Core.Installers;
namespace AlayaCore
{
@@ -20,12 +23,13 @@ namespace AlayaCore
private readonly IJavaService _javaService;
private readonly IModService _modService;
private readonly IGameLaunchService _gameLaunchService;
private readonly IGameInstallService _gameInstallService;
private readonly ISettingsService _settingsService;
private readonly IAuthService _authService;
private readonly LauncherOptions _options;
public bool CanRun { get; private set; }
public bool NeedsUpdating { get; private set; }
public LaunchPlan? CurrentPlan { get; private set; }
public LaunchDirector(
@@ -35,6 +39,9 @@ namespace AlayaCore
IJavaService javaService,
IModService modService,
IGameLaunchService gameLaunchService,
IGameInstallService gameInstallService,
ISettingsService settingsService,
IAuthService authService,
LauncherOptions options)
{
_manifestService = manifestService ?? throw new ArgumentNullException(nameof(manifestService));
@@ -43,6 +50,9 @@ namespace AlayaCore
_javaService = javaService ?? throw new ArgumentNullException(nameof(javaService));
_modService = modService ?? throw new ArgumentNullException(nameof(modService));
_gameLaunchService = gameLaunchService ?? throw new ArgumentNullException(nameof(gameLaunchService));
_gameInstallService = gameInstallService ?? throw new ArgumentNullException(nameof(gameInstallService));
_settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
_authService = authService ?? throw new ArgumentNullException(nameof(authService));
_options = options ?? throw new ArgumentNullException(nameof(options));
}
@@ -61,7 +71,8 @@ namespace AlayaCore
javaNeedsInstallOrUpdate: false,
minecraftNeedsInstallOrUpdate: false,
neoforgeNeedsInstallOrUpdate: false,
modsNeedSync: false);
modsNeedSync: false,
needAuthenticating: false);
ApplyPlan(launcherUpdatePlan);
return launcherUpdatePlan;
@@ -78,6 +89,10 @@ namespace AlayaCore
!environment.JavaInstalled ||
!string.Equals(environment.JavaVersion, manifest.RequiredJavaVersion, StringComparison.OrdinalIgnoreCase);
bool needAuthenticating = !await _authService
.IsAuthenticatedAsync(cancellationToken)
.ConfigureAwait(false);
bool minecraftNeedsInstallOrUpdate =
_options.ForceReinstall ||
!environment.MinecraftInstalled ||
@@ -97,13 +112,18 @@ namespace AlayaCore
javaNeedsInstallOrUpdate: javaNeedsInstallOrUpdate,
minecraftNeedsInstallOrUpdate: minecraftNeedsInstallOrUpdate,
neoforgeNeedsInstallOrUpdate: neoforgeNeedsInstallOrUpdate,
modsNeedSync: modsNeedSync);
modsNeedSync: modsNeedSync,
needAuthenticating: needAuthenticating);
ApplyPlan(plan);
return plan;
}
public async Task InstallOrUpdateAsync(CancellationToken cancellationToken = default)
public async Task InstallOrUpdateAsync(CancellationToken cancellationToken = default,
EventHandler<InstallerProgressChangedEventArgs>? minecraftProgess = null,
EventHandler<ByteProgress>? byteProgress = null,
IProgress<InstallerProgressChangedEventArgs>? neoForgeProgress = null,
IProgress<ByteProgress>? neoForgeByteProgress = null)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -113,8 +133,8 @@ namespace AlayaCore
{
cancellationToken.ThrowIfCancellationRequested();
ManifestModel? manifest = null;
InstallEnvironment? environment = null;
ManifestModel? manifest;
InstallEnvironment? environment;
switch (plan.State)
{
@@ -135,7 +155,6 @@ namespace AlayaCore
case LaunchState.InstallJava:
{
manifest = await EnsureCurrentManifestAsync(cancellationToken).ConfigureAwait(false);
environment = await _installStateService
.GetCurrentEnvironmentAsync(cancellationToken)
.ConfigureAwait(false);
@@ -147,20 +166,43 @@ namespace AlayaCore
break;
}
case LaunchState.NeedAuthenticating:
{
await _authService.AuthenticateAsync(cancellationToken);
break;
}
case LaunchState.InstallMinecraft:
{
throw new NotImplementedException("Minecraft install/update flow has not been implemented yet.");
manifest = await EnsureCurrentManifestAsync(cancellationToken).ConfigureAwait(false);
environment = await _installStateService
.GetCurrentEnvironmentAsync(cancellationToken)
.ConfigureAwait(false);
await _gameInstallService
.EnsureMinecraftInstalledAsync(manifest, environment, cancellationToken, minecraftProgess, byteProgress)
.ConfigureAwait(false);
break;
}
case LaunchState.InstallNeoforge:
{
throw new NotImplementedException("NeoForge install/update flow has not been implemented yet.");
manifest = await EnsureCurrentManifestAsync(cancellationToken).ConfigureAwait(false);
environment = await _installStateService
.GetCurrentEnvironmentAsync(cancellationToken)
.ConfigureAwait(false);
await _gameInstallService
.EnsureNeoForgeInstalledAsync(manifest, environment, cancellationToken, neoForgeProgress, neoForgeByteProgress)
.ConfigureAwait(false);
break;
}
case LaunchState.SyncMods:
{
manifest = await EnsureCurrentManifestAsync(cancellationToken).ConfigureAwait(false);
environment = await _installStateService
.GetCurrentEnvironmentAsync(cancellationToken)
.ConfigureAwait(false);
@@ -185,6 +227,18 @@ namespace AlayaCore
plan = await EvaluateAsync(cancellationToken).ConfigureAwait(false);
}
if (_options.ForceReinstall)
{
await _settingsService.SetForceReinstallAsync(false, cancellationToken).ConfigureAwait(false);
}
plan = await EvaluateAsync(cancellationToken).ConfigureAwait(false);
if (!plan.CanRun)
{
throw new InvalidOperationException("Install/update completed, but the launcher is still not in a runnable state.");
}
}
public async Task LaunchAsync(CancellationToken cancellationToken = default)
@@ -250,7 +304,7 @@ namespace AlayaCore
}
var requiredMods = manifest.Files
.Where(file => file.Type == AlayaCore.Utilities.Enums.FileType.Mod)
.Where(file => file.Type == FileType.Mod)
.ToList();
var installedMods = environment.InstalledModsManifest.Mods;

View File

@@ -1,7 +1,27 @@
using System;
using System.IO;
using AlayaCore.Abstractions.Interfaces;
using AlayaCore.Utilities.Enums;
using CmlLib.Core;
namespace AlayaCore.Models
{
public class AlayaPath
public class AlayaPath : MinecraftPath
{
public AlayaPath(IFileStore fileStore)
{
BasePath = NormalizePath(fileStore.Get(FolderLocation.Game));
Library = NormalizePath(BasePath + "/libraries");
Versions = NormalizePath(BasePath + "/versions");
Resource = NormalizePath(BasePath + "/resources");
Runtime = NormalizePath(fileStore.GetOrCreate(FolderLocation.JavaRuntime));
Assets = NormalizePath(BasePath + "/assets");
CreateDirs();
}
}
}

View File

@@ -1,7 +1,30 @@
using AlayaCore.Abstractions.Configuration;
namespace AlayaCore.Models.Configuration
{
public class GameOptions
public class GameOptions : BaseConfig
{
public override string FileName => "Game.json";
public string? LaunchVersion { get; set; }
public int MinimumRamMB { get; set; }
public int MaximumRamMB { get; set; }
public int ScreenWidth { get; set; }
public int ScreenHeight { get; set; }
public bool Fullscreen { get; set; }
public static GameOptions Default { get; } = new GameOptions
{
LaunchVersion = null,
MinimumRamMB = 1024,
MaximumRamMB = 2048,
ScreenWidth = 1920,
ScreenHeight = 1080,
Fullscreen = false,
};
}
}

View File

@@ -1,7 +1,16 @@
using AlayaCore.Abstractions.Configuration;
namespace AlayaCore.Models.Configuration
{
public sealed class LauncherOptions
public sealed class LauncherOptions : BaseConfig
{
public bool ForceReinstall { get; set; }
public override string FileName => "Launcher.json";
public static LauncherOptions Default { get; } = new LauncherOptions
{
ForceReinstall = false,
};
}
}

View File

@@ -23,16 +23,15 @@ namespace AlayaCore.Models.Manifests.DTO
[JsonProperty("minecraftVersion", Required = Required.Always)]
public string MinecraftVersion { get; set; } = string.Empty;
[JsonProperty("minecraftUrl", Required = Required.Always)]
[JsonConverter(typeof(UriConverter))]
public Uri MinecraftUrl { get; set; } = null!;
[JsonProperty("neoforgedVersion", Required = Required.Always)]
public string NeoforgedVersion { get; set; } = string.Empty;
[JsonProperty("neoforgedUrl", Required = Required.Always)]
[JsonProperty("serverUrl", Required = Required.Always)]
[JsonConverter(typeof(UriConverter))]
public Uri NeoforgedUrl { get; set; } = null!;
public Uri ServerUrl { get; set; } = null!;
[JsonProperty("serverPort", Required = Required.Always)]
public int ServerPort { get; }
[JsonProperty("files", Required = Required.Always)]
public List<ModFileEntryDto> Files { get; set; } = new List<ModFileEntryDto>();

View File

@@ -10,11 +10,12 @@ namespace AlayaCore.Models.Manifests
{
public Version AlayaVersion { get; }
public string RequiredJavaVersion { get; }
public Uri RequiredJavaUrl { get; }
public Uri RequiredJavaBaseUrl { get; }
public string MinecraftVersion { get; }
public Uri MinecraftUrl { get; }
public string NeoforgedVersion { get; }
public Uri NeoforgedUrl { get; }
public Uri ServerUrl { get; }
public int ServerPort { get; }
public IReadOnlyList<ModFileEntry> Files { get; }
public ManifestModel(
@@ -22,20 +23,18 @@ namespace AlayaCore.Models.Manifests
string requiredJavaVersion,
Uri requiredJavaUrl,
string minecraftVersion,
Uri minecraftUrl,
string neoforgedVersion,
Uri neoforgedUrl,
Uri serverUrl,
int serverPort,
IEnumerable<ModFileEntry> files)
{
AlayaVersion = alayaVersion ?? throw new ArgumentNullException(nameof(alayaVersion));
RequiredJavaVersion = RequireNonEmpty(requiredJavaVersion, nameof(requiredJavaVersion));
RequiredJavaUrl = requiredJavaUrl ?? throw new ArgumentNullException(nameof(requiredJavaUrl));
RequiredJavaBaseUrl = requiredJavaUrl ?? throw new ArgumentNullException(nameof(requiredJavaUrl));
MinecraftVersion = RequireNonEmpty(minecraftVersion, nameof(minecraftVersion));
MinecraftUrl = minecraftUrl ?? throw new ArgumentNullException(nameof(minecraftUrl));
NeoforgedVersion = RequireNonEmpty(neoforgedVersion, nameof(neoforgedVersion));
NeoforgedUrl = neoforgedUrl ?? throw new ArgumentNullException(nameof(neoforgedUrl));
if (files == null)
{

View File

@@ -1,7 +1,113 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using AlayaCore.Abstractions.Interfaces;
using AlayaCore.Abstractions.Interfaces.Services;
using AlayaCore.Utilities.Enums;
using CmlLib.Core.Auth;
using CmlLib.Core.Auth.Microsoft;
using Microsoft.Identity.Client;
using XboxAuthNet.Game.Msal;
using XboxAuthNet.Game.Msal.OAuth;
namespace AlayaCore.Services
{
public class AuthService
public sealed class AuthService : IAuthService
{
private readonly IFileStore _fileStore;
private MSession? _session;
private IPublicClientApplication? _clientApp;
private JELoginHandler? _loginHandler;
public AuthService(IFileStore fileStore)
{
_fileStore = fileStore ?? throw new ArgumentNullException(nameof(fileStore));
}
public Task<bool> IsAuthenticatedAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
bool authenticated = _session != null && _session.CheckIsValid();
return Task.FromResult(authenticated);
}
public async Task AuthenticateAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
JELoginHandler loginHandler = await BuildHandlerAsync().ConfigureAwait(false);
try
{
_session = await loginHandler
.AuthenticateSilently(cancellationToken: cancellationToken)
.ConfigureAwait(false);
}
catch
{
_session = await loginHandler
.AuthenticateInteractively(cancellationToken: cancellationToken)
.ConfigureAwait(false);
}
if (_session == null || !_session.CheckIsValid())
{
throw new InvalidOperationException("Authentication did not produce a valid Minecraft session.");
}
}
public async Task SignOutAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
JELoginHandler loginHandler = await BuildHandlerAsync().ConfigureAwait(false);
await loginHandler.Signout(cancellationToken).ConfigureAwait(false);
_session = null;
}
public async Task<MSession> GetSessionAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (_session != null && _session.CheckIsValid())
{
return _session;
}
await AuthenticateAsync(cancellationToken).ConfigureAwait(false);
if (_session == null || !_session.CheckIsValid())
{
throw new InvalidOperationException("No valid Minecraft session is available.");
}
return _session;
}
private async Task<JELoginHandler> BuildHandlerAsync()
{
if (_loginHandler != null)
{
return _loginHandler;
}
string accountDirectory = _fileStore.GetOrCreate(FolderLocation.Data);
string accountFilePath = Path.Combine(accountDirectory, "accounts.json");
_clientApp = await MsalClientHelper
.BuildApplicationWithCache("d91042d4-3eb5-43e4-b3ed-600e1d0760ff")
.ConfigureAwait(false);
_loginHandler = new JELoginHandlerBuilder()
.WithOAuthProvider(new MsalCodeFlowProvider(_clientApp))
.WithAccountManager(accountFilePath)
.Build();
return _loginHandler;
}
}
}

View File

@@ -1,7 +1,272 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using AlayaCore.Abstractions.Interfaces;
using AlayaCore.Abstractions.Interfaces.Services;
using AlayaCore.Installation;
using AlayaCore.Models;
using AlayaCore.Models.Manifests;
using AlayaCore.Utilities.Enums;
using CmlLib.Core;
using CmlLib.Core.Installer.NeoForge;
using CmlLib.Core.Installer.NeoForge.Installers;
using CmlLib.Core.Installers;
using CmlLib.Core.Java;
using CmlLib.Core.VersionMetadata;
namespace AlayaCore.Services
{
public class GameInstallService
public sealed class GameInstallService : IGameInstallService
{
private const string InstalledModsManifestFileName = "InstalledModsManifest.json";
private readonly IFileStore _fileStore;
private readonly ISettingsService _settingsService;
private AlayaPath? _gamePath;
private MinecraftLauncher? _minecraftLauncher;
public GameInstallService(
IFileStore fileStore,
ISettingsService settingsService)
{
_fileStore = fileStore ?? throw new ArgumentNullException(nameof(fileStore));
_settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
}
public async Task EnsureMinecraftInstalledAsync(
ManifestModel manifest,
InstallEnvironment environment,
CancellationToken cancellationToken = default,
EventHandler<InstallerProgressChangedEventArgs>? minecraftProgess = null,
EventHandler<ByteProgress>? byteProgress = null)
{
if (manifest == null)
{
throw new ArgumentNullException(nameof(manifest));
}
if (environment == null)
{
throw new ArgumentNullException(nameof(environment));
}
cancellationToken.ThrowIfCancellationRequested();
if (string.IsNullOrWhiteSpace(manifest.MinecraftVersion))
{
throw new InvalidDataException("Minecraft version is missing.");
}
bool alreadyInstalled =
environment.MinecraftInstalled &&
string.Equals(environment.MinecraftVersion, manifest.MinecraftVersion, StringComparison.OrdinalIgnoreCase);
if (alreadyInstalled)
{
return;
}
bool versionMismatch =
environment.MinecraftInstalled &&
!string.Equals(environment.MinecraftVersion, manifest.MinecraftVersion, StringComparison.OrdinalIgnoreCase);
if (versionMismatch)
{
await CleanOldInstallAsync(cancellationToken).ConfigureAwait(false);
}
MinecraftLauncher launcher = GetOrCreateLauncher(minecraftProgess, byteProgress);
await launcher
.InstallAsync(manifest.MinecraftVersion, cancellationToken)
.ConfigureAwait(false);
}
public async Task EnsureNeoForgeInstalledAsync(
ManifestModel manifest,
InstallEnvironment environment,
CancellationToken cancellationToken = default,
IProgress<InstallerProgressChangedEventArgs>? progress = null,
IProgress<ByteProgress>? byteProgress = null)
{
if (manifest == null)
{
throw new ArgumentNullException(nameof(manifest));
}
if (environment == null)
{
throw new ArgumentNullException(nameof(environment));
}
cancellationToken.ThrowIfCancellationRequested();
if (string.IsNullOrWhiteSpace(manifest.MinecraftVersion))
{
throw new InvalidDataException("Minecraft version is missing.");
}
if (string.IsNullOrWhiteSpace(manifest.NeoforgedVersion))
{
throw new InvalidDataException("NeoForge version is missing.");
}
bool alreadyInstalled =
environment.NeoforgedInstalled &&
string.Equals(environment.NeoforgedVersion, manifest.NeoforgedVersion, StringComparison.OrdinalIgnoreCase);
if (alreadyInstalled)
{
return;
}
if (!environment.MinecraftInstalled ||
!string.Equals(environment.MinecraftVersion, manifest.MinecraftVersion, StringComparison.OrdinalIgnoreCase))
{
await EnsureMinecraftInstalledAsync(manifest, environment, cancellationToken).ConfigureAwait(false);
}
if (string.IsNullOrWhiteSpace(environment.JavaPath))
{
throw new InvalidOperationException("A valid Java installation is required before installing NeoForge.");
}
MinecraftLauncher launcher = GetOrCreateLauncher();
await DownloadAndInstallNeoForgeAsync(
launcher,
manifest,
environment,
cancellationToken,
progress, byteProgress).ConfigureAwait(false);
await launcher
.InstallAsync(manifest.MinecraftVersion, cancellationToken)
.ConfigureAwait(false);
}
public async Task VerifyFilesAsync(
ManifestModel manifest,
CancellationToken cancellationToken = default)
{
if (manifest == null)
{
throw new ArgumentNullException(nameof(manifest));
}
cancellationToken.ThrowIfCancellationRequested();
if (string.IsNullOrWhiteSpace(manifest.MinecraftVersion))
{
throw new InvalidDataException("Minecraft version is missing.");
}
MinecraftLauncher launcher = GetOrCreateLauncher();
await launcher
.InstallAsync(manifest.MinecraftVersion, cancellationToken)
.ConfigureAwait(false);
}
private async Task CleanOldInstallAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
string gamePath = GetMinecraftPath();
if (Directory.Exists(gamePath))
{
Directory.Delete(gamePath, recursive: true);
}
string installedModsManifestPath = Path.Combine(
_fileStore.Get(FolderLocation.Manifests),
InstalledModsManifestFileName);
if (File.Exists(installedModsManifestPath))
{
File.Delete(installedModsManifestPath);
}
_gamePath = null;
_minecraftLauncher = null;
await _settingsService.UpdateLaunchVersionAsync(string.Empty, cancellationToken).ConfigureAwait(false);
}
private MinecraftLauncher GetOrCreateLauncher(EventHandler<InstallerProgressChangedEventArgs>? minecraftProgess = null,
EventHandler<ByteProgress>? byteProgress = null)
{
if (_minecraftLauncher != null)
{
return _minecraftLauncher;
}
_gamePath = new AlayaPath(_fileStore);
_minecraftLauncher = new MinecraftLauncher(_gamePath);
if(byteProgress != null)
_minecraftLauncher.ByteProgressChanged += byteProgress;
if(minecraftProgess != null)
_minecraftLauncher.FileProgressChanged += minecraftProgess;
return _minecraftLauncher;
}
private async Task DownloadAndInstallNeoForgeAsync(
MinecraftLauncher launcher,
ManifestModel manifest,
InstallEnvironment environment,
CancellationToken cancellationToken,
IProgress<InstallerProgressChangedEventArgs>? progress = null,
IProgress<ByteProgress>? byteProgress = null)
{
if (launcher == null)
{
throw new ArgumentNullException(nameof(launcher));
}
bool neoForgeMismatch =
environment.NeoforgedInstalled &&
!string.Equals(environment.NeoforgedVersion, manifest.NeoforgedVersion, StringComparison.OrdinalIgnoreCase);
if (neoForgeMismatch)
{
await CleanOldInstallAsync(cancellationToken).ConfigureAwait(false);
return;
}
NeoForgeInstaller installer = new NeoForgeInstaller(launcher);
NeoForgeInstallOptions options = new NeoForgeInstallOptions
{
CancellationToken = cancellationToken,
JavaPath = environment.JavaPath,
SkipIfAlreadyInstalled = true
};
if(progress != null)
options.FileProgress = progress;
if(byteProgress != null)
options.ByteProgress = byteProgress;
string version = await installer
.Install(manifest.MinecraftVersion, manifest.NeoforgedVersion, options)
.ConfigureAwait(false);
await _settingsService
.UpdateLaunchVersionAsync(version, cancellationToken)
.ConfigureAwait(false);
}
private string GetMinecraftPath()
{
return _fileStore.GetOrCreate(FolderLocation.Game);
}
}
}

View File

@@ -1,7 +1,124 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using AlayaCore.Abstractions.Interfaces;
using AlayaCore.Abstractions.Interfaces.Services;
using AlayaCore.Installation;
using AlayaCore.Models;
using AlayaCore.Models.Configuration;
using AlayaCore.Models.Manifests;
using AlayaCore.Utilities.Enums;
using CmlLib.Core;
using CmlLib.Core.Auth;
using CmlLib.Core.ProcessBuilder;
namespace AlayaCore.Services
{
public class GameLaunchService
public sealed class GameLaunchService : IGameLaunchService
{
private readonly IAuthService _authService;
private readonly IFileStore _fileStore;
private readonly ILaunchDirector _director;
private readonly GameOptions _gameOptions;
private AlayaPath? _gamePath;
private MinecraftLauncher? _minecraftLauncher;
public GameLaunchService(
IAuthService authService,
IFileStore fileStore,
ILaunchDirector director,
GameOptions gameOptions)
{
_authService = authService ?? throw new ArgumentNullException(nameof(authService));
_fileStore = fileStore ?? throw new ArgumentNullException(nameof(fileStore));
_director = director ?? throw new ArgumentNullException(nameof(director));
_gameOptions = gameOptions ?? throw new ArgumentNullException(nameof(gameOptions));
}
public async Task LaunchAsync(
ManifestModel manifest,
InstallEnvironment environment,
CancellationToken cancellationToken = default)
{
if (!_director.CanRun)
{
throw new InvalidOperationException("The launcher is not in a runnable state.");
}
if (manifest == null)
{
throw new ArgumentNullException(nameof(manifest));
}
if (environment == null)
{
throw new ArgumentNullException(nameof(environment));
}
if (string.IsNullOrWhiteSpace(_gameOptions.LaunchVersion))
{
throw new InvalidDataException("GameOptions.LaunchVersion is not configured.");
}
if (string.IsNullOrWhiteSpace(environment.JavaPath))
{
throw new InvalidOperationException("A valid Java path is required to launch the game.");
}
cancellationToken.ThrowIfCancellationRequested();
MLaunchOption option = await BuildLaunchOptions(manifest, environment);
MinecraftLauncher launcher = GetOrCreateLauncher();
var process = await launcher
.CreateProcessAsync(_gameOptions.LaunchVersion, option)
.ConfigureAwait(false);
var processWrapper = new ProcessWrapper(process);
processWrapper.StartWithEvents();
await processWrapper.WaitForExitTaskAsync().ConfigureAwait(false);
}
private MinecraftLauncher GetOrCreateLauncher()
{
if (_minecraftLauncher != null)
{
return _minecraftLauncher;
}
_gamePath = new AlayaPath(_fileStore);
_minecraftLauncher = new MinecraftLauncher(_gamePath);
return _minecraftLauncher;
}
private string GetMinecraftPath()
{
return _fileStore.GetOrCreate(FolderLocation.Game);
}
private async Task<MLaunchOption> BuildLaunchOptions(ManifestModel manifest, InstallEnvironment environment)
{
if (manifest.ServerUrl == null)
{
throw new InvalidDataException("Manifest Server Url is not configured.");
}
return new MLaunchOption
{
Session = await _authService.GetSessionAsync(),
JavaPath = environment.JavaPath,
MinimumRamMb = _gameOptions.MinimumRamMB,
MaximumRamMb = _gameOptions.MaximumRamMB,
ScreenWidth = _gameOptions.ScreenWidth,
ScreenHeight = _gameOptions.ScreenHeight,
ServerIp = manifest.ServerUrl.Host,
ServerPort = manifest.ServerPort,
DockName = "AlayaCraft"
};
}
}
}

View File

@@ -12,13 +12,13 @@ using AlayaCore.Models.Results;
namespace AlayaCore.Services
{
public sealed class DownloadService : IDownloadService
public sealed class HttpDownloadService : IDownloadService
{
private const int BUFFER_SIZE = 81920;
private readonly IHttpClient _httpClient;
public DownloadService(IHttpClient httpClient)
public HttpDownloadService(IHttpClient httpClient)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
}

View File

@@ -1,24 +1,32 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using AlayaCore.Abstractions.Interfaces;
using AlayaCore.Abstractions.Interfaces.Services;
using AlayaCore.Installation;
using AlayaCore.Models.Manifests;
using AlayaCore.Utilities.Enums;
using Newtonsoft.Json.Linq;
namespace AlayaCore.Services
{
public sealed class InstallationStateService : IInstallStateService
{
private const string JAVA_RUNTIME_FOLDER_NAME = "java-runtime-epsilon";
private const string VersionsFolderName = "versions";
private readonly IFileStore _fileStore;
private readonly IManifestService _manifestService;
public InstallationStateService(IManifestService manifestService)
public InstallationStateService(
IFileStore fileStore,
IManifestService manifestService)
{
_fileStore = fileStore ?? throw new ArgumentNullException(nameof(fileStore));
_manifestService = manifestService ?? throw new ArgumentNullException(nameof(manifestService));
}
@@ -36,26 +44,20 @@ namespace AlayaCore.Services
javaVersion = GetJavaVersion(javaPath!);
}
bool minecraftInstalled = IsMinecraftInstalled();
string? minecraftVersion = null;
bool neoforgeInstalled = IsNeoforgeInstalled();
string? neoforgeVersion = null;
InstalledVersionState versionState = GetInstalledVersionState();
InstalledModsManifestModel installedModsManifest =
await _manifestService.GetInstalledModsManifestAsync(cancellationToken).ConfigureAwait(false)
?? new InstalledModsManifestModel();
await _manifestService.GetInstalledModsManifestAsync(cancellationToken).ConfigureAwait(false);
return new InstallEnvironment(
osPlatform: platform,
javaInstalled: javaInstalled,
javaPath: javaPath,
javaVersion: javaVersion,
minecraftInstalled: minecraftInstalled,
minecraftVersion: minecraftVersion,
neoforgedInstalled: neoforgeInstalled,
neoforgedVersion: neoforgeVersion,
minecraftInstalled: !string.IsNullOrWhiteSpace(versionState.MinecraftVersion),
minecraftVersion: versionState.MinecraftVersion,
neoforgedInstalled: !string.IsNullOrWhiteSpace(versionState.NeoForgeVersion),
neoforgedVersion: versionState.NeoForgeVersion,
installedModsManifest: installedModsManifest);
}
@@ -131,18 +133,14 @@ namespace AlayaCore.Services
: null;
}
private static bool TryGetJavaPath(out string? javaPath)
private bool TryGetJavaPath(out string? javaPath)
{
string executableName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? "java.exe"
: "java";
? "javaw.exe"
: "javaw";
string fullPath = Path.Combine(
AppContext.BaseDirectory,
"Java",
JAVA_RUNTIME_FOLDER_NAME,
"bin",
executableName);
string runtimePath = _fileStore.GetOrCreate(FolderLocation.JavaRuntime);
string fullPath = Path.Combine(runtimePath, "bin", executableName);
if (!File.Exists(fullPath))
{
@@ -154,14 +152,130 @@ namespace AlayaCore.Services
return true;
}
private static bool IsMinecraftInstalled()
private InstalledVersionState GetInstalledVersionState()
{
return true;
string versionsPath = GetVersionsPath();
if (!Directory.Exists(versionsPath))
{
return InstalledVersionState.Empty();
}
private static bool IsNeoforgeInstalled()
string[] versionDirectories = Directory.GetDirectories(versionsPath);
if (versionDirectories.Length == 0)
{
return true;
return InstalledVersionState.Empty();
}
string? minecraftVersion = null;
string? neoForgeVersion = null;
foreach (string versionDirectory in versionDirectories)
{
string? versionFolderName = Path.GetFileName(versionDirectory);
if (string.IsNullOrWhiteSpace(versionFolderName))
{
continue;
}
string versionJsonPath = Path.Combine(versionDirectory, $"{versionFolderName}.json");
if (!File.Exists(versionJsonPath))
{
continue;
}
JObject versionJson = LoadJson(versionJsonPath);
string? id = versionJson.Value<string>("id");
string? inheritsFrom = versionJson.Value<string>("inheritsFrom");
if (string.IsNullOrWhiteSpace(id))
{
continue;
}
if (IsNeoForgeVersion(id, inheritsFrom))
{
neoForgeVersion = id;
if (string.IsNullOrWhiteSpace(minecraftVersion) && !string.IsNullOrWhiteSpace(inheritsFrom))
{
minecraftVersion = inheritsFrom;
}
continue;
}
if (string.IsNullOrWhiteSpace(minecraftVersion))
{
minecraftVersion = id;
}
}
return new InstalledVersionState(minecraftVersion, neoForgeVersion);
}
private string GetVersionsPath()
{
return Path.Combine(_fileStore.GetOrCreate(FolderLocation.Game), VersionsFolderName);
}
private static bool IsNeoForgeVersion(string? id, string? inheritsFrom)
{
return ContainsNeoForgeMarker(id) || ContainsNeoForgeMarker(inheritsFrom);
}
private static bool ContainsNeoForgeMarker(string? value)
{
if (string.IsNullOrWhiteSpace(value))
{
return false;
}
return value.Contains("neoforge", StringComparison.OrdinalIgnoreCase) ||
value.Contains("neoforged", StringComparison.OrdinalIgnoreCase);
}
private static JObject LoadJson(string path)
{
if (string.IsNullOrWhiteSpace(path))
{
throw new ArgumentException("Path cannot be null, empty, or whitespace.", nameof(path));
}
string json = File.ReadAllText(path);
if (string.IsNullOrWhiteSpace(json))
{
throw new InvalidDataException($"File '{path}' was empty.");
}
return JObject.Parse(json);
}
private sealed class InstalledVersionState
{
public string? MinecraftVersion { get; }
public string? NeoForgeVersion { get; }
public InstalledVersionState(string? minecraftVersion, string? neoForgeVersion)
{
MinecraftVersion = Normalize(minecraftVersion);
NeoForgeVersion = Normalize(neoForgeVersion);
}
public static InstalledVersionState Empty()
{
return new InstalledVersionState(null, null);
}
private static string? Normalize(string? value)
{
return string.IsNullOrWhiteSpace(value) ? null : value;
}
}
}
}

View File

@@ -1,24 +1,30 @@
using System;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using AlayaCore.Abstractions.Interfaces;
using AlayaCore.Abstractions.Interfaces.Services;
using AlayaCore.Installation;
using AlayaCore.Models.Manifests;
using AlayaCore.Utilities.Enums;
namespace AlayaCore.Services
{
public sealed class JavaService : IJavaService
{
private const string DOWNLOAD_FILE_NAME = "java-runtime.download";
private const string JAVA_INSTALL_FOLDER_NAME = "Java";
private const string JAVA_ARCHIVE_HASH_PLACEHOLDER = "REPLACE_WITH_MANIFEST_HASH_SUPPORT";
private const string JavaArchiveHashPlaceholder = "REPLACE_WITH_MANIFEST_HASH_SUPPORT";
private const string BaseUrl = "https://aka.ms/download-jdk/";
private readonly IDownloadService _downloadService;
private readonly IFileStore _fileStore;
public JavaService(IDownloadService downloadService)
public JavaService(IDownloadService downloadService, IFileStore fileStore)
{
_downloadService = downloadService ?? throw new ArgumentNullException(nameof(downloadService));
_fileStore = fileStore ?? throw new ArgumentNullException(nameof(fileStore));
}
public async Task EnsureValidJavaInstalledAsync(
@@ -38,26 +44,28 @@ namespace AlayaCore.Services
cancellationToken.ThrowIfCancellationRequested();
string installDirectory = GetJavaInstallDirectory();
if (environment.JavaInstalled &&
Directory.Exists(installDirectory) &&
string.Equals(environment.JavaVersion, manifest.RequiredJavaVersion, StringComparison.OrdinalIgnoreCase))
{
return;
}
if (environment.JavaInstalled && !string.IsNullOrWhiteSpace(environment.JavaPath))
if (Directory.Exists(installDirectory))
{
string javaRootFolder = ResolveJavaRootFolder(environment.JavaPath);
await RemoveJavaAsync(javaRootFolder, cancellationToken).ConfigureAwait(false);
await RemoveDirectoryAsync(installDirectory, cancellationToken).ConfigureAwait(false);
}
string downloadPath = GetJavaDownloadPath();
string installDirectory = GetJavaInstallDirectory();
Uri downloadUri = GetPlatformSpecificJavaUri(manifest.RequiredJavaVersion);
string downloadPath = GetJavaDownloadPath(downloadUri);
Directory.CreateDirectory(Path.GetDirectoryName(downloadPath) ?? AppContext.BaseDirectory);
Directory.CreateDirectory(installDirectory);
await DownloadJavaAsync(
manifest.RequiredJavaUrl,
downloadUri,
downloadPath,
cancellationToken).ConfigureAwait(false);
@@ -67,52 +75,100 @@ namespace AlayaCore.Services
cancellationToken).ConfigureAwait(false);
}
private static string GetJavaInstallDirectory()
public Uri GetPlatformSpecificJavaUri(string javaVersion)
{
return Path.Combine(AppContext.BaseDirectory, JAVA_INSTALL_FOLDER_NAME);
if (string.IsNullOrWhiteSpace(javaVersion))
{
throw new ArgumentException("Java version must be provided.", nameof(javaVersion));
}
private static string GetJavaDownloadPath()
{
return Path.Combine(AppContext.BaseDirectory, "Temp", DOWNLOAD_FILE_NAME);
string os = GetOsSegment();
string arch = GetArchitectureSegment();
string extension = GetFileExtension(os);
string fileName = $"microsoft-jdk-{javaVersion}-{os}-{arch}.{extension}";
return new Uri($"{BaseUrl}{fileName}");
}
private static string ResolveJavaRootFolder(string javaExecutablePath)
private string GetJavaInstallDirectory()
{
if (string.IsNullOrWhiteSpace(javaExecutablePath))
{
throw new ArgumentException("Java executable path cannot be null, empty, or whitespace.", nameof(javaExecutablePath));
return _fileStore.GetOrCreate(FolderLocation.Java);
}
string? binFolder = Path.GetDirectoryName(javaExecutablePath);
if (string.IsNullOrWhiteSpace(binFolder))
private string GetJavaDownloadPath(Uri downloadUri)
{
throw new InvalidOperationException("Could not resolve the Java bin directory.");
}
string? javaRootFolder = Path.GetDirectoryName(binFolder);
if (string.IsNullOrWhiteSpace(javaRootFolder))
if (downloadUri == null)
{
throw new InvalidOperationException("Could not resolve the Java installation directory.");
throw new ArgumentNullException(nameof(downloadUri));
}
return javaRootFolder;
string fileName = Path.GetFileName(downloadUri.AbsolutePath);
if (string.IsNullOrWhiteSpace(fileName))
{
throw new InvalidOperationException("Could not determine Java archive file name from download URI.");
}
private static Task RemoveJavaAsync(
string oldJavaFolder,
return Path.Combine(_fileStore.GetOrCreate(FolderLocation.Downloads), fileName);
}
private static string GetOsSegment()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return "windows";
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return "linux";
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return "macos";
}
throw new PlatformNotSupportedException("Unsupported operating system.");
}
private static string GetArchitectureSegment()
{
return RuntimeInformation.OSArchitecture switch
{
Architecture.X64 => "x64",
Architecture.Arm64 => "aarch64",
Architecture.X86 => throw new NotSupportedException("X86 is not supported."),
Architecture.Arm => throw new NotSupportedException("Arm32 is not supported."),
_ => throw new PlatformNotSupportedException(
$"Unsupported architecture: {RuntimeInformation.OSArchitecture}")
};
}
private static string GetFileExtension(string os)
{
return os switch
{
"windows" => "zip",
"linux" => "tar.gz",
"macos" => "tar.gz",
_ => throw new PlatformNotSupportedException($"Unsupported OS: {os}")
};
}
private static Task RemoveDirectoryAsync(
string directoryPath,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(oldJavaFolder))
if (string.IsNullOrWhiteSpace(directoryPath))
{
throw new ArgumentException("Old Java folder cannot be null, empty, or whitespace.", nameof(oldJavaFolder));
throw new ArgumentException("Directory path cannot be null, empty, or whitespace.", nameof(directoryPath));
}
cancellationToken.ThrowIfCancellationRequested();
if (Directory.Exists(oldJavaFolder))
if (Directory.Exists(directoryPath))
{
Directory.Delete(oldJavaFolder, recursive: true);
Directory.Delete(directoryPath, recursive: true);
}
return Task.CompletedTask;
@@ -140,22 +196,32 @@ namespace AlayaCore.Services
cancellationToken.ThrowIfCancellationRequested();
// Replace this when your manifest includes a Java archive/runtime hash.
string directory = Path.GetDirectoryName(destinationPath);
if (!string.IsNullOrWhiteSpace(directory))
{
Directory.CreateDirectory(directory);
}
if (File.Exists(destinationPath))
{
File.Delete(destinationPath);
}
await _downloadService.DownloadFileAsync(
javaUri,
destinationPath,
JAVA_ARCHIVE_HASH_PLACEHOLDER,
JavaArchiveHashPlaceholder,
cancellationToken: cancellationToken).ConfigureAwait(false);
}
private static Task InstallJavaAsync(
string installerPath,
private static async Task InstallJavaAsync(
string archivePath,
string installDirectory,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(installerPath))
if (string.IsNullOrWhiteSpace(archivePath))
{
throw new ArgumentException("Installer path cannot be null, empty, or whitespace.", nameof(installerPath));
throw new ArgumentException("Archive path cannot be null, empty, or whitespace.", nameof(archivePath));
}
if (string.IsNullOrWhiteSpace(installDirectory))
@@ -165,23 +231,87 @@ namespace AlayaCore.Services
cancellationToken.ThrowIfCancellationRequested();
if (!File.Exists(installerPath))
if (!File.Exists(archivePath))
{
throw new FileNotFoundException("Java installer or archive was not found.", installerPath);
throw new FileNotFoundException("Java archive was not found.", archivePath);
}
Directory.CreateDirectory(installDirectory);
// TODO:
// Implement this based on the actual Java package format.
//
// Examples:
// - If the file is a .zip, extract it into installDirectory
// - If it is a .tar.gz, unpack it appropriately
// - If it is an installer executable, launch it silently
//
// For now this is intentionally left explicit rather than guessing the wrong install strategy.
throw new NotImplementedException("InstallJavaAsync must be implemented for the actual Java package format.");
if (archivePath.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
{
ZipFile.ExtractToDirectory(archivePath, installDirectory);
return;
}
if (archivePath.EndsWith(".tar.gz", StringComparison.OrdinalIgnoreCase))
{
await ExtractTarGzAsync(archivePath, installDirectory, cancellationToken).ConfigureAwait(false);
return;
}
throw new NotSupportedException(
$"Unsupported Java archive format: {Path.GetFileName(archivePath)}");
}
private static async Task ExtractTarGzAsync(
string archivePath,
string destinationDirectory,
CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(archivePath))
{
throw new ArgumentException("Archive path cannot be null, empty, or whitespace.", nameof(archivePath));
}
if (string.IsNullOrWhiteSpace(destinationDirectory))
{
throw new ArgumentException("Destination directory cannot be null, empty, or whitespace.", nameof(destinationDirectory));
}
Directory.CreateDirectory(destinationDirectory);
var startInfo = new ProcessStartInfo
{
FileName = "tar",
Arguments = $"-xzf \"{archivePath}\" -C \"{destinationDirectory}\"",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
using var process = new Process { StartInfo = startInfo };
await using (cancellationToken.Register(() =>
{
try
{
if (!process.HasExited)
{
process.Kill();
}
}
catch
{
// Ignore race conditions if the process exits while cancellation is being handled.
}
}))
{
process.Start();
string standardOutput = await process.StandardOutput.ReadToEndAsync().ConfigureAwait(false);
string standardError = await process.StandardError.ReadToEndAsync().ConfigureAwait(false);
await Task.Run(() => process.WaitForExit(), cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
if (process.ExitCode != 0)
{
throw new InvalidOperationException(
$"tar extraction failed with exit code {process.ExitCode}. Error: {standardError}. Output: {standardOutput}");
}
}
}
}
}

View File

@@ -3,11 +3,13 @@ using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using AlayaCore.Abstractions.Interfaces;
using AlayaCore.Abstractions.Interfaces.Clients;
using AlayaCore.Abstractions.Interfaces.Services;
using AlayaCore.Models.Configuration;
using AlayaCore.Models.Manifests;
using AlayaCore.Models.Manifests.DTO;
using AlayaCore.Utilities.Enums;
using AlayaCore.Utilities.Extensions;
using Newtonsoft.Json;
@@ -21,15 +23,18 @@ namespace AlayaCore.Services
private readonly IDownloadService _downloadService;
private readonly IHttpClient _httpClient;
private readonly IFileStore _fileStore;
private readonly ManifestServiceOptions _options;
public ManifestService(
IDownloadService downloadService,
IHttpClient httpClient,
IFileStore fileStore,
ManifestServiceOptions options)
{
_downloadService = downloadService ?? throw new ArgumentNullException(nameof(downloadService));
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
_fileStore = fileStore ?? throw new ArgumentNullException(nameof(fileStore));
_options = options ?? throw new ArgumentNullException(nameof(options));
}
@@ -168,17 +173,17 @@ namespace AlayaCore.Services
public string GetLauncherManifestPath()
{
return Path.Combine(_options.ManifestDirectoryPath, LAUNCHER_MANIFEST_FILE_NAME);
return Path.Combine(_fileStore.GetOrCreate(FolderLocation.Manifests), LAUNCHER_MANIFEST_FILE_NAME);
}
public string GetCoreManifestPath()
{
return Path.Combine(_options.ManifestDirectoryPath, CORE_MANIFEST_FILE_NAME);
return Path.Combine(_fileStore.GetOrCreate(FolderLocation.Manifests), CORE_MANIFEST_FILE_NAME);
}
public string GetInstalledModsManifestPath()
{
return Path.Combine(_options.ManifestDirectoryPath, INSTALLED_MODS_MANIFEST_FILE_NAME);
return Path.Combine(_fileStore.GetOrCreate(FolderLocation.Manifests), INSTALLED_MODS_MANIFEST_FILE_NAME);
}
private async Task<TModel?> LoadLocalManifestAsync<TDto, TModel>(

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using AlayaCore.Abstractions.Interfaces;
using AlayaCore.Abstractions.Interfaces.Clients;
using AlayaCore.Abstractions.Interfaces.Services;
using AlayaCore.Installation;
@@ -21,23 +22,23 @@ namespace AlayaCore.Services
{
public sealed class ModService : IModService
{
private const string InstalledModsManifestFileName = "InstalledModsManifest.json";
private const string INSTALLED_MODS_MANIFEST_FILE_NAME = "InstalledModsManifest.json";
private readonly IDownloadService _downloadService;
private readonly ModrinthConnectionOptions _options;
private readonly ManifestServiceOptions _manifestOptions;
private readonly IHttpClient _httpClient;
private readonly IFileStore _fileStore;
public ModService(
IDownloadService downloadService,
ModrinthConnectionOptions options,
ManifestServiceOptions manifestOptions,
IHttpClient httpClient)
IHttpClient httpClient,
IFileStore fileStore)
{
_downloadService = downloadService ?? throw new ArgumentNullException(nameof(downloadService));
_options = options ?? throw new ArgumentNullException(nameof(options));
_manifestOptions = manifestOptions ?? throw new ArgumentNullException(nameof(manifestOptions));
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
_fileStore = fileStore ?? throw new ArgumentNullException(nameof(fileStore));
}
public async Task ProcessModsAsync(
@@ -238,7 +239,7 @@ namespace AlayaCore.Services
return $"{baseUrl}/version_file/{sha512Hash}";
}
private static string GetModDestinationPath(ModFileEntry fileEntry)
private string GetModDestinationPath(ModFileEntry fileEntry)
{
if (fileEntry == null)
{
@@ -251,17 +252,16 @@ namespace AlayaCore.Services
}
string modsDirectory = GetModsDirectoryPath();
Directory.CreateDirectory(modsDirectory);
return Path.Combine(modsDirectory, fileEntry.FileName);
}
private static string GetModsDirectoryPath()
private string GetModsDirectoryPath()
{
return Path.Combine(AppContext.BaseDirectory, "Game", "mods");
return _fileStore.GetOrCreate(FolderLocation.Mods);
}
private static void RemoveStaleMods(IEnumerable<ModFileEntry> requiredMods)
private void RemoveStaleMods(IEnumerable<ModFileEntry> requiredMods)
{
if (requiredMods == null)
{
@@ -269,6 +269,7 @@ namespace AlayaCore.Services
}
string modsDirectory = GetModsDirectoryPath();
if (!Directory.Exists(modsDirectory))
{
return;
@@ -302,10 +303,9 @@ namespace AlayaCore.Services
List<ModFileEntry> entries = installedMods.ToList();
InstalledModsManifestModel manifest = new InstalledModsManifestModel(entries);
string manifestsDirectory = _manifestOptions.ManifestDirectoryPath;
Directory.CreateDirectory(manifestsDirectory);
string manifestsDirectory = _fileStore.GetOrCreate(FolderLocation.Manifests);
string manifestPath = Path.Combine(manifestsDirectory, InstalledModsManifestFileName);
string manifestPath = Path.Combine(manifestsDirectory, INSTALLED_MODS_MANIFEST_FILE_NAME);
string temporaryManifestPath = manifestPath + ".tmp";
InstalledModsManifestDto dto = manifest.ToDto();

View File

@@ -2,21 +2,30 @@ using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using AlayaCore.Abstractions.Configuration;
using AlayaCore.Abstractions.Interfaces;
using AlayaCore.Abstractions.Interfaces.Services;
using AlayaCore.Models.Configuration;
using AlayaCore.Utilities.Enums;
using Newtonsoft.Json;
namespace AlayaCore.Services
{
public sealed class SettingsService : ISettingsService
{
private const string LauncherSettingsFileName = "Launcher.json";
private readonly IFileStore _fileStore;
public LauncherOptions LauncherOptions { get; }
public GameOptions GameOptions { get; }
public SettingsService(LauncherOptions launcherOptions)
public SettingsService(
LauncherOptions launcherOptions,
GameOptions gameOptions,
IFileStore fileStore)
{
LauncherOptions = launcherOptions ?? throw new ArgumentNullException(nameof(launcherOptions));
GameOptions = gameOptions ?? throw new ArgumentNullException(nameof(gameOptions));
_fileStore = fileStore ?? throw new ArgumentNullException(nameof(fileStore));
}
public async Task SetForceReinstallAsync(bool value, CancellationToken cancellationToken = default)
@@ -25,10 +34,16 @@ namespace AlayaCore.Services
await SaveLauncherOptionsAsync(cancellationToken).ConfigureAwait(false);
}
public async Task UpdateLaunchVersionAsync(string newVersion, CancellationToken cancellationToken = default)
{
GameOptions.LaunchVersion = newVersion ?? string.Empty;
await SaveGameOptionsAsync(cancellationToken).ConfigureAwait(false);
}
public async Task SaveLauncherOptionsAsync(CancellationToken cancellationToken = default)
{
await SaveAsync(
LauncherSettingsFileName,
LauncherOptions.FileName,
LauncherOptions,
cancellationToken).ConfigureAwait(false);
}
@@ -36,7 +51,7 @@ namespace AlayaCore.Services
public async Task LoadLauncherOptionsAsync(CancellationToken cancellationToken = default)
{
LauncherOptions? loadedOptions = await LoadAsync<LauncherOptions>(
LauncherSettingsFileName,
LauncherOptions.FileName,
cancellationToken).ConfigureAwait(false);
if (loadedOptions == null)
@@ -47,10 +62,49 @@ namespace AlayaCore.Services
LauncherOptions.ForceReinstall = loadedOptions.ForceReinstall;
}
public async Task SaveGameOptionsAsync(CancellationToken cancellationToken = default)
{
await SaveAsync(
GameOptions.FileName,
GameOptions,
cancellationToken).ConfigureAwait(false);
}
public async Task LoadGameOptionsAsync(CancellationToken cancellationToken = default)
{
GameOptions? loadedOptions = await LoadAsync<GameOptions>(
GameOptions.FileName,
cancellationToken).ConfigureAwait(false);
if (loadedOptions == null)
{
return;
}
GameOptions.LaunchVersion = loadedOptions.LaunchVersion;
GameOptions.MinimumRamMB = loadedOptions.MinimumRamMB;
GameOptions.MaximumRamMB = loadedOptions.MaximumRamMB;
GameOptions.ScreenWidth = loadedOptions.ScreenWidth;
GameOptions.ScreenHeight = loadedOptions.ScreenHeight;
GameOptions.Fullscreen = loadedOptions.Fullscreen;
}
public async Task LoadAllAsync(CancellationToken cancellationToken = default)
{
await LoadLauncherOptionsAsync(cancellationToken).ConfigureAwait(false);
await LoadGameOptionsAsync(cancellationToken).ConfigureAwait(false);
}
public async Task SaveAllAsync(CancellationToken cancellationToken = default)
{
await SaveLauncherOptionsAsync(cancellationToken).ConfigureAwait(false);
await SaveGameOptionsAsync(cancellationToken).ConfigureAwait(false);
}
private async Task SaveAsync<T>(
string fileName,
T value,
CancellationToken cancellationToken)
CancellationToken cancellationToken) where T : BaseConfig
{
if (string.IsNullOrWhiteSpace(fileName))
{
@@ -89,7 +143,7 @@ namespace AlayaCore.Services
private async Task<T?> LoadAsync<T>(
string fileName,
CancellationToken cancellationToken)
CancellationToken cancellationToken) where T : BaseConfig
{
if (string.IsNullOrWhiteSpace(fileName))
{
@@ -124,9 +178,9 @@ namespace AlayaCore.Services
}
}
private static string GetFullPath(string fileName)
private string GetFullPath(string fileName)
{
return Path.Combine(AppContext.BaseDirectory, "Config", fileName);
return Path.Combine(_fileStore.GetOrCreate(FolderLocation.Config), fileName);
}
}
}

View File

@@ -4,6 +4,7 @@ namespace AlayaCore.States
{
Ready,
LauncherNeedsUpdate,
NeedAuthenticating,
InstallJava,
InstallMinecraft,
InstallNeoforge,
@@ -17,35 +18,46 @@ namespace AlayaCore.States
public bool MinecraftNeedsInstallOrUpdate { get; }
public bool NeoforgeNeedsInstallOrUpdate { get; }
public bool ModsNeedSync { get; }
public bool NeedAuthenticating { get; }
public LaunchState State => ComputeState();
public bool CanRun =>
State == LaunchState.Ready;
public bool CanRun => State == LaunchState.Ready;
public bool NeedsUpdating =>
State != LaunchState.Ready;
LauncherNeedsUpdate ||
JavaNeedsInstallOrUpdate ||
MinecraftNeedsInstallOrUpdate ||
NeoforgeNeedsInstallOrUpdate ||
ModsNeedSync;
public bool NeedsAttention =>
NeedsUpdating || NeedAuthenticating;
public LaunchPlan(
bool launcherNeedsUpdate,
bool javaNeedsInstallOrUpdate,
bool minecraftNeedsInstallOrUpdate,
bool neoforgeNeedsInstallOrUpdate,
bool modsNeedSync)
bool modsNeedSync,
bool needAuthenticating)
{
LauncherNeedsUpdate = launcherNeedsUpdate;
JavaNeedsInstallOrUpdate = javaNeedsInstallOrUpdate;
MinecraftNeedsInstallOrUpdate = minecraftNeedsInstallOrUpdate;
NeoforgeNeedsInstallOrUpdate = neoforgeNeedsInstallOrUpdate;
ModsNeedSync = modsNeedSync;
NeedAuthenticating = needAuthenticating;
}
private LaunchState ComputeState()
{
// Priority order matters a LOT here
if (LauncherNeedsUpdate)
return LaunchState.LauncherNeedsUpdate;
if (NeedAuthenticating)
return LaunchState.NeedAuthenticating;
if (JavaNeedsInstallOrUpdate)
return LaunchState.InstallJava;
@@ -68,7 +80,8 @@ namespace AlayaCore.States
javaNeedsInstallOrUpdate: false,
minecraftNeedsInstallOrUpdate: false,
neoforgeNeedsInstallOrUpdate: false,
modsNeedSync: false);
modsNeedSync: false,
needAuthenticating: false);
}
}
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Globalization;
using Newtonsoft.Json;
namespace AlayaCore.Utilities.Converters
@@ -12,9 +11,9 @@ namespace AlayaCore.Utilities.Converters
{
writer.WriteNull();
}
else if (value is Uri)
else if (value is Uri uri)
{
writer.WriteValue(((Uri)value).AbsoluteUri);
writer.WriteValue(uri.AbsoluteUri);
}
else
{
@@ -33,8 +32,7 @@ namespace AlayaCore.Utilities.Converters
{
try
{
Uri uri = new Uri((string)reader.Value!);
return uri;
return new Uri((string)reader.Value!, UriKind.Absolute);
}
catch (Exception ex)
{
@@ -47,7 +45,7 @@ namespace AlayaCore.Utilities.Converters
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Uri);
return typeof(Uri).IsAssignableFrom(objectType);
}
}
}

View File

@@ -1,7 +1,17 @@
namespace AlayaCore.Utilities.Enums
{
public class FolderLocation
public enum FolderLocation
{
BaseDirectory,
Java,
JavaRuntime,
Game,
Mods,
ResourcePacks,
Config,
Downloads,
Manifests,
Plugins,
Data
}
}

View File

@@ -50,9 +50,9 @@ namespace AlayaCore.Utilities.Extensions
dto.RequiredJavaVersion,
dto.RequiredJavaUrl,
dto.MinecraftVersion,
dto.MinecraftUrl,
dto.NeoforgedVersion,
dto.NeoforgedUrl,
dto.ServerUrl,
dto.ServerPort,
dto.Files?.Select(file => file.ToModel()) ?? Array.Empty<ModFileEntry>());
}
@@ -81,11 +81,9 @@ namespace AlayaCore.Utilities.Extensions
{
AlayaVersion = model.AlayaVersion,
RequiredJavaVersion = model.RequiredJavaVersion,
RequiredJavaUrl = model.RequiredJavaUrl,
RequiredJavaUrl = model.RequiredJavaBaseUrl,
MinecraftVersion = model.MinecraftVersion,
MinecraftUrl = model.MinecraftUrl,
NeoforgedVersion = model.NeoforgedVersion,
NeoforgedUrl = model.NeoforgedUrl,
Files = model.Files.Select(file => file.ToDto()).ToList()
};
}

View File

@@ -6,20 +6,86 @@ using AlayaCore.Utilities.Enums;
namespace AlayaCore.Utilities.Stores
{
public class FileStore : IFileStore
public sealed class LocalFileStore : IFileStore
{
private static readonly string _baseDirectory = AppContext.BaseDirectory;
private static readonly string _javaDirectory = Path.Combine(_baseDirectory, "Java");
private static readonly string BaseDirectoryPath = AppContext.BaseDirectory;
private static readonly string JavaDirectoryPath = Path.Combine(BaseDirectoryPath, "Java");
private static readonly string JavaRuntimeDirectoryPath = Path.Combine(JavaDirectoryPath, "java-runtime-epsilon");
private static readonly string GameDirectoryPath = Path.Combine(BaseDirectoryPath, "Game");
private static readonly string ModsDirectoryPath = Path.Combine(GameDirectoryPath, "mods");
private static readonly string ResourcePacksDirectoryPath = Path.Combine(GameDirectoryPath, "resourcepacks");
private static readonly string ConfigDirectoryPath = Path.Combine(BaseDirectoryPath, "Config");
private static readonly string DownloadsDirectoryPath = Path.Combine(BaseDirectoryPath, "Temp");
private static readonly string ManifestsDirectoryPath = Path.Combine(BaseDirectoryPath, "Manifests");
private static readonly string PluginsDirectoryPath = Path.Combine(GameDirectoryPath, "plugins");
private static readonly string DataDirectoryPath = Path.Combine(BaseDirectoryPath, "Data");
private static readonly Dictionary<FolderLocation, string> _folders = new()
private static readonly IReadOnlyDictionary<FolderLocation, string> Folders =
new Dictionary<FolderLocation, string>
{
{ FolderLocation.BaseDirectory, _baseDirectory },
{ FolderLocation.Java, _javaDirectory}
{ FolderLocation.BaseDirectory, BaseDirectoryPath },
{ FolderLocation.Java, JavaDirectoryPath },
{ FolderLocation.JavaRuntime, JavaRuntimeDirectoryPath },
{ FolderLocation.Game, GameDirectoryPath },
{ FolderLocation.Mods, ModsDirectoryPath },
{ FolderLocation.ResourcePacks, ResourcePacksDirectoryPath },
{ FolderLocation.Config, ConfigDirectoryPath },
{ FolderLocation.Downloads, DownloadsDirectoryPath },
{ FolderLocation.Manifests, ManifestsDirectoryPath},
{ FolderLocation.Plugins, PluginsDirectoryPath },
{ FolderLocation.Data, DataDirectoryPath}
};
public string Get(FolderLocation location)
{
return _folders[location];
if (!Folders.TryGetValue(location, out string? path))
{
throw new ArgumentOutOfRangeException(nameof(location), location, "Unknown folder location.");
}
return path;
}
public string GetOrCreate(FolderLocation location)
{
string path = Get(location);
Directory.CreateDirectory(path);
return path;
}
public string Combine(FolderLocation location, params string[] paths)
{
if (paths == null)
{
throw new ArgumentNullException(nameof(paths));
}
string rootPath = Get(location);
if (paths.Length == 0)
{
return rootPath;
}
string[] combinedPaths = new string[paths.Length + 1];
combinedPaths[0] = rootPath;
for (int i = 0; i < paths.Length; i++)
{
if (string.IsNullOrWhiteSpace(paths[i]))
{
throw new ArgumentException("Path segments cannot be null, empty, or whitespace.", nameof(paths));
}
combinedPaths[i + 1] = paths[i];
}
return Path.Combine(combinedPaths);
}
public bool Exists(FolderLocation location)
{
return Directory.Exists(Get(location));
}
}
}

View File

@@ -44,9 +44,25 @@
"netstandard2.1": {
"targetAlias": "netstandard2.1",
"dependencies": {
"CmlLib.Core": {
"target": "Package",
"version": "[4.0.6, )"
},
"CmlLib.Core.Auth.Microsoft": {
"target": "Package",
"version": "[3.3.1, )"
},
"CmlLib.Core.Installer.NeoForge": {
"target": "Package",
"version": "[4.0.0, )"
},
"Newtonsoft.Json": {
"target": "Package",
"version": "[13.0.4, )"
},
"XboxAuthNet.Game.Msal": {
"target": "Package",
"version": "[0.1.3, )"
}
},
"imports": [

View File

@@ -1,2 +1,7 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" />
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)system.text.json/8.0.5/buildTransitive/netstandard2.0/System.Text.Json.targets" Condition="Exists('$(NuGetPackageRoot)system.text.json/8.0.5/buildTransitive/netstandard2.0/System.Text.Json.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/8.0.1/buildTransitive/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions/8.0.1/buildTransitive/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.targets')" />
</ImportGroup>
</Project>

View File

@@ -13,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("AlayaCore")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+823ccf4b877ddf1fc1293f72c6b704d6a449ddaa")]
[assembly: System.Reflection.AssemblyProductAttribute("AlayaCore")]
[assembly: System.Reflection.AssemblyTitleAttribute("AlayaCore")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

View File

@@ -1 +1 @@
02d72db7e7301cd87568f68f39a003b2484df3c699c3ceed1d9bd04f25f6e3dd
c63b3bf6f0fd998ece1d911c58e61eb584840e2e19d535b62f67f54b1510e49e

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,40 @@
{
"version": 2,
"dgSpecHash": "iqHG7z//q/I=",
"dgSpecHash": "pXFuC0zr6Zg=",
"success": true,
"projectFilePath": "/Users/ryanmacham/Documents/Coding/AlayaCore/AlayaCore/AlayaCore.csproj",
"expectedPackageFiles": [
"/Users/ryanmacham/.nuget/packages/cmllib.core/4.0.6/cmllib.core.4.0.6.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/cmllib.core.auth.microsoft/3.3.1/cmllib.core.auth.microsoft.3.3.1.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/cmllib.core.commons/4.0.0/cmllib.core.commons.4.0.0.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/cmllib.core.installer.neoforge/4.0.0/cmllib.core.installer.neoforge.4.0.0.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/htmlagilitypack/1.11.48/htmlagilitypack.1.11.48.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/lzma-sdk/19.0.0/lzma-sdk.19.0.0.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/microsoft.bcl.asyncinterfaces/8.0.0/microsoft.bcl.asyncinterfaces.8.0.0.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/microsoft.extensions.dependencyinjection.abstractions/8.0.1/microsoft.extensions.dependencyinjection.abstractions.8.0.1.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/microsoft.extensions.logging.abstractions/8.0.1/microsoft.extensions.logging.abstractions.8.0.1.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/microsoft.identity.client/4.61.3/microsoft.identity.client.4.61.3.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/microsoft.identity.client.extensions.msal/4.61.3/microsoft.identity.client.extensions.msal.4.61.3.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/microsoft.identitymodel.abstractions/6.35.0/microsoft.identitymodel.abstractions.6.35.0.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/newtonsoft.json/13.0.4/newtonsoft.json.13.0.4.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/sharpziplib/1.4.2/sharpziplib.1.4.2.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/system.buffers/4.5.1/system.buffers.4.5.1.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/system.diagnostics.diagnosticsource/6.0.1/system.diagnostics.diagnosticsource.6.0.1.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/system.io.filesystem.accesscontrol/5.0.0/system.io.filesystem.accesscontrol.5.0.0.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/system.memory/4.5.5/system.memory.4.5.5.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/system.net.http.json/8.0.0/system.net.http.json.8.0.0.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/system.numerics.vectors/4.4.0/system.numerics.vectors.4.4.0.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/system.runtime.compilerservices.unsafe/6.0.0/system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/system.security.accesscontrol/5.0.0/system.security.accesscontrol.5.0.0.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/system.security.cryptography.protecteddata/4.5.0/system.security.cryptography.protecteddata.4.5.0.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/system.security.principal.windows/5.0.0/system.security.principal.windows.5.0.0.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/system.text.encodings.web/8.0.0/system.text.encodings.web.8.0.0.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/system.text.json/8.0.5/system.text.json.8.0.5.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/system.threading.tasks.dataflow/8.0.1/system.threading.tasks.dataflow.8.0.1.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/system.threading.tasks.extensions/4.5.4/system.threading.tasks.extensions.4.5.4.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/xboxauthnet/3.0.4/xboxauthnet.3.0.4.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/xboxauthnet.game/1.4.1/xboxauthnet.game.1.4.1.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/xboxauthnet.game.msal/0.1.3/xboxauthnet.game.msal.0.1.3.nupkg.sha512",
"/Users/ryanmacham/.nuget/packages/netstandard.library.ref/2.1.0/netstandard.library.ref.2.1.0.nupkg.sha512"
],
"logs": []

View File

@@ -1 +1 @@
"restore":{"projectUniqueName":"/Users/ryanmacham/Documents/Coding/AlayaCore/AlayaCore/AlayaCore.csproj","projectName":"AlayaCore","projectPath":"/Users/ryanmacham/Documents/Coding/AlayaCore/AlayaCore/AlayaCore.csproj","packagesPath":"","outputPath":"/Users/ryanmacham/Documents/Coding/AlayaCore/AlayaCore/obj/","projectStyle":"PackageReference","originalTargetFrameworks":["netstandard2.1"],"sources":{"https://api.nuget.org/v3/index.json":{}},"frameworks":{"netstandard2.1":{"targetAlias":"netstandard2.1","projectReferences":{}}},"warningProperties":{"warnAsError":["NU1605"]},"restoreAuditProperties":{"enableAudit":"true","auditLevel":"low","auditMode":"direct"},"SdkAnalysisLevel":"10.0.200"}"frameworks":{"netstandard2.1":{"targetAlias":"netstandard2.1","dependencies":{"Newtonsoft.Json":{"target":"Package","version":"[13.0.4, )"}},"imports":["net461","net462","net47","net471","net472","net48","net481"],"assetTargetFallback":true,"warn":true,"downloadDependencies":[{"name":"NETStandard.Library.Ref","version":"[2.1.0, 2.1.0]"}],"frameworkReferences":{"NETStandard.Library":{"privateAssets":"all"}},"runtimeIdentifierGraphPath":"/usr/local/share/dotnet/sdk/10.0.201/RuntimeIdentifierGraph.json"}}
"restore":{"projectUniqueName":"/Users/ryanmacham/Documents/Coding/AlayaCore/AlayaCore/AlayaCore.csproj","projectName":"AlayaCore","projectPath":"/Users/ryanmacham/Documents/Coding/AlayaCore/AlayaCore/AlayaCore.csproj","packagesPath":"","outputPath":"/Users/ryanmacham/Documents/Coding/AlayaCore/AlayaCore/obj/","projectStyle":"PackageReference","originalTargetFrameworks":["netstandard2.1"],"sources":{"https://api.nuget.org/v3/index.json":{}},"frameworks":{"netstandard2.1":{"targetAlias":"netstandard2.1","projectReferences":{}}},"warningProperties":{"warnAsError":["NU1605"]},"restoreAuditProperties":{"enableAudit":"true","auditLevel":"low","auditMode":"direct"},"SdkAnalysisLevel":"10.0.200"}"frameworks":{"netstandard2.1":{"targetAlias":"netstandard2.1","dependencies":{"CmlLib.Core":{"target":"Package","version":"[4.0.6, )"},"CmlLib.Core.Auth.Microsoft":{"target":"Package","version":"[3.3.1, )"},"CmlLib.Core.Installer.NeoForge":{"target":"Package","version":"[4.0.0, )"},"Newtonsoft.Json":{"target":"Package","version":"[13.0.4, )"},"XboxAuthNet.Game.Msal":{"target":"Package","version":"[0.1.3, )"}},"imports":["net461","net462","net47","net471","net472","net48","net481"],"assetTargetFallback":true,"warn":true,"downloadDependencies":[{"name":"NETStandard.Library.Ref","version":"[2.1.0, 2.1.0]"}],"frameworkReferences":{"NETStandard.Library":{"privateAssets":"all"}},"runtimeIdentifierGraphPath":"/usr/local/share/dotnet/sdk/10.0.201/RuntimeIdentifierGraph.json"}}

View File

@@ -1 +1 @@
17752277078247393
17754069520908862

View File

@@ -1 +1 @@
17752277078247393
17754069520908862