Modernize NDecrypt

This commit is contained in:
Matt Nadareski
2024-07-20 22:00:54 -04:00
parent e2c2c804a5
commit 2df30d095b
48 changed files with 852 additions and 994 deletions

53
.github/workflows/build_program.yml vendored Normal file
View File

@@ -0,0 +1,53 @@
name: Build Program
on:
push:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
project: [NDecrypt]
runtime: [win-x86, win-x64, win-arm64, linux-x64, linux-arm64, osx-x64]
framework: [net8.0] #[net20, net35, net40, net452, net472, net48, netcoreapp3.1, net5.0, net6.0, net7.0, net8.0]
conf: [Release, Debug]
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet publish ${{ matrix.project }}/${{ matrix.project }}.csproj -f ${{ matrix.framework }} -r ${{ matrix.runtime }} -c ${{ matrix.conf == 'Release' && 'Release -p:DebugType=None -p:DebugSymbols=false' || 'Debug'}} --self-contained true --version-suffix ${{ github.sha }} ${{ (startsWith(matrix.framework, 'net5') || startsWith(matrix.framework, 'net6') || startsWith(matrix.framework, 'net7') || startsWith(matrix.framework, 'net8')) && '-p:PublishSingleFile=true' || ''}}
- name: Archive build
run: zip -r ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_${{ matrix.conf }}.zip ${{ matrix.project }}/bin/${{ matrix.conf }}/${{ matrix.framework }}/${{ matrix.runtime }}/publish/
- name: Upload build
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_${{ matrix.conf }}
path: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_${{ matrix.conf }}.zip
- name: Upload to rolling
uses: ncipollo/release-action@v1.14.0
with:
allowUpdates: True
artifacts: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_${{ matrix.conf }}.zip
body: 'Last built commit: ${{ github.sha }}'
name: 'Rolling Release'
prerelease: True
replacesArtifacts: True
tag: "rolling"
updateOnlyUnreleased: True

17
.github/workflows/check_pr.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
name: Build PR
on: [pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Build
run: dotnet build

View File

@@ -1,4 +1,4 @@
Copyright (c) 2018-2023 Matt Nadareski
Copyright (c) 2018-2024 Matt Nadareski
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:

View File

@@ -2,7 +2,7 @@ using System;
using System.IO;
using System.Linq;
using System.Numerics;
using NDecrypt.Core.Tools;
using SabreTools.IO.Readers;
namespace NDecrypt.Core
{
@@ -37,7 +37,7 @@ namespace NDecrypt.Core
/// <summary>
/// Path to the keyfile [3DS only]
/// </summary>
public string KeyFile { get; set; }
public string? KeyFile { get; set; }
/// <summary>
/// Flag to indicate keyfile format to use [3DS only]
@@ -119,9 +119,9 @@ namespace NDecrypt.Core
/// Setup all of the necessary constants from aes_keys.txt
/// </summary>
/// <param name="keyfile">Path to aes_keys.txt</param>
private void InitAesKeysTxt(string keyfile)
private void InitAesKeysTxt(string? keyfile)
{
if (!File.Exists(keyfile))
if (keyfile == null || !File.Exists(keyfile))
{
IsReady = false;
return;
@@ -129,7 +129,7 @@ namespace NDecrypt.Core
try
{
using (IniReader reader = new IniReader(keyfile))
using (var reader = new IniReader(keyfile))
{
// This is required to preserve sign for BigInteger
byte[] signByte = new byte[] { 0x00 };
@@ -142,7 +142,7 @@ namespace NDecrypt.Core
if (reader.KeyValuePair == null || string.IsNullOrWhiteSpace(reader.KeyValuePair?.Key))
break;
var kvp = reader.KeyValuePair.Value;
var kvp = reader.KeyValuePair!.Value;
byte[] value = StringToByteArray(kvp.Value).Reverse().ToArray();
byte[] valueWithSign = value.Concat(signByte).ToArray();
@@ -240,9 +240,9 @@ namespace NDecrypt.Core
/// </summary>
/// <param name="keyfile">Path to keys.bin</param>
/// <remarks>keys.bin should be in little endian format</remarks>
private void InitKeysBin(string keyfile)
private void InitKeysBin(string? keyfile)
{
if (!File.Exists(keyfile))
if (keyfile == null || !File.Exists(keyfile))
{
IsReady = false;
return;

View File

@@ -1,21 +1,54 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net48;net6.0</TargetFrameworks>
<!-- Assembly Properties -->
<TargetFrameworks>net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<VersionPrefix>0.2.5</VersionPrefix>
<!-- Package Properties -->
<Authors>Matt Nadareski</Authors>
<Copyright>Copyright (c)2018-2023 Matt Nadareski</Copyright>
<Description>Common code for all NDecrypt processors</Description>
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<RepositoryUrl>https://github.com/SabreTools/NDecrypt</RepositoryUrl>
<Version>0.2.5</Version>
<AssemblyVersion>$(Version)</AssemblyVersion>
<FileVersion>$(Version)</FileVersion>
<IncludeSource>true</IncludeSource>
<IncludeSymbols>true</IncludeSymbols>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<!-- Support All Frameworks -->
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))">
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
</PropertyGroup>
<!-- Support for old .NET versions -->
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net40`))">
<PackageReference Include="MinAsyncBridge" Version="0.12.4" />
<PackageReference Include="MinTasksExtensionsBridge" Version="0.3.4" />
<PackageReference Include="MinThreadingBridge" Version="0.11.4" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith(`net4`)) AND !$(TargetFramework.StartsWith(`net40`))">
<PackageReference Include="IndexRange" Version="1.0.3" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BouncyCastle.NetCore" Version="1.9.0" />
<PackageReference Include="SabreTools.Hashing" Version="1.2.1" />
<PackageReference Include="SabreTools.IO" Version="1.4.11" />
</ItemGroup>
</Project>

View File

@@ -1,36 +0,0 @@
using System;
namespace NDecrypt.Core.Tools
{
/// <summary>
/// Available hashing types
/// </summary>
[Flags]
public enum Hash
{
CRC = 1 << 0,
MD5 = 1 << 1,
SHA1 = 1 << 2,
SHA256 = 1 << 3,
SHA384 = 1 << 4,
SHA512 = 1 << 5,
// Special combinations
Standard = CRC | MD5 | SHA1,
DeepHashes = SHA256 | SHA384 | SHA512,
SecureHashes = MD5 | SHA1 | SHA256 | SHA384 | SHA512,
All = CRC | MD5 | SHA1 | SHA256 | SHA384 | SHA512,
}
/// <summary>
/// Different types of INI rows being parsed
/// </summary>
public enum IniRowType
{
None,
SectionHeader,
KeyValue,
Comment,
Invalid,
}
}

View File

@@ -1,123 +0,0 @@
using System;
using System.Linq;
using System.Security.Cryptography;
namespace NDecrypt.Core.Tools
{
/// <summary>
/// Async hashing class wraper
/// </summary>
public class Hasher
{
public Hash HashType { get; private set; }
private IDisposable _hasher;
public Hasher(Hash hashType)
{
this.HashType = hashType;
GetHasher();
}
/// <summary>
/// Generate the correct hashing class based on the hash type
/// </summary>
private void GetHasher()
{
switch (HashType)
{
case Hash.CRC:
_hasher = new OptimizedCRC.OptimizedCRC();
break;
case Hash.MD5:
_hasher = MD5.Create();
break;
case Hash.SHA1:
_hasher = SHA1.Create();
break;
case Hash.SHA256:
_hasher = SHA256.Create();
break;
case Hash.SHA384:
_hasher = SHA384.Create();
break;
case Hash.SHA512:
_hasher = SHA512.Create();
break;
}
}
public void Dispose()
{
_hasher.Dispose();
}
/// <summary>
/// Process a buffer of some length with the internal hash algorithm
/// </summary>
public void Process(byte[] buffer, int size)
{
switch (HashType)
{
case Hash.CRC:
(_hasher as OptimizedCRC.OptimizedCRC).Update(buffer, 0, size);
break;
case Hash.MD5:
case Hash.SHA1:
case Hash.SHA256:
case Hash.SHA384:
case Hash.SHA512:
(_hasher as HashAlgorithm).TransformBlock(buffer, 0, size, null, 0);
break;
}
}
/// <summary>
/// Terminate the internal hash algorigthm
/// </summary>
public void Terminate()
{
byte[] emptyBuffer = new byte[0];
switch (HashType)
{
case Hash.CRC:
(_hasher as OptimizedCRC.OptimizedCRC).Update(emptyBuffer, 0, 0);
break;
case Hash.MD5:
case Hash.SHA1:
case Hash.SHA256:
case Hash.SHA384:
case Hash.SHA512:
(_hasher as HashAlgorithm).TransformFinalBlock(emptyBuffer, 0, 0);
break;
}
}
/// <summary>
/// Get internal hash as a byte array
/// </summary>
public byte[] GetHash()
{
switch (HashType)
{
case Hash.CRC:
return BitConverter.GetBytes((_hasher as OptimizedCRC.OptimizedCRC).Value).Reverse().ToArray();
case Hash.MD5:
case Hash.SHA1:
case Hash.SHA256:
case Hash.SHA384:
case Hash.SHA512:
return (_hasher as HashAlgorithm).Hash;
}
return null;
}
}
}

View File

@@ -1,148 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace NDecrypt.Core.Tools
{
public class IniReader : IDisposable
{
/// <summary>
/// Internal stream reader for inputting
/// </summary>
private readonly StreamReader sr;
/// <summary>
/// Get if at end of stream
/// </summary>
public bool EndOfStream
{
get
{
return sr?.EndOfStream ?? true;
}
}
/// <summary>
/// Contents of the currently read line as a key value pair
/// </summary>
public KeyValuePair<string, string>? KeyValuePair { get; private set; } = null;
/// <summary>
/// Contents of the current line, unprocessed
/// </summary>
public string CurrentLine { get; private set; } = string.Empty;
/// <summary>
/// Get the current line number
/// </summary>
public long LineNumber { get; private set; } = 0;
/// <summary>
/// Current row type
/// </summary>
public IniRowType RowType { get; private set; } = IniRowType.None;
/// <summary>
/// Current section being read
/// </summary>
public string Section { get; private set; } = string.Empty;
/// <summary>
/// Validate that rows are in key=value format
/// </summary>
public bool ValidateRows { get; set; } = true;
/// <summary>
/// Constructor for reading from a file
/// </summary>
public IniReader(string filename)
{
sr = new StreamReader(filename);
}
/// <summary>
/// Constructor for reading from a stream
/// </summary>
public IniReader(Stream stream, Encoding encoding)
{
sr = new StreamReader(stream, encoding);
}
/// <summary>
/// Read the next line in the INI file
/// </summary>
public bool ReadNextLine()
{
if (!(sr.BaseStream?.CanRead ?? false) || sr.EndOfStream)
return false;
CurrentLine = sr.ReadLine().Trim();
LineNumber++;
ProcessLine();
return true;
}
/// <summary>
/// Process the current line and extract out values
/// </summary>
private void ProcessLine()
{
// Comment
if (CurrentLine.StartsWith(";") || CurrentLine.StartsWith("#"))
{
KeyValuePair = null;
RowType = IniRowType.Comment;
}
// Section
else if (CurrentLine.StartsWith("[") && CurrentLine.EndsWith("]"))
{
KeyValuePair = null;
RowType = IniRowType.SectionHeader;
Section = CurrentLine.TrimStart('[').TrimEnd(']');
}
// KeyValuePair
else if (CurrentLine.Contains("="))
{
// Split the line by '=' for key-value pairs
string[] data = CurrentLine.Split('=');
// If the value field contains an '=', we need to put them back in
string key = data[0].Trim();
string value = string.Join("=", data.Skip(1)).Trim();
KeyValuePair = new KeyValuePair<string, string>(key, value);
RowType = IniRowType.KeyValue;
}
// Empty
else if (string.IsNullOrEmpty(CurrentLine))
{
KeyValuePair = null;
CurrentLine = string.Empty;
RowType = IniRowType.None;
}
// Invalid
else
{
KeyValuePair = null;
RowType = IniRowType.Invalid;
if (ValidateRows)
throw new InvalidDataException($"Invalid INI row found, cannot continue: {CurrentLine}");
}
}
/// <summary>
/// Dispose of the underlying reader
/// </summary>
public void Dispose()
{
sr.Dispose();
}
}
}

View File

@@ -1,153 +0,0 @@
/*
Copyright (c) 2012-2015 Eugene Larchenko (spct@mail.ru)
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.
*/
using System;
namespace OptimizedCRC
{
internal class OptimizedCRC : IDisposable
{
private const uint kCrcPoly = 0xEDB88320;
private const uint kInitial = 0xFFFFFFFF;
private const int CRC_NUM_TABLES = 8;
private static readonly uint[] Table;
static OptimizedCRC()
{
unchecked
{
Table = new uint[256 * CRC_NUM_TABLES];
int i;
for (i = 0; i < 256; i++)
{
uint r = (uint)i;
for (int j = 0; j < 8; j++)
{
r = (r >> 1) ^ (kCrcPoly & ~((r & 1) - 1));
}
Table[i] = r;
}
for (; i < 256 * CRC_NUM_TABLES; i++)
{
uint r = Table[i - 256];
Table[i] = Table[r & 0xFF] ^ (r >> 8);
}
}
}
public uint UnsignedValue;
public OptimizedCRC()
{
Init();
}
/// <summary>
/// Reset CRC
/// </summary>
public void Init()
{
UnsignedValue = kInitial;
}
public int Value
{
get { return (int)~UnsignedValue; }
}
public void Update(byte[] data, int offset, int count)
{
new ArraySegment<byte>(data, offset, count); // check arguments
if (count == 0)
{
return;
}
var table = OptimizedCRC.Table;
uint crc = UnsignedValue;
for (; (offset & 7) != 0 && count != 0; count--)
{
crc = (crc >> 8) ^ table[(byte)crc ^ data[offset++]];
}
if (count >= 8)
{
/*
* Idea from 7-zip project sources (http://7-zip.org/sdk.html)
*/
int end = (count - 8) & ~7;
count -= end;
end += offset;
while (offset != end)
{
crc ^= (uint)(data[offset] + (data[offset + 1] << 8) + (data[offset + 2] << 16) + (data[offset + 3] << 24));
uint high = (uint)(data[offset + 4] + (data[offset + 5] << 8) + (data[offset + 6] << 16) + (data[offset + 7] << 24));
offset += 8;
crc = table[(byte)crc + 0x700]
^ table[(byte)(crc >>= 8) + 0x600]
^ table[(byte)(crc >>= 8) + 0x500]
^ table[/*(byte)*/(crc >> 8) + 0x400]
^ table[(byte)(high) + 0x300]
^ table[(byte)(high >>= 8) + 0x200]
^ table[(byte)(high >>= 8) + 0x100]
^ table[/*(byte)*/(high >> 8) + 0x000];
}
}
while (count-- != 0)
{
crc = (crc >> 8) ^ table[(byte)crc ^ data[offset++]];
}
UnsignedValue = crc;
}
static public int Compute(byte[] data, int offset, int count)
{
var crc = new OptimizedCRC();
crc.Update(data, offset, count);
return crc.Value;
}
static public int Compute(byte[] data)
{
return Compute(data, 0, data.Length);
}
static public int Compute(ArraySegment<byte> block)
{
return Compute(block.Array, block.Offset, block.Count);
}
public void Dispose()
{
UnsignedValue = 0;
}
}
}

View File

@@ -47,7 +47,7 @@ namespace NDecrypt.N3DS
using (BinaryReader reader = new BinaryReader(File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
using (BinaryWriter writer = new BinaryWriter(File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)))
{
CIAHeader header = CIAHeader.Read(reader);
CIAHeader? header = CIAHeader.Read(reader);
if (header == null)
{
Console.WriteLine("Error: Not a 3DS CIA!");
@@ -77,7 +77,7 @@ namespace NDecrypt.N3DS
private void ProcessAllPartitions(CIAHeader ciaHeader, BinaryReader reader, BinaryWriter writer)
{
// Iterate over all NCCH partitions
for (int p = 0; p < ciaHeader.Partitions.Length; p++)
for (int p = 0; p < ciaHeader.Partitions!.Length; p++)
{
NCCHHeader ncchHeader = ciaHeader.Partitions[0];
ProcessPartition(ciaHeader, ncchHeader, reader, writer);
@@ -99,7 +99,7 @@ namespace NDecrypt.N3DS
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} is not verified due to force flag being set.");
}
// If we're not forcing the operation, check if the 'NoCrypto' bit is set
else if (ncchHeader.Flags.PossblyDecrypted ^ decryptArgs.Encrypt)
else if (ncchHeader.Flags!.PossblyDecrypted ^ decryptArgs.Encrypt)
{
Console.WriteLine($"Partition {ncchHeader.PartitionNumber}: Already " + (decryptArgs.Encrypt ? "Encrypted" : "Decrypted") + "?...");
return;
@@ -159,7 +159,7 @@ namespace NDecrypt.N3DS
}
else
{
masks = ncchHeader.Flags.BitMasks;
masks = ncchHeader.Flags!.BitMasks;
method = ncchHeader.Flags.CryptoMethod;
}
@@ -209,12 +209,12 @@ namespace NDecrypt.N3DS
if (ncchHeader.ExtendedHeaderSizeInBytes > 0)
{
reader.BaseStream.Seek((ncchHeader.Entry.Offset * mediaUnitSize) + 0x200, SeekOrigin.Begin);
reader.BaseStream.Seek((ncchHeader.Entry!.Offset * mediaUnitSize) + 0x200, SeekOrigin.Begin);
writer.BaseStream.Seek((ncchHeader.Entry.Offset * mediaUnitSize) + 0x200, SeekOrigin.Begin);
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + ": ExHeader");
var cipher = CreateAESCipher(ncchHeader.NormalKey2C, ncchHeader.PlainIV, decryptArgs.Encrypt);
var cipher = CreateAESCipher(ncchHeader.NormalKey2C, ncchHeader.PlainIV!, decryptArgs.Encrypt);
writer.Write(cipher.ProcessBytes(reader.ReadBytes(Constants.CXTExtendedDataHeaderLength)));
writer.Flush();
return true;
@@ -224,8 +224,6 @@ namespace NDecrypt.N3DS
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: No Extended Header... Skipping...");
return false;
}
return false;
}
/// <summary>
@@ -239,8 +237,8 @@ namespace NDecrypt.N3DS
// TODO: Determine how to figure out the MediaUnitSize without an NCSD header. Is it a default value?
uint mediaUnitSize = 0x200; // mediaUnitSize;
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin);
ExeFSHeader exefsHeader = ExeFSHeader.Read(reader);
reader.BaseStream.Seek((ncchHeader.Entry!.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin);
ExeFSHeader? exefsHeader = ExeFSHeader.Read(reader);
// If the header failed to read, log and return
if (exefsHeader == null)
@@ -249,7 +247,7 @@ namespace NDecrypt.N3DS
return;
}
foreach (ExeFSFileHeader fileHeader in exefsHeader.FileHeaders)
foreach (ExeFSFileHeader fileHeader in exefsHeader.FileHeaders!)
{
// Only decrypt a file if it's a code binary
if (!fileHeader.IsCodeBinary)
@@ -259,7 +257,7 @@ namespace NDecrypt.N3DS
uint datalenB = ((fileHeader.FileSize) % (1024 * 1024));
uint ctroffset = ((fileHeader.FileOffset + mediaUnitSize) / 0x10);
byte[] exefsIVWithOffsetForHeader = AddToByteArray(ncchHeader.ExeFSIV, (int)ctroffset);
byte[] exefsIVWithOffsetForHeader = AddToByteArray(ncchHeader.ExeFSIV!, (int)ctroffset);
var firstCipher = CreateAESCipher(ncchHeader.NormalKey, exefsIVWithOffsetForHeader, decryptArgs.Encrypt);
var secondCipher = CreateAESCipher(ncchHeader.NormalKey2C, exefsIVWithOffsetForHeader, !decryptArgs.Encrypt);
@@ -298,12 +296,12 @@ namespace NDecrypt.N3DS
// TODO: Determine how to figure out the MediaUnitSize without an NCSD header. Is it a default value?
uint mediaUnitSize = 0x200; // mediaUnitSize;
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin);
reader.BaseStream.Seek((ncchHeader.Entry!.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin);
writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin);
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": ExeFS Filename Table");
var exeFSFilenameTable = CreateAESCipher(ncchHeader.NormalKey2C, ncchHeader.ExeFSIV, decryptArgs.Encrypt);
var exeFSFilenameTable = CreateAESCipher(ncchHeader.NormalKey2C, ncchHeader.ExeFSIV!, decryptArgs.Encrypt);
writer.Write(exeFSFilenameTable.ProcessBytes(reader.ReadBytes((int)mediaUnitSize)));
writer.Flush();
}
@@ -323,11 +321,11 @@ namespace NDecrypt.N3DS
int exefsSizeB = (int)((long)((ncchHeader.ExeFSSizeInMediaUnits - 1) * mediaUnitSize) % (1024 * 1024));
int ctroffsetE = (int)(mediaUnitSize / 0x10);
byte[] exefsIVWithOffset = AddToByteArray(ncchHeader.ExeFSIV, ctroffsetE);
byte[] exefsIVWithOffset = AddToByteArray(ncchHeader.ExeFSIV!, ctroffsetE);
var exeFS = CreateAESCipher(ncchHeader.NormalKey2C, exefsIVWithOffset, decryptArgs.Encrypt);
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits + 1) * mediaUnitSize, SeekOrigin.Begin);
reader.BaseStream.Seek((ncchHeader.Entry!.Offset + ncchHeader.ExeFSOffsetInMediaUnits + 1) * mediaUnitSize, SeekOrigin.Begin);
writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits + 1) * mediaUnitSize, SeekOrigin.Begin);
if (exefsSizeM > 0)
{
@@ -370,7 +368,7 @@ namespace NDecrypt.N3DS
ProcessExeFSFilenameTable(ncchHeader, reader, writer);
// For all but the original crypto method, process each of the files in the table
if (ncchHeader.Flags.CryptoMethod != CryptoMethod.Original)
if (ncchHeader.Flags!.CryptoMethod != CryptoMethod.Original)
ProcessExeFSFileEntries(ncchHeader, reader, writer);
// Decrypt the rest of the ExeFS
@@ -399,9 +397,9 @@ namespace NDecrypt.N3DS
long romfsSizeM = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * mediaUnitSize) / (1024 * 1024));
int romfsSizeB = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * mediaUnitSize) % (1024 * 1024));
var cipher = CreateAESCipher(ncchHeader.NormalKey, ncchHeader.RomFSIV, decryptArgs.Encrypt);
var cipher = CreateAESCipher(ncchHeader.NormalKey, ncchHeader.RomFSIV!, decryptArgs.Encrypt);
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin);
reader.BaseStream.Seek((ncchHeader.Entry!.Offset + ncchHeader.RomFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin);
writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin);
if (romfsSizeM > 0)
{
@@ -432,13 +430,13 @@ namespace NDecrypt.N3DS
uint mediaUnitSize = 0x200; // ncsdHeader.MediaUnitSize;
// Write the new CryptoMethod
writer.BaseStream.Seek((ncchHeader.Entry.Offset * mediaUnitSize) + 0x18B, SeekOrigin.Begin);
writer.BaseStream.Seek((ncchHeader.Entry!.Offset * mediaUnitSize) + 0x18B, SeekOrigin.Begin);
writer.Write((byte)CryptoMethod.Original);
writer.Flush();
// Write the new BitMasks flag
writer.BaseStream.Seek((ncchHeader.Entry.Offset * mediaUnitSize) + 0x18F, SeekOrigin.Begin);
BitMasks flag = ncchHeader.Flags.BitMasks;
BitMasks flag = ncchHeader.Flags!.BitMasks;
flag &= (BitMasks)((byte)(BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) ^ 0xFF);
flag |= BitMasks.NoCrypto;
writer.Write((byte)flag);
@@ -513,9 +511,9 @@ namespace NDecrypt.N3DS
//}
}
var cipher = CreateAESCipher(ncchHeader.NormalKey, ncchHeader.RomFSIV, decryptArgs.Encrypt);
var cipher = CreateAESCipher(ncchHeader.NormalKey, ncchHeader.RomFSIV!, decryptArgs.Encrypt);
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin);
reader.BaseStream.Seek((ncchHeader.Entry!.Offset + ncchHeader.RomFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin);
writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * mediaUnitSize, SeekOrigin.Begin);
if (romfsSizeM > 0)
{
@@ -547,7 +545,7 @@ namespace NDecrypt.N3DS
uint mediaUnitSize = 0x200; // ncsdHeader.MediaUnitSize;
// Write the new CryptoMethod
writer.BaseStream.Seek((ncchHeader.Entry.Offset * mediaUnitSize) + 0x18B, SeekOrigin.Begin);
writer.BaseStream.Seek((ncchHeader.Entry!.Offset * mediaUnitSize) + 0x18B, SeekOrigin.Begin);
// For partitions 1 and up, set crypto-method to 0x00
if (ncchHeader.PartitionNumber > 0)
@@ -562,7 +560,7 @@ namespace NDecrypt.N3DS
// Write the new BitMasks flag
writer.BaseStream.Seek((ncchHeader.Entry.Offset * mediaUnitSize) + 0x18F, SeekOrigin.Begin);
BitMasks flag = ncchHeader.Flags.BitMasks;
BitMasks flag = ncchHeader.Flags!.BitMasks;
flag &= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator | BitMasks.NoCrypto) ^ (BitMasks)0xFF;
// TODO: Determine how to figure out the original crypto method, if possible

View File

@@ -3,9 +3,9 @@
internal class Constants
{
// Setup Keys and IVs
public static byte[] PlainCounter = new byte[] { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
public static byte[] ExefsCounter = new byte[] { 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
public static byte[] RomfsCounter = new byte[] { 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
public static byte[] PlainCounter = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
public static byte[] ExefsCounter = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
public static byte[] RomfsCounter = [0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
public const int CXTExtendedDataHeaderLength = 0x800;
}

View File

@@ -31,21 +31,21 @@ namespace NDecrypt.N3DS.Headers
/// 12 Special memory
/// 13 Process has access to CPU core 2 (New3DS only)
/// </summary>
public byte[][] Descriptors { get; private set; }
public byte[][]? Descriptors { get; private set; }
/// <summary>
/// Reserved
/// </summary>
public byte[] Reserved { get; private set; }
public byte[]? Reserved { get; private set; }
/// <summary>
/// Read from a stream and get ARM11 kernel capabilities, if possible
/// </summary>
/// <param name="reader">BinaryReader representing the input stream</param>
/// <returns>ARM11 kernel capabilities object, null on error</returns>
public static ARM11KernelCapabilities Read(BinaryReader reader)
public static ARM11KernelCapabilities? Read(BinaryReader reader)
{
ARM11KernelCapabilities kc = new ARM11KernelCapabilities();
var kc = new ARM11KernelCapabilities();
try
{

View File

@@ -7,7 +7,7 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Program ID
/// </summary>
public byte[] ProgramID { get; private set; }
public byte[]? ProgramID { get; private set; }
/// <summary>
/// Core version (The Title ID low of the required FIRM)
@@ -37,27 +37,27 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Resource limit descriptors. The first byte here controls the maximum allowed CpuTime.
/// </summary>
public byte[][] ResourceLimitDescriptors { get; private set; }
public byte[][]? ResourceLimitDescriptors { get; private set; }
/// <summary>
/// Storage info
/// </summary>
public StorageInfo StorageInfo { get; private set; }
public StorageInfo? StorageInfo { get; private set; }
/// <summary>
/// Service access control
/// </summary>
public byte[][] ServiceAccessControl { get; private set; }
public byte[][]? ServiceAccessControl { get; private set; }
/// <summary>
/// Extended service access control, support for this was implemented with 9.3.0-X.
/// </summary>
public byte[][] ExtendedServiceAccessControl { get; private set; }
public byte[][]? ExtendedServiceAccessControl { get; private set; }
/// <summary>
/// Reserved
/// </summary>
public byte[] Reserved { get; private set; }
public byte[]? Reserved { get; private set; }
/// <summary>
/// Resource limit category. (0 = APPLICATION, 1 = SYS_APPLET, 2 = LIB_APPLET, 3 = OTHER (sysmodules running under the BASE memregion))
@@ -69,9 +69,9 @@ namespace NDecrypt.N3DS.Headers
/// </summary>
/// <param name="reader">BinaryReader representing the input stream</param>
/// <returns>ARM11 local system capabilities object, null on error</returns>
public static ARM11LocalSystemCapabilities Read(BinaryReader reader)
public static ARM11LocalSystemCapabilities? Read(BinaryReader reader)
{
ARM11LocalSystemCapabilities lsc = new ARM11LocalSystemCapabilities();
var lsc = new ARM11LocalSystemCapabilities();
try
{

View File

@@ -7,7 +7,7 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Descriptors
/// </summary>
public ARM9AccessControlDescriptors[] Descriptors { get; private set; }
public ARM9AccessControlDescriptors[]? Descriptors { get; private set; }
/// <summary>
/// ARM9 Descriptor Version. Originally this value had to be ≥ 2. Starting with 9.3.0-X this value has to be either value 2 or value 3.
@@ -19,9 +19,9 @@ namespace NDecrypt.N3DS.Headers
/// </summary>
/// <param name="reader">BinaryReader representing the input stream</param>
/// <returns>ARM9 access control object, null on error</returns>
public static ARM9AccessControl Read(BinaryReader reader)
public static ARM9AccessControl? Read(BinaryReader reader)
{
ARM9AccessControl ac = new ARM9AccessControl();
var ac = new ARM9AccessControl();
try
{

View File

@@ -7,26 +7,26 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// ARM11 local system capabilities
/// </summary>
public ARM11LocalSystemCapabilities ARM11LocalSystemCapabilities { get; private set; }
public ARM11LocalSystemCapabilities? ARM11LocalSystemCapabilities { get; private set; }
/// <summary>
/// ARM11 kernel capabilities
/// </summary>
public ARM11KernelCapabilities ARM11KernelCapabilities { get; private set; }
public ARM11KernelCapabilities? ARM11KernelCapabilities { get; private set; }
/// <summary>
/// ARM9 access control
/// </summary>
public ARM9AccessControl ARM9AccessControl { get; private set; }
public ARM9AccessControl? ARM9AccessControl { get; private set; }
/// <summary>
/// Read from a stream and get access control info, if possible
/// </summary>
/// <param name="reader">BinaryReader representing the input stream</param>
/// <returns>Access control info object, null on error</returns>
public static AccessControlInfo Read(BinaryReader reader)
public static AccessControlInfo? Read(BinaryReader reader)
{
AccessControlInfo aci = new AccessControlInfo();
var aci = new AccessControlInfo();
try
{

View File

@@ -49,7 +49,7 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Content Index
/// </summary>
public byte[] ContentIndex { get; private set; }
public byte[]? ContentIndex { get; private set; }
#region Content Index
@@ -59,27 +59,27 @@ namespace NDecrypt.N3DS.Headers
/// <remarks>
/// https://www.3dbrew.org/wiki/CIA#Certificate_Chain
/// </remarks>
public Certificate[] CertificateChain { get; set; }
public Certificate[]? CertificateChain { get; set; }
/// <summary>
/// Ticket
/// </summary>
public Ticket Ticket { get; set; }
public Ticket? Ticket { get; set; }
/// <summary>
/// TMD file data
/// </summary>
public TitleMetadata TMDFileData { get; set; }
public TitleMetadata? TMDFileData { get; set; }
/// <summary>
/// Content file data
/// </summary>
public NCCHHeader[] Partitions { get; set; }
public NCCHHeader[]? Partitions { get; set; }
/// <summary>
/// Meta file data (Not a necessary component)
/// </summary>
public MetaFile MetaFileData { get; set; }
public MetaFile? MetaFileData { get; set; }
#endregion
@@ -88,9 +88,9 @@ namespace NDecrypt.N3DS.Headers
/// </summary>
/// <param name="reader">BinaryReader representing the input stream</param>
/// <returns>CIA header object, null on error</returns>
public static CIAHeader Read(BinaryReader reader)
public static CIAHeader? Read(BinaryReader reader)
{
CIAHeader header = new CIAHeader();
var header = new CIAHeader();
try
{
@@ -107,9 +107,9 @@ namespace NDecrypt.N3DS.Headers
reader.BaseStream.Seek(64 - (reader.BaseStream.Position % 64), SeekOrigin.Current);
header.CertificateChain = new Certificate[3];
header.CertificateChain[0] = Certificate.Read(reader); // CA
header.CertificateChain[1] = Certificate.Read(reader); // Ticket
header.CertificateChain[2] = Certificate.Read(reader); // TMD
header.CertificateChain[0] = Certificate.Read(reader)!; // CA
header.CertificateChain[1] = Certificate.Read(reader)!; // Ticket
header.CertificateChain[2] = Certificate.Read(reader)!; // TMD
if (reader.BaseStream.Position % 64 != 0)
reader.BaseStream.Seek(64 - (reader.BaseStream.Position % 64), SeekOrigin.Current);
@@ -126,7 +126,7 @@ namespace NDecrypt.N3DS.Headers
while (reader.BaseStream.Position < startingPosition + header.ContentSize)
{
long initPosition = reader.BaseStream.Position;
NCCHHeader ncchHeader = NCCHHeader.Read(reader, readSignature: true);
NCCHHeader? ncchHeader = NCCHHeader.Read(reader, readSignature: true);
if (ncchHeader == null)
break;

View File

@@ -7,36 +7,36 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// SCI
/// </summary>
public SystemControlInfo SCI { get; private set; }
public SystemControlInfo? SCI { get; private set; }
/// <summary>
/// ACI
/// </summary>
public AccessControlInfo ACI { get; private set; }
public AccessControlInfo? ACI { get; private set; }
/// <summary>
/// AccessDesc signature (RSA-2048-SHA256)
/// </summary>
public byte[] AccessDescSignature { get; private set; }
public byte[]? AccessDescSignature { get; private set; }
/// <summary>
/// NCCH HDR RSA-2048 public key
/// </summary>
public byte[] NCCHHDRPublicKey { get; private set; }
public byte[]? NCCHHDRPublicKey { get; private set; }
/// <summary>
/// ACI (for limitation of first ACI)
/// </summary>
public AccessControlInfo ACIForLimitations { get; private set; }
public AccessControlInfo? ACIForLimitations { get; private set; }
/// <summary>
/// Read from a stream and get a CXI extended header, if possible
/// </summary>
/// <param name="reader">BinaryReader representing the input stream</param>
/// <returns>CXI extended header object, null on error</returns>
public static CXIExtendedHeader Read(BinaryReader reader)
public static CXIExtendedHeader? Read(BinaryReader reader)
{
CXIExtendedHeader header = new CXIExtendedHeader();
var header = new CXIExtendedHeader();
try
{

View File

@@ -24,17 +24,17 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Signature
/// </summary>
public byte[] Signature { get; private set; }
public byte[]? Signature { get; private set; }
/// <summary>
/// Issuer
/// </summary>
public byte[] Issuer { get; private set; }
public byte[]? Issuer { get; private set; }
/// <summary>
/// Issuer as a trimmed string
/// </summary>
public string IssuerString => Issuer != null && Issuer.Length > 0
public string? IssuerString => Issuer != null && Issuer.Length > 0
? Encoding.ASCII.GetString(Issuer)?.TrimEnd('\0')
: null;
@@ -46,12 +46,12 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Name
/// </summary>
public byte[] Name { get; private set; }
public byte[]? Name { get; private set; }
/// <summary>
/// Name as a trimmed string
/// </summary>
public string NameString => Name != null && Name.Length > 0
public string? NameString => Name != null && Name.Length > 0
? Encoding.ASCII.GetString(Name)?.TrimEnd('\0')
: null;
@@ -66,7 +66,7 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Modulus
/// </summary>
public byte[] Modulus { get; private set; }
public byte[]? Modulus { get; private set; }
/// <summary>
/// Public Exponent
@@ -81,7 +81,7 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Public Key
/// </summary>
public byte[] PublicKey { get; private set; }
public byte[]? PublicKey { get; private set; }
#endregion
@@ -90,9 +90,9 @@ namespace NDecrypt.N3DS.Headers
/// </summary>
/// <param name="reader">BinaryReader representing the input stream</param>
/// <returns>Certificate object, null on error</returns>
public static Certificate Read(BinaryReader reader)
public static Certificate? Read(BinaryReader reader)
{
Certificate ct = new Certificate();
var ct = new Certificate();
try
{

View File

@@ -7,7 +7,7 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Address
/// </summary>
public byte[] Address { get; private set; }
public byte[]? Address { get; private set; }
/// <summary>
/// Physical region size (in page-multiples)
@@ -24,9 +24,9 @@ namespace NDecrypt.N3DS.Headers
/// </summary>
/// <param name="reader">BinaryReader representing the input stream</param>
/// <returns>Code set info object, null on error</returns>
public static CodeSetInfo Read(BinaryReader reader)
public static CodeSetInfo? Read(BinaryReader reader)
{
CodeSetInfo csi = new CodeSetInfo();
var csi = new CodeSetInfo();
try
{

View File

@@ -30,16 +30,16 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// SHA-256 hash
/// </summary>
public byte[] SHA256Hash { get; private set; }
public byte[]? SHA256Hash { get; private set; }
/// <summary>
/// Read from a stream and get content chunk record, if possible
/// </summary>
/// <param name="reader">BinaryReader representing the input stream</param>
/// <returns>Content chunk record object, null on error</returns>
public static ContentChunkRecord Read(BinaryReader reader)
public static ContentChunkRecord? Read(BinaryReader reader)
{
ContentChunkRecord ccr = new ContentChunkRecord();
var ccr = new ContentChunkRecord();
try
{

View File

@@ -17,16 +17,16 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// SHA-256 hash of the next k content records that have not been hashed yet
/// </summary>
public byte[] UnhashedContentRecordsSHA256Hash { get; private set; }
public byte[]? UnhashedContentRecordsSHA256Hash { get; private set; }
/// <summary>
/// Read from a stream and get content info record, if possible
/// </summary>
/// <param name="reader">BinaryReader representing the input stream</param>
/// <returns>Content info record object, null on error</returns>
public static ContentInfoRecord Read(BinaryReader reader)
public static ContentInfoRecord? Read(BinaryReader reader)
{
ContentInfoRecord cir = new ContentInfoRecord();
var cir = new ContentInfoRecord();
try
{

View File

@@ -7,14 +7,14 @@ namespace NDecrypt.N3DS.Headers
internal class ExeFSFileHeader
{
// .code\0\0\0
private readonly byte[] codeSegmentBytes = new byte[] { 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x00, 0x00, 0x00 };
private readonly byte[] codeSegmentBytes = [0x2e, 0x63, 0x6f, 0x64, 0x65, 0x00, 0x00, 0x00];
/// <summary>
/// File name
/// </summary>
public byte[] FileName { get; private set; }
public string ReadableFileName { get { return Encoding.ASCII.GetString(FileName); } }
public bool IsCodeBinary { get { return Enumerable.SequenceEqual(FileName, codeSegmentBytes); } }
public byte[]? FileName { get; private set; }
public string ReadableFileName { get { return Encoding.ASCII.GetString(FileName!); } }
public bool IsCodeBinary { get { return Enumerable.SequenceEqual(FileName!, codeSegmentBytes); } }
/// <summary>
/// File offset
@@ -29,16 +29,16 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// SHA256 hash calculated over the entire file contents
/// </summary>
public byte[] FileHash { get; set; }
public byte[]? FileHash { get; set; }
/// <summary>
/// Read from a stream and get an ExeFS file header, if possible
/// </summary>
/// <param name="reader">BinaryReader representing the input stream</param>
/// <returns>ExeFS file header object, null on error</returns>
public static ExeFSFileHeader Read(BinaryReader reader)
public static ExeFSFileHeader? Read(BinaryReader reader)
{
ExeFSFileHeader header = new ExeFSFileHeader();
var header = new ExeFSFileHeader();
try
{

View File

@@ -7,27 +7,27 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// File headers (10 headers maximum, 16 bytes each)
/// </summary>
public ExeFSFileHeader[] FileHeaders { get; private set; }
public ExeFSFileHeader[]? FileHeaders { get; private set; }
/// <summary>
/// Reserved
/// </summary>
public byte[] Reserved { get; private set; }
public byte[]? Reserved { get; private set; }
/// <summary>
/// Read from a stream and get an ExeFS header, if possible
/// </summary>
/// <param name="reader">BinaryReader representing the input stream</param>
/// <returns>ExeFS header object, null on error</returns>
public static ExeFSHeader Read(BinaryReader reader)
public static ExeFSHeader? Read(BinaryReader reader)
{
ExeFSHeader header = new ExeFSHeader();
var header = new ExeFSHeader();
try
{
header.FileHeaders = new ExeFSFileHeader[10];
for (int i = 0; i < 10; i++)
header.FileHeaders[i] = ExeFSFileHeader.Read(reader);
header.FileHeaders[i] = ExeFSFileHeader.Read(reader)!;
header.Reserved = reader.ReadBytes(0x20);

View File

@@ -7,12 +7,12 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Title ID dependency list - Taken from the application's ExHeader
/// </summary>
public byte[] TitleIDDependencyList { get; private set; }
public byte[]? TitleIDDependencyList { get; private set; }
/// <summary>
/// Reserved
/// </summary>
public byte[] Reserved1 { get; private set; }
public byte[]? Reserved1 { get; private set; }
/// <summary>
/// Core Version
@@ -22,21 +22,21 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Reserved
/// </summary>
public byte[] Reserved2 { get; private set; }
public byte[]? Reserved2 { get; private set; }
/// <summary>
/// Icon Data(.ICN) - Taken from the application's ExeFS
/// </summary>
public byte[] IconData { get; private set; }
public byte[]? IconData { get; private set; }
/// <summary>
/// Read from a stream and get the Metafile data, if possible
/// </summary>
/// <param name="reader">BinaryReader representing the input stream</param>
/// <returns>Metafile data object, null on error</returns>
public static MetaFile Read(BinaryReader reader)
public static MetaFile? Read(BinaryReader reader)
{
MetaFile metaFile = new MetaFile();
var metaFile = new MetaFile();
try
{

View File

@@ -16,12 +16,12 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Partition table entry for the current partition
/// </summary>
public PartitionTableEntry Entry { get; set; }
public PartitionTableEntry? Entry { get; set; }
/// <summary>
/// RSA-2048 signature of the NCCH header, using SHA-256.
/// </summary>
public byte[] RSA2048Signature { get; private set; }
public byte[]? RSA2048Signature { get; private set; }
/// <summary>
/// Content size, in media units (1 media unit = 0x200 bytes)
@@ -31,10 +31,10 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Partition ID
/// </summary>
public byte[] PartitionId { get; private set; }
public byte[] PlainIV { get { return PartitionId.Concat(Constants.PlainCounter).ToArray(); } }
public byte[] ExeFSIV { get { return PartitionId.Concat(Constants.ExefsCounter).ToArray(); } }
public byte[] RomFSIV { get { return PartitionId.Concat(Constants.RomfsCounter).ToArray(); } }
public byte[]? PartitionId { get; private set; }
public byte[]? PlainIV { get { return [.. PartitionId, .. Constants.PlainCounter]; } }
public byte[]? ExeFSIV { get { return [.. PartitionId, .. Constants.ExefsCounter]; } }
public byte[]? RomFSIV { get { return [.. PartitionId, .. Constants.RomfsCounter]; } }
/// <summary>
/// Boot rom key
@@ -82,27 +82,27 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Program ID
/// </summary>
public byte[] ProgramId { get; private set; }
public byte[]? ProgramId { get; private set; }
/// <summary>
/// Reserved
/// </summary>
public byte[] Reserved1 { get; private set; }
public byte[]? Reserved1 { get; private set; }
/// <summary>
/// Logo Region SHA-256 hash. (For applications built with SDK 5+) (Supported from firmware: 5.0.0-11)
/// </summary>
public byte[] LogoRegionHash { get; private set; }
public byte[]? LogoRegionHash { get; private set; }
/// <summary>
/// Product code
/// </summary>
public byte[] ProductCode { get; private set; }
public byte[]? ProductCode { get; private set; }
/// <summary>
/// Extended header SHA-256 hash (SHA256 of 2x Alignment Size, beginning at 0x0 of ExHeader)
/// </summary>
public byte[] ExtendedHeaderHash { get; private set; }
public byte[]? ExtendedHeaderHash { get; private set; }
/// <summary>
/// Extended header size, in bytes
@@ -112,12 +112,12 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Reserved
/// </summary>
public byte[] Reserved2 { get; private set; }
public byte[]? Reserved2 { get; private set; }
/// <summary>
/// Flags
/// </summary>
public NCCHHeaderFlags Flags { get; private set; }
public NCCHHeaderFlags? Flags { get; private set; }
/// <summary>
/// Plain region offset, in media units
@@ -157,7 +157,7 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Reserved
/// </summary>
public byte[] Reserved3 { get; private set; }
public byte[]? Reserved3 { get; private set; }
/// <summary>
/// RomFS offset, in media units
@@ -177,19 +177,19 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Reserved
/// </summary>
public byte[] Reserved4 { get; private set; }
public byte[]? Reserved4 { get; private set; }
/// <summary>
/// ExeFS superblock SHA-256 hash - (SHA-256 hash, starting at 0x0 of the ExeFS over the number of
/// media units specified in the ExeFS hash region size)
/// </summary>
public byte[] ExeFSSuperblockHash { get; private set; }
public byte[]? ExeFSSuperblockHash { get; private set; }
/// <summary>
/// RomFS superblock SHA-256 hash - (SHA-256 hash, starting at 0x0 of the RomFS over the number
/// of media units specified in the RomFS hash region size)
/// </summary>
public byte[] RomFSSuperblockHash { get; private set; }
public byte[]? RomFSSuperblockHash { get; private set; }
/// <summary>
/// Read from a stream and get an NCCH header, if possible
@@ -197,9 +197,9 @@ namespace NDecrypt.N3DS.Headers
/// <param name="reader">BinaryReader representing the input stream</param>
/// <param name="readSignature">True if the RSA signature is read, false otherwise</param>
/// <returns>NCCH header object, null on error</returns>
public static NCCHHeader Read(BinaryReader reader, bool readSignature)
public static NCCHHeader? Read(BinaryReader reader, bool readSignature)
{
NCCHHeader header = new NCCHHeader();
var header = new NCCHHeader();
try
{

View File

@@ -57,9 +57,9 @@ namespace NDecrypt.N3DS.Headers
/// </summary>
/// <param name="reader">BinaryReader representing the input stream</param>
/// <returns>NCCH header flags object, null on error</returns>
public static NCCHHeaderFlags Read(BinaryReader reader)
public static NCCHHeaderFlags? Read(BinaryReader reader)
{
NCCHHeaderFlags flags = new NCCHHeaderFlags();
var flags = new NCCHHeaderFlags();
try
{

View File

@@ -12,7 +12,7 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// RSA-2048 SHA-256 signature of the NCSD header
/// </summary>
public byte[] RSA2048Signature { get; private set; }
public byte[]? RSA2048Signature { get; private set; }
/// <summary>
/// Size of the NCSD image, in media units (1 media unit = 0x200 bytes)
@@ -22,7 +22,7 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Media ID
/// </summary>
public byte[] MediaId { get; private set; }
public byte[]? MediaId { get; private set; }
/// <summary>
/// Partitions FS type (0=None, 1=Normal, 3=FIRM, 4=AGB_FIRM save)
@@ -32,37 +32,37 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Partitions crypt type (each byte corresponds to a partition in the partition table)
/// </summary>
public byte[] PartitionsCryptType { get; private set; }
public byte[]? PartitionsCryptType { get; private set; }
/// <summary>
/// Offset & Length partition table, in media units
/// </summary>
public PartitionTableEntry[] PartitionsTable { get; private set; }
public PartitionTableEntry[]? PartitionsTable { get; private set; }
/// <summary>
/// Partition table entry for Executable Content (CXI)
/// </summary>
public PartitionTableEntry ExecutableContent { get { return PartitionsTable[0]; } }
public PartitionTableEntry ExecutableContent { get { return PartitionsTable![0]; } }
/// <summary>
/// Partition table entry for E-Manual (CFA)
/// </summary>
public PartitionTableEntry EManual { get { return PartitionsTable[1]; } }
public PartitionTableEntry EManual { get { return PartitionsTable![1]; } }
/// <summary>
/// Partition table entry for Download Play Child container (CFA)
/// </summary>
public PartitionTableEntry DownloadPlayChildContainer { get { return PartitionsTable[2]; } }
public PartitionTableEntry DownloadPlayChildContainer { get { return PartitionsTable![2]; } }
/// <summary>
/// Partition table entry for New3DS Update Data (CFA)
/// </summary>
public PartitionTableEntry New3DSUpdateData { get { return PartitionsTable[6]; } }
public PartitionTableEntry New3DSUpdateData { get { return PartitionsTable![6]; } }
/// <summary>
/// Partition table entry for Update Data (CFA)
/// </summary>
public PartitionTableEntry UpdateData { get { return PartitionsTable[7]; } }
public PartitionTableEntry UpdateData { get { return PartitionsTable![7]; } }
#endregion
@@ -71,7 +71,7 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Exheader SHA-256 hash
/// </summary>
public byte[] ExheaderHash { get; private set; }
public byte[]? ExheaderHash { get; private set; }
/// <summary>
/// Additional header size
@@ -86,53 +86,53 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Partition Flags
/// </summary>
public byte[] PartitionFlags { get; private set; }
public byte[]? PartitionFlags { get; private set; }
/// <summary>
/// Backup Write Wait Time (The time to wait to write save to backup after the card is recognized (0-255
/// seconds)).NATIVE_FIRM loads this flag from the gamecard NCSD header starting with 6.0.0-11.
/// </summary>
public byte BackupWriteWaitTime { get { return PartitionFlags[(int)NCSDFlags.BackupWriteWaitTime]; } }
public byte BackupWriteWaitTime { get { return PartitionFlags![(int)NCSDFlags.BackupWriteWaitTime]; } }
/// <summary>
/// Media Card Device (1 = NOR Flash, 2 = None, 3 = BT) (SDK 3.X+)
/// </summary>
public MediaCardDeviceType MediaCardDevice3X { get { return (MediaCardDeviceType)PartitionFlags[(int)NCSDFlags.MediaCardDevice3X]; } }
public MediaCardDeviceType MediaCardDevice3X { get { return (MediaCardDeviceType)PartitionFlags![(int)NCSDFlags.MediaCardDevice3X]; } }
/// <summary>
/// Media Platform Index (1 = CTR)
/// </summary>
public MediaPlatformIndex MediaPlatformIndex { get { return (MediaPlatformIndex)PartitionFlags[(int)NCSDFlags.MediaPlatformIndex]; } }
public MediaPlatformIndex MediaPlatformIndex { get { return (MediaPlatformIndex)PartitionFlags![(int)NCSDFlags.MediaPlatformIndex]; } }
/// <summary>
/// Media Type Index (0 = Inner Device, 1 = Card1, 2 = Card2, 3 = Extended Device)
/// </summary>
public MediaTypeIndex MediaTypeIndex { get { return (MediaTypeIndex)PartitionFlags[(int)NCSDFlags.MediaTypeIndex]; } }
public MediaTypeIndex MediaTypeIndex { get { return (MediaTypeIndex)PartitionFlags![(int)NCSDFlags.MediaTypeIndex]; } }
/// <summary>
/// Media Unit Size i.e. u32 MediaUnitSize = 0x200*2^flags[6];
/// </summary>
public uint MediaUnitSize { get { return (uint)(0x200 * Math.Pow(2, PartitionFlags[(int)NCSDFlags.MediaUnitSize])); } }
public uint MediaUnitSize { get { return (uint)(0x200 * Math.Pow(2, PartitionFlags![(int)NCSDFlags.MediaUnitSize])); } }
/// <summary>
/// Media Card Device (1 = NOR Flash, 2 = None, 3 = BT) (Only SDK 2.X)
/// </summary>
public MediaCardDeviceType MediaCardDevice2X { get { return (MediaCardDeviceType)PartitionFlags[(int)NCSDFlags.MediaCardDevice2X]; } }
public MediaCardDeviceType MediaCardDevice2X { get { return (MediaCardDeviceType)PartitionFlags![(int)NCSDFlags.MediaCardDevice2X]; } }
/// <summary>
/// Partition ID table
/// </summary>
public byte[][] PartitionIdTable { get; private set; }
public byte[][]? PartitionIdTable { get; private set; }
/// <summary>
/// Reserved
/// </summary>
public byte[] Reserved1 { get; private set; }
public byte[]? Reserved1 { get; private set; }
/// <summary>
/// Reserved?
/// </summary>
public byte[] Reserved2 { get; private set; }
public byte[]? Reserved2 { get; private set; }
/// <summary>
/// Support for this was implemented with 9.6.0-X FIRM. Bit0=1 enables using bits 1-2, it's unknown
@@ -154,12 +154,12 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Unknown
/// </summary>
public byte[] Unknown { get; private set; }
public byte[]? Unknown { get; private set; }
/// <summary>
/// Encrypted MBR partition-table, for the TWL partitions(key-data used for this keyslot is console-unique).
/// </summary>
public byte[] EncryptedMBR { get; private set; }
public byte[]? EncryptedMBR { get; private set; }
#endregion
@@ -168,17 +168,17 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// CARD2: Writable Address In Media Units (For 'On-Chip' Savedata). CARD1: Always 0xFFFFFFFF.
/// </summary>
public byte[] CARD2WritableAddressMediaUnits { get; private set; }
public byte[]? CARD2WritableAddressMediaUnits { get; private set; }
/// <summary>
/// Card Info Bitmask
/// </summary>
public byte[] CardInfoBytemask { get; private set; }
public byte[]? CardInfoBytemask { get; private set; }
/// <summary>
/// Reserved1
/// </summary>
public byte[] Reserved3 { get; private set; }
public byte[]? Reserved3 { get; private set; }
/// <summary>
/// Title version
@@ -193,36 +193,37 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Reserved2
/// </summary>
public byte[] Reserved4 { get; private set; }
public byte[]? Reserved4 { get; private set; }
/// <summary>
/// Card seed keyY (first u64 is Media ID (same as first NCCH partitionId))
/// </summary>
public byte[] CardSeedKeyY { get; private set; }
public byte[]? CardSeedKeyY { get; private set; }
/// <summary>
/// Encrypted card seed (AES-CCM, keyslot 0x3B for retail cards, see CTRCARD_SECSEED) /// </summary>
public byte[] EncryptedCardSeed { get; private set; }
/// Encrypted card seed (AES-CCM, keyslot 0x3B for retail cards, see CTRCARD_SECSEED)
/// </summary>
public byte[]? EncryptedCardSeed { get; private set; }
/// <summary>
/// Card seed AES-MAC
/// </summary>
public byte[] CardSeedAESMAC { get; private set; }
public byte[]? CardSeedAESMAC { get; private set; }
/// <summary>
/// Card seed nonce
/// </summary>
public byte[] CardSeedNonce { get; private set; }
public byte[]? CardSeedNonce { get; private set; }
/// <summary>
/// Reserved3
/// </summary>
public byte[] Reserved5 { get; private set; }
public byte[]? Reserved5 { get; private set; }
/// <summary>
/// Copy of first NCCH header (excluding RSA signature)
/// </summary>
public NCCHHeader BackupHeader { get; private set; }
public NCCHHeader? BackupHeader { get; private set; }
#endregion
@@ -231,17 +232,17 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// CardDeviceReserved1
/// </summary>
public byte[] CardDeviceReserved1 { get; private set; }
public byte[]? CardDeviceReserved1 { get; private set; }
/// <summary>
/// TitleKey
/// </summary>
public byte[] TitleKey { get; private set; }
public byte[]? TitleKey { get; private set; }
/// <summary>
/// CardDeviceReserved2
/// </summary>
public byte[] CardDeviceReserved2 { get; private set; }
public byte[]? CardDeviceReserved2 { get; private set; }
#endregion
@@ -251,9 +252,9 @@ namespace NDecrypt.N3DS.Headers
/// <param name="reader">BinaryReader representing the input stream</param>
/// <param name="development">True if development cart, false otherwise</param>
/// <returns>NCSD header object, null on error</returns>
public static NCSDHeader Read(BinaryReader reader, bool development)
public static NCSDHeader? Read(BinaryReader reader, bool development)
{
NCSDHeader header = new NCSDHeader();
var header = new NCSDHeader();
try
{
@@ -269,7 +270,7 @@ namespace NDecrypt.N3DS.Headers
header.PartitionsTable = new PartitionTableEntry[8];
for (int i = 0; i < 8; i++)
header.PartitionsTable[i] = PartitionTableEntry.Read(reader);
header.PartitionsTable[i] = PartitionTableEntry.Read(reader)!;
if (header.PartitionsFSType == FilesystemType.Normal
|| header.PartitionsFSType == FilesystemType.None)

View File

@@ -19,9 +19,9 @@ namespace NDecrypt.N3DS.Headers
/// </summary>
/// <param name="reader">BinaryReader representing the input stream</param>
/// <returns>Partition table entry object, null on error</returns>
public static PartitionTableEntry Read(BinaryReader reader)
public static PartitionTableEntry? Read(BinaryReader reader)
{
PartitionTableEntry entry = new PartitionTableEntry();
var entry = new PartitionTableEntry();
try
{

View File

@@ -31,7 +31,7 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Reserved
/// </summary>
public byte[] Reserved1 { get; private set; }
public byte[]? Reserved1 { get; private set; }
/// <summary>
/// Level 2 logical offset
@@ -51,7 +51,7 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Reserved
/// </summary>
public byte[] Reserved2 { get; private set; }
public byte[]? Reserved2 { get; private set; }
/// <summary>
/// Level 3 logical offset
@@ -63,7 +63,7 @@ namespace NDecrypt.N3DS.Headers
/// </summary>
public ulong Level3HashdataSize { get; private set; }
/// <summary>
/// <summary>s
/// Level 3 block size, in log2
/// </summary>
public uint Level3BlockSizeLog2 { get; private set; }
@@ -71,12 +71,12 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Reserved
/// </summary>
public byte[] Reserved3 { get; private set; }
public byte[]? Reserved3 { get; private set; }
/// <summary>
/// Reserved
/// </summary>
public byte[] Reserved4 { get; private set; }
public byte[]? Reserved4 { get; private set; }
/// <summary>
/// Optional info size.
@@ -88,9 +88,9 @@ namespace NDecrypt.N3DS.Headers
/// </summary>
/// <param name="reader">BinaryReader representing the input stream</param>
/// <returns>RomFS header object, null on error</returns>
public static RomFSHeader Read(BinaryReader reader)
public static RomFSHeader? Read(BinaryReader reader)
{
RomFSHeader header = new RomFSHeader();
var header = new RomFSHeader();
try
{

View File

@@ -7,22 +7,22 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Extdata ID
/// </summary>
public byte[] ExtdataID { get; private set; }
public byte[]? ExtdataID { get; private set; }
/// <summary>
/// System savedata IDs
/// </summary>
public byte[] SystemSavedataIDs { get; private set; }
public byte[]? SystemSavedataIDs { get; private set; }
/// <summary>
/// Storage accessible unique IDs
/// </summary>
public byte[] StorageAccessibleUniqueIDs { get; private set; }
public byte[]? StorageAccessibleUniqueIDs { get; private set; }
/// <summary>
/// Filesystem access info
/// </summary>
public byte[] FilesystemAccessInfo { get; private set; }
public byte[]? FilesystemAccessInfo { get; private set; }
/// <summary>
/// Other attributes
@@ -34,9 +34,9 @@ namespace NDecrypt.N3DS.Headers
/// </summary>
/// <param name="reader">BinaryReader representing the input stream</param>
/// <returns>Storage info object, null on error</returns>
public static StorageInfo Read(BinaryReader reader)
public static StorageInfo? Read(BinaryReader reader)
{
StorageInfo si = new StorageInfo();
var si = new StorageInfo();
try
{

View File

@@ -7,12 +7,12 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Application title (default is "CtrApp")
/// </summary>
public char[] ApplicationTitle { get; private set; }
public char[]? ApplicationTitle { get; private set; }
/// <summary>
/// Reserved
/// </summary>
public byte[] Reserved1 { get; private set; }
public byte[]? Reserved1 { get; private set; }
/// <summary>
/// Flag (bit 0: CompressExefsCode, bit 1: SDApplication)
@@ -22,12 +22,12 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Remaster version
/// </summary>
public byte[] RemasterVersion { get; private set; }
public byte[]? RemasterVersion { get; private set; }
/// <summary>
/// Text code set info
/// </summary>
public CodeSetInfo TextCodesetInfo { get; private set; }
public CodeSetInfo? TextCodesetInfo { get; private set; }
/// <summary>
/// Stack size
@@ -37,17 +37,17 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Read-only code set info
/// </summary>
public CodeSetInfo ReadOnlyCodeSetInfo { get; private set; }
public CodeSetInfo? ReadOnlyCodeSetInfo { get; private set; }
/// <summary>
/// Reserved
/// </summary>
public byte[] Reserved2 { get; private set; }
public byte[]? Reserved2 { get; private set; }
/// <summary>
/// Data code set info
/// </summary>
public CodeSetInfo DataCodeSetInfo { get; private set; }
public CodeSetInfo? DataCodeSetInfo { get; private set; }
/// <summary>
/// BSS size
@@ -57,21 +57,21 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Dependency module (program ID) list
/// </summary>
public byte[][] DependencyModuleList { get; private set; }
public byte[][]? DependencyModuleList { get; private set; }
/// <summary>
/// SystemInfo
/// </summary>
public SystemInfo SystemInfo { get; private set; }
public SystemInfo? SystemInfo { get; private set; }
/// <summary>
/// Read from a stream and get system control info, if possible
/// </summary>
/// <param name="reader">BinaryReader representing the input stream</param>
/// <returns>System control info object, null on error</returns>
public static SystemControlInfo Read(BinaryReader reader)
public static SystemControlInfo? Read(BinaryReader reader)
{
SystemControlInfo sci = new SystemControlInfo();
var sci = new SystemControlInfo();
try
{

View File

@@ -12,21 +12,21 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Jump ID
/// </summary>
public byte[] JumpID { get; private set; }
public byte[]? JumpID { get; private set; }
/// <summary>
/// Reserved
/// </summary>
public byte[] Reserved { get; private set; }
public byte[]? Reserved { get; private set; }
/// <summary>
/// Read from a stream and get system info, if possible
/// </summary>
/// <param name="reader">BinaryReader representing the input stream</param>
/// <returns>System info object, null on error</returns>
public static SystemInfo Read(BinaryReader reader)
public static SystemInfo? Read(BinaryReader reader)
{
SystemInfo si = new SystemInfo();
var si = new SystemInfo();
try
{

View File

@@ -26,24 +26,24 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Signature
/// </summary>
public byte[] Signature { get; private set; }
public byte[]? Signature { get; private set; }
/// <summary>
/// Issuer
/// </summary>
public byte[] Issuer { get; private set; }
public byte[]? Issuer { get; private set; }
/// <summary>
/// Issuer as a trimmed string
/// </summary>
public string IssuerString => Issuer != null && Issuer.Length > 0
public string? IssuerString => Issuer != null && Issuer.Length > 0
? Encoding.ASCII.GetString(Issuer)?.TrimEnd('\0')
: null;
/// <summary>
/// ECC PublicKey
/// </summary>
public byte[] ECCPublicKey { get; private set; }
public byte[]? ECCPublicKey { get; private set; }
/// <summary>
/// Version (For 3DS this is always 1)
@@ -74,7 +74,7 @@ namespace NDecrypt.N3DS.Headers
/// The titlekey is used to decrypt content downloaded from the CDN using 128-bit AES-CBC with
/// the content index (as big endian u16, padded with trailing zeroes) as the IV.
/// </remarks>
public byte[] TitleKey { get; private set; }
public byte[]? TitleKey { get; private set; }
/// <summary>
/// Reserved
@@ -129,7 +129,7 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Reserved
/// </summary>
public byte[] Reserved4 { get; private set; }
public byte[]? Reserved4 { get; private set; }
/// <summary>
/// eShop Account ID?
@@ -149,7 +149,7 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Reserved
/// </summary>
public byte[] Reserved6 { get; private set; }
public byte[]? Reserved6 { get; private set; }
/// <summary>
/// Limits
@@ -157,7 +157,7 @@ namespace NDecrypt.N3DS.Headers
/// <remarks>
/// In demos, the first u32 in the "Limits" section is 0x4, then the second u32 is the max-playcount.
/// </remarks>
public int[] Limits { get; private set; }
public int[]? Limits { get; private set; }
/// <summary>
/// Denotes if the ticket denotes a demo or not
@@ -179,7 +179,7 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Content Index
/// </summary>
public byte[] ContentIndex { get; private set; }
public byte[]? ContentIndex { get; private set; }
/// <summary>
/// Certificate chain
@@ -187,7 +187,7 @@ namespace NDecrypt.N3DS.Headers
/// <remarks>
/// https://www.3dbrew.org/wiki/Ticket#Certificate_Chain
/// </remarks>
public Certificate[] CertificateChain { get; set; }
public Certificate[]? CertificateChain { get; set; }
/// <summary>
/// Read from a stream and get ticket, if possible
@@ -195,9 +195,9 @@ namespace NDecrypt.N3DS.Headers
/// <param name="reader">BinaryReader representing the input stream</param>
/// <param name="ticketSize">Ticket size from the header</param>
/// <returns>Ticket object, null on error</returns>
public static Ticket Read(BinaryReader reader, int ticketSize)
public static Ticket? Read(BinaryReader reader, int ticketSize)
{
Ticket tk = new Ticket();
var tk = new Ticket();
try
{
@@ -264,8 +264,8 @@ namespace NDecrypt.N3DS.Headers
if (ticketSize > (reader.BaseStream.Position - startingPosition) + (2 * 0x200))
{
tk.CertificateChain = new Certificate[2];
tk.CertificateChain[0] = Certificate.Read(reader); // Ticket
tk.CertificateChain[1] = Certificate.Read(reader); // CA
tk.CertificateChain[0] = Certificate.Read(reader)!; // Ticket
tk.CertificateChain[1] = Certificate.Read(reader)!; // CA
}
return tk;

View File

@@ -26,17 +26,17 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Signature
/// </summary>
public byte[] Signature { get; private set; }
public byte[]? Signature { get; private set; }
/// <summary>
/// Signature Issuer
/// </summary>
public byte[] SignatureIssuer { get; private set; }
public byte[]? SignatureIssuer { get; private set; }
/// <summary>
/// Signature Issuer as a trimmed string
/// </summary>
public string SignatureIssuerString => SignatureIssuer != null && SignatureIssuer.Length > 0
public string? SignatureIssuerString => SignatureIssuer != null && SignatureIssuer.Length > 0
? Encoding.ASCII.GetString(SignatureIssuer)?.TrimEnd('\0')
: null;
@@ -103,7 +103,7 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// Reserved
/// </summary>
public byte[] Reserved3 { get; private set; }
public byte[]? Reserved3 { get; private set; }
/// <summary>
/// Access Rights
@@ -133,18 +133,18 @@ namespace NDecrypt.N3DS.Headers
/// <summary>
/// SHA-256 Hash of the Content Info Records
/// </summary>
public byte[] SHA256HashContentInfoRecords { get; private set; }
public byte[]? SHA256HashContentInfoRecords { get; private set; }
/// <summary>
/// There are 64 of these records, usually only the first is used.
/// </summary>
public ContentInfoRecord[] ContentInfoRecords { get; private set; }
public ContentInfoRecord[]? ContentInfoRecords { get; private set; }
/// <summary>
/// There is one of these for each content contained in this title.
/// (Determined by "Content Count" in the TMD Header).
/// </summary>
public ContentChunkRecord[] ContentChunkRecords { get; private set; }
public ContentChunkRecord[]? ContentChunkRecords { get; private set; }
/// <summary>
/// Certificate chain
@@ -152,7 +152,7 @@ namespace NDecrypt.N3DS.Headers
/// <remarks>
/// https://www.3dbrew.org/wiki/Title_metadata#Certificate_Chain
/// </remarks>
public Certificate[] CertificateChain { get; set; }
public Certificate[]? CertificateChain { get; set; }
/// <summary>
/// Read from a stream and get ticket metadata, if possible
@@ -160,9 +160,9 @@ namespace NDecrypt.N3DS.Headers
/// <param name="reader">BinaryReader representing the input stream</param>
/// <param name="metadataSize">Metadata size from the header</param>
/// <returns>Title metadata object, null on error</returns>
public static TitleMetadata Read(BinaryReader reader, int metadataSize)
public static TitleMetadata? Read(BinaryReader reader, int metadataSize)
{
TitleMetadata tm = new TitleMetadata();
var tm = new TitleMetadata();
try
{
@@ -214,20 +214,20 @@ namespace NDecrypt.N3DS.Headers
tm.ContentInfoRecords = new ContentInfoRecord[64];
for (int i = 0; i < 64; i++)
{
tm.ContentInfoRecords[i] = ContentInfoRecord.Read(reader);
tm.ContentInfoRecords[i] = ContentInfoRecord.Read(reader)!;
}
tm.ContentChunkRecords = new ContentChunkRecord[tm.ContentCount];
for (int i = 0; i < tm.ContentCount; i++)
{
tm.ContentChunkRecords[i] = ContentChunkRecord.Read(reader);
tm.ContentChunkRecords[i] = ContentChunkRecord.Read(reader)!;
}
if (metadataSize > (reader.BaseStream.Position - startingPosition) + (2 * 0x200))
{
tm.CertificateChain = new Certificate[2];
tm.CertificateChain[0] = Certificate.Read(reader); // TMD
tm.CertificateChain[1] = Certificate.Read(reader); // CA
tm.CertificateChain[0] = Certificate.Read(reader)!; // TMD
tm.CertificateChain[1] = Certificate.Read(reader)!; // CA
}
return tm;

View File

@@ -1,17 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net48;net6.0</TargetFrameworks>
<!-- Assembly Properties -->
<TargetFrameworks>net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<VersionPrefix>0.2.5</VersionPrefix>
<!-- Package Properties -->
<Authors>Matt Nadareski</Authors>
<Copyright>Copyright (c)2018-2023 Matt Nadareski</Copyright>
<Description>3DS encryption and decryption code</Description>
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<RepositoryUrl>https://github.com/SabreTools/NDecrypt</RepositoryUrl>
<Version>0.2.5</Version>
<AssemblyVersion>$(Version)</AssemblyVersion>
<FileVersion>$(Version)</FileVersion>
<IncludeSource>true</IncludeSource>
<IncludeSymbols>true</IncludeSymbols>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<!-- Support All Frameworks -->
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))">
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>

View File

@@ -46,7 +46,7 @@ namespace NDecrypt.N3DS
using (BinaryReader reader = new BinaryReader(File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
using (BinaryWriter writer = new BinaryWriter(File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)))
{
NCSDHeader header = NCSDHeader.Read(reader, decryptArgs.Development);
NCSDHeader? header = NCSDHeader.Read(reader, decryptArgs.Development);
if (header == null)
{
Console.WriteLine("Error: Not a 3DS cart image!");
@@ -78,7 +78,7 @@ namespace NDecrypt.N3DS
// Iterate over all 8 NCCH partitions
for (int p = 0; p < 8; p++)
{
NCCHHeader ncchHeader = GetPartitionHeader(ncsdHeader, reader, p);
NCCHHeader? ncchHeader = GetPartitionHeader(ncsdHeader, reader, p);
if (ncchHeader == null)
continue;
@@ -93,9 +93,9 @@ namespace NDecrypt.N3DS
/// <param name="reader">BinaryReader representing the input stream</param>
/// <param name="partitionNumber">Partition number to attempt to retrieve</param>
/// <returns>NCCH header for the partition requested, null on error</returns>
private NCCHHeader GetPartitionHeader(NCSDHeader ncsdHeader, BinaryReader reader, int partitionNumber)
private NCCHHeader? GetPartitionHeader(NCSDHeader ncsdHeader, BinaryReader reader, int partitionNumber)
{
if (!ncsdHeader.PartitionsTable[partitionNumber].IsValid())
if (!ncsdHeader.PartitionsTable![partitionNumber].IsValid())
{
Console.WriteLine($"Partition {partitionNumber} Not found... Skipping...");
return null;
@@ -104,7 +104,7 @@ namespace NDecrypt.N3DS
// Seek to the beginning of the NCCH partition
reader.BaseStream.Seek((ncsdHeader.PartitionsTable[partitionNumber].Offset * ncsdHeader.MediaUnitSize), SeekOrigin.Begin);
NCCHHeader partitionHeader = NCCHHeader.Read(reader, readSignature: true);
NCCHHeader? partitionHeader = NCCHHeader.Read(reader, readSignature: true);
if (partitionHeader == null)
{
Console.WriteLine($"Partition {partitionNumber} Unable to read NCCH header");
@@ -131,7 +131,7 @@ namespace NDecrypt.N3DS
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} is not verified due to force flag being set.");
}
// If we're not forcing the operation, check if the 'NoCrypto' bit is set
else if (ncchHeader.Flags.PossblyDecrypted ^ decryptArgs.Encrypt)
else if (ncchHeader.Flags!.PossblyDecrypted ^ decryptArgs.Encrypt)
{
Console.WriteLine($"Partition {ncchHeader.PartitionNumber}: Already " + (decryptArgs.Encrypt ? "Encrypted" : "Decrypted") + "?...");
return;
@@ -184,12 +184,12 @@ namespace NDecrypt.N3DS
CryptoMethod method;
if (decryptArgs.Encrypt)
{
masks = ncsdHeader.BackupHeader.Flags.BitMasks;
masks = ncsdHeader.BackupHeader!.Flags!.BitMasks;
method = ncsdHeader.BackupHeader.Flags.CryptoMethod;
}
else
{
masks = ncchHeader.Flags.BitMasks;
masks = ncchHeader.Flags!.BitMasks;
method = ncchHeader.Flags.CryptoMethod;
}
@@ -237,12 +237,12 @@ namespace NDecrypt.N3DS
{
if (ncchHeader.ExtendedHeaderSizeInBytes > 0)
{
reader.BaseStream.Seek((ncchHeader.Entry.Offset * ncsdHeader.MediaUnitSize) + 0x200, SeekOrigin.Begin);
reader.BaseStream.Seek((ncchHeader.Entry!.Offset * ncsdHeader.MediaUnitSize) + 0x200, SeekOrigin.Begin);
writer.BaseStream.Seek((ncchHeader.Entry.Offset * ncsdHeader.MediaUnitSize) + 0x200, SeekOrigin.Begin);
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + ": ExHeader");
var cipher = CreateAESCipher(ncchHeader.NormalKey2C, ncchHeader.PlainIV, decryptArgs.Encrypt);
var cipher = CreateAESCipher(ncchHeader.NormalKey2C, ncchHeader.PlainIV!, decryptArgs.Encrypt);
byte[] readBytes = reader.ReadBytes(Constants.CXTExtendedDataHeaderLength);
byte[] processedBytes = cipher.ProcessBytes(readBytes);
writer.Write(processedBytes);
@@ -265,8 +265,8 @@ namespace NDecrypt.N3DS
/// <param name="writer">BinaryWriter representing the output stream</param>
private void ProcessExeFSFileEntries(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
{
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
ExeFSHeader exefsHeader = ExeFSHeader.Read(reader);
reader.BaseStream.Seek((ncchHeader.Entry!.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
ExeFSHeader? exefsHeader = ExeFSHeader.Read(reader);
// If the header failed to read, log and return
if (exefsHeader == null)
@@ -275,7 +275,7 @@ namespace NDecrypt.N3DS
return;
}
foreach (ExeFSFileHeader fileHeader in exefsHeader.FileHeaders)
foreach (ExeFSFileHeader fileHeader in exefsHeader.FileHeaders!)
{
// Only decrypt a file if it's a code binary
if (!fileHeader.IsCodeBinary)
@@ -285,7 +285,7 @@ namespace NDecrypt.N3DS
uint datalenB = ((fileHeader.FileSize) % (1024 * 1024));
uint ctroffset = ((fileHeader.FileOffset + ncsdHeader.MediaUnitSize) / 0x10);
byte[] exefsIVWithOffsetForHeader = AddToByteArray(ncchHeader.ExeFSIV, (int)ctroffset);
byte[] exefsIVWithOffsetForHeader = AddToByteArray(ncchHeader.ExeFSIV!, (int)ctroffset);
var firstCipher = CreateAESCipher(ncchHeader.NormalKey, exefsIVWithOffsetForHeader, decryptArgs.Encrypt);
var secondCipher = CreateAESCipher(ncchHeader.NormalKey2C, exefsIVWithOffsetForHeader, !decryptArgs.Encrypt);
@@ -328,12 +328,12 @@ namespace NDecrypt.N3DS
/// <param name="writer">BinaryWriter representing the output stream</param>
private void ProcessExeFSFilenameTable(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryReader reader, BinaryWriter writer)
{
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
reader.BaseStream.Seek((ncchHeader.Entry!.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
Console.WriteLine($"Partition {ncchHeader.PartitionNumber} ExeFS: " + (decryptArgs.Encrypt ? "Encrypting" : "Decrypting") + $": ExeFS Filename Table");
var cipher = CreateAESCipher(ncchHeader.NormalKey2C, ncchHeader.ExeFSIV, decryptArgs.Encrypt);
var cipher = CreateAESCipher(ncchHeader.NormalKey2C, ncchHeader.ExeFSIV!, decryptArgs.Encrypt);
byte[] readBytes = reader.ReadBytes((int)ncsdHeader.MediaUnitSize);
byte[] processedBytes = cipher.ProcessBytes(readBytes);
writer.Write(processedBytes);
@@ -358,11 +358,11 @@ namespace NDecrypt.N3DS
int exefsSizeB = (int)((long)((ncchHeader.ExeFSSizeInMediaUnits - 1) * ncsdHeader.MediaUnitSize) % (1024 * 1024));
int ctroffsetE = (int)(ncsdHeader.MediaUnitSize / 0x10);
byte[] exefsIVWithOffset = AddToByteArray(ncchHeader.ExeFSIV, ctroffsetE);
byte[] exefsIVWithOffset = AddToByteArray(ncchHeader.ExeFSIV!, ctroffsetE);
var cipher = CreateAESCipher(ncchHeader.NormalKey2C, exefsIVWithOffset, decryptArgs.Encrypt);
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits + 1) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
reader.BaseStream.Seek((ncchHeader.Entry!.Offset + ncchHeader.ExeFSOffsetInMediaUnits + 1) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.ExeFSOffsetInMediaUnits + 1) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
if (exefsSizeM > 0)
{
@@ -410,7 +410,7 @@ namespace NDecrypt.N3DS
ProcessExeFSFilenameTable(ncsdHeader, ncchHeader, reader, writer);
// For all but the original crypto method, process each of the files in the table
if (ncchHeader.Flags.CryptoMethod != CryptoMethod.Original)
if (ncchHeader.Flags!.CryptoMethod != CryptoMethod.Original)
ProcessExeFSFileEntries(ncsdHeader, ncchHeader, reader, writer);
// Decrypt the rest of the ExeFS
@@ -437,9 +437,9 @@ namespace NDecrypt.N3DS
long romfsSizeM = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * ncsdHeader.MediaUnitSize) / (1024 * 1024));
int romfsSizeB = (int)((long)(ncchHeader.RomFSSizeInMediaUnits * ncsdHeader.MediaUnitSize) % (1024 * 1024));
var cipher = CreateAESCipher(ncchHeader.NormalKey, ncchHeader.RomFSIV, decryptArgs.Encrypt);
var cipher = CreateAESCipher(ncchHeader.NormalKey, ncchHeader.RomFSIV!, decryptArgs.Encrypt);
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
reader.BaseStream.Seek((ncchHeader.Entry!.Offset + ncchHeader.RomFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
if (romfsSizeM > 0)
{
@@ -472,13 +472,13 @@ namespace NDecrypt.N3DS
private void UpdateDecryptCryptoAndMasks(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryWriter writer)
{
// Write the new CryptoMethod
writer.BaseStream.Seek((ncchHeader.Entry.Offset * ncsdHeader.MediaUnitSize) + 0x18B, SeekOrigin.Begin);
writer.BaseStream.Seek((ncchHeader.Entry!.Offset * ncsdHeader.MediaUnitSize) + 0x18B, SeekOrigin.Begin);
writer.Write((byte)CryptoMethod.Original);
writer.Flush();
// Write the new BitMasks flag
writer.BaseStream.Seek((ncchHeader.Entry.Offset * ncsdHeader.MediaUnitSize) + 0x18F, SeekOrigin.Begin);
BitMasks flag = ncchHeader.Flags.BitMasks;
BitMasks flag = ncchHeader.Flags!.BitMasks;
flag &= (BitMasks)((byte)(BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) ^ 0xFF);
flag |= BitMasks.NoCrypto;
writer.Write((byte)flag);
@@ -506,7 +506,7 @@ namespace NDecrypt.N3DS
}
// For all but the original crypto method, process each of the files in the table
if (ncsdHeader.BackupHeader.Flags.CryptoMethod != CryptoMethod.Original)
if (ncsdHeader.BackupHeader!.Flags!.CryptoMethod != CryptoMethod.Original)
ProcessExeFSFileEntries(ncsdHeader, ncchHeader, reader, writer);
// Encrypt the filename table
@@ -539,7 +539,7 @@ namespace NDecrypt.N3DS
// Encrypting RomFS for partitions 1 and up always use Key0x2C
if (ncchHeader.PartitionNumber > 0)
{
if (ncsdHeader.BackupHeader.Flags?.BitMasks.HasFlag(BitMasks.FixedCryptoKey) == true) // except if using zero-key
if (ncsdHeader.BackupHeader!.Flags?.BitMasks.HasFlag(BitMasks.FixedCryptoKey) == true) // except if using zero-key
{
ncchHeader.NormalKey = 0x00;
}
@@ -550,9 +550,9 @@ namespace NDecrypt.N3DS
}
}
var cipher = CreateAESCipher(ncchHeader.NormalKey, ncchHeader.RomFSIV, decryptArgs.Encrypt);
var cipher = CreateAESCipher(ncchHeader.NormalKey, ncchHeader.RomFSIV!, decryptArgs.Encrypt);
reader.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
reader.BaseStream.Seek((ncchHeader.Entry!.Offset + ncchHeader.RomFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
writer.BaseStream.Seek((ncchHeader.Entry.Offset + ncchHeader.RomFSOffsetInMediaUnits) * ncsdHeader.MediaUnitSize, SeekOrigin.Begin);
if (romfsSizeM > 0)
{
@@ -585,7 +585,7 @@ namespace NDecrypt.N3DS
private void UpdateEncryptCryptoAndMasks(NCSDHeader ncsdHeader, NCCHHeader ncchHeader, BinaryWriter writer)
{
// Write the new CryptoMethod
writer.BaseStream.Seek((ncchHeader.Entry.Offset * ncsdHeader.MediaUnitSize) + 0x18B, SeekOrigin.Begin);
writer.BaseStream.Seek((ncchHeader.Entry!.Offset * ncsdHeader.MediaUnitSize) + 0x18B, SeekOrigin.Begin);
// For partitions 1 and up, set crypto-method to 0x00
if (ncchHeader.PartitionNumber > 0)
@@ -593,15 +593,15 @@ namespace NDecrypt.N3DS
// If partition 0, restore crypto-method from backup flags
else
writer.Write((byte)ncsdHeader.BackupHeader.Flags.CryptoMethod);
writer.Write((byte)ncsdHeader.BackupHeader!.Flags!.CryptoMethod);
writer.Flush();
// Write the new BitMasks flag
writer.BaseStream.Seek((ncchHeader.Entry.Offset * ncsdHeader.MediaUnitSize) + 0x18F, SeekOrigin.Begin);
BitMasks flag = ncchHeader.Flags.BitMasks;
BitMasks flag = ncchHeader.Flags!.BitMasks;
flag &= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator | BitMasks.NoCrypto) ^ (BitMasks)0xFF;
flag |= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) & ncsdHeader.BackupHeader.Flags.BitMasks;
flag |= (BitMasks.FixedCryptoKey | BitMasks.NewKeyYGenerator) & ncsdHeader.BackupHeader!.Flags!.BitMasks;
writer.Write((byte)flag);
writer.Flush();
}

View File

@@ -2,8 +2,8 @@
{
internal class Constants
{
public static byte[] NDSEncryptionData = new byte[]
{
public static byte[] NDSEncryptionData =
[
0x99,0xD5,0x20,0x5F,0x57,0x44,0xF5,0xB9,0x6E,0x19,0xA4,0xD9,0x9E,0x6A,0x5A,0x94,
0xD8,0xAE,0xF1,0xEB,0x41,0x75,0xE2,0x3A,0x93,0x82,0xD0,0x32,0x33,0xEE,0x31,0xD5,
0xCC,0x57,0x61,0x9A,0x37,0x06,0xA2,0x1B,0x79,0x39,0x72,0xF5,0x55,0xAE,0xF6,0xBE,
@@ -265,7 +265,7 @@
0x6B,0x3A,0xA6,0x6B,0x35,0xD2,0x2F,0x43,0xCD,0x02,0xFD,0xB5,0xE9,0xBC,0x5B,0xAA,
0xD8,0xA4,0x19,0x7E,0x0E,0x5D,0x94,0x81,0x9E,0x6F,0x77,0xAD,0xD6,0x0E,0x74,0x93,
0x96,0xE7,0xC4,0x18,0x5F,0xAD,0xF5,0x19,
};
];
#region ARM9 decryption check values

View File

@@ -20,8 +20,8 @@ namespace NDecrypt.Nitro
#region Encryption process variables
private uint[] cardHash = new uint[0x412];
private uint[] arg2 = new uint[3];
private uint[] _cardHash = new uint[0x412];
private uint[] _arg2 = new uint[3];
#endregion
@@ -42,7 +42,7 @@ namespace NDecrypt.Nitro
using (BinaryReader reader = new BinaryReader(File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
using (BinaryWriter writer = new BinaryWriter(File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)))
{
NDSHeader header = NDSHeader.Read(reader);
NDSHeader? header = NDSHeader.Read(reader);
if (header == null)
{
Console.WriteLine("Error: Not a DS or DSi Rom!");
@@ -79,7 +79,7 @@ namespace NDecrypt.Nitro
// If we're not forcing the operation, check to see if we should be proceeding
else
{
bool? isDecrypted = CheckIfDecrypted(reader);
bool? isDecrypted = DSTool.CheckIfDecrypted(reader);
if (isDecrypted == null)
{
Console.WriteLine("File has an empty secure area, cannot proceed");
@@ -102,7 +102,7 @@ namespace NDecrypt.Nitro
/// </summary>
/// <param name="reader">BinaryReader representing the input stream</param>
/// <returns>True if the file has known values for a decrypted file, null if it's empty, false otherwise</returns>
private bool? CheckIfDecrypted(BinaryReader reader)
private static bool? CheckIfDecrypted(BinaryReader reader)
{
reader.BaseStream.Seek(0x4000, SeekOrigin.Begin);
uint firstValue = reader.ReadUInt32();
@@ -175,8 +175,8 @@ namespace NDecrypt.Nitro
// Perform the initialization steps
Init1(ndsHeader);
if (!decryptArgs.Encrypt) Decrypt(ref p1, ref p0);
arg2[1] <<= 1;
arg2[2] >>= 1;
_arg2[1] <<= 1;
_arg2[2] >>= 1;
Init2();
// If we're decrypting, set the proper flags
@@ -245,8 +245,8 @@ namespace NDecrypt.Nitro
/// <param name="ndsHeader">NDS header representing the DS file</param>
private void Init1(NDSHeader ndsHeader)
{
Buffer.BlockCopy(Constants.NDSEncryptionData, 0, cardHash, 0, 4 * (1024 + 18));
arg2 = new uint[] { ndsHeader.Gamecode, ndsHeader.Gamecode >> 1, ndsHeader.Gamecode << 1 };
Buffer.BlockCopy(Constants.NDSEncryptionData, 0, _cardHash, 0, 4 * (1024 + 18));
_arg2 = [ndsHeader.Gamecode, ndsHeader.Gamecode >> 1, ndsHeader.Gamecode << 1];
Init2();
Init2();
}
@@ -256,12 +256,12 @@ namespace NDecrypt.Nitro
/// </summary>
private void Init2()
{
Encrypt(ref arg2[2], ref arg2[1]);
Encrypt(ref arg2[1], ref arg2[0]);
Encrypt(ref _arg2[2], ref _arg2[1]);
Encrypt(ref _arg2[1], ref _arg2[0]);
byte[] allBytes = BitConverter.GetBytes(arg2[0])
.Concat(BitConverter.GetBytes(arg2[1]))
.Concat(BitConverter.GetBytes(arg2[2]))
byte[] allBytes = BitConverter.GetBytes(_arg2[0])
.Concat(BitConverter.GetBytes(_arg2[1]))
.Concat(BitConverter.GetBytes(_arg2[2]))
.ToArray();
UpdateHashtable(allBytes);
@@ -278,13 +278,13 @@ namespace NDecrypt.Nitro
uint b = arg2;
for (int i = 17; i > 1; i--)
{
uint c = cardHash[i] ^ a;
uint c = _cardHash[i] ^ a;
a = b ^ Lookup(c);
b = c;
}
arg1 = b ^ cardHash[0];
arg2 = a ^ cardHash[1];
arg1 = b ^ _cardHash[0];
arg2 = a ^ _cardHash[1];
}
/// <summary>
@@ -298,13 +298,13 @@ namespace NDecrypt.Nitro
uint b = arg2;
for (int i = 0; i < 16; i++)
{
uint c = cardHash[i] ^ a;
uint c = _cardHash[i] ^ a;
a = b ^ Lookup(c);
b = c;
}
arg2 = a ^ cardHash[16];
arg1 = b ^ cardHash[17];
arg2 = a ^ _cardHash[16];
arg1 = b ^ _cardHash[17];
}
/// <summary>
@@ -319,10 +319,10 @@ namespace NDecrypt.Nitro
uint c = (v >> 8) & 0xFF;
uint d = (v >> 0) & 0xFF;
a = cardHash[a + 18 + 0];
b = cardHash[b + 18 + 256];
c = cardHash[c + 18 + 512];
d = cardHash[d + 18 + 768];
a = _cardHash[a + 18 + 0];
b = _cardHash[b + 18 + 256];
c = _cardHash[c + 18 + 512];
d = _cardHash[d + 18 + 768];
return d + (c ^ (b + a));
}
@@ -342,7 +342,7 @@ namespace NDecrypt.Nitro
r3 |= arg1[(j * 4 + i) & 7];
}
cardHash[j] ^= r3;
_cardHash[j] ^= r3;
}
uint tmp1 = 0;
@@ -350,14 +350,14 @@ namespace NDecrypt.Nitro
for (int i = 0; i < 18; i += 2)
{
Encrypt(ref tmp1, ref tmp2);
cardHash[i + 0] = tmp1;
cardHash[i + 1] = tmp2;
_cardHash[i + 0] = tmp1;
_cardHash[i + 1] = tmp2;
}
for (int i = 0; i < 0x400; i += 2)
{
Encrypt(ref tmp1, ref tmp2);
cardHash[i + 18 + 0] = tmp1;
cardHash[i + 18 + 1] = tmp2;
_cardHash[i + 18 + 0] = tmp1;
_cardHash[i + 18 + 1] = tmp2;
}
}
}

View File

@@ -1,6 +1,4 @@
using System;
namespace NDecrypt.Nitro
namespace NDecrypt.Nitro
{
internal enum NDSUnitcode : byte
{

View File

@@ -9,7 +9,7 @@ namespace NDecrypt.Nitro.Headers
/// <summary>
/// Game Title
/// </summary>
public char[] GameTitle { get; private set; }
public char[]? GameTitle { get; private set; }
/// <summary>
/// Gamecode
@@ -19,7 +19,7 @@ namespace NDecrypt.Nitro.Headers
/// <summary>
/// Makercode
/// </summary>
public char[] Makercode { get; private set; }
public char[]? Makercode { get; private set; }
/// <summary>
/// Unitcode
@@ -40,7 +40,7 @@ namespace NDecrypt.Nitro.Headers
/// <summary>
/// Reserved
/// </summary>
public byte[] Reserved1 { get; private set; }
public byte[]? Reserved1 { get; private set; }
/// <summary>
/// Game Revision (used by DSi titles)
@@ -140,12 +140,12 @@ namespace NDecrypt.Nitro.Headers
/// <summary>
/// Normal card control register settings (0x00416657 for OneTimePROM)
/// </summary>
public byte[] NormalCardControlRegisterSettings { get; private set; }
public byte[]? NormalCardControlRegisterSettings { get; private set; }
/// <summary>
/// Secure card control register settings (0x081808F8 for OneTimePROM)
/// </summary>
public byte[] SecureCardControlRegisterSettings { get; private set; }
public byte[]? SecureCardControlRegisterSettings { get; private set; }
/// <summary>
/// Icon Banner offset (NDSi same as NDS, but with new extra entries)
@@ -165,17 +165,17 @@ namespace NDecrypt.Nitro.Headers
/// <summary>
/// ARM9 autoload
/// </summary>
public byte[] ARM9Autoload { get; private set; }
public byte[]? ARM9Autoload { get; private set; }
/// <summary>
/// ARM7 autoload
/// </summary>
public byte[] ARM7Autoload { get; private set; }
public byte[]? ARM7Autoload { get; private set; }
/// <summary>
/// Secure disable
/// </summary>
public byte[] SecureDisable { get; private set; }
public byte[]? SecureDisable { get; private set; }
/// <summary>
/// NTR region ROM size (excluding DSi area)
@@ -190,12 +190,12 @@ namespace NDecrypt.Nitro.Headers
/// <summary>
///Reserved (0x88, 0x8C, 0x90 = Unknown, used by DSi)
/// </summary>
public byte[] Reserved2 { get; private set; }
public byte[]? Reserved2 { get; private set; }
/// <summary>
/// Nintendo Logo
/// </summary>
public byte[] NintendoLogo { get; private set; }
public byte[]? NintendoLogo { get; private set; }
/// <summary>
/// Nintendo Logo CRC
@@ -210,7 +210,7 @@ namespace NDecrypt.Nitro.Headers
/// <summary>
/// Debugger reserved
/// </summary>
public byte[] DebuggerReserved { get; private set; }
public byte[]? DebuggerReserved { get; private set; }
#endregion
@@ -219,42 +219,42 @@ namespace NDecrypt.Nitro.Headers
/// <summary>
/// Global MBK1..MBK5 Settings
/// </summary>
public byte[] GlobalMBK15Settings { get; private set; }
public byte[]? GlobalMBK15Settings { get; private set; }
/// <summary>
/// Local MBK6..MBK8 Settings for ARM9
/// </summary>
public byte[] LocalMBK68SettingsARM9 { get; private set; }
public byte[]? LocalMBK68SettingsARM9 { get; private set; }
/// <summary>
/// Local MBK6..MBK8 Settings for ARM7
/// </summary>
public byte[] LocalMBK68SettingsARM7 { get; private set; }
public byte[]? LocalMBK68SettingsARM7 { get; private set; }
/// <summary>
/// Global MBK9 Setting
/// </summary>
public byte[] GlobalMBK9Setting { get; private set; }
public byte[]? GlobalMBK9Setting { get; private set; }
/// <summary>
///
/// </summary>
public byte[] RegionFlags { get; private set; }
public byte[]? RegionFlags { get; private set; }
/// <summary>
/// Access control
/// </summary>
public byte[] AccessControl { get; private set; }
public byte[]? AccessControl { get; private set; }
/// <summary>
/// ARM7 SCFG EXT mask (controls which devices to enable)
/// </summary>
public byte[] ARM7SCFGEXTMask { get; private set; }
public byte[]? ARM7SCFGEXTMask { get; private set; }
/// <summary>
/// Reserved/flags? When bit2 of byte 0x1bf is set, usage of banner.sav from the title data dir is enabled.(additional banner data)
/// </summary>
public byte[] ReservedFlags { get; private set; }
public byte[]? ReservedFlags { get; private set; }
/// <summary>
/// ARM9i rom offset
@@ -264,7 +264,7 @@ namespace NDecrypt.Nitro.Headers
/// <summary>
/// Reserved
/// </summary>
public byte[] Reserved3 { get; private set; }
public byte[]? Reserved3 { get; private set; }
/// <summary>
/// ARM9i load address
@@ -284,7 +284,7 @@ namespace NDecrypt.Nitro.Headers
/// <summary>
/// Pointer to base address where various structures and parameters are passed to the title - what is that???
/// </summary>
public byte[] Reserved4 { get; private set; }
public byte[]? Reserved4 { get; private set; }
/// <summary>
/// ARM7i load address
@@ -354,7 +354,7 @@ namespace NDecrypt.Nitro.Headers
/// <summary>
/// Unknown (used by DSi)
/// </summary>
public byte[] Unknown1 { get; private set; }
public byte[]? Unknown1 { get; private set; }
/// <summary>
/// NTR+TWL region ROM size (total size including DSi area)
@@ -364,7 +364,7 @@ namespace NDecrypt.Nitro.Headers
/// <summary>
/// Unknown (used by DSi)
/// </summary>
public byte[] Unknown2 { get; private set; }
public byte[]? Unknown2 { get; private set; }
/// <summary>
/// Modcrypt area 1 offset
@@ -389,7 +389,7 @@ namespace NDecrypt.Nitro.Headers
/// <summary>
/// Title ID
/// </summary>
public byte[] TitleID { get; private set; }
public byte[]? TitleID { get; private set; }
/// <summary>
/// DSiWare: "public.sav" size
@@ -404,67 +404,67 @@ namespace NDecrypt.Nitro.Headers
/// <summary>
/// Reserved (zero)
/// </summary>
public byte[] ReservedZero { get; private set; }
public byte[]? ReservedZero { get; private set; }
/// <summary>
/// Unknown (used by DSi)
/// </summary>
public byte[] Unknown3 { get; private set; }
public byte[]? Unknown3 { get; private set; }
/// <summary>
/// ARM9 (with encrypted secure area) SHA1 HMAC hash
/// </summary>
public byte[] ARM9WithSecureAreaSHA1HMACHash { get; private set; }
public byte[]? ARM9WithSecureAreaSHA1HMACHash { get; private set; }
/// <summary>
/// ARM7 SHA1 HMAC hash
/// </summary>
public byte[] ARM7SHA1HMACHash { get; private set; }
public byte[]? ARM7SHA1HMACHash { get; private set; }
/// <summary>
/// Digest master SHA1 HMAC hash
/// </summary>
public byte[] DigestMasterSHA1HMACHash { get; private set; }
public byte[]? DigestMasterSHA1HMACHash { get; private set; }
/// <summary>
/// Banner SHA1 HMAC hash
/// </summary>
public byte[] BannerSHA1HMACHash { get; private set; }
public byte[]? BannerSHA1HMACHash { get; private set; }
/// <summary>
/// ARM9i (decrypted) SHA1 HMAC hash
/// </summary>
public byte[] ARM9iDecryptedSHA1HMACHash { get; private set; }
public byte[]? ARM9iDecryptedSHA1HMACHash { get; private set; }
/// <summary>
/// ARM7i (decrypted) SHA1 HMAC hash
/// </summary>
public byte[] ARM7iDecryptedSHA1HMACHash { get; private set; }
public byte[]? ARM7iDecryptedSHA1HMACHash { get; private set; }
/// <summary>
/// Reserved
/// </summary>
public byte[] Reserved5 { get; private set; }
public byte[]? Reserved5 { get; private set; }
/// <summary>
/// ARM9 (without secure area) SHA1 HMAC hash
/// </summary>
public byte[] ARM9NoSecureAreaSHA1HMACHash { get; private set; }
public byte[]? ARM9NoSecureAreaSHA1HMACHash { get; private set; }
/// <summary>
/// Reserved
/// </summary>
public byte[] Reserved6 { get; private set; }
public byte[]? Reserved6 { get; private set; }
/// <summary>
/// Reserved and unchecked region, always zero. Used for passing arguments in debug environment.
/// </summary>
public byte[] ReservedAndUnchecked { get; private set; }
public byte[]? ReservedAndUnchecked { get; private set; }
/// <summary>
/// RSA signature (the first 0xE00 bytes of the header are signed with an 1024-bit RSA signature).
/// </summary>
public byte[] RSASignature { get; private set; }
public byte[]? RSASignature { get; private set; }
#endregion
@@ -473,9 +473,9 @@ namespace NDecrypt.Nitro.Headers
/// </summary>
/// <param name="reader">BinaryReader representing the input stream</param>
/// <returns>NDS/NDSi header object, null on error</returns>
public static NDSHeader Read(BinaryReader reader)
public static NDSHeader? Read(BinaryReader reader)
{
NDSHeader header = new NDSHeader();
var header = new NDSHeader();
try
{

View File

@@ -1,17 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net48;net6.0</TargetFrameworks>
<!-- Assembly Properties -->
<TargetFrameworks>net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<VersionPrefix>0.2.5</VersionPrefix>
<!-- Package Properties -->
<Authors>Matt Nadareski</Authors>
<Copyright>Copyright (c)2018-2023 Matt Nadareski</Copyright>
<Description>NDS encryption and decryption code</Description>
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<RepositoryUrl>https://github.com/SabreTools/NDecrypt</RepositoryUrl>
<Version>0.2.5</Version>
<AssemblyVersion>$(Version)</AssemblyVersion>
<FileVersion>$(Version)</FileVersion>
<IncludeSource>true</IncludeSource>
<IncludeSymbols>true</IncludeSymbols>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<!-- Support All Frameworks -->
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))">
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>

View File

@@ -1,9 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using NDecrypt.Core.Tools;
using SabreTools.Hashing;
namespace NDecrypt
{
@@ -14,106 +11,24 @@ namespace NDecrypt
/// </summary>
/// <param name="input">Filename to get information from</param>
/// <returns>Formatted string representing the hashes, null on error</returns>
public static string GetInfo(string input)
public static string? GetInfo(string input)
{
// If the file doesn't exist, return null
if (!File.Exists(input))
return null;
// Get the file length
long size = new FileInfo(input).Length;
// Open the file
Stream inputStream = File.OpenRead(input);
try
{
// Get a list of hashers to run over the buffer
List<Hasher> hashers = new List<Hasher>
{
new Hasher(Hash.CRC),
new Hasher(Hash.MD5),
new Hasher(Hash.SHA1),
new Hasher(Hash.SHA256),
};
// Initialize the hashing helpers
int buffersize = 3 * 1024 * 1024;
byte[] buffer = new byte[buffersize];
/*
Please note that some of the following code is adapted from
RomVault. This is a modified version of how RomVault does
threaded hashing. As such, some of the terminology and code
is the same, though variable names and comments may have
been tweaked to better fit this code base.
*/
// Pre load the buffer
int next = buffersize > size ? (int)size : buffersize;
int current = inputStream.Read(buffer, 0, next);
long refsize = size;
while (refsize > 0)
{
// Run hashes in parallel
if (current > 0)
Parallel.ForEach(hashers, h => h.Process(buffer, current));
// Load the next buffer
refsize -= current;
next = buffersize > refsize ? (int)refsize : buffersize;
if (next > 0)
current = inputStream.Read(buffer, 0, next);
}
// Finalize all hashing helpers
Parallel.ForEach(hashers, h => h.Terminate());
// Get the results
string result = $"Size: {size}\n"
+ $"CRC32: {ByteArrayToString(hashers.First(h => h.HashType == Hash.CRC).GetHash()) ?? ""}\n"
+ $"MD5: {ByteArrayToString(hashers.First(h => h.HashType == Hash.MD5).GetHash()) ?? ""}\n"
+ $"SHA1: {ByteArrayToString(hashers.First(h => h.HashType == Hash.SHA1).GetHash()) ?? ""}\n"
+ $"SHA256: {ByteArrayToString(hashers.First(h => h.HashType == Hash.SHA256).GetHash()) ?? ""}\n";
// Dispose of the hashers
hashers.ForEach(h => h.Dispose());
return result;
}
catch
{
return null;
}
finally
{
inputStream.Dispose();
}
}
/// <summary>
/// Convert a byte array to a hex string
/// </summary>
/// <param name="bytes">Byte array to convert</param>
/// <returns>Hex string representing the byte array</returns>
/// <link>http://stackoverflow.com/questions/311165/how-do-you-convert-byte-array-to-hexadecimal-string-and-vice-versa</link>
private static string ByteArrayToString(byte[] bytes)
{
// If we get null in, we send null out
if (bytes == null)
// Get the file information, if possible
HashType[] hashTypes = [HashType.CRC32, HashType.MD5, HashType.SHA1, HashType.SHA256];
var hashDict = HashTool.GetFileHashesAndSize(input, hashTypes, out long size);
if (hashDict == null)
return null;
try
{
string hex = BitConverter.ToString(bytes);
return hex.Replace("-", string.Empty).ToLowerInvariant();
}
catch
{
return null;
}
// Get the results
return $"Size: {size}\n"
+ $"CRC32: {hashDict.First(h => h.Key == HashType.CRC32).Value ?? string.Empty}\n"
+ $"MD5: {hashDict.First(h => h.Key == HashType.MD5).Value ?? string.Empty}\n"
+ $"SHA1: {hashDict.First(h => h.Key == HashType.SHA1).Value ?? string.Empty}\n"
+ $"SHA256: {hashDict.First(h => h.Key == HashType.SHA256).Value ?? string.Empty}\n";
}
}
}

View File

@@ -1,23 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net48;net6.0</TargetFrameworks>
<RuntimeIdentifiers>win-x86;win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<!-- Assembly Properties -->
<TargetFrameworks>net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
<OutputType>Exe</OutputType>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<VersionPrefix>0.2.5</VersionPrefix>
<!-- Package Properties -->
<Title>NDecrypt</Title>
<AssemblyName>NDecrypt</AssemblyName>
<Description>DS/3DS Encryption Tool</Description>
<Authors>Matt Nadareski</Authors>
<Product>NDecrypt</Product>
<Copyright>Copyright (c)2018-2023 Matt Nadareski</Copyright>
<Description>DS/3DS Encryption Tool</Description>
<Copyright>Copyright (c) Matt Nadareski 2018-2024</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<RepositoryUrl>https://github.com/SabreTools/NDecrypt</RepositoryUrl>
<Version>0.2.5</Version>
<AssemblyVersion>$(Version)</AssemblyVersion>
<FileVersion>$(Version)</FileVersion>
<IncludeSource>true</IncludeSource>
<IncludeSymbols>true</IncludeSymbols>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<!-- Support All Frameworks -->
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))">
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>

View File

@@ -1,6 +1,5 @@
using System;
using System.IO;
using System.Reflection;
using NDecrypt.Core;
using NDecrypt.N3DS;
using NDecrypt.Nitro;
@@ -31,7 +30,7 @@ namespace NDecrypt
return;
}
var decryptArgs = new DecryptArgs();
var decryptArgs = new DecryptArgs();
if (args[0] == "decrypt" || args[0] == "d")
{
decryptArgs.Encrypt = false;
@@ -48,7 +47,7 @@ namespace NDecrypt
bool outputHashes = false;
int start = 1;
for ( ; start < args.Length; start++)
for (; start < args.Length; start++)
{
if (args[start] == "-c" || args[start] == "--citra")
{
@@ -75,7 +74,7 @@ namespace NDecrypt
string tempPath = args[start];
if (string.IsNullOrWhiteSpace(tempPath))
Console.WriteLine($"Invalid keyfile path: null or empty path found!");
tempPath = Path.GetFullPath(tempPath);
if (!File.Exists(tempPath))
Console.WriteLine($"Invalid keyfile path: file {tempPath} not found!");
@@ -91,10 +90,13 @@ namespace NDecrypt
// Derive the keyfile path based on the runtime folder if not already set
if (string.IsNullOrWhiteSpace(decryptArgs.KeyFile))
{
using var processModule = System.Diagnostics.Process.GetCurrentProcess().MainModule;
string applicationDirectory = Path.GetDirectoryName(processModule?.FileName) ?? string.Empty;
if (decryptArgs.UseCitraKeyFile)
decryptArgs.KeyFile = Path.Combine(Path.GetDirectoryName(AppContext.BaseDirectory), "aes_keys.txt");
decryptArgs.KeyFile = Path.Combine(applicationDirectory, "aes_keys.txt");
else
decryptArgs.KeyFile = Path.Combine(Path.GetDirectoryName(AppContext.BaseDirectory), "keys.bin");
decryptArgs.KeyFile = Path.Combine(applicationDirectory, "keys.bin");
}
// If we are using a Citra keyfile, there are no development keys
@@ -103,7 +105,7 @@ namespace NDecrypt
Console.WriteLine("Citra keyfiles don't contain development keys; disabling the option...");
decryptArgs.Development = false;
}
// Initialize the constants, if possible
decryptArgs.Initialize();
@@ -136,7 +138,7 @@ namespace NDecrypt
private static void ProcessPath(string path, DecryptArgs decryptArgs, bool outputHashes)
{
Console.WriteLine(path);
ITool tool = DeriveTool(path, decryptArgs);
ITool? tool = DeriveTool(path, decryptArgs);
if (tool?.ProcessFile() != true)
Console.WriteLine("Processing failed!");
else if (outputHashes)
@@ -147,7 +149,7 @@ namespace NDecrypt
/// Display a basic help text
/// </summary>
/// <param name="err">Additional error text to display, can be null to ignore</param>
private static void DisplayHelp(string err = null)
private static void DisplayHelp(string? err = null)
{
if (!string.IsNullOrWhiteSpace(err))
Console.WriteLine($"Error: {err}");
@@ -175,10 +177,10 @@ More than one path can be specified at a time.");
/// <param name="filename">Filename to derive the tool from</param>
/// <param name="decryptArgs">Arguments to pass to the tools on creation</param>
/// <returns></returns>
private static ITool DeriveTool(string filename, DecryptArgs decryptArgs)
private static ITool? DeriveTool(string filename, DecryptArgs decryptArgs)
{
FileType type = DetermineFileType(filename);
switch(type)
switch (type)
{
case FileType.NDS:
Console.WriteLine("File recognized as Nintendo DS");
@@ -222,13 +224,13 @@ More than one path can be specified at a time.");
else if (filename.EndsWith(".3ds", StringComparison.OrdinalIgnoreCase))
return FileType.N3DS;
else if (filename.EndsWith(".cia", StringComparison.OrdinalIgnoreCase))
return FileType.N3DSCIA;
return FileType.NULL;
}
/// <summary>
/// Write out the hashes of a file to a named file
/// </summary>
@@ -240,10 +242,10 @@ More than one path can be specified at a time.");
return;
// Get the hash string from the file
string hashString = HashingHelper.GetInfo(filename);
string? hashString = HashingHelper.GetInfo(filename);
if (hashString == null)
return;
// Open the output file and write the hashes
using (var fs = File.Create(Path.GetFullPath(filename) + ".hash"))
using (var sw = new StreamWriter(fs))

View File

@@ -1,6 +1,7 @@
# NDecrypt
[![Build status](https://ci.appveyor.com/api/projects/status/cc1n298syn6r50mq?svg=true)](https://ci.appveyor.com/project/mnadareski/ndecrypt)
[![Program Build](https://github.com/SabreTools/NDecrypt/actions/workflows/build_program.yml/badge.svg)](https://github.com/SabreTools/NDecrypt/actions/workflows/build_program.yml)
A simple tool for simple people.
@@ -16,24 +17,29 @@ This is a code port of 3 different programs:
This tool allows you to encrypt and decrypt your personally dumped NDS and N3DS roms with minimal hassle. The only caveat right now is that you need a `keys.bin` file for your personally obtained encryption keys.
## Where do I find it?
For the most recent stable build, download the latest release here: [Releases Page](https://github.com/SabreTools/NDecrypt/releases)
For the latest WIP build here: [Rolling Release](https://github.com/SabreTools/NDecrypt/releases/tag/rolling)
## So how do I use this?
NDecrypt.exe <operation> [flags] <path> ...
NDecrypt.exe <operation> [flags] <path> ...
Possible values for <operation>:
e, encrypt - Encrypt the input files
d, decrypt - Decrypt the input files
Possible values for <operation>:
e, encrypt - Encrypt the input files
d, decrypt - Decrypt the input files
Possible values for [flags] (one or more can be used):
-c, --citra - Enable using aes_keys.txt instead of keys.bin
-dev, --development - Enable using development keys, if available
-f, --force - Force operation by avoiding sanity checks
-h, --hash - Output size and hashes to a companion file
-k, --keyfile <path> - Path to keys.bin or aes_keys.txt
<path> can be any file or folder that contains uncompressed items.
More than one path can be specified at a time.
Possible values for [flags] (one or more can be used):
-c, --citra - Enable using aes_keys.txt instead of keys.bin
-dev, --development - Enable using development keys, if available
-f, --force - Force operation by avoiding sanity checks
-h, --hash - Output size and hashes to a companion file
-k, --keyfile <path> - Path to keys.bin or aes_keys.txt
<path> can be any file or folder that contains uncompressed items.
More than one path can be specified at a time.
**Note:** This overwrites the input files, so make backups if you're working on your original, personal dumps.
@@ -74,4 +80,4 @@ I'd like to thank the developers of the original programs for doing the actual h
## Disclaimer
This program is **ONLY** for use with personally dumped files and keys and is not meant for enabling illegal activity. I do not condone using this program for anything other than personal use and research. If this program is used for anything other than that, I cannot be held liable for anything that happens.
This program is **ONLY** for use with personally dumped files and keys and is not meant for enabling illegal activity. I do not condone using this program for anything other than personal use and research. If this program is used for anything other than that, I cannot be held liable for anything that happens.

View File

@@ -8,10 +8,6 @@ pull_requests:
# vm template
image: Visual Studio 2022
# msbuild configuration
configuration:
- Debug
# install dependencies
install:
- cd %APPVEYOR_BUILD_FOLDER%
@@ -19,25 +15,7 @@ install:
# build step
build_script:
- cmd: dotnet restore
- cmd: dotnet build NDecrypt\NDecrypt.csproj --framework net48
- cmd: dotnet publish NDecrypt\NDecrypt.csproj --framework net6.0 --runtime win-x86 --self-contained true -p:PublishSingleFile=true
- cmd: dotnet publish NDecrypt\NDecrypt.csproj --framework net6.0 --runtime win-x64 --self-contained true -p:PublishSingleFile=true
- cmd: dotnet publish NDecrypt\NDecrypt.csproj --framework net6.0 --runtime linux-x64 --self-contained true -p:PublishSingleFile=true
- cmd: dotnet publish NDecrypt\NDecrypt.csproj --framework net6.0 --runtime osx-x64 --self-contained true -p:PublishSingleFile=true
# post-build script
after_build:
- cmd: cd %APPVEYOR_BUILD_FOLDER%\NDecrypt\bin\Debug\net48
- cmd: 7z a -tzip %APPVEYOR_BUILD_FOLDER%\NDecrypt-%APPVEYOR_REPO_COMMIT%_net48.zip *
- cmd: cd %APPVEYOR_BUILD_FOLDER%\NDecrypt\bin\Debug\net6.0\win-x86\publish\
- cmd: 7z a -tzip %APPVEYOR_BUILD_FOLDER%\NDecrypt-%APPVEYOR_REPO_COMMIT%_net6.0_win-x86.zip *
- cmd: cd %APPVEYOR_BUILD_FOLDER%\NDecrypt\bin\Debug\net6.0\win-x64\publish\
- cmd: 7z a -tzip %APPVEYOR_BUILD_FOLDER%\NDecrypt-%APPVEYOR_REPO_COMMIT%_net6.0_win-x64.zip *
- cmd: cd %APPVEYOR_BUILD_FOLDER%\NDecrypt\bin\Debug\net6.0\linux-x64\publish\
- cmd: 7z a -tzip %APPVEYOR_BUILD_FOLDER%\NDecrypt-%APPVEYOR_REPO_COMMIT%_net6.0_linux-x64.zip *
- cmd: cd %APPVEYOR_BUILD_FOLDER%\NDecrypt\bin\Debug\net6.0\osx-x64\publish\
- cmd: 7z a -tzip %APPVEYOR_BUILD_FOLDER%\NDecrypt-%APPVEYOR_REPO_COMMIT%_net6.0_osx-x64.zip *
- dotnet build
# success/failure tracking
on_success:
@@ -45,17 +23,4 @@ on_success:
- ps: ./send.ps1 success $env:WEBHOOK_URL
on_failure:
- ps: Invoke-RestMethod https://raw.githubusercontent.com/DiscordHooks/appveyor-discord-webhook/master/send.ps1 -o send.ps1
- ps: ./send.ps1 failure $env:WEBHOOK_URL
# artifact linking
artifacts:
- path: NDecrypt-%APPVEYOR_REPO_COMMIT%_net48.zip
name: NDecrypt (.NET Framework 4.8)
- path: NDecrypt-%APPVEYOR_REPO_COMMIT%_net6.0_win-x86.zip
name: NDecrypt (.NET 6.0, Windows x86)
- path: NDecrypt-%APPVEYOR_REPO_COMMIT%_net6.0_win-x64.zip
name: NDecrypt (.NET 6.0, Windows x64)
- path: NDecrypt-%APPVEYOR_REPO_COMMIT%_net6.0_linux-x64.zip
name: NDecrypt (.NET 6.0, Linux x64)
- path: NDecrypt-%APPVEYOR_REPO_COMMIT%_net6.0_osx-x64.zip
name: NDecrypt (.NET 6.0, OSX x64)
- ps: ./send.ps1 failure $env:WEBHOOK_URL

143
publish-nix.sh Normal file
View File

@@ -0,0 +1,143 @@
#!/bin/bash
# This batch file assumes the following:
# - .NET 8.0 (or newer) SDK is installed and in PATH
# - zip is installed and in PATH
# - Git is installed and in PATH
#
# If any of these are not satisfied, the operation may fail
# in an unpredictable way and result in an incomplete output.
# Optional parameters
USE_ALL=false
NO_BUILD=false
NO_ARCHIVE=false
while getopts "uba" OPTION; do
case $OPTION in
u)
USE_ALL=true
;;
b)
NO_BUILD=true
;;
a)
NO_ARCHIVE=true
;;
*)
echo "Invalid option provided"
exit 1
;;
esac
done
# Set the current directory as a variable
BUILD_FOLDER=$PWD
# Set the current commit hash
COMMIT=$(git log --pretty=%H -1)
# Output the selected options
echo "Selected Options:"
echo " Use all frameworks (-u) $USE_ALL"
echo " No build (-b) $NO_BUILD"
echo " No archive (-a) $NO_ARCHIVE"
echo " "
# Create the build matrix arrays
FRAMEWORKS=("net8.0")
RUNTIMES=("win-x86" "win-x64" "win-arm64" "linux-x64" "linux-arm64" "osx-x64" "osx-arm64")
# Use expanded lists, if requested
if [ $USE_ALL = true ]; then
FRAMEWORKS=("net40" "net452" "net462" "net472" "net48" "netcoreapp3.1" "net5.0" "net6.0" "net7.0" "net8.0")
fi
# Create the filter arrays
SINGLE_FILE_CAPABLE=("net5.0" "net6.0" "net7.0" "net8.0")
VALID_APPLE_FRAMEWORKS=("net6.0" "net7.0" "net8.0")
VALID_CROSS_PLATFORM_FRAMEWORKS=("netcoreapp3.1" "net5.0" "net6.0" "net7.0" "net8.0")
VALID_CROSS_PLATFORM_RUNTIMES=("win-arm64" "linux-x64" "linux-arm64" "osx-x64" "osx-arm64")
# Only build if requested
if [ $NO_BUILD = false ]; then
# Restore Nuget packages for all builds
echo "Restoring Nuget packages"
dotnet restore
# Build Program
for FRAMEWORK in "${FRAMEWORKS[@]}"; do
for RUNTIME in "${RUNTIMES[@]}"; do
# Output the current build
echo "===== Build Program - $FRAMEWORK, $RUNTIME ====="
# If we have an invalid combination of framework and runtime
if [[ ! $(echo ${VALID_CROSS_PLATFORM_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]; then
if [[ $(echo ${VALID_CROSS_PLATFORM_RUNTIMES[@]} | fgrep -w $RUNTIME) ]]; then
echo "Skipped due to invalid combination"
continue
fi
fi
# If we have Apple silicon but an unsupported framework
if [[ ! $(echo ${VALID_APPLE_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]; then
if [ $RUNTIME = "osx-arm64" ]; then
echo "Skipped due to no Apple Silicon support"
continue
fi
fi
# Only .NET 5 and above can publish to a single file
if [[ $(echo ${SINGLE_FILE_CAPABLE[@]} | fgrep -w $FRAMEWORK) ]]; then
# Only include Debug if building all
if [ $USE_ALL = true ]; then
dotnet publish NDecrypt/NDecrypt.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true
fi
dotnet publish NDecrypt/NDecrypt.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
else
# Only include Debug if building all
if [ $USE_ALL = true ]; then
dotnet publish NDecrypt/NDecrypt.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT
fi
dotnet publish NDecrypt/NDecrypt.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:DebugType=None -p:DebugSymbols=false
fi
done
done
fi
# Only create archives if requested
if [ $NO_ARCHIVE = false ]; then
# Create Test archives
for FRAMEWORK in "${FRAMEWORKS[@]}"; do
for RUNTIME in "${RUNTIMES[@]}"; do
# Output the current build
echo "===== Archive Program - $FRAMEWORK, $RUNTIME ====="
# If we have an invalid combination of framework and runtime
if [[ ! $(echo ${VALID_CROSS_PLATFORM_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]; then
if [[ $(echo ${VALID_CROSS_PLATFORM_RUNTIMES[@]} | fgrep -w $RUNTIME) ]]; then
echo "Skipped due to invalid combination"
continue
fi
fi
# If we have Apple silicon but an unsupported framework
if [[ ! $(echo ${VALID_APPLE_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]; then
if [ $RUNTIME = "osx-arm64" ]; then
echo "Skipped due to no Apple Silicon support"
continue
fi
fi
# Only include Debug if building all
if [ $USE_ALL = true ]; then
cd $BUILD_FOLDER/NDecrypt/bin/Debug/${FRAMEWORK}/${RUNTIME}/publish/
zip -r $BUILD_FOLDER/NDecrypt_${FRAMEWORK}_${RUNTIME}_debug.zip .
fi
cd $BUILD_FOLDER/NDecrypt/bin/Release/${FRAMEWORK}/${RUNTIME}/publish/
zip -r $BUILD_FOLDER/NDecrypt_${FRAMEWORK}_${RUNTIME}_release.zip .
done
done
# Reset the directory
cd $BUILD_FOLDER
fi

128
publish-win.ps1 Normal file
View File

@@ -0,0 +1,128 @@
# This batch file assumes the following:
# - .NET 8.0 (or newer) SDK is installed and in PATH
# - 7-zip commandline (7z.exe) is installed and in PATH
# - Git for Windows is installed and in PATH
#
# If any of these are not satisfied, the operation may fail
# in an unpredictable way and result in an incomplete output.
# Optional parameters
param(
[Parameter(Mandatory = $false)]
[Alias("UseAll")]
[switch]$USE_ALL,
[Parameter(Mandatory = $false)]
[Alias("NoBuild")]
[switch]$NO_BUILD,
[Parameter(Mandatory = $false)]
[Alias("NoArchive")]
[switch]$NO_ARCHIVE
)
# Set the current directory as a variable
$BUILD_FOLDER = $PSScriptRoot
# Set the current commit hash
$COMMIT = git log --pretty=format:"%H" -1
# Output the selected options
Write-Host "Selected Options:"
Write-Host " Use all frameworks (-UseAll) $USE_ALL"
Write-Host " No build (-NoBuild) $NO_BUILD"
Write-Host " No archive (-NoArchive) $NO_ARCHIVE"
Write-Host " "
# Create the build matrix arrays
$FRAMEWORKS = @('net8.0')
$RUNTIMES = @('win-x86', 'win-x64', 'win-arm64', 'linux-x64', 'linux-arm64', 'osx-x64', 'osx-arm64')
# Use expanded lists, if requested
if ($USE_ALL.IsPresent) {
$FRAMEWORKS = @('net40', 'net452', 'net462', 'net472', 'net48', 'netcoreapp3.1', 'net5.0', 'net6.0', 'net7.0', 'net8.0')
}
# Create the filter arrays
$SINGLE_FILE_CAPABLE = @('net5.0', 'net6.0', 'net7.0', 'net8.0')
$VALID_APPLE_FRAMEWORKS = @('net6.0', 'net7.0', 'net8.0')
$VALID_CROSS_PLATFORM_FRAMEWORKS = @('netcoreapp3.1', 'net5.0', 'net6.0', 'net7.0', 'net8.0')
$VALID_CROSS_PLATFORM_RUNTIMES = @('win-arm64', 'linux-x64', 'linux-arm64', 'osx-x64', 'osx-arm64')
# Only build if requested
if (!$NO_BUILD.IsPresent) {
# Restore Nuget packages for all builds
Write-Host "Restoring Nuget packages"
dotnet restore
# Build Program
foreach ($FRAMEWORK in $FRAMEWORKS) {
foreach ($RUNTIME in $RUNTIMES) {
# Output the current build
Write-Host "===== Build Program - $FRAMEWORK, $RUNTIME ====="
# If we have an invalid combination of framework and runtime
if ($VALID_CROSS_PLATFORM_FRAMEWORKS -notcontains $FRAMEWORK -and $VALID_CROSS_PLATFORM_RUNTIMES -contains $RUNTIME) {
Write-Host "Skipped due to invalid combination"
continue
}
# If we have Apple silicon but an unsupported framework
if ($VALID_APPLE_FRAMEWORKS -notcontains $FRAMEWORK -and $RUNTIME -eq 'osx-arm64') {
Write-Host "Skipped due to no Apple Silicon support"
continue
}
# Only .NET 5 and above can publish to a single file
if ($SINGLE_FILE_CAPABLE -contains $FRAMEWORK) {
# Only include Debug if building all
if ($USE_ALL.IsPresent) {
dotnet publish NDecrypt\NDecrypt.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true
}
dotnet publish NDecrypt\NDecrypt.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
}
else {
# Only include Debug if building all
if ($USE_ALL.IsPresent) {
dotnet publish NDecrypt\NDecrypt.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT
}
dotnet publish NDecrypt\NDecrypt.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:DebugType=None -p:DebugSymbols=false
}
}
}
}
# Only create archives if requested
if (!$NO_ARCHIVE.IsPresent) {
# Create Program archives
foreach ($FRAMEWORK in $FRAMEWORKS) {
foreach ($RUNTIME in $RUNTIMES) {
# Output the current build
Write-Host "===== Archive Program - $FRAMEWORK, $RUNTIME ====="
# If we have an invalid combination of framework and runtime
if ($VALID_CROSS_PLATFORM_FRAMEWORKS -notcontains $FRAMEWORK -and $VALID_CROSS_PLATFORM_RUNTIMES -contains $RUNTIME) {
Write-Host "Skipped due to invalid combination"
continue
}
# If we have Apple silicon but an unsupported framework
if ($VALID_APPLE_FRAMEWORKS -notcontains $FRAMEWORK -and $RUNTIME -eq 'osx-arm64') {
Write-Host "Skipped due to no Apple Silicon support"
continue
}
# Only include Debug if building all
if ($USE_ALL.IsPresent) {
Set-Location -Path $BUILD_FOLDER\Test\bin\Debug\${FRAMEWORK}\${RUNTIME}\publish\
7z a -tzip $BUILD_FOLDER\NDecrypt_${FRAMEWORK}_${RUNTIME}_debug.zip *
}
Set-Location -Path $BUILD_FOLDER\Test\bin\Release\${FRAMEWORK}\${RUNTIME}\publish\
7z a -tzip $BUILD_FOLDER\NDecrypt_${FRAMEWORK}_${RUNTIME}_release.zip *
}
}
# Reset the directory
Set-Location -Path $PSScriptRoot
}