no name: Mii Editor applet support (#2419)

* no name: Mii Editor applet support

* addresses gdkchan feedback

* Fix comment

* Bypass MountCounter of MiiDatabaseManager

* Fix GetSettingsPlatformRegion

* Disable Applet Menu for unsupported firmwares
This commit is contained in:
Ac_K 2021-06-28 20:54:45 +02:00 committed by GitHub
parent fefd4619a5
commit a79b39b913
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 591 additions and 33 deletions

View File

@ -37,6 +37,7 @@ namespace Ryujinx.Common.Logging
ServiceLdn,
ServiceLdr,
ServiceLm,
ServiceMii,
ServiceMm,
ServiceNfc,
ServiceNfp,

View File

@ -49,6 +49,7 @@ namespace Ryujinx.HLE.HOS
internal const int FontSize = 0x1100000;
internal const int IirsSize = 0x8000;
internal const int TimeSize = 0x1000;
internal const int AppletCaptureBufferSize = 0x384000;
internal KernelContext KernelContext { get; }
@ -82,6 +83,9 @@ namespace Ryujinx.HLE.HOS
internal KSharedMemory HidSharedMem { get; private set; }
internal KSharedMemory FontSharedMem { get; private set; }
internal KSharedMemory IirsSharedMem { get; private set; }
internal KTransferMemory AppletCaptureBufferTransfer { get; private set; }
internal SharedFontManager Font { get; private set; }
internal AccountManager AccountManager { get; private set; }
@ -133,21 +137,25 @@ namespace Ryujinx.HLE.HOS
ulong fontPa = region.Address + HidSize;
ulong iirsPa = region.Address + HidSize + FontSize;
ulong timePa = region.Address + HidSize + FontSize + IirsSize;
ulong appletCaptureBufferPa = region.Address + HidSize + FontSize + IirsSize + TimeSize;
KPageList hidPageList = new KPageList();
KPageList fontPageList = new KPageList();
KPageList iirsPageList = new KPageList();
KPageList timePageList = new KPageList();
KPageList appletCaptureBufferPageList = new KPageList();
hidPageList.AddRange(hidPa, HidSize / KPageTableBase.PageSize);
fontPageList.AddRange(fontPa, FontSize / KPageTableBase.PageSize);
iirsPageList.AddRange(iirsPa, IirsSize / KPageTableBase.PageSize);
timePageList.AddRange(timePa, TimeSize / KPageTableBase.PageSize);
appletCaptureBufferPageList.AddRange(appletCaptureBufferPa, AppletCaptureBufferSize / KPageTableBase.PageSize);
var hidStorage = new SharedMemoryStorage(KernelContext, hidPageList);
var fontStorage = new SharedMemoryStorage(KernelContext, fontPageList);
var iirsStorage = new SharedMemoryStorage(KernelContext, iirsPageList);
var timeStorage = new SharedMemoryStorage(KernelContext, timePageList);
var appletCaptureBufferStorage = new SharedMemoryStorage(KernelContext, appletCaptureBufferPageList);
HidStorage = hidStorage;
@ -159,6 +167,8 @@ namespace Ryujinx.HLE.HOS
TimeServiceManager.Instance.Initialize(device, this, timeSharedMemory, timeStorage, TimeSize);
AppletCaptureBufferTransfer = new KTransferMemory(KernelContext, appletCaptureBufferStorage);
AppletState = new AppletStateMgr(this);
AppletState.SetFocus(true);

View File

@ -0,0 +1,105 @@
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletProxy;
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService
{
class ILibraryAppletProxy : IpcService
{
private readonly long _pid;
public ILibraryAppletProxy(long pid)
{
_pid = pid;
}
[CommandHipc(0)]
// GetCommonStateGetter() -> object<nn::am::service::ICommonStateGetter>
public ResultCode GetCommonStateGetter(ServiceCtx context)
{
MakeObject(context, new ICommonStateGetter(context));
return ResultCode.Success;
}
[CommandHipc(1)]
// GetSelfController() -> object<nn::am::service::ISelfController>
public ResultCode GetSelfController(ServiceCtx context)
{
MakeObject(context, new ISelfController(context, _pid));
return ResultCode.Success;
}
[CommandHipc(2)]
// GetWindowController() -> object<nn::am::service::IWindowController>
public ResultCode GetWindowController(ServiceCtx context)
{
MakeObject(context, new IWindowController(_pid));
return ResultCode.Success;
}
[CommandHipc(3)]
// GetAudioController() -> object<nn::am::service::IAudioController>
public ResultCode GetAudioController(ServiceCtx context)
{
MakeObject(context, new IAudioController());
return ResultCode.Success;
}
[CommandHipc(4)]
// GetDisplayController() -> object<nn::am::service::IDisplayController>
public ResultCode GetDisplayController(ServiceCtx context)
{
MakeObject(context, new IDisplayController(context));
return ResultCode.Success;
}
[CommandHipc(10)]
// GetProcessWindingController() -> object<nn::am::service::IProcessWindingController>
public ResultCode GetProcessWindingController(ServiceCtx context)
{
MakeObject(context, new IProcessWindingController());
return ResultCode.Success;
}
[CommandHipc(11)]
// GetLibraryAppletCreator() -> object<nn::am::service::ILibraryAppletCreator>
public ResultCode GetLibraryAppletCreator(ServiceCtx context)
{
MakeObject(context, new ILibraryAppletCreator());
return ResultCode.Success;
}
[CommandHipc(20)]
// OpenLibraryAppletSelfAccessor() -> object<nn::am::service::ILibraryAppletSelfAccessor>
public ResultCode OpenLibraryAppletSelfAccessor(ServiceCtx context)
{
MakeObject(context, new ILibraryAppletSelfAccessor(context));
return ResultCode.Success;
}
[CommandHipc(21)]
// GetAppletCommonFunctions() -> object<nn::am::service::IAppletCommonFunctions>
public ResultCode GetAppletCommonFunctions(ServiceCtx context)
{
MakeObject(context, new IAppletCommonFunctions());
return ResultCode.Success;
}
[CommandHipc(1000)]
// GetDebugFunctions() -> object<nn::am::service::IDebugFunctions>
public ResultCode GetDebugFunctions(ServiceCtx context)
{
MakeObject(context, new IDebugFunctions());
return ResultCode.Success;
}
}
}

View File

@ -24,7 +24,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService
// GetSelfController() -> object<nn::am::service::ISelfController>
public ResultCode GetSelfController(ServiceCtx context)
{
MakeObject(context, new ISelfController(context.Device.System, _pid));
MakeObject(context, new ISelfController(context, _pid));
return ResultCode.Success;
}
@ -51,7 +51,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService
// GetDisplayController() -> object<nn::am::service::IDisplayController>
public ResultCode GetDisplayController(ServiceCtx context)
{
MakeObject(context, new IDisplayController());
MakeObject(context, new IDisplayController(context));
return ResultCode.Success;
}

View File

@ -0,0 +1,16 @@
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletProxy
{
class AppletStandalone
{
public AppletId AppletId;
public LibraryAppletMode LibraryAppletMode;
public Queue<byte[]> InputData;
public AppletStandalone()
{
InputData = new Queue<byte[]>();
}
}
}

View File

@ -0,0 +1,78 @@
using Ryujinx.Common;
using System;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletProxy
{
class ILibraryAppletSelfAccessor : IpcService
{
private AppletStandalone _appletStandalone = new AppletStandalone();
public ILibraryAppletSelfAccessor(ServiceCtx context)
{
if (context.Device.Application.TitleId == 0x0100000000001009)
{
// Create MiiEdit data.
_appletStandalone = new AppletStandalone()
{
AppletId = AppletId.MiiEdit,
LibraryAppletMode = LibraryAppletMode.AllForeground
};
byte[] miiEditInputData = new byte[0x100];
miiEditInputData[0] = 0x03; // Hardcoded unknown value.
_appletStandalone.InputData.Enqueue(miiEditInputData);
}
else
{
throw new NotImplementedException($"{context.Device.Application.TitleId} applet is not implemented.");
}
}
[CommandHipc(0)]
// PopInData() -> object<nn::am::service::IStorage>
public ResultCode PopInData(ServiceCtx context)
{
byte[] appletData = _appletStandalone.InputData.Dequeue();
if (appletData.Length == 0)
{
return ResultCode.NotAvailable;
}
MakeObject(context, new IStorage(appletData));
return ResultCode.Success;
}
[CommandHipc(11)]
// GetLibraryAppletInfo() -> nn::am::service::LibraryAppletInfo
public ResultCode GetLibraryAppletInfo(ServiceCtx context)
{
LibraryAppletInfo libraryAppletInfo = new LibraryAppletInfo()
{
AppletId = _appletStandalone.AppletId,
LibraryAppletMode = _appletStandalone.LibraryAppletMode
};
context.ResponseData.WriteStruct(libraryAppletInfo);
return ResultCode.Success;
}
[CommandHipc(14)]
// GetCallerAppletIdentityInfo() -> nn::am::service::AppletIdentityInfo
public ResultCode GetCallerAppletIdentityInfo(ServiceCtx context)
{
AppletIdentifyInfo appletIdentifyInfo = new AppletIdentifyInfo()
{
AppletId = AppletId.QLaunch,
TitleId = 0x0100000000001000
};
context.ResponseData.WriteStruct(appletIdentifyInfo);
return ResultCode.Success;
}
}
}

View File

@ -0,0 +1,24 @@
using Ryujinx.Common;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletProxy
{
class IProcessWindingController : IpcService
{
public IProcessWindingController() { }
[CommandHipc(0)]
// GetLaunchReason() -> nn::am::service::AppletProcessLaunchReason
public ResultCode GetLaunchReason(ServiceCtx context)
{
// NOTE: Flag is set by using an internal field.
AppletProcessLaunchReason appletProcessLaunchReason = new AppletProcessLaunchReason()
{
Flag = 0
};
context.ResponseData.WriteStruct(appletProcessLaunchReason);
return ResultCode.Success;
}
}
}

View File

@ -0,0 +1,7 @@
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy
{
class IAppletCommonFunctions : IpcService
{
public IAppletCommonFunctions() { }
}
}

View File

@ -2,6 +2,8 @@ using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Settings.Types;
using Ryujinx.HLE.HOS.SystemState;
using System;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy
@ -241,6 +243,18 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
return (ResultCode)_apmSystemManagerServer.GetCurrentPerformanceConfiguration(context);
}
[CommandHipc(300)] // 9.0.0+
// GetSettingsPlatformRegion() -> u8
public ResultCode GetSettingsPlatformRegion(ServiceCtx context)
{
PlatformRegion platformRegion = context.Device.System.State.DesiredRegionCode == (uint)RegionCode.China ? PlatformRegion.China : PlatformRegion.Global;
// FIXME: Call set:sys GetPlatformRegion
context.ResponseData.Write((byte)platformRegion);
return ResultCode.Success;
}
[CommandHipc(900)] // 11.0.0+
// SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled()
public ResultCode SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(ServiceCtx context)

View File

@ -1,7 +1,106 @@
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Memory;
using System;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy
{
class IDisplayController : IpcService
{
public IDisplayController() { }
private KTransferMemory _transferMem;
private bool _lastApplicationCaptureBufferAcquired;
private bool _callerAppletCaptureBufferAcquired;
public IDisplayController(ServiceCtx context)
{
_transferMem = context.Device.System.AppletCaptureBufferTransfer;
}
[CommandHipc(8)] // 2.0.0+
// TakeScreenShotOfOwnLayer(b8, s32)
public ResultCode TakeScreenShotOfOwnLayer(ServiceCtx context)
{
bool unknown1 = context.RequestData.ReadBoolean();
int unknown2 = context.RequestData.ReadInt32();
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { unknown1, unknown2 });
return ResultCode.Success;
}
[CommandHipc(11)]
// ReleaseLastApplicationCaptureBuffer()
public ResultCode ReleaseLastApplicationCaptureBuffer(ServiceCtx context)
{
if (!_lastApplicationCaptureBufferAcquired)
{
return ResultCode.BufferNotAcquired;
}
_lastApplicationCaptureBufferAcquired = false;
return ResultCode.Success;
}
[CommandHipc(15)]
// ReleaseCallerAppletCaptureBuffer()
public ResultCode ReleaseCallerAppletCaptureBuffer(ServiceCtx context)
{
if (!_callerAppletCaptureBufferAcquired)
{
return ResultCode.BufferNotAcquired;
}
_callerAppletCaptureBufferAcquired = false;
return ResultCode.Success;
}
[CommandHipc(16)]
// AcquireLastApplicationCaptureBufferEx() -> (b8, handle<copy>)
public ResultCode AcquireLastApplicationCaptureBufferEx(ServiceCtx context)
{
if (_lastApplicationCaptureBufferAcquired)
{
return ResultCode.BufferAlreadyAcquired;
}
if (context.Process.HandleTable.GenerateHandle(_transferMem, out int handle) != KernelResult.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
_lastApplicationCaptureBufferAcquired = true;
context.ResponseData.Write(_lastApplicationCaptureBufferAcquired);
return ResultCode.Success;
}
[CommandHipc(18)]
// AcquireCallerAppletCaptureBufferEx() -> (b8, handle<copy>)
public ResultCode AcquireCallerAppletCaptureBufferEx(ServiceCtx context)
{
if (_callerAppletCaptureBufferAcquired)
{
return ResultCode.BufferAlreadyAcquired;
}
if (context.Process.HandleTable.GenerateHandle(_transferMem, out int handle) != KernelResult.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
_callerAppletCaptureBufferAcquired = true;
context.ResponseData.Write(_callerAppletCaptureBufferAcquired);
return ResultCode.Success;
}
}
}

View File

@ -35,9 +35,9 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
private uint _screenShotImageOrientation = 0;
private uint _idleTimeDetectionExtension = 0;
public ISelfController(Horizon system, long pid)
public ISelfController(ServiceCtx context, long pid)
{
_libraryAppletLaunchableEvent = new KEvent(system.KernelContext);
_libraryAppletLaunchableEvent = new KEvent(context.Device.System.KernelContext);
_pid = pid;
}
@ -225,6 +225,15 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
return ResultCode.Success;
}
[CommandHipc(41)] // 4.0.0+
// IsSystemBufferSharingEnabled()
public ResultCode IsSystemBufferSharingEnabled(ServiceCtx context)
{
// NOTE: Service checks a private field and return an error if the SystemBufferSharing is disabled.
return ResultCode.NotImplemented;
}
[CommandHipc(44)] // 10.0.0+
// CreateManagedDisplaySeparableLayer() -> (u64, u64)
public ResultCode CreateManagedDisplaySeparableLayer(ServiceCtx context)

View File

@ -15,5 +15,15 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
return ResultCode.Success;
}
[CommandHipc(200)]
[CommandHipc(201)] // 3.0.0+
// OpenLibraryAppletProxy(u64, pid, handle<copy>) -> object<nn::am::service::ILibraryAppletProxy>
public ResultCode OpenLibraryAppletProxy(ServiceCtx context)
{
MakeObject(context, new ILibraryAppletProxy(context.Request.HandleDesc.PId));
return ResultCode.Success;
}
}
}

View File

@ -0,0 +1,12 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
{
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
struct AppletIdentifyInfo
{
public AppletId AppletId;
public uint Padding;
public ulong TitleId;
}
}

View File

@ -0,0 +1,12 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
{
[StructLayout(LayoutKind.Sequential, Size = 0x4)]
struct AppletProcessLaunchReason
{
public byte Flag;
public ushort Unknown1;
public byte Unknown2;
}
}

View File

@ -0,0 +1,11 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
{
[StructLayout(LayoutKind.Sequential, Size = 0x8)]
struct LibraryAppletInfo
{
public AppletId AppletId;
public LibraryAppletMode LibraryAppletMode;
}
}

View File

@ -0,0 +1,14 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
{
[Flags]
enum LibraryAppletMode : uint
{
AllForeground,
PartialForeground,
NoUi,
PartialForegroundWithIndirectDisplay,
AllForegroundInitiallyHidden
}
}

View File

@ -25,7 +25,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService
// GetSelfController() -> object<nn::am::service::ISelfController>
public ResultCode GetSelfController(ServiceCtx context)
{
MakeObject(context, new ISelfController(context.Device.System, _pid));
MakeObject(context, new ISelfController(context, _pid));
return ResultCode.Success;
}
@ -52,7 +52,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService
// GetDisplayController() -> object<nn::am::service::IDisplayController>
public ResultCode GetDisplayController(ServiceCtx context)
{
MakeObject(context, new IDisplayController());
MakeObject(context, new IDisplayController(context));
return ResultCode.Success;
}

View File

@ -14,6 +14,8 @@ namespace Ryujinx.HLE.HOS.Services.Am
ObjectInvalid = (500 << ErrorCodeShift) | ModuleId,
IStorageInUse = (502 << ErrorCodeShift) | ModuleId,
OutOfBounds = (503 << ErrorCodeShift) | ModuleId,
BufferNotAcquired = (504 << ErrorCodeShift) | ModuleId,
BufferAlreadyAcquired = (505 << ErrorCodeShift) | ModuleId,
InvalidParameters = (506 << ErrorCodeShift) | ModuleId,
OpenedAsWrongType = (511 << ErrorCodeShift) | ModuleId,
UnbalancedFatalSection = (512 << ErrorCodeShift) | ModuleId,

View File

@ -499,6 +499,15 @@ namespace Ryujinx.HLE.HOS.Services.Fs
return (ResultCode)result.Value;
}
[CommandHipc(1003)]
// DisableAutoSaveDataCreation()
public ResultCode DisableAutoSaveDataCreation(ServiceCtx context)
{
// NOTE: This call does nothing in original service.
return ResultCode.Success;
}
[CommandHipc(1004)]
// SetGlobalAccessLogMode(u32 mode)
public ResultCode SetGlobalAccessLogMode(ServiceCtx context)

View File

@ -36,7 +36,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii
public static UInt128 GetDeviceId()
{
// FIXME: call set:sys GetMiiAuthorId
return new UInt128(0, 1);
return new UInt128("5279754d69694e780000000000000000"); // RyuMiiNx
}
public static ReadOnlySpan<byte> Ver3FacelineColorTable => new byte[] { 0, 1, 2, 3, 4, 5 };

View File

@ -1,8 +1,41 @@
namespace Ryujinx.HLE.HOS.Services.Mii
using Ryujinx.Common.Logging;
namespace Ryujinx.HLE.HOS.Services.Mii
{
[Service("miiimg")] // 5.0.0+
class IImageDatabaseService : IpcService
{
private uint _imageCount;
private bool _isDirty;
public IImageDatabaseService(ServiceCtx context) { }
[CommandHipc(0)]
// Initialize(b8) -> b8
public ResultCode Initialize(ServiceCtx context)
{
// TODO: Service uses MiiImage:/database.dat if true, seems to use hardcoded data if false.
bool useHardcodedData = context.RequestData.ReadBoolean();
_imageCount = 0;
_isDirty = false;
context.ResponseData.Write(_isDirty);
Logger.Stub?.PrintStub(LogClass.ServiceMii, new { useHardcodedData });
return ResultCode.Success;
}
[CommandHipc(11)]
// GetCount() -> u32
public ResultCode GetCount(ServiceCtx context)
{
context.ResponseData.Write(_imageCount);
Logger.Stub?.PrintStub(LogClass.ServiceMii);
return ResultCode.Success;
}
}
}

View File

@ -101,6 +101,9 @@ namespace Ryujinx.HLE.HOS.Services.Mii
// Ensure we have valid data in the database
_database.Format();
// TODO: Unmount is currently not implemented properly at dispose, implement that and decrement MountCounter.
MountCounter = 0;
MountSave();
}
@ -151,8 +154,6 @@ namespace Ryujinx.HLE.HOS.Services.Mii
}
}
return result;
}
@ -183,6 +184,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii
if (result.IsSuccess())
{
result = _filesystemClient.GetFileSize(out long fileSize, handle);
if (result.IsSuccess())
{
if (fileSize == Unsafe.SizeOf<NintendoFigurineDatabase>())

View File

@ -7,6 +7,7 @@ using LibHac.FsSystem.NcaUtils;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.HLE.Utilities;
using System;
using System.IO;
using System.Text;
@ -271,6 +272,20 @@ namespace Ryujinx.HLE.HOS.Services.Settings
return ResultCode.Success;
}
[CommandHipc(90)]
// GetMiiAuthorId() -> nn::util::Uuid
public ResultCode GetMiiAuthorId(ServiceCtx context)
{
// NOTE: If miiAuthorId is null ResultCode.NullMiiAuthorIdBuffer is returned.
// Doesn't occur in our case.
UInt128 miiAuthorId = Mii.Helper.GetDeviceId();
miiAuthorId.Write(context.ResponseData);
return ResultCode.Success;
}
public byte[] GetFirmwareData(Switch device)
{
const ulong SystemVersionTitleId = 0x0100000000000809;

View File

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Settings.Types
{
enum PlatformRegion
{
Global = 1,
China = 2
}
}

View File

@ -55,8 +55,8 @@ namespace Ryujinx.HLE.HOS.SystemState
DesiredTitleLanguage = language switch
{
SystemLanguage.Taiwanese or
SystemLanguage.TraditionalChinese => TitleLanguage.Taiwanese,
SystemLanguage.Taiwanese => TitleLanguage.Taiwanese,
SystemLanguage.TraditionalChinese or
SystemLanguage.Chinese or
SystemLanguage.SimplifiedChinese => TitleLanguage.Chinese,
_ => Enum.Parse<TitleLanguage>(Enum.GetName(typeof(SystemLanguage), language)),

View File

@ -3,6 +3,7 @@ using ARMeilleure.Translation.PTC;
using Gtk;
using LibHac.Common;
using LibHac.FsSystem;
using LibHac.FsSystem.NcaUtils;
using LibHac.Ns;
using Ryujinx.Audio.Backends.Dummy;
using Ryujinx.Audio.Backends.OpenAL;
@ -87,6 +88,10 @@ namespace Ryujinx.Ui
[GUI] Box _statusBar;
[GUI] MenuItem _optionMenu;
[GUI] MenuItem _manageUserProfiles;
[GUI] MenuItem _fileMenu;
[GUI] MenuItem _loadApplicationFile;
[GUI] MenuItem _loadApplicationFolder;
[GUI] MenuItem _appletMenu;
[GUI] MenuItem _actionMenu;
[GUI] MenuItem _stopEmulation;
[GUI] MenuItem _simulateWakeUpMessage;
@ -165,6 +170,7 @@ namespace Ryujinx.Ui
_applicationLibrary.ApplicationAdded += Application_Added;
_applicationLibrary.ApplicationCountUpdated += ApplicationCount_Updated;
_fileMenu.StateChanged += FileMenu_StateChanged;
_actionMenu.StateChanged += ActionMenu_StateChanged;
_optionMenu.StateChanged += OptionMenu_StateChanged;
@ -576,6 +582,14 @@ namespace Ryujinx.Ui
SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion();
bool isDirectory = Directory.Exists(path);
bool isFirmwareTitle = false;
if (path.StartsWith("@SystemContent"))
{
path = _virtualFileSystem.SwitchPathToSystemPath(path);
isFirmwareTitle = true;
}
if (!SetupValidator.CanStartApplication(_contentManager, path, out UserError userError))
{
@ -636,7 +650,13 @@ namespace Ryujinx.Ui
Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}");
if (Directory.Exists(path))
if (isFirmwareTitle)
{
Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA).");
_emulationContext.LoadNca(path);
}
else if (Directory.Exists(path))
{
string[] romFsFiles = Directory.GetFiles(path, "*.istorage");
@ -1100,6 +1120,20 @@ namespace Ryujinx.Ui
}
}
private void FileMenu_StateChanged(object o, StateChangedArgs args)
{
_appletMenu.Sensitive = _emulationContext == null && _contentManager.GetCurrentFirmwareVersion() != null && _contentManager.GetCurrentFirmwareVersion().Major > 3;
_loadApplicationFile.Sensitive = _emulationContext == null;
_loadApplicationFolder.Sensitive = _emulationContext == null;
}
private void Load_Mii_Edit_Applet(object sender, EventArgs args)
{
string contentPath = _contentManager.GetInstalledContentPath(0x0100000000001009, StorageId.NandSystem, NcaContentType.Program);
LoadApplication(contentPath);
}
private void Open_Ryu_Folder(object sender, EventArgs args)
{
OpenHelper.OpenFolder(AppDataManager.BaseDirPath);
@ -1217,6 +1251,15 @@ namespace Ryujinx.Ui
GtkDialog.CreateInfoDialog(dialogTitle, message);
Logger.Info?.Print(LogClass.Application, message);
// Purge Applet Cache.
DirectoryInfo miiEditorCacheFolder = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache"));
if (miiEditorCacheFolder.Exists)
{
miiEditorCacheFolder.Delete(true);
}
});
}
catch (Exception ex)

View File

@ -19,7 +19,7 @@
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkMenuItem" id="FileMenu">
<object class="GtkMenuItem" id="_fileMenu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">File</property>
@ -29,7 +29,7 @@
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkMenuItem" id="LoadApplicationFile">
<object class="GtkMenuItem" id="_loadApplicationFile">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Open a file chooser to chose a switch compatible file to load</property>
@ -39,7 +39,7 @@
</object>
</child>
<child>
<object class="GtkMenuItem" id="LoadApplicationFolder">
<object class="GtkMenuItem" id="_loadApplicationFolder">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Open a file chooser to chose a switch compatible, unpacked application to load</property>
@ -48,6 +48,30 @@
<signal name="activate" handler="Load_Application_Folder" swapped="no"/>
</object>
</child>
<child>
<object class="GtkMenuItem" id="_appletMenu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Load Applet</property>
<property name="use_underline">True</property>
<child type="submenu">
<object class="GtkMenu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkMenuItem" id="LoadMiiEditApplet">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Open Mii Editor Applet in Standalone mode</property>
<property name="label" translatable="yes">Mii Editor</property>
<property name="use_underline">True</property>
<signal name="activate" handler="Load_Mii_Edit_Applet" swapped="no"/>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem">
<property name="visible">True</property>