Merge branch 'master' into aot
|
@ -1,8 +1,8 @@
|
|||
|
||||
<h1>
|
||||
<img src="https://i.imgur.com/G6Mleco.png"> Ryujinx
|
||||
<a href="https://ci.appveyor.com/project/gdkchan/ryujinx" target="_blank">
|
||||
<img src="https://ci.appveyor.com/api/projects/status/ssg4jwu6ve3k594s?svg=true">
|
||||
<a href="https://ci.appveyor.com/project/gdkchan/ryujinx?branch=master" target="_blank">
|
||||
<img src="https://ci.appveyor.com/api/projects/status/ssg4jwu6ve3k594s/branch/master?svg=true">
|
||||
</a>
|
||||
<a href="https://discord.gg/N2FmfVc">
|
||||
<img src="https://img.shields.io/discord/410208534861447168.svg">
|
||||
|
|
|
@ -26,6 +26,7 @@ namespace Ryujinx.Common.Logging
|
|||
ServiceFs,
|
||||
ServiceHid,
|
||||
ServiceIrs,
|
||||
ServiceLdn,
|
||||
ServiceLdr,
|
||||
ServiceLm,
|
||||
ServiceMm,
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
{
|
||||
public static class EndianSwap
|
||||
{
|
||||
public static ushort Swap16(ushort value) => (ushort)(((value >> 8) & 0xff) | (value << 8));
|
||||
|
||||
public static int Swap32(int value)
|
||||
{
|
||||
uint uintVal = (uint)value;
|
||||
|
||||
return (int)(((uintVal >> 24) & 0x000000ff) |
|
||||
((uintVal >> 8) & 0x0000ff00) |
|
||||
((uintVal << 8) & 0x00ff0000) |
|
||||
((uintVal << 24) & 0xff000000));
|
||||
}
|
||||
|
||||
public static uint FromBigEndianToPlatformEndian(uint value)
|
||||
{
|
||||
uint result = value;
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
result = (uint)EndianSwap.Swap32((int)result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ using Ryujinx.Common;
|
|||
using Ryujinx.HLE.Exceptions;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.FileSystem.Content;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
|
@ -142,7 +143,7 @@ namespace Ryujinx.HLE.HOS.Font
|
|||
const int decMagic = 0x18029a7f;
|
||||
const int key = 0x49621806;
|
||||
|
||||
int encryptedSize = EndianSwap.Swap32(size ^ key);
|
||||
int encryptedSize = BinaryPrimitives.ReverseEndianness(size ^ key);
|
||||
|
||||
_device.Memory.WriteInt32(position + 0, decMagic);
|
||||
_device.Memory.WriteInt32(position + 4, encryptedSize);
|
||||
|
|
|
@ -106,11 +106,9 @@ namespace Ryujinx.HLE.HOS
|
|||
|
||||
public Nacp ControlData { get; set; }
|
||||
|
||||
public string CurrentTitle { get; private set; }
|
||||
|
||||
public string TitleName { get; private set; }
|
||||
|
||||
public string TitleID { get; private set; }
|
||||
public string TitleId { get; private set; }
|
||||
|
||||
public IntegrityCheckLevel FsIntegrityCheckLevel { get; set; }
|
||||
|
||||
|
@ -367,7 +365,7 @@ namespace Ryujinx.HLE.HOS
|
|||
{
|
||||
ControlData = new Nacp(controlFile.AsStream());
|
||||
|
||||
TitleName = CurrentTitle = ControlData.Descriptions[(int) State.DesiredTitleLanguage].Title;
|
||||
TitleName = ControlData.Descriptions[(int)State.DesiredTitleLanguage].Title;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -501,12 +499,12 @@ namespace Ryujinx.HLE.HOS
|
|||
|
||||
Nacp controlData = new Nacp(controlFile.AsStream());
|
||||
|
||||
TitleName = CurrentTitle = controlData.Descriptions[(int)State.DesiredTitleLanguage].Title;
|
||||
TitleID = metaData.Aci0.TitleId.ToString("x16");
|
||||
TitleName = controlData.Descriptions[(int)State.DesiredTitleLanguage].Title;
|
||||
TitleId = metaData.Aci0.TitleId.ToString("x16");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(CurrentTitle))
|
||||
if (string.IsNullOrWhiteSpace(TitleName))
|
||||
{
|
||||
TitleName = CurrentTitle = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title;
|
||||
TitleName = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title;
|
||||
}
|
||||
|
||||
return controlData;
|
||||
|
@ -518,7 +516,7 @@ namespace Ryujinx.HLE.HOS
|
|||
}
|
||||
else
|
||||
{
|
||||
TitleID = CurrentTitle = metaData.Aci0.TitleId.ToString("x16");
|
||||
TitleId = metaData.Aci0.TitleId.ToString("x16");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -558,7 +556,7 @@ namespace Ryujinx.HLE.HOS
|
|||
}
|
||||
}
|
||||
|
||||
TitleID = CurrentTitle = metaData.Aci0.TitleId.ToString("x16");
|
||||
TitleId = metaData.Aci0.TitleId.ToString("x16");
|
||||
|
||||
LoadNso("rtld");
|
||||
LoadNso("main");
|
||||
|
@ -664,8 +662,8 @@ namespace Ryujinx.HLE.HOS
|
|||
|
||||
ContentManager.LoadEntries();
|
||||
|
||||
TitleName = CurrentTitle = metaData.TitleName;
|
||||
TitleID = metaData.Aci0.TitleId.ToString("x16");
|
||||
TitleName = metaData.TitleName;
|
||||
TitleId = metaData.Aci0.TitleId.ToString("x16");
|
||||
|
||||
ProgramLoader.LoadStaticObjects(this, metaData, new IExecutable[] { staticObject });
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp
|
|||
|
||||
return new ApplicationLaunchProperty
|
||||
{
|
||||
TitleId = BitConverter.ToInt64(StringUtils.HexToBytes(context.Device.System.TitleID), 0),
|
||||
TitleId = BitConverter.ToInt64(StringUtils.HexToBytes(context.Device.System.TitleId), 0),
|
||||
Version = 0x00,
|
||||
BaseGameStorageId = (byte)StorageId.NandSystem,
|
||||
UpdateGameStorageId = (byte)StorageId.None
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using Ryujinx.Common;
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
@ -15,8 +16,8 @@ namespace Ryujinx.HLE.HOS.Services.Audio.Types
|
|||
{
|
||||
OpusPacketHeader header = reader.ReadStruct<OpusPacketHeader>();
|
||||
|
||||
header.length = EndianSwap.FromBigEndianToPlatformEndian(header.length);
|
||||
header.finalRange = EndianSwap.FromBigEndianToPlatformEndian(header.finalRange);
|
||||
header.length = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(header.length) : header.length;
|
||||
header.finalRange = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(header.finalRange) : header.finalRange;
|
||||
|
||||
return header;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,19 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Ldn
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn
|
||||
{
|
||||
[Service("ldn:u")]
|
||||
class IUserServiceCreator : IpcService
|
||||
{
|
||||
public IUserServiceCreator(ServiceCtx context) { }
|
||||
|
||||
[Command(0)]
|
||||
// CreateUserLocalCommunicationService() -> object<nn::ldn::detail::IUserLocalCommunicationService>
|
||||
public ResultCode CreateUserLocalCommunicationService(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new IUserLocalCommunicationService(context));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
59
Ryujinx.HLE/HOS/Services/Ldn/NetworkInterface.cs
Normal file
|
@ -0,0 +1,59 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using System.Net;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn
|
||||
{
|
||||
internal class NetworkInterface
|
||||
{
|
||||
public ResultCode NifmState { get; set; }
|
||||
public KEvent StateChangeEvent { get; private set; }
|
||||
|
||||
private NetworkState _state;
|
||||
|
||||
public NetworkInterface(Horizon system)
|
||||
{
|
||||
// TODO(Ac_K): Determine where the internal state is set.
|
||||
NifmState = ResultCode.Success;
|
||||
StateChangeEvent = new KEvent(system);
|
||||
|
||||
_state = NetworkState.None;
|
||||
}
|
||||
|
||||
public ResultCode Initialize(int unknown, int version, IPAddress ipv4Address, IPAddress subnetMaskAddress)
|
||||
{
|
||||
// TODO(Ac_K): Call nn::nifm::InitializeSystem().
|
||||
// If the call failed, it returns the result code.
|
||||
// If the call succeed, it signal and clear an event then start a new thread named nn.ldn.NetworkInterfaceMonitor.
|
||||
|
||||
Logger.PrintStub(LogClass.ServiceLdn, new { version });
|
||||
|
||||
// NOTE: Since we don't support ldn for now, we can return this following result code to make it disabled.
|
||||
return ResultCode.DeviceDisabled;
|
||||
}
|
||||
|
||||
public ResultCode GetState(out NetworkState state)
|
||||
{
|
||||
// Return ResultCode.InvalidArgument if _state is null, doesn't occur in our case.
|
||||
|
||||
state = _state;
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode Finalize()
|
||||
{
|
||||
// TODO(Ac_K): Finalize nifm service then kill the thread named nn.ldn.NetworkInterfaceMonitor.
|
||||
|
||||
_state = NetworkState.None;
|
||||
|
||||
StateChangeEvent.WritableEvent.Signal();
|
||||
StateChangeEvent.WritableEvent.Clear();
|
||||
|
||||
Logger.PrintStub(LogClass.ServiceLdn);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
16
Ryujinx.HLE/HOS/Services/Ldn/ResultCode.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Ldn
|
||||
{
|
||||
enum ResultCode
|
||||
{
|
||||
ModuleId = 203,
|
||||
ErrorCodeShift = 9,
|
||||
|
||||
Success = 0,
|
||||
|
||||
DeviceDisabled = (22 << ErrorCodeShift) | ModuleId,
|
||||
InvalidState = (32 << ErrorCodeShift) | ModuleId,
|
||||
Unknown1 = (48 << ErrorCodeShift) | ModuleId,
|
||||
InvalidArgument = (96 << ErrorCodeShift) | ModuleId,
|
||||
InvalidObject = (97 << ErrorCodeShift) | ModuleId,
|
||||
}
|
||||
}
|
13
Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkState.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
{
|
||||
enum NetworkState
|
||||
{
|
||||
None,
|
||||
Initialized,
|
||||
AccessPoint,
|
||||
AccessPointCreated,
|
||||
Station,
|
||||
StationConnected,
|
||||
Error
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
{
|
||||
class IUserLocalCommunicationService : IpcService
|
||||
{
|
||||
// TODO(Ac_K): Determine what the hardcoded unknown value is.
|
||||
private const int UnknownValue = 90;
|
||||
|
||||
private NetworkInterface _networkInterface;
|
||||
|
||||
private int _stateChangeEventHandle = 0;
|
||||
|
||||
public IUserLocalCommunicationService(ServiceCtx context)
|
||||
{
|
||||
_networkInterface = new NetworkInterface(context.Device.System);
|
||||
}
|
||||
|
||||
[Command(0)]
|
||||
// GetState() -> s32 state
|
||||
public ResultCode GetState(ServiceCtx context)
|
||||
{
|
||||
if (_networkInterface.NifmState != ResultCode.Success)
|
||||
{
|
||||
context.ResponseData.Write((int)NetworkState.Error);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
ResultCode result = _networkInterface.GetState(out NetworkState state);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
context.ResponseData.Write((int)state);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[Command(100)]
|
||||
// AttachStateChangeEvent() -> handle<copy>
|
||||
public ResultCode AttachStateChangeEvent(ServiceCtx context)
|
||||
{
|
||||
if (_stateChangeEventHandle == 0)
|
||||
{
|
||||
if (context.Process.HandleTable.GenerateHandle(_networkInterface.StateChangeEvent.ReadableEvent, out _stateChangeEventHandle) != KernelResult.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
}
|
||||
}
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_stateChangeEventHandle);
|
||||
|
||||
// Return ResultCode.InvalidArgument if handle is null, doesn't occur in our case since we already throw an Exception.
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[Command(400)]
|
||||
// InitializeOld(u64, pid)
|
||||
public ResultCode InitializeOld(ServiceCtx context)
|
||||
{
|
||||
return _networkInterface.Initialize(UnknownValue, 0, null, null);
|
||||
}
|
||||
|
||||
[Command(401)]
|
||||
// Finalize()
|
||||
public ResultCode Finalize(ServiceCtx context)
|
||||
{
|
||||
return _networkInterface.Finalize();
|
||||
}
|
||||
|
||||
[Command(402)] // 7.0.0+
|
||||
// Initialize(u64 ip_addresses, u64, pid)
|
||||
public ResultCode Initialize(ServiceCtx context)
|
||||
{
|
||||
// TODO(Ac_K): Determine what addresses are.
|
||||
IPAddress unknownAddress1 = new IPAddress(context.RequestData.ReadUInt32());
|
||||
IPAddress unknownAddress2 = new IPAddress(context.RequestData.ReadUInt32());
|
||||
|
||||
return _networkInterface.Initialize(UnknownValue, version: 1, unknownAddress1, unknownAddress2);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,9 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Prepo
|
||||
{
|
||||
|
@ -9,13 +14,182 @@ namespace Ryujinx.HLE.HOS.Services.Prepo
|
|||
{
|
||||
public IPrepoService(ServiceCtx context) { }
|
||||
|
||||
[Command(10101)]
|
||||
// SaveReportWithUser(nn::account::Uid, u64, pid, buffer<u8, 9>, buffer<bytes, 5>)
|
||||
public static ResultCode SaveReportWithUser(ServiceCtx context)
|
||||
[Command(10100)] // 1.0.0-5.1.0
|
||||
// SaveReport(u64, pid, buffer<u8, 9>, buffer<bytes, 5>)
|
||||
public ResultCode SaveReportOld(ServiceCtx context)
|
||||
{
|
||||
Logger.PrintStub(LogClass.ServicePrepo);
|
||||
// We don't care about the differences since we don't use the play report.
|
||||
return ProcessReport(context, withUserID: false);
|
||||
}
|
||||
|
||||
[Command(10101)] // 1.0.0-5.1.0
|
||||
// SaveReportWithUserOld(nn::account::Uid, u64, pid, buffer<u8, 9>, buffer<bytes, 5>)
|
||||
public ResultCode SaveReportWithUserOld(ServiceCtx context)
|
||||
{
|
||||
// We don't care about the differences since we don't use the play report.
|
||||
return ProcessReport(context, withUserID: true);
|
||||
}
|
||||
|
||||
[Command(10102)] // 6.0.0+
|
||||
// SaveReport(u64, pid, buffer<u8, 9>, buffer<bytes, 5>)
|
||||
public ResultCode SaveReport(ServiceCtx context)
|
||||
{
|
||||
// We don't care about the differences since we don't use the play report.
|
||||
return ProcessReport(context, withUserID: false);
|
||||
}
|
||||
|
||||
[Command(10103)] // 6.0.0+
|
||||
// SaveReportWithUser(nn::account::Uid, u64, pid, buffer<u8, 9>, buffer<bytes, 5>)
|
||||
public ResultCode SaveReportWithUser(ServiceCtx context)
|
||||
{
|
||||
// We don't care about the differences since we don't use the play report.
|
||||
return ProcessReport(context, withUserID: true);
|
||||
}
|
||||
|
||||
private ResultCode ProcessReport(ServiceCtx context, bool withUserID)
|
||||
{
|
||||
UInt128 userId = withUserID ? new UInt128(context.RequestData.ReadBytes(0x10)) : new UInt128();
|
||||
string gameRoom = StringUtils.ReadUtf8String(context);
|
||||
|
||||
if (withUserID)
|
||||
{
|
||||
if (userId.IsNull)
|
||||
{
|
||||
return ResultCode.InvalidArgument;
|
||||
}
|
||||
}
|
||||
|
||||
if (gameRoom == string.Empty)
|
||||
{
|
||||
return ResultCode.InvalidState;
|
||||
}
|
||||
|
||||
long inputPosition = context.Request.SendBuff[0].Position;
|
||||
long inputSize = context.Request.SendBuff[0].Size;
|
||||
|
||||
if (inputSize == 0)
|
||||
{
|
||||
return ResultCode.InvalidBufferSize;
|
||||
}
|
||||
|
||||
byte[] inputBuffer = context.Memory.ReadBytes(inputPosition, inputSize);
|
||||
|
||||
Logger.PrintInfo(LogClass.ServicePrepo, ReadReportBuffer(inputBuffer, gameRoom, userId));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public string ReadReportBuffer(byte[] buffer, string room, UInt128 userId)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("PlayReport log:");
|
||||
|
||||
if (!userId.IsNull)
|
||||
{
|
||||
sb.AppendLine($" UserId: {userId.ToString()}");
|
||||
}
|
||||
|
||||
sb.AppendLine($" Room: {room}");
|
||||
|
||||
try
|
||||
{
|
||||
using (MemoryStream stream = new MemoryStream(buffer))
|
||||
using (BinaryReader reader = new BinaryReader(stream))
|
||||
{
|
||||
byte unknown1 = reader.ReadByte(); // Version ?
|
||||
short unknown2 = reader.ReadInt16(); // Size ?
|
||||
|
||||
bool isValue = false;
|
||||
|
||||
string fieldStr = string.Empty;
|
||||
|
||||
while (stream.Position != stream.Length)
|
||||
{
|
||||
byte descriptor = reader.ReadByte();
|
||||
|
||||
if (!isValue)
|
||||
{
|
||||
byte[] key = reader.ReadBytes(descriptor - 0xA0);
|
||||
|
||||
fieldStr = $" Key: {Encoding.ASCII.GetString(key)}";
|
||||
|
||||
isValue = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (descriptor > 0xD0) // Int value.
|
||||
{
|
||||
if (descriptor - 0xD0 == 1)
|
||||
{
|
||||
fieldStr += $", Value: {BinaryPrimitives.ReverseEndianness(reader.ReadUInt16())}";
|
||||
}
|
||||
else if (descriptor - 0xD0 == 2)
|
||||
{
|
||||
fieldStr += $", Value: {BinaryPrimitives.ReverseEndianness(reader.ReadInt32())}";
|
||||
}
|
||||
else if (descriptor - 0xD0 == 4)
|
||||
{
|
||||
fieldStr += $", Value: {BinaryPrimitives.ReverseEndianness(reader.ReadInt64())}";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unknown.
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (descriptor > 0xA0 && descriptor < 0xD0) // String value, max size = 0x20 bytes ?
|
||||
{
|
||||
int size = descriptor - 0xA0;
|
||||
string value = string.Empty;
|
||||
byte[] rawValues = new byte[0];
|
||||
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
byte chr = reader.ReadByte();
|
||||
|
||||
if (chr >= 0x20 && chr < 0x7f)
|
||||
{
|
||||
value += (char)chr;
|
||||
}
|
||||
else
|
||||
{
|
||||
Array.Resize(ref rawValues, rawValues.Length + 1);
|
||||
|
||||
rawValues[rawValues.Length - 1] = chr;
|
||||
}
|
||||
}
|
||||
|
||||
if (value != string.Empty)
|
||||
{
|
||||
fieldStr += $", Value: {value}";
|
||||
}
|
||||
|
||||
// TODO(Ac_K): Determine why there are non-alphanumeric values sometimes.
|
||||
if (rawValues.Length > 0)
|
||||
{
|
||||
fieldStr += $", RawValue: 0x{BitConverter.ToString(rawValues).Replace("-", "")}";
|
||||
}
|
||||
}
|
||||
else // Byte value.
|
||||
{
|
||||
fieldStr += $", Value: {descriptor}";
|
||||
}
|
||||
|
||||
sb.AppendLine(fieldStr);
|
||||
|
||||
isValue = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
sb.AppendLine(" Error while parsing the report buffer.");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
15
Ryujinx.HLE/HOS/Services/Prepo/ResultCode.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Prepo
|
||||
{
|
||||
enum ResultCode
|
||||
{
|
||||
ModuleId = 129,
|
||||
ErrorCodeShift = 9,
|
||||
|
||||
Success = 0,
|
||||
|
||||
InvalidArgument = (1 << ErrorCodeShift) | ModuleId,
|
||||
InvalidState = (5 << ErrorCodeShift) | ModuleId,
|
||||
InvalidBufferSize = (9 << ErrorCodeShift) | ModuleId,
|
||||
Unknown1 = (90 << ErrorCodeShift) | ModuleId
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
using ARMeilleure.Memory;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
|
@ -265,13 +266,15 @@ namespace Ryujinx.HLE.HOS.Services.Ro
|
|||
|
||||
int retryCount;
|
||||
|
||||
int addressSpacePageLimit = (int)((memMgr.GetAddrSpaceSize() - size) >> 12);
|
||||
ulong addressSpacePageLimit = (memMgr.GetAddrSpaceSize() - size) >> 12;
|
||||
|
||||
for (retryCount = 0; retryCount < MaxMapRetries; retryCount++)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
targetAddress = memMgr.GetAddrSpaceBaseAddr() + (ulong)(_random.Next(addressSpacePageLimit) << 12);
|
||||
ulong randomOffset = (ulong)(uint)_random.Next(0, (int)addressSpacePageLimit) << 12;
|
||||
|
||||
targetAddress = memMgr.GetAddrSpaceBaseAddr() + randomOffset;
|
||||
|
||||
if (memMgr.InsideAddrSpace(targetAddress, size) && !memMgr.InsideHeapRegion(targetAddress, size) && !memMgr.InsideAliasRegion(targetAddress, size))
|
||||
{
|
||||
|
@ -449,6 +452,8 @@ namespace Ryujinx.HLE.HOS.Services.Ro
|
|||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
info.NroMappedAddress = nroMappedAddress;
|
||||
|
||||
_nroInfos.Add(info);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
enum ResultCode
|
||||
{
|
||||
ModuleId = 22,
|
||||
ErrorCodeShift = 22,
|
||||
ErrorCodeShift = 9,
|
||||
|
||||
Success = 0,
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
@ -199,7 +200,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
|
|||
{
|
||||
int size = context.Memory.ReadByte(bufferPosition);
|
||||
int family = context.Memory.ReadByte(bufferPosition + 1);
|
||||
int port = EndianSwap.Swap16(context.Memory.ReadUInt16(bufferPosition + 2));
|
||||
int port = BinaryPrimitives.ReverseEndianness(context.Memory.ReadUInt16(bufferPosition + 2));
|
||||
|
||||
byte[] rawIp = context.Memory.ReadBytes(bufferPosition + 4, 4);
|
||||
|
||||
|
@ -210,7 +211,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
|
|||
{
|
||||
context.Memory.WriteByte(bufferPosition, 0);
|
||||
context.Memory.WriteByte(bufferPosition + 1, (byte)endPoint.AddressFamily);
|
||||
context.Memory.WriteUInt16(bufferPosition + 2, EndianSwap.Swap16((ushort)endPoint.Port));
|
||||
context.Memory.WriteUInt16(bufferPosition + 2, BinaryPrimitives.ReverseEndianness((ushort)endPoint.Port));
|
||||
context.Memory.WriteBytes(bufferPosition + 4, endPoint.Address.GetAddressBytes());
|
||||
}
|
||||
|
||||
|
|
|
@ -290,8 +290,8 @@ namespace Ryujinx.HLE.HOS.Services.Time
|
|||
// CalculateStandardUserSystemClockDifferenceByUser(buffer<nn::time::sf::ClockSnapshot, 0x19>, buffer<nn::time::sf::ClockSnapshot, 0x19>) -> nn::TimeSpanType
|
||||
public ResultCode CalculateStandardUserSystemClockDifferenceByUser(ServiceCtx context)
|
||||
{
|
||||
ClockSnapshot clockSnapshotA = ReadClockSnapshotFromBuffer(context, context.Request.ExchangeBuff[0]);
|
||||
ClockSnapshot clockSnapshotB = ReadClockSnapshotFromBuffer(context, context.Request.ExchangeBuff[1]);
|
||||
ClockSnapshot clockSnapshotA = ReadClockSnapshotFromBuffer(context, context.Request.PtrBuff[0]);
|
||||
ClockSnapshot clockSnapshotB = ReadClockSnapshotFromBuffer(context, context.Request.PtrBuff[1]);
|
||||
TimeSpanType difference = TimeSpanType.FromSeconds(clockSnapshotB.UserContext.Offset - clockSnapshotA.UserContext.Offset);
|
||||
|
||||
if (clockSnapshotB.UserContext.SteadyTimePoint.ClockSourceId != clockSnapshotA.UserContext.SteadyTimePoint.ClockSourceId || (clockSnapshotB.IsAutomaticCorrectionEnabled && clockSnapshotA.IsAutomaticCorrectionEnabled))
|
||||
|
@ -308,8 +308,8 @@ namespace Ryujinx.HLE.HOS.Services.Time
|
|||
// CalculateSpanBetween(buffer<nn::time::sf::ClockSnapshot, 0x19>, buffer<nn::time::sf::ClockSnapshot, 0x19>) -> nn::TimeSpanType
|
||||
public ResultCode CalculateSpanBetween(ServiceCtx context)
|
||||
{
|
||||
ClockSnapshot clockSnapshotA = ReadClockSnapshotFromBuffer(context, context.Request.ExchangeBuff[0]);
|
||||
ClockSnapshot clockSnapshotB = ReadClockSnapshotFromBuffer(context, context.Request.ExchangeBuff[1]);
|
||||
ClockSnapshot clockSnapshotA = ReadClockSnapshotFromBuffer(context, context.Request.PtrBuff[0]);
|
||||
ClockSnapshot clockSnapshotB = ReadClockSnapshotFromBuffer(context, context.Request.PtrBuff[1]);
|
||||
|
||||
TimeSpanType result;
|
||||
|
||||
|
@ -397,7 +397,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
|
|||
return result;
|
||||
}
|
||||
|
||||
private ClockSnapshot ReadClockSnapshotFromBuffer(ServiceCtx context, IpcBuffDesc ipcDesc)
|
||||
private ClockSnapshot ReadClockSnapshotFromBuffer(ServiceCtx context, IpcPtrBuffDesc ipcDesc)
|
||||
{
|
||||
Debug.Assert(ipcDesc.Size == Marshal.SizeOf<ClockSnapshot>());
|
||||
|
||||
|
|
|
@ -1707,7 +1707,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
|||
Time = new CalendarTime()
|
||||
{
|
||||
Year = (short)calendarTime.Year,
|
||||
Month = (sbyte)(calendarTime.Month + 1),
|
||||
Month = calendarTime.Month,
|
||||
Day = calendarTime.Day,
|
||||
Hour = calendarTime.Hour,
|
||||
Minute = calendarTime.Minute,
|
||||
|
|
|
@ -3,7 +3,7 @@ using System.IO;
|
|||
|
||||
namespace Ryujinx.HLE.Loaders.Npdm
|
||||
{
|
||||
class Aci0
|
||||
public class Aci0
|
||||
{
|
||||
private const int Aci0Magic = 'A' << 0 | 'C' << 8 | 'I' << 16 | '0' << 24;
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ using System.IO;
|
|||
|
||||
namespace Ryujinx.HLE.Loaders.Npdm
|
||||
{
|
||||
class Acid
|
||||
public class Acid
|
||||
{
|
||||
private const int AcidMagic = 'A' << 0 | 'C' << 8 | 'I' << 16 | 'D' << 24;
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Ryujinx.HLE.Loaders.Npdm
|
||||
{
|
||||
class FsAccessControl
|
||||
public class FsAccessControl
|
||||
{
|
||||
public int Version { get; private set; }
|
||||
public ulong PermissionsBitmask { get; private set; }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Ryujinx.HLE.Loaders.Npdm
|
||||
{
|
||||
class KernelAccessControl
|
||||
public class KernelAccessControl
|
||||
{
|
||||
public int[] Capabilities { get; private set; }
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace Ryujinx.HLE.Loaders.Npdm
|
|||
// https://github.com/SciresM/hactool/blob/master/npdm.c
|
||||
// https://github.com/SciresM/hactool/blob/master/npdm.h
|
||||
// http://switchbrew.org/index.php?title=NPDM
|
||||
class Npdm
|
||||
public class Npdm
|
||||
{
|
||||
private const int MetaMagic = 'M' << 0 | 'E' << 8 | 'T' << 16 | 'A' << 24;
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ using System.Text;
|
|||
|
||||
namespace Ryujinx.HLE.Loaders.Npdm
|
||||
{
|
||||
class ServiceAccessControl
|
||||
public class ServiceAccessControl
|
||||
{
|
||||
public IReadOnlyDictionary<string, bool> Services { get; private set; }
|
||||
|
||||
|
|
|
@ -4,9 +4,15 @@
|
|||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseExplicitType</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseExplicitType</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="I" Suffix="" Style="AaBb" /></Policy></s:String>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=ASET/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Astc/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Luma/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=mins/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=nacp/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Npad/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=patreon/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Probs/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Ryujinx/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Sint/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Snorm/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Srgb/@EntryIndexedValue">True</s:Boolean>
|
||||
|
|
|
@ -7,7 +7,9 @@
|
|||
"logging_enable_error": true,
|
||||
"logging_enable_guest": true,
|
||||
"logging_enable_fs_access_log": false,
|
||||
"logging_filtered_classes": [ ],
|
||||
"logging_filtered_classes": [
|
||||
|
||||
],
|
||||
"enable_file_log": true,
|
||||
"system_language": "AmericanEnglish",
|
||||
"docked_mode": false,
|
||||
|
@ -15,12 +17,27 @@
|
|||
"enable_vsync": true,
|
||||
"enable_multicore_scheduling": true,
|
||||
"enable_fs_integrity_checks": true,
|
||||
"fs_global_access_log_mode": 0,
|
||||
"ignore_missing_services": false,
|
||||
"controller_type": "Handheld",
|
||||
"gui_columns": [ true, true, true, true, true, true, true, true, true ],
|
||||
"game_dirs": [],
|
||||
"gui_columns": {
|
||||
"fav_column": true,
|
||||
"icon_column": true,
|
||||
"app_column": true,
|
||||
"dev_column": true,
|
||||
"version_column": true,
|
||||
"time_played_column": true,
|
||||
"last_played_column": true,
|
||||
"file_ext_column": true,
|
||||
"file_size_column": true,
|
||||
"path_column": true
|
||||
},
|
||||
"game_dirs": [
|
||||
|
||||
],
|
||||
"enable_custom_theme": false,
|
||||
"custom_theme_path": "",
|
||||
"enable_keyboard": false,
|
||||
"keyboard_controls": {
|
||||
"left_joycon": {
|
||||
"stick_up": "W",
|
||||
|
@ -54,7 +71,7 @@
|
|||
"toggle_vsync": "Tab"
|
||||
}
|
||||
},
|
||||
"joystick_controls": {
|
||||
"joystick_controls": {
|
||||
"enabled": true,
|
||||
"index": 0,
|
||||
"deadzone": 0.05,
|
||||
|
@ -82,4 +99,4 @@
|
|||
"button_zr": "Axis5"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,8 +7,8 @@ using Ryujinx.HLE;
|
|||
using Ryujinx.HLE.HOS.SystemState;
|
||||
using Ryujinx.HLE.HOS.Services;
|
||||
using Ryujinx.HLE.Input;
|
||||
using Ryujinx.UI;
|
||||
using Ryujinx.UI.Input;
|
||||
using Ryujinx.Ui;
|
||||
using Ryujinx.Ui.Input;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
@ -124,7 +124,7 @@ namespace Ryujinx
|
|||
/// <summary>
|
||||
/// Used to toggle columns in the GUI
|
||||
/// </summary>
|
||||
public List<bool> GuiColumns { get; set; }
|
||||
public GuiColumns GuiColumns { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of directories containing games to be used to load games into the games list
|
||||
|
@ -154,7 +154,7 @@ namespace Ryujinx
|
|||
/// <summary>
|
||||
/// Controller control bindings
|
||||
/// </summary>
|
||||
public UI.Input.NpadController JoystickControls { get; private set; }
|
||||
public Ui.Input.NpadController JoystickControls { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Loads a configuration file from disk
|
||||
|
|
|
@ -2,7 +2,7 @@ using ARMeilleure.Translation.AOT;
|
|||
using Gtk;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Profiler;
|
||||
using Ryujinx.UI;
|
||||
using Ryujinx.Ui;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
|
@ -19,16 +19,20 @@ namespace Ryujinx
|
|||
|
||||
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
||||
AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit;
|
||||
GLib.ExceptionManager.UnhandledException += Glib_UnhandledException;
|
||||
|
||||
Profile.Initialize();
|
||||
|
||||
Application.Init();
|
||||
|
||||
Application gtkApplication = new Application("Ryujinx.Ryujinx", GLib.ApplicationFlags.None);
|
||||
MainWindow mainWindow = new MainWindow(args, gtkApplication);
|
||||
string appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RyuFs", "system", "prod.keys");
|
||||
string userProfilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".switch", "prod.keys");
|
||||
if (!File.Exists(appDataPath) && !File.Exists(userProfilePath))
|
||||
{
|
||||
GtkDialog.CreateErrorDialog($"Key file was not found. Please refer to `KEYS.md` for more info");
|
||||
}
|
||||
|
||||
gtkApplication.Register(GLib.Cancellable.Current);
|
||||
gtkApplication.AddWindow(mainWindow);
|
||||
MainWindow mainWindow = new MainWindow();
|
||||
mainWindow.Show();
|
||||
|
||||
if (args.Length == 1)
|
||||
|
@ -47,7 +51,7 @@ namespace Ryujinx
|
|||
|
||||
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
var exception = e.ExceptionObject as Exception;
|
||||
Exception exception = e.ExceptionObject as Exception;
|
||||
|
||||
Logger.PrintError(LogClass.Emulation, $"Unhandled exception caught: {exception}");
|
||||
|
||||
|
@ -57,5 +61,17 @@ namespace Ryujinx
|
|||
Logger.Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
private static void Glib_UnhandledException(GLib.UnhandledExceptionArgs e)
|
||||
{
|
||||
Exception exception = e.ExceptionObject as Exception;
|
||||
|
||||
Logger.PrintError(LogClass.Application, $"Unhandled exception caught: {exception}");
|
||||
|
||||
if (e.IsTerminating)
|
||||
{
|
||||
Logger.Shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
010034e005c9c000
|
||||
01004f8006a78000
|
||||
010051f00ac5e000
|
||||
010056e00853a000
|
||||
0100574009f9e000
|
||||
0100628004bce000
|
||||
0100633007d48000
|
||||
|
@ -16,15 +17,20 @@
|
|||
010068f00aa78000
|
||||
01006a800016e000
|
||||
010072800cbe8000
|
||||
01007300020fa000
|
||||
01007330027ee000
|
||||
0100749009844000
|
||||
01007a4008486000
|
||||
01007ef00011e000
|
||||
010080b00ad66000
|
||||
01008db008c2c000
|
||||
010094e00b52e000
|
||||
01009aa000faa000
|
||||
01009b90006dc000
|
||||
01009cc00c97c000
|
||||
0100a4200a284000
|
||||
0100a5c00d162000
|
||||
0100abf008968000
|
||||
0100ae000aebc000
|
||||
0100b3f000be2000
|
||||
0100bc2004ff4000
|
||||
|
|
|
@ -18,23 +18,50 @@
|
|||
<Optimize>false</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Due to GtkSharp. -->
|
||||
<!-- Due to .net core 3.0 embedded resource loading -->
|
||||
<PropertyGroup>
|
||||
<EmbeddedResourceUseDependentUponConvention>false</EmbeddedResourceUseDependentUponConvention>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'osx-x64'">
|
||||
<DefineConstants>MACOS_BUILD</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Ui\AboutWindow.glade" />
|
||||
<None Remove="Ui\assets\BlueCon.png" />
|
||||
<None Remove="Ui\assets\ProCon.png" />
|
||||
<None Remove="Ui\assets\RedCon.png" />
|
||||
<None Remove="Ui\assets\NCAIcon.png" />
|
||||
<None Remove="Ui\assets\NROIcon.png" />
|
||||
<None Remove="Ui\assets\NSOIcon.png" />
|
||||
<None Remove="Ui\assets\NSPIcon.png" />
|
||||
<None Remove="Ui\assets\XCIIcon.png" />
|
||||
<None Remove="Ui\assets\DiscordLogo.png" />
|
||||
<None Remove="Ui\assets\GitHubLogo.png" />
|
||||
<None Remove="Ui\assets\JoyCon.png" />
|
||||
<None Remove="Ui\assets\PatreonLogo.png" />
|
||||
<None Remove="Ui\assets\Icon.png" />
|
||||
<None Remove="Ui\assets\TwitterLogo.png" />
|
||||
<None Remove="Ui\MainWindow.glade" />
|
||||
<None Remove="Ui\SwitchSettings.glade" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Ui\AboutWindow.glade" />
|
||||
<EmbeddedResource Include="Ui\assets\ryujinxNCAIcon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\ryujinxNROIcon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\ryujinxNSOIcon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\ryujinxNSPIcon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\ryujinxXCIIcon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\BlueCon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\ProCon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\RedCon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\NCAIcon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\NROIcon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\NSOIcon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\NSPIcon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\XCIIcon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\DiscordLogo.png" />
|
||||
<EmbeddedResource Include="Ui\assets\GitHubLogo.png" />
|
||||
<EmbeddedResource Include="Ui\assets\JoyCon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\PatreonLogo.png" />
|
||||
<EmbeddedResource Include="Ui\assets\ryujinxIcon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\Icon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\TwitterLogo.png" />
|
||||
<EmbeddedResource Include="Ui\MainWindow.glade" />
|
||||
<EmbeddedResource Include="Ui\SwitchSettings.glade" />
|
||||
|
@ -42,8 +69,8 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.0.121" />
|
||||
<PackageReference Include="GtkSharp" Version="3.22.24.37" />
|
||||
<PackageReference Include="GtkSharp.Dependencies" Version="1.0.1" />
|
||||
<PackageReference Include="GtkSharp" Version="3.22.25.24" />
|
||||
<PackageReference Include="GtkSharp.Dependencies" Version="1.1.0" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
||||
<PackageReference Include="JsonPrettyPrinter" Version="1.0.1.1" />
|
||||
<PackageReference Include="OpenTK.NetStandard" Version="1.0.4" />
|
||||
</ItemGroup>
|
||||
|
@ -61,9 +88,6 @@
|
|||
<None Update="Config.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Theme.css">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="RPsupported.dat">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
|
4054
Ryujinx/Theme.css
9
Ryujinx/Ui/AboutInfo.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Ryujinx.Ui
|
||||
{
|
||||
internal struct AboutInfo
|
||||
{
|
||||
public string InstallVersion;
|
||||
public string InstallCommit;
|
||||
public string InstallBranch;
|
||||
}
|
||||
}
|
|
@ -1,27 +1,22 @@
|
|||
using Gtk;
|
||||
using GUI = Gtk.Builder.ObjectAttribute;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using Utf8Json;
|
||||
using Utf8Json.Resolvers;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.UI
|
||||
using GUI = Gtk.Builder.ObjectAttribute;
|
||||
|
||||
namespace Ryujinx.Ui
|
||||
{
|
||||
public struct Info
|
||||
{
|
||||
public string InstallVersion;
|
||||
public string InstallCommit;
|
||||
public string InstallBranch;
|
||||
}
|
||||
|
||||
public class AboutWindow : Window
|
||||
{
|
||||
public static Info Information { get; private set; }
|
||||
private static AboutInfo AboutInformation { get; set; }
|
||||
|
||||
#pragma warning disable 649
|
||||
#pragma warning disable CS0649
|
||||
#pragma warning disable IDE0044
|
||||
[GUI] Window _aboutWin;
|
||||
[GUI] Label _versionText;
|
||||
[GUI] Image _ryujinxLogo;
|
||||
|
@ -29,7 +24,8 @@ namespace Ryujinx.UI
|
|||
[GUI] Image _gitHubLogo;
|
||||
[GUI] Image _discordLogo;
|
||||
[GUI] Image _twitterLogo;
|
||||
#pragma warning restore 649
|
||||
#pragma warning restore CS0649
|
||||
#pragma warning restore IDE0044
|
||||
|
||||
public AboutWindow() : this(new Builder("Ryujinx.Ui.AboutWindow.glade")) { }
|
||||
|
||||
|
@ -37,8 +33,8 @@ namespace Ryujinx.UI
|
|||
{
|
||||
builder.Autoconnect(this);
|
||||
|
||||
_aboutWin.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.ryujinxIcon.png");
|
||||
_ryujinxLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.ryujinxIcon.png", 100, 100);
|
||||
_aboutWin.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png");
|
||||
_ryujinxLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png" , 100, 100);
|
||||
_patreonLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.PatreonLogo.png", 30 , 30 );
|
||||
_gitHubLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.GitHubLogo.png" , 30 , 30 );
|
||||
_discordLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.DiscordLogo.png", 30 , 30 );
|
||||
|
@ -50,10 +46,10 @@ namespace Ryujinx.UI
|
|||
|
||||
using (Stream stream = File.OpenRead(System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RyuFS", "Installer", "Config", "Config.json")))
|
||||
{
|
||||
Information = JsonSerializer.Deserialize<Info>(stream, resolver);
|
||||
AboutInformation = JsonSerializer.Deserialize<AboutInfo>(stream, resolver);
|
||||
}
|
||||
|
||||
_versionText.Text = $"Version {Information.InstallVersion} - {Information.InstallBranch} ({Information.InstallCommit})";
|
||||
_versionText.Text = $"Version {AboutInformation.InstallVersion} - {AboutInformation.InstallBranch} ({AboutInformation.InstallCommit})";
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
@ -61,7 +57,7 @@ namespace Ryujinx.UI
|
|||
}
|
||||
}
|
||||
|
||||
public void OpenUrl(string url)
|
||||
private static void OpenUrl(string url)
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
|
@ -78,39 +74,39 @@ namespace Ryujinx.UI
|
|||
}
|
||||
|
||||
//Events
|
||||
private void RyujinxButton_Pressed(object obj, ButtonPressEventArgs args)
|
||||
private void RyujinxButton_Pressed(object sender, ButtonPressEventArgs args)
|
||||
{
|
||||
OpenUrl("https://ryujinx.org");
|
||||
}
|
||||
|
||||
private void PatreonButton_Pressed(object obj, ButtonPressEventArgs args)
|
||||
private void PatreonButton_Pressed(object sender, ButtonPressEventArgs args)
|
||||
{
|
||||
OpenUrl("https://www.patreon.com/ryujinx");
|
||||
}
|
||||
|
||||
private void GitHubButton_Pressed(object obj, ButtonPressEventArgs args)
|
||||
private void GitHubButton_Pressed(object sender, ButtonPressEventArgs args)
|
||||
{
|
||||
OpenUrl("https://github.com/Ryujinx/Ryujinx");
|
||||
}
|
||||
|
||||
private void DiscordButton_Pressed(object obj, ButtonPressEventArgs args)
|
||||
private void DiscordButton_Pressed(object sender, ButtonPressEventArgs args)
|
||||
{
|
||||
OpenUrl("https://discordapp.com/invite/N2FmfVc");
|
||||
}
|
||||
|
||||
private void TwitterButton_Pressed(object obj, ButtonPressEventArgs args)
|
||||
private void TwitterButton_Pressed(object sender, ButtonPressEventArgs args)
|
||||
{
|
||||
OpenUrl("https://twitter.com/RyujinxEmu");
|
||||
}
|
||||
|
||||
private void ContributersButton_Pressed(object obj, ButtonPressEventArgs args)
|
||||
private void ContributorsButton_Pressed(object sender, ButtonPressEventArgs args)
|
||||
{
|
||||
OpenUrl("https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a");
|
||||
}
|
||||
|
||||
private void CloseToggle_Activated(object obj, EventArgs args)
|
||||
private void CloseToggle_Activated(object sender, EventArgs args)
|
||||
{
|
||||
Destroy();
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -154,10 +154,10 @@
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<object class="GtkLabel" id="license">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Unlicenced</property>
|
||||
<property name="label" translatable="yes">MIT License</property>
|
||||
<property name="justify">center</property>
|
||||
</object>
|
||||
<packing>
|
||||
|
@ -168,7 +168,7 @@
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<object class="GtkLabel" id="disclaimer">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Ryujinx is not affiliated with Nintendo,
|
||||
|
@ -523,11 +523,11 @@ Andy A (BaronKiko)</property>
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEventBox" id="ContributersButton">
|
||||
<object class="GtkEventBox" id="ContributorsButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="valign">start</property>
|
||||
<signal name="button-press-event" handler="ContributersButton_Pressed" swapped="no"/>
|
||||
<signal name="button-press-event" handler="ContributorsButton_Pressed" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
|
|
11
Ryujinx/Ui/ApplicationAddedEventArgs.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Ui
|
||||
{
|
||||
public class ApplicationAddedEventArgs : EventArgs
|
||||
{
|
||||
public ApplicationData AppData { get; set; }
|
||||
public int NumAppsFound { get; set; }
|
||||
public int NumAppsLoaded { get; set; }
|
||||
}
|
||||
}
|
17
Ryujinx/Ui/ApplicationData.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
namespace Ryujinx.Ui
|
||||
{
|
||||
public struct ApplicationData
|
||||
{
|
||||
public bool Favorite { get; set; }
|
||||
public byte[] Icon { get; set; }
|
||||
public string TitleName { get; set; }
|
||||
public string TitleId { get; set; }
|
||||
public string Developer { get; set; }
|
||||
public string Version { get; set; }
|
||||
public string TimePlayed { get; set; }
|
||||
public string LastPlayed { get; set; }
|
||||
public string FileExtension { get; set; }
|
||||
public string FileSize { get; set; }
|
||||
public string Path { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,66 +1,50 @@
|
|||
using LibHac;
|
||||
using JsonPrettyPrinterPlus;
|
||||
using LibHac;
|
||||
using LibHac.Fs;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.FsSystem.NcaUtils;
|
||||
using LibHac.Spl;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.Loaders.Npdm;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Utf8Json;
|
||||
using Utf8Json.Resolvers;
|
||||
|
||||
using SystemState = Ryujinx.HLE.HOS.SystemState;
|
||||
using TitleLanguage = Ryujinx.HLE.HOS.SystemState.TitleLanguage;
|
||||
|
||||
namespace Ryujinx.UI
|
||||
namespace Ryujinx.Ui
|
||||
{
|
||||
public class ApplicationLibrary
|
||||
{
|
||||
private static Keyset KeySet;
|
||||
private static SystemState.TitleLanguage DesiredTitleLanguage;
|
||||
public static event EventHandler<ApplicationAddedEventArgs> ApplicationAdded;
|
||||
|
||||
private const double SecondsPerMinute = 60.0;
|
||||
private const double SecondsPerHour = SecondsPerMinute * 60;
|
||||
private const double SecondsPerDay = SecondsPerHour * 24;
|
||||
private static readonly byte[] _nspIcon = GetResourceBytes("Ryujinx.Ui.assets.NSPIcon.png");
|
||||
private static readonly byte[] _xciIcon = GetResourceBytes("Ryujinx.Ui.assets.XCIIcon.png");
|
||||
private static readonly byte[] _ncaIcon = GetResourceBytes("Ryujinx.Ui.assets.NCAIcon.png");
|
||||
private static readonly byte[] _nroIcon = GetResourceBytes("Ryujinx.Ui.assets.NROIcon.png");
|
||||
private static readonly byte[] _nsoIcon = GetResourceBytes("Ryujinx.Ui.assets.NSOIcon.png");
|
||||
|
||||
public static byte[] RyujinxNspIcon { get; private set; }
|
||||
public static byte[] RyujinxXciIcon { get; private set; }
|
||||
public static byte[] RyujinxNcaIcon { get; private set; }
|
||||
public static byte[] RyujinxNroIcon { get; private set; }
|
||||
public static byte[] RyujinxNsoIcon { get; private set; }
|
||||
private static Keyset _keySet;
|
||||
private static TitleLanguage _desiredTitleLanguage;
|
||||
private static ApplicationMetadata _appMetadata;
|
||||
|
||||
public static List<ApplicationData> ApplicationLibraryData { get; private set; }
|
||||
|
||||
public struct ApplicationData
|
||||
public static void LoadApplications(List<string> appDirs, Keyset keySet, TitleLanguage desiredTitleLanguage)
|
||||
{
|
||||
public byte[] Icon;
|
||||
public string TitleName;
|
||||
public string TitleId;
|
||||
public string Developer;
|
||||
public string Version;
|
||||
public string TimePlayed;
|
||||
public string LastPlayed;
|
||||
public string FileExt;
|
||||
public string FileSize;
|
||||
public string Path;
|
||||
}
|
||||
int numApplicationsFound = 0;
|
||||
int numApplicationsLoaded = 0;
|
||||
|
||||
public static void Init(List<string> AppDirs, Keyset keySet, SystemState.TitleLanguage desiredTitleLanguage)
|
||||
{
|
||||
KeySet = keySet;
|
||||
DesiredTitleLanguage = desiredTitleLanguage;
|
||||
|
||||
// Loads the default application Icons
|
||||
RyujinxNspIcon = GetResourceBytes("Ryujinx.Ui.assets.ryujinxNSPIcon.png");
|
||||
RyujinxXciIcon = GetResourceBytes("Ryujinx.Ui.assets.ryujinxXCIIcon.png");
|
||||
RyujinxNcaIcon = GetResourceBytes("Ryujinx.Ui.assets.ryujinxNCAIcon.png");
|
||||
RyujinxNroIcon = GetResourceBytes("Ryujinx.Ui.assets.ryujinxNROIcon.png");
|
||||
RyujinxNsoIcon = GetResourceBytes("Ryujinx.Ui.assets.ryujinxNSOIcon.png");
|
||||
_keySet = keySet;
|
||||
_desiredTitleLanguage = desiredTitleLanguage;
|
||||
|
||||
// Builds the applications list with paths to found applications
|
||||
List<string> applications = new List<string>();
|
||||
foreach (string appDir in AppDirs)
|
||||
foreach (string appDir in appDirs)
|
||||
{
|
||||
if (Directory.Exists(appDir) == false)
|
||||
{
|
||||
|
@ -69,30 +53,80 @@ namespace Ryujinx.UI
|
|||
continue;
|
||||
}
|
||||
|
||||
DirectoryInfo AppDirInfo = new DirectoryInfo(appDir);
|
||||
foreach (FileInfo App in AppDirInfo.GetFiles())
|
||||
foreach (string app in Directory.GetFiles(appDir, "*.*", SearchOption.AllDirectories))
|
||||
{
|
||||
if ((Path.GetExtension(App.ToString()) == ".xci") ||
|
||||
(Path.GetExtension(App.ToString()) == ".nca") ||
|
||||
(Path.GetExtension(App.ToString()) == ".nsp") ||
|
||||
(Path.GetExtension(App.ToString()) == ".pfs0") ||
|
||||
(Path.GetExtension(App.ToString()) == ".nro") ||
|
||||
(Path.GetExtension(App.ToString()) == ".nso"))
|
||||
if ((Path.GetExtension(app) == ".xci") ||
|
||||
(Path.GetExtension(app) == ".nro") ||
|
||||
(Path.GetExtension(app) == ".nso") ||
|
||||
(Path.GetFileName(app) == "hbl.nsp"))
|
||||
{
|
||||
applications.Add(App.ToString());
|
||||
applications.Add(app);
|
||||
numApplicationsFound++;
|
||||
}
|
||||
else if ((Path.GetExtension(app) == ".nsp") || (Path.GetExtension(app) == ".pfs0"))
|
||||
{
|
||||
try
|
||||
{
|
||||
bool hasMainNca = false;
|
||||
|
||||
PartitionFileSystem nsp = new PartitionFileSystem(new FileStream(app, FileMode.Open, FileAccess.Read).AsStorage());
|
||||
foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca"))
|
||||
{
|
||||
nsp.OpenFile(out IFile ncaFile, fileEntry.FullPath, OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = new Nca(_keySet, ncaFile.AsStorage());
|
||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||
|
||||
if (nca.Header.ContentType == NcaContentType.Program && !nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
||||
{
|
||||
hasMainNca = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasMainNca)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (InvalidDataException)
|
||||
{
|
||||
Logger.PrintWarning(LogClass.Application, $"{app}: The header key is incorrect or missing and therefore the NCA header content type check has failed.");
|
||||
}
|
||||
|
||||
applications.Add(app);
|
||||
numApplicationsFound++;
|
||||
}
|
||||
else if (Path.GetExtension(app) == ".nca")
|
||||
{
|
||||
try
|
||||
{
|
||||
Nca nca = new Nca(_keySet, new FileStream(app, FileMode.Open, FileAccess.Read).AsStorage());
|
||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||
|
||||
if (nca.Header.ContentType != NcaContentType.Program || nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (InvalidDataException)
|
||||
{
|
||||
Logger.PrintWarning(LogClass.Application, $"{app}: The header key is incorrect or missing and therefore the NCA header content type check has failed.");
|
||||
}
|
||||
|
||||
applications.Add(app);
|
||||
numApplicationsFound++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Loops through applications list, creating a struct for each application and then adding the struct to a list of structs
|
||||
ApplicationLibraryData = new List<ApplicationData>();
|
||||
// Loops through applications list, creating a struct and then firing an event containing the struct for each application
|
||||
foreach (string applicationPath in applications)
|
||||
{
|
||||
double filesize = new FileInfo(applicationPath).Length * 0.000000000931;
|
||||
string titleName = null;
|
||||
string titleId = null;
|
||||
string developer = null;
|
||||
string version = null;
|
||||
double fileSize = new FileInfo(applicationPath).Length * 0.000000000931;
|
||||
string titleName = "Unknown";
|
||||
string titleId = "0000000000000000";
|
||||
string developer = "Unknown";
|
||||
string version = "0";
|
||||
byte[] applicationIcon = null;
|
||||
|
||||
using (FileStream file = new FileStream(applicationPath, FileMode.Open, FileAccess.Read))
|
||||
|
@ -103,158 +137,48 @@ namespace Ryujinx.UI
|
|||
{
|
||||
try
|
||||
{
|
||||
IFileSystem controlFs = null;
|
||||
|
||||
// Store the ControlFS in variable called controlFs
|
||||
PartitionFileSystem pfs;
|
||||
|
||||
if (Path.GetExtension(applicationPath) == ".xci")
|
||||
{
|
||||
Xci xci = new Xci(KeySet, file.AsStorage());
|
||||
Xci xci = new Xci(_keySet, file.AsStorage());
|
||||
|
||||
controlFs = GetControlFs(xci.OpenPartition(XciPartitionType.Secure));
|
||||
pfs = xci.OpenPartition(XciPartitionType.Secure);
|
||||
}
|
||||
else
|
||||
{
|
||||
controlFs = GetControlFs(new PartitionFileSystem(file.AsStorage()));
|
||||
pfs = new PartitionFileSystem(file.AsStorage());
|
||||
}
|
||||
|
||||
// Creates NACP class from the NACP file
|
||||
controlFs.OpenFile(out IFile controlNacpFile, "/control.nacp", OpenMode.Read).ThrowIfFailure();
|
||||
// Store the ControlFS in variable called controlFs
|
||||
IFileSystem controlFs = GetControlFs(pfs);
|
||||
|
||||
Nacp controlData = new Nacp(controlNacpFile.AsStream());
|
||||
|
||||
// Get the title name, title ID, developer name and version number from the NACP
|
||||
version = controlData.DisplayVersion;
|
||||
|
||||
titleName = controlData.Descriptions[(int)DesiredTitleLanguage].Title;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleName))
|
||||
// If this is null then this is probably not a normal NSP, it's probably an ExeFS as an NSP
|
||||
if (controlFs == null)
|
||||
{
|
||||
titleName = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title;
|
||||
}
|
||||
applicationIcon = _nspIcon;
|
||||
|
||||
titleId = controlData.PresenceGroupId.ToString("x16");
|
||||
Result result = pfs.OpenFile(out IFile npdmFile, "/main.npdm", OpenMode.Read);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleId))
|
||||
{
|
||||
titleId = controlData.SaveDataOwnerId.ToString("x16");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleId))
|
||||
{
|
||||
titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("x16");
|
||||
}
|
||||
|
||||
developer = controlData.Descriptions[(int)DesiredTitleLanguage].Developer;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(developer))
|
||||
{
|
||||
developer = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Developer)).Developer;
|
||||
}
|
||||
|
||||
// Read the icon from the ControlFS and store it as a byte array
|
||||
try
|
||||
{
|
||||
controlFs.OpenFile(out IFile icon, $"/icon_{DesiredTitleLanguage}.dat", OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
if (result != ResultFs.PathNotFound)
|
||||
{
|
||||
icon.AsStream().CopyTo(stream);
|
||||
applicationIcon = stream.ToArray();
|
||||
Npdm npdm = new Npdm(npdmFile.AsStream());
|
||||
|
||||
titleName = npdm.TitleName;
|
||||
titleId = npdm.Aci0.TitleId.ToString("x16");
|
||||
}
|
||||
}
|
||||
catch (HorizonResultException)
|
||||
else
|
||||
{
|
||||
foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*"))
|
||||
{
|
||||
if (entry.Name == "control.nacp")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Creates NACP class from the NACP file
|
||||
controlFs.OpenFile(out IFile controlNacpFile, "/control.nacp", OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
controlFs.OpenFile(out IFile icon, entry.FullPath, OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
{
|
||||
icon.AsStream().CopyTo(stream);
|
||||
applicationIcon = stream.ToArray();
|
||||
}
|
||||
|
||||
if (applicationIcon != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (applicationIcon == null)
|
||||
{
|
||||
applicationIcon = NspOrXciIcon(applicationPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (MissingKeyException exception)
|
||||
{
|
||||
titleName = "Unknown";
|
||||
titleId = "Unknown";
|
||||
developer = "Unknown";
|
||||
version = "?";
|
||||
applicationIcon = NspOrXciIcon(applicationPath);
|
||||
|
||||
Logger.PrintWarning(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}");
|
||||
}
|
||||
catch (InvalidDataException)
|
||||
{
|
||||
titleName = "Unknown";
|
||||
titleId = "Unknown";
|
||||
developer = "Unknown";
|
||||
version = "?";
|
||||
applicationIcon = NspOrXciIcon(applicationPath);
|
||||
|
||||
Logger.PrintWarning(LogClass.Application, $"The file is not an NCA file or the header key is incorrect. Errored File: {applicationPath}");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Logger.PrintWarning(LogClass.Application, $"This warning usualy means that you have a DLC in one of you game directories\n{exception}");
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (Path.GetExtension(applicationPath) == ".nro")
|
||||
{
|
||||
BinaryReader reader = new BinaryReader(file);
|
||||
|
||||
byte[] Read(long Position, int Size)
|
||||
{
|
||||
file.Seek(Position, SeekOrigin.Begin);
|
||||
|
||||
return reader.ReadBytes(Size);
|
||||
}
|
||||
|
||||
file.Seek(24, SeekOrigin.Begin);
|
||||
int AssetOffset = reader.ReadInt32();
|
||||
|
||||
if (Encoding.ASCII.GetString(Read(AssetOffset, 4)) == "ASET")
|
||||
{
|
||||
byte[] IconSectionInfo = Read(AssetOffset + 8, 0x10);
|
||||
|
||||
long iconOffset = BitConverter.ToInt64(IconSectionInfo, 0);
|
||||
long iconSize = BitConverter.ToInt64(IconSectionInfo, 8);
|
||||
|
||||
ulong nacpOffset = reader.ReadUInt64();
|
||||
ulong nacpSize = reader.ReadUInt64();
|
||||
|
||||
// Reads and stores game icon as byte array
|
||||
applicationIcon = Read(AssetOffset + iconOffset, (int)iconSize);
|
||||
|
||||
// Creates memory stream out of byte array which is the NACP
|
||||
using (MemoryStream stream = new MemoryStream(Read(AssetOffset + (int)nacpOffset, (int)nacpSize)))
|
||||
{
|
||||
// Creates NACP class from the memory stream
|
||||
Nacp controlData = new Nacp(stream);
|
||||
Nacp controlData = new Nacp(controlNacpFile.AsStream());
|
||||
|
||||
// Get the title name, title ID, developer name and version number from the NACP
|
||||
version = controlData.DisplayVersion;
|
||||
|
||||
titleName = controlData.Descriptions[(int)DesiredTitleLanguage].Title;
|
||||
titleName = controlData.Descriptions[(int)_desiredTitleLanguage].Title;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleName))
|
||||
{
|
||||
|
@ -273,7 +197,123 @@ namespace Ryujinx.UI
|
|||
titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("x16");
|
||||
}
|
||||
|
||||
developer = controlData.Descriptions[(int)DesiredTitleLanguage].Developer;
|
||||
developer = controlData.Descriptions[(int)_desiredTitleLanguage].Developer;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(developer))
|
||||
{
|
||||
developer = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Developer)).Developer;
|
||||
}
|
||||
|
||||
// Read the icon from the ControlFS and store it as a byte array
|
||||
try
|
||||
{
|
||||
controlFs.OpenFile(out IFile icon, $"/icon_{_desiredTitleLanguage}.dat", OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
{
|
||||
icon.AsStream().CopyTo(stream);
|
||||
applicationIcon = stream.ToArray();
|
||||
}
|
||||
}
|
||||
catch (HorizonResultException)
|
||||
{
|
||||
foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*"))
|
||||
{
|
||||
if (entry.Name == "control.nacp")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
controlFs.OpenFile(out IFile icon, entry.FullPath, OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
{
|
||||
icon.AsStream().CopyTo(stream);
|
||||
applicationIcon = stream.ToArray();
|
||||
}
|
||||
|
||||
if (applicationIcon != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (applicationIcon == null)
|
||||
{
|
||||
applicationIcon = Path.GetExtension(applicationPath) == ".xci" ? _xciIcon : _nspIcon;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (MissingKeyException exception)
|
||||
{
|
||||
applicationIcon = Path.GetExtension(applicationPath) == ".xci" ? _xciIcon : _nspIcon;
|
||||
|
||||
Logger.PrintWarning(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}");
|
||||
}
|
||||
catch (InvalidDataException)
|
||||
{
|
||||
applicationIcon = Path.GetExtension(applicationPath) == ".xci" ? _xciIcon : _nspIcon;
|
||||
|
||||
Logger.PrintWarning(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {applicationPath}");
|
||||
}
|
||||
}
|
||||
else if (Path.GetExtension(applicationPath) == ".nro")
|
||||
{
|
||||
BinaryReader reader = new BinaryReader(file);
|
||||
|
||||
byte[] Read(long position, int size)
|
||||
{
|
||||
file.Seek(position, SeekOrigin.Begin);
|
||||
|
||||
return reader.ReadBytes(size);
|
||||
}
|
||||
|
||||
file.Seek(24, SeekOrigin.Begin);
|
||||
int assetOffset = reader.ReadInt32();
|
||||
|
||||
if (Encoding.ASCII.GetString(Read(assetOffset, 4)) == "ASET")
|
||||
{
|
||||
byte[] iconSectionInfo = Read(assetOffset + 8, 0x10);
|
||||
|
||||
long iconOffset = BitConverter.ToInt64(iconSectionInfo, 0);
|
||||
long iconSize = BitConverter.ToInt64(iconSectionInfo, 8);
|
||||
|
||||
ulong nacpOffset = reader.ReadUInt64();
|
||||
ulong nacpSize = reader.ReadUInt64();
|
||||
|
||||
// Reads and stores game icon as byte array
|
||||
applicationIcon = Read(assetOffset + iconOffset, (int)iconSize);
|
||||
|
||||
// Creates memory stream out of byte array which is the NACP
|
||||
using (MemoryStream stream = new MemoryStream(Read(assetOffset + (int)nacpOffset, (int)nacpSize)))
|
||||
{
|
||||
// Creates NACP class from the memory stream
|
||||
Nacp controlData = new Nacp(stream);
|
||||
|
||||
// Get the title name, title ID, developer name and version number from the NACP
|
||||
version = controlData.DisplayVersion;
|
||||
|
||||
titleName = controlData.Descriptions[(int)_desiredTitleLanguage].Title;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleName))
|
||||
{
|
||||
titleName = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title;
|
||||
}
|
||||
|
||||
titleId = controlData.PresenceGroupId.ToString("x16");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleId))
|
||||
{
|
||||
titleId = controlData.SaveDataOwnerId.ToString("x16");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleId))
|
||||
{
|
||||
titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("x16");
|
||||
}
|
||||
|
||||
developer = controlData.Descriptions[(int)_desiredTitleLanguage].Developer;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(developer))
|
||||
{
|
||||
|
@ -283,59 +323,50 @@ namespace Ryujinx.UI
|
|||
}
|
||||
else
|
||||
{
|
||||
applicationIcon = RyujinxNroIcon;
|
||||
titleName = "Application";
|
||||
titleId = "0000000000000000";
|
||||
developer = "Unknown";
|
||||
version = "?";
|
||||
applicationIcon = _nroIcon;
|
||||
}
|
||||
}
|
||||
// If its an NCA or NSO we just set defaults
|
||||
else if ((Path.GetExtension(applicationPath) == ".nca") || (Path.GetExtension(applicationPath) == ".nso"))
|
||||
{
|
||||
if (Path.GetExtension(applicationPath) == ".nca")
|
||||
{
|
||||
applicationIcon = RyujinxNcaIcon;
|
||||
}
|
||||
else if (Path.GetExtension(applicationPath) == ".nso")
|
||||
{
|
||||
applicationIcon = RyujinxNsoIcon;
|
||||
}
|
||||
|
||||
string fileName = Path.GetFileName(applicationPath);
|
||||
string fileExt = Path.GetExtension(applicationPath);
|
||||
|
||||
StringBuilder titlename = new StringBuilder();
|
||||
titlename.Append(fileName);
|
||||
titlename.Remove(fileName.Length - fileExt.Length, fileExt.Length);
|
||||
|
||||
titleName = titlename.ToString();
|
||||
titleId = "0000000000000000";
|
||||
version = "?";
|
||||
developer = "Unknown";
|
||||
applicationIcon = Path.GetExtension(applicationPath) == ".nca" ? _ncaIcon : _nsoIcon;
|
||||
titleName = Path.GetFileNameWithoutExtension(applicationPath);
|
||||
}
|
||||
}
|
||||
|
||||
string[] playedData = GetPlayedData(titleId, "00000000000000000000000000000001");
|
||||
(bool favorite, string timePlayed, string lastPlayed) = GetMetadata(titleId);
|
||||
|
||||
ApplicationData data = new ApplicationData()
|
||||
{
|
||||
Icon = applicationIcon,
|
||||
TitleName = titleName,
|
||||
TitleId = titleId,
|
||||
Developer = developer,
|
||||
Version = version,
|
||||
TimePlayed = playedData[0],
|
||||
LastPlayed = playedData[1],
|
||||
FileExt = Path.GetExtension(applicationPath).ToUpper().Remove(0 ,1),
|
||||
FileSize = (filesize < 1) ? (filesize * 1024).ToString("0.##") + "MB" : filesize.ToString("0.##") + "GB",
|
||||
Path = applicationPath,
|
||||
Favorite = favorite,
|
||||
Icon = applicationIcon,
|
||||
TitleName = titleName,
|
||||
TitleId = titleId,
|
||||
Developer = developer,
|
||||
Version = version,
|
||||
TimePlayed = timePlayed,
|
||||
LastPlayed = lastPlayed,
|
||||
FileExtension = Path.GetExtension(applicationPath).ToUpper().Remove(0 ,1),
|
||||
FileSize = (fileSize < 1) ? (fileSize * 1024).ToString("0.##") + "MB" : fileSize.ToString("0.##") + "GB",
|
||||
Path = applicationPath,
|
||||
};
|
||||
|
||||
ApplicationLibraryData.Add(data);
|
||||
numApplicationsLoaded++;
|
||||
|
||||
OnApplicationAdded(new ApplicationAddedEventArgs()
|
||||
{
|
||||
AppData = data,
|
||||
NumAppsFound = numApplicationsFound,
|
||||
NumAppsLoaded = numApplicationsLoaded
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected static void OnApplicationAdded(ApplicationAddedEventArgs e)
|
||||
{
|
||||
ApplicationAdded?.Invoke(null, e);
|
||||
}
|
||||
|
||||
private static byte[] GetResourceBytes(string resourceName)
|
||||
{
|
||||
Stream resourceStream = Assembly.GetCallingAssembly().GetManifestResourceStream(resourceName);
|
||||
|
@ -346,29 +377,29 @@ namespace Ryujinx.UI
|
|||
return resourceByteArray;
|
||||
}
|
||||
|
||||
private static IFileSystem GetControlFs(PartitionFileSystem Pfs)
|
||||
private static IFileSystem GetControlFs(PartitionFileSystem pfs)
|
||||
{
|
||||
Nca controlNca = null;
|
||||
|
||||
// Add keys to keyset if needed
|
||||
foreach (DirectoryEntryEx ticketEntry in Pfs.EnumerateEntries("/", "*.tik"))
|
||||
// Add keys to key set if needed
|
||||
foreach (DirectoryEntryEx ticketEntry in pfs.EnumerateEntries("/", "*.tik"))
|
||||
{
|
||||
Result result = Pfs.OpenFile(out IFile ticketFile, ticketEntry.FullPath, OpenMode.Read);
|
||||
Result result = pfs.OpenFile(out IFile ticketFile, ticketEntry.FullPath, OpenMode.Read);
|
||||
|
||||
if (result.IsSuccess())
|
||||
{
|
||||
Ticket ticket = new Ticket(ticketFile.AsStream());
|
||||
|
||||
KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(KeySet)));
|
||||
_keySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(_keySet)));
|
||||
}
|
||||
}
|
||||
|
||||
// Find the Control NCA and store it in variable called controlNca
|
||||
foreach (DirectoryEntryEx fileEntry in Pfs.EnumerateEntries("/", "*.nca"))
|
||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||
{
|
||||
Pfs.OpenFile(out IFile ncaFile, fileEntry.FullPath, OpenMode.Read).ThrowIfFailure();
|
||||
pfs.OpenFile(out IFile ncaFile, fileEntry.FullPath, OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = new Nca(KeySet, ncaFile.AsStorage());
|
||||
Nca nca = new Nca(_keySet, ncaFile.AsStorage());
|
||||
|
||||
if (nca.Header.ContentType == NcaContentType.Control)
|
||||
{
|
||||
|
@ -377,84 +408,65 @@ namespace Ryujinx.UI
|
|||
}
|
||||
|
||||
// Return the ControlFS
|
||||
return controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
|
||||
return controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
|
||||
}
|
||||
|
||||
private static string[] GetPlayedData(string TitleId, string UserId)
|
||||
private static (bool favorite, string timePlayed, string lastPlayed) GetMetadata(string titleId)
|
||||
{
|
||||
try
|
||||
string metadataFolder = Path.Combine(new VirtualFileSystem().GetBasePath(), "games", titleId, "gui");
|
||||
string metadataFile = Path.Combine(metadataFolder, "metadata.json");
|
||||
|
||||
IJsonFormatterResolver resolver = CompositeResolver.Create(StandardResolver.AllowPrivateSnakeCase);
|
||||
|
||||
if (!File.Exists(metadataFile))
|
||||
{
|
||||
string[] playedData = new string[2];
|
||||
string savePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RyuFS", "nand", "user", "save", "0000000000000000", UserId, TitleId);
|
||||
Directory.CreateDirectory(metadataFolder);
|
||||
|
||||
if (File.Exists(Path.Combine(savePath, "TimePlayed.dat")) == false)
|
||||
_appMetadata = new ApplicationMetadata
|
||||
{
|
||||
Directory.CreateDirectory(savePath);
|
||||
using (FileStream file = File.OpenWrite(Path.Combine(savePath, "TimePlayed.dat")))
|
||||
{
|
||||
file.Write(Encoding.ASCII.GetBytes("0"));
|
||||
}
|
||||
}
|
||||
using (FileStream fs = File.OpenRead(Path.Combine(savePath, "TimePlayed.dat")))
|
||||
{
|
||||
using (StreamReader sr = new StreamReader(fs))
|
||||
{
|
||||
float timePlayed = float.Parse(sr.ReadLine());
|
||||
Favorite = false,
|
||||
TimePlayed = 0,
|
||||
LastPlayed = "Never"
|
||||
};
|
||||
|
||||
if (timePlayed < SecondsPerMinute)
|
||||
{
|
||||
playedData[0] = $"{timePlayed}s";
|
||||
}
|
||||
else if (timePlayed < SecondsPerHour)
|
||||
{
|
||||
playedData[0] = $"{Math.Round(timePlayed / SecondsPerMinute, 2, MidpointRounding.AwayFromZero)} mins";
|
||||
}
|
||||
else if (timePlayed < SecondsPerDay)
|
||||
{
|
||||
playedData[0] = $"{Math.Round(timePlayed / SecondsPerHour , 2, MidpointRounding.AwayFromZero)} hrs";
|
||||
}
|
||||
else
|
||||
{
|
||||
playedData[0] = $"{Math.Round(timePlayed / SecondsPerDay , 2, MidpointRounding.AwayFromZero)} days";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (File.Exists(Path.Combine(savePath, "LastPlayed.dat")) == false)
|
||||
{
|
||||
Directory.CreateDirectory(savePath);
|
||||
using (FileStream file = File.OpenWrite(Path.Combine(savePath, "LastPlayed.dat")))
|
||||
{
|
||||
file.Write(Encoding.ASCII.GetBytes("Never"));
|
||||
}
|
||||
}
|
||||
|
||||
using (FileStream fs = File.OpenRead(Path.Combine(savePath, "LastPlayed.dat")))
|
||||
{
|
||||
using (StreamReader sr = new StreamReader(fs))
|
||||
{
|
||||
playedData[1] = sr.ReadLine();
|
||||
}
|
||||
}
|
||||
|
||||
return playedData;
|
||||
byte[] saveData = JsonSerializer.Serialize(_appMetadata, resolver);
|
||||
File.WriteAllText(metadataFile, Encoding.UTF8.GetString(saveData, 0, saveData.Length).PrettyPrintJson());
|
||||
}
|
||||
catch
|
||||
|
||||
using (Stream stream = File.OpenRead(metadataFile))
|
||||
{
|
||||
return new string[] { "Unknown", "Unknown" };
|
||||
_appMetadata = JsonSerializer.Deserialize<ApplicationMetadata>(stream, resolver);
|
||||
}
|
||||
|
||||
return (_appMetadata.Favorite, ConvertSecondsToReadableString(_appMetadata.TimePlayed), _appMetadata.LastPlayed);
|
||||
}
|
||||
|
||||
private static byte[] NspOrXciIcon(string applicationPath)
|
||||
private static string ConvertSecondsToReadableString(double seconds)
|
||||
{
|
||||
if (Path.GetExtension(applicationPath) == ".xci")
|
||||
const int secondsPerMinute = 60;
|
||||
const int secondsPerHour = secondsPerMinute * 60;
|
||||
const int secondsPerDay = secondsPerHour * 24;
|
||||
|
||||
string readableString;
|
||||
|
||||
if (seconds < secondsPerMinute)
|
||||
{
|
||||
return RyujinxXciIcon;
|
||||
readableString = $"{seconds}s";
|
||||
}
|
||||
else if (seconds < secondsPerHour)
|
||||
{
|
||||
readableString = $"{Math.Round(seconds / secondsPerMinute, 2, MidpointRounding.AwayFromZero)} mins";
|
||||
}
|
||||
else if (seconds < secondsPerDay)
|
||||
{
|
||||
readableString = $"{Math.Round(seconds / secondsPerHour, 2, MidpointRounding.AwayFromZero)} hrs";
|
||||
}
|
||||
else
|
||||
{
|
||||
return RyujinxNspIcon;
|
||||
readableString = $"{Math.Round(seconds / secondsPerDay, 2, MidpointRounding.AwayFromZero)} days";
|
||||
}
|
||||
|
||||
return readableString;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
9
Ryujinx/Ui/ApplicationMetadata.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Ryujinx.Ui
|
||||
{
|
||||
internal struct ApplicationMetadata
|
||||
{
|
||||
public bool Favorite { get; set; }
|
||||
public double TimePlayed { get; set; }
|
||||
public string LastPlayed { get; set; }
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ using System.Threading;
|
|||
|
||||
using Stopwatch = System.Diagnostics.Stopwatch;
|
||||
|
||||
namespace Ryujinx.UI
|
||||
namespace Ryujinx.Ui
|
||||
{
|
||||
public class GlScreen : GameWindow
|
||||
{
|
||||
|
@ -297,10 +297,13 @@ namespace Ryujinx.UI
|
|||
double hostFps = _device.Statistics.GetSystemFrameRate();
|
||||
double gameFps = _device.Statistics.GetGameFrameRate();
|
||||
|
||||
string titleSection = string.IsNullOrWhiteSpace(_device.System.CurrentTitle) ? string.Empty
|
||||
: " | " + _device.System.CurrentTitle;
|
||||
string titleNameSection = string.IsNullOrWhiteSpace(_device.System.TitleName) ? string.Empty
|
||||
: " | " + _device.System.TitleName;
|
||||
|
||||
_newTitle = $"Ryujinx{titleSection} | Host FPS: {hostFps:0.0} | Game FPS: {gameFps:0.0} | " +
|
||||
string titleIDSection = string.IsNullOrWhiteSpace(_device.System.TitleId) ? string.Empty
|
||||
: " | " + _device.System.TitleId.ToUpper();
|
||||
|
||||
_newTitle = $"Ryujinx{titleNameSection}{titleIDSection} | Host FPS: {hostFps:0.0} | Game FPS: {gameFps:0.0} | " +
|
||||
$"Game Vsync: {(_device.EnableDeviceVsync ? "On" : "Off")}";
|
||||
|
||||
_titleEvent = true;
|
||||
|
|
23
Ryujinx/Ui/GtkDialog.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using Gtk;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Ryujinx.Ui
|
||||
{
|
||||
internal class GtkDialog
|
||||
{
|
||||
internal static void CreateErrorDialog(string errorMessage)
|
||||
{
|
||||
MessageDialog errorDialog = new MessageDialog(null, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, null)
|
||||
{
|
||||
Title = "Ryujinx - Error",
|
||||
Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"),
|
||||
Text = "Ryujinx has encountered an error",
|
||||
SecondaryText = errorMessage,
|
||||
WindowPosition = WindowPosition.Center
|
||||
};
|
||||
errorDialog.SetSizeRequest(100, 20);
|
||||
errorDialog.Run();
|
||||
errorDialog.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
16
Ryujinx/Ui/GuiColumns.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
namespace Ryujinx.Ui
|
||||
{
|
||||
public struct GuiColumns
|
||||
{
|
||||
public bool FavColumn;
|
||||
public bool IconColumn;
|
||||
public bool AppColumn;
|
||||
public bool DevColumn;
|
||||
public bool VersionColumn;
|
||||
public bool TimePlayedColumn;
|
||||
public bool LastPlayedColumn;
|
||||
public bool FileExtColumn;
|
||||
public bool FileSizeColumn;
|
||||
public bool PathColumn;
|
||||
}
|
||||
}
|
|
@ -1,11 +1,12 @@
|
|||
using ARMeilleure.Translation.AOT;
|
||||
using DiscordRPC;
|
||||
using Gtk;
|
||||
using GUI = Gtk.Builder.ObjectAttribute;
|
||||
using JsonPrettyPrinterPlus;
|
||||
using Ryujinx.Audio;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.Gal;
|
||||
using Ryujinx.Graphics.Gal.OpenGL;
|
||||
using Ryujinx.Graphics.Gal;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.Profiler;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
@ -13,25 +14,42 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using Utf8Json;
|
||||
using Utf8Json.Resolvers;
|
||||
|
||||
namespace Ryujinx.UI
|
||||
using GUI = Gtk.Builder.ObjectAttribute;
|
||||
|
||||
namespace Ryujinx.Ui
|
||||
{
|
||||
public class MainWindow : Window
|
||||
{
|
||||
internal static HLE.Switch _device;
|
||||
private static HLE.Switch _device;
|
||||
|
||||
private static IGalRenderer _renderer;
|
||||
|
||||
private static IAalOutput _audioOut;
|
||||
|
||||
private static Application _gtkApplication;
|
||||
private static GlScreen _screen;
|
||||
|
||||
private static ListStore _tableStore;
|
||||
|
||||
private static bool _gameLoaded = false;
|
||||
private static bool _updatingGameTable;
|
||||
private static bool _gameLoaded;
|
||||
private static bool _ending;
|
||||
|
||||
private static string _userId = "00000000000000000000000000000001";
|
||||
private static TreeViewColumn _favColumn;
|
||||
private static TreeViewColumn _appColumn;
|
||||
private static TreeViewColumn _devColumn;
|
||||
private static TreeViewColumn _versionColumn;
|
||||
private static TreeViewColumn _timePlayedColumn;
|
||||
private static TreeViewColumn _lastPlayedColumn;
|
||||
private static TreeViewColumn _fileExtColumn;
|
||||
private static TreeViewColumn _fileSizeColumn;
|
||||
private static TreeViewColumn _pathColumn;
|
||||
|
||||
private static TreeView _treeView;
|
||||
|
||||
public static bool DiscordIntegrationEnabled { get; set; }
|
||||
|
||||
|
@ -39,12 +57,14 @@ namespace Ryujinx.UI
|
|||
|
||||
public static RichPresence DiscordPresence;
|
||||
|
||||
#pragma warning disable 649
|
||||
#pragma warning disable CS0649
|
||||
#pragma warning disable IDE0044
|
||||
[GUI] Window _mainWin;
|
||||
[GUI] CheckMenuItem _fullScreen;
|
||||
[GUI] MenuItem _stopEmulation;
|
||||
[GUI] CheckMenuItem _favToggle;
|
||||
[GUI] CheckMenuItem _iconToggle;
|
||||
[GUI] CheckMenuItem _titleToggle;
|
||||
[GUI] CheckMenuItem _appToggle;
|
||||
[GUI] CheckMenuItem _developerToggle;
|
||||
[GUI] CheckMenuItem _versionToggle;
|
||||
[GUI] CheckMenuItem _timePlayedToggle;
|
||||
|
@ -52,28 +72,33 @@ namespace Ryujinx.UI
|
|||
[GUI] CheckMenuItem _fileExtToggle;
|
||||
[GUI] CheckMenuItem _fileSizeToggle;
|
||||
[GUI] CheckMenuItem _pathToggle;
|
||||
[GUI] Box _box;
|
||||
[GUI] TreeView _gameTable;
|
||||
[GUI] GLArea _glScreen;
|
||||
#pragma warning restore 649
|
||||
[GUI] Label _progressLabel;
|
||||
[GUI] LevelBar _progressBar;
|
||||
#pragma warning restore CS0649
|
||||
#pragma warning restore IDE0044
|
||||
|
||||
public MainWindow(string[] args, Application gtkApplication) : this(new Builder("Ryujinx.Ui.MainWindow.glade"), args, gtkApplication) { }
|
||||
public MainWindow() : this(new Builder("Ryujinx.Ui.MainWindow.glade")) { }
|
||||
|
||||
private MainWindow(Builder builder, string[] args, Application gtkApplication) : base(builder.GetObject("_mainWin").Handle)
|
||||
private MainWindow(Builder builder) : base(builder.GetObject("_mainWin").Handle)
|
||||
{
|
||||
builder.Autoconnect(this);
|
||||
|
||||
DeleteEvent += Window_Close;
|
||||
|
||||
ApplicationLibrary.ApplicationAdded += Application_Added;
|
||||
|
||||
_renderer = new OglRenderer();
|
||||
|
||||
_audioOut = InitializeAudioEngine();
|
||||
|
||||
_device = new HLE.Switch(_renderer, _audioOut);
|
||||
|
||||
_treeView = _gameTable;
|
||||
|
||||
Configuration.Load(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
|
||||
Configuration.InitialConfigure(_device);
|
||||
|
||||
ApplicationLibrary.Init(SwitchSettings.SwitchConfig.GameDirs, _device.System.KeySet, _device.System.State.DesiredTitleLanguage);
|
||||
|
||||
_gtkApplication = gtkApplication;
|
||||
|
||||
ApplyTheme();
|
||||
|
||||
if (DiscordIntegrationEnabled)
|
||||
|
@ -95,117 +120,130 @@ namespace Ryujinx.UI
|
|||
DiscordClient.SetPresence(DiscordPresence);
|
||||
}
|
||||
|
||||
builder.Autoconnect(this);
|
||||
|
||||
DeleteEvent += Window_Close;
|
||||
|
||||
_mainWin.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.ryujinxIcon.png");
|
||||
_mainWin.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png");
|
||||
_stopEmulation.Sensitive = false;
|
||||
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[0]) { _iconToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[1]) { _titleToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[2]) { _developerToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[3]) { _versionToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[4]) { _timePlayedToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[5]) { _lastPlayedToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[6]) { _fileExtToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[7]) { _fileSizeToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[8]) { _pathToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.FavColumn) { _favToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.IconColumn) { _iconToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.AppColumn) { _appToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.DevColumn) { _developerToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.VersionColumn) { _versionToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.TimePlayedColumn) { _timePlayedToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.LastPlayedColumn) { _lastPlayedToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.FileExtColumn) { _fileExtToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.FileSizeColumn) { _fileSizeToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.PathColumn) { _pathToggle.Active = true; }
|
||||
|
||||
if (args.Length == 1)
|
||||
_gameTable.Model = _tableStore = new ListStore(
|
||||
typeof(bool),
|
||||
typeof(Gdk.Pixbuf),
|
||||
typeof(string),
|
||||
typeof(string),
|
||||
typeof(string),
|
||||
typeof(string),
|
||||
typeof(string),
|
||||
typeof(string),
|
||||
typeof(string),
|
||||
typeof(string));
|
||||
|
||||
_tableStore.SetSortFunc(5, TimePlayedSort);
|
||||
_tableStore.SetSortFunc(6, LastPlayedSort);
|
||||
_tableStore.SetSortFunc(8, FileSizeSort);
|
||||
_tableStore.SetSortColumnId(0, SortType.Descending);
|
||||
|
||||
UpdateColumns();
|
||||
#pragma warning disable CS4014
|
||||
UpdateGameTable();
|
||||
#pragma warning restore CS4014
|
||||
}
|
||||
|
||||
internal static void ApplyTheme()
|
||||
{
|
||||
if (!SwitchSettings.SwitchConfig.EnableCustomTheme)
|
||||
{
|
||||
// Temporary code section start, remove this section when game is rendered to the GLArea in the GUI
|
||||
_box.Remove(_glScreen);
|
||||
return;
|
||||
}
|
||||
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[0]) { _gameTable.AppendColumn("Icon", new CellRendererPixbuf(), "pixbuf", 0); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[1]) { _gameTable.AppendColumn("Application", new CellRendererText(), "text", 1); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[2]) { _gameTable.AppendColumn("Developer", new CellRendererText(), "text", 2); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[3]) { _gameTable.AppendColumn("Version", new CellRendererText(), "text", 3); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[4]) { _gameTable.AppendColumn("Time Played", new CellRendererText(), "text", 4); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[5]) { _gameTable.AppendColumn("Last Played", new CellRendererText(), "text", 5); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[6]) { _gameTable.AppendColumn("File Ext", new CellRendererText(), "text", 6); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[7]) { _gameTable.AppendColumn("File Size", new CellRendererText(), "text", 7); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[8]) { _gameTable.AppendColumn("Path", new CellRendererText(), "text", 8); }
|
||||
if (File.Exists(SwitchSettings.SwitchConfig.CustomThemePath) && (System.IO.Path.GetExtension(SwitchSettings.SwitchConfig.CustomThemePath) == ".css"))
|
||||
{
|
||||
CssProvider cssProvider = new CssProvider();
|
||||
|
||||
_tableStore = new ListStore(typeof(Gdk.Pixbuf), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string));
|
||||
_gameTable.Model = _tableStore;
|
||||
cssProvider.LoadFromPath(SwitchSettings.SwitchConfig.CustomThemePath);
|
||||
|
||||
UpdateGameTable();
|
||||
// Temporary code section end
|
||||
StyleContext.AddProviderForScreen(Gdk.Screen.Default, cssProvider, 800);
|
||||
}
|
||||
else
|
||||
{
|
||||
_box.Remove(_glScreen);
|
||||
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[0]) { _gameTable.AppendColumn("Icon", new CellRendererPixbuf(), "pixbuf", 0); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[1]) { _gameTable.AppendColumn("Application", new CellRendererText(), "text", 1); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[2]) { _gameTable.AppendColumn("Developer", new CellRendererText(), "text", 2); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[3]) { _gameTable.AppendColumn("Version", new CellRendererText(), "text", 3); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[4]) { _gameTable.AppendColumn("Time Played", new CellRendererText(), "text", 4); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[5]) { _gameTable.AppendColumn("Last Played", new CellRendererText(), "text", 5); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[6]) { _gameTable.AppendColumn("File Ext", new CellRendererText(), "text", 6); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[7]) { _gameTable.AppendColumn("File Size", new CellRendererText(), "text", 7); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[8]) { _gameTable.AppendColumn("Path", new CellRendererText(), "text", 8); }
|
||||
|
||||
_tableStore = new ListStore(typeof(Gdk.Pixbuf), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string));
|
||||
_gameTable.Model = _tableStore;
|
||||
|
||||
UpdateGameTable();
|
||||
Logger.PrintWarning(LogClass.Application, $"The \"custom_theme_path\" section in \"Config.json\" contains an invalid path: \"{SwitchSettings.SwitchConfig.CustomThemePath}\".");
|
||||
}
|
||||
}
|
||||
|
||||
public static void CreateErrorDialog(string errorMessage)
|
||||
private void UpdateColumns()
|
||||
{
|
||||
MessageDialog errorDialog = new MessageDialog(null, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, errorMessage)
|
||||
foreach (TreeViewColumn column in _gameTable.Columns)
|
||||
{
|
||||
Title = "Ryujinx - Error",
|
||||
Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.ryujinxIcon.png"),
|
||||
WindowPosition = WindowPosition.Center
|
||||
};
|
||||
errorDialog.SetSizeRequest(100, 20);
|
||||
errorDialog.Run();
|
||||
errorDialog.Destroy();
|
||||
_gameTable.RemoveColumn(column);
|
||||
}
|
||||
|
||||
CellRendererToggle favToggle = new CellRendererToggle();
|
||||
favToggle.Toggled += FavToggle_Toggled;
|
||||
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.FavColumn) { _gameTable.AppendColumn("Fav", favToggle, "active", 0); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.IconColumn) { _gameTable.AppendColumn("Icon", new CellRendererPixbuf(), "pixbuf", 1); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.AppColumn) { _gameTable.AppendColumn("Application", new CellRendererText(), "text", 2); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.DevColumn) { _gameTable.AppendColumn("Developer", new CellRendererText(), "text", 3); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.VersionColumn) { _gameTable.AppendColumn("Version", new CellRendererText(), "text", 4); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.TimePlayedColumn) { _gameTable.AppendColumn("Time Played", new CellRendererText(), "text", 5); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.LastPlayedColumn) { _gameTable.AppendColumn("Last Played", new CellRendererText(), "text", 6); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.FileExtColumn) { _gameTable.AppendColumn("File Ext", new CellRendererText(), "text", 7); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.FileSizeColumn) { _gameTable.AppendColumn("File Size", new CellRendererText(), "text", 8); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.PathColumn) { _gameTable.AppendColumn("Path", new CellRendererText(), "text", 9); }
|
||||
|
||||
foreach (TreeViewColumn column in _gameTable.Columns)
|
||||
{
|
||||
if (column.Title == "Fav") { _favColumn = column; }
|
||||
else if (column.Title == "Application") { _appColumn = column; }
|
||||
else if (column.Title == "Developer") { _devColumn = column; }
|
||||
else if (column.Title == "Version") { _versionColumn = column; }
|
||||
else if (column.Title == "Time Played") { _timePlayedColumn = column; }
|
||||
else if (column.Title == "Last Played") { _lastPlayedColumn = column; }
|
||||
else if (column.Title == "File Ext") { _fileExtColumn = column; }
|
||||
else if (column.Title == "File Size") { _fileSizeColumn = column; }
|
||||
else if (column.Title == "Path") { _pathColumn = column; }
|
||||
}
|
||||
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.FavColumn) { _favColumn.SortColumnId = 0; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.IconColumn) { _appColumn.SortColumnId = 2; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.AppColumn) { _devColumn.SortColumnId = 3; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.DevColumn) { _versionColumn.SortColumnId = 4; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.TimePlayedColumn) { _timePlayedColumn.SortColumnId = 5; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.LastPlayedColumn) { _lastPlayedColumn.SortColumnId = 6; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.FileExtColumn) { _fileExtColumn.SortColumnId = 7; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.FileSizeColumn) { _fileSizeColumn.SortColumnId = 8; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.PathColumn) { _pathColumn.SortColumnId = 9; }
|
||||
}
|
||||
|
||||
public static void UpdateGameTable()
|
||||
internal static async Task UpdateGameTable()
|
||||
{
|
||||
if (_updatingGameTable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_updatingGameTable = true;
|
||||
|
||||
_tableStore.Clear();
|
||||
ApplicationLibrary.Init(SwitchSettings.SwitchConfig.GameDirs, _device.System.KeySet, _device.System.State.DesiredTitleLanguage);
|
||||
|
||||
foreach (ApplicationLibrary.ApplicationData AppData in ApplicationLibrary.ApplicationLibraryData)
|
||||
{
|
||||
_tableStore.AppendValues(new Gdk.Pixbuf(AppData.Icon, 75, 75), $"{AppData.TitleName}\n{AppData.TitleId.ToUpper()}", AppData.Developer, AppData.Version, AppData.TimePlayed, AppData.LastPlayed, AppData.FileExt, AppData.FileSize, AppData.Path);
|
||||
}
|
||||
}
|
||||
await Task.Run(() => ApplicationLibrary.LoadApplications(SwitchSettings.SwitchConfig.GameDirs, _device.System.KeySet, _device.System.State.DesiredTitleLanguage));
|
||||
|
||||
public static void ApplyTheme()
|
||||
{
|
||||
CssProvider cssProvider = new CssProvider();
|
||||
|
||||
if (SwitchSettings.SwitchConfig.EnableCustomTheme)
|
||||
{
|
||||
if (File.Exists(SwitchSettings.SwitchConfig.CustomThemePath) && (System.IO.Path.GetExtension(SwitchSettings.SwitchConfig.CustomThemePath) == ".css"))
|
||||
{
|
||||
cssProvider.LoadFromPath(SwitchSettings.SwitchConfig.CustomThemePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.PrintWarning(LogClass.Application, $"The \"custom_theme_path\" section in \"Config.json\" contains an invalid path: \"{SwitchSettings.SwitchConfig.CustomThemePath}\"");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cssProvider.LoadFromPath(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Theme.css"));
|
||||
}
|
||||
|
||||
StyleContext.AddProviderForScreen(Gdk.Screen.Default, cssProvider, 800);
|
||||
_updatingGameTable = false;
|
||||
}
|
||||
|
||||
internal void LoadApplication(string path)
|
||||
{
|
||||
if (_gameLoaded)
|
||||
{
|
||||
CreateErrorDialog("A game has already been loaded. Please close the emulator and try again");
|
||||
GtkDialog.CreateErrorDialog("A game has already been loaded. Please close the emulator and try again");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -267,19 +305,23 @@ namespace Ryujinx.UI
|
|||
End();
|
||||
}
|
||||
|
||||
new Thread(new ThreadStart(CreateGameWindow)).Start();
|
||||
#if MACOS_BUILD
|
||||
CreateGameWindow();
|
||||
#else
|
||||
new Thread(CreateGameWindow).Start();
|
||||
#endif
|
||||
|
||||
_gameLoaded = true;
|
||||
_stopEmulation.Sensitive = true;
|
||||
|
||||
if (DiscordIntegrationEnabled)
|
||||
{
|
||||
if (File.ReadAllLines(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "RPsupported.dat")).Contains(_device.System.TitleID))
|
||||
if (File.ReadAllLines(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "RPsupported.dat")).Contains(_device.System.TitleId))
|
||||
{
|
||||
DiscordPresence.Assets.LargeImageKey = _device.System.TitleID;
|
||||
DiscordPresence.Assets.LargeImageKey = _device.System.TitleId;
|
||||
}
|
||||
|
||||
string state = _device.System.TitleID;
|
||||
string state = _device.System.TitleId;
|
||||
|
||||
if (state == null)
|
||||
{
|
||||
|
@ -307,40 +349,37 @@ namespace Ryujinx.UI
|
|||
DiscordClient.SetPresence(DiscordPresence);
|
||||
}
|
||||
|
||||
try
|
||||
string metadataFolder = System.IO.Path.Combine(new VirtualFileSystem().GetBasePath(), "games", _device.System.TitleId, "gui");
|
||||
string metadataFile = System.IO.Path.Combine(metadataFolder, "metadata.json");
|
||||
|
||||
IJsonFormatterResolver resolver = CompositeResolver.Create(new[] { StandardResolver.AllowPrivateSnakeCase });
|
||||
|
||||
ApplicationMetadata appMetadata;
|
||||
|
||||
if (!File.Exists(metadataFile))
|
||||
{
|
||||
string savePath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RyuFS", "nand", "user", "save", "0000000000000000", _userId, _device.System.TitleID);
|
||||
Directory.CreateDirectory(metadataFolder);
|
||||
|
||||
if (File.Exists(System.IO.Path.Combine(savePath, "TimePlayed.dat")) == false)
|
||||
appMetadata = new ApplicationMetadata
|
||||
{
|
||||
Directory.CreateDirectory(savePath);
|
||||
using (FileStream stream = File.OpenWrite(System.IO.Path.Combine(savePath, "TimePlayed.dat")))
|
||||
{
|
||||
stream.Write(Encoding.ASCII.GetBytes("0"));
|
||||
}
|
||||
}
|
||||
Favorite = false,
|
||||
TimePlayed = 0,
|
||||
LastPlayed = "Never"
|
||||
};
|
||||
|
||||
if (File.Exists(System.IO.Path.Combine(savePath, "LastPlayed.dat")) == false)
|
||||
{
|
||||
Directory.CreateDirectory(savePath);
|
||||
using (FileStream stream = File.OpenWrite(System.IO.Path.Combine(savePath, "LastPlayed.dat")))
|
||||
{
|
||||
stream.Write(Encoding.ASCII.GetBytes("Never"));
|
||||
}
|
||||
}
|
||||
|
||||
using (FileStream stream = File.OpenWrite(System.IO.Path.Combine(savePath, "LastPlayed.dat")))
|
||||
{
|
||||
using (StreamWriter writer = new StreamWriter(stream))
|
||||
{
|
||||
writer.WriteLine(DateTime.UtcNow);
|
||||
}
|
||||
}
|
||||
byte[] data = JsonSerializer.Serialize(appMetadata, resolver);
|
||||
File.WriteAllText(metadataFile, Encoding.UTF8.GetString(data, 0, data.Length).PrettyPrintJson());
|
||||
}
|
||||
catch (ArgumentNullException)
|
||||
|
||||
using (Stream stream = File.OpenRead(metadataFile))
|
||||
{
|
||||
Logger.PrintWarning(LogClass.Application, $"Could not access save path to retrieve time/last played data using: UserID: {_userId}, TitleID: {_device.System.TitleID}");
|
||||
appMetadata = JsonSerializer.Deserialize<ApplicationMetadata>(stream, resolver);
|
||||
}
|
||||
|
||||
appMetadata.LastPlayed = DateTime.UtcNow.ToString();
|
||||
|
||||
byte[] saveData = JsonSerializer.Serialize(appMetadata, resolver);
|
||||
File.WriteAllText(metadataFile, Encoding.UTF8.GetString(saveData, 0, saveData.Length).PrettyPrintJson());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -348,9 +387,9 @@ namespace Ryujinx.UI
|
|||
{
|
||||
Configuration.ConfigureHid(_device, SwitchSettings.SwitchConfig);
|
||||
|
||||
using (GlScreen screen = new GlScreen(_device, _renderer))
|
||||
using (_screen = new GlScreen(_device, _renderer))
|
||||
{
|
||||
screen.MainLoop();
|
||||
_screen.MainLoop();
|
||||
|
||||
End();
|
||||
}
|
||||
|
@ -358,41 +397,49 @@ namespace Ryujinx.UI
|
|||
|
||||
private static void End()
|
||||
{
|
||||
if (_ending)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_ending = true;
|
||||
|
||||
if (_gameLoaded)
|
||||
{
|
||||
try
|
||||
{
|
||||
string savePath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RyuFS", "nand", "user", "save", "0000000000000000", _userId, _device.System.TitleID);
|
||||
double currentPlayTime = 0;
|
||||
string metadataFolder = System.IO.Path.Combine(new VirtualFileSystem().GetBasePath(), "games", _device.System.TitleId, "gui");
|
||||
string metadataFile = System.IO.Path.Combine(metadataFolder, "metadata.json");
|
||||
|
||||
using (FileStream stream = File.OpenRead(System.IO.Path.Combine(savePath, "LastPlayed.dat")))
|
||||
IJsonFormatterResolver resolver = CompositeResolver.Create(new[] { StandardResolver.AllowPrivateSnakeCase });
|
||||
|
||||
ApplicationMetadata appMetadata;
|
||||
|
||||
if (!File.Exists(metadataFile))
|
||||
{
|
||||
Directory.CreateDirectory(metadataFolder);
|
||||
|
||||
appMetadata = new ApplicationMetadata
|
||||
{
|
||||
using (StreamReader reader = new StreamReader(stream))
|
||||
{
|
||||
DateTime startTime = DateTime.Parse(reader.ReadLine());
|
||||
Favorite = false,
|
||||
TimePlayed = 0,
|
||||
LastPlayed = "Never"
|
||||
};
|
||||
|
||||
using (FileStream lastPlayedStream = File.OpenRead(System.IO.Path.Combine(savePath, "TimePlayed.dat")))
|
||||
{
|
||||
using (StreamReader lastPlayedReader = new StreamReader(lastPlayedStream))
|
||||
{
|
||||
currentPlayTime = double.Parse(lastPlayedReader.ReadLine());
|
||||
}
|
||||
}
|
||||
|
||||
using (FileStream timePlayedStream = File.OpenWrite(System.IO.Path.Combine(savePath, "TimePlayed.dat")))
|
||||
{
|
||||
using (StreamWriter timePlayedWriter = new StreamWriter(timePlayedStream))
|
||||
{
|
||||
timePlayedWriter.WriteLine(currentPlayTime + Math.Round(DateTime.UtcNow.Subtract(startTime).TotalSeconds, MidpointRounding.AwayFromZero));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
byte[] data = JsonSerializer.Serialize(appMetadata, resolver);
|
||||
File.WriteAllText(metadataFile, Encoding.UTF8.GetString(data, 0, data.Length).PrettyPrintJson());
|
||||
}
|
||||
catch (ArgumentNullException)
|
||||
|
||||
using (Stream stream = File.OpenRead(metadataFile))
|
||||
{
|
||||
Logger.PrintWarning(LogClass.Application, $"Could not access save path to retrieve time/last played data using: UserID: {_userId}, TitleID: {_device.System.TitleID}");
|
||||
appMetadata = JsonSerializer.Deserialize<ApplicationMetadata>(stream, resolver);
|
||||
}
|
||||
|
||||
DateTime lastPlayedDateTime = DateTime.Parse(appMetadata.LastPlayed);
|
||||
double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds;
|
||||
|
||||
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
|
||||
|
||||
byte[] saveData = JsonSerializer.Serialize(appMetadata, resolver);
|
||||
File.WriteAllText(metadataFile, Encoding.UTF8.GetString(saveData, 0, saveData.Length).PrettyPrintJson());
|
||||
}
|
||||
|
||||
Profile.FinishProfiling();
|
||||
|
@ -425,15 +472,69 @@ namespace Ryujinx.UI
|
|||
}
|
||||
|
||||
//Events
|
||||
private void Row_Activated(object o, RowActivatedArgs args)
|
||||
private void Application_Added(object sender, ApplicationAddedEventArgs e)
|
||||
{
|
||||
Application.Invoke(delegate
|
||||
{
|
||||
_tableStore.AppendValues(
|
||||
e.AppData.Favorite,
|
||||
new Gdk.Pixbuf(e.AppData.Icon, 75, 75),
|
||||
$"{e.AppData.TitleName}\n{e.AppData.TitleId.ToUpper()}",
|
||||
e.AppData.Developer,
|
||||
e.AppData.Version,
|
||||
e.AppData.TimePlayed,
|
||||
e.AppData.LastPlayed,
|
||||
e.AppData.FileExtension,
|
||||
e.AppData.FileSize,
|
||||
e.AppData.Path);
|
||||
|
||||
_progressLabel.Text = $"{e.NumAppsLoaded}/{e.NumAppsFound} Games Loaded";
|
||||
_progressBar.Value = (float)e.NumAppsLoaded / e.NumAppsFound;
|
||||
});
|
||||
}
|
||||
|
||||
private void FavToggle_Toggled(object sender, ToggledArgs args)
|
||||
{
|
||||
_tableStore.GetIter(out TreeIter treeIter, new TreePath(args.Path));
|
||||
|
||||
string titleId = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[1].ToLower();
|
||||
string metadataPath = System.IO.Path.Combine(new VirtualFileSystem().GetBasePath(), "games", titleId, "gui", "metadata.json");
|
||||
|
||||
IJsonFormatterResolver resolver = CompositeResolver.Create(new[] { StandardResolver.AllowPrivateSnakeCase });
|
||||
|
||||
ApplicationMetadata appMetadata;
|
||||
|
||||
using (Stream stream = File.OpenRead(metadataPath))
|
||||
{
|
||||
appMetadata = JsonSerializer.Deserialize<ApplicationMetadata>(stream, resolver);
|
||||
}
|
||||
|
||||
if ((bool)_tableStore.GetValue(treeIter, 0))
|
||||
{
|
||||
_tableStore.SetValue(treeIter, 0, false);
|
||||
|
||||
appMetadata.Favorite = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_tableStore.SetValue(treeIter, 0, true);
|
||||
|
||||
appMetadata.Favorite = true;
|
||||
}
|
||||
|
||||
byte[] saveData = JsonSerializer.Serialize(appMetadata, resolver);
|
||||
File.WriteAllText(metadataPath, Encoding.UTF8.GetString(saveData, 0, saveData.Length).PrettyPrintJson());
|
||||
}
|
||||
|
||||
private void Row_Activated(object sender, RowActivatedArgs args)
|
||||
{
|
||||
_tableStore.GetIter(out TreeIter treeIter, new TreePath(args.Path.ToString()));
|
||||
string path = (string)_tableStore.GetValue(treeIter, 8);
|
||||
string path = (string)_tableStore.GetValue(treeIter, 9);
|
||||
|
||||
LoadApplication(path);
|
||||
}
|
||||
|
||||
private void Load_Application_File(object o, EventArgs args)
|
||||
private void Load_Application_File(object sender, EventArgs args)
|
||||
{
|
||||
FileChooserDialog fileChooser = new FileChooserDialog("Choose the file to open", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Open", ResponseType.Accept);
|
||||
|
||||
|
@ -450,10 +551,10 @@ namespace Ryujinx.UI
|
|||
LoadApplication(fileChooser.Filename);
|
||||
}
|
||||
|
||||
fileChooser.Destroy();
|
||||
fileChooser.Dispose();
|
||||
}
|
||||
|
||||
private void Load_Application_Folder(object o, EventArgs args)
|
||||
private void Load_Application_Folder(object sender, EventArgs args)
|
||||
{
|
||||
FileChooserDialog fileChooser = new FileChooserDialog("Choose the folder to open", this, FileChooserAction.SelectFolder, "Cancel", ResponseType.Cancel, "Open", ResponseType.Accept);
|
||||
|
||||
|
@ -462,35 +563,39 @@ namespace Ryujinx.UI
|
|||
LoadApplication(fileChooser.Filename);
|
||||
}
|
||||
|
||||
fileChooser.Destroy();
|
||||
fileChooser.Dispose();
|
||||
}
|
||||
|
||||
private void Open_Ryu_Folder(object o, EventArgs args)
|
||||
private void Open_Ryu_Folder(object sender, EventArgs args)
|
||||
{
|
||||
Process.Start(new ProcessStartInfo()
|
||||
{
|
||||
FileName = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RyuFs"),
|
||||
FileName = new VirtualFileSystem().GetBasePath(),
|
||||
UseShellExecute = true,
|
||||
Verb = "open"
|
||||
});
|
||||
}
|
||||
|
||||
private void Exit_Pressed(object o, EventArgs args)
|
||||
private void Exit_Pressed(object sender, EventArgs args)
|
||||
{
|
||||
_screen?.Exit();
|
||||
End();
|
||||
}
|
||||
|
||||
private void Window_Close(object o, DeleteEventArgs args)
|
||||
private void Window_Close(object sender, DeleteEventArgs args)
|
||||
{
|
||||
_screen?.Exit();
|
||||
End();
|
||||
}
|
||||
|
||||
private void StopEmulation_Pressed(object o, EventArgs args)
|
||||
private void StopEmulation_Pressed(object sender, EventArgs args)
|
||||
{
|
||||
// TODO: Write logic to kill running game
|
||||
|
||||
_gameLoaded = false;
|
||||
}
|
||||
|
||||
private void FullScreen_Toggled(object o, EventArgs args)
|
||||
private void FullScreen_Toggled(object sender, EventArgs args)
|
||||
{
|
||||
if (_fullScreen.Active)
|
||||
{
|
||||
|
@ -502,19 +607,15 @@ namespace Ryujinx.UI
|
|||
}
|
||||
}
|
||||
|
||||
private void Settings_Pressed(object o, EventArgs args)
|
||||
private void Settings_Pressed(object sender, EventArgs args)
|
||||
{
|
||||
SwitchSettings SettingsWin = new SwitchSettings(_device);
|
||||
|
||||
_gtkApplication.Register(GLib.Cancellable.Current);
|
||||
_gtkApplication.AddWindow(SettingsWin);
|
||||
|
||||
SettingsWin.Show();
|
||||
SwitchSettings settingsWin = new SwitchSettings(_device);
|
||||
settingsWin.Show();
|
||||
}
|
||||
|
||||
private void Update_Pressed(object o, EventArgs args)
|
||||
private void Update_Pressed(object sender, EventArgs args)
|
||||
{
|
||||
string ryuUpdater = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RyuFS", "RyuUpdater.exe");
|
||||
string ryuUpdater = System.IO.Path.Combine(new VirtualFileSystem().GetBasePath(), "RyuUpdater.exe");
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -522,81 +623,249 @@ namespace Ryujinx.UI
|
|||
}
|
||||
catch(System.ComponentModel.Win32Exception)
|
||||
{
|
||||
CreateErrorDialog("Update canceled by user or updater was not found");
|
||||
GtkDialog.CreateErrorDialog("Update canceled by user or updater was not found");
|
||||
}
|
||||
}
|
||||
|
||||
private void About_Pressed(object o, EventArgs args)
|
||||
private void About_Pressed(object sender, EventArgs args)
|
||||
{
|
||||
AboutWindow AboutWin = new AboutWindow();
|
||||
|
||||
_gtkApplication.Register(GLib.Cancellable.Current);
|
||||
_gtkApplication.AddWindow(AboutWin);
|
||||
|
||||
AboutWin.Show();
|
||||
AboutWindow aboutWin = new AboutWindow();
|
||||
aboutWin.Show();
|
||||
}
|
||||
|
||||
private void Icon_Toggled(object o, EventArgs args)
|
||||
private void Fav_Toggled(object sender, EventArgs args)
|
||||
{
|
||||
SwitchSettings.SwitchConfig.GuiColumns[0] = _iconToggle.Active;
|
||||
GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
|
||||
|
||||
updatedColumns.FavColumn = _favToggle.Active;
|
||||
SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
|
||||
|
||||
Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
|
||||
|
||||
UpdateColumns();
|
||||
}
|
||||
|
||||
private void Title_Toggled(object o, EventArgs args)
|
||||
private void Icon_Toggled(object sender, EventArgs args)
|
||||
{
|
||||
SwitchSettings.SwitchConfig.GuiColumns[1] = _titleToggle.Active;
|
||||
GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
|
||||
|
||||
updatedColumns.IconColumn = _iconToggle.Active;
|
||||
SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
|
||||
|
||||
Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
|
||||
|
||||
UpdateColumns();
|
||||
}
|
||||
|
||||
private void Developer_Toggled(object o, EventArgs args)
|
||||
private void Title_Toggled(object sender, EventArgs args)
|
||||
{
|
||||
SwitchSettings.SwitchConfig.GuiColumns[2] = _developerToggle.Active;
|
||||
GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
|
||||
|
||||
updatedColumns.AppColumn = _appToggle.Active;
|
||||
SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
|
||||
|
||||
Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
|
||||
|
||||
UpdateColumns();
|
||||
}
|
||||
|
||||
private void Version_Toggled(object o, EventArgs args)
|
||||
private void Developer_Toggled(object sender, EventArgs args)
|
||||
{
|
||||
SwitchSettings.SwitchConfig.GuiColumns[3] = _versionToggle.Active;
|
||||
GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
|
||||
|
||||
updatedColumns.DevColumn = _developerToggle.Active;
|
||||
SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
|
||||
|
||||
Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
|
||||
|
||||
UpdateColumns();
|
||||
}
|
||||
|
||||
private void TimePlayed_Toggled(object o, EventArgs args)
|
||||
private void Version_Toggled(object sender, EventArgs args)
|
||||
{
|
||||
SwitchSettings.SwitchConfig.GuiColumns[4] = _timePlayedToggle.Active;
|
||||
GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
|
||||
|
||||
updatedColumns.VersionColumn = _versionToggle.Active;
|
||||
SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
|
||||
|
||||
Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
|
||||
|
||||
UpdateColumns();
|
||||
}
|
||||
|
||||
private void LastPlayed_Toggled(object o, EventArgs args)
|
||||
private void TimePlayed_Toggled(object sender, EventArgs args)
|
||||
{
|
||||
SwitchSettings.SwitchConfig.GuiColumns[5] = _lastPlayedToggle.Active;
|
||||
GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
|
||||
|
||||
updatedColumns.TimePlayedColumn = _timePlayedToggle.Active;
|
||||
SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
|
||||
|
||||
Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
|
||||
|
||||
UpdateColumns();
|
||||
}
|
||||
|
||||
private void FileExt_Toggled(object o, EventArgs args)
|
||||
private void LastPlayed_Toggled(object sender, EventArgs args)
|
||||
{
|
||||
SwitchSettings.SwitchConfig.GuiColumns[6] = _fileExtToggle.Active;
|
||||
GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
|
||||
|
||||
updatedColumns.LastPlayedColumn = _lastPlayedToggle.Active;
|
||||
SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
|
||||
|
||||
Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
|
||||
|
||||
UpdateColumns();
|
||||
}
|
||||
|
||||
private void FileSize_Toggled(object o, EventArgs args)
|
||||
private void FileExt_Toggled(object sender, EventArgs args)
|
||||
{
|
||||
SwitchSettings.SwitchConfig.GuiColumns[7] = _fileSizeToggle.Active;
|
||||
GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
|
||||
|
||||
updatedColumns.FileExtColumn = _fileExtToggle.Active;
|
||||
SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
|
||||
|
||||
Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
|
||||
|
||||
UpdateColumns();
|
||||
}
|
||||
|
||||
private void Path_Toggled(object o, EventArgs args)
|
||||
private void FileSize_Toggled(object sender, EventArgs args)
|
||||
{
|
||||
SwitchSettings.SwitchConfig.GuiColumns[8] = _pathToggle.Active;
|
||||
GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
|
||||
|
||||
updatedColumns.FileSizeColumn = _fileSizeToggle.Active;
|
||||
SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
|
||||
|
||||
Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
|
||||
|
||||
UpdateColumns();
|
||||
}
|
||||
|
||||
private void Path_Toggled(object sender, EventArgs args)
|
||||
{
|
||||
GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
|
||||
|
||||
updatedColumns.PathColumn = _pathToggle.Active;
|
||||
SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
|
||||
|
||||
Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
|
||||
|
||||
UpdateColumns();
|
||||
}
|
||||
|
||||
private void RefreshList_Pressed(object sender, ButtonReleaseEventArgs args)
|
||||
{
|
||||
#pragma warning disable CS4014
|
||||
UpdateGameTable();
|
||||
#pragma warning restore CS4014
|
||||
}
|
||||
|
||||
private static int TimePlayedSort(ITreeModel model, TreeIter a, TreeIter b)
|
||||
{
|
||||
string aValue = model.GetValue(a, 5).ToString();
|
||||
string bValue = model.GetValue(b, 5).ToString();
|
||||
|
||||
if (aValue.Length > 4 && aValue.Substring(aValue.Length - 4) == "mins")
|
||||
{
|
||||
aValue = (float.Parse(aValue.Substring(0, aValue.Length - 5)) * 60).ToString();
|
||||
}
|
||||
else if (aValue.Length > 3 && aValue.Substring(aValue.Length - 3) == "hrs")
|
||||
{
|
||||
aValue = (float.Parse(aValue.Substring(0, aValue.Length - 4)) * 3600).ToString();
|
||||
}
|
||||
else if (aValue.Length > 4 && aValue.Substring(aValue.Length - 4) == "days")
|
||||
{
|
||||
aValue = (float.Parse(aValue.Substring(0, aValue.Length - 5)) * 86400).ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
aValue = aValue.Substring(0, aValue.Length - 1);
|
||||
}
|
||||
|
||||
if (bValue.Length > 4 && bValue.Substring(bValue.Length - 4) == "mins")
|
||||
{
|
||||
bValue = (float.Parse(bValue.Substring(0, bValue.Length - 5)) * 60).ToString();
|
||||
}
|
||||
else if (bValue.Length > 3 && bValue.Substring(bValue.Length - 3) == "hrs")
|
||||
{
|
||||
bValue = (float.Parse(bValue.Substring(0, bValue.Length - 4)) * 3600).ToString();
|
||||
}
|
||||
else if (bValue.Length > 4 && bValue.Substring(bValue.Length - 4) == "days")
|
||||
{
|
||||
bValue = (float.Parse(bValue.Substring(0, bValue.Length - 5)) * 86400).ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
bValue = bValue.Substring(0, bValue.Length - 1);
|
||||
}
|
||||
|
||||
if (float.Parse(aValue) > float.Parse(bValue))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (float.Parse(bValue) > float.Parse(aValue))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static int LastPlayedSort(ITreeModel model, TreeIter a, TreeIter b)
|
||||
{
|
||||
string aValue = model.GetValue(a, 6).ToString();
|
||||
string bValue = model.GetValue(b, 6).ToString();
|
||||
|
||||
if (aValue == "Never")
|
||||
{
|
||||
aValue = DateTime.UnixEpoch.ToString();
|
||||
}
|
||||
|
||||
if (bValue == "Never")
|
||||
{
|
||||
bValue = DateTime.UnixEpoch.ToString();
|
||||
}
|
||||
|
||||
return DateTime.Compare(DateTime.Parse(bValue), DateTime.Parse(aValue));
|
||||
}
|
||||
|
||||
private static int FileSizeSort(ITreeModel model, TreeIter a, TreeIter b)
|
||||
{
|
||||
string aValue = model.GetValue(a, 8).ToString();
|
||||
string bValue = model.GetValue(b, 8).ToString();
|
||||
|
||||
if (aValue.Substring(aValue.Length - 2) == "GB")
|
||||
{
|
||||
aValue = (float.Parse(aValue[0..^2]) * 1024).ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
aValue = aValue[0..^2];
|
||||
}
|
||||
|
||||
if (bValue.Substring(bValue.Length - 2) == "GB")
|
||||
{
|
||||
bValue = (float.Parse(bValue[0..^2]) * 1024).ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
bValue = bValue[0..^2];
|
||||
}
|
||||
|
||||
if (float.Parse(aValue) > float.Parse(bValue))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (float.Parse(bValue) > float.Parse(aValue))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -126,13 +126,23 @@
|
|||
<object class="GtkMenuItem" id="GUIColumns">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Select which GUI columns to enable (restart Ryujinx for these changes to take effect)</property>
|
||||
<property name="tooltip_text" translatable="yes">Select which GUI columns to enable</property>
|
||||
<property name="label" translatable="yes">Enable GUI Columns</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="GtkCheckMenuItem" id="_favToggle">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Enable or Disable Favorite Games Column in the game list</property>
|
||||
<property name="label" translatable="yes">Enable Favorite Games Column</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="toggled" handler="Fav_Toggled" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckMenuItem" id="_iconToggle">
|
||||
<property name="visible">True</property>
|
||||
|
@ -144,7 +154,7 @@
|
|||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckMenuItem" id="_titleToggle">
|
||||
<object class="GtkCheckMenuItem" id="_appToggle">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Enable or Disable Title Name/ID Column in the game list</property>
|
||||
|
@ -303,22 +313,96 @@
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="_gameTableWindow">
|
||||
<object class="GtkBox" id="MainBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="_gameTable">
|
||||
<object class="GtkScrolledWindow" id="_gameTableWindow">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="headers_clickable">False</property>
|
||||
<property name="reorderable">True</property>
|
||||
<property name="hover_selection">True</property>
|
||||
<signal name="row-activated" handler="Row_Activated" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="_gameTable">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="reorderable">True</property>
|
||||
<property name="hover_selection">True</property>
|
||||
<signal name="row-activated" handler="Row_Activated" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="FooterBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkEventBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<signal name="button-release-event" handler="RefreshList_Pressed" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="name">RefreshList</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-refresh</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="_progressLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_top">2</property>
|
||||
<property name="margin_bottom">2</property>
|
||||
<property name="label" translatable="yes">0/0 Games Loaded</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLevelBar" id="_progressBar">
|
||||
<property name="width_request">200</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
|
@ -327,20 +411,6 @@
|
|||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkGLArea" id="_glScreen">
|
||||
<property name="width_request">1280</property>
|
||||
<property name="height_request">720</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="app_paintable">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
|
|
@ -3,7 +3,7 @@ using OpenTK.Input;
|
|||
using Ryujinx.HLE.Input;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.UI.Input
|
||||
namespace Ryujinx.Ui.Input
|
||||
{
|
||||
public enum ControllerInputId
|
||||
{
|
||||
|
@ -64,7 +64,6 @@ namespace Ryujinx.UI.Input
|
|||
public struct NpadControllerRight
|
||||
{
|
||||
public ControllerInputId Stick;
|
||||
public ControllerInputId StickY;
|
||||
public ControllerInputId StickButton;
|
||||
public ControllerInputId ButtonA;
|
||||
public ControllerInputId ButtonB;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using OpenTK.Input;
|
||||
using Ryujinx.HLE.Input;
|
||||
|
||||
namespace Ryujinx.UI.Input
|
||||
namespace Ryujinx.Ui.Input
|
||||
{
|
||||
public struct NpadKeyboardLeft
|
||||
{
|
||||
|
|
|
@ -1,27 +1,29 @@
|
|||
using Gtk;
|
||||
using GUI = Gtk.Builder.ObjectAttribute;
|
||||
using Ryujinx.HLE.HOS.SystemState;
|
||||
using Ryujinx.HLE.Input;
|
||||
using Ryujinx.UI.Input;
|
||||
using Ryujinx.Ui.Input;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Ryujinx.UI
|
||||
using GUI = Gtk.Builder.ObjectAttribute;
|
||||
|
||||
namespace Ryujinx.Ui
|
||||
{
|
||||
public class SwitchSettings : Window
|
||||
{
|
||||
internal static Configuration SwitchConfig { get; set; }
|
||||
|
||||
internal HLE.Switch Device { get; set; }
|
||||
private readonly HLE.Switch _device;
|
||||
|
||||
private static ListStore _gameDirsBoxStore;
|
||||
|
||||
private static bool _listeningForKeypress;
|
||||
|
||||
#pragma warning disable 649
|
||||
#pragma warning disable CS0649
|
||||
#pragma warning disable IDE0044
|
||||
[GUI] Window _settingsWin;
|
||||
[GUI] CheckButton _errorLogToggle;
|
||||
[GUI] CheckButton _warningLogToggle;
|
||||
|
@ -51,7 +53,7 @@ namespace Ryujinx.UI
|
|||
[GUI] ToggleButton _removeDir;
|
||||
[GUI] Entry _logPath;
|
||||
[GUI] Entry _graphicsShadersDumpPath;
|
||||
[GUI] Image _controllerImage;
|
||||
[GUI] Image _controller1Image;
|
||||
|
||||
[GUI] ComboBoxText _controller1Type;
|
||||
[GUI] ToggleButton _lStickUp1;
|
||||
|
@ -78,67 +80,70 @@ namespace Ryujinx.UI
|
|||
[GUI] ToggleButton _plus1;
|
||||
[GUI] ToggleButton _r1;
|
||||
[GUI] ToggleButton _zR1;
|
||||
#pragma warning restore 649
|
||||
#pragma warning restore CS0649
|
||||
#pragma warning restore IDE0044
|
||||
|
||||
public static void ConfigureSettings(Configuration Instance) { SwitchConfig = Instance; }
|
||||
public static void ConfigureSettings(Configuration instance) { SwitchConfig = instance; }
|
||||
|
||||
public SwitchSettings(HLE.Switch device) : this(new Builder("Ryujinx.Ui.SwitchSettings.glade"), device) { }
|
||||
|
||||
private SwitchSettings(Builder builder, HLE.Switch device) : base(builder.GetObject("_settingsWin").Handle)
|
||||
{
|
||||
Device = device;
|
||||
|
||||
builder.Autoconnect(this);
|
||||
|
||||
_settingsWin.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.ryujinxIcon.png");
|
||||
_controllerImage.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.JoyCon.png", 500, 500);
|
||||
_device = device;
|
||||
|
||||
_settingsWin.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png");
|
||||
_controller1Image.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.JoyCon.png", 500, 500);
|
||||
|
||||
//Bind Events
|
||||
_lStickUp1.Clicked += (o, args) => Button_Pressed(o, args, _lStickUp1);
|
||||
_lStickDown1.Clicked += (o, args) => Button_Pressed(o, args, _lStickDown1);
|
||||
_lStickLeft1.Clicked += (o, args) => Button_Pressed(o, args, _lStickLeft1);
|
||||
_lStickRight1.Clicked += (o, args) => Button_Pressed(o, args, _lStickRight1);
|
||||
_lStickButton1.Clicked += (o, args) => Button_Pressed(o, args, _lStickButton1);
|
||||
_dpadUp1.Clicked += (o, args) => Button_Pressed(o, args, _dpadUp1);
|
||||
_dpadDown1.Clicked += (o, args) => Button_Pressed(o, args, _dpadDown1);
|
||||
_dpadLeft1.Clicked += (o, args) => Button_Pressed(o, args, _dpadLeft1);
|
||||
_dpadRight1.Clicked += (o, args) => Button_Pressed(o, args, _dpadRight1);
|
||||
_minus1.Clicked += (o, args) => Button_Pressed(o, args, _minus1);
|
||||
_l1.Clicked += (o, args) => Button_Pressed(o, args, _l1);
|
||||
_zL1.Clicked += (o, args) => Button_Pressed(o, args, _zL1);
|
||||
_rStickUp1.Clicked += (o, args) => Button_Pressed(o, args, _rStickUp1);
|
||||
_rStickDown1.Clicked += (o, args) => Button_Pressed(o, args, _rStickDown1);
|
||||
_rStickLeft1.Clicked += (o, args) => Button_Pressed(o, args, _rStickLeft1);
|
||||
_rStickRight1.Clicked += (o, args) => Button_Pressed(o, args, _rStickRight1);
|
||||
_rStickButton1.Clicked += (o, args) => Button_Pressed(o, args, _rStickButton1);
|
||||
_a1.Clicked += (o, args) => Button_Pressed(o, args, _a1);
|
||||
_b1.Clicked += (o, args) => Button_Pressed(o, args, _b1);
|
||||
_x1.Clicked += (o, args) => Button_Pressed(o, args, _x1);
|
||||
_y1.Clicked += (o, args) => Button_Pressed(o, args, _y1);
|
||||
_plus1.Clicked += (o, args) => Button_Pressed(o, args, _plus1);
|
||||
_r1.Clicked += (o, args) => Button_Pressed(o, args, _r1);
|
||||
_zR1.Clicked += (o, args) => Button_Pressed(o, args, _zR1);
|
||||
_lStickUp1.Clicked += (sender, args) => Button_Pressed(sender, args, _lStickUp1);
|
||||
_lStickDown1.Clicked += (sender, args) => Button_Pressed(sender, args, _lStickDown1);
|
||||
_lStickLeft1.Clicked += (sender, args) => Button_Pressed(sender, args, _lStickLeft1);
|
||||
_lStickRight1.Clicked += (sender, args) => Button_Pressed(sender, args, _lStickRight1);
|
||||
_lStickButton1.Clicked += (sender, args) => Button_Pressed(sender, args, _lStickButton1);
|
||||
_dpadUp1.Clicked += (sender, args) => Button_Pressed(sender, args, _dpadUp1);
|
||||
_dpadDown1.Clicked += (sender, args) => Button_Pressed(sender, args, _dpadDown1);
|
||||
_dpadLeft1.Clicked += (sender, args) => Button_Pressed(sender, args, _dpadLeft1);
|
||||
_dpadRight1.Clicked += (sender, args) => Button_Pressed(sender, args, _dpadRight1);
|
||||
_minus1.Clicked += (sender, args) => Button_Pressed(sender, args, _minus1);
|
||||
_l1.Clicked += (sender, args) => Button_Pressed(sender, args, _l1);
|
||||
_zL1.Clicked += (sender, args) => Button_Pressed(sender, args, _zL1);
|
||||
_rStickUp1.Clicked += (sender, args) => Button_Pressed(sender, args, _rStickUp1);
|
||||
_rStickDown1.Clicked += (sender, args) => Button_Pressed(sender, args, _rStickDown1);
|
||||
_rStickLeft1.Clicked += (sender, args) => Button_Pressed(sender, args, _rStickLeft1);
|
||||
_rStickRight1.Clicked += (sender, args) => Button_Pressed(sender, args, _rStickRight1);
|
||||
_rStickButton1.Clicked += (sender, args) => Button_Pressed(sender, args, _rStickButton1);
|
||||
_a1.Clicked += (sender, args) => Button_Pressed(sender, args, _a1);
|
||||
_b1.Clicked += (sender, args) => Button_Pressed(sender, args, _b1);
|
||||
_x1.Clicked += (sender, args) => Button_Pressed(sender, args, _x1);
|
||||
_y1.Clicked += (sender, args) => Button_Pressed(sender, args, _y1);
|
||||
_plus1.Clicked += (sender, args) => Button_Pressed(sender, args, _plus1);
|
||||
_r1.Clicked += (sender, args) => Button_Pressed(sender, args, _r1);
|
||||
_zR1.Clicked += (sender, args) => Button_Pressed(sender, args, _zR1);
|
||||
_controller1Type.Changed += (sender, args) => Controller_Changed(sender, args, _controller1Type.ActiveId, _controller1Image);
|
||||
|
||||
//Setup Currents
|
||||
if (SwitchConfig.EnableFileLog) { _fileLogToggle.Click(); }
|
||||
if (SwitchConfig.LoggingEnableError) { _errorLogToggle.Click(); }
|
||||
if (SwitchConfig.LoggingEnableWarn) { _warningLogToggle.Click(); }
|
||||
if (SwitchConfig.LoggingEnableInfo) { _infoLogToggle.Click(); }
|
||||
if (SwitchConfig.LoggingEnableStub) { _stubLogToggle.Click(); }
|
||||
if (SwitchConfig.LoggingEnableDebug) { _debugLogToggle.Click(); }
|
||||
if (SwitchConfig.LoggingEnableGuest) { _guestLogToggle.Click(); }
|
||||
if (SwitchConfig.LoggingEnableFsAccessLog) { _fsAccessLogToggle.Click(); }
|
||||
if (SwitchConfig.DockedMode) { _dockedModeToggle.Click(); }
|
||||
if (SwitchConfig.EnableDiscordIntegration) { _discordToggle.Click(); }
|
||||
if (SwitchConfig.EnableVsync) { _vSyncToggle.Click(); }
|
||||
if (SwitchConfig.EnableMulticoreScheduling) { _multiSchedToggle.Click(); }
|
||||
if (SwitchConfig.EnableFsIntegrityChecks) { _fsicToggle.Click(); }
|
||||
if (SwitchConfig.IgnoreMissingServices) { _ignoreToggle.Click(); }
|
||||
if (SwitchConfig.EnableKeyboard) { _directKeyboardAccess.Click(); }
|
||||
if (SwitchConfig.EnableCustomTheme) { _custThemeToggle.Click(); }
|
||||
if (SwitchConfig.EnableFileLog) _fileLogToggle.Click();
|
||||
if (SwitchConfig.LoggingEnableError) _errorLogToggle.Click();
|
||||
if (SwitchConfig.LoggingEnableWarn) _warningLogToggle.Click();
|
||||
if (SwitchConfig.LoggingEnableInfo) _infoLogToggle.Click();
|
||||
if (SwitchConfig.LoggingEnableStub) _stubLogToggle.Click();
|
||||
if (SwitchConfig.LoggingEnableDebug) _debugLogToggle.Click();
|
||||
if (SwitchConfig.LoggingEnableGuest) _guestLogToggle.Click();
|
||||
if (SwitchConfig.LoggingEnableFsAccessLog) _fsAccessLogToggle.Click();
|
||||
if (SwitchConfig.DockedMode) _dockedModeToggle.Click();
|
||||
if (SwitchConfig.EnableDiscordIntegration) _discordToggle.Click();
|
||||
if (SwitchConfig.EnableVsync) _vSyncToggle.Click();
|
||||
if (SwitchConfig.EnableMulticoreScheduling) _multiSchedToggle.Click();
|
||||
if (SwitchConfig.EnableFsIntegrityChecks) _fsicToggle.Click();
|
||||
if (SwitchConfig.IgnoreMissingServices) _ignoreToggle.Click();
|
||||
if (SwitchConfig.EnableKeyboard) _directKeyboardAccess.Click();
|
||||
if (SwitchConfig.EnableCustomTheme) _custThemeToggle.Click();
|
||||
|
||||
_systemLanguageSelect.SetActiveId(SwitchConfig.SystemLanguage.ToString());
|
||||
_controller1Type .SetActiveId(SwitchConfig.ControllerType.ToString());
|
||||
Controller_Changed(null, null, _controller1Type.ActiveId, _controller1Image);
|
||||
|
||||
_lStickUp1.Label = SwitchConfig.KeyboardControls.LeftJoycon.StickUp.ToString();
|
||||
_lStickDown1.Label = SwitchConfig.KeyboardControls.LeftJoycon.StickDown.ToString();
|
||||
|
@ -190,7 +195,7 @@ namespace Ryujinx.UI
|
|||
}
|
||||
|
||||
//Events
|
||||
private void Button_Pressed(object obj, EventArgs args, ToggleButton Button)
|
||||
private void Button_Pressed(object sender, EventArgs args, ToggleButton button)
|
||||
{
|
||||
if (_listeningForKeypress == false)
|
||||
{
|
||||
|
@ -198,25 +203,25 @@ namespace Ryujinx.UI
|
|||
|
||||
_listeningForKeypress = true;
|
||||
|
||||
void On_KeyPress(object Obj, KeyPressEventArgs KeyPressed)
|
||||
void On_KeyPress(object o, KeyPressEventArgs keyPressed)
|
||||
{
|
||||
string key = KeyPressed.Event.Key.ToString();
|
||||
string key = keyPressed.Event.Key.ToString();
|
||||
string capKey = key.First().ToString().ToUpper() + key.Substring(1);
|
||||
|
||||
if (Enum.IsDefined(typeof(OpenTK.Input.Key), capKey))
|
||||
{
|
||||
Button.Label = capKey;
|
||||
button.Label = capKey;
|
||||
}
|
||||
else if (GdkToOpenTKInput.ContainsKey(key))
|
||||
else if (GdkToOpenTkInput.ContainsKey(key))
|
||||
{
|
||||
Button.Label = GdkToOpenTKInput[key];
|
||||
button.Label = GdkToOpenTkInput[key];
|
||||
}
|
||||
else
|
||||
{
|
||||
Button.Label = "Space";
|
||||
button.Label = "Space";
|
||||
}
|
||||
|
||||
Button.SetStateFlags(0, true);
|
||||
button.SetStateFlags(0, true);
|
||||
|
||||
KeyPressEvent -= On_KeyPress;
|
||||
|
||||
|
@ -225,11 +230,30 @@ namespace Ryujinx.UI
|
|||
}
|
||||
else
|
||||
{
|
||||
Button.SetStateFlags(0, true);
|
||||
button.SetStateFlags(0, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddDir_Pressed(object obj, EventArgs args)
|
||||
private void Controller_Changed(object sender, EventArgs args, string controllerType, Image controllerImage)
|
||||
{
|
||||
switch (controllerType)
|
||||
{
|
||||
case "ProController":
|
||||
controllerImage.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.ProCon.png", 500, 500);
|
||||
break;
|
||||
case "NpadLeft":
|
||||
controllerImage.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.BlueCon.png", 500, 500);
|
||||
break;
|
||||
case "NpadRight":
|
||||
controllerImage.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.RedCon.png", 500, 500);
|
||||
break;
|
||||
default:
|
||||
controllerImage.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.JoyCon.png", 500, 500);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddDir_Pressed(object sender, EventArgs args)
|
||||
{
|
||||
if (Directory.Exists(_addGameDirBox.Buffer.Text))
|
||||
{
|
||||
|
@ -239,7 +263,7 @@ namespace Ryujinx.UI
|
|||
_addDir.SetStateFlags(0, true);
|
||||
}
|
||||
|
||||
private void BrowseDir_Pressed(object obj, EventArgs args)
|
||||
private void BrowseDir_Pressed(object sender, EventArgs args)
|
||||
{
|
||||
FileChooserDialog fileChooser = new FileChooserDialog("Choose the game directory to add to the list", this, FileChooserAction.SelectFolder, "Cancel", ResponseType.Cancel, "Add", ResponseType.Accept);
|
||||
|
||||
|
@ -248,12 +272,12 @@ namespace Ryujinx.UI
|
|||
_gameDirsBoxStore.AppendValues(fileChooser.Filename);
|
||||
}
|
||||
|
||||
fileChooser.Destroy();
|
||||
fileChooser.Dispose();
|
||||
|
||||
_browseDir.SetStateFlags(0, true);
|
||||
}
|
||||
|
||||
private void RemoveDir_Pressed(object obj, EventArgs args)
|
||||
private void RemoveDir_Pressed(object sender, EventArgs args)
|
||||
{
|
||||
TreeSelection selection = _gameDirsBox.Selection;
|
||||
|
||||
|
@ -263,14 +287,14 @@ namespace Ryujinx.UI
|
|||
_removeDir.SetStateFlags(0, true);
|
||||
}
|
||||
|
||||
private void CustThemeToggle_Activated(object obj, EventArgs args)
|
||||
private void CustThemeToggle_Activated(object sender, EventArgs args)
|
||||
{
|
||||
_custThemePath.Sensitive = _custThemeToggle.Active;
|
||||
_custThemePathLabel.Sensitive = _custThemeToggle.Active;
|
||||
_browseThemePath.Sensitive = _custThemeToggle.Active;
|
||||
}
|
||||
|
||||
private void BrowseThemeDir_Pressed(object obj, EventArgs args)
|
||||
private void BrowseThemeDir_Pressed(object sender, EventArgs args)
|
||||
{
|
||||
FileChooserDialog fileChooser = new FileChooserDialog("Choose the theme to load", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Select", ResponseType.Accept);
|
||||
|
||||
|
@ -282,12 +306,12 @@ namespace Ryujinx.UI
|
|||
_custThemePath.Buffer.Text = fileChooser.Filename;
|
||||
}
|
||||
|
||||
fileChooser.Destroy();
|
||||
fileChooser.Dispose();
|
||||
|
||||
_browseThemePath.SetStateFlags(0, true);
|
||||
}
|
||||
|
||||
private void SaveToggle_Activated(object obj, EventArgs args)
|
||||
private void SaveToggle_Activated(object sender, EventArgs args)
|
||||
{
|
||||
List<string> gameDirs = new List<string>();
|
||||
|
||||
|
@ -358,20 +382,21 @@ namespace Ryujinx.UI
|
|||
SwitchConfig.FsGlobalAccessLogMode = (int)_fsLogSpinAdjustment.Value;
|
||||
|
||||
Configuration.SaveConfig(SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
|
||||
Configuration.Configure(Device, SwitchConfig);
|
||||
Configuration.Configure(_device, SwitchConfig);
|
||||
|
||||
MainWindow.ApplyTheme();
|
||||
#pragma warning disable CS4014
|
||||
MainWindow.UpdateGameTable();
|
||||
|
||||
Destroy();
|
||||
#pragma warning restore CS4014
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void CloseToggle_Activated(object obj, EventArgs args)
|
||||
private void CloseToggle_Activated(object sender, EventArgs args)
|
||||
{
|
||||
Destroy();
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public readonly Dictionary<string, string> GdkToOpenTKInput = new Dictionary<string, string>()
|
||||
public readonly Dictionary<string, string> GdkToOpenTkInput = new Dictionary<string, string>()
|
||||
{
|
||||
{ "Key_0", "Number0" },
|
||||
{ "Key_1", "Number1" },
|
||||
|
|
BIN
Ryujinx/Ui/assets/BlueCon.png
Normal file
After Width: | Height: | Size: 161 KiB |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 282 KiB After Width: | Height: | Size: 324 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 15 KiB |
BIN
Ryujinx/Ui/assets/ProCon.png
Normal file
After Width: | Height: | Size: 317 KiB |
BIN
Ryujinx/Ui/assets/RedCon.png
Normal file
After Width: | Height: | Size: 174 KiB |
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
@ -484,17 +484,28 @@
|
|||
},
|
||||
"game_dirs": {
|
||||
"$id": "#/properties/game_dirs",
|
||||
"type": "string list",
|
||||
"type": "array",
|
||||
"title": "List of Game Directories",
|
||||
"description": "A list of directories containing games to be used to load games into the games list",
|
||||
"default": []
|
||||
},
|
||||
"gui_columns": {
|
||||
"$id": "#/properties/gui_columns",
|
||||
"type": "bool list",
|
||||
"type": "array",
|
||||
"title": "Used to toggle columns in the GUI",
|
||||
"description": "Used to toggle columns in the GUI",
|
||||
"default": [ true, true, true, true, true, true, true, true, true ]
|
||||
"default": {
|
||||
"fav_column": true,
|
||||
"icon_column": true,
|
||||
"app_column": true,
|
||||
"dev_column": true,
|
||||
"version_column": true,
|
||||
"time_played_column": true,
|
||||
"last_played_column": true,
|
||||
"file_ext_column": true,
|
||||
"file_size_column": true,
|
||||
"path_column": true
|
||||
}
|
||||
},
|
||||
"enable_custom_theme": {
|
||||
"$id": "#/properties/enable_custom_theme",
|
||||
|
|