diff --git a/Directory.Packages.props b/Directory.Packages.props
index 5eb7eda3a..1310a8ae4 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -55,6 +55,7 @@
+
diff --git a/src/Ryujinx/Ryujinx.csproj b/src/Ryujinx/Ryujinx.csproj
index 480d14781..881414fb6 100644
--- a/src/Ryujinx/Ryujinx.csproj
+++ b/src/Ryujinx/Ryujinx.csproj
@@ -73,6 +73,7 @@
+
diff --git a/src/Ryujinx/Systems/Starscript/RyujinxStarscript.cs b/src/Ryujinx/Systems/Starscript/RyujinxStarscript.cs
new file mode 100644
index 000000000..9efa16178
--- /dev/null
+++ b/src/Ryujinx/Systems/Starscript/RyujinxStarscript.cs
@@ -0,0 +1,29 @@
+using Ryujinx.Ava.Systems.AppLibrary;
+using Ryujinx.Common;
+using Starscript;
+
+namespace Ryujinx.Ava.Systems.Starscript
+{
+ public static class RyujinxStarscript
+ {
+ public static readonly StarscriptHypervisor Hypervisor = StarscriptHypervisor.Create().WithStandardLibrary(true);
+
+ static RyujinxStarscript()
+ {
+ Hypervisor.Set("ryujinx.releaseChannel",
+ ReleaseInformation.IsCanaryBuild
+ ? "Canary"
+ : ReleaseInformation.IsReleaseBuild
+ ? "Stable"
+ : "Custom");
+ Hypervisor.Set("ryujinx.version", Program.Version);
+ Hypervisor.Set("appLibrary", StarscriptHelper.Wrap(RyujinxApp.MainWindow.ApplicationLibrary));
+ Hypervisor.Set("currentApplication", () =>
+ RyujinxApp.MainWindow.ApplicationLibrary.FindApplication(
+ RyujinxApp.MainWindow.ViewModel.AppHost?.ApplicationId ?? 0,
+ out ApplicationData appData)
+ ? StarscriptHelper.Wrap(appData)
+ : Value.Null);
+ }
+ }
+}
diff --git a/src/Ryujinx/Systems/Starscript/StarscriptHelper.cs b/src/Ryujinx/Systems/Starscript/StarscriptHelper.cs
new file mode 100644
index 000000000..184b8486f
--- /dev/null
+++ b/src/Ryujinx/Systems/Starscript/StarscriptHelper.cs
@@ -0,0 +1,68 @@
+using Gommon;
+using Ryujinx.Ava.Systems.AppLibrary;
+using Starscript;
+using System;
+
+namespace Ryujinx.Ava.Systems.Starscript
+{
+ public static class StarscriptHelper
+ {
+ public static ValueMap Wrap(ApplicationLibrary appLib)
+ {
+ ValueMap lMap = new();
+ lMap.Set("appCount", () => appLib.Applications.Count);
+ lMap.Set("dlcCount", () => appLib.DownloadableContents.Count);
+ lMap.Set("updateCount", () => appLib.TitleUpdates.Count);
+ lMap.Set("has", ctx =>
+ {
+ ulong titleId;
+
+ try
+ {
+ titleId = ctx.Constrain(Constraint.ExactlyOneArgument).NextString(1).ToULong();
+ }
+ catch (FormatException)
+ {
+ throw ctx.Error(
+ $"Invalid input to {ctx.FormattedName}; input must be a hexadecimal number in a string.");
+ }
+
+ return appLib.FindApplication(titleId, out _);
+ });
+ lMap.Set("get", ctx =>
+ {
+ ulong titleId;
+
+ try
+ {
+ titleId = ctx.Constrain(Constraint.ExactlyOneArgument).NextString(1).ToULong();
+ }
+ catch (FormatException)
+ {
+ throw ctx.Error(
+ $"Invalid input to {ctx.FormattedName}; input must be a hexadecimal number in a string.");
+ }
+
+ return appLib.FindApplication(titleId,
+ out ApplicationData applicationData)
+ ? Wrap(applicationData)
+ : null;
+ });
+ return lMap;
+ }
+
+ public static ValueMap Wrap(ApplicationData appData)
+ {
+ ValueMap aMap = new();
+ aMap.Set("name", appData.Name);
+ aMap.Set("version", appData.Version);
+ aMap.Set("developer", appData.Developer);
+ aMap.Set("fileExtension", appData.FileExtension);
+ aMap.Set("fileSize", appData.FileSizeString);
+ aMap.Set("hasLdnGames", appData.HasLdnGames);
+ aMap.Set("timePlayed", appData.TimePlayedString);
+ aMap.Set("isFavorite", appData.Favorite);
+ return aMap;
+ }
+ }
+}
diff --git a/src/Ryujinx/Systems/Starscript/StarscriptTextBox.axaml b/src/Ryujinx/Systems/Starscript/StarscriptTextBox.axaml
new file mode 100644
index 000000000..b30dc3eff
--- /dev/null
+++ b/src/Ryujinx/Systems/Starscript/StarscriptTextBox.axaml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/Ryujinx/Systems/Starscript/StarscriptTextBox.axaml.cs b/src/Ryujinx/Systems/Starscript/StarscriptTextBox.axaml.cs
new file mode 100644
index 000000000..8a772d134
--- /dev/null
+++ b/src/Ryujinx/Systems/Starscript/StarscriptTextBox.axaml.cs
@@ -0,0 +1,95 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Styling;
+using FluentAvalonia.UI.Controls;
+using Humanizer;
+using Ryujinx.Ava.Common.Locale;
+using Ryujinx.Ava.UI.Controls;
+using Ryujinx.Ava.UI.Helpers;
+using Starscript;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Ryujinx.Ava.Systems.Starscript
+{
+ public partial class StarscriptTextBox : RyujinxControl
+ {
+ public IReadOnlyList CurrentSuggestions => ViewModel.CurrentSuggestions;
+
+ public string CurrentScriptSource => ViewModel.CurrentScriptSource;
+ public Exception Exception => ViewModel.Exception;
+ public Script CurrentScript => ViewModel.CurrentScript;
+ public StringSegment CurrentScriptResult => ViewModel.CurrentScriptResult;
+
+ public StarscriptTextBox()
+ {
+ InitializeComponent();
+
+ InputBox.TextInput += HandleTextChanged;
+
+ InputBox.AsyncPopulator = GetSuggestionsAsync;
+ InputBox.MinimumPopulateDelay = 0.Seconds();
+ InputBox.TextFilter = (_, _) => true;
+ InputBox.TextSelector = (text, suggestion) =>
+ {
+ if (text is not null && suggestion is null)
+ return text;
+ if (text is null && suggestion is not null)
+ return suggestion;
+ // ReSharper disable once ConditionIsAlwaysTrueOrFalse
+ if (text is null && suggestion is null)
+ return string.Empty;
+
+ var sb = new StringBuilder(text.Length + suggestion.Length + 1);
+ sb.Append(text);
+
+ for (int i = 0; i < suggestion.Length - 1; i++)
+ {
+ if (text.EndsWith(suggestion[..(suggestion.Length - i - 1)]))
+ {
+ suggestion = suggestion[(suggestion.Length - i - 1)..];
+ break;
+ }
+ }
+
+ sb.Append(suggestion);
+
+ return sb.ToString();
+ };
+
+ Style textStyle = new(x => x.OfType().Descendant().OfType());
+ textStyle.Setters.Add(new Setter(MarginProperty, new Thickness(0, 0)));
+
+ Styles.Add(textStyle);
+ }
+
+ private Task> GetSuggestionsAsync(string input, CancellationToken token)
+ => Task.FromResult(ViewModel.GetSuggestions(input, token));
+
+ private void HandleTextChanged(object sender, TextInputEventArgs eventArgs)
+ {
+ if (sender is AutoCompleteBox)
+ ViewModel.CurrentScriptSource = eventArgs.Text;
+ }
+
+ public static StarscriptTextBox Create(StarscriptHypervisor hv)
+ => new() { ViewModel = new StarscriptTextBoxViewModel(hv) };
+
+ public static async Task Show()
+ {
+ ContentDialog contentDialog = new()
+ {
+ PrimaryButtonText = string.Empty,
+ SecondaryButtonText = string.Empty,
+ CloseButtonText = LocaleManager.Instance[LocaleKeys.UserProfilesClose],
+ Content = new StarscriptTextBox { ViewModel = new() }
+ };
+
+ await ContentDialogHelper.ShowAsync(contentDialog.ApplyStyles());
+ }
+ }
+}
diff --git a/src/Ryujinx/Systems/Starscript/StarscriptTextBoxViewModel.cs b/src/Ryujinx/Systems/Starscript/StarscriptTextBoxViewModel.cs
new file mode 100644
index 000000000..3a2cd591c
--- /dev/null
+++ b/src/Ryujinx/Systems/Starscript/StarscriptTextBoxViewModel.cs
@@ -0,0 +1,123 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using Ryujinx.Ava.UI.ViewModels;
+using Starscript;
+using Starscript.Internal;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Threading;
+
+namespace Ryujinx.Ava.Systems.Starscript
+{
+ public partial class StarscriptTextBoxViewModel : BaseModel
+ {
+ private readonly StarscriptHypervisor _hv;
+
+ public StarscriptTextBoxViewModel(StarscriptHypervisor hv = null)
+ {
+ _hv = hv ?? RyujinxStarscript.Hypervisor;
+ }
+
+ public ObservableCollection CurrentSuggestions { get; } = [];
+
+ [ObservableProperty] private bool _hasError;
+ [ObservableProperty] private StringSegment _currentScriptResult;
+ [ObservableProperty] private string _errorMessage;
+ private Exception _exception;
+ private string _currentScriptSource;
+ private Script _currentScript;
+
+ public Exception Exception
+ {
+ get => _exception;
+ set
+ {
+ ErrorMessage = (_exception = value) switch
+ {
+ ParseException pe => pe.Error.ToString(),
+ StarscriptException se => se.Message,
+ _ => string.Empty
+ };
+
+ OnPropertyChanged();
+ }
+ }
+
+ public string CurrentScriptSource
+ {
+ get => _currentScriptSource;
+ set
+ {
+ _currentScriptSource = value;
+
+ if (value is null)
+ {
+ CurrentScript = null;
+ CurrentScriptResult = null;
+ Exception = null;
+ HasError = false;
+ return;
+ }
+
+ try
+ {
+ CurrentScript = Compiler.DirectCompile(CurrentScriptSource);
+ Exception = null;
+ HasError = false;
+ }
+ catch (ParseException pe)
+ {
+ CurrentScript = null;
+ CurrentScriptResult = null;
+ Exception = pe;
+ HasError = true;
+ }
+
+ OnPropertyChanged();
+ }
+ }
+
+ public Script CurrentScript
+ {
+ get => _currentScript;
+ private set
+ {
+ try
+ {
+ CurrentScriptResult = value?.Execute(_hv)!;
+ _currentScript = value;
+ Exception = null;
+ HasError = false;
+ }
+ catch (StarscriptException se)
+ {
+ _currentScript = null;
+ CurrentScriptResult = null;
+ Exception = se;
+ HasError = true;
+ }
+
+ OnPropertyChanged();
+ }
+ }
+
+ public IEnumerable