Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
dd380ed
Initial background/barebones work
Shivansps Jan 11, 2025
b97825f
Add optional nebula login menu button to custom launcher
Shivansps Jan 11, 2025
1204d39
More configuration options
Shivansps Jan 11, 2025
ea06f6a
Custom mode home screen part 1
Shivansps Jan 12, 2025
cb576cb
Casually rework the entire cache system
Shivansps Jan 12, 2025
2ec27a4
Custom mode home screen part 2
Shivansps Jan 13, 2025
515f9ee
Minor corrections and initial status of the menu as a config option
Shivansps Jan 13, 2025
534d42e
Custom mode home screen part 3
Shivansps Jan 15, 2025
6bfde97
Fix mod settings were not saving on newely installed mods until restart
Shivansps Jan 15, 2025
d17c958
On second thought, do not call Mod Settings.Load() after a install
Shivansps Jan 15, 2025
ea25727
Custom home screen part 4
Shivansps Jan 17, 2025
1f7490b
Merge pull request #266 from Shivansps/fix-mod-settings-not-saving-ne…
Shivansps Jan 17, 2025
2b1f499
Properly report the correct knet data folder for portable AND custom …
Shivansps Jan 17, 2025
6b2aa7c
Custom home screen part 5
Shivansps Jan 18, 2025
dfb4e8a
Custom home screen part 6
Shivansps Jan 18, 2025
6899807
Enable install only if we have nebula data
Shivansps Jan 18, 2025
e1206ed
Custom menu buttons
Shivansps Jan 19, 2025
427441f
Add optional stock community menu item
Shivansps Jan 19, 2025
215b132
crash fix and wrong folder creation
Shivansps Jan 19, 2025
c7e091b
Hide globalsettings warning if mod is using -no_ingame_options
Shivansps Jan 19, 2025
a86d659
Advanced mod upload not selecting last version when unselecting upload
Shivansps Jan 21, 2025
d11fc7b
Change default renderer to software
Shivansps Jan 21, 2025
02a33cc
Add Sectioning to Options Menu (#267)
wookieejedi Jan 21, 2025
6f6b82a
Merge pull request #269 from Shivansps/make-software-rendering-default
Shivansps Jan 21, 2025
54b783a
Merge pull request #268 from Shivansps/advmod-upload-not-selecting-la…
Shivansps Jan 21, 2025
fe7e30b
Initial background/barebones work
Shivansps Jan 11, 2025
32c6360
Add optional nebula login menu button to custom launcher
Shivansps Jan 11, 2025
957312c
More configuration options
Shivansps Jan 11, 2025
ac7cfdb
Custom mode home screen part 1
Shivansps Jan 12, 2025
1cd89a4
Casually rework the entire cache system
Shivansps Jan 12, 2025
3036972
Custom mode home screen part 2
Shivansps Jan 13, 2025
38001d2
Minor corrections and initial status of the menu as a config option
Shivansps Jan 13, 2025
3439167
Custom mode home screen part 3
Shivansps Jan 15, 2025
7111721
Custom home screen part 4
Shivansps Jan 17, 2025
0b95abc
Properly report the correct knet data folder for portable AND custom …
Shivansps Jan 17, 2025
2723ea0
Custom home screen part 5
Shivansps Jan 18, 2025
2223e0d
Custom home screen part 6
Shivansps Jan 18, 2025
4c9c7e7
Enable install only if we have nebula data
Shivansps Jan 18, 2025
11ccf63
Custom menu buttons
Shivansps Jan 19, 2025
7fe4607
Add optional stock community menu item
Shivansps Jan 19, 2025
7f64e23
crash fix and wrong folder creation
Shivansps Jan 19, 2025
9d02412
Hide globalsettings warning if mod is using -no_ingame_options
Shivansps Jan 19, 2025
2e908f4
setting warnings not showing
Shivansps Jan 21, 2025
6eaa09a
Merge branch 'custom-launcher-mode' of https://github.com/Shivansps/K…
Shivansps Jan 21, 2025
e2c5e2d
rename config to options
Shivansps Jan 22, 2025
6cecda0
Make home buttons costumizable
Shivansps Jan 22, 2025
9695bbc
Add AxamlContent, HomeBackgroundStretchMode, and MenuTaskButtonAtTheEnd
Shivansps Jan 22, 2025
63d5196
fix typo
Shivansps Jan 22, 2025
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
1 change: 1 addition & 0 deletions Knossos.NET/AppStyles.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
<!--https://avaloniaui.github.io/icons.html-->
<Styles.Resources>
<StreamGeometry x:Key="3LinesRegular">M2 4.5C2 4.22386 2.22386 4 2.5 4H17.5C17.7761 4 18 4.22386 18 4.5C18 4.77614 17.7761 5 17.5 5H2.5C2.22386 5 2 4.77614 2 4.5Z M2 9.5C2 9.22386 2.22386 9 2.5 9H17.5C17.7761 9 18 9.22386 18 9.5C18 9.77614 17.7761 10 17.5 10H2.5C2.22386 10 2 9.77614 2 9.5Z M2.5 14C2.22386 14 2 14.2239 2 14.5C2 14.7761 2.22386 15 2.5 15H17.5C17.7761 15 18 14.7761 18 14.5C18 14.2239 17.7761 14 17.5 14H2.5Z</StreamGeometry>
<StreamGeometry x:Key="ChevronLeftRegular">M31.8839 8.36612C32.372 8.85427 32.372 9.64573 31.8839 10.1339L18.0178 24L31.8839 37.8661C32.372 38.3543 32.372 39.1457 31.8839 39.6339C31.3957 40.122 30.6043 40.122 30.1161 39.6339L15.3661 24.8839C14.878 24.3957 14.878 23.6043 15.3661 23.1161L30.1161 8.36612C30.6043 7.87796 31.3957 7.87796 31.8839 8.36612Z</StreamGeometry>
</Styles.Resources>

<!--Color Resource Definition-->
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Knossos.NET/Assets/general/discordicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Knossos.NET/Assets/general/menu_home.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Knossos.NET/Assets/general/menu_nebula.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 16 additions & 4 deletions Knossos.NET/Classes/FsoBuild.cs
Original file line number Diff line number Diff line change
Expand Up @@ -277,9 +277,14 @@ public async Task<FsoResult> RunFSO(FsoExecType executableType, string cmdline,
fso.StartInfo.UseShellExecute = false;
if (workingFolder != null)
fso.StartInfo.WorkingDirectory = workingFolder;
if(Knossos.inPortableMode && Knossos.globalSettings.portableFsoPreferences)
if (Knossos.inPortableMode && Knossos.globalSettings.portableFsoPreferences ||
CustomLauncher.IsCustomMode && CustomLauncher.UseCustomFSODataFolder)
{
var prefPath = Path.Combine(KnUtils.KnetFolderPath!, "kn_portable", "HardLightProductions", "FreeSpaceOpen") + Path.DirectorySeparatorChar;
var prefPath = KnUtils.GetFSODataFolderPath();
if (!prefPath.EndsWith(Path.DirectorySeparatorChar))
{
prefPath += Path.DirectorySeparatorChar;
}
Log.Add(Log.LogSeverity.Information, "FsoBuild.RunFSO()", "Used preferences path: " + prefPath);
fso.StartInfo.EnvironmentVariables.Add("FSO_PREFERENCES_PATH", prefPath);
}
Expand Down Expand Up @@ -351,10 +356,17 @@ public async Task<FsoResult> RunFSO(FsoExecType executableType, string cmdline,
cmd.StartInfo.RedirectStandardInput = true;
cmd.StartInfo.StandardOutputEncoding = new UTF8Encoding(false);
cmd.StartInfo.WorkingDirectory = folderPath;
if (Knossos.inPortableMode && Knossos.globalSettings.portableFsoPreferences)
if (Knossos.inPortableMode && Knossos.globalSettings.portableFsoPreferences ||
CustomLauncher.IsCustomMode && CustomLauncher.UseCustomFSODataFolder)
{
cmd.StartInfo.EnvironmentVariables.Add("FSO_PREFERENCES_PATH", Path.Combine(KnUtils.KnetFolderPath!, "kn_portable", "HardLightProductions", "FreeSpaceOpen") + Path.DirectorySeparatorChar);
var prefPath = KnUtils.GetFSODataFolderPath();
if (!prefPath.EndsWith(Path.DirectorySeparatorChar))
{
prefPath += Path.DirectorySeparatorChar;
}
cmd.StartInfo.EnvironmentVariables.Add("FSO_PREFERENCES_PATH", prefPath);
}

cmd.Start();
string result = cmd.StandardOutput.ReadToEnd();
output = result;
Expand Down
216 changes: 186 additions & 30 deletions Knossos.NET/Classes/KnUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,15 @@ public static string GetKnossosDataFolderPath()
{
if (!Knossos.inPortableMode)
{
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData, Environment.SpecialFolderOption.Create), "KnossosNET");
if (CustomLauncher.IsCustomMode)
{
//In custom mode store config files inside modid a subfolder
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData, Environment.SpecialFolderOption.Create), "KnossosNET", CustomLauncher.ModID!);
}
else
{
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData, Environment.SpecialFolderOption.Create), "KnossosNET");
}
}
else
{
Expand All @@ -139,32 +147,61 @@ public static string GetKnossosDataFolderPath()
/// <returns>fullpath as string</returns>
public static string GetFSODataFolderPath()
{
var fsoID = "FreeSpaceOpen";
if(CustomLauncher.IsCustomMode && CustomLauncher.UseCustomFSODataFolder)
{
fsoID = CustomLauncher.ModID!;
}
if (Knossos.inPortableMode && Knossos.globalSettings.portableFsoPreferences)
{
return Path.Combine(KnetFolderPath!, "kn_portable", "HardLightProductions", "FreeSpaceOpen"); //If inPortableMode = true, KnetFolderPath is not null
return Path.Combine(KnetFolderPath!, "kn_portable", "HardLightProductions", fsoID); //If inPortableMode = true, KnetFolderPath is not null
}
else
{
if (!string.IsNullOrEmpty(fsoPrefPath))
{
return fsoPrefPath;
if (CustomLauncher.IsCustomMode && CustomLauncher.UseCustomFSODataFolder)
{
return ReplaceLast(fsoPrefPath, "FreeSpaceOpen", fsoID);
}
else
{
return fsoPrefPath;
}
}
else
{
if (KnUtils.isMacOS)
{
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Application Support", "HardLightProductions", "FreeSpaceOpen");
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Application Support", "HardLightProductions", fsoID);
}
if (IsLinux)
{
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "HardLightProductions", "FreeSpaceOpen");
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "HardLightProductions", fsoID);
}
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData, Environment.SpecialFolderOption.Create), "HardLightProductions", "FreeSpaceOpen");
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData, Environment.SpecialFolderOption.Create), "HardLightProductions", fsoID);
}
}

}

/// <summary>
/// Replace last ocurrence of a word in a string
/// </summary>
/// <param name="source"></param>
/// <param name="find"></param>
/// <param name="replace"></param>
/// <returns></returns>
public static string ReplaceLast(string source, string find, string replace)
{
int index = source.LastIndexOf(find);

if (index == -1)
return source;

return source.Remove(index, find.Length).Insert(index, replace);
}

/// <summary>
/// Saves fsoPrefPath so it can be used by GetFSODataFolderPath()
/// </summary>
Expand Down Expand Up @@ -579,61 +616,180 @@ public static async Task<long> GetSizeOfFolderInBytes(string folderPath, bool re
/// Gets the fullpath to image storage cache
/// </summary>
/// <returns></returns>
public static string GetImageCachePath()
public static string GetCachePath()
{
try
{
return Path.Combine(GetKnossosDataFolderPath(), "image_cache");
return Path.Combine(GetKnossosDataFolderPath(), "cache");
}
catch { return string.Empty; }
}

/// <summary>
/// Downloads a image from a URL, stores it in cache and serves the filestream
/// If the image is already in cache, no download is done
/// Downloads a file from a URL, stores it in cache and serves the filestream
/// If the file is already in cache, a check is done to make sure it has no changed.
/// If it has changed it is re-downloaded
/// </summary>
/// <param name="resourceURL"></param>
/// <returns>Cached filestream or null if failed</returns>
public static async Task<FileStream?> GetRemoteResourceStream(string resourceURL)
{
try
{
var localFile = await GetRemoteResource(resourceURL);
if (localFile != null)
{
var fileStream = new FileStream(localFile, FileMode.Open, FileAccess.Read, FileShare.Read);
if(fileStream.Length == 0)
return null;
return fileStream;
}
}
catch (Exception ex)
{
Log.Add(Log.LogSeverity.Error, "KnUtils.GetRemoteResourceStream()", ex);
}
return null;
}

/// <summary>
/// Downloads a file from a URL, stores it in cache and returns the local path
/// If the file is already in cache, a check is done to make sure it has no changed.
/// If it has changed it is updated
/// </summary>
/// <param name="imageURL"></param>
/// <returns>Cached image filestream or null if failed</returns>
public static async Task<FileStream?> GetImageStream(string imageURL, int attempt = 1)
/// <param name="attempt"></param>
/// <returns>string path or null</returns>
public static async Task<string?> GetRemoteResource(string resourceURL, int attempt = 1)
{
string fileInCachePath = string.Empty;
bool cacheFileIsValid = false;
try
{
return await Task.Run(async () =>
Directory.CreateDirectory(GetCachePath()); //make sure the cache dir exists
var fileName = Path.GetFileName(resourceURL);
fileInCachePath = Path.Combine(GetCachePath(), fileName);
var fileInCacheEtagPath = fileInCachePath + ".etag";
string? remoteEtag = null;
bool cacheFileExists = File.Exists(fileInCachePath);
Uri uri = new Uri(resourceURL);
bool isNebulaFile = Nebula.nebulaMirrors.Contains(uri.Host.ToLower());
cacheFileIsValid = cacheFileExists && new FileInfo(fileInCachePath).Length > 0 ? true : false;

//file exists in cache? check it
if (cacheFileIsValid && cacheFileExists)
{
var imageName = Path.GetFileName(imageURL);
var imageInCachePath = Path.Combine(GetImageCachePath(), imageName);
bool cacheFileEtagExists = File.Exists(fileInCacheEtagPath);
if (isNebulaFile)
{
//This is a nebula file, nebula files are stored by their checksum so they never update
return fileInCachePath;
}
else if (cacheFileEtagExists)
{
//etag info exist, check it
var cachedEtag = await File.ReadAllTextAsync(fileInCacheEtagPath);
remoteEtag = await GetUrlFileEtag(resourceURL);
if (cachedEtag != null && cachedEtag == remoteEtag)
{
//cache is up to date
return fileInCachePath;
}
else
{
Log.Add(Log.LogSeverity.Information, "KnUtils.GetRemoteResource()", "File: "+ fileName + " from cache is outdated, re-download. Cache etag: " + cachedEtag + " Remove etag: " + remoteEtag);
}
}
//not etag info or it has changed, re-download
}

if (File.Exists(imageInCachePath) && new FileInfo(imageInCachePath).Length > 0)
Log.Add(Log.LogSeverity.Information, "KnUtils.GetRemoteResource()", "Downloading: " + resourceURL + " to local cache.");
//download to cache
using (var imageStream = await GetHttpClient().GetStreamAsync(resourceURL))
{
using (var fileStream = new FileStream(fileInCachePath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read))
{
return new FileStream(imageInCachePath, FileMode.Open, FileAccess.Read, FileShare.Read);
await imageStream.CopyToAsync(fileStream);
}
else
}
//save etag
if (!isNebulaFile)
{
try
{
//Download to cache and copy
Directory.CreateDirectory(Path.Combine(GetKnossosDataFolderPath(), "image_cache"));
using (var imageStream = await GetHttpClient().GetStreamAsync(imageURL))
if (remoteEtag == null)
{
remoteEtag = await GetUrlFileEtag(resourceURL);
}
if (remoteEtag != null)
{
File.WriteAllText(fileInCacheEtagPath, remoteEtag, Encoding.UTF8);
}
else
{
var fileStream = new FileStream(imageInCachePath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
await imageStream.CopyToAsync(fileStream);
imageStream.Close();
fileStream.Seek(0, SeekOrigin.Begin);
return fileStream;
Log.Add(Log.LogSeverity.Error, "KnUtils.GetRemoteResource()", "Could not save etag information for file " + resourceURL + " remoteEtag value was null.");
}
}
});
catch (Exception ex)
{
Log.Add(Log.LogSeverity.Error, "KnUtils.GetRemoteResource()", ex);
}
}
return fileInCachePath;
}
catch (Exception ex)
{
Log.Add(Log.LogSeverity.Error, "KnUtils.GetImageStream()", ex);
if (attempt <= 2)
Log.Add(Log.LogSeverity.Error, "KnUtils.GetImagePath()", ex);
if (attempt < 3)
{
await Task.Delay(1000);
return await GetImageStream(imageURL, attempt + 1);
return await GetRemoteResource(resourceURL, attempt + 1);
}
else
{
//If the download somehow fails, but we have a valid local version of this file, pass it, no matter if it is outdated
if (cacheFileIsValid)
{
return fileInCachePath;
}
}
}
return null;
}

/// <summary>
/// Reads etag data from a url file
/// </summary>
/// <returns>etag string or null</returns>
public static async Task<string?> GetUrlFileEtag(string url)
{
try
{
string? newEtag = null;
Log.Add(Log.LogSeverity.Information, "KnUtils.GetUrlFileEtag()", "Getting " + url + " etag.");

var result = await KnUtils.GetHttpClient().GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
newEtag = result.Headers?.ETag?.ToString().Replace("\"", "");
try
{
//workaround because it was not always working on some urls
if (newEtag == null && result.Headers != null)
{
var etagHeader = result.Headers.FirstOrDefault(x => x.Key != null && x.Key.ToLower() == "etag");
newEtag = etagHeader.Value.FirstOrDefault();
}
}
catch { }
Log.Add(Log.LogSeverity.Information, "KnUtils.GetUrlFileEtag()", Path.GetFileName(url) + " etag: " + newEtag);
return newEtag;
}
catch (Exception ex)
{
Log.Add(Log.LogSeverity.Error, "KnUtils.GetUrlFileEtag()", ex);
return null;
}
}

/// <summary>
/// Check FreeSpace available on the disk/partion of path
/// </summary>
Expand Down
Loading