Implement NPDM files parser (#169)

* Implement NPDM files parser

(Currently not used in Ryujinx)

* Add credits

* Add Swap32

* Update Npdm.cs

* Update ACI0.cs

* Update ACID.cs

* Update Npdm.cs

* Update EndianSwap.cs

* Update ACI0.cs

* Update ACID.cs

* Update KernelAccessControl.cs

* Update NpdmInfo.cs

* Update ServiceAccessControl.cs

* Update NpdmInfo.cs
This commit is contained in:
Ac_K 2018-06-18 02:28:11 +00:00 committed by gdkchan
parent 3c4dafff3f
commit 4e430760b4
10 changed files with 753 additions and 0 deletions

View file

@ -0,0 +1,56 @@
using Ryujinx.HLE.OsHle.Utilities;
using System;
using System.IO;
namespace Ryujinx.HLE.Loaders.Npdm
{
class ACI0
{
public string TitleId;
private int FSAccessHeaderOffset;
private int FSAccessHeaderSize;
private int ServiceAccessControlOffset;
private int ServiceAccessControlSize;
private int KernelAccessControlOffset;
private int KernelAccessControlSize;
public FSAccessHeader FSAccessHeader;
public ServiceAccessControl ServiceAccessControl;
public KernelAccessControl KernelAccessControl;
public const long ACI0Magic = 'A' << 0 | 'C' << 8 | 'I' << 16 | '0' << 24;
public ACI0(Stream ACI0Stream, int Offset)
{
ACI0Stream.Seek(Offset, SeekOrigin.Begin);
BinaryReader Reader = new BinaryReader(ACI0Stream);
if (Reader.ReadInt32() != ACI0Magic)
{
throw new InvalidNpdmException("ACI0 Stream doesn't contain ACI0 section!");
}
ACI0Stream.Seek(0x0C, SeekOrigin.Current);
byte[] TempTitleId = Reader.ReadBytes(8);
Array.Reverse(TempTitleId);
TitleId = BitConverter.ToString(TempTitleId).Replace("-", "");
// Reserved (Not currently used, potentially to be used for lowest title ID in future.)
ACI0Stream.Seek(0x08, SeekOrigin.Current);
FSAccessHeaderOffset = Reader.ReadInt32();
FSAccessHeaderSize = Reader.ReadInt32();
ServiceAccessControlOffset = Reader.ReadInt32();
ServiceAccessControlSize = Reader.ReadInt32();
KernelAccessControlOffset = Reader.ReadInt32();
KernelAccessControlSize = Reader.ReadInt32();
FSAccessHeader = new FSAccessHeader(ACI0Stream, Offset + FSAccessHeaderOffset, FSAccessHeaderSize);
ServiceAccessControl = new ServiceAccessControl(ACI0Stream, Offset + ServiceAccessControlOffset, ServiceAccessControlSize);
KernelAccessControl = new KernelAccessControl(ACI0Stream, Offset + KernelAccessControlOffset, KernelAccessControlSize);
}
}
}

View file

@ -0,0 +1,68 @@
using Ryujinx.HLE.OsHle.Utilities;
using System;
using System.IO;
namespace Ryujinx.HLE.Loaders.Npdm
{
class ACID
{
public byte[] RSA2048Signature;
public byte[] RSA2048Modulus;
public int Unknown1;
public int Flags;
public string TitleIdRangeMin;
public string TitleIdRangeMax;
private int FSAccessControlOffset;
private int FSAccessControlSize;
private int ServiceAccessControlOffset;
private int ServiceAccessControlSize;
private int KernelAccessControlOffset;
private int KernelAccessControlSize;
public FSAccessControl FSAccessControl;
public ServiceAccessControl ServiceAccessControl;
public KernelAccessControl KernelAccessControl;
public const long ACIDMagic = 'A' << 0 | 'C' << 8 | 'I' << 16 | 'D' << 24;
public ACID(Stream ACIDStream, int Offset)
{
ACIDStream.Seek(Offset, SeekOrigin.Begin);
BinaryReader Reader = new BinaryReader(ACIDStream);
RSA2048Signature = Reader.ReadBytes(0x100);
RSA2048Modulus = Reader.ReadBytes(0x100);
if (Reader.ReadInt32() != ACIDMagic)
{
throw new InvalidNpdmException("ACID Stream doesn't contain ACID section!");
}
Unknown1 = Reader.ReadInt32(); // Size field used with the above signature(?).
Reader.ReadInt32(); // Padding / Unused
Flags = Reader.ReadInt32(); // Bit0 must be 1 on retail, on devunit 0 is also allowed. Bit1 is unknown.
byte[] TempTitleIdRangeMin = Reader.ReadBytes(8);
Array.Reverse(TempTitleIdRangeMin);
TitleIdRangeMin = BitConverter.ToString(TempTitleIdRangeMin).Replace("-", "");
byte[] TempTitleIdRangeMax = Reader.ReadBytes(8);
Array.Reverse(TempTitleIdRangeMax);
TitleIdRangeMax = BitConverter.ToString(TempTitleIdRangeMax).Replace("-", "");
FSAccessControlOffset = Reader.ReadInt32();
FSAccessControlSize = Reader.ReadInt32();
ServiceAccessControlOffset = Reader.ReadInt32();
ServiceAccessControlSize = Reader.ReadInt32();
KernelAccessControlOffset = Reader.ReadInt32();
KernelAccessControlSize = Reader.ReadInt32();
FSAccessControl = new FSAccessControl(ACIDStream, Offset + FSAccessControlOffset, FSAccessControlSize);
ServiceAccessControl = new ServiceAccessControl(ACIDStream, Offset + ServiceAccessControlOffset, ServiceAccessControlSize);
KernelAccessControl = new KernelAccessControl(ACIDStream, Offset + KernelAccessControlOffset, KernelAccessControlSize);
}
}
}

View file

@ -0,0 +1,28 @@
using System.IO;
namespace Ryujinx.HLE.Loaders.Npdm
{
public class FSAccessControl
{
public int Version;
public ulong PermissionsBitmask;
public int Unknown1;
public int Unknown2;
public int Unknown3;
public int Unknown4;
public FSAccessControl(Stream FSAccessHeaderStream, int Offset, int Size)
{
FSAccessHeaderStream.Seek(Offset, SeekOrigin.Begin);
BinaryReader Reader = new BinaryReader(FSAccessHeaderStream);
Version = Reader.ReadInt32();
PermissionsBitmask = Reader.ReadUInt64();
Unknown1 = Reader.ReadInt32();
Unknown2 = Reader.ReadInt32();
Unknown3 = Reader.ReadInt32();
Unknown4 = Reader.ReadInt32();
}
}
}

View file

@ -0,0 +1,37 @@
using System.IO;
namespace Ryujinx.HLE.Loaders.Npdm
{
public class FSAccessHeader
{
public int Version;
public ulong PermissionsBitmask;
public int DataSize;
public int ContentOwnerIDSize;
public int DataAndContentOwnerIDSize;
public FSAccessHeader(Stream FSAccessHeaderStream, int Offset, int Size)
{
FSAccessHeaderStream.Seek(Offset, SeekOrigin.Begin);
BinaryReader Reader = new BinaryReader(FSAccessHeaderStream);
Version = Reader.ReadInt32();
PermissionsBitmask = Reader.ReadUInt64();
DataSize = Reader.ReadInt32();
if (DataSize != 0x1C)
{
throw new InvalidNpdmException("FSAccessHeader is corrupted!");
}
ContentOwnerIDSize = Reader.ReadInt32();
DataAndContentOwnerIDSize = Reader.ReadInt32();
if (DataAndContentOwnerIDSize != 0x1C)
{
throw new InvalidNpdmException("ContentOwnerID section is not implemented!");
}
}
}
}

View file

@ -0,0 +1,208 @@
using System.Collections.Generic;
using System.IO;
namespace Ryujinx.HLE.Loaders.Npdm
{
public class KernelAccessControlIRQ
{
public uint IRQ0;
public uint IRQ1;
}
public class KernelAccessControlMMIO
{
public ulong Address;
public ulong Size;
public bool IsRO;
public bool IsNormal;
}
public class KernelAccessControlItems
{
public bool HasKernelFlags;
public uint LowestThreadPriority;
public uint HighestThreadPriority;
public uint LowestCpuId;
public uint HighestCpuId;
public bool HasSVCFlags;
public int[] SVCsAllowed;
public List<KernelAccessControlMMIO> NormalMMIO;
public List<KernelAccessControlMMIO> PageMMIO;
public List<KernelAccessControlIRQ> IRQ;
public bool HasApplicationType;
public int ApplicationType;
public bool HasKernelVersion;
public int KernelVersionRelease;
public bool HasHandleTableSize;
public int HandleTableSize;
public bool HasDebugFlags;
public bool AllowDebug;
public bool ForceDebug;
}
public class KernelAccessControl
{
public KernelAccessControlItems[] Items;
public KernelAccessControl(Stream FSAccessControlsStream, int Offset, int Size)
{
FSAccessControlsStream.Seek(Offset, SeekOrigin.Begin);
BinaryReader Reader = new BinaryReader(FSAccessControlsStream);
Items = new KernelAccessControlItems[Size / 4];
for (int i = 0; i < Size / 4; i++)
{
uint Descriptor = Reader.ReadUInt32();
if (Descriptor == 0xFFFFFFFF) //Ignore the descriptor
{
continue;
}
Items[i] = new KernelAccessControlItems();
int LowBits = 0;
while ((Descriptor & 1) != 0)
{
Descriptor >>= 1;
LowBits++;
}
Descriptor >>= 1;
switch (LowBits)
{
case 3: // Kernel flags
{
Items[i].HasKernelFlags = true;
Items[i].HighestThreadPriority = Descriptor & 0x3F;
Items[i].LowestThreadPriority = (Descriptor >> 6) & 0x3F;
Items[i].LowestCpuId = (Descriptor >> 12) & 0xFF;
Items[i].HighestCpuId = (Descriptor >> 20) & 0xFF;
break;
}
case 4: // Syscall mask
{
Items[i].HasSVCFlags = true;
Items[i].SVCsAllowed = new int[0x80];
int SysCallBase = (int)(Descriptor >> 24) * 0x18;
for (int SysCall = 0; SysCall < 0x18 && SysCallBase + SysCall < 0x80; SysCall++)
{
Items[i].SVCsAllowed[SysCallBase + SysCall] = (int)Descriptor & 1;
Descriptor >>= 1;
}
break;
}
case 6: // Map IO/Normal - Never tested.
{
KernelAccessControlMMIO TempNormalMMIO = new KernelAccessControlMMIO
{
Address = (Descriptor & 0xFFFFFF) << 12,
IsRO = (Descriptor >> 24) != 0
};
if (i == Size / 4 - 1)
{
throw new InvalidNpdmException("Invalid Kernel Access Control Descriptors!");
}
Descriptor = Reader.ReadUInt32();
if ((Descriptor & 0x7F) != 0x3F)
{
throw new InvalidNpdmException("Invalid Kernel Access Control Descriptors!");
}
Descriptor >>= 7;
TempNormalMMIO.Size = (Descriptor & 0xFFFFFF) << 12;
TempNormalMMIO.IsNormal = (Descriptor >> 24) != 0;
Items[i].NormalMMIO.Add(TempNormalMMIO);
i++;
break;
}
case 7: // Map Normal Page - Never tested.
{
KernelAccessControlMMIO TempPageMMIO = new KernelAccessControlMMIO
{
Address = Descriptor << 12,
Size = 0x1000,
IsRO = false,
IsNormal = false
};
Items[i].PageMMIO.Add(TempPageMMIO);
break;
}
case 11: // IRQ Pair - Never tested.
{
KernelAccessControlIRQ TempIRQ = new KernelAccessControlIRQ
{
IRQ0 = Descriptor & 0x3FF,
IRQ1 = (Descriptor >> 10) & 0x3FF
};
break;
}
case 13: // App Type
{
Items[i].HasApplicationType = true;
Items[i].ApplicationType = (int)Descriptor & 7;
break;
}
case 14: // Kernel Release Version
{
Items[i].HasKernelVersion = true;
Items[i].KernelVersionRelease = (int)Descriptor;
break;
}
case 15: // Handle Table Size
{
Items[i].HasHandleTableSize = true;
Items[i].HandleTableSize = (int)Descriptor;
break;
}
case 16: // Debug Flags
{
Items[i].HasDebugFlags = true;
Items[i].AllowDebug = (Descriptor & 1) != 0;
Items[i].ForceDebug = ((Descriptor >> 1) & 1) != 0;
break;
}
}
}
}
}
}

View file

@ -0,0 +1,87 @@
using Ryujinx.HLE.OsHle.Utilities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
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 bool Is64Bits;
public int AddressSpaceWidth;
public byte MainThreadPriority;
public byte DefaultCpuId;
public int SystemResourceSize;
public int ProcessCategory;
public int MainEntrypointStackSize;
public string TitleName;
public byte[] ProductCode;
public ulong FSPerms;
private int ACI0Offset;
private int ACI0Size;
private int ACIDOffset;
private int ACIDSize;
public ACI0 ACI0;
public ACID ACID;
public const long NpdmMagic = 'M' << 0 | 'E' << 8 | 'T' << 16 | 'A' << 24;
public Npdm(Stream NPDMStream)
{
BinaryReader Reader = new BinaryReader(NPDMStream);
if (Reader.ReadInt32() != NpdmMagic)
{
throw new InvalidNpdmException("NPDM Stream doesn't contain NPDM file!");
}
Reader.ReadInt64(); // Padding / Unused
// MmuFlags, bit0: 64-bit instructions, bits1-3: address space width (1=64-bit, 2=32-bit). Needs to be <= 0xF
byte MmuFlags = Reader.ReadByte();
Is64Bits = (MmuFlags & 1) != 0;
AddressSpaceWidth = (MmuFlags >> 1) & 7;
Reader.ReadByte(); // Padding / Unused
MainThreadPriority = Reader.ReadByte(); // (0-63)
DefaultCpuId = Reader.ReadByte();
Reader.ReadInt32(); // Padding / Unused
// System resource size (max size as of 5.x: 534773760). Unknown usage.
SystemResourceSize = EndianSwap.Swap32(Reader.ReadInt32());
// ProcessCategory (0: regular title, 1: kernel built-in). Should be 0 here.
ProcessCategory = EndianSwap.Swap32(Reader.ReadInt32());
// Main entrypoint stack size
// (Should(?) be page-aligned. In non-nspwn scenarios, values of 0 can also rarely break in Horizon.
// This might be something auto-adapting or a security feature of some sort ?)
MainEntrypointStackSize = Reader.ReadInt32();
byte[] TempTitleName = Reader.ReadBytes(0x10);
TitleName = Encoding.UTF8.GetString(TempTitleName, 0, TempTitleName.Length).Trim('\0');
ProductCode = Reader.ReadBytes(0x10); // Unknown value
NPDMStream.Seek(0x30, SeekOrigin.Current); // Skip reserved bytes
ACI0Offset = Reader.ReadInt32();
ACI0Size = Reader.ReadInt32();
ACIDOffset = Reader.ReadInt32();
ACIDSize = Reader.ReadInt32();
ACI0 = new ACI0(NPDMStream, ACI0Offset);
ACID = new ACID(NPDMStream, ACIDOffset);
FSPerms = ACI0.FSAccessHeader.PermissionsBitmask & ACID.FSAccessControl.PermissionsBitmask;
}
}
}

View file

@ -0,0 +1,9 @@
using System;
namespace Ryujinx.HLE.Loaders.Npdm
{
public class InvalidNpdmException : Exception
{
public InvalidNpdmException(string ExMsg) : base(ExMsg) { }
}
}

View file

@ -0,0 +1,215 @@
namespace Ryujinx.HLE.Loaders.Npdm
{
enum FSPermissionRW : ulong
{
MountContentType2 = 0x8000000000000801,
MountContentType5 = 0x8000000000000801,
MountContentType3 = 0x8000000000000801,
MountContentType4 = 0x8000000000000801,
MountContentType6 = 0x8000000000000801,
MountContentType7 = 0x8000000000000801,
Unknown0x6 = 0x8000000000000000,
ContentStorageAccess = 0x8000000000000800,
ImageDirectoryAccess = 0x8000000000001000,
MountBisType28 = 0x8000000000000084,
MountBisType29 = 0x8000000000000080,
MountBisType30 = 0x8000000000008080,
MountBisType31 = 0x8000000000008080,
Unknown0xD = 0x8000000000000080,
SdCardAccess = 0xC000000000200000,
GameCardUser = 0x8000000000000010,
SaveDataAccess0 = 0x8000000000040020,
SystemSaveDataAccess0 = 0x8000000000000028,
SaveDataAccess1 = 0x8000000000000020,
SystemSaveDataAccess1 = 0x8000000000000020,
BisPartition0 = 0x8000000000010082,
BisPartition10 = 0x8000000000010080,
BisPartition20 = 0x8000000000010080,
BisPartition21 = 0x8000000000010080,
BisPartition22 = 0x8000000000010080,
BisPartition23 = 0x8000000000010080,
BisPartition24 = 0x8000000000010080,
BisPartition25 = 0x8000000000010080,
BisPartition26 = 0x8000000000000080,
BisPartition27 = 0x8000000000000084,
BisPartition28 = 0x8000000000000084,
BisPartition29 = 0x8000000000000080,
BisPartition30 = 0x8000000000000080,
BisPartition31 = 0x8000000000000080,
BisPartition32 = 0x8000000000000080,
Unknown0x23 = 0xC000000000200000,
GameCard_System = 0x8000000000000100,
MountContent_System = 0x8000000000100008,
HostAccess = 0xC000000000400000
};
enum FSPermissionBool : ulong
{
BisCache = 0x8000000000000080,
EraseMmc = 0x8000000000000080,
GameCardCertificate = 0x8000000000000010,
GameCardIdSet = 0x8000000000000010,
GameCardDriver = 0x8000000000000200,
GameCardAsic = 0x8000000000000200,
SaveDataCreate = 0x8000000000002020,
SaveDataDelete0 = 0x8000000000000060,
SystemSaveDataCreate0 = 0x8000000000000028,
SystemSaveDataCreate1 = 0x8000000000000020,
SaveDataDelete1 = 0x8000000000004028,
SaveDataIterators0 = 0x8000000000000060,
SaveDataIterators1 = 0x8000000000004020,
SaveThumbnails = 0x8000000000020000,
PosixTime = 0x8000000000000400,
SaveDataExtraData = 0x8000000000004060,
GlobalMode = 0x8000000000080000,
SpeedEmulation = 0x8000000000080000,
NULL = 0,
PaddingFiles = 0xC000000000800000,
SaveData_Debug = 0xC000000001000000,
SaveData_SystemManagement = 0xC000000002000000,
Unknown0x16 = 0x8000000004000000,
Unknown0x17 = 0x8000000008000000,
Unknown0x18 = 0x8000000010000000,
Unknown0x19 = 0x8000000000000800,
Unknown0x1A = 0x8000000000004020
}
enum NpdmApplicationType
{
SystemModule,
Application,
Applet
}
enum SvcName
{
svcUnknown0,
svcSetHeapSize,
svcSetMemoryPermission,
svcSetMemoryAttribute,
svcMapMemory,
svcUnmapMemory,
svcQueryMemory,
svcExitProcess,
svcCreateThread,
svcStartThread,
svcExitThread,
svcSleepThread,
svcGetThreadPriority,
svcSetThreadPriority,
svcGetThreadCoreMask,
svcSetThreadCoreMask,
svcGetCurrentProcessorNumber,
svcSignalEvent,
svcClearEvent,
svcMapSharedMemory,
svcUnmapSharedMemory,
svcCreateTransferMemory,
svcCloseHandle,
svcResetSignal,
svcWaitSynchronization,
svcCancelSynchronization,
svcArbitrateLock,
svcArbitrateUnlock,
svcWaitProcessWideKeyAtomic,
svcSignalProcessWideKey,
svcGetSystemTick,
svcConnectToNamedPort,
svcSendSyncRequestLight,
svcSendSyncRequest,
svcSendSyncRequestWithUserBuffer,
svcSendAsyncRequestWithUserBuffer,
svcGetProcessId,
svcGetThreadId,
svcBreak,
svcOutputDebugString,
svcReturnFromException,
svcGetInfo,
svcFlushEntireDataCache,
svcFlushDataCache,
svcMapPhysicalMemory,
svcUnmapPhysicalMemory,
svcGetFutureThreadInfo,
svcGetLastThreadInfo,
svcGetResourceLimitLimitValue,
svcGetResourceLimitCurrentValue,
svcSetThreadActivity,
svcGetThreadContext3,
svcWaitForAddress,
svcSignalToAddress,
svcUnknown1,
svcUnknown2,
svcUnknown3,
svcUnknown4,
svcUnknown5,
svcUnknown6,
svcDumpInfo,
svcDumpInfoNew,
svcUnknown7,
svcUnknown8,
svcCreateSession,
svcAcceptSession,
svcReplyAndReceiveLight,
svcReplyAndReceive,
svcReplyAndReceiveWithUserBuffer,
svcCreateEvent,
svcUnknown9,
svcUnknown10,
svcMapPhysicalMemoryUnsafe,
svcUnmapPhysicalMemoryUnsafe,
svcSetUnsafeLimit,
svcCreateCodeMemory,
svcControlCodeMemory,
svcSleepSystem,
svcReadWriteRegister,
svcSetProcessActivity,
svcCreateSharedMemory,
svcMapTransferMemory,
svcUnmapTransferMemory,
svcCreateInterruptEvent,
svcQueryPhysicalAddress,
svcQueryIoMapping,
svcCreateDeviceAddressSpace,
svcAttachDeviceAddressSpace,
svcDetachDeviceAddressSpace,
svcMapDeviceAddressSpaceByForce,
svcMapDeviceAddressSpaceAligned,
svcMapDeviceAddressSpace,
svcUnmapDeviceAddressSpace,
svcInvalidateProcessDataCache,
svcStoreProcessDataCache,
svcFlushProcessDataCache,
svcDebugActiveProcess,
svcBreakDebugProcess,
svcTerminateDebugProcess,
svcGetDebugEvent,
svcContinueDebugEvent,
svcGetProcessList,
svcGetThreadList,
svcGetDebugThreadContext,
svcSetDebugThreadContext,
svcQueryDebugProcessMemory,
svcReadDebugProcessMemory,
svcWriteDebugProcessMemory,
svcSetHardwareBreakPoint,
svcGetDebugThreadParam,
svcUnknown11,
svcGetSystemInfo,
svcCreatePort,
svcManageNamedPort,
svcConnectToPort,
svcSetProcessMemoryPermission,
svcMapProcessMemory,
svcUnmapProcessMemory,
svcQueryProcessMemory,
svcMapProcessCodeMemory,
svcUnmapProcessCodeMemory,
svcCreateProcess,
svcStartProcess,
svcTerminateProcess,
svcGetProcessInfo,
svcCreateResourceLimit,
svcSetResourceLimitLimitValue,
svcCallSecureMonitor
};
}

View file

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace Ryujinx.HLE.Loaders.Npdm
{
public class ServiceAccessControl
{
public List<(string, bool)> Services = new List<(string, bool)>();
public ServiceAccessControl(Stream ServiceAccessControlStream, int Offset, int Size)
{
ServiceAccessControlStream.Seek(Offset, SeekOrigin.Begin);
BinaryReader Reader = new BinaryReader(ServiceAccessControlStream);
int ByteReaded = 0;
while (ByteReaded != Size)
{
byte ControlByte = Reader.ReadByte();
if (ControlByte == 0x00) break;
int Length = ((ControlByte & 0x07)) + 1;
bool RegisterAllowed = ((ControlByte & 0x80) != 0);
Services.Add((Encoding.ASCII.GetString(Reader.ReadBytes(Length), 0, Length), RegisterAllowed));
ByteReaded += Length + 1;
}
}
}
}

View file

@ -3,5 +3,15 @@
static class EndianSwap static class EndianSwap
{ {
public static short Swap16(short Value) => (short)(((Value >> 8) & 0xff) | (Value << 8)); public static short Swap16(short Value) => (short)(((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));
}
} }
} }