mirror of
https://github.com/DarkStore-3DS/Project_CTR.git
synced 2026-07-06 08:49:04 +00:00
Put everything back.
This commit is contained in:
@@ -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.
|
||||
@@ -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
|
||||
+185
@@ -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>
|
||||
+147
@@ -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>
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
//#include <ntd/n3ds/es/Certficate.h>
|
||||
#include <ntd/n3ds/es/Ticket.h>
|
||||
#include <ntd/n3ds/es/TitleMetaData.h>
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)";)
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user