Compare commits

..

9 Commits

Author SHA1 Message Date
e9824c9053 Comment AppImage builds
It randomly started erroring in GitHub actions with exit code 8 but only sometimes, and I don't have the patience to debug it. I don't even use linux lol
2025-07-28 19:35:47 -05:00
d2037da65f Nullify Locales (ryubing/ryujinx!83)
See merge request ryubing/ryujinx!83
2025-07-28 19:35:47 -05:00
8fe7d54f85 Changes to uk_UA (ryubing/ryujinx!84)
See merge request ryubing/ryujinx!84
2025-07-28 19:35:47 -05:00
b8f9f3e16a Edit TileIDs.cs (ryubing/ryujinx!81)
See merge request ryubing/ryujinx!81
2025-07-28 19:35:47 -05:00
7b1c8717ef Edit compatibility.csv (ryubing/ryujinx!80)
See merge request ryubing/ryujinx!80
2025-07-28 19:35:47 -05:00
6122fa204f simplify completion callback 2025-07-28 17:58:54 -05:00
217fd90568 Use a single parser execution per text box update
Before it would: parse, compile, then execute for getting the formatted result, then run the suggestions function which did parsing on its own. It has now been moved to the new ParseAndGetCompletions function which returns the used ParserResult, saving some work.
2025-07-27 17:35:06 -05:00
95157c0cfd make ApplicationLibrary implement IStarscriptObject 2025-07-27 17:32:39 -05:00
8a3ccaafe3 basic starscript support + a textbox that provides the starscript executed result & compiled script as well as suggestions 2025-07-27 01:09:59 -05:00
10 changed files with 352 additions and 1 deletions

View File

@ -55,6 +55,7 @@
<PackageVersion Include="SkiaSharp" Version="2.88.9" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.9" />
<PackageVersion Include="SPB" Version="0.0.4-build32" />
<PackageVersion Include="Starscript.Net" Version="1.0.36" />
<PackageVersion Include="System.IO.Hashing" Version="9.0.2" />
<PackageVersion Include="System.Management" Version="9.0.2" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />

View File

@ -73,6 +73,7 @@
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
<PackageReference Include="SPB" />
<PackageReference Include="Starscript.Net"/>
<PackageReference Include="SharpZipLib" />
</ItemGroup>

View File

@ -14,6 +14,7 @@ using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common.Models;
using Ryujinx.Ava.Systems.Configuration;
using Ryujinx.Ava.Systems.Configuration.System;
using Ryujinx.Ava.Systems.Starscript;
using Ryujinx.Ava.Utilities;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
@ -25,6 +26,7 @@ using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.HLE.Loaders.Npdm;
using Ryujinx.HLE.Loaders.Processes.Extensions;
using Ryujinx.HLE.Utilities;
using Starscript;
using System;
using System.Collections.Generic;
using System.IO;
@ -41,7 +43,7 @@ using TimeSpan = System.TimeSpan;
namespace Ryujinx.Ava.Systems.AppLibrary
{
public class ApplicationLibrary
public class ApplicationLibrary : IStarscriptObject
{
public Language DesiredLanguage { get; set; }
public event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated;
@ -1611,5 +1613,14 @@ namespace Ryujinx.Ava.Systems.AppLibrary
ApplicationData newApplication = newApplications.First(it => it.IdBase == appIdBase);
_applications.AddOrUpdate(newApplication);
}
private ValueMap _starscriptMap;
public ValueMap ToStarscript()
{
_starscriptMap ??= StarscriptHelper.Wrap(this);
return _starscriptMap;
}
}
}

View File

@ -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", RyujinxApp.MainWindow.ApplicationLibrary);
Hypervisor.Set("currentApplication", () =>
RyujinxApp.MainWindow.ApplicationLibrary.FindApplication(
RyujinxApp.MainWindow.ViewModel.AppHost?.ApplicationId ?? 0,
out ApplicationData appData)
? StarscriptHelper.Wrap(appData)
: Value.Null);
}
}
}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,21 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="using:Ryujinx.Ava.Systems.Starscript"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Ryujinx.Ava.Systems.Starscript.StarscriptTextBox"
x:DataType="local:StarscriptTextBoxViewModel">
<StackPanel Spacing="10">
<TextBlock Text="{Binding ErrorMessage}" IsVisible="{Binding HasError}"/>
<TextBlock Text="{Binding CurrentScriptResult}" IsVisible="{Binding !HasError}"/>
<AutoCompleteBox Name="InputBox"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
FilterMode="Custom"
MinimumPrefixLength="0"
MaxDropDownHeight="400">
</AutoCompleteBox>
</StackPanel>
</UserControl>

View File

@ -0,0 +1,87 @@
using Avalonia;
using Avalonia.Controls;
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 Starscript.Internal;
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<StarscriptTextBoxViewModel>
{
public IReadOnlyList<string> CurrentSuggestions => ViewModel.CurrentSuggestions;
public ParserResult CurrentScriptSource => ViewModel.CurrentScriptSource;
public Exception Exception => ViewModel.Exception;
public Script CurrentScript => ViewModel.CurrentScript;
public StringSegment CurrentScriptResult => ViewModel.CurrentScriptResult;
public StarscriptTextBox()
{
InitializeComponent();
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<AutoCompleteBox>().Descendant().OfType<TextBlock>());
textStyle.Setters.Add(new Setter(MarginProperty, new Thickness(0, 0)));
Styles.Add(textStyle);
}
private Task<IEnumerable<object>> GetSuggestionsAsync(string input, CancellationToken token)
=> Task.FromResult(ViewModel.GetSuggestions(input, token));
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());
}
}
}

View File

@ -0,0 +1,126 @@
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.Linq;
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<string> CurrentSuggestions { get; } = [];
[ObservableProperty] private bool _hasError;
[ObservableProperty] private StringSegment _currentScriptResult;
[ObservableProperty] private string _errorMessage;
private Exception _exception;
private ParserResult _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
};
HasError = value is not null;
OnPropertyChanged();
}
}
public ParserResult CurrentScriptSource
{
get => _currentScriptSource;
set
{
_currentScriptSource = value;
if (value is null)
{
CurrentScript = null;
CurrentScriptResult = null;
Exception = null;
return;
}
CurrentScript = Compiler.SingleCompile(value);
Exception = null;
OnPropertyChanged();
}
}
public Script CurrentScript
{
get => _currentScript;
private set
{
try
{
CurrentScriptResult = value?.Execute(_hv)!;
_currentScript = value;
Exception = null;
}
catch (StarscriptException se)
{
_currentScript = null;
CurrentScriptResult = null;
Exception = se;
}
OnPropertyChanged();
}
}
public void ReExecuteScript()
{
if (_currentScript is null) return;
try
{
CurrentScriptResult = _currentScript.Execute(_hv)!;
}
catch (StarscriptException se)
{
CurrentScriptResult = null;
Exception = se;
}
}
public IEnumerable<object> GetSuggestions(string input, CancellationToken token)
{
CurrentSuggestions.Clear();
CurrentScriptSource = _hv.ParseAndGetCompletions(input, input.Length, CompletionCallback, token);
if (CurrentScriptSource.HasErrors)
{
Exception = new ParseException(CurrentScriptSource.Errors.First());
}
OnPropertyChanged(nameof(CurrentSuggestions));
return CurrentSuggestions;
}
private void CompletionCallback(string result, bool isFunction) => CurrentSuggestions.Add(isFunction ? $"{result}(" : result);
}
}

View File

@ -253,6 +253,10 @@
Header="{ext:Locale MenuBarHelpAbout}"
Icon="{ext:Icon fa-solid fa-circle-info}"
ToolTip.Tip="{ext:Locale OpenAboutTooltip}" />
<MenuItem
Name="StarscriptDebugMenuItem"
Header="Debug Starscript"
Icon="{ext:Icon fa-solid fa-star}" />
<MenuItem
Name="UpdateMenuItem"
IsEnabled="{Binding CanUpdate}"

View File

@ -8,6 +8,7 @@ using LibHac.Ns;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Systems.AppLibrary;
using Ryujinx.Ava.Systems.Configuration;
using Ryujinx.Ava.Systems.Starscript;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
@ -51,6 +52,8 @@ namespace Ryujinx.Ava.UI.Views.Main
CompatibilityListMenuItem.Command = Commands.Create(() => CompatibilityListWindow.Show());
UpdateMenuItem.Command = MainWindowViewModel.UpdateCommand;
StarscriptDebugMenuItem.Command = Commands.Create(StarscriptTextBox.Show);
FaqMenuItem.Command =
SetupGuideMenuItem.Command =