Move dependencies to the top level.

This commit is contained in:
jakcron
2022-04-16 18:27:14 +08:00
parent b67980d3d2
commit 5d62e839e7
844 changed files with 74 additions and 114431 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Jack
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+2
View File
@@ -0,0 +1,2 @@
# libnintendo-n3ds
C++ Library for parsing Nintendo 3DS file formats
@@ -0,0 +1,71 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31229.75
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libnintendo-n3ds", "libnintendo-n3ds\libnintendo-n3ds.vcxproj", "{7789491C-5403-4613-B06B-DB0C58E19A01}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libfmt", "..\..\deps\libfmt\build\visualstudio\libfmt\libfmt.vcxproj", "{F4B0540E-0AAE-4006-944B-356944EF61FA}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libmbedtls", "..\..\deps\libmbedtls\build\visualstudio\libmbedtls\libmbedtls.vcxproj", "{7A7C66F3-2B5B-4E23-85D8-2A74FEDAD92C}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libtoolchain", "..\..\deps\libtoolchain\build\visualstudio\libtoolchain\libtoolchain.vcxproj", "{E194E4B8-1482-40A2-901B-75D4387822E9}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libbroadon-es", "..\..\deps\libbroadon-es\build\visualstudio\libbroadon-es\libbroadon-es.vcxproj", "{8A35D7DC-293A-4669-9C1E-4F45ABFE8838}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7789491C-5403-4613-B06B-DB0C58E19A01}.Debug|x64.ActiveCfg = Debug|x64
{7789491C-5403-4613-B06B-DB0C58E19A01}.Debug|x64.Build.0 = Debug|x64
{7789491C-5403-4613-B06B-DB0C58E19A01}.Debug|x86.ActiveCfg = Debug|Win32
{7789491C-5403-4613-B06B-DB0C58E19A01}.Debug|x86.Build.0 = Debug|Win32
{7789491C-5403-4613-B06B-DB0C58E19A01}.Release|x64.ActiveCfg = Release|x64
{7789491C-5403-4613-B06B-DB0C58E19A01}.Release|x64.Build.0 = Release|x64
{7789491C-5403-4613-B06B-DB0C58E19A01}.Release|x86.ActiveCfg = Release|Win32
{7789491C-5403-4613-B06B-DB0C58E19A01}.Release|x86.Build.0 = Release|Win32
{F4B0540E-0AAE-4006-944B-356944EF61FA}.Debug|x64.ActiveCfg = Debug|x64
{F4B0540E-0AAE-4006-944B-356944EF61FA}.Debug|x64.Build.0 = Debug|x64
{F4B0540E-0AAE-4006-944B-356944EF61FA}.Debug|x86.ActiveCfg = Debug|Win32
{F4B0540E-0AAE-4006-944B-356944EF61FA}.Debug|x86.Build.0 = Debug|Win32
{F4B0540E-0AAE-4006-944B-356944EF61FA}.Release|x64.ActiveCfg = Release|x64
{F4B0540E-0AAE-4006-944B-356944EF61FA}.Release|x64.Build.0 = Release|x64
{F4B0540E-0AAE-4006-944B-356944EF61FA}.Release|x86.ActiveCfg = Release|Win32
{F4B0540E-0AAE-4006-944B-356944EF61FA}.Release|x86.Build.0 = Release|Win32
{7A7C66F3-2B5B-4E23-85D8-2A74FEDAD92C}.Debug|x64.ActiveCfg = Debug|x64
{7A7C66F3-2B5B-4E23-85D8-2A74FEDAD92C}.Debug|x64.Build.0 = Debug|x64
{7A7C66F3-2B5B-4E23-85D8-2A74FEDAD92C}.Debug|x86.ActiveCfg = Debug|Win32
{7A7C66F3-2B5B-4E23-85D8-2A74FEDAD92C}.Debug|x86.Build.0 = Debug|Win32
{7A7C66F3-2B5B-4E23-85D8-2A74FEDAD92C}.Release|x64.ActiveCfg = Release|x64
{7A7C66F3-2B5B-4E23-85D8-2A74FEDAD92C}.Release|x64.Build.0 = Release|x64
{7A7C66F3-2B5B-4E23-85D8-2A74FEDAD92C}.Release|x86.ActiveCfg = Release|Win32
{7A7C66F3-2B5B-4E23-85D8-2A74FEDAD92C}.Release|x86.Build.0 = Release|Win32
{E194E4B8-1482-40A2-901B-75D4387822E9}.Debug|x64.ActiveCfg = Debug|x64
{E194E4B8-1482-40A2-901B-75D4387822E9}.Debug|x64.Build.0 = Debug|x64
{E194E4B8-1482-40A2-901B-75D4387822E9}.Debug|x86.ActiveCfg = Debug|Win32
{E194E4B8-1482-40A2-901B-75D4387822E9}.Debug|x86.Build.0 = Debug|Win32
{E194E4B8-1482-40A2-901B-75D4387822E9}.Release|x64.ActiveCfg = Release|x64
{E194E4B8-1482-40A2-901B-75D4387822E9}.Release|x64.Build.0 = Release|x64
{E194E4B8-1482-40A2-901B-75D4387822E9}.Release|x86.ActiveCfg = Release|Win32
{E194E4B8-1482-40A2-901B-75D4387822E9}.Release|x86.Build.0 = Release|Win32
{8A35D7DC-293A-4669-9C1E-4F45ABFE8838}.Debug|x64.ActiveCfg = Debug|x64
{8A35D7DC-293A-4669-9C1E-4F45ABFE8838}.Debug|x64.Build.0 = Debug|x64
{8A35D7DC-293A-4669-9C1E-4F45ABFE8838}.Debug|x86.ActiveCfg = Debug|Win32
{8A35D7DC-293A-4669-9C1E-4F45ABFE8838}.Debug|x86.Build.0 = Debug|Win32
{8A35D7DC-293A-4669-9C1E-4F45ABFE8838}.Release|x64.ActiveCfg = Release|x64
{8A35D7DC-293A-4669-9C1E-4F45ABFE8838}.Release|x64.Build.0 = Release|x64
{8A35D7DC-293A-4669-9C1E-4F45ABFE8838}.Release|x86.ActiveCfg = Release|Win32
{8A35D7DC-293A-4669-9C1E-4F45ABFE8838}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E9C7A315-F87A-48C9-89E8-1238E856168F}
EndGlobalSection
EndGlobal
@@ -0,0 +1,185 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{7789491c-5403-4613-b06b-db0c58e19a01}</ProjectGuid>
<RootNamespace>libnintendon3ds</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup />
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(SolutionDir)..\..\deps\libmbedtls\include;$(SolutionDir)..\..\deps\libfmt\include;$(SolutionDir)..\..\deps\libtoolchain\include;$(SolutionDir)..\..\deps\libbroadon-es\include;$(ProjectDir)..\..\..\include</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(SolutionDir)..\..\deps\libmbedtls\include;$(SolutionDir)..\..\deps\libfmt\include;$(SolutionDir)..\..\deps\libtoolchain\include;$(SolutionDir)..\..\deps\libbroadon-es\include;$(ProjectDir)..\..\..\include</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(SolutionDir)..\..\deps\libmbedtls\include;$(SolutionDir)..\..\deps\libfmt\include;$(SolutionDir)..\..\deps\libtoolchain\include;$(SolutionDir)..\..\deps\libbroadon-es\include;$(ProjectDir)..\..\..\include</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(SolutionDir)..\..\deps\libmbedtls\include;$(SolutionDir)..\..\deps\libfmt\include;$(SolutionDir)..\..\deps\libtoolchain\include;$(SolutionDir)..\..\deps\libbroadon-es\include;$(ProjectDir)..\..\..\include</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ProjectReference Include="$(SolutionDir)..\..\deps\libmbedtls\build\visualstudio\libmbedtls\libmbedtls.vcxproj">
<Project>{7a7c66f3-2b5b-4e23-85d8-2a74fedad92c}</Project>
</ProjectReference>
<ProjectReference Include="$(SolutionDir)..\..\deps\libfmt\build\visualstudio\libfmt\libfmt.vcxproj">
<Project>{f4b0540e-0aae-4006-944b-356944ef61fa}</Project>
</ProjectReference>
<ProjectReference Include="$(SolutionDir)..\..\deps\libtoolchain\build\visualstudio\libtoolchain\libtoolchain.vcxproj">
<Project>{e194e4b8-1482-40a2-901b-75d4387822e9}</Project>
</ProjectReference>
<ProjectReference Include="$(SolutionDir)..\..\deps\libbroadon-es\build\visualstudio\libbroadon-es\libbroadon-es.vcxproj">
<Project>{8a35d7dc-293a-4669-9c1e-4f45abfe8838}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\..\src\CciFsSnapshotGenerator.cpp" />
<ClCompile Include="..\..\..\src\CiaFsSnapshotGenerator.cpp" />
<ClCompile Include="..\..\..\src\CtrKeyGenerator.cpp" />
<ClCompile Include="..\..\..\src\es\Certificate.cpp" />
<ClCompile Include="..\..\..\src\es\RsaSigner.cpp" />
<ClCompile Include="..\..\..\src\es\Signature.cpp" />
<ClCompile Include="..\..\..\src\es\Ticket.cpp" />
<ClCompile Include="..\..\..\src\es\TitleMetaData.cpp" />
<ClCompile Include="..\..\..\src\ExeFsSnapshotGenerator.cpp" />
<ClCompile Include="..\..\..\src\IvfcStream.cpp" />
<ClCompile Include="..\..\..\src\RomFsSnapshotGenerator.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\..\include\ntd\n3ds.h" />
<ClInclude Include="..\..\..\include\ntd\n3ds\bcwav.h" />
<ClInclude Include="..\..\..\include\ntd\n3ds\cci.h" />
<ClInclude Include="..\..\..\include\ntd\n3ds\CciFsSnapshotGenerator.h" />
<ClInclude Include="..\..\..\include\ntd\n3ds\cia.h" />
<ClInclude Include="..\..\..\include\ntd\n3ds\CiaFsSnapshotGenerator.h" />
<ClInclude Include="..\..\..\include\ntd\n3ds\cro.h" />
<ClInclude Include="..\..\..\include\ntd\n3ds\crr.h" />
<ClInclude Include="..\..\..\include\ntd\n3ds\CtrKeyGenerator.h" />
<ClInclude Include="..\..\..\include\ntd\n3ds\es.h" />
<ClInclude Include="..\..\..\include\ntd\n3ds\es\Certificate.h" />
<ClInclude Include="..\..\..\include\ntd\n3ds\es\ISigner.h" />
<ClInclude Include="..\..\..\include\ntd\n3ds\es\RsaSigner.h" />
<ClInclude Include="..\..\..\include\ntd\n3ds\es\Signature.h" />
<ClInclude Include="..\..\..\include\ntd\n3ds\es\Ticket.h" />
<ClInclude Include="..\..\..\include\ntd\n3ds\es\TitleMetaData.h" />
<ClInclude Include="..\..\..\include\ntd\n3ds\exefs.h" />
<ClInclude Include="..\..\..\include\ntd\n3ds\ExeFsSnapshotGenerator.h" />
<ClInclude Include="..\..\..\include\ntd\n3ds\exheader.h" />
<ClInclude Include="..\..\..\include\ntd\n3ds\firm.h" />
<ClInclude Include="..\..\..\include\ntd\n3ds\ivfc.h" />
<ClInclude Include="..\..\..\include\ntd\n3ds\IvfcStream.h" />
<ClInclude Include="..\..\..\include\ntd\n3ds\ncch.h" />
<ClInclude Include="..\..\..\include\ntd\n3ds\romfs.h" />
<ClInclude Include="..\..\..\include\ntd\n3ds\RomFsSnapshotGenerator.h" />
<ClInclude Include="..\..\..\include\ntd\n3ds\smdh.h" />
<ClInclude Include="..\..\..\include\ntd\n3ds\systemupdater.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
@@ -0,0 +1,147 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="Source Files\es">
<UniqueIdentifier>{6ff37ad4-4d41-489a-8dee-7a67ae5c3a0c}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\ntd">
<UniqueIdentifier>{a8556034-ba3b-4e55-99fe-75f9cec9442d}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\ntd\n3ds">
<UniqueIdentifier>{27d62e82-f179-4eaf-9592-4aefd77854a0}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\ntd\n3ds\es">
<UniqueIdentifier>{8ca97abf-9738-4b26-a728-0bb7a705496a}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\..\src\CciFsSnapshotGenerator.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\CiaFsSnapshotGenerator.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\CtrKeyGenerator.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\ExeFsSnapshotGenerator.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\IvfcStream.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\RomFsSnapshotGenerator.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\es\Certificate.cpp">
<Filter>Source Files\es</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\es\RsaSigner.cpp">
<Filter>Source Files\es</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\es\Signature.cpp">
<Filter>Source Files\es</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\es\Ticket.cpp">
<Filter>Source Files\es</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\es\TitleMetaData.cpp">
<Filter>Source Files\es</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\..\include\ntd\n3ds.h">
<Filter>Header Files\ntd</Filter>
</ClInclude>
<ClInclude Include="..\..\..\include\ntd\n3ds\bcwav.h">
<Filter>Header Files\ntd\n3ds</Filter>
</ClInclude>
<ClInclude Include="..\..\..\include\ntd\n3ds\cci.h">
<Filter>Header Files\ntd\n3ds</Filter>
</ClInclude>
<ClInclude Include="..\..\..\include\ntd\n3ds\CciFsSnapshotGenerator.h">
<Filter>Header Files\ntd\n3ds</Filter>
</ClInclude>
<ClInclude Include="..\..\..\include\ntd\n3ds\cia.h">
<Filter>Header Files\ntd\n3ds</Filter>
</ClInclude>
<ClInclude Include="..\..\..\include\ntd\n3ds\CiaFsSnapshotGenerator.h">
<Filter>Header Files\ntd\n3ds</Filter>
</ClInclude>
<ClInclude Include="..\..\..\include\ntd\n3ds\cro.h">
<Filter>Header Files\ntd\n3ds</Filter>
</ClInclude>
<ClInclude Include="..\..\..\include\ntd\n3ds\crr.h">
<Filter>Header Files\ntd\n3ds</Filter>
</ClInclude>
<ClInclude Include="..\..\..\include\ntd\n3ds\CtrKeyGenerator.h">
<Filter>Header Files\ntd\n3ds</Filter>
</ClInclude>
<ClInclude Include="..\..\..\include\ntd\n3ds\es.h">
<Filter>Header Files\ntd\n3ds</Filter>
</ClInclude>
<ClInclude Include="..\..\..\include\ntd\n3ds\exefs.h">
<Filter>Header Files\ntd\n3ds</Filter>
</ClInclude>
<ClInclude Include="..\..\..\include\ntd\n3ds\ExeFsSnapshotGenerator.h">
<Filter>Header Files\ntd\n3ds</Filter>
</ClInclude>
<ClInclude Include="..\..\..\include\ntd\n3ds\exheader.h">
<Filter>Header Files\ntd\n3ds</Filter>
</ClInclude>
<ClInclude Include="..\..\..\include\ntd\n3ds\firm.h">
<Filter>Header Files\ntd\n3ds</Filter>
</ClInclude>
<ClInclude Include="..\..\..\include\ntd\n3ds\ivfc.h">
<Filter>Header Files\ntd\n3ds</Filter>
</ClInclude>
<ClInclude Include="..\..\..\include\ntd\n3ds\IvfcStream.h">
<Filter>Header Files\ntd\n3ds</Filter>
</ClInclude>
<ClInclude Include="..\..\..\include\ntd\n3ds\ncch.h">
<Filter>Header Files\ntd\n3ds</Filter>
</ClInclude>
<ClInclude Include="..\..\..\include\ntd\n3ds\romfs.h">
<Filter>Header Files\ntd\n3ds</Filter>
</ClInclude>
<ClInclude Include="..\..\..\include\ntd\n3ds\RomFsSnapshotGenerator.h">
<Filter>Header Files\ntd\n3ds</Filter>
</ClInclude>
<ClInclude Include="..\..\..\include\ntd\n3ds\smdh.h">
<Filter>Header Files\ntd\n3ds</Filter>
</ClInclude>
<ClInclude Include="..\..\..\include\ntd\n3ds\systemupdater.h">
<Filter>Header Files\ntd\n3ds</Filter>
</ClInclude>
<ClInclude Include="..\..\..\include\ntd\n3ds\es\Certificate.h">
<Filter>Header Files\ntd\n3ds\es</Filter>
</ClInclude>
<ClInclude Include="..\..\..\include\ntd\n3ds\es\ISigner.h">
<Filter>Header Files\ntd\n3ds\es</Filter>
</ClInclude>
<ClInclude Include="..\..\..\include\ntd\n3ds\es\RsaSigner.h">
<Filter>Header Files\ntd\n3ds\es</Filter>
</ClInclude>
<ClInclude Include="..\..\..\include\ntd\n3ds\es\Signature.h">
<Filter>Header Files\ntd\n3ds\es</Filter>
</ClInclude>
<ClInclude Include="..\..\..\include\ntd\n3ds\es\Ticket.h">
<Filter>Header Files\ntd\n3ds\es</Filter>
</ClInclude>
<ClInclude Include="..\..\..\include\ntd\n3ds\es\TitleMetaData.h">
<Filter>Header Files\ntd\n3ds\es</Filter>
</ClInclude>
</ItemGroup>
</Project>
+26
View File
@@ -0,0 +1,26 @@
#pragma once
// definitions
#include <ntd/n3ds/cci.h>
#include <ntd/n3ds/cia.h>
#include <ntd/n3ds/cro.h>
#include <ntd/n3ds/crr.h>
#include <ntd/n3ds/exefs.h>
#include <ntd/n3ds/exheader.h>
#include <ntd/n3ds/firm.h>
#include <ntd/n3ds/ivfc.h>
#include <ntd/n3ds/ncch.h>
#include <ntd/n3ds/romfs.h>
#include <ntd/n3ds/smdh.h>
// Wrapped IStream
#include <ntd/n3ds/IvfcStream.h>
// Utilities
#include <ntd/n3ds/CtrKeyGenerator.h>
// VirtualFileSystem metadata generators
#include <ntd/n3ds/ExeFsMetaGenerator.h>
#include <ntd/n3ds/RomFsMetaGenerator.h>
#include <ntd/n3ds/CciFsMetaGenerator.h>
#include <ntd/n3ds/CiaFsMetaGenerator.h>
@@ -0,0 +1,19 @@
#pragma once
#include <tc/io/VirtualFileSystem.h>
namespace ntd { namespace n3ds {
struct CciFsShapshotGenerator : public tc::io::VirtualFileSystem::FileSystemSnapshot
{
public:
CciFsShapshotGenerator(const std::shared_ptr<tc::io::IStream>& stream);
private:
CciFsShapshotGenerator();
std::shared_ptr<tc::io::IStream> mBaseStream;
size_t mCurDir;
void addFile(const std::string& name, int64_t offset, int64_t size);
};
}} // namespace ntd::n3ds
@@ -0,0 +1,19 @@
#pragma once
#include <tc/io/VirtualFileSystem.h>
namespace ntd { namespace n3ds {
struct CiaFsSnapshotGenerator : public tc::io::VirtualFileSystem::FileSystemSnapshot
{
public:
CiaFsSnapshotGenerator(const std::shared_ptr<tc::io::IStream>& stream);
private:
CiaFsSnapshotGenerator();
std::shared_ptr<tc::io::IStream> mBaseStream;
size_t mCurDir;
void addFile(const std::string& name, int64_t offset, int64_t size);
};
}} // namespace ntd::n3ds
@@ -0,0 +1,19 @@
#pragma once
#include <tc/types.h>
namespace ntd { namespace n3ds {
class CtrKeyGenerator
{
public:
static void GenerateKey(const uint8_t *x, const uint8_t *y, uint8_t *key);
private:
static int32_t wrap_index(int32_t i);
static void n128_rrot(const uint8_t *in, uint32_t rot, uint8_t *out);
static void n128_lrot(const uint8_t *in, uint32_t rot, uint8_t *out);
static void n128_add(const uint8_t *a, const uint8_t *b, uint8_t *out);
static void n128_sub(const uint8_t *a, const uint8_t *b, uint8_t *out);
static void n128_xor(const uint8_t *a, const uint8_t *b, uint8_t *out);
};
}} // namespace ntd::n3ds
@@ -0,0 +1,14 @@
#pragma once
#include <tc/io/VirtualFileSystem.h>
namespace ntd { namespace n3ds {
struct ExeFsSnapshotGenerator : public tc::io::VirtualFileSystem::FileSystemSnapshot
{
public:
ExeFsSnapshotGenerator(const std::shared_ptr<tc::io::IStream>& stream, bool verify_hashes = true);
private:
ExeFsSnapshotGenerator();
};
}} // namespace ntd::n3ds
+141
View File
@@ -0,0 +1,141 @@
#pragma once
#include <tc/ByteData.h>
#include <tc/io/IStream.h>
#include <tc/io/IOUtil.h>
#include <tc/crypto/Sha256Generator.h>
#include <tc/crypto/CryptoException.h>
#include <tc/ArgumentOutOfRangeException.h>
#include <tc/ObjectDisposedException.h>
namespace ntd { namespace n3ds {
class IvfcStream : public tc::io::IStream
{
public:
IvfcStream();
IvfcStream(const std::shared_ptr<tc::io::IStream>& stream);
/**
* @brief Indicates whether the current stream supports reading.
**/
bool canRead() const;
/**
* @brief Indicates whether the current stream supports writing.
**/
bool canWrite() const;
/**
* @brief Indicates whether the current stream supports seeking.
**/
bool canSeek() const;
/**
* @brief Gets the length in bytes of the stream.
**/
int64_t length();
/**
* @brief Gets the position within the current stream.
*
* @return This is returns the current position within the stream.
**/
int64_t position();
/**
* @brief Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
*
* @param[out] ptr Pointer to an array of bytes. When this method returns, @p ptr contains the specified byte array with the values between 0 and (@p count - 1) replaced by the bytes read from the current source.
* @param[in] count The maximum number of bytes to be read from the current stream.
*
* @return The total number of bytes read into @p ptr. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.
*
* @pre A stream must support reading for @ref read to work.
* @note Use @ref canRead to determine if this stream supports reading.
* @note Exceptions thrown by the base stream are not altered/intercepted, refer to that module's documentation for those exceptions.
*
* @throw tc::ArgumentOutOfRangeException @p count exceeds the length of readable data in the sub stream.
* @throw tc::ObjectDisposedException Methods were called after the stream was closed.
**/
size_t read(byte_t* ptr, size_t count);
/**
* @brief Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
*
* @param[in] ptr Pointer to an array of bytes. This method copies @p count bytes from @p ptr to the current stream.
* @param[in] count The number of bytes to be written to the current stream.
*
* @return The total number of bytes written to the stream. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.
*
* @pre A stream must support writing for @ref write to work.
* @note Use @ref canWrite to determine if this stream supports writing.
* @note Exceptions thrown by the base stream are not altered/intercepted, refer to that module's documentation for those exceptions.
*
* @throw tc::ArgumentOutOfRangeException @p count exceeds the length of writeable data in the sub stream.
* @throw tc::ObjectDisposedException Methods were called after the stream was closed.
**/
size_t write(const byte_t* ptr, size_t count);
/**
* @brief Sets the position within the current stream.
*
* @param[in] offset A byte offset relative to the origin parameter.
* @param[in] origin A value of type @ref tc::io::SeekOrigin indicating the reference point used to obtain the new position.
*
* @return The new position within the current stream.
*
* @pre A stream must support seeking for @ref seek to work.
* @note Use @ref canSeek to determine if this stream supports seeking.
* @note Exceptions thrown by the base stream are not altered/intercepted, refer to that module's documentation for those exceptions.
*
* @throw tc::ArgumentOutOfRangeException @p origin contains an invalid value.
* @throw tc::ObjectDisposedException Methods were called after the stream was closed.
**/
int64_t seek(int64_t offset, tc::io::SeekOrigin origin);
/**
* @brief Sets the length of the current stream. This is not implemented for @ref SubStream.
* @throw tc::NotImplementedException @ref setLength is not implemented for @ref SubStream
* @throw tc::ObjectDisposedException Methods were called after the stream was closed.
**/
void setLength(int64_t length);
/**
* @brief Clears all buffers for this and the base stream and causes any buffered data to be written to the underlying device.
*
* @throw tc::io::IOException An I/O error occurs.
* @throw tc::ObjectDisposedException Methods were called after the stream was closed.
**/
void flush();
/**
* @brief Releases internal resources including base stream and clears internal state.
**/
void dispose();
private:
std::string mModuleLabel;
// base stream
std::shared_ptr<tc::io::IStream> mBaseStream;
// data layer stream
size_t mDataStreamBlockSize;
int64_t mDataStreamLogicalLength;
std::shared_ptr<tc::io::IStream> mDataStream;
inline size_t offsetToBlock(int64_t offset) { return tc::io::IOUtil::castInt64ToSize(offset / tc::io::IOUtil::castSizeToInt64(mDataStreamBlockSize)); };
inline size_t offsetInBlock(int64_t offset) { return tc::io::IOUtil::castInt64ToSize(offset % tc::io::IOUtil::castSizeToInt64(mDataStreamBlockSize)); };
inline int64_t blockToOffset(size_t block) { return tc::io::IOUtil::castSizeToInt64(block) * tc::io::IOUtil::castSizeToInt64(mDataStreamBlockSize); };
// hash cache for data layer
tc::ByteData mHashCache;
inline byte_t* getBlockHash(size_t begin_block) { return mHashCache.data() + (begin_block * tc::crypto::Sha256Generator::kHashSize); }
// hash calc temp
std::array<byte_t, tc::crypto::Sha256Generator::kHashSize> mHash;
tc::crypto::Sha256Generator mHashCalc;
bool validateLayerBlocksWithHashLayer(const byte_t* layer, size_t block_size, size_t block_num, const byte_t* hash_layer);
};
}} // namespace ntd::n3ds
@@ -0,0 +1,30 @@
#pragma once
#include <tc/ByteData.h>
#include <tc/io/VirtualFileSystem.h>
#include <ntd/n3ds/romfs.h>
namespace ntd { namespace n3ds {
struct RomFsSnapshotGenerator : public tc::io::VirtualFileSystem::FileSystemSnapshot
{
public:
RomFsSnapshotGenerator(const std::shared_ptr<tc::io::IStream>& stream);
private:
RomFsSnapshotGenerator();
std::shared_ptr<tc::io::IStream> mBaseStream;
int64_t mDataOffset;
tc::ByteData mDirEntryTable;
std::map<uint32_t, size_t> mDirParentVaddrMap;
inline ntd::n3ds::RomFsDirectoryEntry* getDirEntry(uint32_t vaddr) { return (ntd::n3ds::RomFsDirectoryEntry*)(mDirEntryTable.data() + vaddr); }
tc::ByteData mFileEntryTable;
inline ntd::n3ds::RomFsFileEntry* getFileEntry(uint32_t vaddr) { return (ntd::n3ds::RomFsFileEntry*)(mFileEntryTable.data() + vaddr); }
void addFile(const ntd::n3ds::RomFsFileEntry* file_entry, size_t parent_dir);
void addDirectory(const ntd::n3ds::RomFsDirectoryEntry* dir_entry, size_t parent_dir);
};
}} // namespace ntd::n3ds
+212
View File
@@ -0,0 +1,212 @@
#pragma once
#include <tc/types.h>
namespace ntd { namespace n3ds {
namespace bcwav {
static const uint32_t kCwavMagic = tc::bn::make_struct_magic_uint32("CWAV");
static const uint32_t kCwavVersion = 0x02010000;
static const uint32_t kInfoMagic = tc::bn::make_struct_magic_uint32("INFO");
static const uint32_t kDataMagic = tc::bn::make_struct_magic_uint32("DATA");
enum TypeId
{
TYPE_ID_DSP_ADPCM = 0x300,
TYPE_ID_IMA_ADPCM = 0x301,
TYPE_ID_INFO_BLOCK = 0x7000,
TYPE_ID_DATA_BLOCK = 0x7001,
TYPE_ID_CHANNEL_REF = 0x7100,
};
enum Encoding
{
ENCODING_PCM8 = 0,
ENCODING_PCM16 = 1,
ENCODING_DSP_ADPCM = 2,
ENCODING_IMA_ADPCM = 3
};
enum ChannelIndex
{
CHANNEL_INDEX_L = 0,
CHANNEL_INDEX_R = 1,
};
#pragma pack(push,1)
struct Reference
{
// 0x00
tc::bn::le16<uint16_t> type_id;
// 0x02
tc::bn::pad<2> padding;
// 0x04
tc::bn::le32<uint32_t> offset;
// 0x08
};
struct ReferenceWithSize : public Reference
{
// 0x08
tc::bn::le32<uint32_t> size;
// 0x0C
};
template <typename T>
struct Table
{
// 0x00
tc::bn::le32<uint32_t> count;
// 0x04
T item[];
};
struct FileHeader
{
// 0x00
tc::bn::le32<uint32_t> signature; // 'CWAV'
// 0x04
tc::bn::le16<uint16_t> byte_order_mark; // 0xfeff (always little endian)
// 0x06
tc::bn::le16<uint16_t> header_size;
// 0x08
tc::bn::le32<uint32_t> version; // 2.1.0.0 (0x02010000)
// 0x0C
tc::bn::le32<uint32_t> file_size;
// 0x10
tc::bn::le16<uint16_t> data_blocks;
// 0x12
tc::bn::pad<2> reserved;
// 0x14
};
struct BlockInfo
{
// 0x00
ReferenceWithSize info_block_reference;
// 0x0C
ReferenceWithSize data_block_reference;
// 0x18
};
struct FileInfo
{
// 0x00
FileHeader header;
// 0x14
BlockInfo block_info;
// 0x2C
};
struct BlockHeader
{
// 0x00
tc::bn::le32<uint32_t> kind; // 'INFO' or 'DATA'
// 0x04
tc::bn::le32<uint32_t> size;
// 0x08
};
struct WaveInfo
{
// 0x00
byte_t encoding; // see Encoding
// 0x01
byte_t is_loop; // 0: noloop, 1: loop
// 0x02
tc::bn::pad<2> padding;
// 0x04
tc::bn::le32<uint32_t> sample_rate;
// 0x08
tc::bn::le32<uint32_t> loop_start_frame;
// 0x0C
tc::bn::le32<uint32_t> loop_end_frame;
// 0x10
};
struct InfoBlockBody
{
// 0x00
WaveInfo wave_info;
// 0x10
tc::bn::pad<4> reserved;
// 0x14
Table<Reference> channel_info_reference_table;
// 0x18
};
struct ChannelInfo
{
// 0x00
Reference to_samples;
// 0x08
Reference to_adpcm_info;
// 0x10
tc::bn::pad<4> reserved;
// 0x14
};
struct AdpcmParam
{
// 0x00
std::array<tc::bn::le16<uint16_t>, 16> coef;
// 0x20
};
struct AdpcmContext
{
// 0x00
tc::bn::le16<uint16_t> pred_scale; // Stores the predicted value (4bit) and the scale value (4bit) of Adpcm. The upper 8 bits are not referenced
// 0x02
tc::bn::le16<int16_t> yn1; // Historical data (1st sample value)
// 0x04
tc::bn::le16<int16_t> yn2; // Historical data (2nd sample value)
// 0x06
};
struct DspAdpcmInfo
{
// 0x00
AdpcmParam param;
// 0x20
AdpcmContext context;
// 0x26
AdpcmContext loop_context;
// 0x2C
};
struct ImaAdpcmContext
{
// 0x00
tc::bn::le16<uint16_t> data;
// 0x02
byte_t table_index;
// 0x03
byte_t padding;
// 0x04
};
struct ImaAdpcmInfo
{
// 0x00
ImaAdpcmContext context;
// 0x04
ImaAdpcmContext loop_context;
// 0x08
};
struct InfoBlock
{
// 0x00
BlockHeader header;
// 0x08
InfoBlockBody body;
// 0x20
};
#pragma pack(pop)
} // namespace ntd::n3ds::bcwav
}} // namespace ntd::n3ds
+260
View File
@@ -0,0 +1,260 @@
#pragma once
#include <tc/types.h>
#include <array>
#include <ntd/n3ds/ncch.h>
namespace ntd { namespace n3ds {
#pragma pack(push,8)
/**
* @struct NcsdCommonHeader
* @brief NCSD header is used in both NAND storage and Gamecard Images
*/
struct NcsdCommonHeader
{
static const uint32_t kStructMagic = tc::bn::make_struct_magic_uint32("NCSD");
static const size_t kPartitionNum = 8;
enum PartitionFsType
{
PartitionFsType_None = 0,
PartitionFsType_Normal = 1,
PartitionFsType_FIRM = 3,
PartitionFsType_AGBSave = 4,
};
enum PartitionCryptoType
{
PartitionCryptoType_None = 0x00,
PartitionCryptoType_TWL = 0x01,
PartitionCryptoType_CTR = 0x02,
PartitionCryptoType_SNAKE = 0x03,
};
enum MediaPlatform : byte_t
{
MediaPlatform_CTR = 0,
MediaPlatform_SNAKE = 1,
};
enum MediaType : byte_t
{
MediaType_InnerDevice = 0,
MediaType_Card1 = 1,
MediaType_Card2 = 2,
MediaType_ExtendedDevice = 3,
};
enum FlagIndex
{
FlagIndex_Platform = 4,
FlagIndex_Type = 5,
FlagIndex_BlockSizeLog = 6,
};
struct NcsdFlags
{
tc::bn::pad<4> reserved;
tc::bn::bitarray<1> media_platform;
byte_t media_type;
byte_t block_size_log; // this flag determines the block_size = 1 << (block_size_log + 9), alternatively this is always 0, and the blocksize is always 0x200
tc::bn::bitarray<1> reserved_07;
// raw byte access
byte_t& operator[](size_t index) { return ((byte_t*)this)[index]; }
const byte_t& operator[](size_t index) const { return ((const byte_t*)this)[index]; }
const byte_t* data() const { return ((const byte_t*)this); }
const size_t size() const { return sizeof(uint64_t); }
};
static_assert(sizeof(NcsdFlags) == 8, "NcsdFlags had incorrect size.");
struct OffsetSize
{
tc::bn::le32<uint32_t> blk_offset;
tc::bn::le32<uint32_t> blk_size;
};
static_assert(sizeof(OffsetSize) == 8, "OffsetSize had incorrect size.");
struct GameCardExtendedHeader
{
// 0x90
std::array<tc::bn::le64<uint64_t>, kPartitionNum> partition_id;
// 0xD0
tc::bn::pad<0x30> reserved;
};
struct NandExtendedHeader
{
// 0x90
tc::bn::pad<0x70> reserved;
};
// 0x00
tc::bn::le32<uint32_t> struct_magic; // NCSD
tc::bn::le32<uint32_t> image_blk_size;
tc::bn::le64<uint64_t> title_id;
// 0x10
std::array<byte_t, kPartitionNum> partition_fs_type;
std::array<byte_t, kPartitionNum> partition_crypto_type;
// 0x20
std::array<OffsetSize, kPartitionNum> partition_offsetsize;
// 0x60
/*
std::array<byte_t, 0x20> extended_header_hash; // unused
// 0x80
tc::bn::le32<uint32_t> additional_header_size; // unused
tc::bn::le32<uint32_t> sector0_offset; // unused
*/
tc::bn::pad<0x28> reserved_0x60;
// 0x88
NcsdFlags flags;
// 0x90
union {
GameCardExtendedHeader card_ext;
NandExtendedHeader nand_ext;
};
};
static_assert(sizeof(NcsdCommonHeader) == 0x100, "NcsdCommonHeader had incorrect size.");
struct CciHeader
{
enum CardDevice : byte_t
{
CardDevice_Unspecified = 0,
CardDevice_NorFlash = 1,
CardDevice_None = 2,
CardDevice_BT = 3,
};
enum PartitionIndex : byte_t
{
PartitionIndex_Application = 0,
PartitionIndex_Manual = 1,
PartitionIndex_DlpChild = 2,
PartitionIndex_SnakeCup = 6,
PartitionIndex_CtrCup = 7,
};
enum RomSize : uint32_t
{
RomSize_128MB = 0x40000,
RomSize_256MB = 0x80000,
RomSize_512MB = 0x100000,
RomSize_1GB = 0x200000,
RomSize_2GB = 0x400000,
RomSize_4GB = 0x800000,
};
enum NcsdFlagIndex
{
NcsdFlagIndex_BackupWriteWaitTime = 0,
NcsdFlagIndex_BackupSecurityVersion = 1,
NcsdFlagIndex_CardInfo = 2,
NcsdFlagIndex_CardDevice = 3,
NcsdFlagIndex_MediaPlatform = 4,
NcsdFlagIndex_MediaType = 5,
NcsdFlagIndex_MediaBlockSize = 6,
NcsdFlagIndex_CardDevice_Deprecated = 7,
};
enum CardType
{
CardType_S1 = 0,
CardType_S2 = 1,
};
enum CryptoType
{
CryptoType_Secure0 = 0, // Secure initial data key (keyX bootrom, keyY initial data seed) (used in production ROMs)
CryptoType_Secure1 = 1, // Secure initial data key (keyX bootrom, keyY initial data seed) (used in production ROMs)
CryptoType_Secure2 = 2, // Secure initial data key (keyX bootrom, keyY initial data seed) (used in production ROMs)
CryptoType_FixedKey = 3, // Zeros initial data key (used in non-HSM enviroments like development)
};
struct CardInfo
{
struct Flag
{
byte_t reserved : 5;
byte_t card_type : 1; // see CardType
byte_t crypto_type : 2; // see CryptoType
};
tc::bn::le32<uint32_t> writable_region; // offset in blocks
tc::bn::pad<3> padding;
Flag flag;
tc::bn::pad<0xf8> reserved;
};
static_assert(sizeof(CardInfo) == 0x100, "CardInfo had incorrect size.");
struct MasteringMetadata
{
tc::bn::le64<uint64_t> media_size_used;
tc::bn::pad<0x8> padding0;
tc::bn::le16<uint16_t> title_version;
tc::bn::le16<uint16_t> card_revision;
tc::bn::pad<0xc> padding1;
tc::bn::le64<uint64_t> cver_title_id;
tc::bn::le16<uint16_t> cver_title_version;
tc::bn::pad<0x6> padding2;
tc::bn::pad<0xd0> reserved;
};
static_assert(sizeof(MasteringMetadata) == 0x100, "MasteringMetadata had incorrect size.");
struct InitialData
{
std::array<byte_t, 16> key_source;
std::array<byte_t, 16> encrypted_title_key;
std::array<byte_t, 16> mac;
std::array<byte_t, 12> nonce;
tc::bn::pad<4> padding;
tc::bn::pad<0xc0> reserved;
};
static_assert(sizeof(InitialData) == 0x100, "InitialData had incorrect size.");
struct CardDeviceInfo
{
tc::bn::pad<0x200> card_device_reserved_0;
std::array<byte_t, 16> title_key;
tc::bn::pad<0xf0> card_device_reserved_1;
};
static_assert(sizeof(InitialData) == 0x100, "InitialData had incorrect size.");
public:
// 0x0000 - 0x00ff: RSA2048-PKCS1-SHA2-256 over 0x0100-0x01ff
std::array<byte_t, 0x100> signature;
// 0x0100 - 0x01ff: NcsdCommonHeader
NcsdCommonHeader ncsd_header;
// 0x0200 - 0x02ff: CardInfo
CardInfo card_info;
// 0x0300 - 0x03ff: Mastering metadata
MasteringMetadata mastering_info;
// 0x0400 - 0x0fff: Reserved
tc::bn::pad<0xc00> reserved_00;
// 0x1000 - 0x10ff: Initial Data
InitialData initial_data;
// 0x1100 - 0x11ff: Partition0 NcchCommonHeader
NcchCommonHeader ncch_header;
// 0x1200 - 0x14ff: Contains decrypted titlekey for programming cards
CardDeviceInfo card_device_info;
// 0x1500 - 0x3fff: Reserved
tc::bn::pad<0x2B00> reserved_01;
};
static_assert(sizeof(CciHeader) == 0x4000, "CciHeader had incorrect size.");
#pragma pack(pop)
}} // namespace ntd::n3ds
+61
View File
@@ -0,0 +1,61 @@
#pragma once
#include <tc/types.h>
namespace ntd { namespace n3ds {
#pragma pack(push,1)
struct CiaHeader
{
static const size_t kCiaMaxContentNum = 0x10000;
static const size_t kCiaSectionAlignment = 64;
static const size_t kCiaContentAlignment = 16;
enum Type : uint16_t
{
Type_Normal = 0x0
};
enum FormatVersion : uint16_t
{
FormatVersion_Default = 0x0,
FormatVersion_SimpleCia = 0xFF,
};
tc::bn::le32<uint32_t> header_size;
tc::bn::le16<uint16_t> type;
tc::bn::le16<uint16_t> format_version;
tc::bn::le32<uint32_t> certificate_size;
tc::bn::le32<uint32_t> ticket_size;
tc::bn::le32<uint32_t> tmd_size;
tc::bn::le32<uint32_t> footer_size;
tc::bn::le64<uint64_t> content_size;
tc::bn::bitarray<kCiaMaxContentNum/8, true, false> content_bitarray;
//std::array<byte_t, kCiaMaxContentNum/8> content_bitarray;
};
static_assert(sizeof(CiaHeader) == 0x2020, "CiaHeader had invalid size");
/**
* @struct CiaFooter
* @brief This is an optional section of a CIA file that includes "Lot Check" metadata for CTR Titles (Not TWL titles).
*
* @details
* The CIA footer contains the following metadata
* * List of title dependencies (titleids)
* * The CoreVersion (titleid lower of the target firmware)
* * (Optionally) SystemMenuData blob
*/
struct CiaFooter
{
static const size_t kMaxDependencyNum = 48;
std::array<tc::bn::le64<uint64_t>, kMaxDependencyNum> dependency_list;
tc::bn::pad<0x180> padding0;
tc::bn::le32<uint32_t> firmware_title_id_lower; // aka "core version"
tc::bn::pad<0xfc> padding1;
};
static_assert(sizeof(CiaFooter) == 0x400, "CiaFooter had invalid size");
#pragma pack(pop)
}} // namespace ntd::n3ds
+160
View File
@@ -0,0 +1,160 @@
#pragma once
#include <tc/types.h>
namespace ntd { namespace n3ds {
#pragma pack(push,1)
struct CroHeader
{
static const uint32_t kStructMagic = tc::bn::make_struct_magic_uint32("CRO0");
struct SectionEntry
{
tc::bn::le32<uint32_t> offset;
union
{
tc::bn::le32<uint32_t> size;
tc::bn::le32<uint32_t> num;
};
};
static_assert(sizeof(SectionEntry) == 0x8, "SectionEntry had invalid size");
using Hash = std::array<byte_t, 0x20>;
enum CoreFunctionIndex
{
CoreFunctionIndex_nnroControlObject_ = 0, // 0xFFFFFFFF in CRS
CoreFunctionIndex_OnLoad = 1, // will be called when the module is initialized. Set to 0xFFFFFFFF if not exists.
CoreFunctionIndex_OnExit = 2, // will be called when the module is finalized. Set to 0xFFFFFFFF if not exists.
CoreFunctionIndex_OnUnresolved = 3, // will be called when an unresolved function is called. Set to 0xFFFFFFFF if not exists.
};
enum SectionIndex
{
SectionIndex_Code = 0,
SectionIndex_Data = 1,
SectionIndex_ModuleName = 2,
SectionIndex_SegmentTable = 3, // (size = num*12)
SectionIndex_NamedExportTable = 4, // (size = num * 8)
SectionIndex_IndexedExportTable = 5, // (size = num * 4)
SectionIndex_ExportStrings = 6,
SectionIndex_ExportTree = 7, // fast lookups based on a trie-like structure, (size = num * 8)
SectionIndex_ImportModuleTable = 8, // (size = num * 20)
SectionIndex_ImportPatches = 9, // (size = num * 12)
SectionIndex_NamedImportTable = 10, // (size = num * 8)
SectionIndex_IndexedImportTable = 11, // (size = num * 8)
SectionIndex_AnonymousImportTable = 12, // (size = num * 8)
SectionIndex_ImportStrings = 13,
SectionIndex_Unk14 = 14,
SectionIndex_RelocationPatches = 15, // (size = num * 12)
SectionIndex_Unk16 = 16
};
std::array<Hash, 4> hash_table;
tc::bn::le32<uint32_t> struct_magic;
tc::bn::le32<uint32_t> name_offset;
tc::bn::le32<uint32_t> node0; // Next loaded CRO pointer, set by RO during loading (Usually zero when the CRO is being loaded)
tc::bn::le32<uint32_t> node1; // Previous loaded CRO pointer, set by RO during loading
tc::bn::le32<uint32_t> file_size;
tc::bn::le32<uint32_t> bss_size;
tc::bn::le32<uint32_t> unk0;
tc::bn::le32<uint32_t> unk1;
std::array<tc::bn::le32<uint32_t>, 4> core_function_segment_offset;
std::array<SectionEntry, 17> section;
};
static_assert(sizeof(CroHeader) == 0x138, "CroHeader had invalid size");
struct CroSegmentOffset
{
uint32_t segment_index : 4; // Segment index for table
uint32_t segment_offset : 28; // Offset into segment
};
static_assert(sizeof(CroSegmentOffset) == 0x4, "CroSegmentOffset had invalid size");
struct CroSegmentTableEntry
{
enum SegmentId : uint32_t
{
SegmentId_Text = 0,
SegmentId_RoData = 1,
SegmentId_Data = 2,
SegmentId_Bss = 4
};
tc::bn::le32<uint32_t> segment_offset;
tc::bn::le32<uint32_t> segment_size;
tc::bn::le32<SegmentId> segment_id;
};
static_assert(sizeof(CroSegmentTableEntry) == 0xC, "CroSegmentTableEntry had invalid size");
struct CroNamedExportTableEntry
{
tc::bn::le32<uint32_t> name_offset;
tc::bn::le32<CroSegmentOffset> segment_offset_for_export;
};
static_assert(sizeof(CroNamedExportTableEntry) == 0x8, "CroNamedExportTableEntry had invalid size");
struct CroIndexedExportTableEntry
{
tc::bn::le32<CroSegmentOffset> segment_offset_for_export;
};
static_assert(sizeof(CroIndexedExportTableEntry) == 0x4, "CroIndexedExportTableEntry had invalid size");
struct CroNamedImportTableEntry
{
tc::bn::le32<uint32_t> name_offset;
tc::bn::le32<uint32_t> import_patch_list_offset; // Offset of the head of a linear list that contains the patches for this import
};
static_assert(sizeof(CroNamedImportTableEntry) == 0x8, "CroNamedImportTableEntry had invalid size");
struct CroIndexedImportTableEntry
{
tc::bn::le32<uint32_t> export_symbol_index; // index of the export symbol
tc::bn::le32<uint32_t> import_patch_list_offset; // Offset of the head of a linear list that contains the patches for this import
};
static_assert(sizeof(CroIndexedImportTableEntry) == 0x8, "CroIndexedImportTableEntry had invalid size");
struct CroAnonynousImportTableEntry
{
tc::bn::le32<CroSegmentOffset> export_symbol_segment_offset;
tc::bn::le32<uint32_t> import_patch_list_offset; // Offset of the head of a linear list that contains the patches for this import
};
static_assert(sizeof(CroAnonynousImportTableEntry) == 0x8, "CroAnonynousImportTableEntry had invalid size");
struct CroImportModuleTableEntry
{
tc::bn::le32<uint32_t> module_name_offset;
tc::bn::le32<uint32_t> indexed_import_num;
tc::bn::le32<uint32_t> indexed_import_patch_list_offset; // Offset of the head of a sub list in Indexed Import Table
tc::bn::le32<uint32_t> anonynous_import_num;
tc::bn::le32<uint32_t> anonynous_import_patch_list_offset; // Offset of the head of a sub list in Anonymous Import Table
};
static_assert(sizeof(CroImportModuleTableEntry) == 0x14, "CroImportModuleTableEntry had invalid size");
struct CroPatchEntry
{
enum PatchType : byte_t
{
PatchType_Ignore = 0,
PatchType_WriteU32Absolute = 2, // (base+addend)
PatchType_WriteU32Relative = 3, // (base+addend-in_ptr)
PatchType_ThumbBranch = 10,
PatchType_ARM32Branch = 28,
PatchType_ModifyARM32BranchOffset = 29,
PatchType_WriteU32Absolute_2 = 38, // duplicate of PatchType_WriteU32Absolute
PatchType_WriteU32Relative_2 = 42, // (((signed int)base*2)/2+addend-in_ptr), otherwise err) (This is apparently a subset of relocation type for ARM ELF)
};
tc::bn::le32<CroSegmentOffset> segment_offset_for_output;
PatchType patch_type;
byte_t unk0; // For import patches, non-zero if last entry; for relocation patches, this is the referred segment index
byte_t unk1; // For import patches, 1 is written to first entry if all symbols loaded successfully; unknown (padding?) for relocation patches
byte_t unk2; // Unknown (padding?)
tc::bn::le32<uint32_t> addend;
};
static_assert(sizeof(CroPatchEntry) == 0xC, "CroPatchEntry had invalid size");
#pragma pack(pop)
}} // namespace ntd::n3ds
+48
View File
@@ -0,0 +1,48 @@
#pragma once
#include <tc/types.h>
namespace ntd { namespace n3ds {
#pragma pack(push,1)
struct CrrHeader
{
static const uint32_t kStructMagic = tc::bn::make_struct_magic_uint32("CRR0");
struct Certificate
{
tc::bn::le32<uint32_t> unique_id_mask;
tc::bn::le32<uint32_t> unique_id_pattern;
tc::bn::pad<0x18> reserved0;
std::array<byte_t, 0x100> crr_body_public_key;
std::array<byte_t, 0x100> signature; // PKCS1-RSA2048-SHA2-256 over 0x000-0x11f
};
static_assert(sizeof(Certificate) == 0x220, "Certificate had invalid size");
tc::bn::le32<uint32_t> struct_magic;
tc::bn::pad<4> reserved0;
tc::bn::le32<uint32_t> node0; // prev CRR (0 in file, set by RO module when loaded in memory)
tc::bn::le32<uint32_t> node1; // next CRR (0 in file, set by RO module when loaded in memory)
tc::bn::le32<int32_t> debug_info_offset;
tc::bn::le32<int32_t> debug_info_size;
tc::bn::pad<8> reserved1;
Certificate body_certificate;
};
static_assert(sizeof(CrrHeader) == 0x240, "CrrHeader had invalid size");
struct CrrBodyHeader
{
std::array<byte_t, 0x100> signature; // PKCS1-RSA2048-SHA2-256 over 0x000 - end of hashes (according to 3dbrew a fixed size of 0x358 bytes). CRR0 files must be stored under "romfs:/.crr/". The end of the file is aligned to a 0x1000-byte boundary with 0xCC bytes.
tc::bn::le32<uint32_t> unique_id;
tc::bn::le32<uint32_t> size;
tc::bn::pad<8> reserved0;
tc::bn::le32<uint32_t> hash_offset;
tc::bn::le32<uint32_t> num_hash;
tc::bn::le32<uint32_t> module_id_offset;
tc::bn::le32<uint32_t> module_id_size;
};
static_assert(sizeof(CrrBodyHeader) == 0x120, "CrrBodyHeader had invalid size");
#pragma pack(pop)
}} // namespace ntd::n3ds
+5
View File
@@ -0,0 +1,5 @@
#pragma once
//#include <ntd/n3ds/es/Certficate.h>
#include <ntd/n3ds/es/Ticket.h>
#include <ntd/n3ds/es/TitleMetaData.h>
+117
View File
@@ -0,0 +1,117 @@
#pragma once
#include <bitset>
#include <tc/types.h>
#include <tc/io/IStream.h>
#include <tc/crypto/RsaKey.h>
#include <brd/es/es_cert.h>
#include <ntd/n3ds/es/Signature.h>
#include <tc/ArgumentNullException.h>
#include <tc/ArgumentOutOfRangeException.h>
#include <tc/InvalidOperationException.h>
namespace ntd { namespace n3ds { namespace es {
/**
* @brief Get total size of certificate, from raw certificate blob.
*
* @param[in] data Raw certificate data.
*
* @return Size in bytes of certificate data, 0 if invalid data.
*/
size_t getCertificateSize(byte_t* data);
/**
* @brief Get total size of certificate signature structure, from raw certificate blob.
*
* @param[in] data Raw certificate data.
*
* @return Size in bytes of certificate signature structure, 0 if invalid data.
*/
size_t getCertificateSignatureSize(byte_t* data);
/**
* @brief Get offset of certificate signed data, from raw certificate blob.
*
* @param[in] data Raw certificate data.
*
* @return Offset in bytes of signed certificate data, 0 if invalid data.
*/
size_t getCertificateSignableOffset(byte_t* data);
/**
* @brief Get size of certificate signed data, from raw certificate blob.
*
* @param[in] data Raw certificate data.
*
* @return Size in bytes of signed certificate data, 0 if invalid data.
*/
size_t getCertificateSignableSize(byte_t* data);
/**
* @brief Get pointer to certificate header, from raw certificate blob.
*
* @param[in] data Raw certificate data.
*
* @return Void pointer to certificate header. See @ref ntd::es::ESCertHeader.
*/
void* getCertificateHeaderPtr(byte_t* data);
/**
* @brief Get pointer to certificate public key, from raw certificate blob.
*
* @param[in] data Raw certificate data.
*
* @return Void pointer to certificate public. See @ref ntd::es::ESCertRsa4096PublicKey @ref ntd::es::ESCertRsa2048PublicKey @ref ntd::es::ESCertEcc233PublicKey.
*/
void* getCertificatePublicKeyPtr(byte_t* data);
struct Certificate
{
public:
Certificate() :
signature(),
subject(),
date(),
public_key_type(),
rsa4096_public_key(),
rsa2048_public_key(),
ecc233_public_key()
{
memset(calculated_hash.data(), 0, calculated_hash.size());
}
public:
// these fields are only used when deserialised
ntd::n3ds::es::Signature signature; // This includes the signature type, signature data, and issuer
std::array<byte_t, 32> calculated_hash; // This hash is calculated when deserialised so that signature validation can be performed.
// these fields are used in both deserialisation & serialisation
std::string subject;
uint32_t date; // 32bit unix timestamp, not always set
// public key
brd::es::ESCertPubKeyType public_key_type;
brd::es::Rsa4096PublicKey rsa4096_public_key;
brd::es::Rsa2048PublicKey rsa2048_public_key;
brd::es::Ecc233PublicKey ecc233_public_key;
};
class CertificateDeserialiser : public Certificate
{
public:
// cert stream
CertificateDeserialiser(const std::shared_ptr<tc::io::IStream>& cert_stream);
private:
std::string mModuleLabel;
};
/*
class CertificateSerialiser : public tc::io::IStream
{
public:
CertificateSerialiser();
private:
std::string mModuleLabel;
}
*/
}}} // namespace ntd::n3ds::es
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include <bitset>
#include <tc/types.h>
#include <brd/es/es_sign.h>
namespace ntd { namespace n3ds { namespace es {
class ISigner
{
public:
virtual ~ISigner() = default;
virtual const std::string& getIssuer() = 0;
virtual brd::es::ESSigType getSigType() = 0;
virtual bool signHash(const byte_t* hash, byte_t* signature) = 0;
virtual bool verifyHash(const byte_t* hash, const byte_t* signature) = 0;
};
}}} // namespace ntd::n3ds::es
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#include <bitset>
#include <tc/types.h>
#include <tc/crypto/RsaKey.h>
#include <ntd/n3ds/es/ISigner.h>
#include <tc/InvalidOperationException.h>
#include <tc/ArgumentOutOfRangeException.h>
namespace ntd { namespace n3ds { namespace es {
class RsaSigner : public ntd::n3ds::es::ISigner
{
public:
RsaSigner(brd::es::ESSigType sig_type, const std::string& issuer, const tc::crypto::RsaKey& rsa_key);
const std::string& getIssuer();
brd::es::ESSigType getSigType();
bool signHash(const byte_t* hash, byte_t* signature);
bool verifyHash(const byte_t* hash, const byte_t* signature);
private:
brd::es::ESSigType mSigType;
std::string mIssuer;
tc::crypto::RsaKey mRsaKey;
};
}}} // namespace ntd::n3ds::es
+50
View File
@@ -0,0 +1,50 @@
#pragma once
#include <bitset>
#include <tc/types.h>
#include <tc/io/IStream.h>
#include <tc/crypto/RsaKey.h>
#include <brd/es/es_sign.h>
#include <tc/ArgumentNullException.h>
#include <tc/ArgumentOutOfRangeException.h>
#include <tc/InvalidOperationException.h>
namespace ntd { namespace n3ds { namespace es {
size_t getSignatureSizeFromSigType(brd::es::ESSigType sig_type);
size_t getSignatureIssuerOffset(brd::es::ESSigType sig_type);
struct Signature
{
public:
Signature() :
sig_type(),
sig(),
issuer()
{}
public:
brd::es::ESSigType sig_type;
tc::ByteData sig;
std::string issuer;
};
class SignatureDeserialiser : public Signature
{
public:
// input stream
SignatureDeserialiser(const std::shared_ptr<tc::io::IStream>& stream);
private:
std::string mModuleLabel;
};
/*
class SignatureSerialiser : public tc::io::IStream
{
public:
SignatureSerialiser();
private:
std::string mModuleLabel;
}
*/
}}} // namespace ntd::n3ds::es
+72
View File
@@ -0,0 +1,72 @@
#pragma once
#include <bitset>
#include <tc/types.h>
#include <tc/io/IStream.h>
#include <ntd/n3ds/es/Signature.h>
#include <tc/ArgumentNullException.h>
#include <tc/ArgumentOutOfRangeException.h>
#include <tc/InvalidOperationException.h>
namespace ntd { namespace n3ds { namespace es {
struct Ticket
{
public:
Ticket() :
signature(),
ticket_id(0),
device_id(0),
title_id(0),
ticket_version(0),
license_type(0),
key_id(0),
ec_account_id(0),
launch_count(0),
enabled_content()
{
memset(calculated_hash.data(), 0, calculated_hash.size());
memset(title_key.data(), 0, title_key.size());
}
public:
// these fields are only used in deserialisation
ntd::n3ds::es::Signature signature;
std::array<byte_t, 32> calculated_hash;
// these fields are used in both deserialisation & serialisation
std::array<byte_t, 16> title_key;
uint64_t ticket_id;
uint32_t device_id;
uint64_t title_id;
uint16_t ticket_version;
byte_t license_type;
byte_t key_id;
// reserved region data
uint32_t ec_account_id;
// lp record
uint32_t launch_count; // 0 = unlimited, x = limited to x launches
std::bitset<0x10000> enabled_content;
};
class TicketDeserialiser : public Ticket
{
public:
// tik stream
TicketDeserialiser(const std::shared_ptr<tc::io::IStream>& tik_stream);
private:
std::string mModuleLabel;
};
/*
class TicketSerialiser : public tc::io::IStream
{
public:
// tmd Ticket, issuer, RsaKey
TicketSerialiser();
private:
std::string mModuleLabel;
}
*/
}}} // namespace ntd::n3ds::es
@@ -0,0 +1,106 @@
#pragma once
#include <tc/types.h>
#include <tc/io/IStream.h>
#include <ntd/n3ds/es/Signature.h>
#include <tc/ArgumentNullException.h>
#include <tc/ArgumentOutOfRangeException.h>
#include <tc/InvalidOperationException.h>
namespace ntd { namespace n3ds { namespace es {
struct TitleMetaData
{
public:
TitleMetaData() :
signature(),
title_id(0),
title_version(0),
content_info()
{
memset(calculated_hash.data(), 0, calculated_hash.size());
twl_custom_data.public_save_data_size = 0;
twl_custom_data.private_save_data_size = 0;
twl_custom_data.flag = 0;
ctr_custom_data.save_data_size = 0;
ctr_custom_data.is_snake_only = false;
}
struct ContentInfo
{
public:
ContentInfo() :
id(0),
index(0),
is_encrypted(0),
is_optional(0),
size(0)
{
memset(hash.data(), 0, hash.size());
}
ContentInfo(uint32_t id, uint16_t index, bool is_encrypted, bool is_optional, int64_t size, std::array<byte_t, 32>& hash) :
id(id),
index(index),
is_encrypted(is_encrypted),
is_optional(is_optional),
size(size),
hash(hash)
{
}
public:
uint32_t id;
uint16_t index;
bool is_encrypted;
bool is_optional;
int64_t size;
std::array<byte_t, 32> hash;
};
public:
// these fields are only used in deserialisation
ntd::n3ds::es::Signature signature;
std::array<byte_t, 32> calculated_hash;
// these fields are used in both deserialisation & serialisation
uint64_t title_id;
uint16_t title_version;
struct TwlCustomData
{
uint32_t public_save_data_size;
uint32_t private_save_data_size;
uint8_t flag;
} twl_custom_data;
struct CtrCustomData
{
uint32_t save_data_size;
bool is_snake_only;
} ctr_custom_data;
std::vector<ContentInfo> content_info;
};
class TitleMetaDataDeserialiser : public TitleMetaData
{
public:
// tmd stream
TitleMetaDataDeserialiser(const std::shared_ptr<tc::io::IStream>& tmd_stream);
private:
TitleMetaDataDeserialiser();
std::string mModuleLabel;
};
/*
class TitleMetaDataSerialiser : public tc::io::IStream
{
public:
// tmd TitleMetaData, issuer, RsaKey
TitleMetaDataSerialiser();
private:
std::string mModuleLabel;
}
*/
}}} // namespace ntd::n3ds::es
+33
View File
@@ -0,0 +1,33 @@
#pragma once
#include <tc/types.h>
namespace ntd { namespace n3ds {
#pragma pack(push,1)
struct ExeFsHeader
{
static const size_t kFileNum = 8;
static const size_t kExeFsSectionAlignSize = 0x200;
struct FileEntry
{
tc::bn::string<8> name;
tc::bn::le32<uint32_t> offset;
tc::bn::le32<uint32_t> size;
};
using FileHash = std::array<byte_t, 0x20>;
std::array<FileEntry, kFileNum> file_table;
tc::bn::pad<0x80> reserved;
std::array<FileHash, kFileNum> hash_table;
// inline method to get pointer to file hash, since the ordering is unintuitive
FileHash* getFileHash(size_t index) { return (index < kFileNum) ? &hash_table[kFileNum - 1 - index] : nullptr; }
};
static_assert(sizeof(ExeFsHeader) == 0x200, "ExeFsHeader had invalid size");
#pragma pack(pop)
}} // namespace ntd::n3ds
+317
View File
@@ -0,0 +1,317 @@
#pragma once
#include <tc/types.h>
namespace ntd { namespace n3ds {
#pragma pack(push,1)
struct SystemControlInfo
{
enum Flags
{
Flags_CompressExefsPartition0 = 0,
Flags_SdmcApplication = 1,
};
struct CodeSegmentInfo
{
tc::bn::le32<uint32_t> address;
tc::bn::le32<uint32_t> num_max_pages;
tc::bn::le32<uint32_t> code_size;
};
static_assert(sizeof(CodeSegmentInfo) == 0xC, "CodeSegmentInfo had incorrect size.");
public:
tc::bn::string<8> name;
tc::bn::pad<5> padding0;
union
{
tc::bn::bitarray<1> bitarray;
byte_t raw;
} flags;
tc::bn::le16<uint16_t> remaster_version;
CodeSegmentInfo text;
tc::bn::le32<uint32_t> stack_size;
CodeSegmentInfo rodata;
tc::bn::pad<4> padding1;
CodeSegmentInfo data;
tc::bn::le32<uint32_t> bss_size;
std::array<tc::bn::le64<uint64_t>, 0x30> dependency_list;
tc::bn::le64<uint64_t> savedata_size;
tc::bn::le64<uint64_t> jump_id;
tc::bn::pad<0x30> padding2;
};
static_assert(sizeof(SystemControlInfo) == 0x200, "SystemControlInfo had incorrect size.");
struct AccessControlInfo
{
enum CpuSpeed
{
CpuSpeed_268MHz = 0,
CpuSpeed_804MHz = 1,
};
enum SystemMode : byte_t
{
SystemMode_PROD = 0, // For Apps 64MB memory
SystemMode_DEV1 = 2, // For Apps 96MB memory
SystemMode_DEV2 = 3, // For Apps 80MB memory
SystemMode_DEV3 = 4, // For Apps 72MB memory
SystemMode_DEV4 = 5, // For Apps 32MB memory
};
enum SystemModeExt : byte_t
{
SystemModeExt_LEGACY = 0, // Use SystemMode setting
SystemModeExt_PROD = 1, // For Apps 124MB memory
SystemModeExt_DEV1 = 2, // For Apps 178MB memory
//SystemModeExt_DEV2 = 3, // Unknown App memory allocation (devunit only?, doesn't exist?)
};
enum FileSystemAccess
{
FileSystemAccess_CategorySystemApplication,
FileSystemAccess_CategoryHardwareCheck,
FileSystemAccess_CategoryFileSystemTool,
FileSystemAccess_Debug,
FileSystemAccess_TwlCardBackup,
FileSystemAccess_TwlNandData,
FileSystemAccess_Boss,
FileSystemAccess_DirectSdmc,
FileSystemAccess_Core,
FileSystemAccess_CtrNandRo,
FileSystemAccess_CtrNandRw,
FileSystemAccess_CtrNandRoWrite,
FileSystemAccess_CategorySystemSettings,
FileSystemAccess_CardBoard,
FileSystemAccess_ExportImportIvs,
FileSystemAccess_DirectSdmcWrite,
FileSystemAccess_SwitchCleanup,
FileSystemAccess_SaveDataMove,
FileSystemAccess_Shop,
FileSystemAccess_Shell,
FileSystemAccess_CategoryHomeMenu,
FileSystemAccess_ExternalSeed,
};
enum OtherAttribute
{
OtherAttribute_NotUseRomFs,
OtherAttribute_UseExtendedSavedataAccessControl,
};
enum ResourceLimitDescriptorIndex
{
ResourceLimitDescriptorIndex_MaxCpu = 0,
};
enum ResourceLimitCategory : byte_t
{
ResourceLimitCategory_Application = 0,
ResourceLimitCategory_SysApplet = 1,
ResourceLimitCategory_LibApplet = 2,
ResourceLimitCategory_Other = 3
};
struct Flags
{
uint32_t enable_l2_cache : 1; // 0=disable, 1=enable
uint32_t cpu_speed : 1; // CpuSpeed
uint32_t : 6;
uint32_t system_mode_ext : 4; // SystemModeExt
uint32_t : 4;
uint32_t ideal_processor : 2; // 0-3, default=0
uint32_t affinity_mask : 2; // 0-3, default=0
uint32_t system_mode : 4; // SystemMode
int32_t thread_priority : 8;
};
static_assert(sizeof(Flags) == sizeof(uint32_t), "Flags had incorrect size.");
// the reason for the reverse ordering is because the normal serialization of this field is: "field |= save_id", then "field = field << 20"
struct AccessibleUniqueIds
{
uint64_t save_id2 : 20;
uint64_t save_id1 : 20;
uint64_t save_id0 : 20;
uint64_t flag : 1;
uint64_t : 0;
};
static_assert(sizeof(AccessibleUniqueIds) == sizeof(uint64_t), "AccessibleUniqueIds had incorrect size.");
using ServiceName = tc::bn::string<8>; // Those char[8] server names
enum DescriptorPrefix
{
DescriptorPrefix_InterruptNumList = 3,
DescriptorPrefix_SysCallList = 4,
DescriptorPrefix_ReleaseKernelVersion = 6,
DescriptorPrefix_HandleTableSize = 7,
DescriptorPrefix_OtherCapabilities = 8,
DescriptorPrefix_MappingStatic = 9,
DescriptorPrefix_MappingIo = 11
};
static const size_t kMaxSerializableInterruptCount = 32; // number of serializable interupts
static const size_t kMaxInterruptValue = 0x80; // system maximum total interupts
static const size_t kMaxInterruptDescriptorNum = 8;
struct InterruptDescriptor
{
uint32_t interrupt_0 : 7;
uint32_t interrupt_1 : 7;
uint32_t interrupt_2 : 7;
uint32_t interrupt_3 : 7;
uint32_t padding_bit : 1; // zero
uint32_t prefix_bits : 3; // all ones
};
static_assert(sizeof(InterruptDescriptor) == sizeof(uint32_t), "InterruptDescriptor had incorrect size.");
static const size_t kMaxSerializableSystemCallCount = 0xC0; // this is the physical maximum storable system calls
static const size_t kMaxSystemCallValue = 0x7d; // this is the maximum value for a system call according to the system
static const size_t kMaxSystemCallDescriptorNum = 8;
struct SystemCallDescriptor
{
uint32_t systemcall_lower_bitarray : 24;
uint32_t systemcall_upper : 3;
uint32_t padding_bit : 1; // zero
uint32_t prefix_bits : 4; // all ones
};
static_assert(sizeof(SystemCallDescriptor) == sizeof(uint32_t), "SystemCallDescriptor had incorrect size.");
struct ReleaseKernelVersionDescriptor
{
uint32_t version : 16;
uint32_t : 9;
uint32_t padding_bit : 1; // zero
uint32_t prefix_bits : 6; // all ones
};
static_assert(sizeof(ReleaseKernelVersionDescriptor) == sizeof(uint32_t), "ReleaseKernelVersionDescriptor had incorrect size.");
struct HandleTableSizeDescriptor
{
uint32_t size : 16;
uint32_t : 8;
uint32_t padding_bit : 1; // zero
uint32_t prefix_bits : 7; // all ones
};
static_assert(sizeof(HandleTableSizeDescriptor) == sizeof(uint32_t), "HandleTableSizeDescriptor had incorrect size.");
enum MemoryType
{
MemoryType_Application = 1,
MemoryType_System = 2,
MemoryType_Base = 3
};
// KernelFlags
struct OtherCapabilitiesDescriptor
{
uint32_t permit_debug : 1;
uint32_t force_debug : 1;
uint32_t can_use_non_alphabet_and_number : 1;
uint32_t can_write_shared_page : 1;
uint32_t can_use_privileged_priority : 1;
uint32_t permit_main_function_argument : 1;
uint32_t can_share_device_memory : 1;
uint32_t runnable_on_sleep : 1;
uint32_t memory_type : 4; // MemoryType
uint32_t special_memory_layout : 1;
uint32_t can_access_core2 : 1;
uint32_t : 9;
uint32_t padding_bit : 1; // zero
uint32_t prefix_bits : 8; // all ones
};
static_assert(sizeof(OtherCapabilitiesDescriptor) == sizeof(uint32_t), "OtherCapabilitiesDescriptor had incorrect size.");
// MappingStaticDescriptor come in pairs: [begin_page,end_page) where begin.flag == (true: range read-only, false: range is read-write), and end.flag == (true: static mapping, false: io mapping)
struct MappingStaticDescriptor
{
uint32_t page : 20; // page2addr: addr = page << 20
uint32_t flag : 1;
uint32_t : 1;
uint32_t padding_bit : 1; // zero
uint32_t prefix_bits : 9; // all ones
};
static_assert(sizeof(MappingStaticDescriptor) == sizeof(uint32_t), "MappingStaticDescriptor had incorrect size.");
// MappingIODescriptor describe single page IO mappings, these are always read-write permissions. [begin_page)
// When a multi-page IO mapping is required, these is indicated with two MappingStaticDescriptor
struct MappingIODescriptor
{
uint32_t page : 20; // page2addr: addr = page << 20
uint32_t padding_bit : 1; // zero
uint32_t prefix_bits : 11; // all ones
};
static_assert(sizeof(MappingIODescriptor) == sizeof(uint32_t), "MappingIODescriptor had incorrect size.");
enum Arm9Capability
{
Arm9Capability_FsMountNand = 0,
Arm9Capability_FsMountNandRoWrite = 1,
Arm9Capability_FsMountTwln = 2,
Arm9Capability_FsMountWnand = 3,
Arm9Capability_FsMountCardSpi = 4,
Arm9Capability_UseSdif3 = 5,
Arm9Capability_CreateSeed = 6,
Arm9Capability_UseCardSpi = 7,
Arm9Capability_SdApplication = 8,
Arm9Capability_UseDirectSdmc = 9,
};
public:
// begin ARM11 System
tc::bn::le64<uint64_t> program_id;
tc::bn::le32<uint32_t> core_version;
tc::bn::le32<Flags> flags;
std::array<tc::bn::le16<uint16_t>, 16> resource_limit_descriptor; // for each descriptor use, see ResourceLimitDescriptorIndex
union
{
tc::bn::le64<uint64_t> ext_savedata_id; // this field is used when other_attributes.test(USE_EXTENDED_SAVEDATA_ACCESS_CONTROL) == false
tc::bn::le64<AccessibleUniqueIds> accessible_unique_ids_1; // this field is used when other_attributes.test(USE_EXTENDED_SAVEDATA_ACCESS_CONTROL) == true
};
std::array<tc::bn::le32<uint32_t>, 2> system_savedata_id;
tc::bn::le64<AccessibleUniqueIds> accessible_unique_ids_0; // other_attributes.test(USE_EXTENDED_SAVEDATA_ACCESS_CONTROL): true = accessible_save_ids, false: other_user_save_ids
tc::bn::bitarray<7> fs_access; // FileSystemAccess
tc::bn::bitarray<1> other_attributes; // OtherAttribute
std::array<ServiceName, 34> service_access_control; // In firmware versions prior to 9.2.0(?) this array is only 32 elements long
tc::bn::pad<0xf> padding0;
byte_t resource_limit_category; // ResourceLimitCategory
// end ARM11 System
// begin ARM11 Kernel
std::array<tc::bn::le32<uint32_t>, 28> kernel_descriptors; // Descripters are a collection of u32s, with masks to differentiate between different descs. Impl'd Order: SysCallControl, InteruptNumList, AddressMapping(ExHdr order: IoMaps, MemMaps, AccDesc order: MemMaps, IoMaps), OtherCapabilities, HandleTableSize, ReleaseKernelVersion
tc::bn::pad<0x10> padding1;
// end ARM11 Kernel
// begin ARM9
tc::bn::bitarray<0xf> arm9_access_control;
byte_t desc_version;
// end ARM9
};
static_assert(sizeof(AccessControlInfo) == 0x200, "AccessControlInfo had incorrect size.");
struct ExtendedHeader
{
SystemControlInfo system_control_info;
AccessControlInfo access_control_info;
};
static_assert(sizeof(ExtendedHeader) == 0x400, "ExtendedHeader had invalid size");
struct AccessDescriptor
{
using Rsa2048Block = std::array<byte_t, 0x100>;
Rsa2048Block signature;
Rsa2048Block ncch_rsa_modulus;
AccessControlInfo access_control_info;
};
static_assert(sizeof(AccessDescriptor) == 0x400, "AccessDescriptor had invalid size");
#pragma pack(pop)
}} // namespace ntd::n3ds
+40
View File
@@ -0,0 +1,40 @@
#pragma once
#include <tc/types.h>
namespace ntd { namespace n3ds {
#pragma pack(push,1)
struct FirmwareHeader
{
static const uint32_t kStructMagic = tc::bn::make_struct_magic_uint32("FIRM");
struct SectionHeader
{
enum CopyMethod
{
CopyMethod_NDMA = 0,
CopyMethod_XDMA = 1,
CopyMethod_MEMCPY = 2
};
tc::bn::le32<uint32_t> offset;
tc::bn::le32<uint32_t> address;
tc::bn::le32<uint32_t> size;
tc::bn::le32<uint32_t> copy_method;
std::array<byte_t, 0x20> hash; // SHA2-256 over 0x000+offset - 0x000+offset+size-1
};
tc::bn::le32<uint32_t> struct_magic; // FIRM
tc::bn::le32<uint32_t> priority;
tc::bn::le32<uint32_t> entrypoint_arm11;
tc::bn::le32<uint32_t> entrypoint_arm9;
tc::bn::pad<0x30> reserved1;
std::array<SectionHeader, 4> section;
std::array<byte_t, 0x100> signature; // RSA2048-PKCS1-SHA2-256 over 0x000-0x0ff
};
static_assert(sizeof(FirmwareHeader) == 0x200, "FirmwareHeader had invalid size");
#pragma pack(pop)
}} // namespace ntd::n3ds
+70
View File
@@ -0,0 +1,70 @@
#pragma once
#include <tc/types.h>
namespace ntd { namespace n3ds {
#pragma pack(push,1)
struct IvfcHeader
{
static const uint32_t kStructMagic = tc::bn::make_struct_magic_uint32("IVFC");
enum TypeId : uint32_t
{
TypeId_A = 0x10000, // CTR RomFS
TypeId_B = 0x20000, // CTR SaveData
};
tc::bn::le32<uint32_t> struct_magic;
tc::bn::le32<TypeId> type_id;
};
static_assert(sizeof(IvfcHeader) == 0x8, "IvfcHeader had invalid size");
struct IvfcLevelEntry
{
tc::bn::le64<uint64_t> offset;
tc::bn::le64<uint64_t> size;
tc::bn::le32<uint32_t> block_size_log2;
tc::bn::pad<4> reserved;
};
static_assert(sizeof(IvfcLevelEntry) == 0x18, "IvfcLevelEntry had invalid size");
/**
* @struct IvfcCtrRomfsHeader
* @details
* IVFC for CTR uses 4 levels (1 master, 3 auxillary)
*
* * The master level follows the IvfcCtrRomfsHeader aligned to 0x10 bytes (0x60 bytes total), for head.master_hash_size bytes, validating aux level 0
* * Aux level 0 validates aux level 1
* * Aux level 1 validates aux level 2
* * Aux level 2 is RomFS data
*/
struct IvfcCtrRomfsHeader
{
static const size_t kLevelNum = 3;
static const size_t kHeaderAlign = 0x10;
static const size_t kDefaultRomFsBlockSize = 0x1000;
IvfcHeader head;
tc::bn::le32<uint32_t> master_hash_size;
std::array<IvfcLevelEntry, kLevelNum> level;
tc::bn::le32<uint32_t> header_size; // header_size == 0x5c
tc::bn::pad<4> reserved;
};
static_assert(sizeof(IvfcCtrRomfsHeader) == 0x5C, "IvfcCtrRomfsHeader had invalid size");
struct IvfcCtrSavedataHeader
{
static const size_t kLevelNum = 4;
IvfcHeader head;
tc::bn::le64<uint64_t> master_hash_size;
std::array<IvfcLevelEntry, kLevelNum> level;
tc::bn::le32<uint32_t> header_size; // header_size == 0x78
std::array<byte_t, 4> reserved;
};
static_assert(sizeof(IvfcCtrSavedataHeader) == 0x78, "IvfcCtrSavedataHeader had invalid size");
#pragma pack(pop)
}} // namespace ntd::n3ds
+158
View File
@@ -0,0 +1,158 @@
#pragma once
#include <tc/types.h>
namespace ntd { namespace n3ds {
#pragma pack(push,4)
struct NcchCommonHeader
{
static const uint32_t kStructMagic = tc::bn::make_struct_magic_uint32("NCCH");
enum FormatVersion
{
FormatVersion_CFA = 0,
FormatVersion_CXI_PROTOTYPE = 1,
FormatVersion_CXI = 2,
};
enum FlagIndex
{
FlagIndex_SecurityVersion = 3, // this flag determines the security version for secure crypto
FlagIndex_ContentPlatform = 4,
FlagIndex_ContentTypeFlag = 5,
FlagIndex_BlockSizeLog = 6, // this flag determines the block_size = 1 << (block_size_log + 9), alternatively this is always 0, and the blocksize is always 0x200
FlagIndex_OtherFlag = 7,
};
enum ContentPlatform
{
ContentPlatform_CTR = 0,
ContentPlatform_SNAKE = 1,
};
enum FormType
{
FormType_Unassigned = 0, // invalid
FormType_SimpleContent = 1, // CFA (Non-executable data archive)
FormType_ExecutableWithoutRomFS = 2, // CXI (ExeFS Only)
FormType_Executable = 3, // CXI (ExeFS & RomFS)
};
enum ContentType
{
ContentType_Application = 0,
ContentType_SystemUpdate = 1, // CTR update
ContentType_Manual = 2,
ContentType_Child = 3,
ContentType_Trial = 4,
ContentType_ExtendedSystemUpdate = 5, // SNAKE update
};
enum OtherFlag
{
OtherFlag_FixedAesKey = 0,
OtherFlag_NoMountRomFS = 1,
OtherFlag_NoEncryption = 2,
OtherFlag_SeededAesKeyY = 5,
OtherFlag_ManualDisclosure = 6,
};
struct NcchFlags
{
struct ContentTypeFlag
{
byte_t form_type : 2;
byte_t content_type : 6;
};
static_assert(sizeof(ContentTypeFlag) == 1, "ContentTypeFlag had incorrect size.");
tc::bn::pad<3> reserved;
byte_t security_version; // this determines the secure crypto mode, where != 0 this uses a different key for: romfs, & non .icon&.banner exefs
tc::bn::bitarray<1> content_platform;
ContentTypeFlag content_flag;
byte_t block_size_log; // this flag determines the block_size = 1 << (block_size_log + 9), alternatively this is always 0, and the blocksize is always 0x200
tc::bn::bitarray<1> other_flag;
// raw byte access
byte_t& operator[](size_t index) { return ((byte_t*)this)[index]; }
const byte_t& operator[](size_t index) const { return ((const byte_t*)this)[index]; }
const byte_t* data() const { return ((const byte_t*)this); }
const size_t size() const { return sizeof(uint64_t); }
};
static_assert(sizeof(NcchFlags) == 8, "NcchFlags had incorrect size.");
// 0x00
tc::bn::le32<uint32_t> struct_magic; // NCCH
tc::bn::le32<uint32_t> content_blk_size;
tc::bn::le64<uint64_t> content_id;
// 0x10
tc::bn::string<2> maker_code;
tc::bn::le16<uint16_t> format_version;
std::array<byte_t, 4> seed_checksum;
tc::bn::le64<uint64_t> program_id;
// 0x20
tc::bn::pad<16> reserved_00;
// 0x30
std::array<byte_t, 0x20> logo_hash; // SHA-256 over the entire logo region
// 0x50
tc::bn::string<16> product_code;
// 0x60
std::array<byte_t, 0x20> exhdr_hash; // SHA-256 over exhdr_size of the exhdr region
// 0x80
tc::bn::le32<uint32_t> exhdr_size; // note that this size does not include the access_desc binary that follows the exheader
tc::bn::pad<4> reserved_01;
NcchFlags flags;
// 0x90
tc::bn::le32<uint32_t> plain_region_blk_offset;
tc::bn::le32<uint32_t> plain_region_blk_size;
tc::bn::le32<uint32_t> logo_blk_offset;
tc::bn::le32<uint32_t> logo_blk_size;
// 0xA0
tc::bn::le32<uint32_t> exefs_blk_offset;
tc::bn::le32<uint32_t> exefs_blk_size;
tc::bn::le32<uint32_t> exefs_prot_blk_size;
tc::bn::pad<4> reserved_02;
// 0xB0
tc::bn::le32<uint32_t> romfs_blk_offset;
tc::bn::le32<uint32_t> romfs_blk_size;
tc::bn::le32<uint32_t> romfs_prot_blk_size;
tc::bn::pad<4> reserved_03;
// 0xC0
std::array<byte_t, 0x20> exefs_prot_hash;
// 0xE0
std::array<byte_t, 0x20> romfs_prot_hash;
// 0x100
};
static_assert(sizeof(NcchCommonHeader) == 0x100, "NcchCommonHeader had incorrect size.");
struct NcchHeader
{
// 0x000-0x0FF : RSA2048-PKCS1-SHA2-256 signature over 0x100-0x1FF
std::array<byte_t, 0x100> signature;
// 0x100-0x1FF : NcchCommonHeader
NcchCommonHeader header;
// 0x200
};
static_assert(sizeof(NcchHeader) == 0x200, "NcchHeader had incorrect size.");
struct CxiHeader
{
// 0x000-0x0FF : RSA2048-PKCS1-SHA2-256 signature over 0x100-0x1FF
std::array<byte_t, 0x100> signature;
// 0x100-0x1FF : NcchCommonHeader
NcchCommonHeader header;
// 0x200-0x5FF : NcchExtendedHeader
std::array<byte_t, 0x400> extended_header;
// 0x600-0x9FF : NcchAccessControlExtended
std::array<byte_t, 0x400> access_ctrl_ext;
// 0xA00
};
static_assert(sizeof(CxiHeader) == 0xA00, "CxiHeader had incorrect size.");
#pragma pack(pop)
}} // namespace ntd::n3ds
+62
View File
@@ -0,0 +1,62 @@
#pragma once
#include <tc/types.h>
namespace ntd { namespace n3ds {
#pragma pack(push,1)
struct RomFsHeader
{
static const size_t kSectionNum = 4;
static const size_t kRomFsDataAlignSize = 0x10;
struct SectionGeometry
{
tc::bn::le32<uint32_t> offset;
tc::bn::le32<uint32_t> size;
};
tc::bn::le32<uint32_t> header_size;
SectionGeometry dir_hash_bucket;
SectionGeometry dir_entry;
SectionGeometry file_hash_bucket;
SectionGeometry file_entry;
tc::bn::le32<uint32_t> data_offset;
};
static_assert(sizeof(RomFsHeader) == 0x28, "RomFsHeader had invalid size");
#ifdef _WIN32
#pragma warning(disable : 4200) // silence warnings for usage of empty arrays in stucts (for name[])
#endif
struct RomFsDirectoryEntry
{
tc::bn::le32<uint32_t> parent_offset; // parent directory
tc::bn::le32<uint32_t> sibling_offset; // next sibling directory
tc::bn::le32<uint32_t> child_offset; // directory child
tc::bn::le32<uint32_t> file_offset; // file child
tc::bn::le32<uint32_t> hash_sibling_offset; // hashtable sibling directory
tc::bn::le32<uint32_t> name_size; // size of name field
tc::bn::le16<char16_t> name[]; // variable length le-16bit unicode name
};
static_assert(sizeof(RomFsDirectoryEntry) == 0x18, "RomFsDirectoryEntry had invalid size");
struct RomFsFileEntry
{
tc::bn::le32<uint32_t> parent_offset; // parent directory
tc::bn::le32<uint32_t> sibling_offset; // next sibling file
tc::bn::le64<uint64_t> data_offset; // file data offset
tc::bn::le64<uint64_t> data_size; // file data size
tc::bn::le32<uint32_t> hash_sibling_offset; // hashtable sibling file
tc::bn::le32<uint32_t> name_size; // size of name field
tc::bn::le16<char16_t> name[]; // variable length le-16bit unicode name
};
static_assert(sizeof(RomFsFileEntry) == 0x20, "RomFsFileEntry had invalid size");
#ifdef _WIN32
#pragma warning(default : 4200)
#endif
#pragma pack(pop)
}} // namespace ntd::n3ds
+128
View File
@@ -0,0 +1,128 @@
#pragma once
#include <tc/types.h>
namespace ntd { namespace n3ds {
#pragma pack(push,1)
struct SystemMenuDataHeader
{
static const uint32_t kStructMagic = tc::bn::make_struct_magic_uint32("SMDH");
static const uint16_t kFormatVersion = 0;
static const size_t kTitleNum = 16;
struct ApplicationTitle
{
static const size_t kShortDescriptionLength = 0x40;
static const size_t kLongDescriptionLength = 0x80;
static const size_t kPublisherLength = 0x40;
std::array<tc::bn::le16<char16_t>, kShortDescriptionLength> short_description;
std::array<tc::bn::le16<char16_t>, kLongDescriptionLength> long_description;
std::array<tc::bn::le16<char16_t>, kPublisherLength> publisher;
};
static_assert(sizeof(ApplicationTitle) == 0x200, "ApplicationTitle had invalid size");
enum Language
{
Language_Japanese = 0,
Language_English = 1,
Language_French = 2,
Language_German = 3,
Language_Italian = 4,
Language_Spanish = 5,
Language_ChineseSimplified = 6,
Language_Korean = 7,
Language_Dutch = 8,
Language_Portuguese = 9,
Language_Russian = 10,
Language_ChineseTraditional = 11,
};
struct ApplicationSettings
{
static const size_t kAgeRatingNum = 16;
enum Organisation
{
Organisation_CERO = 0, // Japan
Organisation_ESRB = 1, // North America (USA?)
Organisation_USK = 3, // Germany
Organisation_PEGI_GEN = 4, // Europe
Organisation_PEGI_PRT = 6, // Portugal
Organisation_PEGI_BBFC = 7, // UK
Organisation_COB = 8, // Australia (& NZ?)
Organisation_GRB = 9, // South Korea
Organisation_CGSRR = 10 // Taiwan
};
enum RegionFlag
{
RegionFlag_Japan = 0,
RegionFlag_NorthAmerica = 1,
RegionFlag_Europe = 2,
RegionFlag_Australia = 3,
RegionFlag_China = 4,
RegionFlag_Korea = 5,
RegionFlag_Taiwan = 6
};
enum AppFlag
{
AppFlag_Visible = 0,
AppFlag_Autoboot = 1,
AppFlag_Use3dEffect = 2,
AppFlag_RequireAcceptEula = 3,
AppFlag_AutosaveOnExit = 4,
AppFlag_UseExtendedBanner = 5,
AppFlag_RatingUsed = 6,
AppFlag_UsesSaveData = 7,
AppFlag_RecordUsage = 8,
AppFlag_DisableSaveDataBackup = 10,
AppFlag_EnableMiiverseJumpArgs = 11,
AppFlag_SnakeOnly = 12,
AppFlag_DepositSale = 13
};
struct AgeRating
{
byte_t age : 5;
byte_t no_age_restriction : 1;
byte_t rating_pending : 1;
byte_t enabled_rating : 1;
};
static_assert(sizeof(AgeRating) == 0x1, "AgeRating had invalid size");
struct EulaVersion
{
uint32_t minor : 8;
uint32_t major : 8;
uint32_t : 0;
};
static_assert(sizeof(EulaVersion) == 0x4, "EulaVersion had invalid size");
std::array<AgeRating, kAgeRatingNum> age_rating;
tc::bn::bitarray<sizeof(uint32_t)> region_lockout; // see RegionFlag
tc::bn::le32<uint32_t> match_maker_id;
tc::bn::le64<uint64_t> match_maker_bit_id;
tc::bn::bitarray<sizeof(uint32_t)> flags; // see AppFlag
tc::bn::le32<EulaVersion> eula_version;
tc::bn::le32<float> optiminal_animation_frame; // for banner
tc::bn::le32<uint32_t> cec_id;
};
static_assert(sizeof(ApplicationSettings) == 0x30, "ApplicationSettings had invalid size");
tc::bn::le32<uint32_t> struct_magic; // SMDH
tc::bn::le16<uint16_t> version;
tc::bn::pad<2> reserved0;
std::array<ApplicationTitle, kTitleNum> title;
ApplicationSettings settings;
tc::bn::pad<8> reserved1;
std::array<byte_t, 0x480> small_icon_data;
std::array<byte_t, 0x1200> large_icon_data;
};
static_assert(sizeof(SystemMenuDataHeader) == 0x36C0, "SystemMenuDataHeader had invalid size");
#pragma pack(pop)
}} // namespace ntd::n3ds
+129
View File
@@ -0,0 +1,129 @@
#pragma once
#include <tc/types.h>
namespace ntd { namespace n3ds {
#pragma pack(push,1)
struct ContentAddr
{
tc::bn::le32<uint32_t> begin;
tc::bn::le32<uint32_t> end;
};
static_assert(sizeof(ContentAddr) == 0x8, "ContentAddr had invalid size");
struct ContHeader
{
static const uint32_t kStructMagic = tc::bn::make_struct_magic_uint32("CONT");
static const uint32_t kFormatVersion = 4;
// 0x000
tc::bn::le32<uint32_t> struct_magic; // CONT
tc::bn::le32<uint32_t> format_version; // 0x4
tc::bn::pad<0xBF8> padding_08;
// 0xC00
ContentAddr content_addr[];
};
static_assert(sizeof(ContHeader) == 0xC00, "ContHeader had invalid size");
struct LegacyUpdaterConfig
{
ContentAddr unk_addr;
tc::bn::le64<uint64_t> update_kernel_id;
};
static_assert(sizeof(LegacyUpdaterConfig) == 0x10, "LegacyUpdaterConfig had invalid size");
struct UpdaterConfig
{
tc::bn::le64<uint64_t> update_kernel_id;
ContentAddr updater_info_addr;
ContentAddr twl_font_addr;
ContentAddr cup_list_addr;
};
static_assert(sizeof(UpdaterConfig) == 0x20, "UpdaterConfig had invalid size");
struct UpdaterInfo
{
tc::bn::le32<uint32_t> firm_ver_major;
tc::bn::le32<uint32_t> firm_ver_minor;
tc::bn::le32<uint32_t> firm_ver_build;
tc::bn::le32<uint32_t> rel_step;
tc::bn::le32<uint32_t> firm_revision;
tc::bn::pad<4> reserved;
tc::bn::string<0x20> patch_info;
tc::bn::le32<uint32_t> kernel_ver_major;
tc::bn::le32<uint32_t> kernel_ver_minor;
};
static_assert(sizeof(UpdaterInfo) == 0x40, "UpdaterInfo had invalid size");
struct UpdaterSpec
{
static const uint32_t kStructMagic = tc::bn::make_struct_magic_uint32("SPEC");
static const uint32_t kFormatVersion = 0;
enum Region
{
Region_JP = 0,
Region_US = 1,
Region_EU = 2,
Region_CN = 4,
Region_KR = 5,
Region_TW = 6
};
enum SystemMode
{
SystemMode_prod = 0,
SystemMode_dev1 = 2,
SystemMode_dev2 = 3,
SystemMode_dev3 = 4
};
enum Flag
{
Flag_AutoRun = 0,
Flag_SkipImport = 1,
Flag_TmpUpdater = 2,
Flag_ForceChangeSystemMode = 3,
// SystemUpdater sets this flag at run-time if "rom:/contents/ClearProgram" is present
Flag_ClearProgramBeforeImport = 27,
// SystemUpdater sets this flag at run-time if "rom:/contents/ClearTicket" is present
Flag_ClearTickets = 28,
// SystemUpdater sets this flag at run-time
Flag_RebootAfterUpdate = 29,
// SystemUpdater sets these flags at run-time from key-combos
Flag_NotIncrementSeed = 30,
Flag_SdmcLog = 31
};
// 0x000
tc::bn::le32<uint32_t> struct_magic; // SPEC
tc::bn::le32<uint32_t> format_version; // 0x0
byte_t region;
byte_t system_mode;
tc::bn::pad<6> padding;
tc::bn::bitarray<4> flags;
};
static_assert(sizeof(UpdaterSpec) == 0x14, "UpdaterSpec had invalid size");
struct DeleteSpec
{
enum Flag
{
Flag_DeleteNonSystemTitles = 0, // this deletes all titles from NAND without the system app bit, excluding "Self", see below
Flag_DeleteSelf = 1 // This refers to when the systemupdater was installed to NAND (old install method), the titleID was 0x000400000fd00200
};
tc::bn::le32<uint32_t> id_list_length;
tc::bn::bitarray<4> flags;
tc::bn::pad<8> padding;
tc::bn::le64<uint64_t> id_list[];
};
static_assert(sizeof(DeleteSpec) == 0x10, "DeleteSpec had invalid size");
#pragma pack(pop)
}} // namespace ntd::n3ds
+197
View File
@@ -0,0 +1,197 @@
# C++/C Recursive Project Makefile
# (c) Jack
# Version 6 (20211110)
# Project Name
PROJECT_NAME = libnintendo-n3ds
# Project Relative Paths
PROJECT_PATH = $(CURDIR)
PROJECT_SRC_PATH = src
PROJECT_SRC_SUBDIRS = $(PROJECT_SRC_PATH) $(PROJECT_SRC_PATH)/es
PROJECT_INCLUDE_PATH = include
#PROJECT_TESTSRC_PATH = test
#PROJECT_TESTSRC_SUBDIRS = $(PROJECT_TESTSRC_PATH)
PROJECT_BIN_PATH = bin
#PROJECT_DOCS_PATH = docs
#PROJECT_DOXYFILE_PATH = Doxyfile
# Determine if the root makefile has been established, and if not establish this makefile as the root makefile
ifeq ($(ROOT_PROJECT_NAME),)
export ROOT_PROJECT_NAME = $(PROJECT_NAME)
export ROOT_PROJECT_PATH = $(PROJECT_PATH)
export ROOT_PROJECT_DEPENDENCY_PATH = $(ROOT_PROJECT_PATH)/deps
endif
# Shared Library Definitions
PROJECT_SO_VER_MAJOR = 0
PROJECT_SO_VER_MINOR = 1
PROJECT_SO_VER_PATCH = 0
PROJECT_SONAME = $(PROJECT_NAME).so.$(PROJECT_SO_VER_MAJOR)
PROJECT_SO_FILENAME = $(PROJECT_SONAME).$(PROJECT_SO_VER_MINOR).$(PROJECT_SO_VER_PATCH)
# Project Dependencies
PROJECT_DEPEND = broadon-es toolchain mbedtls fmt
PROJECT_DEPEND_LOCAL_DIR = libbroadon-es libtoolchain libmbedtls libfmt
# Generate compiler flags for including project include path
ifneq ($(PROJECT_INCLUDE_PATH),)
INC += -I"$(PROJECT_INCLUDE_PATH)"
endif
# Generate compiler flags for local included dependencies
ifneq ($(PROJECT_DEPEND_LOCAL_DIR),)
LIB += $(foreach dep,$(PROJECT_DEPEND_LOCAL_DIR), -L"$(ROOT_PROJECT_DEPENDENCY_PATH)/$(dep)/bin")
INC += $(foreach dep,$(PROJECT_DEPEND_LOCAL_DIR), -I"$(ROOT_PROJECT_DEPENDENCY_PATH)/$(dep)/include")
endif
# Generate compiler flags for external dependencies
ifneq ($(PROJECT_DEPEND),)
LIB += $(foreach dep,$(PROJECT_DEPEND), -l$(dep))
endif
# Detect Platform
ifeq ($(PROJECT_PLATFORM),)
ifeq ($(OS), Windows_NT)
export PROJECT_PLATFORM = WIN32
else
UNAME = $(shell uname -s)
ifeq ($(UNAME), Darwin)
export PROJECT_PLATFORM = MACOS
else
export PROJECT_PLATFORM = GNU
endif
endif
endif
# Detect Architecture
ifeq ($(PROJECT_PLATFORM_ARCH),)
ifeq ($(PROJECT_PLATFORM), WIN32)
export PROJECT_PLATFORM_ARCH = x86_64
else ifeq ($(PROJECT_PLATFORM), GNU)
export PROJECT_PLATFORM_ARCH = $(shell uname -m)
else ifeq ($(PROJECT_PLATFORM), MACOS)
export PROJECT_PLATFORM_ARCH = $(shell uname -m)
else
export PROJECT_PLATFORM_ARCH = x86_64
endif
endif
# Generate platform specific compiler flags
ifeq ($(PROJECT_PLATFORM), WIN32)
# Windows Flags/Libs
CC = x86_64-w64-mingw32-gcc
CXX = x86_64-w64-mingw32-g++
WARNFLAGS = -Wall -Wno-unused-value -Wno-unused-but-set-variable
ARCHFLAGS =
INC +=
LIB += -static
ARFLAGS = cr -o
else ifeq ($(PROJECT_PLATFORM), GNU)
# GNU/Linux Flags/Libs
#CC =
#CXX =
WARNFLAGS = -Wall -Wno-unused-value -Wno-unused-but-set-variable
ARCHFLAGS =
INC +=
LIB +=
ARFLAGS = cr -o
else ifeq ($(PROJECT_PLATFORM), MACOS)
# MacOS Flags/Libs
#CC =
#CXX =
WARNFLAGS = -Wall -Wno-unused-value -Wno-unused-private-field
ARCHFLAGS = -arch $(PROJECT_PLATFORM_ARCH)
INC +=
LIB +=
ARFLAGS = rc
endif
# Compiler Flags
CXXFLAGS = -std=c++11 $(INC) $(WARNFLAGS) $(ARCHFLAGS) -fPIC
CFLAGS = -std=c11 $(INC) $(WARNFLAGS) $(ARCHFLAGS) -fPIC
# Object Files
SRC_OBJ = $(foreach dir,$(PROJECT_SRC_SUBDIRS),$(subst .cpp,.o,$(wildcard $(dir)/*.cpp))) $(foreach dir,$(PROJECT_SRC_SUBDIRS),$(subst .cc,.o,$(wildcard $(dir)/*.cc))) $(foreach dir,$(PROJECT_SRC_SUBDIRS),$(subst .c,.o,$(wildcard $(dir)/*.c)))
TESTSRC_OBJ = $(foreach dir,$(PROJECT_TESTSRC_SUBDIRS),$(subst .cpp,.o,$(wildcard $(dir)/*.cpp))) $(foreach dir,$(PROJECT_TESTSRC_SUBDIRS),$(subst .cc,.o,$(wildcard $(dir)/*.cc))) $(foreach dir,$(PROJECT_TESTSRC_SUBDIRS),$(subst .c,.o,$(wildcard $(dir)/*.c)))
# all is the default, user should specify what the default should do
# - 'static_lib' for building static library
# - 'shared_lib' for building shared library
# - 'program' for building the program
# - 'test_program' for building the test program
# These can typically be used together however *_lib and program should not be used together
all: static_lib
clean: clean_object_files remove_binary_dir
# Object Compile Rules
%.o: %.c
@echo CC $<
@$(CC) $(CFLAGS) -c $< -o $@
%.o: %.cpp
@echo CXX $<
@$(CXX) $(CXXFLAGS) -c $< -o $@
%.o: %.cc
@echo CXX $<
@$(CXX) $(CXXFLAGS) -c $< -o $@
# Binary Directory
.PHONY: create_binary_dir
create_binary_dir:
@mkdir -p "$(PROJECT_BIN_PATH)"
.PHONY: remove_binary_dir
remove_binary_dir:
ifneq ($(PROJECT_BIN_PATH),)
@rm -rf "$(PROJECT_BIN_PATH)"
endif
.PHONY: clean_object_files
clean_object_files:
@rm -f $(SRC_OBJ) $(TESTSRC_OBJ)
# Build Library
static_lib: $(SRC_OBJ) create_binary_dir
@echo LINK $(PROJECT_BIN_PATH)/$(PROJECT_NAME).a
@ar $(ARFLAGS) "$(PROJECT_BIN_PATH)/$(PROJECT_NAME).a" $(SRC_OBJ)
shared_lib: $(SRC_OBJ) create_binary_dir
@echo LINK $(PROJECT_BIN_PATH)/$(PROJECT_SO_FILENAME)
@gcc -shared -Wl,-soname,$(PROJECT_SONAME) -o "$(PROJECT_BIN_PATH)/$(PROJECT_SO_FILENAME)" $(SRC_OBJ)
# Build Program
program: $(SRC_OBJ) create_binary_dir
@echo LINK $(PROJECT_BIN_PATH)/$(PROJECT_NAME)
@$(CXX) $(ARCHFLAGS) $(SRC_OBJ) $(LIB) -o "$(PROJECT_BIN_PATH)/$(PROJECT_NAME)"
# Build Test Program
test_program: $(TESTSRC_OBJ) $(SRC_OBJ) create_binary_dir
ifneq ($(PROJECT_TESTSRC_PATH),)
@echo LINK $(PROJECT_BIN_PATH)/$(PROJECT_NAME)_test
@$(CXX) $(ARCHFLAGS) $(TESTSRC_OBJ) $(SRC_OBJ) $(LIB) -o "$(PROJECT_BIN_PATH)/$(PROJECT_NAME)_test"
endif
# Documentation
.PHONY: docs
docs:
ifneq ($(PROJECT_DOCS_PATH),)
doxygen "$(PROJECT_DOXYFILE_PATH)"
endif
.PHONY: clean_docs
clean_docs:
ifneq ($(PROJECT_DOCS_PATH),)
@rm -rf "$(PROJECT_DOCS_PATH)"
endif
# Dependencies
.PHONY: deps
deps:
@$(foreach lib,$(PROJECT_DEPEND_LOCAL_DIR), cd "$(ROOT_PROJECT_DEPENDENCY_PATH)/$(lib)" && $(MAKE) static_lib && cd "$(PROJECT_PATH)";)
.PHONY: clean_deps
clean_deps:
@$(foreach lib,$(PROJECT_DEPEND_LOCAL_DIR), cd "$(ROOT_PROJECT_DEPENDENCY_PATH)/$(lib)" && $(MAKE) clean && cd "$(PROJECT_PATH)";)
+131
View File
@@ -0,0 +1,131 @@
#include <ntd/n3ds/CciFsSnapshotGenerator.h>
#include <tc/io/SubStream.h>
#include <tc/crypto/Sha256Generator.h>
#include <ntd/n3ds/cci.h>
#include <iostream>
#include <iomanip>
#include <sstream>
#include <tc/cli/FormatUtil.h>
ntd::n3ds::CciFsShapshotGenerator::CciFsShapshotGenerator(const std::shared_ptr<tc::io::IStream>& stream) :
FileSystemSnapshot(),
mBaseStream(stream),
mCurDir(0)
{
// validate stream properties
if (mBaseStream == nullptr)
{
throw tc::ObjectDisposedException("ntd::n3ds::CciFsShapshotGenerator", "Failed to open input stream.");
}
if (mBaseStream->canRead() == false || mBaseStream->canSeek() == false)
{
throw tc::NotSupportedException("ntd::n3ds::CciFsShapshotGenerator", "Input stream requires read/seek permissions.");
}
// validate and read CCI header
ntd::n3ds::CciHeader hdr;
if (mBaseStream->length() < sizeof(ntd::n3ds::CciHeader))
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CciFsShapshotGenerator", "Input stream is too small.");
}
mBaseStream->seek(0, tc::io::SeekOrigin::Begin);
mBaseStream->read((byte_t*)(&hdr), sizeof(ntd::n3ds::CciHeader));
if (hdr.ncsd_header.struct_magic.unwrap() != hdr.ncsd_header.kStructMagic)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CciFsShapshotGenerator", "CCI header had invalid struct magic.");
}
if (hdr.ncsd_header.flags.media_type != hdr.ncsd_header.MediaType_Card1 && hdr.ncsd_header.flags.media_type != hdr.ncsd_header.MediaType_Card2)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CciFsShapshotGenerator", "CCI header had unexpected media type.");
}
if (hdr.ncsd_header.flags.block_size_log != 0)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CciFsShapshotGenerator", "CCI header had unexpected block size.");
}
if (hdr.ncsd_header.flags.media_platform.test(hdr.ncsd_header.MediaPlatform_CTR) == false)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CciFsShapshotGenerator", "CCI header had unexpected supported platforms.");
}
// parse header partitions
struct PartitionInformation
{
int64_t offset;
int64_t size;
uint64_t title_id;
};
std::array<PartitionInformation, ntd::n3ds::NcsdCommonHeader::kPartitionNum> partition;
int64_t used_image_size = 0;
for (size_t i = 0; i < partition.size(); i++)
{
if (hdr.ncsd_header.partition_fs_type[i] != hdr.ncsd_header.PartitionFsType_None)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CciFsShapshotGenerator", "CCI partition had unexpected fs type.");
}
if (hdr.ncsd_header.partition_crypto_type[i] != hdr.ncsd_header.PartitionCryptoType_None)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CciFsShapshotGenerator", "CCI partition had unexpected crypto type.");
}
partition[i].offset = int64_t(hdr.ncsd_header.partition_offsetsize[i].blk_offset.unwrap()) << 9;
partition[i].size = int64_t(hdr.ncsd_header.partition_offsetsize[i].blk_size.unwrap()) << 9;
partition[i].title_id = hdr.ncsd_header.card_ext.partition_id[i].unwrap();
used_image_size = std::max<int64_t>((partition[i].offset + partition[i].size), used_image_size);
}
if (stream->length() < used_image_size)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CciFsShapshotGenerator", "Input stream is too small, given calculated CCI partition geometry.");
}
// Add root directory
dir_entries.push_back(DirEntry());
mCurDir = dir_entries.size() - 1;
dir_entries[mCurDir].dir_listing.abs_path = tc::io::Path("/");
dir_entry_path_map[tc::io::Path("/")] = mCurDir;
// populate virtual filesystem
// initial data
//addFile("initial_data.bin", sizeof(ntd::n3ds::NcsdCommonHeader) + 0xe00, sizeof(ntd::n3ds::CardInfoHeader::InitialData));
// NCCH partitions
for (size_t i = 0; i < partition.size(); i++)
{
if (partition[i].size != 0)
{
std::stringstream ss;
ss << std::hex << std::setfill('0') << std::setw(2) << i;
ss << "_";
ss << std::hex << std::setfill('0') << std::setw(16) << partition[i].title_id;
ss << ".app";
addFile(ss.str(), partition[i].offset, partition[i].size);
}
}
}
void ntd::n3ds::CciFsShapshotGenerator::addFile(const std::string& name, int64_t offset, int64_t size)
{
FileEntry tmp;
tmp.stream = std::make_shared<tc::io::SubStream>(tc::io::SubStream(mBaseStream, offset, size));
// create file path
tc::io::Path file_path = dir_entries[mCurDir].dir_listing.abs_path + std::string(name);
// add file entry to list
file_entries.push_back(std::move(tmp));
// add file entry to map
file_entry_path_map[file_path] = file_entries.size()-1;
// add name to parent directory listing
dir_entries[mCurDir].dir_listing.file_list.push_back(name);
}
+241
View File
@@ -0,0 +1,241 @@
#include <ntd/n3ds/CiaFsSnapshotGenerator.h>
#include <tc/io/SubStream.h>
#include <tc/crypto/Sha256Generator.h>
#include <ntd/n3ds/cia.h>
#include <brd/es/es_tmd.h>
#include <iostream>
#include <iomanip>
#include <sstream>
#include <tc/cli/FormatUtil.h>
ntd::n3ds::CiaFsSnapshotGenerator::CiaFsSnapshotGenerator(const std::shared_ptr<tc::io::IStream>& stream) :
FileSystemSnapshot()
{
mBaseStream = stream;
// validate stream properties
if (mBaseStream == nullptr)
{
throw tc::ObjectDisposedException("ntd::n3ds::CiaFsSnapshotGenerator", "Failed to open input stream.");
}
if (mBaseStream->canRead() == false || mBaseStream->canSeek() == false)
{
throw tc::NotSupportedException("ntd::n3ds::CiaFsSnapshotGenerator", "Input stream requires read/seek permissions.");
}
// validate and read CIA header
ntd::n3ds::CiaHeader hdr;
if (mBaseStream->length() < sizeof(ntd::n3ds::CiaHeader))
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsSnapshotGenerator", "Input stream is too small.");
}
mBaseStream->seek(0, tc::io::SeekOrigin::Begin);
mBaseStream->read((byte_t*)(&hdr), sizeof(ntd::n3ds::CiaHeader));
if (hdr.header_size.unwrap() != sizeof(ntd::n3ds::CiaHeader))
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsSnapshotGenerator", "CIA header had unexpected size.");
}
if (hdr.format_version.unwrap() != ntd::n3ds::CiaHeader::FormatVersion_Default)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsSnapshotGenerator", "CIA header had unexpected format version.");
}
if (hdr.type.unwrap() != ntd::n3ds::CiaHeader::Type_Normal)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsSnapshotGenerator", "CIA header had unexpected type.");
}
// parse header sections
enum CiaSectionIndex
{
Header,
Certificate,
Tik,
Tmd,
Content,
Footer
};
struct CiaSectionInformation
{
int64_t offset;
int64_t size;
};
std::array<CiaSectionInformation, 6> section;
section[Header].size = hdr.header_size.unwrap();
section[Header].offset = 0;
section[Certificate].size = hdr.certificate_size.unwrap();
section[Certificate].offset = align<int64_t>(section[Header].offset + section[Header].size, CiaHeader::kCiaSectionAlignment);
section[Tik].size = hdr.ticket_size.unwrap();
section[Tik].offset = align<int64_t>(section[Certificate].offset + section[Certificate].size, CiaHeader::kCiaSectionAlignment);
section[Tmd].size = hdr.tmd_size.unwrap();
section[Tmd].offset = align<int64_t>(section[Tik].offset + section[Tik].size, CiaHeader::kCiaSectionAlignment);
if (uint64_t(std::numeric_limits<int64_t>::max()) < hdr.content_size.unwrap())
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsSnapshotGenerator", "CIA header had too large content section.");
}
section[Content].size = hdr.content_size.unwrap();
section[Content].offset = align<int64_t>(section[Tmd].offset + section[Tmd].size, CiaHeader::kCiaSectionAlignment);
section[Footer].size = hdr.footer_size.unwrap();
section[Footer].offset = align<int64_t>(section[Content].offset + section[Content].size, CiaHeader::kCiaSectionAlignment);
int64_t total_size = section[Footer].offset + section[Footer].size;
if (stream->length() < total_size)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsSnapshotGenerator", "Input stream is too small, given calculated CIA section geometry.");
}
// Add root directory
dir_entries.push_back(DirEntry());
mCurDir = dir_entries.size() - 1;
dir_entries[mCurDir].dir_listing.abs_path = tc::io::Path("/");
dir_entry_path_map[tc::io::Path("/")] = mCurDir;
// populate virtual filesystem
if (section[Certificate].size != 0)
{
addFile("cert", section[Certificate].offset, section[Certificate].size);
}
if (section[Tik].size != 0)
{
addFile("tik", section[Tik].offset, section[Tik].size);
}
if (section[Tmd].size != 0)
{
addFile("tmd", section[Tmd].offset, section[Tmd].size);
}
if (section[Footer].size != 0)
{
addFile("footer", section[Footer].offset, section[Footer].size);
}
if (section[Content].size != 0)
{
// if TMD exists, add the (included) <cid>.app files to the file system
if (section[Tmd].size != 0)
{
// test size before reading
if (section[Tmd].size < (sizeof(brd::es::ESV1TitleMeta) + sizeof(brd::es::ESV1ContentMeta)))
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsSnapshotGenerator", "TMD was too small.");
}
// import TMD v1 data
if (tc::is_int64_t_too_large_for_size_t(section[Tmd].size))
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsSnapshotGenerator", "TMD was too large to read into memory.");
}
tc::ByteData tmd_data = tc::ByteData(static_cast<size_t>(section[Tmd].size));
mBaseStream->seek(section[Tmd].offset, tc::io::SeekOrigin::Begin);
if (mBaseStream->read(tmd_data.data(), tmd_data.size()) < tmd_data.size())
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsSnapshotGenerator", "TMD had unexpected size after reading.");
}
// get pointer
brd::es::ESV1TitleMeta* tmd = (brd::es::ESV1TitleMeta*)tmd_data.data();
if (tmd->sig.sigType.unwrap() != brd::es::ESSigType::RSA2048_SHA256)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsSnapshotGenerator", "TMD had unexpected signature type.");
}
if (tmd->head.version != 1)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsSnapshotGenerator", "TMD had unexpected format version.");
}
// hash for staged validiation
std::array<byte_t, tc::crypto::Sha256Generator::kHashSize> hash;
// TODO validate signature
/*
tc::crypto::GenerateSha256Hash(hash.data(), (byte_t*)&tmd->sig.issuer, (size_t)((byte_t*)&tmd->v1Head.cmdGroups - (byte_t*)&tmd->sig.issuer));
if (tc::crypto::VerifyRsa2048Pkcs1Sha256(tmd->sig.sig.data(), hash.data(), <rsa key here>))
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsMetaGenerator", "TMD had invalid signature.");
}
*/
tc::crypto::GenerateSha256Hash(hash.data(), (byte_t*)&tmd->v1Head.cmdGroups, sizeof(tmd->v1Head.cmdGroups));
if (memcmp(hash.data(), tmd->v1Head.hash.data(), hash.size()) != 0)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsSnapshotGenerator", "TMD had invalid CMD group hash.");
}
/*
std::cout << "[Tmd]" << std::endl;
std::cout << " > Issuer: " << tmd->sig.issuer.data() << std::endl;
std::cout << " > Version: " << (int)tmd->head.version << std::endl;
std::cout << " > CACrl Version: " << (int)tmd->head.caCrlVersion << std::endl;
std::cout << " > SignerCrl Version: " << (int)tmd->head.signerCrlVersion << std::endl;
std::cout << " > TitleId: " << std::hex << std::setfill('0') << std::setw(16) << tmd->head.titleId.unwrap() << std::endl;
*/
size_t cmd_table_num = tmd->v1Head.cmdGroups[0].nCmds.unwrap();
if (tmd_data.size() != (sizeof(brd::es::ESV1TitleMeta) + (cmd_table_num * sizeof(brd::es::ESV1ContentMeta))))
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsSnapshotGenerator", "TMD had unexpected size");
}
tc::crypto::GenerateSha256Hash(hash.data(), (byte_t*)&tmd->contents, cmd_table_num * sizeof(brd::es::ESV1ContentMeta));
if (memcmp(hash.data(), tmd->v1Head.cmdGroups[0].groupHash.data(), hash.size()) != 0)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::CiaFsSnapshotGenerator", "TMD had invalid CMD group[0] hash.");
}
// TODO SHA2 cmd_table before processing
int64_t content_offset = 0;
for (size_t i = 0; i < cmd_table_num; i++)
{
/*
std::cout << "Found content ";
std::cout << "cid(" << std::hex << std::setfill('0') << std::setw(8) << tmd->contents[i].cid.unwrap() << "), ";
std::cout << "index(" << std::hex << std::setfill('0') << std::setw(4) << tmd->contents[i].index.unwrap() << "), ";
std::cout << "type(" << std::hex << std::setfill('0') << std::setw(4) << tmd->contents[i].type.unwrap() << "), ";
std::cout << "size(" << std::hex << std::setfill('0') << std::setw(16) << tmd->contents[i].size.unwrap() << "), ";
std::cout << "" << std::endl;
*/
if (hdr.content_bitarray.test(tmd->contents[i].index.unwrap()))
{
std::stringstream ss;
ss << std::hex << std::setfill('0') << std::setw(8) << tmd->contents[i].cid.unwrap() << ".app";
int64_t content_size = align<int64_t>(tmd->contents[i].size.unwrap(), CiaHeader::kCiaContentAlignment);
addFile(ss.str(), section[Content].offset + content_offset, content_size);
content_offset += content_size;
}
}
}
// otherwise include the content blob as one file (00000000.app)
else
{
addFile("00000000.app", section[Content].offset, section[Content].size);
}
}
}
void ntd::n3ds::CiaFsSnapshotGenerator::addFile(const std::string& name, int64_t offset, int64_t size)
{
FileEntry tmp;
tmp.stream = std::make_shared<tc::io::SubStream>(tc::io::SubStream(mBaseStream, offset, size));
// create file path
tc::io::Path file_path = dir_entries[mCurDir].dir_listing.abs_path + std::string(name);
// add file entry to list
file_entries.push_back(std::move(tmp));
// add file entry to map
file_entry_path_map[file_path] = file_entries.size()-1;
// add name to parent directory listing
dir_entries[mCurDir].dir_listing.file_list.push_back(name);
}
+99
View File
@@ -0,0 +1,99 @@
#include <ntd/n3ds/CtrKeyGenerator.h>
void ntd::n3ds::CtrKeyGenerator::GenerateKey(const uint8_t *x, const uint8_t *y, uint8_t *key)
{
static const uint8_t KEYGEN_CONST[16] = { 0x1F, 0xF9, 0xE9, 0xAA, 0xC5, 0xFE, 0x04, 0x08, 0x02, 0x45, 0x91, 0xDC, 0x5D, 0x52, 0x76, 0x8A };
// overall algo:
// key = (((x <<< 2) ^ y) + KEYGEN_CONST) >>> 41
uint8_t x_rot[16], key_xy[16], key_xyc[16];
// x_rot = x <<< 2
n128_lrot(x, 2, x_rot);
// key_xy = x_rot ^ y
n128_xor(x_rot, y, key_xy);
// key_xyc = key_xy + KEYGEN_CONST
n128_add(key_xy, KEYGEN_CONST, key_xyc);
n128_rrot(key_xyc, 41, key);
}
// 128bit wrap-around math
int32_t ntd::n3ds::CtrKeyGenerator::wrap_index(int32_t i)
{
return i < 0 ? ((i % 16) + 16) % 16 : (i > 15 ? i % 16 : i);
}
void ntd::n3ds::CtrKeyGenerator::n128_rrot(const uint8_t *in, uint32_t rot, uint8_t *out)
{
uint32_t bit_shift, byte_shift;
rot = rot % 128;
byte_shift = rot / 8;
bit_shift = rot % 8;
for (int32_t i = 0; i < 16; i++) {
out[i] = (in[wrap_index(i - byte_shift)] >> bit_shift) | (in[wrap_index(i - byte_shift - 1)] << (8 - bit_shift));
}
}
void ntd::n3ds::CtrKeyGenerator::n128_lrot(const uint8_t *in, uint32_t rot, uint8_t *out)
{
uint32_t bit_shift, byte_shift;
rot = rot % 128;
byte_shift = rot / 8;
bit_shift = rot % 8;
for (int32_t i = 0; i < 16; i++) {
out[i] = (in[wrap_index(i + byte_shift)] << bit_shift) | (in[wrap_index(i + byte_shift + 1)] >> (8 - bit_shift));
}
}
/* out = a + b
*/
void ntd::n3ds::CtrKeyGenerator::n128_add(const uint8_t *a, const uint8_t *b, uint8_t *out)
{
uint8_t carry = 0;
uint32_t sum = 0;
for (int i = 15; i >= 0; i--) {
sum = a[i] + b[i] + carry;
carry = sum >> 8;
out[i] = sum & 0xff;
}
}
/* out = a - b
*/
void ntd::n3ds::CtrKeyGenerator::n128_sub(const uint8_t *a, const uint8_t *b, uint8_t *out)
{
uint8_t carry = 0;
uint32_t sum = 0;
for (int i = 15; i >= 0; i--) {
sum = a[i] - (b[i] + carry);
// check to see if anything was borrowed from next byte
if (a[i] < (b[i] + carry)) {
sum += 0x100;
carry = 1;
}
else {
carry = 0;
}
// set value
out[i] = sum & 0xff;
}
}
void ntd::n3ds::CtrKeyGenerator::n128_xor(const uint8_t *a, const uint8_t *b, uint8_t *out)
{
for (int i = 0; i < 16; i++) {
out[i] = a[i] ^ b[i];
}
}
+122
View File
@@ -0,0 +1,122 @@
#include <ntd/n3ds/ExeFsSnapshotGenerator.h>
#include <tc/io/SubStream.h>
#include <tc/crypto/Sha256Generator.h>
#include <tc/crypto/CryptoException.h>
#include <tc/io/MemoryStream.h>
#include <ntd/n3ds/exefs.h>
#include <iostream>
#include <iomanip>
#include <sstream>
#include <tc/cli/FormatUtil.h>
ntd::n3ds::ExeFsSnapshotGenerator::ExeFsSnapshotGenerator(const std::shared_ptr<tc::io::IStream>& stream, bool verify_hashes) :
FileSystemSnapshot()
{
// validate stream properties
if (stream == nullptr)
{
throw tc::ObjectDisposedException("ntd::n3ds::ExeFsSnapshotGenerator", "Failed to open input stream.");
}
if (stream->canRead() == false || stream->canSeek() == false)
{
throw tc::NotSupportedException("ntd::n3ds::ExeFsSnapshotGenerator", "Input stream requires read/seek permissions.");
}
// validate and read EXEFS header
ntd::n3ds::ExeFsHeader hdr;
if (stream->length() < sizeof(ntd::n3ds::ExeFsHeader))
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::ExeFsSnapshotGenerator", "Input stream is too small.");
}
stream->seek(0, tc::io::SeekOrigin::Begin);
stream->read((byte_t*)(&hdr), sizeof(ntd::n3ds::ExeFsHeader));
if (hdr.file_table[0].name[0] == 0 || hdr.file_table[0].offset.unwrap() != 0 || hdr.hash_table[ntd::n3ds::ExeFsHeader::kFileNum - 1][0] == 0)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::ExeFsSnapshotGenerator", "ExeFsHeader is corrupted (Bad first entry).");
}
// parse header sections
struct SectionInformation
{
std::string name;
uint32_t offset;
uint32_t size;
std::array<byte_t, 32> hash;
};
std::vector<SectionInformation> section;
int64_t pos = 0;
for (size_t i = 0; i < ntd::n3ds::ExeFsHeader::kFileNum; i++)
{
// skip empty entry
if (hdr.file_table[i].name.size() == 0 || hdr.file_table[i].size.unwrap() == 0) { continue; }
if (hdr.file_table[i].offset.unwrap() != pos)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::ExeFsSnapshotGenerator", "ExeFs section had unexpected offset.");
}
SectionInformation tmp;
tmp.name = hdr.file_table[i].name.decode();
tmp.offset = hdr.file_table[i].offset.unwrap();
tmp.size = hdr.file_table[i].size.unwrap();
tmp.hash = hdr.hash_table[ntd::n3ds::ExeFsHeader::kFileNum - 1 - i];
pos = align<int64_t>((static_cast<int64_t>(tmp.offset) + static_cast<int64_t>(tmp.size)), ntd::n3ds::ExeFsHeader::kExeFsSectionAlignSize);
section.push_back(std::move(tmp));
}
// Add root directory
dir_entries.push_back(DirEntry());
auto cur_dir = &dir_entries.front();
cur_dir->dir_listing.abs_path = tc::io::Path("/");
dir_entry_path_map[tc::io::Path("/")] = dir_entries.size()-1;
// populate virtual filesystem
std::array<byte_t, tc::crypto::Sha256Generator::kHashSize> hash_tmp;
for (size_t i = 0; i < section.size(); i++)
{
if (section[i].size != 0)
{
FileEntry tmp;
// if we verify the hashes, we import and validate file, after validation creating a memorystream
if (verify_hashes)
{
auto tmp_data = tc::ByteData(section[i].size);
stream->seek(section[i].offset + sizeof(ntd::n3ds::ExeFsHeader), tc::io::SeekOrigin::Begin);
stream->read(tmp_data.data(), tmp_data.size());
tc::crypto::GenerateSha256Hash(hash_tmp.data(), tmp_data.data(), tmp_data.size());
if (memcmp(hash_tmp.data(), section[i].hash.data(), hash_tmp.size()) != 0)
{
throw tc::crypto::CryptoException("ntd::n3ds::ExeFsSnapshotGenerator", "File failed hash check.");
}
tmp.stream = std::make_shared<tc::io::MemoryStream>(tc::io::MemoryStream(std::move(tmp_data)));
}
// otherwise we just create a substream
else
{
tmp.stream = std::make_shared<tc::io::SubStream>(tc::io::SubStream(stream, static_cast<int64_t>(section[i].offset) + static_cast<int64_t>(sizeof(ntd::n3ds::ExeFsHeader)), static_cast<int64_t>(section[i].size)));
}
// create file path
tc::io::Path file_path = cur_dir->dir_listing.abs_path + section[i].name;
// add file entry to list
file_entries.push_back(std::move(tmp));
// add file entry to map
file_entry_path_map[file_path] = file_entries.size()-1;
// add name to parent directory listing
cur_dir->dir_listing.file_list.push_back(section[i].name);
}
}
}
+467
View File
@@ -0,0 +1,467 @@
#include <ntd/n3ds/IvfcStream.h>
#include <tc/io/SubStream.h>
#include <tc/io/IOUtil.h>
#include <tc/io/StreamUtil.h>
#include <ntd/n3ds/ivfc.h>
#include <iostream>
#include <iomanip>
#include <sstream>
#include <tc/cli/FormatUtil.h>
ntd::n3ds::IvfcStream::IvfcStream() :
mModuleLabel("ntd::n3ds::IvfcStream"),
mBaseStream(),
mDataStreamBlockSize(0),
mDataStreamLogicalLength(0),
mDataStream(),
mHashCache()
{
}
ntd::n3ds::IvfcStream::IvfcStream(const std::shared_ptr<tc::io::IStream>& stream) :
IvfcStream()
{
mBaseStream = stream;
// validate stream properties
if (mBaseStream == nullptr)
{
throw tc::ObjectDisposedException("ntd::n3ds::IvfcStream", "stream is null.");
}
if (mBaseStream->canRead() == false)
{
throw tc::InvalidOperationException("ntd::n3ds::IvfcStream", "stream does not support reading.");
}
if (mBaseStream->canSeek() == false)
{
throw tc::InvalidOperationException("ntd::n3ds::IvfcStream", "stream does not support seeking.");
}
// validate and read IVFC header
if (mBaseStream->length() < sizeof(ntd::n3ds::IvfcCtrRomfsHeader))
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::IvfcStream", "stream is too small.");
}
ntd::n3ds::IvfcCtrRomfsHeader hdr;
mBaseStream->seek(0, tc::io::SeekOrigin::Begin);
mBaseStream->read((byte_t*)&hdr, sizeof(ntd::n3ds::IvfcCtrRomfsHeader));
if (hdr.head.struct_magic.unwrap() != ntd::n3ds::IvfcHeader::kStructMagic)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::IvfcStream", "IVFC header had invalid struct magic.");
}
if (hdr.head.type_id.unwrap() != ntd::n3ds::IvfcHeader::TypeId_A)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::IvfcStream", "IVFC header had unexpected type id.");
}
enum LevelIndex
{
MasterHash,
HashLevel0,
HashLevel1,
DataLevel2
};
// parse level layout
struct LevelInfo
{
int64_t offset;
int64_t size;
size_t block_size;
size_t block_num;
};
std::array<LevelInfo, 4> section;
for (size_t i = 0; i < section.size(); i++)
{
if (i == MasterHash)
{
section[i].offset = int64_t(align<size_t>(sizeof(ntd::n3ds::IvfcCtrRomfsHeader), ntd::n3ds::IvfcCtrRomfsHeader::kHeaderAlign));
section[i].size = int64_t(hdr.master_hash_size.unwrap());
section[i].block_size = 0;
section[i].block_num = 0;
}
else
{
if (tc::is_uint64_t_too_large_for_int64_t(hdr.level[i-1].offset.unwrap()))
{
throw tc::OutOfMemoryException("ntd::n3ds::IvfcStream", "IVFC layer offset too large.");
}
if (tc::is_uint64_t_too_large_for_int64_t(hdr.level[i-1].size.unwrap()))
{
throw tc::OutOfMemoryException("ntd::n3ds::IvfcStream", "IVFC layer size too large.");
}
section[i].offset = int64_t(hdr.level[i-1].offset.unwrap());
section[i].size = int64_t(hdr.level[i-1].size.unwrap());
section[i].block_size = size_t(size_t(1) << size_t(hdr.level[i-1].block_size_log2.unwrap()));
section[i].block_num = size_t(section[i].size / int64_t(section[i].block_size)) + (size_t(section[i].size % int64_t(section[i].block_size)) ? 1 : 0);
/*
std::cout << "i: " << std::dec << i << std::endl;
std::cout << "size : " << std::dec << section[i].size << std::endl;
std::cout << "block_size_log2: " << std::dec << hdr.level[i-1].block_size_log2.unwrap() << std::endl;
std::cout << "block_size : " << std::dec << section[i].block_size << std::endl;
std::cout << "block_num : " << std::dec << section[i].block_num << std::endl;
*/
}
}
// set actual offsets
section[DataLevel2].offset = align<int64_t>(section[MasterHash].offset + section[MasterHash].size, section[DataLevel2].block_size);
section[HashLevel0].offset = align<int64_t>(section[DataLevel2].offset + section[DataLevel2].size, section[HashLevel0].block_size);
section[HashLevel1].offset = align<int64_t>(section[HashLevel0].offset + section[HashLevel0].size, section[HashLevel1].block_size);
// verify that the hash tables can be read into memory
if (tc::is_int64_t_too_large_for_size_t(section[MasterHash].size))
{
throw tc::OutOfMemoryException("ntd::n3ds::IvfcStream", "IVFC master hash table too large.");
}
if (tc::is_int64_t_too_large_for_size_t(section[HashLevel0].size))
{
throw tc::OutOfMemoryException("ntd::n3ds::IvfcStream", "IVFC level0 hash table too large.");
}
if (tc::is_int64_t_too_large_for_size_t(section[HashLevel1].size))
{
throw tc::OutOfMemoryException("ntd::n3ds::IvfcStream", "IVFC level1 hash table too large.");
}
// validate hash tree
if ((section[DataLevel2].block_num * tc::crypto::Sha256Generator::kHashSize) != section[HashLevel1].size)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::IvfcStream", "IVFC level1 hash table had unexpected size.");
}
if ((section[HashLevel1].block_num * tc::crypto::Sha256Generator::kHashSize) != section[HashLevel0].size)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::IvfcStream", "IVFC level0 hash table had unexpected size.");
}
if ((section[HashLevel0].block_num * tc::crypto::Sha256Generator::kHashSize) != section[MasterHash].size)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::IvfcStream", "IVFC master hash table had unexpected size.");
}
auto master_hash_data = tc::ByteData(static_cast<size_t>(section[MasterHash].size));
auto hash_level0_data = tc::ByteData(align<size_t>(static_cast<size_t>(section[HashLevel0].size), section[HashLevel0].block_size));
auto hash_level1_data = tc::ByteData(align<size_t>(static_cast<size_t>(section[HashLevel1].size), section[HashLevel1].block_size));
mBaseStream->seek(section[MasterHash].offset, tc::io::SeekOrigin::Begin);
mBaseStream->read(master_hash_data.data(), master_hash_data.size());
mBaseStream->seek(section[HashLevel0].offset, tc::io::SeekOrigin::Begin);
mBaseStream->read(hash_level0_data.data(), hash_level0_data.size());
mBaseStream->seek(section[HashLevel1].offset, tc::io::SeekOrigin::Begin);
mBaseStream->read(hash_level1_data.data(), hash_level1_data.size());
/*
std::cout << "Master Hash:" << std::endl;
std::cout << tc::cli::FormatUtil::formatBytesAsHxdHexString(master_hash_data);
std::cout << "Level 0:" << std::endl;
std::cout << tc::cli::FormatUtil::formatBytesAsHxdHexString(hash_level0_data);
std::cout << "Level 1:" << std::endl;
std::cout << tc::cli::FormatUtil::formatBytesAsHxdHexString(hash_level1_data);
*/
// validate level0
if (validateLayerBlocksWithHashLayer(hash_level0_data.data(), section[HashLevel0].block_size, section[HashLevel0].block_num, master_hash_data.data()) == false)
{
throw tc::crypto::CryptoException("ntd::n3ds::IvfcStream", "Hash layer0 failed hash validation.");
}
// validate level1
if (validateLayerBlocksWithHashLayer(hash_level1_data.data(), section[HashLevel1].block_size, section[HashLevel1].block_num, hash_level0_data.data()) == false)
{
throw tc::crypto::CryptoException("ntd::n3ds::IvfcStream", "Hash layer0 failed hash validation.");
}
// save hash level1 data for data layer
mHashCache = hash_level1_data;
// create data layer
mDataStreamBlockSize = section[DataLevel2].block_size;
mDataStreamLogicalLength = section[DataLevel2].size;
mDataStream = std::shared_ptr<tc::io::SubStream>(new tc::io::SubStream(mBaseStream, section[DataLevel2].offset, tc::io::IOUtil::castSizeToInt64(section[DataLevel2].block_num) * tc::io::IOUtil::castSizeToInt64(section[DataLevel2].block_size)));
}
bool ntd::n3ds::IvfcStream::canRead() const
{
return mDataStream == nullptr ? false : mDataStream->canRead();
}
bool ntd::n3ds::IvfcStream::canWrite() const
{
return false; // always false this is a read-only stream
}
bool ntd::n3ds::IvfcStream::canSeek() const
{
return mDataStream == nullptr ? false : mDataStream->canSeek();
}
int64_t ntd::n3ds::IvfcStream::length()
{
return mDataStream == nullptr ? 0 : mDataStreamLogicalLength;
}
int64_t ntd::n3ds::IvfcStream::position()
{
return mDataStream == nullptr ? 0 : mDataStream->position();
}
size_t ntd::n3ds::IvfcStream::read(byte_t* ptr, size_t count)
{
if (mBaseStream == nullptr)
{
throw tc::ObjectDisposedException(mModuleLabel+"::read()", "Failed to read from stream (stream is disposed)");
}
// track read_count
size_t data_read_count = 0;
// get predicted read count
count = tc::io::IOUtil::getReadableCount(this->length(), this->position(), count);
// if count is 0 just return
if (count == 0) return data_read_count;
// determine begin & end offsets
int64_t begin_read_offset = this->position();
int64_t end_read_offset = begin_read_offset + tc::io::IOUtil::castSizeToInt64(count);
int64_t begin_aligned_offset = begin_read_offset - offsetInBlock(begin_read_offset);
int64_t end_aligned_offset = end_read_offset - offsetInBlock(end_read_offset) + (offsetInBlock(end_read_offset) ? mDataStreamBlockSize : 0x0);
size_t block_num = offsetToBlock(end_aligned_offset - begin_aligned_offset);
bool read_partial_begin_block = false;
size_t partial_begin_block = offsetToBlock(begin_read_offset);
size_t partial_begin_block_offset = 0;
size_t partial_begin_block_size = mDataStreamBlockSize;
bool read_partial_end_block = false;
size_t partial_end_block = offsetToBlock(end_read_offset);
size_t partial_end_block_offset = 0;
size_t partial_end_block_size = mDataStreamBlockSize;
if (offsetInBlock(begin_read_offset) != 0)
{
read_partial_begin_block = true;
partial_begin_block_offset += offsetInBlock(begin_read_offset);
partial_begin_block_size -= partial_begin_block_offset;
}
if (offsetInBlock(end_read_offset) != 0)
{
if (partial_begin_block == partial_end_block)
{
read_partial_begin_block = true;
partial_begin_block_size -= (mDataStreamBlockSize - offsetInBlock(end_read_offset));
}
else
{
read_partial_end_block = true;
partial_end_block_size = offsetInBlock(end_read_offset);
}
}
size_t continuous_block_num = block_num - (size_t)read_partial_begin_block - (size_t)read_partial_end_block;
size_t continuous_begin_block = (continuous_block_num == 0) ? 0 : (offsetToBlock(begin_aligned_offset) + (size_t)read_partial_begin_block);
/*
std::cout << "##############################################" << std::endl;
std::cout << "count: 0x" << std::hex << count << std::endl;
std::cout << "begin_read_offset: 0x" << std::hex << begin_read_offset << std::endl;
std::cout << "end_read_offset: 0x" << std::hex << end_read_offset << std::endl;
std::cout << "begin_aligned_offset: 0x" << std::hex << begin_aligned_offset << std::endl;
std::cout << "end_aligned_offset: 0x" << std::hex << end_aligned_offset << std::endl;
std::cout << "block_num: 0x" << std::hex << block_num << std::endl;
std::cout << "partial_begin:" << std::endl;
std::cout << " read_block: " << std::boolalpha << read_partial_begin_block << std::endl;
std::cout << " block: 0x" << std::hex << partial_begin_block << std::endl;
std::cout << " offset: 0x" << std::hex << partial_begin_block_offset << std::endl;
std::cout << " size: 0x" << std::hex << partial_begin_block_size << std::endl;
std::cout << "partial_end:" << std::endl;
std::cout << " read_block: " << std::boolalpha << read_partial_end_block << std::endl;
std::cout << " block: 0x" << std::hex << partial_end_block << std::endl;
std::cout << " offset: 0x" << std::hex << partial_end_block_offset << std::endl;
std::cout << " size: 0x" << std::hex << partial_end_block_size << std::endl;
std::cout << "continuous:" << std::endl;
std::cout << " block: 0x" << std::hex << continuous_begin_block << std::endl;
std::cout << " block_num: 0x" << std::hex << continuous_block_num << std::endl;
*/
if (block_num == 0)
{
tc::InvalidOperationException("ntd::n3ds::IvfcStream", "Invalid block number (0 blocks, would have returned before now if count==0)");
}
if (block_num < continuous_block_num)
{
tc::InvalidOperationException("ntd::n3ds::IvfcStream", "Invalid block number (underflow error)");
}
// allocate memory for partial block
tc::ByteData partial_block = tc::ByteData(mDataStreamBlockSize);
// read un-aligned begin block
if (read_partial_begin_block)
{
// read block
this->seek(blockToOffset(partial_begin_block), tc::io::SeekOrigin::Begin);
mDataStream->read(partial_block.data(), partial_block.size());
// verify block
if (validateLayerBlocksWithHashLayer(partial_block.data(), mDataStreamBlockSize, 1, getBlockHash(partial_begin_block)) == false)
{
throw tc::crypto::CryptoException("ntd::n3ds::IvfcStream", "Data layer block(s) failed hash validation.");
}
// copy out block carving
memcpy(ptr + data_read_count, partial_block.data() + partial_begin_block_offset, partial_begin_block_size);
// increment data read count
data_read_count += partial_begin_block_size;
}
// read continous blocks
if (continuous_block_num > 0)
{
// read blocks
this->seek(blockToOffset(continuous_begin_block), tc::io::SeekOrigin::Begin);
mDataStream->read(ptr + data_read_count, continuous_block_num * mDataStreamBlockSize);
// verify blocks
if (validateLayerBlocksWithHashLayer(ptr + data_read_count, mDataStreamBlockSize, continuous_block_num, getBlockHash(continuous_begin_block)) == false)
{
throw tc::crypto::CryptoException("ntd::n3ds::IvfcStream", "Data layer block(s) failed hash validation.");
}
// increment data read count
data_read_count += continuous_block_num * mDataStreamBlockSize;
}
// read un-aligned end block
if (read_partial_end_block)
{
// read block
this->seek(blockToOffset(partial_end_block), tc::io::SeekOrigin::Begin);
mDataStream->read(partial_block.data(), partial_block.size());
// verify block
if (validateLayerBlocksWithHashLayer(partial_block.data(), mDataStreamBlockSize, 1, getBlockHash(partial_end_block)) == false)
{
throw tc::crypto::CryptoException("ntd::n3ds::IvfcStream", "Data layer block(s) failed hash validation.");
}
// copy out block carving
memcpy(ptr + data_read_count, partial_block.data() + partial_end_block_offset, partial_end_block_size);
// increment
data_read_count += partial_end_block_size;
}
// restore expected logical position
this->seek(begin_read_offset + tc::io::IOUtil::castSizeToInt64(data_read_count), tc::io::SeekOrigin::Begin);
// return data read count
return data_read_count;
}
size_t ntd::n3ds::IvfcStream::write(const byte_t* ptr, size_t count)
{
throw tc::NotImplementedException(mModuleLabel+"::write()", "write is not supported for IvfcStream");
}
int64_t ntd::n3ds::IvfcStream::seek(int64_t offset, tc::io::SeekOrigin origin)
{
if (mDataStream == nullptr)
{
throw tc::ObjectDisposedException(mModuleLabel+"::seek()", "Failed to set stream position (stream is disposed)");
}
return mDataStream->seek(offset, origin);
}
void ntd::n3ds::IvfcStream::setLength(int64_t length)
{
if (mDataStream == nullptr)
{
throw tc::ObjectDisposedException(mModuleLabel+"::setLength()", "Failed to set stream length (stream is disposed)");
}
throw tc::NotSupportedException(mModuleLabel+"::setLength()", "setLength is not supported for IvfcStream");
}
void ntd::n3ds::IvfcStream::flush()
{
if (mDataStream == nullptr)
{
throw tc::ObjectDisposedException(mModuleLabel+"::seek()", "Failed to flush stream (stream is disposed)");
}
mDataStream->flush();
mBaseStream->flush();
}
void ntd::n3ds::IvfcStream::dispose()
{
if (mDataStream.get() != nullptr)
{
// dispose data stream
mDataStream->dispose();
// release ptr
mDataStream.reset();
}
if (mBaseStream.get() != nullptr)
{
// dispose base stream
mBaseStream->dispose();
// release ptr
mBaseStream.reset();
}
// clear hash cache
mHashCache = tc::ByteData();
}
bool ntd::n3ds::IvfcStream::validateLayerBlocksWithHashLayer(const byte_t* layer, size_t block_size, size_t block_num, const byte_t* hash_layer)
{
size_t bad_block = block_num;
for (size_t i = 0; i < block_num; i++)
{
const byte_t* blk_ptr = layer + (block_size * i);
size_t blk_size = block_size;
const byte_t* blk_hash_ptr = hash_layer + (mHashCalc.kHashSize * i);
//std::cout << tc::cli::FormatUtil::formatBytesAsHxdHexString(blk_hash_ptr, block_size);
mHashCalc.initialize();
mHashCalc.update(blk_ptr, blk_size);
mHashCalc.getHash(mHash.data());
//std::cout << "test hash: " << tc::cli::FormatUtil::formatBytesAsString(blk_hash_ptr, 32, true, ":") << std::endl;
//std::cout << "calc hash: " << tc::cli::FormatUtil::formatBytesAsString(mHash.data(), 32, true, ":") << std::endl;
// if good hash, reduce bad block count
if (memcmp(mHash.data(), blk_hash_ptr, mHashCalc.kHashSize) == 0)
{
bad_block -= 1;
}
else
{
//std::cout << "BadBlock:" << std::endl;
//std::cout << tc::cli::FormatUtil::formatBytesAsHxdHexString(blk_ptr, block_size);
}
}
return bad_block == 0;
}
+360
View File
@@ -0,0 +1,360 @@
#include <ntd/n3ds/RomFsSnapshotGenerator.h>
#include <iostream>
#include <iomanip>
#include <sstream>
#include <tc/cli.h>
#include <tc/io.h>
#include <tc/string.h>
ntd::n3ds::RomFsSnapshotGenerator::RomFsSnapshotGenerator(const std::shared_ptr<tc::io::IStream>& stream) :
FileSystemSnapshot(),
mBaseStream(stream),
mDataOffset(0),
mDirEntryTable(),
mDirParentVaddrMap(),
mFileEntryTable()
{
//std::cout << "RomFsSnapshotGenerator begin" << std::endl;
// validate stream properties
if (mBaseStream == nullptr)
{
throw tc::ObjectDisposedException("ntd::n3ds::RomFsSnapshotGenerator", "Failed to open input stream.");
}
if (mBaseStream->canRead() == false || mBaseStream->canSeek() == false)
{
throw tc::NotSupportedException("ntd::n3ds::RomFsSnapshotGenerator", "Input stream requires read/seek permissions.");
}
//std::cout << "pos() -> " << mBaseStream->position() << std::endl;
// validate and read ROMFS header
ntd::n3ds::RomFsHeader hdr;
if (mBaseStream->length() < sizeof(ntd::n3ds::RomFsHeader))
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::RomFsSnapshotGenerator", "Input stream is too small.");
}
mBaseStream->seek(0, tc::io::SeekOrigin::Begin);
mBaseStream->read((byte_t*)(&hdr), sizeof(ntd::n3ds::RomFsHeader));
/*
std::cout << "hdr.header_size : " << hdr.header_size.unwrap() << std::endl;
std::cout << "sizeof(ntd::n3ds::RomFsHeader) : " << sizeof(ntd::n3ds::RomFsHeader) << std::endl;
std::cout << "hdr.dir_hash_bucket.offset : " << hdr.dir_hash_bucket.offset.unwrap() << std::endl;
std::cout << "hdr.data_offset : " << hdr.data_offset.unwrap() << std::endl;
std::cout << "expected data offset : " << align<uint32_t>(hdr.file_entry.offset.unwrap() + hdr.file_entry.size.unwrap(), ntd::n3ds::RomFsHeader::kRomFsDataAlignSize) << std::endl;
*/
if (hdr.header_size.unwrap() != sizeof(ntd::n3ds::RomFsHeader) ||
hdr.dir_hash_bucket.offset.unwrap() != sizeof(ntd::n3ds::RomFsHeader) ||
hdr.data_offset.unwrap() != align<uint32_t>(hdr.file_entry.offset.unwrap() + hdr.file_entry.size.unwrap(), ntd::n3ds::RomFsHeader::kRomFsDataAlignSize))
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::RomFsSnapshotGenerator", "RomFsHeader is corrupted.");
}
// save data offset
mDataOffset = hdr.data_offset.unwrap();
// get dir entry ptr
mDirEntryTable = tc::ByteData(hdr.dir_entry.size.unwrap());
mBaseStream->seek(hdr.dir_entry.offset.unwrap(), tc::io::SeekOrigin::Begin);
mBaseStream->read(mDirEntryTable.data(), mDirEntryTable.size());
// get file entry ptr
mFileEntryTable = tc::ByteData(hdr.file_entry.size.unwrap());
mBaseStream->seek(hdr.file_entry.offset.unwrap(), tc::io::SeekOrigin::Begin);
mBaseStream->read(mFileEntryTable.data(), mFileEntryTable.size());
//std::cout << "DirTable:" << std::endl;
//std::cout << tc::cli::FormatUtil::formatBytesAsHxdHexString(mDirEntryTable.data(), mDirEntryTable.size());
/*
for (uint32_t v_addr = 0; v_addr < mDirEntryTable.size();)
{
std::cout << "Dir: 0x" << std::hex << v_addr << std::endl;
std::cout << " > parent: 0x" << std::hex << getDirEntry(v_addr)->parent_offset.unwrap() << std::endl;
std::cout << " > sibling: 0x" << std::hex << getDirEntry(v_addr)->sibling_offset.unwrap() << std::endl;
std::cout << " > child_offset: 0x" << std::hex << getDirEntry(v_addr)->child_offset.unwrap() << std::endl;
std::cout << " > file_offset: 0x" << std::hex << getDirEntry(v_addr)->file_offset.unwrap() << std::endl;
std::cout << " > hash_sibling: 0x" << std::hex << getDirEntry(v_addr)->hash_sibling_offset.unwrap() << std::endl;
std::cout << " > name_size: 0x" << std::hex << getDirEntry(v_addr)->name_size.unwrap() << std::endl;
uint32_t total_size = sizeof(ntd::n3ds::RomFsDirectoryEntry) + align<uint32_t>(getDirEntry(v_addr)->name_size.unwrap(), 4);
std::cout << " > entry_size: 0x" << std::hex << total_size << std::endl;
if (getDirEntry(v_addr)->sibling_offset.unwrap() < v_addr)
{
std::cout << "DirEntry looks sus" << std::endl;
break;
}
v_addr += total_size;
}
*/
//std::cout << "FileTable:" << std::endl;
//std::cout << tc::cli::FormatUtil::formatBytesAsHxdHexString(mFileEntryTable.data(), mFileEntryTable.size());
/*
for (uint32_t v_addr = 0; v_addr < mFileEntryTable.size();)
{
std::cout << "File: 0x" << std::hex << v_addr << std::endl;
std::cout << " > parent: 0x" << std::hex << getFileEntry(v_addr)->parent_offset.unwrap() << std::endl;
std::cout << " > sibling: 0x" << std::hex << getFileEntry(v_addr)->sibling_offset.unwrap() << std::endl;
std::cout << " > data_offset: 0x" << std::hex << getFileEntry(v_addr)->data_offset.unwrap() << std::endl;
std::cout << " > data_size: 0x" << std::hex << getFileEntry(v_addr)->data_size.unwrap() << std::endl;
std::cout << " > hash_sibling: 0x" << std::hex << getFileEntry(v_addr)->hash_sibling_offset.unwrap() << std::endl;
std::cout << " > name_size: 0x" << std::hex << getFileEntry(v_addr)->name_size.unwrap() << std::endl;
uint32_t total_size = sizeof(ntd::n3ds::RomFsFileEntry) + align<uint32_t>(getFileEntry(v_addr)->name_size.unwrap(), 4);
std::cout << " > entry_size: 0x" << std::hex << total_size << std::endl;
if (getFileEntry(v_addr)->sibling_offset.unwrap() < v_addr)
{
std::cout << "FileEntry looks sus" << std::endl;
break;
}
v_addr += total_size;
}
*/
if (getDirEntry(0)->parent_offset.unwrap() != 0 ||
getDirEntry(0)->sibling_offset.unwrap() != 0xffffffff ||
getDirEntry(0)->name_size.unwrap() != 0)
{
throw tc::ArgumentOutOfRangeException("ntd::n3ds::RomFsSnapshotGenerator", "Root RomFsDirectoryEntry corrupted.");
}
// add/index directories
DirEntry dir_tmp;
for (uint32_t v_addr = 0; v_addr < mDirEntryTable.size();)
{
// create root entry
if (v_addr == 0)
{
// create dir path
tc::io::Path dir_path = tc::io::Path("/");
dir_tmp.dir_listing.abs_path = dir_path;
// add dir entry to list
dir_entries.push_back(dir_tmp);
// add dir entry to map
dir_entry_path_map[dir_path] = dir_entries.size() - 1;
mDirParentVaddrMap[v_addr] = dir_entries.size() - 1;
}
// else create a regular entry
else
{
// check parent is in map
if (mDirParentVaddrMap.find(getDirEntry(v_addr)->parent_offset.unwrap()) == mDirParentVaddrMap.end())
throw tc::InvalidOperationException("ntd::n3ds::RomFsSnapshotGenerator", "Directory had invalid parent");
// save/transcode file name
std::u16string utf16_string;
std::string utf8_string;
size_t str_len = getDirEntry(v_addr)->name_size.unwrap() / sizeof(uint16_t);
for (size_t i = 0; i < str_len; i++)
{
utf16_string.push_back(getDirEntry(v_addr)->name[i].unwrap());
}
tc::string::TranscodeUtil::UTF16ToUTF8(utf16_string, utf8_string);
// create dir path
size_t parent_index = mDirParentVaddrMap[getDirEntry(v_addr)->parent_offset.unwrap()];
tc::io::Path dir_path = dir_entries[parent_index].dir_listing.abs_path + utf8_string;
dir_tmp.dir_listing.abs_path = dir_path;
// add dir entry to list
dir_entries.push_back(std::move(dir_tmp));
// add dir entry to map
dir_entry_path_map[dir_path] = dir_entries.size() - 1;
mDirParentVaddrMap[v_addr] = dir_entries.size() - 1;
// add name to parent directory listing
dir_entries[parent_index].dir_listing.dir_list.push_back(utf8_string);
}
uint32_t total_size = sizeof(ntd::n3ds::RomFsDirectoryEntry) + align<uint32_t>(getDirEntry(v_addr)->name_size.unwrap(), 4);
if (getDirEntry(v_addr)->sibling_offset.unwrap() < v_addr)
{
throw tc::InvalidOperationException("ntd::n3ds::RomFsSnapshotGenerator", "Possibly corrupted directory entry");
}
v_addr += total_size;
}
// add files
FileEntry file_tmp;
for (uint32_t v_addr = 0; v_addr < mFileEntryTable.size();)
{
// check parent is in map
if (mDirParentVaddrMap.find(getFileEntry(v_addr)->parent_offset.unwrap()) == mDirParentVaddrMap.end())
throw tc::InvalidOperationException("ntd::n3ds::RomFsSnapshotGenerator", "File had invalid parent");
if (getFileEntry(v_addr)->data_size.unwrap() != 0)
{
// substream
//std::cout << "trying to add file" << std::endl;
//std::cout << "offset " << std::hex << getFileEntry(v_addr)->data_offset.unwrap() << std::endl;
//std::cout << "size " << std::hex << getFileEntry(v_addr)->data_size.unwrap() << std::endl;
file_tmp.stream = std::shared_ptr<tc::io::SubStream>(new tc::io::SubStream(mBaseStream, mDataOffset + getFileEntry(v_addr)->data_offset.unwrap(), getFileEntry(v_addr)->data_size.unwrap()));
//std::cout << "file was added" << std::endl;
}
else
{
// empty stream
file_tmp.stream = std::shared_ptr<tc::io::SubStream>(new tc::io::SubStream());
}
// save/transcode file name
std::u16string utf16_string;
std::string utf8_string;
size_t str_len = getFileEntry(v_addr)->name_size.unwrap() / sizeof(uint16_t);
for (size_t i = 0; i < str_len; i++)
{
utf16_string.push_back(getFileEntry(v_addr)->name[i].unwrap());
}
tc::string::TranscodeUtil::UTF16ToUTF8(utf16_string, utf8_string);
// create file path
size_t parent_index = mDirParentVaddrMap[getFileEntry(v_addr)->parent_offset.unwrap()];
tc::io::Path file_path = dir_entries[parent_index].dir_listing.abs_path + utf8_string;
// add file entry to list
file_entries.push_back(std::move(file_tmp));
// add file entry to map
file_entry_path_map[file_path] = file_entries.size() - 1;
// add name to parent directory listing
dir_entries[parent_index].dir_listing.file_list.push_back(utf8_string);
uint32_t total_size = sizeof(ntd::n3ds::RomFsFileEntry) + align<uint32_t>(getFileEntry(v_addr)->name_size.unwrap(), 4);
if (getFileEntry(v_addr)->sibling_offset.unwrap() < v_addr)
{
throw tc::InvalidOperationException("ntd::n3ds::RomFsSnapshotGenerator", "Possibly corrupted file entry");
}
v_addr += total_size;
}
// old style recursive add
//addDirectory(getDirEntry(0), 0);
}
void ntd::n3ds::RomFsSnapshotGenerator::addFile(const ntd::n3ds::RomFsFileEntry* file_entry, size_t parent_dir)
{
// create file entry
FileEntry tmp;
if (file_entry->data_size.unwrap() != 0)
{
// substream
//std::cout << "trying to add file" << std::endl;
//std::cout << "offset " << std::hex << file_entry->data_offset.unwrap() << std::endl;
//std::cout << "size " << std::hex << file_entry->data_size.unwrap() << std::endl;
tmp.stream = std::shared_ptr<tc::io::SubStream>(new tc::io::SubStream(mBaseStream, mDataOffset + file_entry->data_offset.unwrap(), file_entry->data_size.unwrap()));
//std::cout << "file was added" << std::endl;
}
else
{
// empty stream
tmp.stream = std::shared_ptr<tc::io::SubStream>(new tc::io::SubStream());
}
// save/transcode file name
std::u16string utf16_string;
std::string utf8_string;
size_t str_len = file_entry->name_size.unwrap() / sizeof(uint16_t);
for (size_t i = 0; i < str_len; i++)
{
utf16_string.push_back(file_entry->name[i].unwrap());
}
tc::string::TranscodeUtil::UTF16ToUTF8(utf16_string, utf8_string);
// create file path
tc::io::Path file_path = dir_entries[parent_dir].dir_listing.abs_path + utf8_string;
// add file entry to list
file_entries.push_back(tmp);
// add file entry to map
file_entry_path_map[file_path] = file_entries.size() - 1;
// add name to parent directory listing
dir_entries[parent_dir].dir_listing.file_list.push_back(utf8_string);
}
void ntd::n3ds::RomFsSnapshotGenerator::addDirectory(const ntd::n3ds::RomFsDirectoryEntry* dir_entry, size_t parent_dir)
{
// create dir entry
DirEntry tmp;
// save/transcode file name
std::u16string utf16_string;
std::string utf8_string;
size_t str_len = dir_entry->name_size.unwrap() / sizeof(uint16_t);
for (size_t i = 0; i < str_len; i++)
{
utf16_string.push_back(dir_entry->name[i].unwrap());
}
tc::string::TranscodeUtil::UTF16ToUTF8(utf16_string, utf8_string);
// this is the root entry
if (dir_entry->parent_offset.unwrap() == 0 && dir_entry->sibling_offset.unwrap() == 0xffffffff && dir_entry->name_size.unwrap() == 0)
{
// create dir path
tc::io::Path dir_path = tc::io::Path("/");
tmp.dir_listing.abs_path = dir_path;
// add dir entry to list
dir_entries.push_back(tmp);
// add dir entry to map
dir_entry_path_map[dir_path] = dir_entries.size() - 1;
}
// this is a regular directory
else
{
// create dir path
tc::io::Path dir_path = dir_entries[parent_dir].dir_listing.abs_path + utf8_string;
tmp.dir_listing.abs_path = dir_path;
// add dir entry to list
dir_entries.push_back(tmp);
// add dir entry to map
dir_entry_path_map[dir_path] = dir_entries.size() - 1;
// add name to parent directory listing
dir_entries[parent_dir].dir_listing.dir_list.push_back(utf8_string);
}
// get cur_dir pointer
auto cur_dir = dir_entries.size() - 1;
// add file children
for (uint32_t child = dir_entry->file_offset.unwrap();
child != 0xffffffff;
child = getFileEntry(child)->sibling_offset.unwrap())
{
//std::cout << "file child addr :" << std::hex << std::setw(8) << std::setfill('0') << child << std::endl;
addFile(getFileEntry(child), cur_dir);
}
// add dir children
for (uint32_t child = dir_entry->child_offset.unwrap();
child != 0xffffffff;
child = getDirEntry(child)->sibling_offset.unwrap())
{
addDirectory(getDirEntry(child), cur_dir);
}
}
+200
View File
@@ -0,0 +1,200 @@
#include <ntd/n3ds/es/Certificate.h>
#include <fmt/core.h>
#include <brd/es/es_cert.h>
#include <tc/ByteData.h>
#include <tc/crypto/Sha256Generator.h>
#include <tc/cli.h>
size_t ntd::n3ds::es::getCertificateSize(byte_t* data)
{
size_t signature_size = 0;
size_t public_key_size = 0;
if (data == nullptr) { return 0; }
signature_size = getCertificateSignatureSize(data);
if (signature_size == 0) { return 0; }
brd::es::ESCertHeader* header = (brd::es::ESCertHeader*)(data + signature_size);
brd::es::ESCertPubKeyType public_key_type = header->pubKeyType.unwrap();
switch (public_key_type)
{
case brd::es::ESCertPubKeyType::RSA4096:
public_key_size = sizeof(brd::es::ESCertRsa4096PublicKey);
break;
case brd::es::ESCertPubKeyType::RSA2048:
public_key_size = sizeof(brd::es::ESCertRsa2048PublicKey);
break;
case brd::es::ESCertPubKeyType::ECC:
public_key_size = sizeof(brd::es::ESCertEcc233PublicKey);
break;
default:
return 0;
}
return signature_size + sizeof(brd::es::ESCertHeader) + public_key_size;
}
size_t ntd::n3ds::es::getCertificateSignatureSize(byte_t* data)
{
size_t signature_size = 0;
if (data == nullptr) { return 0; }
brd::es::ESSigType sig_type = ((tc::bn::be32<brd::es::ESSigType>*)data)->unwrap();
switch (sig_type)
{
case brd::es::ESSigType::RSA4096_SHA1:
case brd::es::ESSigType::RSA4096_SHA256:
signature_size = sizeof(brd::es::ESSigRsa4096);
break;
case brd::es::ESSigType::RSA2048_SHA1:
case brd::es::ESSigType::RSA2048_SHA256:
signature_size = sizeof(brd::es::ESSigRsa2048);
break;
case brd::es::ESSigType::ECC_SHA1:
case brd::es::ESSigType::ECC_SHA256:
signature_size = sizeof(brd::es::ESSigEcc233);
break;
default:
return 0;
}
return signature_size;
}
size_t ntd::n3ds::es::getCertificateSignableOffset(byte_t* data)
{
size_t signature_size = 0;
if (data == nullptr) { return 0; }
signature_size = getCertificateSignatureSize(data);
if (signature_size == 0) { return 0; }
return signature_size - sizeof(brd::es::ESIssuer);
}
size_t ntd::n3ds::es::getCertificateSignableSize(byte_t* data)
{
return getCertificateSize(data) - getCertificateSignableOffset(data);
}
void* ntd::n3ds::es::getCertificateHeaderPtr(byte_t* data)
{
size_t signature_size = 0;
if (data == nullptr) { return nullptr; }
signature_size = getCertificateSignatureSize(data);
if (signature_size == 0) { return nullptr; }
return (data + signature_size);
}
void* ntd::n3ds::es::getCertificatePublicKeyPtr(byte_t* data)
{
size_t signature_size = 0;
if (data == nullptr) { return nullptr; }
signature_size = getCertificateSignatureSize(data);
if (signature_size == 0) { return nullptr; }
return (data + signature_size + sizeof(brd::es::ESCertHeader));
}
ntd::n3ds::es::CertificateDeserialiser::CertificateDeserialiser(const std::shared_ptr<tc::io::IStream>& cert_stream) :
Certificate(),
mModuleLabel("ntd::n3ds::es::CertificateDeserialiser")
{
if (cert_stream == nullptr)
{
throw tc::ArgumentNullException(mModuleLabel, "Stream was null.");
}
// process signature
this->signature = SignatureDeserialiser(cert_stream);
switch (this->signature.sig_type)
{
case brd::es::ESSigType::RSA4096_SHA1:
case brd::es::ESSigType::RSA4096_SHA256:
case brd::es::ESSigType::RSA2048_SHA1:
case brd::es::ESSigType::RSA2048_SHA256:
case brd::es::ESSigType::ECC_SHA1:
case brd::es::ESSigType::ECC_SHA256:
break;
default:
throw tc::ArgumentOutOfRangeException(mModuleLabel, "CERT had unexpected signature type.");
}
size_t signature_size = getSignatureSizeFromSigType(this->signature.sig_type);
// get certificate header
brd::es::ESCertHeader header;
cert_stream->seek(signature_size, tc::io::SeekOrigin::Begin);
if (cert_stream->read((byte_t*)&header, sizeof(brd::es::ESCertHeader)) < sizeof(brd::es::ESCertHeader))
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "CERT had unexpected size after reading.");
}
// get public key
union CertificatePublicKey
{
brd::es::ESCertRsa4096PublicKey rsa4096;
brd::es::ESCertRsa2048PublicKey rsa2048;
brd::es::ESCertEcc233PublicKey ecc233;
} public_key;
size_t public_key_size;
switch (header.pubKeyType.unwrap())
{
case brd::es::ESCertPubKeyType::RSA4096:
public_key_size = sizeof(brd::es::ESCertRsa4096PublicKey);
break;
case brd::es::ESCertPubKeyType::RSA2048:
public_key_size = sizeof(brd::es::ESCertRsa2048PublicKey);
break;
case brd::es::ESCertPubKeyType::ECC:
public_key_size = sizeof(brd::es::ESCertEcc233PublicKey);
break;
default:
throw tc::ArgumentOutOfRangeException(mModuleLabel, "CERT had unexpected public key type.");
}
cert_stream->seek(signature_size + sizeof(brd::es::ESCertHeader), tc::io::SeekOrigin::Begin);
if (cert_stream->read((byte_t*)&public_key, public_key_size) < public_key_size)
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "CERT had unexpected size after reading.");
}
// calculate hash for optional signature validation later
tc::ByteData total_cert_data = tc::ByteData(signature_size + sizeof(brd::es::ESCertHeader) + public_key_size);
cert_stream->seek(0, tc::io::SeekOrigin::Begin);
if (cert_stream->read(total_cert_data.data(), total_cert_data.size()) < total_cert_data.size())
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "CERT had unexpected size after reading.");
}
tc::crypto::GenerateSha256Hash(calculated_hash.data(), total_cert_data.data() + getCertificateSignableOffset(total_cert_data.data()), ntd::n3ds::es::getCertificateSignableSize(total_cert_data.data()));
// store properties
//this->signature = SignatureDeserialiser(cert_stream);
this->subject = header.name.deviceId.decode();
this->date = header.date.unwrap();
this->public_key_type = header.pubKeyType.unwrap();
switch (header.pubKeyType.unwrap())
{
case brd::es::ESCertPubKeyType::RSA4096:
memcpy(&this->rsa4096_public_key, &public_key.rsa4096.pubKey, sizeof(brd::es::Rsa4096PublicKey));
break;
case brd::es::ESCertPubKeyType::RSA2048:
memcpy(&this->rsa2048_public_key, &public_key.rsa2048.pubKey, sizeof(brd::es::Rsa2048PublicKey));
break;
case brd::es::ESCertPubKeyType::ECC:
memcpy(&this->ecc233_public_key, &public_key.ecc233.pubKey, sizeof(brd::es::Ecc233PublicKey));
break;
}
}
+84
View File
@@ -0,0 +1,84 @@
#include <ntd/n3ds/es/RsaSigner.h>
#include <tc/crypto/RsaPkcs1Sha1Signer.h>
#include <tc/crypto/RsaPkcs1Sha256Signer.h>
ntd::n3ds::es::RsaSigner::RsaSigner(brd::es::ESSigType sig_type, const std::string& issuer, const tc::crypto::RsaKey& rsa_key) :
mSigType(sig_type),
mIssuer(issuer),
mRsaKey(rsa_key)
{
switch (mSigType) {
case brd::es::ESSigType::RSA4096_SHA1:
case brd::es::ESSigType::RSA4096_SHA256:
if ((mRsaKey.n.size() << 3) != 4096) throw tc::ArgumentOutOfRangeException("ntd::n3ds::es::RsaSigner::RsaSigner()", "Key size inferred from SigType did not match actual key size.");
break;
case brd::es::ESSigType::RSA2048_SHA1:
case brd::es::ESSigType::RSA2048_SHA256:
if ((mRsaKey.n.size() << 3) != 2048) throw tc::ArgumentOutOfRangeException("ntd::n3ds::es::RsaSigner::RsaSigner()", "Key size inferred from SigType did not match actual key size.");
break;
default:
throw tc::ArgumentOutOfRangeException("ntd::n3ds::es::RsaSigner::RsaSigner()", "SigType not supported for RsaSigner.");
break;
}
}
const std::string& ntd::n3ds::es::RsaSigner::getIssuer()
{
return mIssuer;
}
brd::es::ESSigType ntd::n3ds::es::RsaSigner::getSigType()
{
return mSigType;
}
bool ntd::n3ds::es::RsaSigner::signHash(const byte_t* hash, byte_t* signature)
{
bool signSucceed = false;
switch (mSigType) {
case brd::es::ESSigType::RSA4096_SHA1:
signSucceed = tc::crypto::SignRsa4096Pkcs1Sha1(signature, hash, mRsaKey);
break;
case brd::es::ESSigType::RSA2048_SHA1:
signSucceed = tc::crypto::SignRsa2048Pkcs1Sha1(signature, hash, mRsaKey);
break;
case brd::es::ESSigType::RSA4096_SHA256:
signSucceed = tc::crypto::SignRsa4096Pkcs1Sha256(signature, hash, mRsaKey);
break;
case brd::es::ESSigType::RSA2048_SHA256:
signSucceed = tc::crypto::SignRsa2048Pkcs1Sha256(signature, hash, mRsaKey);
break;
default:
signSucceed = false;
break;
}
return signSucceed;
}
bool ntd::n3ds::es::RsaSigner::verifyHash(const byte_t* hash, const byte_t* signature)
{
bool verifySucceed = false;
switch (mSigType) {
case brd::es::ESSigType::RSA4096_SHA1:
verifySucceed = tc::crypto::VerifyRsa4096Pkcs1Sha1(signature, hash, mRsaKey);
break;
case brd::es::ESSigType::RSA2048_SHA1:
verifySucceed = tc::crypto::VerifyRsa2048Pkcs1Sha1(signature, hash, mRsaKey);
break;
case brd::es::ESSigType::RSA4096_SHA256:
verifySucceed = tc::crypto::VerifyRsa4096Pkcs1Sha256(signature, hash, mRsaKey);
break;
case brd::es::ESSigType::RSA2048_SHA256:
verifySucceed = tc::crypto::VerifyRsa2048Pkcs1Sha256(signature, hash, mRsaKey);
break;
default:
verifySucceed = false;
break;
}
return verifySucceed;
}
+138
View File
@@ -0,0 +1,138 @@
#include <ntd/n3ds/es/Signature.h>
#include <fmt/core.h>
#include <brd/es/es_cert.h>
#include <tc/ByteData.h>
#include <tc/crypto/Sha256Generator.h>
#include <tc/cli.h>
size_t ntd::n3ds::es::getSignatureSizeFromSigType(brd::es::ESSigType sig_type)
{
size_t signature_size = 0;
switch (sig_type)
{
case brd::es::ESSigType::RSA4096_SHA1:
case brd::es::ESSigType::RSA4096_SHA256:
signature_size = sizeof(brd::es::ESSigRsa4096);
break;
case brd::es::ESSigType::RSA2048_SHA1:
case brd::es::ESSigType::RSA2048_SHA256:
signature_size = sizeof(brd::es::ESSigRsa2048);
break;
case brd::es::ESSigType::ECC_SHA1:
case brd::es::ESSigType::ECC_SHA256:
signature_size = sizeof(brd::es::ESSigEcc233);
break;
default:
signature_size = 0;
}
return signature_size;
}
size_t ntd::n3ds::es::getSignatureIssuerOffset(brd::es::ESSigType sig_type)
{
size_t issuer_offset = 0;
switch (sig_type)
{
case brd::es::ESSigType::RSA4096_SHA1:
case brd::es::ESSigType::RSA4096_SHA256:
issuer_offset = sizeof(brd::es::ESSigRsa4096) - sizeof(brd::es::ESIssuer);
break;
case brd::es::ESSigType::RSA2048_SHA1:
case brd::es::ESSigType::RSA2048_SHA256:
issuer_offset = sizeof(brd::es::ESSigRsa2048) - sizeof(brd::es::ESIssuer);
break;
case brd::es::ESSigType::ECC_SHA1:
case brd::es::ESSigType::ECC_SHA256:
issuer_offset = sizeof(brd::es::ESSigEcc233) - sizeof(brd::es::ESIssuer);
break;
default:
issuer_offset = 0;
}
return issuer_offset;
}
ntd::n3ds::es::SignatureDeserialiser::SignatureDeserialiser(const std::shared_ptr<tc::io::IStream>& stream) :
Signature(),
mModuleLabel("ntd::n3ds::es::SignatureDeserialiser")
{
if (stream == nullptr)
{
throw tc::ArgumentNullException(mModuleLabel, "Stream was null.");
}
// must have at least 4 bytes for signature magic code
if (stream->length() < sizeof(uint32_t))
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "Stream was too small to import signature.");
}
// get signature
enum class SignType {
RSA4096,
RSA2048,
ECDSA233,
};
union SignatureSignature
{
tc::bn::be32<brd::es::ESSigType> sigType;
brd::es::ESSigRsa4096 rsa4096;
brd::es::ESSigRsa2048 rsa2048;
brd::es::ESSigEcc233 ecdsa233;
} signature_data;
size_t signature_size = 0;
stream->seek(0, tc::io::SeekOrigin::Begin);
if (stream->read((byte_t*)&signature_data, sizeof(uint32_t)) < sizeof(uint32_t))
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "Unexpected size after reading.");
}
switch (signature_data.sigType.unwrap())
{
case brd::es::ESSigType::RSA4096_SHA1:
case brd::es::ESSigType::RSA4096_SHA256:
signature_size = sizeof(brd::es::ESSigRsa4096);
break;
case brd::es::ESSigType::RSA2048_SHA1:
case brd::es::ESSigType::RSA2048_SHA256:
signature_size = sizeof(brd::es::ESSigRsa2048);
break;
case brd::es::ESSigType::ECC_SHA1:
case brd::es::ESSigType::ECC_SHA256:
signature_size = sizeof(brd::es::ESSigEcc233);
break;
default:
throw tc::ArgumentOutOfRangeException(mModuleLabel, "Unexpected signature type.");
}
stream->seek(0, tc::io::SeekOrigin::Begin);
if (stream->read((byte_t*)&signature_data, signature_size) < signature_size)
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "Unexpected size after reading.");
}
// store properties
this->sig_type = signature_data.sigType.unwrap();
switch (signature_data.sigType.unwrap())
{
case brd::es::ESSigType::RSA4096_SHA1:
case brd::es::ESSigType::RSA4096_SHA256:
this->sig = tc::ByteData(signature_data.rsa4096.sig.data(), signature_data.rsa4096.sig.size());
this->issuer = signature_data.rsa4096.issuer.decode();
break;
case brd::es::ESSigType::RSA2048_SHA1:
case brd::es::ESSigType::RSA2048_SHA256:
this->sig = tc::ByteData(signature_data.rsa2048.sig.data(), signature_data.rsa2048.sig.size());
this->issuer = signature_data.rsa2048.issuer.decode();
break;
case brd::es::ESSigType::ECC_SHA1:
case brd::es::ESSigType::ECC_SHA256:
this->sig = tc::ByteData((byte_t*)&signature_data.ecdsa233.sig, sizeof(brd::es::Ecc233Sig));
this->issuer = signature_data.ecdsa233.issuer.decode();
break;
default:
break;
}
}
+182
View File
@@ -0,0 +1,182 @@
#include <ntd/n3ds/es/Ticket.h>
#include <fmt/core.h>
#include <brd/es/es_ticket.h>
#include <tc/ByteData.h>
#include <tc/crypto/Sha256Generator.h>
#include <tc/cli.h>
ntd::n3ds::es::TicketDeserialiser::TicketDeserialiser(const std::shared_ptr<tc::io::IStream>& tik_stream) :
Ticket(),
mModuleLabel("ntd::n3ds::es::TicketDeserialiser")
{
if (tik_stream == nullptr)
{
throw tc::ArgumentNullException(mModuleLabel, "Stream was null.");
}
if (tik_stream->length() < sizeof(brd::es::ESV1Ticket))
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "Stream was too small to import ticket.");
}
// import TIK v1 data (less the section headers)
tc::ByteData tik_data = tc::ByteData(sizeof(brd::es::ESV1Ticket));
tik_stream->seek(0, tc::io::SeekOrigin::Begin);
if (tik_stream->read(tik_data.data(), tik_data.size()) < tik_data.size())
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TIK had unexpected size after reading.");
}
// get pointer
brd::es::ESV1Ticket* tik = (brd::es::ESV1Ticket*)tik_data.data();
if (tik->head.sig.sigType.unwrap() != brd::es::ESSigType::RSA2048_SHA256)
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TIK had unexpected signature type.");
}
if (tik->head.version != 1)
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TIK had unexpected format version.");
}
if (tik->v1Head.hdrVersion.unwrap() != 1)
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TIK v1 header extension had unexpected format version.");
}
if (tik->v1Head.hdrSize.unwrap() != sizeof(brd::es::ESV1TicketHeader))
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TIK v1 header extension had header size.");
}
if (tik->v1Head.sectHdrOfst.unwrap() != sizeof(brd::es::ESV1TicketHeader))
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TIK v1 header extension had poorly aligned sectHdrOfst.");
}
if (tik->v1Head.sectHdrEntrySize.unwrap() != sizeof(brd::es::ESV1SectionHeader))
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TIK v1 header extension had unexpected size for section headers.");
}
if (tik_stream->length() < static_cast<int64_t>(sizeof(brd::es::ESTicket) + tik->v1Head.ticketSize.unwrap()))
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "Stream was too small for calculated ticket size.");
}
// import full TIK v1 data
tik_data = tc::ByteData(sizeof(brd::es::ESTicket) + tik->v1Head.ticketSize.unwrap());
tik_stream->seek(0, tc::io::SeekOrigin::Begin);
if (tik_stream->read(tik_data.data(), tik_data.size()) < tik_data.size())
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TIK had unexpected size after reading.");
}
// update pointer
tik = (brd::es::ESV1Ticket*)tik_data.data();
/*
fmt::print("ESTicket:\n");
fmt::print("sigType: {:x}\n", tik->head.sig.sigType.unwrap());
fmt::print("issuer: {}\n", tik->head.sig.issuer.str());
fmt::print("version: {:d}\n", tik->head.version);
fmt::print("caCrlVersion: {:d}\n", tik->head.caCrlVersion);
fmt::print("signerCrlVersion: {:d}\n", tik->head.signerCrlVersion);
fmt::print("titleKey: {}\n", tc::cli::FormatUtil::formatBytesAsString(tik->head.titleKey.data(), tik->head.titleKey.size(), true, ""));
fmt::print("ticketId: {:016x}\n", tik->head.ticketId.unwrap());
fmt::print("deviceId: {:08x}\n", tik->head.deviceId.unwrap());
fmt::print("titleId: {:016x}\n", tik->head.titleId.unwrap());
fmt::print("sysAccessMask: {}\n", tc::cli::FormatUtil::formatBytesAsString(tik->head.sysAccessMask.data(), tik->head.sysAccessMask.size(), true, ""));
fmt::print("ticketVersion: {:d}\n", tik->head.ticketVersion.unwrap());
fmt::print("accessTitleId: {:08x}\n", tik->head.accessTitleId.unwrap());
fmt::print("accessTitleMask: {:08x}\n", tik->head.accessTitleMask.unwrap());
fmt::print("licenseType: {:02x}\n", tik->head.licenseType);
fmt::print("keyId: {:02x}\n", tik->head.keyId);
fmt::print("propertyMask: {:04x}\n", tik->head.propertyMask.unwrap());
fmt::print("customData: {}\n", tc::cli::FormatUtil::formatBytesAsString(tik->head.customData.data(), tik->head.customData.size(), true, ""));
fmt::print("audit: {:02x}\n", tik->head.audit);
fmt::print("cidxMask: {}\n", tc::cli::FormatUtil::formatBytesAsString(tik->head.cidxMask.data(), tik->head.cidxMask.size(), true, ""));
for (size_t i = 0; i < tik->head.limits.size(); i++)
{
fmt::print("lp entry {}: code={:08x}, limit={:08x}\n", i, tik->head.limits[i].code.unwrap(), tik->head.limits[i].limit.unwrap());
}
fmt::print("ESV1TicketHeader:\n");
fmt::print("hdrVersion: {:04x}\n", tik->v1Head.hdrVersion.unwrap());
fmt::print("hdrSize: {:04x}\n", tik->v1Head.hdrSize.unwrap());
fmt::print("ticketSize: {:08x}\n", tik->v1Head.ticketSize.unwrap());
fmt::print("sectHdrOfst: {:08x}\n", tik->v1Head.sectHdrOfst.unwrap());
fmt::print("nSectHdrs: {:04x}\n", tik->v1Head.nSectHdrs.unwrap());
fmt::print("sectHdrEntrySize: {:04x}\n", tik->v1Head.sectHdrEntrySize.unwrap());
fmt::print("flags: {:08x}\n", tik->v1Head.flags.unwrap());
size_t sect_hdr_num = tik->v1Head.nSectHdrs.unwrap();
//brd::es::ESV1SectionHeader* sect_hdr = (brd::es::ESV1SectionHeader*)(tik_data.data() + sizeof(brd::es::ESTicket) + tik->v1Head.sectHdrOfst.unwrap());
for (size_t i = 0; i < sect_hdr_num; i++)
{
fmt::print("sectHdr {:d}:\n", i);
fmt::print(" sectOfst: {:08x}\n", tik->sectHdrs[i].sectOfst.unwrap());
fmt::print(" nRecords: {:08x}\n", tik->sectHdrs[i].nRecords.unwrap());
fmt::print(" recordSize: {:08x}\n", tik->sectHdrs[i].recordSize.unwrap());
fmt::print(" sectionSize: {:08x}\n", tik->sectHdrs[i].sectionSize.unwrap());
fmt::print(" sectionType: {:04x}\n", tik->sectHdrs[i].sectionType.unwrap());
fmt::print(" flags: {:04x}\n", tik->sectHdrs[i].flags.unwrap());
}
*/
struct TicketReservedForCtr
{
tc::bn::pad<0x14> reserved_00;
tc::bn::le32<uint32_t> ec_account_id;
tc::bn::pad<0x01> reserved_01;
};
// generate hash
byte_t* tik_hash_begin = (byte_t*)&tik->head.sig.issuer;
size_t tik_hash_size = tik_data.size() - size_t(tik_hash_begin - tik_data.data());
tc::crypto::GenerateSha256Hash(this->calculated_hash.data(), tik_hash_begin, tik_hash_size);
// basic fields
this->signature.sig_type = tik->head.sig.sigType.unwrap();
this->signature.sig = tc::ByteData(tik->head.sig.sig.data(), tik->head.sig.sig.size());
this->signature.issuer = tik->head.sig.issuer.decode();
this->title_key = tik->head.titleKey;
this->ticket_id = tik->head.ticketId.unwrap();
this->device_id = tik->head.deviceId.unwrap();
this->title_id = tik->head.titleId.unwrap();
this->ticket_version = tik->head.ticketVersion.unwrap();
this->license_type = tik->head.licenseType;
this->key_id = tik->head.keyId;
// process data from reserved field
auto custom_data = (TicketReservedForCtr*)tik->head.reserved.data();
this->ec_account_id = custom_data->ec_account_id.unwrap();
// find the demo launch limit
for (size_t i = 0; i < tik->head.limits.size(); i++)
{
if (tik->head.limits[i].code.unwrap() == (uint32_t)brd::es::ESLimitCode::NUM_LAUNCH)
{
launch_count = tik->head.limits[i].limit.unwrap();
}
}
// process content index
for (size_t i = 0; i < tik->v1Head.nSectHdrs.unwrap(); i++)
{
if (tik->sectHdrs[i].sectionType.unwrap() == (uint32_t)brd::es::ESItemType::CONTENT)
{
if (tik->sectHdrs[i].recordSize.unwrap() != sizeof(brd::es::ESV1ContentRecord))
{
throw tc::InvalidOperationException(mModuleLabel, fmt::format("Invalid size for ESV1ContentRecord. (expected: 0x{:x}, got 0x{:x})", sizeof(brd::es::ESV1ContentRecord), tik->sectHdrs[i].recordSize.unwrap()));
}
brd::es::ESV1ContentRecord* content_records = (brd::es::ESV1ContentRecord*)(tik_data.data() + sizeof(brd::es::ESTicket) + tik->sectHdrs[i].sectOfst.unwrap());
for (size_t j = 0; j < tik->sectHdrs[i].nRecords.unwrap(); j++)
{
tc::bn::bitarray<0x80>* access_mask = (tc::bn::bitarray<0x80>*)content_records[j].accessMask.data();
for (size_t bit = 0; bit < access_mask->bit_size(); bit++)
{
if (access_mask->test(bit))
{
this->enabled_content.set(content_records[j].offset.unwrap() + bit);
}
}
}
}
}
}
+143
View File
@@ -0,0 +1,143 @@
#include <ntd/n3ds/es/TitleMetaData.h>
#include <fmt/core.h>
#include <brd/es/es_tmd.h>
#include <tc/ByteData.h>
#include <tc/crypto/Sha256Generator.h>
ntd::n3ds::es::TitleMetaDataDeserialiser::TitleMetaDataDeserialiser(const std::shared_ptr<tc::io::IStream>& tmd_stream) :
TitleMetaData(),
mModuleLabel("ntd::n3ds::es::TitleMetaDataDeserialiser")
{
if (tmd_stream == nullptr)
{
throw tc::ArgumentNullException(mModuleLabel, "TMD stream was null.");
}
if (tmd_stream->length() < (sizeof(brd::es::ESV1TitleMeta) + sizeof(brd::es::ESV1ContentMeta)))
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TMD was too small.");
}
// import TMD v1 data (less the ESV1ContentMeta data)
tc::ByteData tmd_data = tc::ByteData(sizeof(brd::es::ESV1TitleMeta));
tmd_stream->seek(0, tc::io::SeekOrigin::Begin);
if (tmd_stream->read(tmd_data.data(), tmd_data.size()) < tmd_data.size())
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TMD had unexpected size after reading.");
}
// get pointer
brd::es::ESV1TitleMeta* tmd = (brd::es::ESV1TitleMeta*)tmd_data.data();
if (tmd->sig.sigType.unwrap() != brd::es::ESSigType::RSA2048_SHA256)
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TMD had unexpected signature type.");
}
if (tmd->head.version != 1)
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TMD had unexpected format version.");
}
/*
std::cout << "[Tmd]" << std::endl;
std::cout << " > Issuer: " << tmd->sig.issuer.data() << std::endl;
std::cout << " > Version: " << (int)tmd->head.version << std::endl;
std::cout << " > CACrl Version: " << (int)tmd->head.caCrlVersion << std::endl;
std::cout << " > SignerCrl Version: " << (int)tmd->head.signerCrlVersion << std::endl;
std::cout << " > TitleId: " << std::hex << std::setfill('0') << std::setw(16) << tmd->head.titleId.unwrap() << std::endl;
*/
// determine if the TMD is the correct size given expected number of ESV1ContentMeta entries
size_t cmd_table_num = tmd->v1Head.cmdGroups[0].nCmds.unwrap();
size_t tmd_full_size = sizeof(brd::es::ESV1TitleMeta) + (cmd_table_num * sizeof(brd::es::ESV1ContentMeta));
if (tmd_stream->length() < (tmd_full_size))
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TMD was too small.");
}
// import full TMD v1 data
tmd_data = tc::ByteData(tmd_full_size);
tmd_stream->seek(0, tc::io::SeekOrigin::Begin);
if (tmd_stream->read(tmd_data.data(), tmd_data.size()) < tmd_data.size())
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TMD had unexpected size after reading.");
}
// update tmd pointer
tmd = (brd::es::ESV1TitleMeta*)tmd_data.data();
// hash for staged validiation
std::array<byte_t, tc::crypto::Sha256Generator::kHashSize> hash;
// calculate hash for optional signature validation later
tc::crypto::GenerateSha256Hash(calculated_hash.data(), (byte_t*)&tmd->sig.issuer, (size_t)((byte_t*)&tmd->v1Head.cmdGroups - (byte_t*)&tmd->sig.issuer));
// verify v1 ESV1ContentMetaGroup array using hash in v1 header
tc::crypto::GenerateSha256Hash(hash.data(), (byte_t*)&tmd->v1Head.cmdGroups, sizeof(tmd->v1Head.cmdGroups));
if (memcmp(hash.data(), tmd->v1Head.hash.data(), hash.size()) != 0)
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TMD had invalid CMD group hash.");
}
// verify ESV1ContentMeta array
tc::crypto::GenerateSha256Hash(hash.data(), (byte_t*)&tmd->contents, cmd_table_num * sizeof(brd::es::ESV1ContentMeta));
if (memcmp(hash.data(), tmd->v1Head.cmdGroups[0].groupHash.data(), hash.size()) != 0)
{
throw tc::ArgumentOutOfRangeException(mModuleLabel, "TMD had invalid CMD group[0] hash.");
}
// verify other fields
if (tmd->head.sysVersion.unwrap() != 0)
{
throw tc::InvalidOperationException(mModuleLabel, "TMD sysVersion had unexpected value.");
}
if (tmd->head.type.unwrap() != brd::es::ESTitleType::CT_TITLE)
{
throw tc::InvalidOperationException(mModuleLabel, "TMD type had unexpected value.");
}
// process header now that it is verified
this->signature.sig_type = tmd->sig.sigType.unwrap();
this->signature.sig = tc::ByteData(tmd->sig.sig.data(), tmd->sig.sig.size());
this->signature.issuer = tmd->sig.issuer.decode();
this->title_id = tmd->head.titleId.unwrap();
this->title_version = tmd->head.titleVersion.unwrap();
struct TmdCustomDataForCtr
{
struct TwlCustomData
{
tc::bn::le32<uint32_t> public_save_data_size;
tc::bn::le32<uint32_t> private_save_data_size;
tc::bn::pad<4> padding;
byte_t flag;
};
struct CtrCustomData
{
tc::bn::le32<uint32_t> save_data_size;
tc::bn::bitarray<1> flag;
};
union {
TwlCustomData twl;
CtrCustomData ctr;
};
};
auto custom_data = (TmdCustomDataForCtr*)tmd->head.customData.data();
this->twl_custom_data.public_save_data_size = custom_data->twl.public_save_data_size;
this->twl_custom_data.private_save_data_size = custom_data->twl.private_save_data_size;
this->twl_custom_data.flag = custom_data->twl.flag;
this->ctr_custom_data.save_data_size = custom_data->ctr.save_data_size;
this->ctr_custom_data.is_snake_only = custom_data->ctr.flag.test(0);
// process ESV1ContentMeta entries
for (size_t i = 0; i < cmd_table_num; i++)
{
this->content_info.push_back(ContentInfo(
tmd->contents[i].cid.unwrap(),
tmd->contents[i].index.unwrap(),
(tmd->contents[i].type.unwrap() & (uint16_t)brd::es::ESContentType_ENCRYPTED) == (uint16_t)brd::es::ESContentType_ENCRYPTED,
(tmd->contents[i].type.unwrap() & (uint16_t)brd::es::ESContentType_OPTIONAL) == (uint16_t)brd::es::ESContentType_OPTIONAL,
(int64_t)tmd->contents[i].size.unwrap(),
tmd->contents[i].hash));
}
}