Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 23 additions & 8 deletions Emerald.CoreX/Core.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
public partial class Core(ILogger<Core> _logger, INotificationService _notify, IBaseSettingsService settingsService) : ObservableObject
{
public const string GamesFolderName = "EmeraldGames";
public MinecraftLauncher Launcher { get; set; }

Check warning on line 21 in Emerald.CoreX/Core.cs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest)

Non-nullable property 'Launcher' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

public event EventHandler? VersionsRefreshed;

public bool IsRunning { get; set; } = false;
public MinecraftPath? BasePath { get; private set; } = null;
public bool IsOfflineMode { get; private set; } = false;
Expand All @@ -31,6 +33,9 @@
[ObservableProperty]
private bool _initialized = false;

[ObservableProperty]
private bool _isRefreshing = false;

public Models.GameSettings GameOptions = new();

private SavedGameCollection[] SavedgamesWithPaths = [];
Expand All @@ -50,7 +55,7 @@
Directory.CreateDirectory(gamesFolder);
}

SavedgamesWithPaths = settingsService.Get<SavedGameCollection[]>(SettingsKeys.SavedGames, [], true);
SavedgamesWithPaths = settingsService.Get<SavedGameCollection[]>(SettingsKeys.SavedGames, []);

var collection = SavedgamesWithPaths.FirstOrDefault(x => x.BasePath == BasePath.BasePath);
if (collection == null)
Expand Down Expand Up @@ -90,7 +95,7 @@
list.Add(new SavedGameCollection(BasePath.BasePath, toSave));

SavedgamesWithPaths = list.ToArray();
settingsService.Set(SettingsKeys.SavedGames, SavedgamesWithPaths, true);
settingsService.Set(SettingsKeys.SavedGames, SavedgamesWithPaths);

_logger.LogInformation("Saved {count} games", toSave.Length);
}
Expand All @@ -101,7 +106,6 @@
}
}


/// <summary>
/// Initializes the Core with the given Minecraft path and retrieves the list of available vanilla Minecraft versions.
/// </summary>
Expand All @@ -114,6 +118,7 @@
isIndeterminate: true,
isCancellable: true
);
IsRefreshing = true;
try
{
GameOptions = settingsService.Get("BaseGameOptions", Models.GameSettings.FromMLaunchOption(new()));
Expand All @@ -136,8 +141,9 @@
var l = await Launcher.GetAllVersionsAsync(not.CancellationToken.Value);

VanillaVersions.Clear();
VanillaVersions.AddRange(l.Select(x => new Versions.Version() { Metadata = x, BasedOn = x.Name, ReleaseType = x.Type }));
VanillaVersions.AddRange(l.Select(x => new Versions.Version() { ReleaseTime = x.ReleaseTime.DateTime, BasedOn = x.Name, ReleaseType = x.Type }));
IsOfflineMode = false;
_notify.Complete(not.Id, true);
}
catch (HttpRequestException)
{
Expand All @@ -150,7 +156,16 @@
_notify.Complete(not.Id, false, ex.Message, ex);
Initialized = false;
}
_logger.LogInformation("Loaded {count} vanilla versions", VanillaVersions.Count);
finally
{
foreach (var game in Games)
{
game.CreateMCLauncher(IsOfflineMode);
}
_logger.LogInformation("Loaded {count} vanilla versions", VanillaVersions.Count);
IsRefreshing = false;
VersionsRefreshed?.Invoke(this, new());
}
}

/// <summary>
Expand All @@ -165,7 +180,6 @@

try
{

_logger.LogInformation("Installing game {version}", version.BasedOn);

if(game == null)
Expand All @@ -178,7 +192,8 @@
isOffline: IsOfflineMode,
showFileProgress: showFileprog
);


SaveGames();
}
catch (Exception ex)
{
Expand All @@ -194,9 +209,9 @@

var path = Path.Combine( BasePath.BasePath, GamesFolderName, version.DisplayName);


var game = new Game(new(path), GameOptions, version);

Check notice on line 212 in Emerald.CoreX/Core.cs

View check run for this annotation

codefactor.io / CodeFactor

Emerald.CoreX/Core.cs#L212

Code should not contain multiple blank lines in a row. (SA1507)


Games.Add(game);
SaveGames();

Expand Down
37 changes: 27 additions & 10 deletions Emerald.CoreX/Game.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,8 @@ public Game(MinecraftPath path, Models.GameSettings options, Versions.Version ve
_logger.LogInformation("Game instance created with path: {Path} and options: {Options}", path, options);
}

/// <summary>
/// Installs the specified Minecraft version, including downloading necessary files
/// and handling both online and offline modes.
/// </summary>
/// <param name="isOffline">Indicates whether the installation is performed in offline mode, bypassing online resources.</param>
/// <param name="showFileProgress">Determines whether detailed file progress information is displayed during the installation process.</param>
/// <returns>A task that represents the asynchronous installation operation.</returns>
public async Task InstallVersion(bool isOffline = false, bool showFileProgress = false)
public void CreateMCLauncher(bool isOffline)
{
_logger.LogInformation("Starting InstallVersion with isOffline: {IsOffline}, showFileProgress: {ShowFileProgress}", isOffline, showFileProgress);
var param = MinecraftLauncherParameters.CreateDefault(Path);

if (isOffline)
Expand All @@ -59,8 +51,20 @@ public async Task InstallVersion(bool isOffline = false, bool showFileProgress =
}

Launcher = new MinecraftLauncher(param);
}
/// <summary>
/// Installs the specified Minecraft version, including downloading necessary files
/// and handling both online and offline modes.
/// </summary>
/// <param name="isOffline">Indicates whether the installation is performed in offline mode, bypassing online resources.</param>
/// <param name="showFileProgress">Determines whether detailed file progress information is displayed during the installation process.</param>
/// <returns>A task that represents the asynchronous installation operation.</returns>
public async Task InstallVersion(bool isOffline = false, bool showFileProgress = false)
{
_logger.LogInformation("Starting InstallVersion with isOffline: {IsOffline}, showFileProgress: {ShowFileProgress}", isOffline, showFileProgress);
CreateMCLauncher(isOffline);

var not = _notify.Create(
var not = _notify.Create(
"Initializing Version",
$"Initializing {Version.Type} version {Version.DisplayName}",
0,
Expand Down Expand Up @@ -101,6 +105,19 @@ public async Task InstallVersion(bool isOffline = false, bool showFileProgress =
}
}

Version.RealVersion = ver;

if (isOffline) //checking if verison actually exists
{
var vers = await Launcher.GetAllVersionsAsync();
var mver = vers.Where(x => x.Name == ver).First();
if (mver == null)
{
_logger.LogWarning("Version {Version} not found in offline mode. Can't proceed installation.", ver);
throw new NullReferenceException($"Version {ver} not found in offline mode. Can't proceed installation.");
}
}

(string Files, string bytes, double prog) prog = (string.Empty, string.Empty, 0);

await Launcher.InstallAsync(
Expand Down
93 changes: 43 additions & 50 deletions Emerald.CoreX/Helpers/BaseSettingsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
using System.Text.Json;
using System;
using System.IO;
using System.Threading.Tasks;
using Windows.Storage;

namespace Emerald.Services;

Expand All @@ -15,91 +13,86 @@ public class BaseSettingsService : IBaseSettingsService
WriteIndented = true
};

private readonly string _settingsFolder;

public event EventHandler<string>? APINoMatch;

public BaseSettingsService(ILogger<BaseSettingsService> logger)
{
_logger = logger;

// Use the LocalFolder path as the base folder for file-based settings
_settingsFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Emerald", "Settings");

// Ensure the directory exists immediately
if (!Directory.Exists(_settingsFolder))
{
Directory.CreateDirectory(_settingsFolder);
}
}

public void Set<T>(string key, T value, bool storeInFile = false)
public void Set<T>(string key, T value)
{
try
{
if (storeInFile)
{
SaveToFileAsync(key, value).Wait();
}
else
{
string json = JsonSerializer.Serialize(value, _jsonOptions);
ApplicationData.Current.LocalSettings.Values[key] = json;
}
SaveToFile(key, value);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error saving key '{Key}'", key);
// fallback: if in-memory save failed, try file once
if (!storeInFile)
{
try { SaveToFileAsync(key, value).Wait(); }
catch (Exception fileEx) { _logger.LogError(fileEx, "Fallback file save failed for '{Key}'", key); }
}
_logger.LogError(ex, "Error saving key '{Key}' to file.", key);
}
}

public T Get<T>(string key, T defaultVal, bool loadFromFile = false)
public T Get<T>(string key, T defaultVal)
{
try
{
if (loadFromFile)
{
return LoadFromFileAsync(key, defaultVal).GetAwaiter().GetResult();
}
else if (ApplicationData.Current.LocalSettings.Values.TryGetValue(key, out object? value)
&& value is string json
&& !string.IsNullOrWhiteSpace(json))
{
return JsonSerializer.Deserialize<T>(json) ?? defaultVal;
}
return LoadFromFile(key, defaultVal);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error loading key '{Key}'", key);
// fallback: if in-memory load failed, try file once
if (!loadFromFile)
_logger.LogError(ex, "Error loading key '{Key}' from file.", key);

// If load fails, try to persist the default so the file is corrected for next time
try
{
try { return LoadFromFileAsync(key, defaultVal).GetAwaiter().GetResult(); }
catch (Exception fileEx) { _logger.LogError(fileEx, "Fallback file load failed for '{Key}'", key); }
Set(key, defaultVal);
}
catch (Exception writeEx)
{
_logger.LogError(writeEx, "Could not write default value for '{Key}' after load failure.", key);
}
}

// if all else fails, persist default so next time there's a valid value
Set(key, defaultVal, storeInFile: loadFromFile);
return defaultVal;
return defaultVal;
}
}

private async Task SaveToFileAsync<T>(string key, T value)
private void SaveToFile<T>(string key, T value)
{
string fileName = $"{key}.json";
StorageFile file = await ApplicationData.Current.LocalFolder.CreateFileAsync(
fileName, CreationCollisionOption.ReplaceExisting);

string filePath = Path.Combine(_settingsFolder, $"{key}.json");
string json = JsonSerializer.Serialize(value, _jsonOptions);
await FileIO.WriteTextAsync(file, json);
File.WriteAllText(filePath, json);
}

private async Task<T> LoadFromFileAsync<T>(string key, T defaultVal)
private T LoadFromFile<T>(string key, T defaultVal)
{
string filePath = Path.Combine(_settingsFolder, $"{key}.json");

if (!File.Exists(filePath))
{
// If the file doesn't exist, create it with the default value immediately
Set(key, defaultVal);
return defaultVal;
}

try
{
string fileName = $"{key}.json";
StorageFile file = await ApplicationData.Current.LocalFolder.GetFileAsync(fileName);
string json = await FileIO.ReadTextAsync(file);
string json = File.ReadAllText(filePath);
return JsonSerializer.Deserialize<T>(json) ?? defaultVal;
}
catch (FileNotFoundException)
catch (JsonException jsonEx)
{
_logger.LogError(jsonEx, "Corrupted JSON for key '{Key}'. Returning default.", key);
return defaultVal;
}
}
Expand Down
2 changes: 0 additions & 2 deletions Emerald.CoreX/Installers/Fabric.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ public async Task<string> InstallAsync(MinecraftPath path, string mcversion, str
this.Log().LogInformation("Installing Fabric Loader for {mcversion}", mcversion);
try
{

var fabricInstaller = new FabricInstaller(new HttpClient());

if (!online)
Expand All @@ -72,7 +71,6 @@ public async Task<string> InstallAsync(MinecraftPath path, string mcversion, str
return FabricInstaller.GetVersionName(mcversion, modversion ?? (await fabricInstaller.GetFirstLoader(mcversion))?.Version ?? throw new NullReferenceException("No internet and no mod name found."));
}


string? versionName = null;

if (modversion == null)
Expand Down
2 changes: 0 additions & 2 deletions Emerald.CoreX/Installers/Quilt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ public async Task<string> InstallAsync(MinecraftPath path, string mcversion, str
this.Log().LogInformation("Installing Quilt Loader for {mcversion}", mcversion);
try
{

var QuiltInstaller = new QuiltInstaller(new HttpClient());

if (!online)
Expand All @@ -72,7 +71,6 @@ public async Task<string> InstallAsync(MinecraftPath path, string mcversion, str
return QuiltInstaller.GetVersionName(mcversion, modversion ?? (await QuiltInstaller.GetFirstLoader(mcversion))?.Version ?? throw new NullReferenceException("No internet and no mod name found."));
}


string? versionName = null;

if (modversion == null)
Expand Down
3 changes: 0 additions & 3 deletions Emerald.CoreX/Models/GameSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ public partial class GameSettings : ObservableObject
[ObservableProperty]
private int _serverPort = 25565;


[ObservableProperty]
private bool _HashCheck;

Expand All @@ -69,12 +68,10 @@ public partial class GameSettings : ObservableObject

public ObservableCollection<string> JVMArgs { get; set; } = new();


[JsonIgnore]
public string ScreenSizeStatus =>
FullScreen ? "FullScreen".Localize() : ((ScreenWidth > 0 && ScreenHeight > 0) ? $"{ScreenWidth} × {ScreenHeight}" : "Default".Localize());


public MLaunchOption ToMLaunchOption()
{
var opt = new MLaunchOption
Expand Down
1 change: 0 additions & 1 deletion Emerald.CoreX/Services/AccountService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ public async Task LoadAllAccountsAsync()

_accounts.AddRange(storedAccounts.Where(acc => acc.Type == AccountType.Offline));


// Add any new online accounts that aren't in storage
foreach (var onlineAccount in onlineAccounts)
{
Expand Down
4 changes: 2 additions & 2 deletions Emerald.CoreX/Services/IBaseSettingsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public interface IBaseSettingsService
{
event EventHandler<string>? APINoMatch;

void Set<T>(string key, T value, bool storeInFile = false);
void Set<T>(string key, T value);

T Get<T>(string key, T defaultVal, bool loadFromFile = false);
T Get<T>(string key, T defaultVal);
}
Loading
Loading