From 6bb75a4049c6b11fd4a1843d78b01049a2e9661e Mon Sep 17 00:00:00 2001
From: ParantezTech <12572227+par274@users.noreply.github.com>
Date: Fri, 9 Oct 2026 17:22:05 +0300
Subject: [PATCH] [SharpEmu] Add bundled demo game
---
.gitignore | 1 +
src/SharpEmu.CLI/SharpEmu.CLI.csproj | 67 ++++++++++++++++++++++++++++
src/SharpEmu.GUI/BundledDemo.cs | 34 ++++++++++++++
src/SharpEmu.GUI/MainWindow.axaml.cs | 48 +++++++++++++-------
4 files changed, 135 insertions(+), 15 deletions(-)
create mode 100644 src/SharpEmu.GUI/BundledDemo.cs
diff --git a/.gitignore b/.gitignore
index 4403e8b..3aaef39 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@
[Aa]rtifacts/
logs/
.tmp/
+src/SharpEmu.Demo/
x86/
x64/
diff --git a/src/SharpEmu.CLI/SharpEmu.CLI.csproj b/src/SharpEmu.CLI/SharpEmu.CLI.csproj
index 7cc497b..dfe7ddc 100644
--- a/src/SharpEmu.CLI/SharpEmu.CLI.csproj
+++ b/src/SharpEmu.CLI/SharpEmu.CLI.csproj
@@ -180,4 +180,71 @@ SPDX-License-Identifier: GPL-2.0-or-later
SkipUnchangedFiles="true" />
+
+ e3a6dd3
+ demo
+ $(BaseIntermediateOutputPath)sharpemu-demo/$(SharpEmuDemoTag)
+ $(SharpEmuDemoCacheDir)/sharpemu-demo.zip
+ $([MSBuild]::NormalizeDirectory('$(MSBuildProjectDirectory)', '..', 'SharpEmu.Demo'))
+ $(SharpEmuDemoDir).tag
+
+
+
+
+
+
+
+ <_SharpEmuDemoCurrent Condition="'$(SharpEmuDemoTag)' != '' And '$(_SharpEmuDemoExtractedTag)' == '$(SharpEmuDemoTag)'">true
+
+
+
+
+
+ true
+
+
+
+
+
+
+
+
+ <_SharpEmuDemoFiles Include="$(SharpEmuDemoDir)**/*" Exclude="$(SharpEmuDemoStamp)" Condition="'$(SharpEmuDemoComplete)' == 'true'" />
+
+
+
+
+
+
+
+ <_SharpEmuDemoFiles Include="$(SharpEmuDemoDir)**/*" Exclude="$(SharpEmuDemoStamp)" Condition="'$(SharpEmuDemoComplete)' == 'true'" />
+
+
+
+
+
diff --git a/src/SharpEmu.GUI/BundledDemo.cs b/src/SharpEmu.GUI/BundledDemo.cs
new file mode 100644
index 0000000..31a8c5c
--- /dev/null
+++ b/src/SharpEmu.GUI/BundledDemo.cs
@@ -0,0 +1,34 @@
+// Copyright (C) 2026 SharpEmu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+using System.IO;
+
+namespace SharpEmu.GUI;
+
+internal static class BundledDemo
+{
+ internal const string FolderName = "demo";
+
+ private static readonly string[] RequiredFiles =
+ [
+ "eboot.bin",
+ Path.Combine("sce_sys", "param.json"),
+ Path.Combine("sce_sys", "icon0.png"),
+ Path.Combine("sce_sys", "pic0.png"),
+ Path.Combine("sce_sys", "snd0.at9"),
+ ];
+
+ internal static string? FindEboot(string baseDirectory)
+ {
+ var root = Path.Combine(baseDirectory, FolderName);
+ foreach (var file in RequiredFiles)
+ {
+ if (!File.Exists(Path.Combine(root, file)))
+ {
+ return null;
+ }
+ }
+
+ return Path.GetFullPath(Path.Combine(root, "eboot.bin"));
+ }
+}
diff --git a/src/SharpEmu.GUI/MainWindow.axaml.cs b/src/SharpEmu.GUI/MainWindow.axaml.cs
index ffb0962..567c893 100644
--- a/src/SharpEmu.GUI/MainWindow.axaml.cs
+++ b/src/SharpEmu.GUI/MainWindow.axaml.cs
@@ -2008,21 +2008,7 @@ public partial class MainWindow : Window
continue;
}
- long size = 0;
- try
- {
- size = new FileInfo(fullPath).Length;
- }
- catch (IOException exception)
- {
- Console.Error.WriteLine(
- $"[GUI][WARN] Could not inspect executable '{fullPath}': {exception.Message}");
- }
-
- var (title, titleId, version) = TryReadParamJson(fullPath);
- games.Add(new GameEntry(
- title ?? GameNameFor(fullPath), titleId, version, fullPath, size,
- FindCoverFor(fullPath), FindBackgroundFor(fullPath)));
+ games.Add(CreateGameEntry(fullPath));
}
}
catch (Exception exception)
@@ -2033,9 +2019,41 @@ public partial class MainWindow : Window
}
games.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase));
+ AddBundledDemo(games, excludedPaths);
return games;
}
+ private static void AddBundledDemo(List games, IReadOnlySet excludedPaths)
+ {
+ var demo = BundledDemo.FindEboot(AppContext.BaseDirectory);
+ if (demo is null || excludedPaths.Contains(demo))
+ {
+ return;
+ }
+
+ games.RemoveAll(game => string.Equals(game.Path, demo, GameLibraryPath.Comparison));
+ games.Insert(0, CreateGameEntry(demo));
+ }
+
+ private static GameEntry CreateGameEntry(string fullPath)
+ {
+ long size = 0;
+ try
+ {
+ size = new FileInfo(fullPath).Length;
+ }
+ catch (IOException exception)
+ {
+ Console.Error.WriteLine(
+ $"[GUI][WARN] Could not inspect executable '{fullPath}': {exception.Message}");
+ }
+
+ var (title, titleId, version) = TryReadParamJson(fullPath);
+ return new GameEntry(
+ title ?? GameNameFor(fullPath), titleId, version, fullPath, size,
+ FindCoverFor(fullPath), FindBackgroundFor(fullPath));
+ }
+
///
/// Reads the game title, title id and content version from
/// sce_sys/param.json next to the executable, when present.