Support HomeBrew Loader (#577)
* Make it possibles to load hb-loader and hb-menu One issue remains with hb-menu homebrew icons because of SIMD issues (libjpeg-turbo related) and netloader doesn't work. * Implement GetApplicationControlData * Fix shared fonts for NSO/NRO * Add homebrew NRO romfs support This readd the NRO support by parsing the ASET header * Address comments about HomebrewRomFs * override Dispose in homebrew romfs stream * Use a struct for file timestamp * Simplify positional increments in GetApplicationControlData * Address comments * improve readability of the memory permission check in SetProcessMemoryPermission * Fix previous broken check * Add address space checks in SetProcessMemoryPermission
This commit is contained in:
parent
7e9f555574
commit
b126ea48c6
17 changed files with 633 additions and 17 deletions
|
@ -1,5 +1,6 @@
|
||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.HLE.HOS;
|
||||||
using Ryujinx.HLE.HOS.Services.FspSrv;
|
using Ryujinx.HLE.HOS.Services.FspSrv;
|
||||||
|
using Ryujinx.HLE.Utilities;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -279,5 +280,34 @@ namespace Ryujinx.HLE.FileSystem
|
||||||
|
|
||||||
throw new InvalidOperationException($"Path {path} is not a child directory of {_rootPath}");
|
throw new InvalidOperationException($"Path {path} is not a child directory of {_rootPath}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public FileTimestamp GetFileTimeStampRaw(string name)
|
||||||
|
{
|
||||||
|
CheckIfDescendentOfRootPath(name);
|
||||||
|
|
||||||
|
DateTime creationDateTime = DateTime.UnixEpoch;
|
||||||
|
DateTime modifiedDateTime = DateTime.UnixEpoch;
|
||||||
|
DateTime lastAccessDateTime = DateTime.UnixEpoch;
|
||||||
|
|
||||||
|
if (File.Exists(name))
|
||||||
|
{
|
||||||
|
creationDateTime = File.GetCreationTime(name);
|
||||||
|
modifiedDateTime = File.GetLastWriteTime(name);
|
||||||
|
lastAccessDateTime = File.GetLastAccessTime(name);
|
||||||
|
}
|
||||||
|
else if (Directory.Exists(name))
|
||||||
|
{
|
||||||
|
creationDateTime = Directory.GetCreationTime(name);
|
||||||
|
modifiedDateTime = Directory.GetLastWriteTime(name);
|
||||||
|
lastAccessDateTime = Directory.GetLastAccessTime(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FileTimestamp
|
||||||
|
{
|
||||||
|
CreationDateTime = creationDateTime,
|
||||||
|
ModifiedDateTime = modifiedDateTime,
|
||||||
|
LastAccessDateTime = lastAccessDateTime
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.HLE.HOS;
|
||||||
using Ryujinx.HLE.HOS.Services.FspSrv;
|
using Ryujinx.HLE.HOS.Services.FspSrv;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.FileSystem
|
namespace Ryujinx.HLE.FileSystem
|
||||||
{
|
{
|
||||||
|
@ -36,5 +37,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||||
long GetFreeSpace(ServiceCtx context);
|
long GetFreeSpace(ServiceCtx context);
|
||||||
|
|
||||||
long GetTotalSpace(ServiceCtx context);
|
long GetTotalSpace(ServiceCtx context);
|
||||||
|
|
||||||
|
FileTimestamp GetFileTimeStampRaw(string name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,5 +143,10 @@ namespace Ryujinx.HLE.FileSystem
|
||||||
{
|
{
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public FileTimestamp GetFileTimeStampRaw(string name)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,5 +160,10 @@ namespace Ryujinx.HLE.FileSystem
|
||||||
{
|
{
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public FileTimestamp GetFileTimeStampRaw(string name)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
77
Ryujinx.HLE/HOS/HomebrewRomFsStream.cs
Normal file
77
Ryujinx.HLE/HOS/HomebrewRomFsStream.cs
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS
|
||||||
|
{
|
||||||
|
class HomebrewRomFsStream : Stream
|
||||||
|
{
|
||||||
|
private Stream _baseStream;
|
||||||
|
private long _positionOffset;
|
||||||
|
|
||||||
|
public HomebrewRomFsStream(Stream baseStream, long positionOffset)
|
||||||
|
{
|
||||||
|
_baseStream = baseStream;
|
||||||
|
_positionOffset = positionOffset;
|
||||||
|
|
||||||
|
_baseStream.Position = _positionOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanRead => _baseStream.CanRead;
|
||||||
|
|
||||||
|
public override bool CanSeek => _baseStream.CanSeek;
|
||||||
|
|
||||||
|
public override bool CanWrite => false;
|
||||||
|
|
||||||
|
public override long Length => _baseStream.Length - _positionOffset;
|
||||||
|
|
||||||
|
public override long Position
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _baseStream.Position - _positionOffset;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_baseStream.Position = value + _positionOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Flush()
|
||||||
|
{
|
||||||
|
_baseStream.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
return _baseStream.Read(buffer, offset, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
|
{
|
||||||
|
if (origin == SeekOrigin.Begin)
|
||||||
|
{
|
||||||
|
offset += _positionOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _baseStream.Seek(offset, origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetLength(long value)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_baseStream.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -102,6 +102,8 @@ namespace Ryujinx.HLE.HOS
|
||||||
|
|
||||||
public Horizon(Switch device)
|
public Horizon(Switch device)
|
||||||
{
|
{
|
||||||
|
ControlData = new Nacp();
|
||||||
|
|
||||||
Device = device;
|
Device = device;
|
||||||
|
|
||||||
State = new SystemStateMgr();
|
State = new SystemStateMgr();
|
||||||
|
@ -549,14 +551,58 @@ namespace Ryujinx.HLE.HOS
|
||||||
|
|
||||||
bool isNro = Path.GetExtension(filePath).ToLower() == ".nro";
|
bool isNro = Path.GetExtension(filePath).ToLower() == ".nro";
|
||||||
|
|
||||||
using (FileStream input = new FileStream(filePath, FileMode.Open))
|
FileStream input = new FileStream(filePath, FileMode.Open);
|
||||||
{
|
|
||||||
IExecutable staticObject = isNro
|
|
||||||
? (IExecutable)new NxRelocatableObject(input)
|
|
||||||
: new NxStaticObject(input);
|
|
||||||
|
|
||||||
ProgramLoader.LoadStaticObjects(this, metaData, new IExecutable[] { staticObject });
|
IExecutable staticObject;
|
||||||
|
|
||||||
|
if (isNro)
|
||||||
|
{
|
||||||
|
NxRelocatableObject obj = new NxRelocatableObject(input);
|
||||||
|
staticObject = obj;
|
||||||
|
|
||||||
|
// homebrew NRO can actually have some data after the actual NRO
|
||||||
|
if (input.Length > obj.FileSize)
|
||||||
|
{
|
||||||
|
input.Position = obj.FileSize;
|
||||||
|
|
||||||
|
BinaryReader reader = new BinaryReader(input);
|
||||||
|
|
||||||
|
uint asetMagic = reader.ReadUInt32();
|
||||||
|
|
||||||
|
if (asetMagic == 0x54455341)
|
||||||
|
{
|
||||||
|
uint asetVersion = reader.ReadUInt32();
|
||||||
|
if (asetVersion == 0)
|
||||||
|
{
|
||||||
|
ulong iconOffset = reader.ReadUInt64();
|
||||||
|
ulong iconSize = reader.ReadUInt64();
|
||||||
|
|
||||||
|
ulong nacpOffset = reader.ReadUInt64();
|
||||||
|
ulong nacpSize = reader.ReadUInt64();
|
||||||
|
|
||||||
|
ulong romfsOffset = reader.ReadUInt64();
|
||||||
|
ulong romfsSize = reader.ReadUInt64();
|
||||||
|
|
||||||
|
if (romfsSize != 0)
|
||||||
|
{
|
||||||
|
Device.FileSystem.SetRomFs(new HomebrewRomFsStream(input, obj.FileSize + (long)romfsOffset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.PrintWarning(LogClass.Loader, $"Unsupported ASET header version found \"{asetVersion}\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
staticObject = new NxStaticObject(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentManager.LoadEntries();
|
||||||
|
|
||||||
|
ProgramLoader.LoadStaticObjects(this, metaData, new IExecutable[] { staticObject });
|
||||||
}
|
}
|
||||||
|
|
||||||
private Npdm GetDefaultNpdm()
|
private Npdm GetDefaultNpdm()
|
||||||
|
|
|
@ -386,6 +386,132 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||||
return _process.MemoryManager.UnmapPhysicalMemory(address, size);
|
return _process.MemoryManager.UnmapPhysicalMemory(address, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public KernelResult MapProcessCodeMemory64(int handle, ulong dst, ulong src, ulong size)
|
||||||
|
{
|
||||||
|
return MapProcessCodeMemory(handle, dst, src, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KernelResult MapProcessCodeMemory(int handle, ulong dst, ulong src, ulong size)
|
||||||
|
{
|
||||||
|
if (!PageAligned(dst) || !PageAligned(src))
|
||||||
|
{
|
||||||
|
return KernelResult.InvalidAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PageAligned(size) || size == 0)
|
||||||
|
{
|
||||||
|
return KernelResult.InvalidSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
KProcess currentProcess = _system.Scheduler.GetCurrentProcess();
|
||||||
|
|
||||||
|
KProcess targetProcess = currentProcess.HandleTable.GetObject<KProcess>(handle);
|
||||||
|
|
||||||
|
if (targetProcess == null)
|
||||||
|
{
|
||||||
|
return KernelResult.InvalidHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetProcess.MemoryManager.OutsideAddrSpace(dst, size) ||
|
||||||
|
targetProcess.MemoryManager.OutsideAddrSpace(src, size) ||
|
||||||
|
targetProcess.MemoryManager.InsideAliasRegion(dst, size) ||
|
||||||
|
targetProcess.MemoryManager.InsideHeapRegion(dst, size))
|
||||||
|
{
|
||||||
|
return KernelResult.InvalidMemRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size + dst <= dst || size + src <= src)
|
||||||
|
{
|
||||||
|
return KernelResult.InvalidMemState;
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetProcess.MemoryManager.MapProcessCodeMemory(dst, src, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KernelResult UnmapProcessCodeMemory64(int handle, ulong dst, ulong src, ulong size)
|
||||||
|
{
|
||||||
|
return UnmapProcessCodeMemory(handle, dst, src, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KernelResult UnmapProcessCodeMemory(int handle, ulong dst, ulong src, ulong size)
|
||||||
|
{
|
||||||
|
if (!PageAligned(dst) || !PageAligned(src))
|
||||||
|
{
|
||||||
|
return KernelResult.InvalidAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PageAligned(size) || size == 0)
|
||||||
|
{
|
||||||
|
return KernelResult.InvalidSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
KProcess currentProcess = _system.Scheduler.GetCurrentProcess();
|
||||||
|
|
||||||
|
KProcess targetProcess = currentProcess.HandleTable.GetObject<KProcess>(handle);
|
||||||
|
|
||||||
|
if (targetProcess == null)
|
||||||
|
{
|
||||||
|
return KernelResult.InvalidHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetProcess.MemoryManager.OutsideAddrSpace(dst, size) ||
|
||||||
|
targetProcess.MemoryManager.OutsideAddrSpace(src, size) ||
|
||||||
|
targetProcess.MemoryManager.InsideAliasRegion(dst, size) ||
|
||||||
|
targetProcess.MemoryManager.InsideHeapRegion(dst, size))
|
||||||
|
{
|
||||||
|
return KernelResult.InvalidMemRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size + dst <= dst || size + src <= src)
|
||||||
|
{
|
||||||
|
return KernelResult.InvalidMemState;
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetProcess.MemoryManager.UnmapProcessCodeMemory(dst, src, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KernelResult SetProcessMemoryPermission64(int handle, ulong src, ulong size, MemoryPermission permission)
|
||||||
|
{
|
||||||
|
return SetProcessMemoryPermission(handle, src, size, permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KernelResult SetProcessMemoryPermission(int handle, ulong src, ulong size, MemoryPermission permission)
|
||||||
|
{
|
||||||
|
if (!PageAligned(src))
|
||||||
|
{
|
||||||
|
return KernelResult.InvalidAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PageAligned(size) || size == 0)
|
||||||
|
{
|
||||||
|
return KernelResult.InvalidSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (permission != MemoryPermission.None &&
|
||||||
|
permission != MemoryPermission.Read &&
|
||||||
|
permission != MemoryPermission.ReadAndWrite &&
|
||||||
|
permission != MemoryPermission.ReadAndExecute)
|
||||||
|
{
|
||||||
|
return KernelResult.InvalidPermission;
|
||||||
|
}
|
||||||
|
|
||||||
|
KProcess currentProcess = _system.Scheduler.GetCurrentProcess();
|
||||||
|
|
||||||
|
KProcess targetProcess = currentProcess.HandleTable.GetObject<KProcess>(handle);
|
||||||
|
|
||||||
|
if (targetProcess == null)
|
||||||
|
{
|
||||||
|
return KernelResult.InvalidHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetProcess.MemoryManager.OutsideAddrSpace(src, size))
|
||||||
|
{
|
||||||
|
return KernelResult.InvalidMemState;
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetProcess.MemoryManager.SetProcessMemoryPermission(src, size, permission);
|
||||||
|
}
|
||||||
|
|
||||||
private static bool PageAligned(ulong position)
|
private static bool PageAligned(ulong position)
|
||||||
{
|
{
|
||||||
return (position & (KMemoryManager.PageSize - 1)) == 0;
|
return (position & (KMemoryManager.PageSize - 1)) == 0;
|
||||||
|
|
|
@ -71,7 +71,10 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||||
{ 0x6f, nameof(SvcHandler.GetSystemInfo64) },
|
{ 0x6f, nameof(SvcHandler.GetSystemInfo64) },
|
||||||
{ 0x70, nameof(SvcHandler.CreatePort64) },
|
{ 0x70, nameof(SvcHandler.CreatePort64) },
|
||||||
{ 0x71, nameof(SvcHandler.ManageNamedPort64) },
|
{ 0x71, nameof(SvcHandler.ManageNamedPort64) },
|
||||||
{ 0x72, nameof(SvcHandler.ConnectToPort64) }
|
{ 0x72, nameof(SvcHandler.ConnectToPort64) },
|
||||||
|
{ 0x73, nameof(SvcHandler.SetProcessMemoryPermission64) },
|
||||||
|
{ 0x77, nameof(SvcHandler.MapProcessCodeMemory64) },
|
||||||
|
{ 0x78, nameof(SvcHandler.UnmapProcessCodeMemory64) }
|
||||||
};
|
};
|
||||||
|
|
||||||
_svcTable64 = new Action<SvcHandler, CpuThreadState>[0x80];
|
_svcTable64 = new Action<SvcHandler, CpuThreadState>[0x80];
|
||||||
|
|
11
Ryujinx.HLE/HOS/Services/FspSrv/FileTimestamp.cs
Normal file
11
Ryujinx.HLE/HOS/Services/FspSrv/FileTimestamp.cs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
|
{
|
||||||
|
struct FileTimestamp
|
||||||
|
{
|
||||||
|
public DateTime CreationDateTime;
|
||||||
|
public DateTime ModifiedDateTime;
|
||||||
|
public DateTime LastAccessDateTime;
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,8 +38,8 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
{ 10, Commit },
|
{ 10, Commit },
|
||||||
{ 11, GetFreeSpaceSize },
|
{ 11, GetFreeSpaceSize },
|
||||||
{ 12, GetTotalSpaceSize },
|
{ 12, GetTotalSpaceSize },
|
||||||
{ 13, CleanDirectoryRecursively }
|
{ 13, CleanDirectoryRecursively },
|
||||||
//{ 14, GetFileTimeStampRaw }
|
{ 14, GetFileTimeStampRaw }
|
||||||
};
|
};
|
||||||
|
|
||||||
_openPaths = new HashSet<string>();
|
_openPaths = new HashSet<string>();
|
||||||
|
@ -368,6 +368,34 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetFileTimeStampRaw(buffer<bytes<0x301>, 0x19, 0x301> path) -> bytes<0x20> timestamp
|
||||||
|
public long GetFileTimeStampRaw(ServiceCtx context)
|
||||||
|
{
|
||||||
|
string name = ReadUtf8String(context);
|
||||||
|
|
||||||
|
string path = _provider.GetFullPath(name);
|
||||||
|
|
||||||
|
if (_provider.FileExists(path) || _provider.DirectoryExists(path))
|
||||||
|
{
|
||||||
|
FileTimestamp timestamp = _provider.GetFileTimeStampRaw(path);
|
||||||
|
|
||||||
|
context.ResponseData.Write(new DateTimeOffset(timestamp.CreationDateTime).ToUnixTimeSeconds());
|
||||||
|
context.ResponseData.Write(new DateTimeOffset(timestamp.ModifiedDateTime).ToUnixTimeSeconds());
|
||||||
|
context.ResponseData.Write(new DateTimeOffset(timestamp.LastAccessDateTime).ToUnixTimeSeconds());
|
||||||
|
|
||||||
|
byte[] data = new byte[8];
|
||||||
|
|
||||||
|
// is valid?
|
||||||
|
data[0] = 1;
|
||||||
|
|
||||||
|
context.ResponseData.Write(data);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
|
||||||
|
}
|
||||||
|
|
||||||
private bool IsPathAlreadyInUse(string path)
|
private bool IsPathAlreadyInUse(string path)
|
||||||
{
|
{
|
||||||
lock (_openPaths)
|
lock (_openPaths)
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
using Ryujinx.HLE.HOS.Ipc;
|
using LibHac;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.HLE.HOS.Ipc;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Ns
|
namespace Ryujinx.HLE.HOS.Services.Ns
|
||||||
{
|
{
|
||||||
|
@ -9,14 +13,211 @@ namespace Ryujinx.HLE.HOS.Services.Ns
|
||||||
|
|
||||||
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => _commands;
|
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => _commands;
|
||||||
|
|
||||||
private bool _isInitialized;
|
|
||||||
|
|
||||||
public IApplicationManagerInterface()
|
public IApplicationManagerInterface()
|
||||||
{
|
{
|
||||||
_commands = new Dictionary<int, ServiceProcessRequest>
|
_commands = new Dictionary<int, ServiceProcessRequest>
|
||||||
{
|
{
|
||||||
|
{ 400, GetApplicationControlData }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long GetApplicationControlData(ServiceCtx context)
|
||||||
|
{
|
||||||
|
long position = context.Request.ReceiveBuff[0].Position;
|
||||||
|
|
||||||
|
Nacp nacp = context.Device.System.ControlData;
|
||||||
|
|
||||||
|
for (int i = 0; i < 0x10; i++)
|
||||||
|
{
|
||||||
|
NacpDescription description = nacp.Descriptions[i];
|
||||||
|
|
||||||
|
byte[] titleData = new byte[0x200];
|
||||||
|
byte[] developerData = new byte[0x100];
|
||||||
|
|
||||||
|
if (description !=null && description.Title != null)
|
||||||
|
{
|
||||||
|
byte[] titleDescriptionData = Encoding.ASCII.GetBytes(description.Title);
|
||||||
|
Buffer.BlockCopy(titleDescriptionData, 0, titleData, 0, titleDescriptionData.Length);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (description != null && description.Developer != null)
|
||||||
|
{
|
||||||
|
byte[] developerDescriptionData = Encoding.ASCII.GetBytes(description.Developer);
|
||||||
|
Buffer.BlockCopy(developerDescriptionData, 0, developerData, 0, developerDescriptionData.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Memory.WriteBytes(position, titleData);
|
||||||
|
context.Memory.WriteBytes(position + 0x200, developerData);
|
||||||
|
|
||||||
|
position += i * 0x300;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] isbn = new byte[0x25];
|
||||||
|
|
||||||
|
if (nacp.Isbn != null)
|
||||||
|
{
|
||||||
|
byte[] isbnData = Encoding.ASCII.GetBytes(nacp.Isbn);
|
||||||
|
Buffer.BlockCopy(isbnData, 0, isbn, 0, isbnData.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Memory.WriteBytes(position, isbn);
|
||||||
|
position += isbn.Length;
|
||||||
|
|
||||||
|
context.Memory.WriteByte(position++, nacp.StartupUserAccount);
|
||||||
|
context.Memory.WriteByte(position++, nacp.TouchScreenUsageMode);
|
||||||
|
context.Memory.WriteByte(position++, nacp.AocRegistrationType);
|
||||||
|
|
||||||
|
context.Memory.WriteInt32(position, nacp.AttributeFlag);
|
||||||
|
position += 4;
|
||||||
|
|
||||||
|
context.Memory.WriteUInt32(position, nacp.SupportedLanguageFlag);
|
||||||
|
position += 4;
|
||||||
|
|
||||||
|
context.Memory.WriteUInt32(position, nacp.ParentalControlFlag);
|
||||||
|
position += 4;
|
||||||
|
|
||||||
|
context.Memory.WriteByte(position++, nacp.Screenshot);
|
||||||
|
context.Memory.WriteByte(position++, nacp.VideoCapture);
|
||||||
|
context.Memory.WriteByte(position++, nacp.DataLossConfirmation);
|
||||||
|
context.Memory.WriteByte(position++, nacp.PlayLogPolicy);
|
||||||
|
|
||||||
|
context.Memory.WriteUInt64(position, nacp.PresenceGroupId);
|
||||||
|
position += 8;
|
||||||
|
|
||||||
|
for (int i = 0; i < nacp.RatingAge.Length; i++)
|
||||||
|
{
|
||||||
|
context.Memory.WriteSByte(position++, nacp.RatingAge[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] displayVersion = new byte[0x10];
|
||||||
|
|
||||||
|
if (nacp.DisplayVersion != null)
|
||||||
|
{
|
||||||
|
byte[] displayVersionData = Encoding.ASCII.GetBytes(nacp.DisplayVersion);
|
||||||
|
Buffer.BlockCopy(displayVersionData, 0, displayVersion, 0, displayVersionData.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Memory.WriteBytes(position, displayVersion);
|
||||||
|
position += displayVersion.Length;
|
||||||
|
|
||||||
|
context.Memory.WriteUInt64(position, nacp.AddOnContentBaseId);
|
||||||
|
position += 8;
|
||||||
|
|
||||||
|
context.Memory.WriteUInt64(position, nacp.SaveDataOwnerId);
|
||||||
|
position += 8;
|
||||||
|
|
||||||
|
context.Memory.WriteInt64(position, nacp.UserAccountSaveDataSize);
|
||||||
|
position += 8;
|
||||||
|
|
||||||
|
context.Memory.WriteInt64(position, nacp.UserAccountSaveDataJournalSize);
|
||||||
|
position += 8;
|
||||||
|
|
||||||
|
context.Memory.WriteInt64(position, nacp.DeviceSaveDataSize);
|
||||||
|
position += 8;
|
||||||
|
|
||||||
|
context.Memory.WriteInt64(position, nacp.DeviceSaveDataJournalSize);
|
||||||
|
position += 8;
|
||||||
|
|
||||||
|
context.Memory.WriteInt64(position, nacp.BcatDeliveryCacheStorageSize);
|
||||||
|
position += 8;
|
||||||
|
|
||||||
|
byte[] applicationErrorCodeCategory = new byte[0x8];
|
||||||
|
|
||||||
|
if (nacp.ApplicationErrorCodeCategory != null)
|
||||||
|
{
|
||||||
|
byte[] applicationErrorCodeCategoryData = Encoding.ASCII.GetBytes(nacp.ApplicationErrorCodeCategory);
|
||||||
|
Buffer.BlockCopy(applicationErrorCodeCategoryData, 0, applicationErrorCodeCategoryData, 0, applicationErrorCodeCategoryData.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Memory.WriteBytes(position, applicationErrorCodeCategory);
|
||||||
|
position += applicationErrorCodeCategory.Length;
|
||||||
|
|
||||||
|
for (int i = 0; i < nacp.LocalCommunicationId.Length; i++)
|
||||||
|
{
|
||||||
|
context.Memory.WriteUInt64(position, nacp.LocalCommunicationId[i]);
|
||||||
|
position += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Memory.WriteByte(position++, nacp.LogoType);
|
||||||
|
context.Memory.WriteByte(position++, nacp.LogoHandling);
|
||||||
|
context.Memory.WriteByte(position++, nacp.RuntimeAddOnContentInstall);
|
||||||
|
|
||||||
|
byte[] reserved000 = new byte[0x3];
|
||||||
|
context.Memory.WriteBytes(position, reserved000);
|
||||||
|
position += reserved000.Length;
|
||||||
|
|
||||||
|
context.Memory.WriteByte(position++, nacp.CrashReport);
|
||||||
|
context.Memory.WriteByte(position++, nacp.Hdcp);
|
||||||
|
context.Memory.WriteUInt64(position, nacp.SeedForPseudoDeviceId);
|
||||||
|
position += 8;
|
||||||
|
|
||||||
|
byte[] bcatPassphrase = new byte[65];
|
||||||
|
if (nacp.BcatPassphrase != null)
|
||||||
|
{
|
||||||
|
byte[] bcatPassphraseData = Encoding.ASCII.GetBytes(nacp.BcatPassphrase);
|
||||||
|
Buffer.BlockCopy(bcatPassphraseData, 0, bcatPassphrase, 0, bcatPassphraseData.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Memory.WriteBytes(position, bcatPassphrase);
|
||||||
|
position += bcatPassphrase.Length;
|
||||||
|
|
||||||
|
context.Memory.WriteByte(position++, nacp.Reserved01);
|
||||||
|
|
||||||
|
byte[] reserved02 = new byte[0x6];
|
||||||
|
context.Memory.WriteBytes(position, reserved02);
|
||||||
|
position += reserved02.Length;
|
||||||
|
|
||||||
|
context.Memory.WriteInt64(position, nacp.UserAccountSaveDataSizeMax);
|
||||||
|
position += 8;
|
||||||
|
|
||||||
|
context.Memory.WriteInt64(position, nacp.UserAccountSaveDataJournalSizeMax);
|
||||||
|
position += 8;
|
||||||
|
|
||||||
|
context.Memory.WriteInt64(position, nacp.DeviceSaveDataSizeMax);
|
||||||
|
position += 8;
|
||||||
|
|
||||||
|
context.Memory.WriteInt64(position, nacp.DeviceSaveDataJournalSizeMax);
|
||||||
|
position += 8;
|
||||||
|
|
||||||
|
context.Memory.WriteInt64(position, nacp.TemporaryStorageSize);
|
||||||
|
position += 8;
|
||||||
|
|
||||||
|
context.Memory.WriteInt64(position, nacp.CacheStorageSize);
|
||||||
|
position += 8;
|
||||||
|
|
||||||
|
context.Memory.WriteInt64(position, nacp.CacheStorageJournalSize);
|
||||||
|
position += 8;
|
||||||
|
|
||||||
|
context.Memory.WriteInt64(position, nacp.CacheStorageDataAndJournalSizeMax);
|
||||||
|
position += 8;
|
||||||
|
|
||||||
|
context.Memory.WriteInt16(position, nacp.CacheStorageIndex);
|
||||||
|
position += 2;
|
||||||
|
|
||||||
|
byte[] reserved03 = new byte[0x6];
|
||||||
|
context.Memory.WriteBytes(position, reserved03);
|
||||||
|
position += reserved03.Length;
|
||||||
|
|
||||||
|
|
||||||
|
for (int i = 0; i < 16; i++)
|
||||||
|
{
|
||||||
|
ulong value = 0;
|
||||||
|
|
||||||
|
if (nacp.PlayLogQueryableApplicationId.Count > i)
|
||||||
|
{
|
||||||
|
value = nacp.PlayLogQueryableApplicationId[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Memory.WriteUInt64(position, value);
|
||||||
|
position += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Memory.WriteByte(position++, nacp.PlayLogQueryCapability);
|
||||||
|
context.Memory.WriteByte(position++, nacp.RepairFlag);
|
||||||
|
context.Memory.WriteByte(position++, nacp.ProgramIndex);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,15 @@ namespace Ryujinx.HLE.HOS.Services.Ns
|
||||||
{
|
{
|
||||||
_commands = new Dictionary<int, ServiceProcessRequest>
|
_commands = new Dictionary<int, ServiceProcessRequest>
|
||||||
{
|
{
|
||||||
//...
|
{ 7996, GetApplicationManagerInterface }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long GetApplicationManagerInterface(ServiceCtx context)
|
||||||
|
{
|
||||||
|
MakeObject(context, new IApplicationManagerInterface());
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
34
Ryujinx.HLE/HOS/Services/Pm/IShellInterface.cs
Normal file
34
Ryujinx.HLE/HOS/Services/Pm/IShellInterface.cs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
using Ryujinx.HLE.HOS.Ipc;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Pm
|
||||||
|
{
|
||||||
|
class IShellInterface : IpcService
|
||||||
|
{
|
||||||
|
private Dictionary<int, ServiceProcessRequest> _commands;
|
||||||
|
|
||||||
|
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => _commands;
|
||||||
|
|
||||||
|
public IShellInterface()
|
||||||
|
{
|
||||||
|
_commands = new Dictionary<int, ServiceProcessRequest>
|
||||||
|
{
|
||||||
|
{ 6, GetApplicationPid }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetApplicationPid() -> u64
|
||||||
|
public long GetApplicationPid(ServiceCtx context)
|
||||||
|
{
|
||||||
|
// FIXME: This is wrong but needed to make hb loader works
|
||||||
|
// TODO: Change this when we will have a way to process via a PM like interface.
|
||||||
|
long pid = context.Process.Pid;
|
||||||
|
|
||||||
|
context.ResponseData.Write(pid);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ using Ryujinx.HLE.HOS.Services.Ns;
|
||||||
using Ryujinx.HLE.HOS.Services.Nv;
|
using Ryujinx.HLE.HOS.Services.Nv;
|
||||||
using Ryujinx.HLE.HOS.Services.Pctl;
|
using Ryujinx.HLE.HOS.Services.Pctl;
|
||||||
using Ryujinx.HLE.HOS.Services.Pl;
|
using Ryujinx.HLE.HOS.Services.Pl;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Pm;
|
||||||
using Ryujinx.HLE.HOS.Services.Prepo;
|
using Ryujinx.HLE.HOS.Services.Prepo;
|
||||||
using Ryujinx.HLE.HOS.Services.Psm;
|
using Ryujinx.HLE.HOS.Services.Psm;
|
||||||
using Ryujinx.HLE.HOS.Services.Set;
|
using Ryujinx.HLE.HOS.Services.Set;
|
||||||
|
@ -131,6 +132,7 @@ namespace Ryujinx.HLE.HOS.Services
|
||||||
case "ns:am":
|
case "ns:am":
|
||||||
return new IApplicationManagerInterface();
|
return new IApplicationManagerInterface();
|
||||||
|
|
||||||
|
case "ns:am2":
|
||||||
case "ns:ec":
|
case "ns:ec":
|
||||||
return new IServiceGetterInterface();
|
return new IServiceGetterInterface();
|
||||||
|
|
||||||
|
@ -161,6 +163,9 @@ namespace Ryujinx.HLE.HOS.Services
|
||||||
case "pl:u":
|
case "pl:u":
|
||||||
return new ISharedFontManager();
|
return new ISharedFontManager();
|
||||||
|
|
||||||
|
case "pm:shell":
|
||||||
|
return new IShellInterface();
|
||||||
|
|
||||||
case "prepo:a":
|
case "prepo:a":
|
||||||
return new IPrepoService();
|
return new IPrepoService();
|
||||||
|
|
||||||
|
|
|
@ -23,9 +23,10 @@ namespace Ryujinx.HLE.HOS.Services.Sm
|
||||||
{
|
{
|
||||||
_commands = new Dictionary<int, ServiceProcessRequest>
|
_commands = new Dictionary<int, ServiceProcessRequest>
|
||||||
{
|
{
|
||||||
{ 0, Initialize },
|
{ 0, Initialize },
|
||||||
{ 1, GetService },
|
{ 1, GetService },
|
||||||
{ 2, RegisterService }
|
{ 2, RegisterService },
|
||||||
|
{ 3, UnregisterService }
|
||||||
};
|
};
|
||||||
|
|
||||||
_registeredServices = new ConcurrentDictionary<string, KPort>();
|
_registeredServices = new ConcurrentDictionary<string, KPort>();
|
||||||
|
@ -128,6 +129,36 @@ namespace Ryujinx.HLE.HOS.Services.Sm
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long UnregisterService(ServiceCtx context)
|
||||||
|
{
|
||||||
|
if (!_isInitialized)
|
||||||
|
{
|
||||||
|
return ErrorCode.MakeError(ErrorModule.Sm, SmErr.NotInitialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
long namePosition = context.RequestData.BaseStream.Position;
|
||||||
|
|
||||||
|
string name = ReadName(context);
|
||||||
|
|
||||||
|
context.RequestData.BaseStream.Seek(namePosition + 8, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
bool isLight = (context.RequestData.ReadInt32() & 1) != 0;
|
||||||
|
|
||||||
|
int maxSessions = context.RequestData.ReadInt32();
|
||||||
|
|
||||||
|
if (name == string.Empty)
|
||||||
|
{
|
||||||
|
return ErrorCode.MakeError(ErrorModule.Sm, SmErr.InvalidName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_registeredServices.TryRemove(name, out _))
|
||||||
|
{
|
||||||
|
return ErrorCode.MakeError(ErrorModule.Sm, SmErr.NotRegistered);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
private static string ReadName(ServiceCtx context)
|
private static string ReadName(ServiceCtx context)
|
||||||
{
|
{
|
||||||
string name = string.Empty;
|
string name = string.Empty;
|
||||||
|
|
|
@ -5,5 +5,6 @@ namespace Ryujinx.HLE.HOS.Services.Sm
|
||||||
public const int NotInitialized = 2;
|
public const int NotInitialized = 2;
|
||||||
public const int AlreadyRegistered = 4;
|
public const int AlreadyRegistered = 4;
|
||||||
public const int InvalidName = 6;
|
public const int InvalidName = 6;
|
||||||
|
public const int NotRegistered = 7;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -13,6 +13,7 @@ namespace Ryujinx.HLE.Loaders.Executables
|
||||||
public int RoOffset { get; private set; }
|
public int RoOffset { get; private set; }
|
||||||
public int DataOffset { get; private set; }
|
public int DataOffset { get; private set; }
|
||||||
public int BssSize { get; private set; }
|
public int BssSize { get; private set; }
|
||||||
|
public int FileSize { get; private set; }
|
||||||
|
|
||||||
public int BssOffset => DataOffset + Data.Length;
|
public int BssOffset => DataOffset + Data.Length;
|
||||||
|
|
||||||
|
@ -59,6 +60,8 @@ namespace Ryujinx.HLE.Loaders.Executables
|
||||||
Text = Read(textOffset, textSize);
|
Text = Read(textOffset, textSize);
|
||||||
Ro = Read(roOffset, roSize);
|
Ro = Read(roOffset, roSize);
|
||||||
Data = Read(dataOffset, dataSize);
|
Data = Read(dataOffset, dataSize);
|
||||||
|
|
||||||
|
FileSize = fileSize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Reference in a new issue